X-Git-Url: https://git.saurik.com/apple/javascriptcore.git/blobdiff_plain/a253471d7f8e4d91bf6ebabab00155c3b387d3d0..93a3786624b2768d89bfa27e46598dc64e2fb70a:/runtime/JSScope.cpp?ds=sidebyside diff --git a/runtime/JSScope.cpp b/runtime/JSScope.cpp new file mode 100644 index 0000000..69ff1e4 --- /dev/null +++ b/runtime/JSScope.cpp @@ -0,0 +1,635 @@ +/* + * Copyright (C) 2012 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "JSScope.h" + +#include "JSActivation.h" +#include "JSGlobalObject.h" +#include "JSNameScope.h" +#include "JSWithScope.h" +#include "Operations.h" + +namespace JSC { + +ASSERT_HAS_TRIVIAL_DESTRUCTOR(JSScope); + +void JSScope::visitChildren(JSCell* cell, SlotVisitor& visitor) +{ + JSScope* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, &s_info); + COMPILE_ASSERT(StructureFlags & OverridesVisitChildren, OverridesVisitChildrenWithoutSettingFlag); + ASSERT(thisObject->structure()->typeInfo().overridesVisitChildren()); + + Base::visitChildren(thisObject, visitor); + visitor.append(&thisObject->m_next); +} + +bool JSScope::isDynamicScope(bool& requiresDynamicChecks) const +{ + switch (structure()->typeInfo().type()) { + case GlobalObjectType: + return static_cast(this)->isDynamicScope(requiresDynamicChecks); + case ActivationObjectType: + return static_cast(this)->isDynamicScope(requiresDynamicChecks); + case NameScopeObjectType: + return static_cast(this)->isDynamicScope(requiresDynamicChecks); + default: + RELEASE_ASSERT_NOT_REACHED(); + break; + } + + return false; +} + +JSObject* JSScope::objectAtScope(JSScope* scope) +{ + JSObject* object = scope; + if (object->structure()->typeInfo().type() == WithScopeType) + return jsCast(object)->object(); + + return object; +} + +int JSScope::localDepth() +{ + int scopeDepth = 0; + ScopeChainIterator iter = this->begin(); + ScopeChainIterator end = this->end(); + while (!iter->inherits(&JSActivation::s_info)) { + ++iter; + if (iter == end) + break; + ++scopeDepth; + } + return scopeDepth; +} + +struct LookupResult { + JSValue base() const { return m_base; } + JSValue value() const { return m_value; } + void setBase(JSValue base) { ASSERT(base); m_base = base; } + void setValue(JSValue value) { ASSERT(value); m_value = value; } + +private: + JSValue m_base; + JSValue m_value; +}; + + +static void setPutPropertyAccessOffset(PutToBaseOperation* operation, PropertyOffset offset) +{ + ASSERT(isOutOfLineOffset(offset)); + operation->m_offset = offset; + operation->m_offsetInButterfly = offsetInButterfly(offset); +} + +static bool executeResolveOperations(CallFrame* callFrame, JSScope* scope, const Identifier& propertyName, ResolveOperation* pc, LookupResult& result) +{ + while (true) { + switch (pc->m_operation) { + case ResolveOperation::Fail: + return false; + case ResolveOperation::CheckForDynamicEntriesBeforeGlobalScope: { + while (JSScope* nextScope = scope->next()) { + if (scope->isActivationObject() && scope->structure() != scope->globalObject()->activationStructure()) + return false; + ASSERT(scope->isNameScopeObject() || scope->isVariableObject() || scope->isGlobalObject()); + scope = nextScope; + } + pc++; + break; + } + case ResolveOperation::SetBaseToUndefined: + result.setBase(jsUndefined()); + pc++; + continue; + case ResolveOperation::SetBaseToScope: + result.setBase(scope); + pc++; + continue; + case ResolveOperation::ReturnScopeAsBase: + result.setBase(scope); + return true; + case ResolveOperation::SetBaseToGlobal: + result.setBase(scope->globalObject()); + pc++; + continue; + case ResolveOperation::SkipScopes: { + int count = pc->m_scopesToSkip; + while (count--) + scope = scope->next(); + ASSERT(scope); + pc++; + continue; + } + case ResolveOperation::SkipTopScopeNode: + if (callFrame->r(pc->m_activationRegister).jsValue()) + scope = scope->next(); + ASSERT(scope); + pc++; + continue; + case ResolveOperation::GetAndReturnScopedVar: + ASSERT(jsCast(scope)->registerAt(pc->m_offset).get()); + result.setValue(jsCast(scope)->registerAt(pc->m_offset).get()); + return true; + case ResolveOperation::GetAndReturnGlobalVar: + result.setValue(pc->m_registerAddress->get()); + return true; + case ResolveOperation::GetAndReturnGlobalVarWatchable: + result.setValue(pc->m_registerAddress->get()); + return true; + case ResolveOperation::ReturnGlobalObjectAsBase: + result.setBase(callFrame->lexicalGlobalObject()); + return true; + case ResolveOperation::GetAndReturnGlobalProperty: { + JSGlobalObject* globalObject = scope->globalObject(); + if (globalObject->structure() == pc->m_structure.get()) { + result.setValue(globalObject->getDirect(pc->m_offset)); + return true; + } + + PropertySlot slot(globalObject); + if (!globalObject->getPropertySlot(callFrame, propertyName, slot)) + return false; + + JSValue value = slot.getValue(callFrame, propertyName); + if (callFrame->hadException()) + return false; + + Structure* structure = globalObject->structure(); + + // Don't try to cache prototype lookups + if (globalObject != slot.slotBase() || !slot.isCacheableValue() || !structure->propertyAccessesAreCacheable()) { + result.setValue(value); + return true; + } + + pc->m_structure.set(callFrame->vm(), callFrame->codeBlock()->ownerExecutable(), structure); + pc->m_offset = slot.cachedOffset(); + result.setValue(value); + return true; + } + } + } +} + +template JSObject* JSScope::resolveContainingScopeInternal(CallFrame* callFrame, const Identifier& identifier, PropertySlot& slot, Vector* operations, PutToBaseOperation* putToBaseOperation, bool ) +{ + JSScope* scope = callFrame->scope(); + ASSERT(scope); + int scopeCount = 0; + bool seenGenericObjectScope = false; + bool requiresDynamicChecks = false; + bool skipTopScopeNode = false; + int activationRegister = 0; + CodeBlock* codeBlock = callFrame->codeBlock(); + if (mode == UnknownResolve) { + ASSERT(operations->isEmpty()); + if (codeBlock->codeType() == FunctionCode && codeBlock->needsActivation()) { + activationRegister = codeBlock->activationRegister(); + JSValue activation = callFrame->r(activationRegister).jsValue(); + + // If the activation register doesn't match our actual scope, a dynamic + // scope has been inserted so we shouldn't skip the top scope node. + if (activation == scope) { + jsCast(activation.asCell())->isDynamicScope(requiresDynamicChecks); + if (!requiresDynamicChecks) { + ASSERT(jsCast(activation.asCell())->symbolTable()->get(identifier.impl()).isNull()); + scope = scope->next(); + ASSERT(scope); + skipTopScopeNode = true; + } + } else if (!activation) + skipTopScopeNode = true; + } + } else + ASSERT(operations->size()); + + if (codeBlock->codeType() == EvalCode && scope->next()) + requiresDynamicChecks = true; + + if (mode == UnknownResolve && putToBaseOperation) + putToBaseOperation->m_kind = PutToBaseOperation::Generic; + + do { + JSObject* object = JSScope::objectAtScope(scope); + slot = PropertySlot(object); + + bool currentScopeNeedsDynamicChecks = false; + if (!(scope->isVariableObject() || scope->isNameScopeObject()) || (scope->next() && scope->isDynamicScope(currentScopeNeedsDynamicChecks))) + seenGenericObjectScope = true; + + requiresDynamicChecks = requiresDynamicChecks || currentScopeNeedsDynamicChecks; + + if (object->getPropertySlot(callFrame, identifier, slot)) { + if (mode == UnknownResolve) { + if (seenGenericObjectScope) + goto fail; + if (putToBaseOperation) + putToBaseOperation->m_isDynamic = requiresDynamicChecks; + if (!scope->next()) { + // Global lookup of some kind + JSGlobalObject* globalObject = jsCast(scope); + SymbolTableEntry entry = globalObject->symbolTable()->get(identifier.impl()); + if (!entry.isNull()) { + if (requiresDynamicChecks) + operations->append(ResolveOperation::checkForDynamicEntriesBeforeGlobalScope()); + + if (putToBaseOperation) { + putToBaseOperation->m_isDynamic = requiresDynamicChecks; + if (entry.isReadOnly()) + putToBaseOperation->m_kind = PutToBaseOperation::Readonly; + else if (entry.couldBeWatched()) { + putToBaseOperation->m_kind = PutToBaseOperation::GlobalVariablePutChecked; + putToBaseOperation->m_predicatePointer = entry.addressOfIsWatched(); + } else + putToBaseOperation->m_kind = PutToBaseOperation::GlobalVariablePut; + putToBaseOperation->m_registerAddress = &globalObject->registerAt(entry.getIndex()); + } + // Override custom accessor behaviour that the DOM introduces for some + // event handlers declared on function declarations. + if (!requiresDynamicChecks) + slot.setValue(globalObject, globalObject->registerAt(entry.getIndex()).get()); + switch (returnValues) { + case ReturnValue: + ASSERT(!putToBaseOperation); + operations->append(ResolveOperation::getAndReturnGlobalVar(&globalObject->registerAt(entry.getIndex()), entry.couldBeWatched())); + break; + case ReturnBase: + ASSERT(putToBaseOperation); + operations->append(ResolveOperation::returnGlobalObjectAsBase()); + break; + case ReturnBaseAndValue: + ASSERT(putToBaseOperation); + operations->append(ResolveOperation::setBaseToGlobal()); + operations->append(ResolveOperation::getAndReturnGlobalVar(&globalObject->registerAt(entry.getIndex()), entry.couldBeWatched())); + break; + case ReturnThisAndValue: + ASSERT(!putToBaseOperation); + operations->append(ResolveOperation::setBaseToUndefined()); + operations->append(ResolveOperation::getAndReturnGlobalVar(&globalObject->registerAt(entry.getIndex()), entry.couldBeWatched())); + break; + } + } else { + if (!slot.isCacheableValue() || slot.slotBase() != globalObject) + goto fail; + + if (requiresDynamicChecks) + operations->append(ResolveOperation::checkForDynamicEntriesBeforeGlobalScope()); + + if (putToBaseOperation) { + putToBaseOperation->m_isDynamic = requiresDynamicChecks; + putToBaseOperation->m_kind = PutToBaseOperation::GlobalPropertyPut; + putToBaseOperation->m_structure.set(callFrame->vm(), callFrame->codeBlock()->ownerExecutable(), globalObject->structure()); + setPutPropertyAccessOffset(putToBaseOperation, slot.cachedOffset()); + } + switch (returnValues) { + case ReturnValue: + ASSERT(!putToBaseOperation); + operations->append(ResolveOperation::getAndReturnGlobalProperty()); + break; + case ReturnBase: + ASSERT(putToBaseOperation); + operations->append(ResolveOperation::returnGlobalObjectAsBase()); + break; + case ReturnBaseAndValue: + ASSERT(putToBaseOperation); + operations->append(ResolveOperation::setBaseToGlobal()); + operations->append(ResolveOperation::getAndReturnGlobalProperty()); + break; + case ReturnThisAndValue: + ASSERT(!putToBaseOperation); + operations->append(ResolveOperation::setBaseToUndefined()); + operations->append(ResolveOperation::getAndReturnGlobalProperty()); + break; + } + } + return object; + } + if (!requiresDynamicChecks) { + // Normal lexical lookup + JSVariableObject* variableObject = jsCast(scope); + ASSERT(variableObject); + ASSERT(variableObject->symbolTable()); + SymbolTableEntry entry = variableObject->symbolTable()->get(identifier.impl()); + // Defend against the variable being actually inserted by eval. + if (entry.isNull()) { + ASSERT(!jsDynamicCast(variableObject)); + goto fail; + } + // If we're getting the 'arguments' then give up on life. + if (identifier == callFrame->propertyNames().arguments) + goto fail; + + if (putToBaseOperation) { + putToBaseOperation->m_kind = entry.isReadOnly() ? PutToBaseOperation::Readonly : PutToBaseOperation::VariablePut; + putToBaseOperation->m_structure.set(callFrame->vm(), callFrame->codeBlock()->ownerExecutable(), callFrame->lexicalGlobalObject()->activationStructure()); + putToBaseOperation->m_offset = entry.getIndex(); + putToBaseOperation->m_scopeDepth = (skipTopScopeNode ? 1 : 0) + scopeCount; + } + + if (skipTopScopeNode) + operations->append(ResolveOperation::skipTopScopeNode(activationRegister)); + + operations->append(ResolveOperation::skipScopes(scopeCount)); + switch (returnValues) { + case ReturnBaseAndValue: + operations->append(ResolveOperation::setBaseToScope()); + operations->append(ResolveOperation::getAndReturnScopedVar(entry.getIndex())); + break; + + case ReturnBase: + operations->append(ResolveOperation::returnScopeAsBase()); + break; + + case ReturnThisAndValue: + operations->append(ResolveOperation::setBaseToUndefined()); + // fallthrough + case ReturnValue: + operations->append(ResolveOperation::getAndReturnScopedVar(entry.getIndex())); + break; + } + return object; + } + fail: + if (!operations->size()) + operations->append(ResolveOperation::resolveFail()); + } + return object; + } + scopeCount++; + } while ((scope = scope->next())); + + if (mode == UnknownResolve) { + ASSERT(operations->isEmpty()); + if (seenGenericObjectScope) { + operations->append(ResolveOperation::resolveFail()); + return 0; + } + if (putToBaseOperation) { + putToBaseOperation->m_isDynamic = requiresDynamicChecks; + putToBaseOperation->m_kind = PutToBaseOperation::GlobalPropertyPut; + putToBaseOperation->m_structure.clear(); + putToBaseOperation->m_offset = -1; + } + if (requiresDynamicChecks) + operations->append(ResolveOperation::checkForDynamicEntriesBeforeGlobalScope()); + switch (returnValues) { + case ReturnValue: + ASSERT(!putToBaseOperation); + operations->append(ResolveOperation::getAndReturnGlobalProperty()); + break; + case ReturnBase: + ASSERT(putToBaseOperation); + operations->append(ResolveOperation::returnGlobalObjectAsBase()); + break; + case ReturnBaseAndValue: + ASSERT(putToBaseOperation); + operations->append(ResolveOperation::setBaseToGlobal()); + operations->append(ResolveOperation::getAndReturnGlobalProperty()); + break; + case ReturnThisAndValue: + ASSERT(!putToBaseOperation); + operations->append(ResolveOperation::setBaseToUndefined()); + operations->append(ResolveOperation::getAndReturnGlobalProperty()); + break; + } + } + return 0; +} + +template JSObject* JSScope::resolveContainingScope(CallFrame* callFrame, const Identifier& identifier, PropertySlot& slot, Vector* operations, PutToBaseOperation* putToBaseOperation, bool isStrict) +{ + if (operations->size()) + return resolveContainingScopeInternal(callFrame, identifier, slot, operations, putToBaseOperation, isStrict); + JSObject* result = resolveContainingScopeInternal(callFrame, identifier, slot, operations, putToBaseOperation, isStrict); + operations->shrinkToFit(); + return result; +} + +JSValue JSScope::resolve(CallFrame* callFrame, const Identifier& identifier, ResolveOperations* operations) +{ + ASSERT(operations); + LookupResult fastResult; + if (operations->size() && executeResolveOperations(callFrame, callFrame->scope(), identifier, operations->data(), fastResult)) { + ASSERT(fastResult.value()); + ASSERT(!callFrame->hadException()); + return fastResult.value(); + } + + if (callFrame->hadException()) + return JSValue(); + + PropertySlot slot; + if (JSScope::resolveContainingScope(callFrame, identifier, slot, operations, 0, false)) { + ASSERT(operations->size()); + return slot.getValue(callFrame, identifier); + } + ASSERT(operations->size()); + + return throwError(callFrame, createUndefinedVariableError(callFrame, identifier)); +} + +JSValue JSScope::resolveBase(CallFrame* callFrame, const Identifier& identifier, bool isStrict, ResolveOperations* operations, PutToBaseOperation* putToBaseOperations) +{ + ASSERT(operations); + ASSERT_UNUSED(putToBaseOperations, putToBaseOperations); + LookupResult fastResult; + if (operations->size() && executeResolveOperations(callFrame, callFrame->scope(), identifier, operations->data(), fastResult)) { + ASSERT(fastResult.base()); + ASSERT(!callFrame->hadException()); + return fastResult.base(); + } + + if (callFrame->hadException()) + return JSValue(); + + PropertySlot slot; + if (JSObject* base = JSScope::resolveContainingScope(callFrame, identifier, slot, operations, putToBaseOperations, isStrict)) { + ASSERT(operations->size()); + return base; + } + + if (!isStrict) + return callFrame->lexicalGlobalObject(); + + return throwError(callFrame, createErrorForInvalidGlobalAssignment(callFrame, identifier.string())); +} + +JSValue JSScope::resolveWithBase(CallFrame* callFrame, const Identifier& identifier, Register* base, ResolveOperations* operations, PutToBaseOperation* putToBaseOperations) +{ + ASSERT(operations); + ASSERT_UNUSED(putToBaseOperations, putToBaseOperations); + LookupResult fastResult; + if (operations->size() && executeResolveOperations(callFrame, callFrame->scope(), identifier, operations->data(), fastResult)) { + ASSERT(fastResult.base()); + ASSERT(fastResult.value()); + ASSERT(!callFrame->hadException()); + *base = fastResult.base(); + return fastResult.value(); + } + + if (callFrame->hadException()) + return JSValue(); + + PropertySlot slot; + if (JSObject* propertyBase = JSScope::resolveContainingScope(callFrame, identifier, slot, operations, putToBaseOperations, false)) { + ASSERT(operations->size()); + JSValue value = slot.getValue(callFrame, identifier); + if (callFrame->vm().exception) + return JSValue(); + + *base = propertyBase; + return value; + } + ASSERT(operations->size()); + + return throwError(callFrame, createUndefinedVariableError(callFrame, identifier)); +} + +JSValue JSScope::resolveWithThis(CallFrame* callFrame, const Identifier& identifier, Register* base, ResolveOperations* operations) +{ + ASSERT(operations); + LookupResult fastResult; + if (operations->size() && executeResolveOperations(callFrame, callFrame->scope(), identifier, operations->data(), fastResult)) { + ASSERT(fastResult.base()); + ASSERT(fastResult.value()); + ASSERT(!callFrame->hadException()); + *base = fastResult.base(); + return fastResult.value(); + } + + if (callFrame->hadException()) + return JSValue(); + + PropertySlot slot; + if (JSObject* propertyBase = JSScope::resolveContainingScope(callFrame, identifier, slot, operations, 0, false)) { + ASSERT(operations->size()); + JSValue value = slot.getValue(callFrame, identifier); + if (callFrame->vm().exception) + return JSValue(); + ASSERT(value); + *base = propertyBase->structure()->typeInfo().isEnvironmentRecord() ? jsUndefined() : JSValue(propertyBase); + return value; + } + ASSERT(operations->size()); + + return throwError(callFrame, createUndefinedVariableError(callFrame, identifier)); +} + +void JSScope::resolvePut(CallFrame* callFrame, JSValue base, const Identifier& property, JSValue value, PutToBaseOperation* operation) +{ + ASSERT_UNUSED(operation, operation); + ASSERT(base); + ASSERT(value); + switch (operation->m_kind) { + case PutToBaseOperation::Uninitialised: + CRASH(); + + case PutToBaseOperation::Readonly: + return; + + case PutToBaseOperation::GlobalVariablePutChecked: + if (*operation->m_predicatePointer) + goto genericHandler; + case PutToBaseOperation::GlobalVariablePut: + if (operation->m_isDynamic) { + JSObject* baseObject = jsCast(base); + if (baseObject != callFrame->lexicalGlobalObject()) { + if (baseObject->isGlobalObject()) + ASSERT(!jsCast(baseObject)->assertRegisterIsInThisObject(operation->m_registerAddress)); + goto genericHandler; + } + } + operation->m_registerAddress->set(callFrame->vm(), base.asCell(), value); + return; + + case PutToBaseOperation::VariablePut: { + if (operation->m_isDynamic) { + JSObject* baseObject = jsCast(base); + if (baseObject->structure() != operation->m_structure.get()) + goto genericHandler; + } + JSVariableObject* variableObject = jsCast(base); + variableObject->registerAt(operation->m_offset).set(callFrame->vm(), variableObject, value); + return; + } + + case PutToBaseOperation::GlobalPropertyPut: { + JSObject* object = jsCast(base); + if (operation->m_structure.get() != object->structure()) + break; + object->putDirect(callFrame->vm(), operation->m_offset, value); + return; + } + + genericHandler: + case PutToBaseOperation::Generic: + PutPropertySlot slot(operation->m_isStrict); + base.put(callFrame, property, value, slot); + return; + } + ASSERT(operation->m_kind == PutToBaseOperation::GlobalPropertyPut); + PutPropertySlot slot(operation->m_isStrict); + base.put(callFrame, property, value, slot); + if (!slot.isCacheable()) + return; + if (callFrame->hadException()) + return; + JSObject* baseObject = jsCast(base); + if (!baseObject->structure()->propertyAccessesAreCacheable()) + return; + if (slot.base() != callFrame->lexicalGlobalObject()) + return; + if (slot.base() != baseObject) + return; + ASSERT(!baseObject->hasInlineStorage()); + operation->m_structure.set(callFrame->vm(), callFrame->codeBlock()->ownerExecutable(), baseObject->structure()); + setPutPropertyAccessOffset(operation, slot.cachedOffset()); + return; +} + +JSValue JSScope::resolveGlobal(CallFrame* callFrame, const Identifier& identifier, JSGlobalObject* globalObject, ResolveOperation* resolveOperation) +{ + ASSERT(resolveOperation); + ASSERT(resolveOperation->m_operation == ResolveOperation::GetAndReturnGlobalProperty); + ASSERT_UNUSED(globalObject, callFrame->lexicalGlobalObject() == globalObject); + + LookupResult fastResult; + if (executeResolveOperations(callFrame, callFrame->scope(), identifier, resolveOperation, fastResult)) { + ASSERT(fastResult.value()); + ASSERT(!callFrame->hadException()); + return fastResult.value(); + } + + if (callFrame->hadException()) + return JSValue(); + + return throwError(callFrame, createUndefinedVariableError(callFrame, identifier)); +} + + +} // namespace JSC