blob: 0d35cce3be6c62f283427c0979c10a0a44b9b1b6 [file] [log] [blame]
/*
* 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"
namespace JSC {
ASSERT_HAS_TRIVIAL_DESTRUCTOR(JSScope);
void JSScope::visitChildren(JSCell* cell, SlotVisitor& visitor)
{
JSScope* thisObject = jsCast<JSScope*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, JSObject::s_classinfo());
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<const JSGlobalObject*>(this)->isDynamicScope(requiresDynamicChecks);
case ActivationObjectType:
return static_cast<const JSActivation*>(this)->isDynamicScope(requiresDynamicChecks);
case NameScopeObjectType:
return static_cast<const JSNameScope*>(this)->isDynamicScope(requiresDynamicChecks);
default:
ASSERT_NOT_REACHED();
break;
}
return false;
}
JSObject* JSScope::objectAtScope(JSScope* scope)
{
JSObject* object = scope;
if (object->structure()->typeInfo().type() == WithScopeType)
return jsCast<JSWithScope*>(object)->object();
return object;
}
int JSScope::localDepth()
{
int scopeDepth = 0;
ScopeChainIterator iter = this->begin();
ScopeChainIterator end = this->end();
while (!iter->inherits(JSActivation::s_classinfo())) {
++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<JSVariableObject*>(scope)->registerAt(pc->m_offset).get());
result.setValue(jsCast<JSVariableObject*>(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->globalData(), callFrame->codeBlock()->ownerExecutable(), structure);
pc->m_offset = slot.cachedOffset();
result.setValue(value);
return true;
}
}
}
}
template <JSScope::LookupMode mode, JSScope::ReturnValues returnValues> JSObject* JSScope::resolveContainingScopeInternal(CallFrame* callFrame, const Identifier& identifier, PropertySlot& slot, Vector<ResolveOperation>* 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<JSActivation*>(activation.asCell())->isDynamicScope(requiresDynamicChecks);
if (!requiresDynamicChecks) {
ASSERT(jsCast<JSActivation*>(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<JSGlobalObject*>(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->globalData(), 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<JSVariableObject*>(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<JSNameScope*>(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->globalData(), 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 <JSScope::ReturnValues returnValues> JSObject* JSScope::resolveContainingScope(CallFrame* callFrame, const Identifier& identifier, PropertySlot& slot, Vector<ResolveOperation>* operations, PutToBaseOperation* putToBaseOperation, bool isStrict)
{
if (operations->size())
return resolveContainingScopeInternal<KnownResolve, returnValues>(callFrame, identifier, slot, operations, putToBaseOperation, isStrict);
JSObject* result = resolveContainingScopeInternal<UnknownResolve, returnValues>(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<ReturnValue>(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<ReturnBase>(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<ReturnBaseAndValue>(callFrame, identifier, slot, operations, putToBaseOperations, false)) {
ASSERT(operations->size());
JSValue value = slot.getValue(callFrame, identifier);
if (callFrame->globalData().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<ReturnThisAndValue>(callFrame, identifier, slot, operations, 0, false)) {
ASSERT(operations->size());
JSValue value = slot.getValue(callFrame, identifier);
if (callFrame->globalData().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<JSObject*>(base);
if (baseObject != callFrame->lexicalGlobalObject()) {
if (baseObject->isGlobalObject())
ASSERT(!jsCast<JSGlobalObject*>(baseObject)->assertRegisterIsInThisObject(operation->m_registerAddress));
goto genericHandler;
}
}
operation->m_registerAddress->set(callFrame->globalData(), base.asCell(), value);
return;
case PutToBaseOperation::VariablePut: {
if (operation->m_isDynamic) {
JSObject* baseObject = jsCast<JSObject*>(base);
if (baseObject->structure() != operation->m_structure.get())
goto genericHandler;
}
JSVariableObject* variableObject = jsCast<JSVariableObject*>(base);
variableObject->registerAt(operation->m_offset).set(callFrame->globalData(), variableObject, value);
return;
}
case PutToBaseOperation::GlobalPropertyPut: {
JSObject* object = jsCast<JSObject*>(base);
if (operation->m_structure.get() != object->structure())
break;
object->putDirect(callFrame->globalData(), 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<JSObject*>(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->globalData(), 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