current commit
261007.21 · deps: update V8 to 15.5.35.20
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_OBJECTS_JS_REGEXP_H_
#define V8_OBJECTS_JS_REGEXP_H_

#include <optional>
#include <utility>

#include "include/v8-regexp.h"
#include "src/base/bit-field.h"
#include "src/base/vector.h"
#include "src/objects/contexts.h"
#include "src/objects/js-array.h"
#include "src/objects/trusted-object.h"
#include "src/regexp/regexp-flags.h"

// Has to be the last include (doesn't have include guards):
#include "src/objects/object-macros.h"

namespace v8::internal {

class RegExpData;

class RegExpData;

// Regular expressions
V8_OBJECT class JSRegExp : public JSObject {
 public:
  using GlobalBit = base::BitField<bool, 0, 1, uint32_t>;
  using IgnoreCaseBit = GlobalBit::Next<bool, 1>;
  using MultilineBit = IgnoreCaseBit::Next<bool, 1>;
  using StickyBit = MultilineBit::Next<bool, 1>;
  using UnicodeBit = StickyBit::Next<bool, 1>;
  using DotAllBit = UnicodeBit::Next<bool, 1>;
  using LinearBit = DotAllBit::Next<bool, 1>;
  using HasIndicesBit = LinearBit::Next<bool, 1>;
  using UnicodeSetsBit = HasIndicesBit::Next<bool, 1>;
  enum Flag : uint32_t {
    kNone = 0,
    kGlobal = GlobalBit::kMask,
    kIgnoreCase = IgnoreCaseBit::kMask,
    kMultiline = MultilineBit::kMask,
    kSticky = StickyBit::kMask,
    kUnicode = UnicodeBit::kMask,
    kDotAll = DotAllBit::kMask,
    kLinear = LinearBit::kMask,
    kHasIndices = HasIndicesBit::kMask,
    kUnicodeSets = UnicodeSetsBit::kMask,
  };
  using Flags = base::Flags<Flag>;
  static constexpr int kFlagCount = 9;

  V8_EXPORT_PRIVATE static MaybeDirectHandle<JSRegExp> New(
      Isolate* isolate, DirectHandle<String> original_source, Flags flags,
      uint32_t backtrack_limit = kNoBacktrackLimit);

  static MaybeDirectHandle<JSRegExp> Initialize(
      Isolate* isolate, DirectHandle<JSRegExp> regexp,
      DirectHandle<String> original_source, Flags flags,
      uint32_t backtrack_limit = kNoBacktrackLimit);
  static MaybeDirectHandle<JSRegExp> Initialize(
      Isolate* isolate, DirectHandle<JSRegExp> regexp,
      DirectHandle<String> original_source, DirectHandle<String> flags_string);

  DECL_ACCESSORS(last_index, Tagged<Object>)

  // Instance fields accessors.
  // Returns the escaped source as specced by
  // https://tc39.es/ecma262/#sec-escaperegexppattern.
  inline Tagged<String> source(IsolateForSandbox isolate) const;
  inline Flags flags() const;
  inline void set_flags(Tagged<Object> value,
                        WriteBarrierMode mode = UPDATE_WRITE_BARRIER);

  DECL_TRUSTED_POINTER_ACCESSORS(data, RegExpData)

  static constexpr Flag AsJSRegExpFlag(regexp::Flag f) {
    return static_cast<Flag>(f);
  }
  static constexpr Flags AsJSRegExpFlags(regexp::Flags f) {
    return Flags{static_cast<int>(f)};
  }
  static constexpr regexp::Flags AsRegExpFlags(Flags f) {
    return regexp::Flags{static_cast<int>(f)};
  }

  static std::optional<regexp::Flag> FlagFromChar(char c) {
    std::optional<regexp::Flag> f = regexp::TryFlagFromChar(c);
    if (!f.has_value()) return f;
    if (f.value() == regexp::Flag::kLinear &&
        !v8_flags.enable_experimental_regexp_engine) {
      return {};
    }
    return f;
  }

  static_assert(static_cast<int>(kNone) == v8::RegExp::kNone);
#define V(_, Camel, ...)                                                   \
  static_assert(static_cast<int>(Flag::k##Camel) == v8::RegExp::k##Camel); \
  static_assert(static_cast<int>(Flag::k##Camel) ==                        \
                static_cast<int>(regexp::Flag::k##Camel));
  REGEXP_FLAG_LIST(V)
#undef V
  static_assert(kFlagCount == v8::RegExp::kFlagCount);
  static_assert(kFlagCount == regexp::kFlagCount);

  static std::optional<Flags> FlagsFromString(Isolate* isolate,
                                              DirectHandle<String> flags);

  V8_EXPORT_PRIVATE static DirectHandle<String> StringFromFlags(
      Isolate* isolate, Flags flags);

  // Each capture (including the match itself) needs two registers.
  static constexpr int RegistersForCaptureCount(int count) {
    return (count + 1) * 2;
  }
  static constexpr int CaptureCountForRegisters(int register_count) {
    DCHECK_EQ(register_count % 2, 0);
    DCHECK_GE(register_count, 2);
    return (register_count - 2) / 2;
  }
  // ATOM regexps don't have captures.
  static constexpr int kAtomCaptureCount = 0;
  static constexpr int kAtomRegisterCount = 2;

  // Dispatched behavior.
  DECL_PRINTER(JSRegExp)
  DECL_VERIFIER(JSRegExp)

  /* This is already an in-object field. */
  // TODO(v8:8944): improve handling of in-object fields
  static const int kLastIndexOffset;
  static constexpr int kInObjectFieldCount = 1;

  // The initial value of the last_index field on a new JSRegExp instance.
  static constexpr int kInitialLastIndexValue = 0;

  // The actual object size including in-object fields.
  static const int kSize;
  static int Size() { return kSize; }

  // Descriptor array index to important methods in the prototype.
  static constexpr int kExecFunctionDescriptorIndex = 1;
  static constexpr int kSymbolMatchFunctionDescriptorIndex = 15;
  static constexpr int kSymbolMatchAllFunctionDescriptorIndex = 16;
  static constexpr int kSymbolReplaceFunctionDescriptorIndex = 17;
  static constexpr int kSymbolSearchFunctionDescriptorIndex = 18;
  static constexpr int kSymbolSplitFunctionDescriptorIndex = 19;

  // The uninitialized value for a regexp code object.
  static constexpr int kUninitializedValue = -1;

  // If the backtrack limit is set to this marker value, no limit is applied.
  static constexpr uint32_t kNoBacktrackLimit = 0;

  // The heuristic value for the length of the subject string for which we
  // tier-up to the compiler immediately, instead of using the interpreter.
  static constexpr int kTierUpForSubjectLengthValue = 1000;

  // Maximum number of captures allowed.
  static constexpr int kMaxCaptures = 1 << 16;

  using FlagsBuffer = base::EmbeddedVector<char, kFlagCount + 1>;
  inline static const char* FlagsToString(Flags flags, FlagsBuffer* out_buffer);

  class BodyDescriptor;

  static const int kHeaderSize;

 private:
  friend class RegExpData;
  friend class TorqueGeneratedJSRegExpAsserts;

 public:
  TrustedPointerMember<RegExpData, kRegExpDataIndirectPointerTag> data_;
  TaggedMember<Object> flags_;
} V8_OBJECT_END;

inline constexpr int JSRegExp::kHeaderSize = sizeof(JSRegExp);
inline constexpr int JSRegExp::kLastIndexOffset = kHeaderSize;
inline constexpr int JSRegExp::kSize =
    kHeaderSize + kInObjectFieldCount * kTaggedSize;

DEFINE_OPERATORS_FOR_FLAGS(JSRegExp::Flags)

class RegExpDataWrapper;

V8_OBJECT class RegExpData : public ExposedTrustedObject {
  V8_IT_OWN_TYPE;

 public:
  enum class Type : uint8_t {
    ATOM,          // A simple string match.
    IRREGEXP,      // Compiled with Irregexp (code or bytecode).
    EXPERIMENTAL,  // Compiled to use the experimental linear time engine.
  };

  // The first-character bitset covers the latin-1 range, one bit per character.
  static constexpr int kQuickCheckBitsetChars = 256;
  static constexpr int kQuickCheckBitsetBitsPerWord = 32;
  static constexpr int kQuickCheckBitsetWords =
      kQuickCheckBitsetChars / kQuickCheckBitsetBitsPerWord;

  // Where |c| lives in the bitset: which word to load, and which bit to test
  // in it.  For 'a' (97) that is word 3, bit 1.
  static constexpr std::pair<int, uint32_t> QuickCheckBitsetBit(uint8_t c) {
    return {c / kQuickCheckBitsetBitsPerWord,
            uint32_t{1} << (c % kQuickCheckBitsetBitsPerWord)};
  }

  // Flags not exposed to JS, stored separately from flags_ above.
  // TODO(jgruber): Merge these into flags_, which has spare bits.
  enum InternalFlag : uint32_t {
    // Set if either quick-check filter was built.  Most regexps get neither,
    // so this lets the exec path skip straight past both with one test
    // instead of loading the subject and running the bitset check to find out.
    kHasQuickCheck = 1 << 0,
  };

  inline Type type_tag() const;
  inline void set_type_tag(Type);

  inline Tagged<String> original_source() const;
  inline void set_original_source(Tagged<String> value,
                                  WriteBarrierMode mode = UPDATE_WRITE_BARRIER);

  inline Tagged<String> escaped_source() const;
  inline void set_escaped_source(Tagged<String> value,
                                 WriteBarrierMode mode = UPDATE_WRITE_BARRIER);

  inline JSRegExp::Flags flags() const;
  inline void set_flags(JSRegExp::Flags flags);

  inline Tagged<RegExpDataWrapper> wrapper() const;
  inline void set_wrapper(Tagged<RegExpDataWrapper> value,
                          WriteBarrierMode mode = UPDATE_WRITE_BARRIER);

  inline uint32_t quick_check_mask() const;
  inline void set_quick_check_mask(uint32_t value);

  inline uint32_t quick_check_value() const;
  inline void set_quick_check_value(uint32_t value);

  inline void set_quick_check_reject_bitset_word(int index, uint32_t value);

  inline uint32_t internal_flags() const;
  inline void set_internal_flags(uint32_t value);

  inline void clear_quick_check();

  // True iff the quick-check filters prove that no match can begin at
  // |subject[index]|.  |subject| must be one-byte and flat.  Mirrored by
  // RegExpExecInternal in builtins-regexp-gen.cc; keep the two in sync.
  bool QuickCheckRejects(base::Vector<const uint8_t> subject, int index) const;

  inline int capture_count() const;

  static constexpr bool TypeSupportsCaptures(Type t) {
    return t == Type::IRREGEXP || t == Type::EXPERIMENTAL;
  }

  V8_EXPORT_PRIVATE bool HasCompiledCode() const;

  DECL_PRINTER(RegExpData)
  DECL_VERIFIER(RegExpData)

  class BodyDescriptor;

  TaggedMember<Smi> type_tag_;
  TaggedMember<String> original_source_;
  TaggedMember<String> escaped_source_;
  TaggedMember<Smi> flags_;
  TaggedMember<RegExpDataWrapper> wrapper_;
  uint32_t quick_check_mask_;
  uint32_t quick_check_value_;
  // One bit per latin-1 character.  Set means no match can begin with that
  // character; clear means "maybe".
  //
  // TODO(jgruber): Move the quick-check fields down to IrRegExpData.  Only
  // Irregexp ever fills them in, so ATOM data pays 40 bytes for nothing.
  // EXPERIMENTAL data is already an IrRegExpData, but shares the exec path's
  // check with IRREGEXP and does not fill the fields in either, so the fields
  // stay dead there whichever class they live on.
  uint32_t quick_check_reject_bitset_[kQuickCheckBitsetWords];
  uint32_t internal_flags_;
#if TAGGED_SIZE_8_BYTES
  uint32_t optional_padding_;
#endif
} V8_OBJECT_END;

V8_OBJECT class RegExpDataWrapper : public Struct {
 public:
  DECL_TRUSTED_POINTER_ACCESSORS(data, RegExpData)

  DECL_PRINTER(RegExpDataWrapper)
  DECL_VERIFIER(RegExpDataWrapper)

  class BodyDescriptor;

  TrustedPointerMember<RegExpData, kRegExpDataIndirectPointerTag> data_;
} V8_OBJECT_END;

V8_OBJECT class AtomRegExpData : public RegExpData {
 public:
  inline Tagged<String> pattern() const;
  inline void set_pattern(Tagged<String> value,
                          WriteBarrierMode mode = UPDATE_WRITE_BARRIER);

  DECL_PRINTER(AtomRegExpData)
  DECL_VERIFIER(AtomRegExpData)

  class BodyDescriptor;

  TaggedMember<String> pattern_;
} V8_OBJECT_END;

V8_OBJECT class IrRegExpData : public RegExpData {
 public:
  DECL_CODE_POINTER_ACCESSORS(latin1_code)
  DECL_CODE_POINTER_ACCESSORS(uc16_code)
  inline bool has_code(bool is_one_byte) const;
  inline void set_code(bool is_one_byte, Tagged<Code> code);
  inline Tagged<Code> code(IsolateForSandbox isolate, bool is_one_byte) const;
  DECL_PROTECTED_POINTER_ACCESSORS(latin1_bytecode, TrustedByteArray)
  DECL_PROTECTED_POINTER_ACCESSORS(uc16_bytecode, TrustedByteArray)
  DECL_PROTECTED_POINTER_ACCESSORS(capture_name_map, TrustedFixedArray)
  inline void set_capture_name_map(DirectHandle<TrustedFixedArray> value);
  inline bool has_bytecode(bool is_one_byte) const;
  inline void clear_bytecode(bool is_one_byte);
  inline void set_bytecode(bool is_one_byte, Tagged<TrustedByteArray> bytecode);
  inline Tagged<TrustedByteArray> bytecode(bool is_one_byte) const;
  inline int max_register_count() const;
  inline void set_max_register_count(int value);
  // Number of captures (without the match itself).
  inline int capture_count() const;
  inline void set_capture_count(int value);
  inline int ticks_until_tier_up() const;
  inline void set_ticks_until_tier_up(int value);
  inline int backtrack_limit() const;
  inline void set_backtrack_limit(int value);

  inline uint32_t bit_field() const;
  inline void set_bit_field(uint32_t value);
  DECL_BOOLEAN_ACCESSORS(can_be_zero_length)
  DECL_BOOLEAN_ACCESSORS(is_linear_executable)

  struct Bits {
    using CanBeZeroLengthBit = base::BitField<bool, 0, 1, uint32_t>;
    using IsLinearExecutableBit = CanBeZeroLengthBit::Next<bool, 1>;
  };

  bool CanTierUp();
  bool MarkedForTierUp();
  void ResetLastTierUpTick();
  void TierUpTick();
  void MarkTierUpForNextExec();
  bool ShouldProduceBytecode();

  void DiscardCompiledCodeForSerialization();

  // Sets the bytecode as well as initializing trampoline slots to the
  // RegExpExperimentalTrampoline.
  void SetBytecodeForExperimental(Isolate* isolate,
                                  Tagged<TrustedByteArray> bytecode);

  DECL_PRINTER(IrRegExpData)
  DECL_VERIFIER(IrRegExpData)

  class BodyDescriptor;

  ProtectedTaggedMember<TrustedByteArray> latin1_bytecode_;
  ProtectedTaggedMember<TrustedByteArray> uc16_bytecode_;
  ProtectedTaggedMember<TrustedFixedArray> capture_name_map_;
  CodePointerMember latin1_code_;
  CodePointerMember uc16_code_;
  TaggedMember<Smi> max_register_count_;
  TaggedMember<Smi> capture_count_;
  TaggedMember<Smi> ticks_until_tier_up_;
  TaggedMember<Smi> backtrack_limit_;
  TaggedMember<Smi> bit_field_;
} V8_OBJECT_END;

// JSRegExpResult is just a JSArray with a specific initial map.
// This initial map adds in-object properties for "index" and "input"
// properties, as assigned by RegExp.prototype.exec, which allows
// faster creation of RegExp exec results.
// This class just holds constants used when creating the result.
// After creation the result must be treated as a JSArray in all regards.
// JSRegExpResult is a JSArray with in-object properties for exec() results.
// Shape-style class: no own C++ storage. Fields live in the parent JSArray's
// in-object property slots at fixed offsets past JSArray::kHeaderSize. Matches
// the layout that legacy Torque-generated for the `extern shape` declaration
// in js-regexp.tq: `kHeaderSize` is inherited from JSArray (no new header),
// and `kInObjectPropertyCount` is the total count of in-object properties
// relative to JSArray::kHeaderSize.
V8_OBJECT class JSRegExpResult : public JSArray {
  V8_IT_REUSE_PARENT;

 public:
  // TODO(joshualitt): We would like to add printers and verifiers to
  // JSRegExpResult, and maybe JSRegExpResultIndices, but both have the same
  // instance type as JSArray.

  // Slot indices of the in-object properties, relative to JSArray::kHeaderSize.
  static constexpr int kIndexSlotIndex = 0;
  static constexpr int kInputSlotIndex = 1;
  static constexpr int kGroupsSlotIndex = 2;
  static constexpr int kRegexpInputSlotIndex = 3;
  static constexpr int kRegexpLastIndexSlotIndex = 4;
  static constexpr int kInObjectPropertyCount = 5;

  static constexpr int kIndexOffset =
      JSArray::kHeaderSize + kIndexSlotIndex * kTaggedSize;
  static constexpr int kInputOffset =
      JSArray::kHeaderSize + kInputSlotIndex * kTaggedSize;
  static constexpr int kGroupsOffset =
      JSArray::kHeaderSize + kGroupsSlotIndex * kTaggedSize;
  static constexpr int kRegexpInputOffset =
      JSArray::kHeaderSize + kRegexpInputSlotIndex * kTaggedSize;
  static constexpr int kRegexpLastIndexOffset =
      JSArray::kHeaderSize + kRegexpLastIndexSlotIndex * kTaggedSize;
  static constexpr int kSize =
      JSArray::kHeaderSize + kInObjectPropertyCount * kTaggedSize;

  static constexpr int kMapIndexInContext = Context::REGEXP_RESULT_MAP_INDEX;
} V8_OBJECT_END;

V8_OBJECT class JSRegExpResultWithIndices : public JSRegExpResult {
  V8_IT_REUSE_PARENT;

 public:
  // Additional in-object property past JSRegExpResult's fields.
  static constexpr int kAdditionalInObjectPropertyCount = 1;
  static constexpr int kIndicesOffset = JSRegExpResult::kSize;
  static constexpr int kSize =
      JSRegExpResult::kSize + kAdditionalInObjectPropertyCount * kTaggedSize;
  // Total in-object property count relative to JSArray::kHeaderSize.
  static constexpr int kInObjectPropertyCount =
      JSRegExpResult::kInObjectPropertyCount + kAdditionalInObjectPropertyCount;
} V8_OBJECT_END;

// JSRegExpResultIndices is just a JSArray with a specific initial map.
// This initial map adds in-object properties for "group"
// properties, as assigned by RegExp.prototype.exec, which allows
// faster creation of RegExp exec results.
// This class just holds constants used when creating the result.
// After creation the result must be treated as a JSArray in all regards.
V8_OBJECT class JSRegExpResultIndices : public JSArray {
  V8_IT_REUSE_PARENT;

 public:
  static DirectHandle<JSRegExpResultIndices> BuildIndices(
      Isolate* isolate, DirectHandle<RegExpMatchInfo> match_info,
      DirectHandle<RegExpData> re_data);

  static constexpr int kGroupsSlotIndex = 0;
  static constexpr int kInObjectPropertyCount = 1;

  static constexpr int kGroupsOffset =
      JSArray::kHeaderSize + kGroupsSlotIndex * kTaggedSize;
  static constexpr int kSize =
      JSArray::kHeaderSize + kInObjectPropertyCount * kTaggedSize;

  // Descriptor index of groups.
  static constexpr int kGroupsDescriptorIndex = 1;
} V8_OBJECT_END;

V8_OBJECT class JSRegExpConstructor : public JSFunctionWithPrototype {
} V8_OBJECT_END;

}  // namespace v8::internal

#include "src/objects/object-macros-undef.h"

#endif  // V8_OBJECTS_JS_REGEXP_H_