diff options
Diffstat (limited to 'src/bun.js/bindings/OnigurumaRegExp.cpp')
-rw-r--r-- | src/bun.js/bindings/OnigurumaRegExp.cpp | 804 |
1 files changed, 804 insertions, 0 deletions
diff --git a/src/bun.js/bindings/OnigurumaRegExp.cpp b/src/bun.js/bindings/OnigurumaRegExp.cpp new file mode 100644 index 000000000..b49717b05 --- /dev/null +++ b/src/bun.js/bindings/OnigurumaRegExp.cpp @@ -0,0 +1,804 @@ +#include "OnigurumaRegExp.h" + +#include "ZigGlobalObject.h" +#define ONIG_ESCAPE_UCHAR_COLLISION +#include "oniguruma/src/oniguruma.h" + +using namespace JSC; +using namespace WebCore; + +#include "WebCoreJSClientData.h" + +extern "C" EncodedJSValue jsFunctionGetOnigurumaRegExpConstructor(JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName) +{ + auto& vm = lexicalGlobalObject->vm(); + Zig::GlobalObject *globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject); + return JSValue::encode(globalObject->OnigurumaRegExpConstructor()); +} + +namespace Zig { + +static WTF::String to16Bit(ASCIILiteral str) +{ + return WTF::String::make16BitFrom8BitSource(str.characters8(), str.length()); +} + +static WTF::String to16Bit(JSC::JSString* str, JSC::JSGlobalObject *globalObject) { + if (!str->is8Bit() || str->length() == 0) { + return str->value(globalObject); + } + + auto value = str->value(globalObject); + return WTF::String::make16BitFrom8BitSource(value.characters8(), value.length()); +} + + +static WTF::String to16Bit(JSValue jsValue, JSC::JSGlobalObject *globalObject, ASCIILiteral defaultValue) { + if (!jsValue || jsValue.isUndefinedOrNull()) { + return to16Bit(defaultValue); + } + + auto *jsString = jsValue.toString(globalObject); + if (jsString->length() == 0) { + return to16Bit(defaultValue); + } + + return to16Bit(jsString, globalObject); +} + +static inline bool is16BitLineTerminator(UChar c) +{ + return c == '\r' || c == '\n' || (c & ~1) == 0x2028; +} + +static inline WTF::String escapedPattern(const WTF::String& pattern, const UChar* characters, size_t length) +{ + bool previousCharacterWasBackslash = false; + bool inBrackets = false; + bool shouldEscape = false; + + // 15.10.6.4 specifies that RegExp.prototype.toString must return '/' + source + '/', + // and also states that the result must be a valid RegularExpressionLiteral. '//' is + // not a valid RegularExpressionLiteral (since it is a single line comment), and hence + // source cannot ever validly be "". If the source is empty, return a different Pattern + // that would match the same thing. + if (!length) + return "(?:)"_s; + + // early return for strings that don't contain a forwards slash and LineTerminator + for (unsigned i = 0; i < length; ++i) { + UChar ch = characters[i]; + if (!previousCharacterWasBackslash) { + if (inBrackets) { + if (ch == ']') + inBrackets = false; + } else { + if (ch == '/') { + shouldEscape = true; + break; + } + if (ch == '[') + inBrackets = true; + } + } + + if (is16BitLineTerminator(ch)) { + shouldEscape = true; + break; + } + + if (previousCharacterWasBackslash) + previousCharacterWasBackslash = false; + else + previousCharacterWasBackslash = ch == '\\'; + } + + if (!shouldEscape) + return pattern; + + previousCharacterWasBackslash = false; + inBrackets = false; + StringBuilder result; + for (unsigned i = 0; i < length; ++i) { + UChar ch = characters[i]; + if (!previousCharacterWasBackslash) { + if (inBrackets) { + if (ch == ']') + inBrackets = false; + } else { + if (ch == '/') + result.append('\\'); + else if (ch == '[') + inBrackets = true; + } + } + + // escape LineTerminator + if (is16BitLineTerminator(ch)) { + if (!previousCharacterWasBackslash) { + result.append('\\'); + } + + if (ch == '\n') { + result.append('n'); + } + else if (ch == '\r') { + result.append('r'); + } + else if (ch == 0x2028) { + result.append("u2028"); + } + else { + result.append("u2029"); + } + } else + result.append(ch); + + if (previousCharacterWasBackslash) + previousCharacterWasBackslash = false; + else + previousCharacterWasBackslash = ch == '\\'; + } + + return result.toString(); +} + +WTF::String sortRegExpFlags(WTF::String flagsString) { + WTF::Vector<UChar> flags = {'d', 'g', 'i', 'm', 's', 'u', 'y'}; + WTF::StringBuilder result; + + for (auto flag : flags) { + if (flagsString.contains(flag)) { + result.append(flag); + } + } + + return result.toString(); +} + +bool validateRegExpFlags(WTF::StringView flags){ + std::map<char16_t, bool> flagsAllowed = {{'g', false}, {'i', false}, {'m', false}, {'s', false}, {'u', false}, {'y', false}, {'d', false}}; + for (auto flag : flags.codeUnits()) { + auto flagItr = flagsAllowed.find(flag); + if (flagItr == flagsAllowed.end() || flagItr->second) { + return false; + } + flagItr->second = true; + } + + return true; +} + +class OnigurumaRegExpPrototype final : public JSC::JSNonFinalObject { + public: + using Base = JSC::JSNonFinalObject; + + static OnigurumaRegExpPrototype* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure) + { + OnigurumaRegExpPrototype* ptr = new (NotNull, JSC::allocateCell<OnigurumaRegExpPrototype>(vm)) OnigurumaRegExpPrototype(vm, globalObject, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + template<typename CellType, JSC::SubspaceAccess> + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.plainObjectSpace(); + } + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + } + + private: + OnigurumaRegExpPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); + }; + + + +class OnigurumaRegEx final : public JSC::JSDestructibleObject { +public: + using Base = JSC::JSDestructibleObject; + + static OnigurumaRegEx* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure) + { + OnigurumaRegEx* ptr = new (NotNull, JSC::allocateCell<OnigurumaRegEx>(vm)) OnigurumaRegEx(vm, globalObject, structure); + ptr->finishCreation(vm); + return ptr; + } + + static OnigurumaRegEx* create(JSC::JSGlobalObject* globalObject, WTF::String&& pattern, WTF::String&& flags, regex_t* regExpCode) { + auto *structure = reinterpret_cast<Zig::GlobalObject*>(globalObject)->OnigurumaRegExpStructure(); + auto *object = create(globalObject->vm(), globalObject, structure); + object->m_flagsString = WTFMove(flags); + object->m_patternString = WTFMove(pattern); + object->m_onigurumaRegExp = regExpCode; + + return object; + } + + + DECLARE_EXPORT_INFO; + template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + + return WebCore::subspaceForImpl<OnigurumaRegEx, UseCustomHeapCellType::No>( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForOnigurumaRegExp.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForOnigurumaRegExp = WTFMove(space); }, + [](auto& spaces) { return spaces.m_subspaceForOnigurumaRegExp.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForOnigurumaRegExp = WTFMove(space); }); + + } + + static void destroy(JSC::JSCell* cell) { + static_cast<OnigurumaRegEx*>(cell)->OnigurumaRegEx::~OnigurumaRegEx(); + } + + ~OnigurumaRegEx() { + if (m_onigurumaRegExp) { + onig_free(m_onigurumaRegExp); + } + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(RegExpObjectType, StructureFlags), info()); + } + + // static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&); + + const WTF::String& flagsString() const { return m_flagsString; } + void setFlagsString(const WTF::String& flagsString) { m_flagsString = flagsString; } + const WTF::String& patternString() const { return m_patternString; } + void setPatternString(const WTF::String& patternString) { m_patternString = patternString; } + + regex_t* m_onigurumaRegExp = NULL; + int32_t m_lastIndex = 0; + +private: + OnigurumaRegEx(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&) { + Base::finishCreation(vm()); + + } + + WTF::String m_patternString = {}; + WTF::String m_flagsString = {}; + +}; + +const ClassInfo OnigurumaRegExpConstructor::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(OnigurumaRegExpConstructor) }; +const ClassInfo OnigurumaRegExpPrototype::s_info = { "Object"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(OnigurumaRegExpPrototype) }; +const ClassInfo OnigurumaRegEx::s_info = { "RegExp"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(OnigurumaRegEx) }; + +JSC_DEFINE_CUSTOM_GETTER(onigurumaRegExpProtoGetterGlobal, (JSGlobalObject *globalObject, EncodedJSValue encodedThis, PropertyName)) +{ + auto *thisValue = jsDynamicCast<OnigurumaRegEx*>(JSValue::decode(encodedThis)); + if (UNLIKELY(!thisValue)) { + return JSValue::encode(jsUndefined()); + } + return JSValue::encode(jsBoolean(thisValue->flagsString().contains('g'))); +} + +JSC_DEFINE_CUSTOM_GETTER(onigurumaRegExpProtoGetterDotAll, (JSGlobalObject *globalObject, EncodedJSValue encodedThis, PropertyName)) +{ + auto *thisValue = jsDynamicCast<OnigurumaRegEx*>(JSValue::decode(encodedThis)); + if (UNLIKELY(!thisValue)) { + return JSValue::encode(jsUndefined()); + } + return JSValue::encode(jsBoolean(thisValue->flagsString().contains('s'))); +} + +JSC_DEFINE_CUSTOM_GETTER(onigurumaRegExpProtoGetterHasIndices, (JSGlobalObject *globalObject, EncodedJSValue encodedThis, PropertyName)) +{ + auto *thisValue = jsDynamicCast<OnigurumaRegEx*>(JSValue::decode(encodedThis)); + if (UNLIKELY(!thisValue)) { + return JSValue::encode(jsUndefined()); + } + return JSValue::encode(jsBoolean(thisValue->flagsString().contains('d'))); +} + +JSC_DEFINE_CUSTOM_GETTER(onigurumaRegExpProtoGetterIgnoreCase, (JSGlobalObject *globalObject, EncodedJSValue encodedThis, PropertyName)) +{ + auto *thisValue = jsDynamicCast<OnigurumaRegEx*>(JSValue::decode(encodedThis)); + if (UNLIKELY(!thisValue)) { + return JSValue::encode(jsUndefined()); + } + return JSValue::encode(jsBoolean(thisValue->flagsString().contains('i'))); +} + +JSC_DEFINE_CUSTOM_GETTER(onigurumaRegExpProtoGetterMultiline, (JSGlobalObject *globalObject, EncodedJSValue encodedThis, PropertyName)) +{ + auto *thisValue = jsDynamicCast<OnigurumaRegEx*>(JSValue::decode(encodedThis)); + if (UNLIKELY(!thisValue)) { + return JSValue::encode(jsUndefined()); + } + return JSValue::encode(jsBoolean(thisValue->flagsString().contains('m'))); +} + +JSC_DEFINE_CUSTOM_GETTER(onigurumaRegExpProtoGetterSticky, (JSGlobalObject *globalObject, EncodedJSValue encodedThis, PropertyName)) +{ + auto *thisValue = jsDynamicCast<OnigurumaRegEx*>(JSValue::decode(encodedThis)); + if (UNLIKELY(!thisValue)) { + return JSValue::encode(jsUndefined()); + } + + return JSValue::encode(jsBoolean(thisValue->flagsString().contains('y'))); +} + +JSC_DEFINE_CUSTOM_GETTER(onigurumaRegExpProtoGetterUnicode, (JSGlobalObject *globalObject, EncodedJSValue encodedThis, PropertyName)) +{ + auto *thisValue = jsDynamicCast<OnigurumaRegEx*>(JSValue::decode(encodedThis)); + if (UNLIKELY(!thisValue)) { + return JSValue::encode(jsUndefined()); + } + return JSValue::encode(jsBoolean(thisValue->flagsString().contains('u'))); +} + +JSC_DEFINE_CUSTOM_GETTER(onigurumaRegExpProtoGetterSource, (JSGlobalObject *globalObject, EncodedJSValue encodedThis, PropertyName)) +{ + auto *thisValue = jsDynamicCast<OnigurumaRegEx*>(JSValue::decode(encodedThis)); + if (!thisValue) + return JSValue::encode(jsUndefined()); + + return JSValue::encode(jsString(globalObject->vm(), escapedPattern(thisValue->patternString(), thisValue->patternString().characters16(), thisValue->patternString().length()))); +} + +JSC_DEFINE_CUSTOM_GETTER(onigurumaRegExpProtoGetterFlags, (JSGlobalObject *globalObject, EncodedJSValue encodedThis, PropertyName)) +{ + auto *thisValue = jsDynamicCast<OnigurumaRegEx*>(JSValue::decode(encodedThis)); + if (!thisValue) + return JSValue::encode(jsUndefined()); + + return JSValue::encode(jsString(globalObject->vm(), thisValue->flagsString())); +} + +JSC_DEFINE_CUSTOM_GETTER(onigurumaRegExpProtoGetterLastIndex, (JSGlobalObject *globalObject, EncodedJSValue encodedThis, PropertyName)) +{ + auto *thisValue = jsDynamicCast<OnigurumaRegEx*>(JSValue::decode(encodedThis)); + return JSValue::encode(jsNumber(thisValue->m_lastIndex)); +} + +JSC_DEFINE_CUSTOM_SETTER(onigurumaRegExpProtoSetterLastIndex, (JSGlobalObject *globalObject, EncodedJSValue encodedThis, EncodedJSValue encodedValue, PropertyName)) +{ + auto *thisValue = jsDynamicCast<OnigurumaRegEx*>(JSValue::decode(encodedThis)); + auto throwScope = DECLARE_THROW_SCOPE(globalObject->vm()); + JSValue value = JSValue::decode(encodedValue); + if (!value.isAnyInt()) { + throwException(globalObject, throwScope, createTypeError(globalObject, "lastIndex must be an integer"_s)); + return false; + } + int32_t lastIndex = value.toInt32(globalObject); + thisValue->m_lastIndex = lastIndex; + return true; +} + +// compile is deprecated +JSC_DEFINE_HOST_FUNCTION(onigurumaRegExpProtoFuncCompile, (JSGlobalObject *globalObject, JSC::CallFrame *callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_CATCH_SCOPE(vm); + auto throwScope = DECLARE_THROW_SCOPE(vm); + + JSValue thisValue = callFrame->thisValue(); + auto *thisRegExp = jsDynamicCast<OnigurumaRegEx*>(callFrame->thisValue()); + if (UNLIKELY(!thisRegExp)) + return JSValue::encode(jsUndefined()); + + if (thisRegExp->globalObject() != globalObject) { + throwScope.throwException(globalObject, createTypeError(globalObject, makeString("RegExp.prototype.compile function's Realm must be the same to |this| RegExp object"_s))); + return JSValue::encode({}); + } + + JSValue arg0 = callFrame->argument(0); + JSValue arg1 = callFrame->argument(1); + + if (auto* regExpObject = jsDynamicCast<OnigurumaRegEx*>(arg0)) { + if (!arg1.isUndefined()) { + throwScope.throwException(globalObject, createTypeError(globalObject, makeString("Cannot supply flags when constructing one RegExp from another."_s))); + return JSValue::encode({}); + } + thisRegExp->setPatternString(regExpObject->patternString()); + thisRegExp->setFlagsString(regExpObject->flagsString()); + } else { + WTF::String newPatternString = to16Bit(arg0, globalObject, "(?:)"_s); + RETURN_IF_EXCEPTION(scope, {}); + + WTF::String newFlagsString = to16Bit(arg1, globalObject, ""_s); + RETURN_IF_EXCEPTION(scope, {}); + + if (!validateRegExpFlags(newFlagsString)) { + throwScope.throwException(globalObject, createSyntaxError(globalObject, makeString("Invalid flags supplied to RegExp constructor."_s))); + return JSValue::encode({}); + } + + newFlagsString = sortRegExpFlags(newFlagsString); + + thisRegExp->setPatternString(newPatternString); + thisRegExp->setFlagsString(newFlagsString); + } + + OnigEncoding encoding = ONIG_ENCODING_UTF16_LE; + onig_initialize(&encoding, 1); + + OnigOptionType options = 0; + if (thisRegExp->flagsString().contains('i')) { + options |= ONIG_OPTION_IGNORECASE; + } + if (thisRegExp->flagsString().contains('m')) { + options |= ONIG_OPTION_MULTILINE; + } else { + options |= ONIG_OPTION_SINGLELINE; + } + if (thisRegExp->flagsString().contains('s')) { + options |= ONIG_OPTION_MULTILINE; + } + + OnigErrorInfo errorInfo = { 0 }; + regex_t* onigRegExp = NULL; + int errorCode = onig_new( + &onigRegExp, + reinterpret_cast<const OnigUChar*>(thisRegExp->patternString().characters16()), + reinterpret_cast<const OnigUChar*>(thisRegExp->patternString().characters16() + thisRegExp->patternString().length()), + options, + ONIG_ENCODING_UTF16_LE, + ONIG_SYNTAX_DEFAULT, + &errorInfo + ); + + if (errorCode != ONIG_NORMAL) { + OnigUChar errorBuff[ONIG_MAX_ERROR_MESSAGE_LEN] = { 0 }; + int length = onig_error_code_to_str(errorBuff, errorCode, &errorInfo); + WTF::StringBuilder errorMessage; + errorMessage.append("Invalid regular expression: "_s); + if (length < 0) { + errorMessage.append("An unknown error occurred."_s); + } else { + errorMessage.appendCharacters(errorBuff, length); + } + throwScope.throwException(globalObject, createSyntaxError(globalObject, errorMessage.toString())); + return JSValue::encode({}); + } + + if (thisRegExp->m_onigurumaRegExp) { + onig_free(thisRegExp->m_onigurumaRegExp); + } + + thisRegExp->m_onigurumaRegExp = onigRegExp; + thisRegExp->m_lastIndex = 0; + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(onigurumaRegExpProtoFuncTest, (JSGlobalObject *globalObject, JSC::CallFrame *callFrame)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto *thisValue = jsDynamicCast<OnigurumaRegEx*>(callFrame->thisValue()); + if (!thisValue) + return JSValue::encode(jsUndefined()); + + JSValue arg = callFrame->argument(0); + if (!arg.isString()) { + scope.throwException(globalObject, createTypeError(globalObject, "Argument 0 of RegExp.prototype.test must be a string"_s)); + return JSValue::encode(jsBoolean(false)); + } + + WTF::String string = to16Bit(arg, globalObject, ""_s); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + OnigRegion *region = onig_region_new(); + + const OnigUChar* end = reinterpret_cast<const OnigUChar*>(string.characters16() + string.length()); + const OnigUChar* start = reinterpret_cast<const OnigUChar*>(string.characters16() + thisValue->m_lastIndex); + const OnigUChar* range = end; + + if (thisValue->m_lastIndex >= string.length()) { + onig_region_free(region, 1); + thisValue->m_lastIndex = 0; + return JSValue::encode(jsBoolean(false)); + } + + int result = onig_search( + thisValue->m_onigurumaRegExp, + reinterpret_cast<const OnigUChar*>(string.characters16()), + end, + start, + range, + region, + ONIG_OPTION_DEFAULT + ); + + if (result < 0) { + thisValue->m_lastIndex = 0; + onig_region_free(region, 1); + return JSValue::encode(jsBoolean(false)); + } + + if (thisValue->flagsString().contains('y') && region->beg[0] != thisValue->m_lastIndex) { + onig_region_free(region, 1); + return JSValue::encode(jsBoolean(false)); + } + + if (thisValue->flagsString().contains('g')) { + thisValue->m_lastIndex = region->end[0] / 2; + } else { + thisValue->m_lastIndex = 0; + } + + onig_region_free(region, 1); + + return JSValue::encode(jsBoolean(true)); +} + +JSC_DEFINE_HOST_FUNCTION(onigurumaRegExpProtoFuncExec, (JSGlobalObject *globalObject ,JSC::CallFrame *callFrame)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto throwScope = DECLARE_THROW_SCOPE(vm); + + auto *thisValue = jsDynamicCast<OnigurumaRegEx*>(callFrame->thisValue()); + if (!thisValue) + return JSValue::encode(jsUndefined()); + + JSValue arg = callFrame->argument(0); + if (!arg || arg.isUndefinedOrNull()) { + thisValue->m_lastIndex = 0; + return JSValue::encode(jsNull()); + } + + WTF::String string = to16Bit(arg, globalObject, ""_s); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + OnigRegion *region = onig_region_new(); + + const OnigUChar* end = reinterpret_cast<const OnigUChar*>(string.characters16() + string.length()); + const OnigUChar* start = reinterpret_cast<const OnigUChar*>(string.characters16() + thisValue->m_lastIndex); + const OnigUChar* range = end; + + int result = onig_search( + thisValue->m_onigurumaRegExp, + reinterpret_cast<const OnigUChar*>(string.characters16()), + end, + start, + range, + region, + ONIG_OPTION_DEFAULT + ); + + if (result < 0) { + onig_region_free(region, 1); + thisValue->m_lastIndex = 0; + return JSValue::encode(jsNull()); + } + + JSArray *array = constructEmptyArray(globalObject, nullptr, 0); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + JSArray *indicesArray = constructEmptyArray(globalObject, nullptr, 0); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + + array->putDirect(vm, vm.propertyNames->index, jsNumber(region->beg[0] / 2)); + array->putDirect(vm, vm.propertyNames->input, jsString(vm, string)); + array->putDirect(vm, vm.propertyNames->groups, jsUndefined()); + + for (int i = 0; i < region->num_regs; i++) { + size_t outStringLen = (region->end[i] / 2) - (region->beg[i] / 2); + UChar *ptr; + WTF::String outString; + if (outStringLen > 0) { + outString = WTF::String::createUninitialized(static_cast<unsigned int>(outStringLen), ptr); + if (UNLIKELY(!ptr)) { + throwOutOfMemoryError(globalObject, scope); + onig_region_free(region, 1); + return JSValue::encode(jsNull()); + } + + memcpy(ptr, (region->beg[i] / 2) + string.characters16(), outStringLen * sizeof(UChar)); + } + + array->putDirectIndex(globalObject, i, jsString(vm, outString)); + + JSArray *indices = constructEmptyArray(globalObject, nullptr, 0); + RETURN_IF_EXCEPTION(scope, JSValue::encode({})); + indices->putDirectIndex(globalObject, 0, jsNumber(region->beg[i] / 2)); + indices->putDirectIndex(globalObject, 1, jsNumber(region->end[i] / 2)); + indicesArray->putDirectIndex(globalObject, i, indices); + } + + if (thisValue->flagsString().contains('d')) { + array->putDirect(vm, vm.propertyNames->indices, indicesArray); + } + + if (thisValue->flagsString().contains('g')) { + thisValue->m_lastIndex = region->end[0] / 2; + } else { + thisValue->m_lastIndex = 0; + } + + onig_region_free(region, 1); + + return JSValue::encode(array); +} + +JSC_DEFINE_HOST_FUNCTION(onigurumaRegExpProtoFuncToString, (JSGlobalObject *globalObject ,JSC::CallFrame *callFrame)) +{ + auto *thisValue = jsDynamicCast<OnigurumaRegEx*>(callFrame->thisValue()); + if (!thisValue) + return JSValue::encode(jsUndefined()); + + WTF::String patternString = escapedPattern(thisValue->patternString(), thisValue->patternString().characters16(), thisValue->patternString().length()); + WTF::String flagsString = thisValue->flagsString(); + + WTF::StringBuilder source; + source.append("/"_s); + source.append(patternString); + source.append("/"_s); + source.append(flagsString); + + return JSValue::encode(jsString(globalObject->vm(), source.toString())); +} + +void OnigurumaRegExpPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); + this->putDirectNativeFunction(vm, globalObject, PropertyName(vm.propertyNames->compile), 2, onigurumaRegExpProtoFuncCompile, ImplementationVisibility::Public, NoIntrinsic, static_cast<unsigned>(0)); + this->putDirectNativeFunction(vm, globalObject, PropertyName(vm.propertyNames->exec), 1, onigurumaRegExpProtoFuncExec, ImplementationVisibility::Public, NoIntrinsic, static_cast<unsigned>(0)); + this->putDirectNativeFunction(vm, globalObject, PropertyName(vm.propertyNames->toString), 0, onigurumaRegExpProtoFuncToString, ImplementationVisibility::Public, NoIntrinsic, static_cast<unsigned>(0)); + this->putDirectCustomAccessor(vm, vm.propertyNames->global, JSC::CustomGetterSetter::create(vm, onigurumaRegExpProtoGetterGlobal, nullptr), 0 | PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly); + this->putDirectCustomAccessor(vm, vm.propertyNames->dotAll, JSC::CustomGetterSetter::create(vm, onigurumaRegExpProtoGetterDotAll, nullptr), 0 | PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly); + this->putDirectCustomAccessor(vm, vm.propertyNames->hasIndices, JSC::CustomGetterSetter::create(vm, onigurumaRegExpProtoGetterHasIndices, nullptr), 0 | PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly); + this->putDirectCustomAccessor(vm, vm.propertyNames->ignoreCase, JSC::CustomGetterSetter::create(vm, onigurumaRegExpProtoGetterIgnoreCase, nullptr), 0 | PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly); + this->putDirectCustomAccessor(vm, vm.propertyNames->multiline, JSC::CustomGetterSetter::create(vm, onigurumaRegExpProtoGetterMultiline, nullptr), 0 | PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly); + this->putDirectCustomAccessor(vm, vm.propertyNames->sticky, JSC::CustomGetterSetter::create(vm, onigurumaRegExpProtoGetterSticky, nullptr), 0 | PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly); + this->putDirectCustomAccessor(vm, vm.propertyNames->unicode, JSC::CustomGetterSetter::create(vm, onigurumaRegExpProtoGetterUnicode, nullptr), 0 | PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly); + this->putDirectCustomAccessor(vm, vm.propertyNames->source, JSC::CustomGetterSetter::create(vm, onigurumaRegExpProtoGetterSource, nullptr), 0 | PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly); + this->putDirectCustomAccessor(vm, vm.propertyNames->flags, JSC::CustomGetterSetter::create(vm, onigurumaRegExpProtoGetterFlags, nullptr), 0 | PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly); + this->putDirectCustomAccessor(vm, vm.propertyNames->lastIndex, JSC::CustomGetterSetter::create(vm, onigurumaRegExpProtoGetterLastIndex, onigurumaRegExpProtoSetterLastIndex), 0 | PropertyAttribute::CustomAccessor);; + this->putDirectNativeFunction(vm, globalObject, PropertyName(vm.propertyNames->test), 1, onigurumaRegExpProtoFuncTest, ImplementationVisibility::Public, NoIntrinsic, static_cast<unsigned>(0)); + + this->putDirectBuiltinFunction(vm, globalObject, vm.propertyNames->matchSymbol, onigurumaRegExpPrototypeMatchCodeGenerator(vm), static_cast<unsigned>(0)); + this->putDirectBuiltinFunction(vm, globalObject, vm.propertyNames->matchAllSymbol, onigurumaRegExpPrototypeMatchAllCodeGenerator(vm), static_cast<unsigned>(0)); + this->putDirectBuiltinFunction(vm, globalObject, vm.propertyNames->replaceSymbol, onigurumaRegExpPrototypeReplaceCodeGenerator(vm), static_cast<unsigned>(0)); + this->putDirectBuiltinFunction(vm, globalObject, vm.propertyNames->searchSymbol, onigurumaRegExpPrototypeSearchCodeGenerator(vm), static_cast<unsigned>(0)); + this->putDirectBuiltinFunction(vm, globalObject, vm.propertyNames->splitSymbol, onigurumaRegExpPrototypeSplitCodeGenerator(vm), static_cast<unsigned>(0)); +} + +JSC::Structure* OnigurumaRegExpConstructor::createClassStructure(JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) +{ + JSC::VM& vm = globalObject->vm(); + return OnigurumaRegEx::createStructure( + vm, + globalObject, + prototype + ); +} +JSC::JSObject* OnigurumaRegExpConstructor::createPrototype(JSC::JSGlobalObject* globalObject) +{ + return OnigurumaRegExpPrototype::create(globalObject->vm(), globalObject, OnigurumaRegExpPrototype::createStructure(globalObject->vm(), globalObject, globalObject->objectPrototype())); +} + + +void OnigurumaRegExpConstructor::finishCreation(VM &vm, JSValue prototype) +{ + + Base::finishCreation(vm, 0, "RegExp"_s, PropertyAdditionMode::WithoutStructureTransition); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + ASSERT(inherits(info())); +} + +OnigurumaRegExpConstructor* OnigurumaRegExpConstructor::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSValue prototype) +{ + OnigurumaRegExpConstructor* ptr = new (NotNull, JSC::allocateCell<OnigurumaRegExpConstructor>(vm)) OnigurumaRegExpConstructor(vm, structure, construct); + ptr->finishCreation(vm, prototype); + return ptr; +} + +static JSC::EncodedJSValue constructOrCall(Zig::GlobalObject *globalObject, JSValue arg0, JSValue arg1) +{ + auto &vm = globalObject->vm(); + auto scope = DECLARE_CATCH_SCOPE(vm); + auto throwScope = DECLARE_THROW_SCOPE(vm); + + WTF::String patternString = to16Bit(arg0, globalObject, "(?:)"_s); + RETURN_IF_EXCEPTION(scope, {}); + + WTF::String flagsString = to16Bit(arg1, globalObject, ""_s); + RETURN_IF_EXCEPTION(scope, {}); + + if(!validateRegExpFlags(flagsString)) { + throwScope.throwException(globalObject, createSyntaxError(globalObject, makeString("Invalid flags supplied to RegExp constructor."_s))); + return JSValue::encode({}); + } + + flagsString = sortRegExpFlags(flagsString); + + OnigEncoding encoding = ONIG_ENCODING_UTF16_LE; + onig_initialize(&encoding, 1); + + OnigOptionType options = 0; + if (flagsString.contains('i')) { + options |= ONIG_OPTION_IGNORECASE; + } + if (flagsString.contains('m')) { + options |= ONIG_OPTION_MULTILINE; + } else { + options |= ONIG_OPTION_SINGLELINE; + } + if (flagsString.contains('s')) { + options |= ONIG_OPTION_MULTILINE; + } + + OnigErrorInfo errorInfo = { 0 }; + regex_t* onigRegExp = NULL; + int errorCode = onig_new( + &onigRegExp, + reinterpret_cast<const OnigUChar*>(patternString.characters16()), + reinterpret_cast<const OnigUChar*>(patternString.characters16() + patternString.length()), + options, + ONIG_ENCODING_UTF16_LE, + ONIG_SYNTAX_DEFAULT, + &errorInfo + ); + + if (errorCode != ONIG_NORMAL) { + OnigUChar errorBuff[ONIG_MAX_ERROR_MESSAGE_LEN] = { 0 }; + int length = onig_error_code_to_str(errorBuff, errorCode, &errorInfo); + WTF::StringBuilder errorMessage; + errorMessage.append("Invalid regular expression: "_s); + if (length < 0) { + errorMessage.append("An unknown error occurred."_s); + } else { + errorMessage.appendCharacters(errorBuff, length); + } + throwScope.throwException(globalObject, createSyntaxError(globalObject, errorMessage.toString())); + return JSValue::encode({}); + } + + RETURN_IF_EXCEPTION(scope, {}); + + OnigurumaRegEx *result = OnigurumaRegEx::create(globalObject, WTFMove(patternString), WTFMove(flagsString), onigRegExp); + + return JSValue::encode(result); +} + +JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES OnigurumaRegExpConstructor::construct(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) +{ + Zig::GlobalObject *globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject); + JSC::VM &vm = globalObject->vm(); + JSObject* newTarget = asObject(callFrame->newTarget()); + auto* constructor = globalObject->OnigurumaRegExpConstructor(); + Structure* structure = globalObject->OnigurumaRegExpStructure(); + if (constructor != newTarget) { + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* functionGlobalObject = reinterpret_cast<Zig::GlobalObject*>( + // ShadowRealm functions belong to a different global object. + getFunctionRealm(globalObject, newTarget) + ); + RETURN_IF_EXCEPTION(scope, {}); + structure = InternalFunction::createSubclassStructure( + globalObject, + newTarget, + functionGlobalObject->OnigurumaRegExpStructure() + ); + } + + return constructOrCall(globalObject, callFrame->argument(0), callFrame->argument(1)); +} + +} |