f31b7928ae
See https://github.com/tc39/proposal-regexp-lookbehind for a high-level description of the feature and examples. This is one of the features requested in https://github.com/dart-lang/sdk/issues/34935. This work takes the feature as present in the v8 engine and appropriately merges it into our irregexp fork. Notable changes to the irregexp codebase to introduce this feature: ----- We can no longer assume that all matching proceeds forwards, since lookbehind matching proceeds backwards. Similarly, we cannot assume that we can only be at the start of a string if we started matching from that point. The direction of matching must also be taken into consideration when doing bounds checking, which previously assumed the engine would never attempt to look before the start of a string. ----- We may now parse backreferences to captures before the capture they reference, since we parse regular expressions left to right, but lookbehinds perform captures as they evaluate the string from right to left. Since RegExpBackReference objects contain a pointer to their corresponding capture, this means that we may need to create RegExpCapture objects prior to the parsing of the corresponding captured subexpression. Thus, RegExpCapture objects are now only initialized with their index, and the body is set later when the subexpression is encountered and parsed. This means any method that operates on the body of a RegExpCapture can no longer be const, which also affects the rest of the RegExpTree class hierarchy. This also means that we don't have a valid max_match length for backreferences based off the capture body, and must assume they can end up being any length. ----- Change-Id: Iffe0e71b17b1a0c6fea77235e8aee5c093005811 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/94540 Commit-Queue: Stevie Strickland <sstrickl@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
240 lines
9.7 KiB
C++
240 lines
9.7 KiB
C++
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
#ifndef RUNTIME_VM_REGEXP_ASSEMBLER_H_
|
|
#define RUNTIME_VM_REGEXP_ASSEMBLER_H_
|
|
|
|
#include "vm/compiler/assembler/assembler.h"
|
|
#include "vm/compiler/backend/il.h"
|
|
#include "vm/object.h"
|
|
|
|
namespace dart {
|
|
|
|
// Utility function for the DotPrinter
|
|
void PrintUtf16(uint16_t c);
|
|
|
|
/// Convenience wrapper around a BlockEntryInstr pointer.
|
|
class BlockLabel : public ValueObject {
|
|
// Used by the IR assembler.
|
|
public:
|
|
BlockLabel();
|
|
|
|
JoinEntryInstr* block() const { return block_; }
|
|
|
|
bool IsBound() const { return is_bound_; }
|
|
void SetBound(intptr_t block_id) {
|
|
ASSERT(!is_bound_);
|
|
block_->set_block_id(block_id);
|
|
is_bound_ = true;
|
|
}
|
|
|
|
bool IsLinked() const { return !is_bound_ && is_linked_; }
|
|
void SetLinked() { is_linked_ = true; }
|
|
|
|
intptr_t Position() const {
|
|
ASSERT(IsBound());
|
|
return block_->block_id();
|
|
}
|
|
|
|
private:
|
|
JoinEntryInstr* block_;
|
|
|
|
bool is_bound_;
|
|
bool is_linked_;
|
|
|
|
// Used by the bytecode assembler.
|
|
public:
|
|
~BlockLabel() { ASSERT(!is_linked()); }
|
|
|
|
intptr_t pos() const { return pos_; }
|
|
bool is_bound() const { return IsBound(); }
|
|
bool is_linked() const { return IsLinked(); }
|
|
|
|
void Unuse() {
|
|
pos_ = 0;
|
|
is_bound_ = false;
|
|
is_linked_ = false;
|
|
}
|
|
|
|
void bind_to(intptr_t pos) {
|
|
pos_ = pos;
|
|
is_bound_ = true;
|
|
is_linked_ = false;
|
|
ASSERT(is_bound());
|
|
}
|
|
|
|
void link_to(intptr_t pos) {
|
|
pos_ = pos;
|
|
is_bound_ = false;
|
|
is_linked_ = true;
|
|
ASSERT(is_linked());
|
|
}
|
|
|
|
private:
|
|
intptr_t pos_;
|
|
};
|
|
|
|
class RegExpMacroAssembler : public ZoneAllocated {
|
|
public:
|
|
// The implementation must be able to handle at least:
|
|
static const intptr_t kMaxRegister = (1 << 16) - 1;
|
|
static const intptr_t kMaxCPOffset = (1 << 15) - 1;
|
|
static const intptr_t kMinCPOffset = -(1 << 15);
|
|
|
|
static const intptr_t kTableSizeBits = 7;
|
|
static const intptr_t kTableSize = 1 << kTableSizeBits;
|
|
static const intptr_t kTableMask = kTableSize - 1;
|
|
|
|
enum {
|
|
kParamRegExpIndex = 0,
|
|
kParamStringIndex,
|
|
kParamStartOffsetIndex,
|
|
kParamCount
|
|
};
|
|
|
|
enum IrregexpImplementation { kBytecodeImplementation, kIRImplementation };
|
|
|
|
explicit RegExpMacroAssembler(Zone* zone);
|
|
virtual ~RegExpMacroAssembler();
|
|
// The maximal number of pushes between stack checks. Users must supply
|
|
// kCheckStackLimit flag to push operations (instead of kNoStackLimitCheck)
|
|
// at least once for every stack_limit() pushes that are executed.
|
|
virtual intptr_t stack_limit_slack() = 0;
|
|
virtual bool CanReadUnaligned() = 0;
|
|
virtual void AdvanceCurrentPosition(intptr_t by) = 0; // Signed cp change.
|
|
virtual void AdvanceRegister(intptr_t reg, intptr_t by) = 0; // r[reg] += by.
|
|
// Continues execution from the position pushed on the top of the backtrack
|
|
// stack by an earlier PushBacktrack(BlockLabel*).
|
|
virtual void Backtrack() = 0;
|
|
virtual void BindBlock(BlockLabel* label) = 0;
|
|
virtual void CheckAtStart(BlockLabel* on_at_start) = 0;
|
|
// Dispatch after looking the current character up in a 2-bits-per-entry
|
|
// map. The destinations vector has up to 4 labels.
|
|
virtual void CheckCharacter(unsigned c, BlockLabel* on_equal) = 0;
|
|
// Bitwise and the current character with the given constant and then
|
|
// check for a match with c.
|
|
virtual void CheckCharacterAfterAnd(unsigned c,
|
|
unsigned and_with,
|
|
BlockLabel* on_equal) = 0;
|
|
virtual void CheckCharacterGT(uint16_t limit, BlockLabel* on_greater) = 0;
|
|
virtual void CheckCharacterLT(uint16_t limit, BlockLabel* on_less) = 0;
|
|
virtual void CheckGreedyLoop(BlockLabel* on_tos_equals_current_position) = 0;
|
|
virtual void CheckNotAtStart(intptr_t cp_offset,
|
|
BlockLabel* on_not_at_start) = 0;
|
|
virtual void CheckNotBackReference(intptr_t start_reg,
|
|
bool read_backward,
|
|
BlockLabel* on_no_match) = 0;
|
|
virtual void CheckNotBackReferenceIgnoreCase(intptr_t start_reg,
|
|
bool read_backward,
|
|
BlockLabel* on_no_match) = 0;
|
|
// Check the current character for a match with a literal character. If we
|
|
// fail to match then goto the on_failure label. End of input always
|
|
// matches. If the label is NULL then we should pop a backtrack address off
|
|
// the stack and go to that.
|
|
virtual void CheckNotCharacter(unsigned c, BlockLabel* on_not_equal) = 0;
|
|
virtual void CheckNotCharacterAfterAnd(unsigned c,
|
|
unsigned and_with,
|
|
BlockLabel* on_not_equal) = 0;
|
|
// Subtract a constant from the current character, then and with the given
|
|
// constant and then check for a match with c.
|
|
virtual void CheckNotCharacterAfterMinusAnd(uint16_t c,
|
|
uint16_t minus,
|
|
uint16_t and_with,
|
|
BlockLabel* on_not_equal) = 0;
|
|
virtual void CheckCharacterInRange(uint16_t from,
|
|
uint16_t to, // Both inclusive.
|
|
BlockLabel* on_in_range) = 0;
|
|
virtual void CheckCharacterNotInRange(uint16_t from,
|
|
uint16_t to, // Both inclusive.
|
|
BlockLabel* on_not_in_range) = 0;
|
|
|
|
// The current character (modulus the kTableSize) is looked up in the byte
|
|
// array, and if the found byte is non-zero, we jump to the on_bit_set label.
|
|
virtual void CheckBitInTable(const TypedData& table,
|
|
BlockLabel* on_bit_set) = 0;
|
|
|
|
// Checks for preemption and serves as an OSR entry.
|
|
virtual void CheckPreemption(bool is_backtrack) {}
|
|
|
|
// Checks whether the given offset from the current position is before
|
|
// the end of the string. May overwrite the current character.
|
|
virtual void CheckPosition(intptr_t cp_offset, BlockLabel* on_outside_input) {
|
|
LoadCurrentCharacter(cp_offset, on_outside_input, true);
|
|
}
|
|
// Check whether a standard/default character class matches the current
|
|
// character. Returns false if the type of special character class does
|
|
// not have custom support.
|
|
// May clobber the current loaded character.
|
|
virtual bool CheckSpecialCharacterClass(uint16_t type,
|
|
BlockLabel* on_no_match) {
|
|
return false;
|
|
}
|
|
virtual void Fail() = 0;
|
|
// Check whether a register is >= a given constant and go to a label if it
|
|
// is. Backtracks instead if the label is NULL.
|
|
virtual void IfRegisterGE(intptr_t reg,
|
|
intptr_t comparand,
|
|
BlockLabel* if_ge) = 0;
|
|
// Check whether a register is < a given constant and go to a label if it is.
|
|
// Backtracks instead if the label is NULL.
|
|
virtual void IfRegisterLT(intptr_t reg,
|
|
intptr_t comparand,
|
|
BlockLabel* if_lt) = 0;
|
|
// Check whether a register is == to the current position and go to a
|
|
// label if it is.
|
|
virtual void IfRegisterEqPos(intptr_t reg, BlockLabel* if_eq) = 0;
|
|
virtual IrregexpImplementation Implementation() = 0;
|
|
// The assembler is closed, iff there is no current instruction assigned.
|
|
virtual bool IsClosed() const = 0;
|
|
// Jump to the target label without setting it as the current instruction.
|
|
virtual void GoTo(BlockLabel* to) = 0;
|
|
virtual void LoadCurrentCharacter(intptr_t cp_offset,
|
|
BlockLabel* on_end_of_input,
|
|
bool check_bounds = true,
|
|
intptr_t characters = 1) = 0;
|
|
virtual void PopCurrentPosition() = 0;
|
|
virtual void PopRegister(intptr_t register_index) = 0;
|
|
// Prints string within the generated code. Used for debugging.
|
|
virtual void Print(const char* str) = 0;
|
|
// Prints all emitted blocks.
|
|
virtual void PrintBlocks() = 0;
|
|
// Pushes the label on the backtrack stack, so that a following Backtrack
|
|
// will go to this label. Always checks the backtrack stack limit.
|
|
virtual void PushBacktrack(BlockLabel* label) = 0;
|
|
virtual void PushCurrentPosition() = 0;
|
|
virtual void PushRegister(intptr_t register_index) = 0;
|
|
virtual void ReadCurrentPositionFromRegister(intptr_t reg) = 0;
|
|
virtual void ReadStackPointerFromRegister(intptr_t reg) = 0;
|
|
virtual void SetCurrentPositionFromEnd(intptr_t by) = 0;
|
|
virtual void SetRegister(intptr_t register_index, intptr_t to) = 0;
|
|
// Return whether the matching (with a global regexp) will be restarted.
|
|
virtual bool Succeed() = 0;
|
|
virtual void WriteCurrentPositionToRegister(intptr_t reg,
|
|
intptr_t cp_offset) = 0;
|
|
virtual void ClearRegisters(intptr_t reg_from, intptr_t reg_to) = 0;
|
|
virtual void WriteStackPointerToRegister(intptr_t reg) = 0;
|
|
|
|
// Controls the generation of large inlined constants in the code.
|
|
void set_slow_safe(bool ssc) { slow_safe_compiler_ = ssc; }
|
|
bool slow_safe() { return slow_safe_compiler_; }
|
|
|
|
enum GlobalMode { NOT_GLOBAL, GLOBAL, GLOBAL_NO_ZERO_LENGTH_CHECK };
|
|
// Set whether the regular expression has the global flag. Exiting due to
|
|
// a failure in a global regexp may still mean success overall.
|
|
inline void set_global_mode(GlobalMode mode) { global_mode_ = mode; }
|
|
inline bool global() { return global_mode_ != NOT_GLOBAL; }
|
|
inline bool global_with_zero_length_check() { return global_mode_ == GLOBAL; }
|
|
|
|
Zone* zone() const { return zone_; }
|
|
|
|
private:
|
|
bool slow_safe_compiler_;
|
|
bool global_mode_;
|
|
Zone* zone_;
|
|
};
|
|
|
|
} // namespace dart
|
|
|
|
#endif // RUNTIME_VM_REGEXP_ASSEMBLER_H_
|