X-Git-Url: https://git.saurik.com/apple/javascriptcore.git/blobdiff_plain/9dae56ea45a0f5f8136a5c93d6f3a7f99399ca73..2656c66b5b30d5597e842a751c7f19ad6c2fe31a:/runtime/RegExpObject.cpp diff --git a/runtime/RegExpObject.cpp b/runtime/RegExpObject.cpp index f8e0522..4e4c02c 100644 --- a/runtime/RegExpObject.cpp +++ b/runtime/RegExpObject.cpp @@ -1,6 +1,6 @@ /* * Copyright (C) 1999-2000 Harri Porten (porten@kde.org) - * Copyright (C) 2003, 2007, 2008 Apple Inc. All Rights Reserved. + * Copyright (C) 2003, 2007, 2008, 2012 Apple Inc. All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -21,20 +21,28 @@ #include "config.h" #include "RegExpObject.h" +#include "ButterflyInlines.h" +#include "CopiedSpaceInlines.h" +#include "Error.h" +#include "ExceptionHelpers.h" #include "JSArray.h" #include "JSGlobalObject.h" #include "JSString.h" +#include "Lexer.h" +#include "Lookup.h" +#include "JSCInlines.h" #include "RegExpConstructor.h" +#include "RegExpMatchesArray.h" #include "RegExpPrototype.h" +#include +#include namespace JSC { -static JSValuePtr regExpObjectGlobal(ExecState*, const Identifier&, const PropertySlot&); -static JSValuePtr regExpObjectIgnoreCase(ExecState*, const Identifier&, const PropertySlot&); -static JSValuePtr regExpObjectMultiline(ExecState*, const Identifier&, const PropertySlot&); -static JSValuePtr regExpObjectSource(ExecState*, const Identifier&, const PropertySlot&); -static JSValuePtr regExpObjectLastIndex(ExecState*, const Identifier&, const PropertySlot&); -static void setRegExpObjectLastIndex(ExecState*, JSObject*, JSValuePtr); +static EncodedJSValue regExpObjectGlobal(ExecState*, JSObject*, EncodedJSValue, PropertyName); +static EncodedJSValue regExpObjectIgnoreCase(ExecState*, JSObject*, EncodedJSValue, PropertyName); +static EncodedJSValue regExpObjectMultiline(ExecState*, JSObject*, EncodedJSValue, PropertyName); +static EncodedJSValue regExpObjectSource(ExecState*, JSObject*, EncodedJSValue, PropertyName); } // namespace JSC @@ -42,9 +50,9 @@ static void setRegExpObjectLastIndex(ExecState*, JSObject*, JSValuePtr); namespace JSC { -ASSERT_CLASS_FITS_IN_CELL(RegExpObject); +STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(RegExpObject); -const ClassInfo RegExpObject::info = { "RegExp", 0, 0, ExecState::regExpTable }; +const ClassInfo RegExpObject::s_info = { "RegExp", &Base::s_info, 0, ExecState::regExpTable, CREATE_METHOD_TABLE(RegExpObject) }; /* Source for RegExpObject.lut.h @begin regExpTable @@ -52,116 +60,278 @@ const ClassInfo RegExpObject::info = { "RegExp", 0, 0, ExecState::regExpTable }; ignoreCase regExpObjectIgnoreCase DontDelete|ReadOnly|DontEnum multiline regExpObjectMultiline DontDelete|ReadOnly|DontEnum source regExpObjectSource DontDelete|ReadOnly|DontEnum - lastIndex regExpObjectLastIndex DontDelete|DontEnum @end */ -RegExpObject::RegExpObject(PassRefPtr structure, PassRefPtr regExp) - : JSObject(structure) - , d(new RegExpObjectData(regExp, 0)) +RegExpObject::RegExpObject(VM& vm, Structure* structure, RegExp* regExp) + : JSNonFinalObject(vm, structure) + , m_regExp(vm, this, regExp) + , m_lastIndexIsWritable(true) { + m_lastIndex.setWithoutWriteBarrier(jsNumber(0)); } -RegExpObject::~RegExpObject() +void RegExpObject::finishCreation(VM& vm) { + Base::finishCreation(vm); + ASSERT(inherits(info())); } -bool RegExpObject::getOwnPropertySlot(ExecState* exec, const Identifier& propertyName, PropertySlot& slot) +void RegExpObject::visitChildren(JSCell* cell, SlotVisitor& visitor) { - return getStaticValueSlot(exec, ExecState::regExpTable(exec), this, propertyName, slot); + RegExpObject* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + COMPILE_ASSERT(StructureFlags & OverridesVisitChildren, OverridesVisitChildrenWithoutSettingFlag); + ASSERT(thisObject->structure()->typeInfo().overridesVisitChildren()); + + Base::visitChildren(thisObject, visitor); + visitor.append(&thisObject->m_regExp); + visitor.append(&thisObject->m_lastIndex); } -JSValuePtr regExpObjectGlobal(ExecState*, const Identifier&, const PropertySlot& slot) +bool RegExpObject::getOwnPropertySlot(JSObject* object, ExecState* exec, PropertyName propertyName, PropertySlot& slot) { - return jsBoolean(asRegExpObject(slot.slotBase())->regExp()->global()); + if (propertyName == exec->propertyNames().lastIndex) { + RegExpObject* regExp = asRegExpObject(object); + unsigned attributes = regExp->m_lastIndexIsWritable ? DontDelete | DontEnum : DontDelete | DontEnum | ReadOnly; + slot.setValue(regExp, attributes, regExp->getLastIndex()); + return true; + } + return getStaticValueSlot(exec, ExecState::regExpTable(exec->vm()), jsCast(object), propertyName, slot); } -JSValuePtr regExpObjectIgnoreCase(ExecState*, const Identifier&, const PropertySlot& slot) +bool RegExpObject::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName) { - return jsBoolean(asRegExpObject(slot.slotBase())->regExp()->ignoreCase()); -} - -JSValuePtr regExpObjectMultiline(ExecState*, const Identifier&, const PropertySlot& slot) -{ - return jsBoolean(asRegExpObject(slot.slotBase())->regExp()->multiline()); + if (propertyName == exec->propertyNames().lastIndex) + return false; + return Base::deleteProperty(cell, exec, propertyName); } -JSValuePtr regExpObjectSource(ExecState* exec, const Identifier&, const PropertySlot& slot) +void RegExpObject::getOwnNonIndexPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) { - return jsString(exec, asRegExpObject(slot.slotBase())->regExp()->pattern()); + if (mode == IncludeDontEnumProperties) + propertyNames.add(exec->propertyNames().lastIndex); + Base::getOwnNonIndexPropertyNames(object, exec, propertyNames, mode); } -JSValuePtr regExpObjectLastIndex(ExecState* exec, const Identifier&, const PropertySlot& slot) +void RegExpObject::getPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) { - return jsNumber(exec, asRegExpObject(slot.slotBase())->lastIndex()); + if (mode == IncludeDontEnumProperties) + propertyNames.add(exec->propertyNames().lastIndex); + Base::getPropertyNames(object, exec, propertyNames, mode); } -void RegExpObject::put(ExecState* exec, const Identifier& propertyName, JSValuePtr value, PutPropertySlot& slot) +static bool reject(ExecState* exec, bool throwException, const char* message) { - lookupPut(exec, propertyName, value, ExecState::regExpTable(exec), this, slot); + if (throwException) + throwTypeError(exec, ASCIILiteral(message)); + return false; } -void setRegExpObjectLastIndex(ExecState* exec, JSObject* baseObject, JSValuePtr value) +bool RegExpObject::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow) { - asRegExpObject(baseObject)->setLastIndex(value.toInteger(exec)); + if (propertyName == exec->propertyNames().lastIndex) { + RegExpObject* regExp = asRegExpObject(object); + if (descriptor.configurablePresent() && descriptor.configurable()) + return reject(exec, shouldThrow, "Attempting to change configurable attribute of unconfigurable property."); + if (descriptor.enumerablePresent() && descriptor.enumerable()) + return reject(exec, shouldThrow, "Attempting to change enumerable attribute of unconfigurable property."); + if (descriptor.isAccessorDescriptor()) + return reject(exec, shouldThrow, "Attempting to change access mechanism for an unconfigurable property."); + if (!regExp->m_lastIndexIsWritable) { + if (descriptor.writablePresent() && descriptor.writable()) + return reject(exec, shouldThrow, "Attempting to change writable attribute of unconfigurable property."); + if (!sameValue(exec, regExp->getLastIndex(), descriptor.value())) + return reject(exec, shouldThrow, "Attempting to change value of a readonly property."); + return true; + } + if (descriptor.writablePresent() && !descriptor.writable()) + regExp->m_lastIndexIsWritable = false; + if (descriptor.value()) + regExp->setLastIndex(exec, descriptor.value(), false); + return true; + } + + return Base::defineOwnProperty(object, exec, propertyName, descriptor, shouldThrow); } -JSValuePtr RegExpObject::test(ExecState* exec, const ArgList& args) +EncodedJSValue regExpObjectGlobal(ExecState*, JSObject* slotBase, EncodedJSValue, PropertyName) { - return jsBoolean(match(exec, args)); + return JSValue::encode(jsBoolean(asRegExpObject(slotBase)->regExp()->global())); } -JSValuePtr RegExpObject::exec(ExecState* exec, const ArgList& args) +EncodedJSValue regExpObjectIgnoreCase(ExecState*, JSObject* slotBase, EncodedJSValue, PropertyName) { - if (match(exec, args)) - return exec->lexicalGlobalObject()->regExpConstructor()->arrayOfMatches(exec); - return jsNull(); + return JSValue::encode(jsBoolean(asRegExpObject(slotBase)->regExp()->ignoreCase())); +} + +EncodedJSValue regExpObjectMultiline(ExecState*, JSObject* slotBase, EncodedJSValue, PropertyName) +{ + return JSValue::encode(jsBoolean(asRegExpObject(slotBase)->regExp()->multiline())); } -static JSValuePtr callRegExpObject(ExecState* exec, JSObject* function, JSValuePtr, const ArgList& args) +template +static inline void appendLineTerminatorEscape(StringBuilder&, CharacterType); + +template <> +inline void appendLineTerminatorEscape(StringBuilder& builder, LChar lineTerminator) { - return asRegExpObject(function)->exec(exec, args); + if (lineTerminator == '\n') + builder.append('n'); + else + builder.append('r'); } -CallType RegExpObject::getCallData(CallData& callData) +template <> +inline void appendLineTerminatorEscape(StringBuilder& builder, UChar lineTerminator) { - callData.native.function = callRegExpObject; - return CallTypeHost; + if (lineTerminator == '\n') + builder.append('n'); + else if (lineTerminator == '\r') + builder.append('r'); + else if (lineTerminator == 0x2028) + builder.appendLiteral("u2028"); + else + builder.appendLiteral("u2029"); } -// Shared implementation used by test and exec. -bool RegExpObject::match(ExecState* exec, const ArgList& args) +template +static inline JSValue regExpObjectSourceInternal(ExecState* exec, String pattern, const CharacterType* characters, unsigned length) { - RegExpConstructor* regExpConstructor = exec->lexicalGlobalObject()->regExpConstructor(); + bool previousCharacterWasBackslash = false; + bool inBrackets = false; + bool shouldEscape = false; - UString input = args.isEmpty() ? regExpConstructor->input() : args.at(exec, 0).toString(exec); - if (input.isNull()) { - throwError(exec, GeneralError, "No input to " + toString(exec) + "."); - return 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 jsNontrivialString(exec, ASCIILiteral("(?:)")); + + // early return for strings that don't contain a forwards slash and LineTerminator + for (unsigned i = 0; i < length; ++i) { + CharacterType ch = characters[i]; + if (!previousCharacterWasBackslash) { + if (inBrackets) { + if (ch == ']') + inBrackets = false; + } else { + if (ch == '/') { + shouldEscape = true; + break; + } + if (ch == '[') + inBrackets = true; + } + } + + if (Lexer::isLineTerminator(ch)) { + shouldEscape = true; + break; + } + + if (previousCharacterWasBackslash) + previousCharacterWasBackslash = false; + else + previousCharacterWasBackslash = ch == '\\'; } - if (!regExp()->global()) { - int position; - int length; - regExpConstructor->performMatch(d->regExp.get(), input, 0, position, length); - return position >= 0; + if (!shouldEscape) + return jsString(exec, pattern); + + previousCharacterWasBackslash = false; + inBrackets = false; + StringBuilder result; + for (unsigned i = 0; i < length; ++i) { + CharacterType ch = characters[i]; + if (!previousCharacterWasBackslash) { + if (inBrackets) { + if (ch == ']') + inBrackets = false; + } else { + if (ch == '/') + result.append('\\'); + else if (ch == '[') + inBrackets = true; + } + } + + // escape LineTerminator + if (Lexer::isLineTerminator(ch)) { + if (!previousCharacterWasBackslash) + result.append('\\'); + + appendLineTerminatorEscape(result, ch); + } else + result.append(ch); + + if (previousCharacterWasBackslash) + previousCharacterWasBackslash = false; + else + previousCharacterWasBackslash = ch == '\\'; } - if (d->lastIndex < 0 || d->lastIndex > input.size()) { - d->lastIndex = 0; - return false; + return jsString(exec, result.toString()); +} + + + +EncodedJSValue regExpObjectSource(ExecState* exec, JSObject* slotBase, EncodedJSValue, PropertyName) +{ + String pattern = asRegExpObject(slotBase)->regExp()->pattern(); + if (pattern.is8Bit()) + return JSValue::encode(regExpObjectSourceInternal(exec, pattern, pattern.characters8(), pattern.length())); + return JSValue::encode(regExpObjectSourceInternal(exec, pattern, pattern.characters16(), pattern.length())); +} + +void RegExpObject::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot) +{ + if (propertyName == exec->propertyNames().lastIndex) { + asRegExpObject(cell)->setLastIndex(exec, value, slot.isStrictMode()); + return; } + Base::put(cell, exec, propertyName, value, slot); +} - int position; - int length; - regExpConstructor->performMatch(d->regExp.get(), input, static_cast(d->lastIndex), position, length); - if (position < 0) { - d->lastIndex = 0; - return false; +JSValue RegExpObject::exec(ExecState* exec, JSString* string) +{ + if (MatchResult result = match(exec, string)) + return RegExpMatchesArray::create(exec, string, regExp(), result); + return jsNull(); +} + +// Shared implementation used by test and exec. +MatchResult RegExpObject::match(ExecState* exec, JSString* string) +{ + RegExp* regExp = this->regExp(); + RegExpConstructor* regExpConstructor = exec->lexicalGlobalObject()->regExpConstructor(); + String input = string->value(exec); + VM& vm = exec->vm(); + if (!regExp->global()) + return regExpConstructor->performMatch(vm, regExp, string, input, 0); + + JSValue jsLastIndex = getLastIndex(); + unsigned lastIndex; + if (LIKELY(jsLastIndex.isUInt32())) { + lastIndex = jsLastIndex.asUInt32(); + if (lastIndex > input.length()) { + setLastIndex(exec, 0); + return MatchResult::failed(); + } + } else { + double doubleLastIndex = jsLastIndex.toInteger(exec); + if (doubleLastIndex < 0 || doubleLastIndex > input.length()) { + setLastIndex(exec, 0); + return MatchResult::failed(); + } + lastIndex = static_cast(doubleLastIndex); } - d->lastIndex = position + length; - return true; + MatchResult result = regExpConstructor->performMatch(vm, regExp, string, input, lastIndex); + setLastIndex(exec, result.end); + return result; } } // namespace JSC