/* * 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