#include "builtin/RegExp.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/TypeTraits.h"
#include "frontend/TokenStream.h"
#include "irregexp/RegExpParser.h"
#include "jit/InlinableNatives.h"
#include "js/PropertySpec.h"
#include "util/StringBuffer.h"
#include "util/Unicode.h"
#include "vm/JSContext.h"
#include "vm/RegExpStatics.h"
#include "vm/SelfHosting.h"
#include "vm/EnvironmentObject-inl.h"
#include "vm/JSObject-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/ObjectOperations-inl.h"
#include "vm/UnboxedObject-inl.h"
using namespace js;
using mozilla::CheckedInt;
using mozilla::IsAsciiDigit;
using JS::CompileOptions;
bool js::CreateRegExpMatchResult(JSContext* cx, HandleString input,
const MatchPairs& matches,
MutableHandleValue rval) {
MOZ_ASSERT(input);
JSObject* templateObject =
cx->realm()->regExps.getOrCreateMatchResultTemplateObject(cx);
if (!templateObject) {
return false;
}
size_t numPairs = matches.length();
MOZ_ASSERT(numPairs > 0);
RootedArrayObject arr(cx, NewDenseFullyAllocatedArrayWithTemplate(
cx, numPairs, templateObject));
if (!arr) {
return false;
}
for (size_t i = 0; i < numPairs; i++) {
const MatchPair& pair = matches[i];
if (pair.isUndefined()) {
MOZ_ASSERT(i != 0); arr->setDenseInitializedLength(i + 1);
arr->initDenseElement(i, UndefinedValue());
} else {
JSLinearString* str =
NewDependentString(cx, input, pair.start, pair.length());
if (!str) {
return false;
}
arr->setDenseInitializedLength(i + 1);
arr->initDenseElement(i, StringValue(str));
}
}
arr->setSlot(RegExpRealm::MatchResultObjectIndexSlot,
Int32Value(matches[0].start));
arr->setSlot(RegExpRealm::MatchResultObjectInputSlot, StringValue(input));
#ifdef DEBUG
RootedValue test(cx);
RootedId id(cx, NameToId(cx->names().index));
if (!NativeGetProperty(cx, arr, id, &test)) {
return false;
}
MOZ_ASSERT(test == arr->getSlot(RegExpRealm::MatchResultObjectIndexSlot));
id = NameToId(cx->names().input);
if (!NativeGetProperty(cx, arr, id, &test)) {
return false;
}
MOZ_ASSERT(test == arr->getSlot(RegExpRealm::MatchResultObjectInputSlot));
#endif
rval.setObject(*arr);
return true;
}
static int32_t CreateRegExpSearchResult(const MatchPairs& matches) {
uint32_t position = matches[0].start;
uint32_t lastIndex = matches[0].limit;
MOZ_ASSERT(position < 0x8000);
MOZ_ASSERT(lastIndex < 0x8000);
return position | (lastIndex << 15);
}
static RegExpRunStatus ExecuteRegExpImpl(JSContext* cx, RegExpStatics* res,
MutableHandleRegExpShared re,
HandleLinearString input,
size_t searchIndex,
VectorMatchPairs* matches,
size_t* endIndex) {
RegExpRunStatus status =
RegExpShared::execute(cx, re, input, searchIndex, matches, endIndex);
if (status == RegExpRunStatus_Success && res) {
if (matches) {
if (!res->updateFromMatchPairs(cx, input, *matches)) {
return RegExpRunStatus_Error;
}
} else {
res->updateLazily(cx, input, re, searchIndex);
}
}
return status;
}
bool js::ExecuteRegExpLegacy(JSContext* cx, RegExpStatics* res,
Handle<RegExpObject*> reobj,
HandleLinearString input, size_t* lastIndex,
bool test, MutableHandleValue rval) {
RootedRegExpShared shared(cx, RegExpObject::getShared(cx, reobj));
if (!shared) {
return false;
}
VectorMatchPairs matches;
RegExpRunStatus status =
ExecuteRegExpImpl(cx, res, &shared, input, *lastIndex, &matches, nullptr);
if (status == RegExpRunStatus_Error) {
return false;
}
if (status == RegExpRunStatus_Success_NotFound) {
rval.setNull();
return true;
}
*lastIndex = matches[0].limit;
if (test) {
rval.setBoolean(true);
return true;
}
return CreateRegExpMatchResult(cx, input, matches, rval);
}
static bool CheckPatternSyntaxSlow(JSContext* cx, HandleAtom pattern,
RegExpFlag flags) {
LifoAllocScope allocScope(&cx->tempLifoAlloc());
CompileOptions options(cx);
frontend::TokenStream dummyTokenStream(cx, options, nullptr, 0, nullptr);
return irregexp::ParsePatternSyntax(dummyTokenStream, allocScope.alloc(),
pattern, flags & UnicodeFlag);
}
static RegExpShared* CheckPatternSyntax(JSContext* cx, HandleAtom pattern,
RegExpFlag flags) {
if (RegExpShared* shared = cx->zone()->regExps().maybeGet(pattern, flags)) {
#ifdef DEBUG
if (!CheckPatternSyntaxSlow(cx, pattern, flags)) {
MOZ_ASSERT(cx->isThrowingOutOfMemory() || cx->isThrowingOverRecursed());
return nullptr;
}
#endif
return shared;
}
if (!CheckPatternSyntaxSlow(cx, pattern, flags)) {
return nullptr;
}
return cx->zone()->regExps().get(cx, pattern, flags);
}
static bool RegExpInitializeIgnoringLastIndex(JSContext* cx,
Handle<RegExpObject*> obj,
HandleValue patternValue,
HandleValue flagsValue) {
RootedAtom pattern(cx);
if (patternValue.isUndefined()) {
pattern = cx->names().empty;
} else {
pattern = ToAtom<CanGC>(cx, patternValue);
if (!pattern) {
return false;
}
}
RegExpFlag flags = RegExpFlag(0);
if (!flagsValue.isUndefined()) {
RootedString flagStr(cx, ToString<CanGC>(cx, flagsValue));
if (!flagStr) {
return false;
}
if (!ParseRegExpFlags(cx, flagStr, &flags)) {
return false;
}
}
RegExpShared* shared = CheckPatternSyntax(cx, pattern, flags);
if (!shared) {
return false;
}
obj->initIgnoringLastIndex(pattern, flags);
obj->setShared(*shared);
return true;
}
bool js::RegExpCreate(JSContext* cx, HandleValue patternValue,
HandleValue flagsValue, MutableHandleValue rval) {
Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx, GenericObject));
if (!regexp) {
return false;
}
if (!RegExpInitializeIgnoringLastIndex(cx, regexp, patternValue,
flagsValue)) {
return false;
}
regexp->zeroLastIndex(cx);
rval.setObject(*regexp);
return true;
}
MOZ_ALWAYS_INLINE bool IsRegExpObject(HandleValue v) {
return v.isObject() && v.toObject().is<RegExpObject>();
}
bool js::IsRegExp(JSContext* cx, HandleValue value, bool* result) {
if (!value.isObject()) {
*result = false;
return true;
}
RootedObject obj(cx, &value.toObject());
RootedValue isRegExp(cx);
RootedId matchId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().match));
if (!GetProperty(cx, obj, obj, matchId, &isRegExp)) {
return false;
}
if (!isRegExp.isUndefined()) {
*result = ToBoolean(isRegExp);
return true;
}
ESClass cls;
if (!GetClassOfValue(cx, value, &cls)) {
return false;
}
*result = cls == ESClass::RegExp;
return true;
}
MOZ_ALWAYS_INLINE bool regexp_compile_impl(JSContext* cx,
const CallArgs& args) {
MOZ_ASSERT(IsRegExpObject(args.thisv()));
Rooted<RegExpObject*> regexp(cx, &args.thisv().toObject().as<RegExpObject>());
RootedValue patternValue(cx, args.get(0));
ESClass cls;
if (!GetClassOfValue(cx, patternValue, &cls)) {
return false;
}
if (cls == ESClass::RegExp) {
if (args.hasDefined(1)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_NEWREGEXP_FLAGGED);
return false;
}
RootedObject patternObj(cx, &patternValue.toObject());
RootedAtom sourceAtom(cx);
RegExpFlag flags;
{
RegExpShared* shared = RegExpToShared(cx, patternObj);
if (!shared) {
return false;
}
sourceAtom = shared->getSource();
flags = shared->getFlags();
}
regexp->initIgnoringLastIndex(sourceAtom, flags);
} else {
RootedValue P(cx, patternValue);
RootedValue F(cx, args.get(1));
if (!RegExpInitializeIgnoringLastIndex(cx, regexp, P, F)) {
return false;
}
}
if (regexp->lookupPure(cx->names().lastIndex)->writable()) {
regexp->zeroLastIndex(cx);
} else {
RootedValue zero(cx, Int32Value(0));
if (!SetProperty(cx, regexp, cx->names().lastIndex, zero)) {
return false;
}
}
args.rval().setObject(*regexp);
return true;
}
static bool regexp_compile(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsRegExpObject, regexp_compile_impl>(cx, args);
}
bool js::regexp_construct(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
bool patternIsRegExp;
if (!IsRegExp(cx, args.get(0), &patternIsRegExp)) {
return false;
}
if (!args.isConstructing()) {
if (patternIsRegExp && !args.hasDefined(1)) {
RootedObject patternObj(cx, &args[0].toObject());
RootedValue patternConstructor(cx);
if (!GetProperty(cx, patternObj, patternObj, cx->names().constructor,
&patternConstructor)) {
return false;
}
if (patternConstructor.isObject() &&
patternConstructor.toObject() == args.callee()) {
args.rval().set(args[0]);
return true;
}
}
}
RootedValue patternValue(cx, args.get(0));
ESClass cls;
if (!GetClassOfValue(cx, patternValue, &cls)) {
return false;
}
if (cls == ESClass::RegExp) {
RootedObject patternObj(cx, &patternValue.toObject());
RootedAtom sourceAtom(cx);
RegExpFlag flags;
RootedRegExpShared shared(cx);
{
shared = RegExpToShared(cx, patternObj);
if (!shared) {
return false;
}
sourceAtom = shared->getSource();
flags = shared->getFlags();
if (cx->zone() != shared->zone()) {
shared = nullptr;
}
}
RootedObject proto(cx);
if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_RegExp, &proto)) {
return false;
}
Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx, GenericObject, proto));
if (!regexp) {
return false;
}
if (args.hasDefined(1)) {
RegExpFlag flagsArg = RegExpFlag(0);
RootedString flagStr(cx, ToString<CanGC>(cx, args[1]));
if (!flagStr) {
return false;
}
if (!ParseRegExpFlags(cx, flagStr, &flagsArg)) {
return false;
}
if (flags != flagsArg) {
shared = nullptr;
}
if (!(flags & UnicodeFlag) && flagsArg & UnicodeFlag) {
shared = CheckPatternSyntax(cx, sourceAtom, flagsArg);
if (!shared) {
return false;
}
}
flags = flagsArg;
}
regexp->initAndZeroLastIndex(sourceAtom, flags, cx);
if (shared) {
regexp->setShared(*shared);
}
args.rval().setObject(*regexp);
return true;
}
RootedValue P(cx);
RootedValue F(cx);
if (patternIsRegExp) {
RootedObject patternObj(cx, &patternValue.toObject());
if (!GetProperty(cx, patternObj, patternObj, cx->names().source, &P)) {
return false;
}
F = args.get(1);
if (F.isUndefined()) {
if (!GetProperty(cx, patternObj, patternObj, cx->names().flags, &F)) {
return false;
}
}
} else {
P = patternValue;
F = args.get(1);
}
RootedObject proto(cx);
if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_RegExp, &proto)) {
return false;
}
Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx, GenericObject, proto));
if (!regexp) {
return false;
}
if (!RegExpInitializeIgnoringLastIndex(cx, regexp, P, F)) {
return false;
}
regexp->zeroLastIndex(cx);
args.rval().setObject(*regexp);
return true;
}
bool js::regexp_construct_raw_flags(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(!args.isConstructing());
RootedAtom sourceAtom(cx, AtomizeString(cx, args[0].toString()));
if (!sourceAtom) {
return false;
}
int32_t flags = int32_t(args[1].toNumber());
RegExpObject* regexp = RegExpAlloc(cx, GenericObject);
if (!regexp) {
return false;
}
regexp->initAndZeroLastIndex(sourceAtom, RegExpFlag(flags), cx);
args.rval().setObject(*regexp);
return true;
}
MOZ_ALWAYS_INLINE bool IsRegExpPrototype(HandleValue v, JSContext* cx) {
return (v.isObject() &&
cx->global()->maybeGetRegExpPrototype() == &v.toObject());
}
MOZ_ALWAYS_INLINE bool regexp_global_impl(JSContext* cx, const CallArgs& args) {
MOZ_ASSERT(IsRegExpObject(args.thisv()));
RegExpObject* reObj = &args.thisv().toObject().as<RegExpObject>();
args.rval().setBoolean(reObj->global());
return true;
}
bool js::regexp_global(JSContext* cx, unsigned argc, JS::Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (IsRegExpPrototype(args.thisv(), cx)) {
args.rval().setUndefined();
return true;
}
return CallNonGenericMethod<IsRegExpObject, regexp_global_impl>(cx, args);
}
MOZ_ALWAYS_INLINE bool regexp_ignoreCase_impl(JSContext* cx,
const CallArgs& args) {
MOZ_ASSERT(IsRegExpObject(args.thisv()));
RegExpObject* reObj = &args.thisv().toObject().as<RegExpObject>();
args.rval().setBoolean(reObj->ignoreCase());
return true;
}
bool js::regexp_ignoreCase(JSContext* cx, unsigned argc, JS::Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (IsRegExpPrototype(args.thisv(), cx)) {
args.rval().setUndefined();
return true;
}
return CallNonGenericMethod<IsRegExpObject, regexp_ignoreCase_impl>(cx, args);
}
MOZ_ALWAYS_INLINE bool regexp_multiline_impl(JSContext* cx,
const CallArgs& args) {
MOZ_ASSERT(IsRegExpObject(args.thisv()));
RegExpObject* reObj = &args.thisv().toObject().as<RegExpObject>();
args.rval().setBoolean(reObj->multiline());
return true;
}
bool js::regexp_multiline(JSContext* cx, unsigned argc, JS::Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (IsRegExpPrototype(args.thisv(), cx)) {
args.rval().setUndefined();
return true;
}
return CallNonGenericMethod<IsRegExpObject, regexp_multiline_impl>(cx, args);
}
MOZ_ALWAYS_INLINE bool regexp_source_impl(JSContext* cx, const CallArgs& args) {
MOZ_ASSERT(IsRegExpObject(args.thisv()));
RegExpObject* reObj = &args.thisv().toObject().as<RegExpObject>();
RootedAtom src(cx, reObj->getSource());
if (!src) {
return false;
}
JSString* str = EscapeRegExpPattern(cx, src);
if (!str) {
return false;
}
args.rval().setString(str);
return true;
}
static bool regexp_source(JSContext* cx, unsigned argc, JS::Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (IsRegExpPrototype(args.thisv(), cx)) {
args.rval().setString(cx->names().emptyRegExp);
return true;
}
return CallNonGenericMethod<IsRegExpObject, regexp_source_impl>(cx, args);
}
MOZ_ALWAYS_INLINE bool regexp_sticky_impl(JSContext* cx, const CallArgs& args) {
MOZ_ASSERT(IsRegExpObject(args.thisv()));
RegExpObject* reObj = &args.thisv().toObject().as<RegExpObject>();
args.rval().setBoolean(reObj->sticky());
return true;
}
bool js::regexp_sticky(JSContext* cx, unsigned argc, JS::Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (IsRegExpPrototype(args.thisv(), cx)) {
args.rval().setUndefined();
return true;
}
return CallNonGenericMethod<IsRegExpObject, regexp_sticky_impl>(cx, args);
}
MOZ_ALWAYS_INLINE bool regexp_unicode_impl(JSContext* cx,
const CallArgs& args) {
MOZ_ASSERT(IsRegExpObject(args.thisv()));
RegExpObject* reObj = &args.thisv().toObject().as<RegExpObject>();
args.rval().setBoolean(reObj->unicode());
return true;
}
bool js::regexp_unicode(JSContext* cx, unsigned argc, JS::Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (IsRegExpPrototype(args.thisv(), cx)) {
args.rval().setUndefined();
return true;
}
return CallNonGenericMethod<IsRegExpObject, regexp_unicode_impl>(cx, args);
}
const JSPropertySpec js::regexp_properties[] = {
JS_SELF_HOSTED_GET("flags", "RegExpFlagsGetter", 0),
JS_PSG("global", regexp_global, 0),
JS_PSG("ignoreCase", regexp_ignoreCase, 0),
JS_PSG("multiline", regexp_multiline, 0),
JS_PSG("source", regexp_source, 0),
JS_PSG("sticky", regexp_sticky, 0),
JS_PSG("unicode", regexp_unicode, 0),
JS_PS_END};
const JSFunctionSpec js::regexp_methods[] = {
JS_SELF_HOSTED_FN(js_toSource_str, "RegExpToString", 0, 0),
JS_SELF_HOSTED_FN(js_toString_str, "RegExpToString", 0, 0),
JS_FN("compile", regexp_compile, 2, 0),
JS_SELF_HOSTED_FN("exec", "RegExp_prototype_Exec", 1, 0),
JS_SELF_HOSTED_FN("test", "RegExpTest", 1, 0),
JS_SELF_HOSTED_SYM_FN(match, "RegExpMatch", 1, 0),
JS_SELF_HOSTED_SYM_FN(matchAll, "RegExpMatchAll", 1, 0),
JS_SELF_HOSTED_SYM_FN(replace, "RegExpReplace", 2, 0),
JS_SELF_HOSTED_SYM_FN(search, "RegExpSearch", 1, 0),
JS_SELF_HOSTED_SYM_FN(split, "RegExpSplit", 2, 0),
JS_FS_END};
#define STATIC_PAREN_GETTER_CODE(parenNum) \
if (!res->createParen(cx, parenNum, args.rval())) return false; \
if (args.rval().isUndefined()) \
args.rval().setString(cx->runtime()->emptyString); \
return true
#define DEFINE_STATIC_GETTER(name, code) \
static bool name(JSContext* cx, unsigned argc, Value* vp) { \
CallArgs args = CallArgsFromVp(argc, vp); \
RegExpStatics* res = GlobalObject::getRegExpStatics(cx, cx->global()); \
if (!res) return false; \
code; \
}
DEFINE_STATIC_GETTER(static_input_getter,
return res->createPendingInput(cx, args.rval()))
DEFINE_STATIC_GETTER(static_lastMatch_getter,
return res->createLastMatch(cx, args.rval()))
DEFINE_STATIC_GETTER(static_lastParen_getter,
return res->createLastParen(cx, args.rval()))
DEFINE_STATIC_GETTER(static_leftContext_getter,
return res->createLeftContext(cx, args.rval()))
DEFINE_STATIC_GETTER(static_rightContext_getter,
return res->createRightContext(cx, args.rval()))
DEFINE_STATIC_GETTER(static_paren1_getter, STATIC_PAREN_GETTER_CODE(1))
DEFINE_STATIC_GETTER(static_paren2_getter, STATIC_PAREN_GETTER_CODE(2))
DEFINE_STATIC_GETTER(static_paren3_getter, STATIC_PAREN_GETTER_CODE(3))
DEFINE_STATIC_GETTER(static_paren4_getter, STATIC_PAREN_GETTER_CODE(4))
DEFINE_STATIC_GETTER(static_paren5_getter, STATIC_PAREN_GETTER_CODE(5))
DEFINE_STATIC_GETTER(static_paren6_getter, STATIC_PAREN_GETTER_CODE(6))
DEFINE_STATIC_GETTER(static_paren7_getter, STATIC_PAREN_GETTER_CODE(7))
DEFINE_STATIC_GETTER(static_paren8_getter, STATIC_PAREN_GETTER_CODE(8))
DEFINE_STATIC_GETTER(static_paren9_getter, STATIC_PAREN_GETTER_CODE(9))
#define DEFINE_STATIC_SETTER(name, code) \
static bool name(JSContext* cx, unsigned argc, Value* vp) { \
RegExpStatics* res = GlobalObject::getRegExpStatics(cx, cx->global()); \
if (!res) return false; \
code; \
return true; \
}
static bool static_input_setter(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RegExpStatics* res = GlobalObject::getRegExpStatics(cx, cx->global());
if (!res) {
return false;
}
RootedString str(cx, ToString<CanGC>(cx, args.get(0)));
if (!str) {
return false;
}
res->setPendingInput(str);
args.rval().setString(str);
return true;
}
const JSPropertySpec js::regexp_static_props[] = {
JS_PSGS("input", static_input_getter, static_input_setter,
JSPROP_PERMANENT | JSPROP_ENUMERATE),
JS_PSG("lastMatch", static_lastMatch_getter,
JSPROP_PERMANENT | JSPROP_ENUMERATE),
JS_PSG("lastParen", static_lastParen_getter,
JSPROP_PERMANENT | JSPROP_ENUMERATE),
JS_PSG("leftContext", static_leftContext_getter,
JSPROP_PERMANENT | JSPROP_ENUMERATE),
JS_PSG("rightContext", static_rightContext_getter,
JSPROP_PERMANENT | JSPROP_ENUMERATE),
JS_PSG("$1", static_paren1_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
JS_PSG("$2", static_paren2_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
JS_PSG("$3", static_paren3_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
JS_PSG("$4", static_paren4_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
JS_PSG("$5", static_paren5_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
JS_PSG("$6", static_paren6_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
JS_PSG("$7", static_paren7_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
JS_PSG("$8", static_paren8_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
JS_PSG("$9", static_paren9_getter, JSPROP_PERMANENT | JSPROP_ENUMERATE),
JS_PSGS("$_", static_input_getter, static_input_setter, JSPROP_PERMANENT),
JS_PSG("$&", static_lastMatch_getter, JSPROP_PERMANENT),
JS_PSG("$+", static_lastParen_getter, JSPROP_PERMANENT),
JS_PSG("$`", static_leftContext_getter, JSPROP_PERMANENT),
JS_PSG("$'", static_rightContext_getter, JSPROP_PERMANENT),
JS_SELF_HOSTED_SYM_GET(species, "RegExpSpecies", 0),
JS_PS_END};
template <typename CharT>
static bool IsTrailSurrogateWithLeadSurrogateImpl(HandleLinearString input,
size_t index) {
JS::AutoCheckCannotGC nogc;
MOZ_ASSERT(index > 0 && index < input->length());
const CharT* inputChars = input->chars<CharT>(nogc);
return unicode::IsTrailSurrogate(inputChars[index]) &&
unicode::IsLeadSurrogate(inputChars[index - 1]);
}
static bool IsTrailSurrogateWithLeadSurrogate(HandleLinearString input,
int32_t index) {
if (index <= 0 || size_t(index) >= input->length()) {
return false;
}
return input->hasLatin1Chars()
? IsTrailSurrogateWithLeadSurrogateImpl<Latin1Char>(input, index)
: IsTrailSurrogateWithLeadSurrogateImpl<char16_t>(input, index);
}
static RegExpRunStatus ExecuteRegExp(JSContext* cx, HandleObject regexp,
HandleString string, int32_t lastIndex,
VectorMatchPairs* matches,
size_t* endIndex) {
Handle<RegExpObject*> reobj = regexp.as<RegExpObject>();
RootedRegExpShared re(cx, RegExpObject::getShared(cx, reobj));
if (!re) {
return RegExpRunStatus_Error;
}
RegExpStatics* res = GlobalObject::getRegExpStatics(cx, cx->global());
if (!res) {
return RegExpRunStatus_Error;
}
RootedLinearString input(cx, string->ensureLinear(cx));
if (!input) {
return RegExpRunStatus_Error;
}
MOZ_ASSERT(lastIndex >= 0 && size_t(lastIndex) <= input->length());
if (reobj->unicode()) {
if (IsTrailSurrogateWithLeadSurrogate(input, lastIndex)) {
lastIndex--;
}
}
RegExpRunStatus status =
ExecuteRegExpImpl(cx, res, &re, input, lastIndex, matches, endIndex);
if (status == RegExpRunStatus_Error) {
return RegExpRunStatus_Error;
}
return status;
}
static bool RegExpMatcherImpl(JSContext* cx, HandleObject regexp,
HandleString string, int32_t lastIndex,
MutableHandleValue rval) {
VectorMatchPairs matches;
RegExpRunStatus status =
ExecuteRegExp(cx, regexp, string, lastIndex, &matches, nullptr);
if (status == RegExpRunStatus_Error) {
return false;
}
if (status == RegExpRunStatus_Success_NotFound) {
rval.setNull();
return true;
}
return CreateRegExpMatchResult(cx, string, matches, rval);
}
bool js::RegExpMatcher(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 3);
MOZ_ASSERT(IsRegExpObject(args[0]));
MOZ_ASSERT(args[1].isString());
MOZ_ASSERT(args[2].isNumber());
RootedObject regexp(cx, &args[0].toObject());
RootedString string(cx, args[1].toString());
int32_t lastIndex;
MOZ_ALWAYS_TRUE(ToInt32(cx, args[2], &lastIndex));
return RegExpMatcherImpl(cx, regexp, string, lastIndex, args.rval());
}
bool js::RegExpMatcherRaw(JSContext* cx, HandleObject regexp,
HandleString input, int32_t maybeLastIndex,
MatchPairs* maybeMatches, MutableHandleValue output) {
if (maybeMatches && maybeMatches->pairsRaw()[0] > MatchPair::NoMatch) {
return CreateRegExpMatchResult(cx, input, *maybeMatches, output);
}
MOZ_ASSERT(maybeLastIndex >= 0);
return RegExpMatcherImpl(cx, regexp, input, maybeLastIndex, output);
}
static bool RegExpSearcherImpl(JSContext* cx, HandleObject regexp,
HandleString string, int32_t lastIndex,
int32_t* result) {
VectorMatchPairs matches;
RegExpRunStatus status =
ExecuteRegExp(cx, regexp, string, lastIndex, &matches, nullptr);
if (status == RegExpRunStatus_Error) {
return false;
}
if (status == RegExpRunStatus_Success_NotFound) {
*result = -1;
return true;
}
*result = CreateRegExpSearchResult(matches);
return true;
}
bool js::RegExpSearcher(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 3);
MOZ_ASSERT(IsRegExpObject(args[0]));
MOZ_ASSERT(args[1].isString());
MOZ_ASSERT(args[2].isNumber());
RootedObject regexp(cx, &args[0].toObject());
RootedString string(cx, args[1].toString());
int32_t lastIndex;
MOZ_ALWAYS_TRUE(ToInt32(cx, args[2], &lastIndex));
int32_t result = 0;
if (!RegExpSearcherImpl(cx, regexp, string, lastIndex, &result)) {
return false;
}
args.rval().setInt32(result);
return true;
}
bool js::RegExpSearcherRaw(JSContext* cx, HandleObject regexp,
HandleString input, int32_t lastIndex,
MatchPairs* maybeMatches, int32_t* result) {
MOZ_ASSERT(lastIndex >= 0);
if (maybeMatches && maybeMatches->pairsRaw()[0] > MatchPair::NoMatch) {
*result = CreateRegExpSearchResult(*maybeMatches);
return true;
}
return RegExpSearcherImpl(cx, regexp, input, lastIndex, result);
}
bool js::RegExpTester(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 3);
MOZ_ASSERT(IsRegExpObject(args[0]));
MOZ_ASSERT(args[1].isString());
MOZ_ASSERT(args[2].isNumber());
RootedObject regexp(cx, &args[0].toObject());
RootedString string(cx, args[1].toString());
int32_t lastIndex;
MOZ_ALWAYS_TRUE(ToInt32(cx, args[2], &lastIndex));
size_t endIndex = 0;
RegExpRunStatus status =
ExecuteRegExp(cx, regexp, string, lastIndex, nullptr, &endIndex);
if (status == RegExpRunStatus_Error) {
return false;
}
if (status == RegExpRunStatus_Success) {
MOZ_ASSERT(endIndex <= INT32_MAX);
args.rval().setInt32(int32_t(endIndex));
} else {
args.rval().setInt32(-1);
}
return true;
}
bool js::RegExpTesterRaw(JSContext* cx, HandleObject regexp, HandleString input,
int32_t lastIndex, int32_t* endIndex) {
MOZ_ASSERT(lastIndex >= 0);
size_t endIndexTmp = 0;
RegExpRunStatus status =
ExecuteRegExp(cx, regexp, input, lastIndex, nullptr, &endIndexTmp);
if (status == RegExpRunStatus_Success) {
MOZ_ASSERT(endIndexTmp <= INT32_MAX);
*endIndex = int32_t(endIndexTmp);
return true;
}
if (status == RegExpRunStatus_Success_NotFound) {
*endIndex = -1;
return true;
}
return false;
}
using CapturesVector = GCVector<Value, 4>;
struct JSSubString {
JSLinearString* base = nullptr;
size_t offset = 0;
size_t length = 0;
JSSubString() = default;
void initEmpty(JSLinearString* base) {
this->base = base;
offset = length = 0;
}
void init(JSLinearString* base, size_t offset, size_t length) {
this->base = base;
this->offset = offset;
this->length = length;
}
};
static void GetParen(JSLinearString* matched, const JS::Value& capture,
JSSubString* out) {
if (capture.isUndefined()) {
out->initEmpty(matched);
return;
}
JSLinearString& captureLinear = capture.toString()->asLinear();
out->init(&captureLinear, 0, captureLinear.length());
}
template <typename CharT>
static bool InterpretDollar(JSLinearString* matched, JSLinearString* string,
size_t position, size_t tailPos,
Handle<CapturesVector> captures,
JSLinearString* replacement,
const CharT* replacementBegin,
const CharT* currentDollar,
const CharT* replacementEnd, JSSubString* out,
size_t* skip) {
MOZ_ASSERT(*currentDollar == '$');
if (currentDollar + 1 >= replacementEnd) {
return false;
}
char16_t c = currentDollar[1];
if (IsAsciiDigit(c)) {
unsigned num = JS7_UNDEC(c);
if (num > captures.length()) {
return false;
}
const CharT* currentChar = currentDollar + 2;
if (currentChar < replacementEnd) {
c = *currentChar;
if (IsAsciiDigit(c)) {
unsigned tmpNum = 10 * num + JS7_UNDEC(c);
if (tmpNum <= captures.length()) {
currentChar++;
num = tmpNum;
}
}
}
if (num == 0) {
return false;
}
*skip = currentChar - currentDollar;
MOZ_ASSERT(num <= captures.length());
GetParen(matched, captures[num - 1], out);
return true;
}
*skip = 2;
switch (c) {
default:
return false;
case '$':
out->init(replacement, currentDollar - replacementBegin, 1);
break;
case '&':
out->init(matched, 0, matched->length());
break;
case '+':
if (captures.length() == 0) {
out->initEmpty(matched);
} else {
GetParen(matched, captures[captures.length() - 1], out);
}
break;
case '`':
out->init(string, 0, position);
break;
case '\'':
out->init(string, tailPos, string->length() - tailPos);
break;
}
return true;
}
template <typename CharT>
static bool FindReplaceLengthString(JSContext* cx, HandleLinearString matched,
HandleLinearString string, size_t position,
size_t tailPos,
Handle<CapturesVector> captures,
HandleLinearString replacement,
size_t firstDollarIndex, size_t* sizep) {
CheckedInt<uint32_t> replen = replacement->length();
JS::AutoCheckCannotGC nogc;
MOZ_ASSERT(firstDollarIndex < replacement->length());
const CharT* replacementBegin = replacement->chars<CharT>(nogc);
const CharT* currentDollar = replacementBegin + firstDollarIndex;
const CharT* replacementEnd = replacementBegin + replacement->length();
do {
JSSubString sub;
size_t skip;
if (InterpretDollar(matched, string, position, tailPos, captures,
replacement, replacementBegin, currentDollar,
replacementEnd, &sub, &skip)) {
if (sub.length > skip) {
replen += sub.length - skip;
} else {
replen -= skip - sub.length;
}
currentDollar += skip;
} else {
currentDollar++;
}
currentDollar = js_strchr_limit(currentDollar, '$', replacementEnd);
} while (currentDollar);
if (!replen.isValid()) {
ReportAllocationOverflow(cx);
return false;
}
*sizep = replen.value();
return true;
}
static bool FindReplaceLength(JSContext* cx, HandleLinearString matched,
HandleLinearString string, size_t position,
size_t tailPos, Handle<CapturesVector> captures,
HandleLinearString replacement,
size_t firstDollarIndex, size_t* sizep) {
return replacement->hasLatin1Chars()
? FindReplaceLengthString<Latin1Char>(
cx, matched, string, position, tailPos, captures,
replacement, firstDollarIndex, sizep)
: FindReplaceLengthString<char16_t>(cx, matched, string, position,
tailPos, captures, replacement,
firstDollarIndex, sizep);
}
template <typename CharT>
static void DoReplace(HandleLinearString matched, HandleLinearString string,
size_t position, size_t tailPos,
Handle<CapturesVector> captures,
HandleLinearString replacement, size_t firstDollarIndex,
StringBuffer& sb) {
JS::AutoCheckCannotGC nogc;
const CharT* replacementBegin = replacement->chars<CharT>(nogc);
const CharT* currentChar = replacementBegin;
MOZ_ASSERT(firstDollarIndex < replacement->length());
const CharT* currentDollar = replacementBegin + firstDollarIndex;
const CharT* replacementEnd = replacementBegin + replacement->length();
do {
size_t len = currentDollar - currentChar;
sb.infallibleAppend(currentChar, len);
currentChar = currentDollar;
JSSubString sub;
size_t skip;
if (InterpretDollar(matched, string, position, tailPos, captures,
replacement, replacementBegin, currentDollar,
replacementEnd, &sub, &skip)) {
sb.infallibleAppendSubstring(sub.base, sub.offset, sub.length);
currentChar += skip;
currentDollar += skip;
} else {
currentDollar++;
}
currentDollar = js_strchr_limit(currentDollar, '$', replacementEnd);
} while (currentDollar);
sb.infallibleAppend(currentChar,
replacement->length() - (currentChar - replacementBegin));
}
static bool NeedTwoBytes(HandleLinearString string,
HandleLinearString replacement,
HandleLinearString matched,
Handle<CapturesVector> captures) {
if (string->hasTwoByteChars()) {
return true;
}
if (replacement->hasTwoByteChars()) {
return true;
}
if (matched->hasTwoByteChars()) {
return true;
}
for (size_t i = 0, len = captures.length(); i < len; i++) {
const Value& capture = captures[i];
if (capture.isUndefined()) {
continue;
}
if (capture.toString()->hasTwoByteChars()) {
return true;
}
}
return false;
}
bool js::RegExpGetSubstitution(JSContext* cx, HandleArrayObject matchResult,
HandleLinearString string, size_t position,
HandleLinearString replacement,
size_t firstDollarIndex,
MutableHandleValue rval) {
MOZ_ASSERT(firstDollarIndex < replacement->length());
uint32_t matchResultLength = matchResult->length();
MOZ_ASSERT(matchResultLength > 0);
MOZ_ASSERT(matchResultLength == matchResult->getDenseInitializedLength());
const Value& matchedValue = matchResult->getDenseElement(0);
RootedLinearString matched(cx, matchedValue.toString()->ensureLinear(cx));
if (!matched) {
return false;
}
size_t matchLength = matched->length();
MOZ_ASSERT(position <= string->length());
uint32_t nCaptures = matchResultLength - 1;
Rooted<CapturesVector> captures(cx, CapturesVector(cx));
if (!captures.reserve(nCaptures)) {
return false;
}
for (uint32_t i = 1; i <= nCaptures; i++) {
const Value& capture = matchResult->getDenseElement(i);
if (capture.isUndefined()) {
captures.infallibleAppend(capture);
continue;
}
JSLinearString* captureLinear = capture.toString()->ensureLinear(cx);
if (!captureLinear) {
return false;
}
captures.infallibleAppend(StringValue(captureLinear));
}
CheckedInt<uint32_t> checkedTailPos(0);
checkedTailPos += position;
checkedTailPos += matchLength;
if (!checkedTailPos.isValid()) {
ReportAllocationOverflow(cx);
return false;
}
uint32_t tailPos = checkedTailPos.value();
size_t reserveLength;
if (!FindReplaceLength(cx, matched, string, position, tailPos, captures,
replacement, firstDollarIndex, &reserveLength)) {
return false;
}
StringBuffer result(cx);
if (NeedTwoBytes(string, replacement, matched, captures)) {
if (!result.ensureTwoByteChars()) {
return false;
}
}
if (!result.reserve(reserveLength)) {
return false;
}
if (replacement->hasLatin1Chars()) {
DoReplace<Latin1Char>(matched, string, position, tailPos, captures,
replacement, firstDollarIndex, result);
} else {
DoReplace<char16_t>(matched, string, position, tailPos, captures,
replacement, firstDollarIndex, result);
}
JSString* resultString = result.finishString();
if (!resultString) {
return false;
}
rval.setString(resultString);
return true;
}
bool js::GetFirstDollarIndex(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
JSString* str = args[0].toString();
MOZ_ASSERT(str->length() != 0);
int32_t index = -1;
if (!GetFirstDollarIndexRaw(cx, str, &index)) {
return false;
}
args.rval().setInt32(index);
return true;
}
template <typename TextChar>
static MOZ_ALWAYS_INLINE int GetFirstDollarIndexImpl(const TextChar* text,
uint32_t textLen) {
const TextChar* end = text + textLen;
for (const TextChar* c = text; c != end; ++c) {
if (*c == '$') {
return c - text;
}
}
return -1;
}
int32_t js::GetFirstDollarIndexRawFlat(JSLinearString* text) {
uint32_t len = text->length();
JS::AutoCheckCannotGC nogc;
if (text->hasLatin1Chars()) {
return GetFirstDollarIndexImpl(text->latin1Chars(nogc), len);
}
return GetFirstDollarIndexImpl(text->twoByteChars(nogc), len);
}
bool js::GetFirstDollarIndexRaw(JSContext* cx, JSString* str, int32_t* index) {
JSLinearString* text = str->ensureLinear(cx);
if (!text) {
return false;
}
*index = GetFirstDollarIndexRawFlat(text);
return true;
}
bool js::RegExpPrototypeOptimizable(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
args.rval().setBoolean(
RegExpPrototypeOptimizableRaw(cx, &args[0].toObject()));
return true;
}
bool js::RegExpPrototypeOptimizableRaw(JSContext* cx, JSObject* proto) {
AutoUnsafeCallWithABI unsafe;
AutoAssertNoPendingException aanpe(cx);
if (!proto->isNative()) {
return false;
}
NativeObject* nproto = static_cast<NativeObject*>(proto);
Shape* shape = cx->realm()->regExps.getOptimizableRegExpPrototypeShape();
if (shape == nproto->lastProperty()) {
return true;
}
JSFunction* flagsGetter;
if (!GetOwnGetterPure(cx, proto, NameToId(cx->names().flags), &flagsGetter)) {
return false;
}
if (!flagsGetter) {
return false;
}
if (!IsSelfHostedFunctionWithName(flagsGetter,
cx->names().RegExpFlagsGetter)) {
return false;
}
JSNative globalGetter;
if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().global),
&globalGetter)) {
return false;
}
if (globalGetter != regexp_global) {
return false;
}
JSNative ignoreCaseGetter;
if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().ignoreCase),
&ignoreCaseGetter)) {
return false;
}
if (ignoreCaseGetter != regexp_ignoreCase) {
return false;
}
JSNative multilineGetter;
if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().multiline),
&multilineGetter)) {
return false;
}
if (multilineGetter != regexp_multiline) {
return false;
}
JSNative stickyGetter;
if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().sticky),
&stickyGetter)) {
return false;
}
if (stickyGetter != regexp_sticky) {
return false;
}
JSNative unicodeGetter;
if (!GetOwnNativeGetterPure(cx, proto, NameToId(cx->names().unicode),
&unicodeGetter)) {
return false;
}
if (unicodeGetter != regexp_unicode) {
return false;
}
bool has = false;
if (!HasOwnDataPropertyPure(
cx, proto, SYMBOL_TO_JSID(cx->wellKnownSymbols().match), &has)) {
return false;
}
if (!has) {
return false;
}
if (!HasOwnDataPropertyPure(
cx, proto, SYMBOL_TO_JSID(cx->wellKnownSymbols().search), &has)) {
return false;
}
if (!has) {
return false;
}
if (!HasOwnDataPropertyPure(cx, proto, NameToId(cx->names().exec), &has)) {
return false;
}
if (!has) {
return false;
}
cx->realm()->regExps.setOptimizableRegExpPrototypeShape(
nproto->lastProperty());
return true;
}
bool js::RegExpInstanceOptimizable(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
args.rval().setBoolean(RegExpInstanceOptimizableRaw(cx, &args[0].toObject(),
&args[1].toObject()));
return true;
}
bool js::RegExpInstanceOptimizableRaw(JSContext* cx, JSObject* obj,
JSObject* proto) {
AutoUnsafeCallWithABI unsafe;
AutoAssertNoPendingException aanpe(cx);
RegExpObject* rx = &obj->as<RegExpObject>();
Shape* shape = cx->realm()->regExps.getOptimizableRegExpInstanceShape();
if (shape == rx->lastProperty()) {
return true;
}
if (!rx->hasStaticPrototype()) {
return false;
}
if (rx->staticPrototype() != proto) {
return false;
}
if (!RegExpObject::isInitialShape(rx)) {
return false;
}
cx->realm()->regExps.setOptimizableRegExpInstanceShape(rx->lastProperty());
return true;
}
bool js::intrinsic_GetElemBaseForLambda(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
JSObject& lambda = args[0].toObject();
args.rval().setUndefined();
if (!lambda.is<JSFunction>()) {
return true;
}
RootedFunction fun(cx, &lambda.as<JSFunction>());
if (!fun->isInterpreted() || fun->isClassConstructor()) {
return true;
}
JSScript* script = JSFunction::getOrCreateScript(cx, fun);
if (!script) {
return false;
}
jsbytecode* pc = script->code();
if (JSOp(*pc) != JSOP_GETALIASEDVAR || fun->needsSomeEnvironmentObject()) {
return true;
}
EnvironmentCoordinate ec(pc);
EnvironmentObject* env = &fun->environment()->as<EnvironmentObject>();
for (unsigned i = 0; i < ec.hops(); ++i) {
env = &env->enclosingEnvironment().as<EnvironmentObject>();
}
Value b = env->aliasedBinding(ec);
pc += JSOP_GETALIASEDVAR_LENGTH;
if (JSOp(*pc) != JSOP_GETARG || GET_ARGNO(pc) != 0) {
return true;
}
pc += JSOP_GETARG_LENGTH;
if (JSOp(*pc) != JSOP_GETELEM) {
return true;
}
pc += JSOP_GETELEM_LENGTH;
if (JSOp(*pc) != JSOP_RETURN) {
return true;
}
if (!b.isObject()) {
return true;
}
JSObject& bobj = b.toObject();
const Class* clasp = bobj.getClass();
if (!clasp->isNative() || clasp->getOpsLookupProperty() ||
clasp->getOpsGetProperty()) {
return true;
}
args.rval().setObject(bobj);
return true;
}
bool js::intrinsic_GetStringDataProperty(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
RootedObject obj(cx, &args[0].toObject());
if (!obj->isNative()) {
args.rval().setUndefined();
return true;
}
JSAtom* atom = AtomizeString(cx, args[1].toString());
if (!atom) {
return false;
}
Value v;
if (GetPropertyPure(cx, obj, AtomToId(atom), &v) && v.isString()) {
args.rval().set(v);
} else {
args.rval().setUndefined();
}
return true;
}