Commit 08c9629f authored by keuchel@chromium.org's avatar keuchel@chromium.org

Static resolution of outer variables in eval code.

So far free variables references in eval code are not statically
resolved. For example in
    function foo() { var x = 1; eval("y = x"); }
the variable x will get mode DYNAMIC and y will get mode DYNAMIC_GLOBAL,
i.e. free variable references trigger dynamic lookups with a fast case
handling for global variables.

The CL introduces static resolution of free variables references in eval
code. If possible variable references are resolved to bindings belonging to
outer scopes of the eval call site.

This is achieved by deserializing the outer scope chain using
Scope::DeserializeScopeChain prior to parsing the eval code similar to lazy
parsing of functions. The existing code for variable resolution is used,
however resolution starts at the first outer unresolved scope instead of
always starting at the root of the scope tree.

This is a prerequisite for statically checking validity of assignments in
the extended code as specified by the current ES.next draft which will be
introduced by a subsequent CL. More specifically section 11.13 of revision 4
of the ES.next draft reads:
* It is a Syntax Error if the AssignmentExpression is contained in extended
  code and the LeftHandSideExpression is an Identifier that does not
  statically resolve to a declarative environment record binding or if the
  resolved binding is an immutable binding.

TEST=existing tests in mjsunit

Review URL: http://codereview.chromium.org/8508052

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@9999 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 4c41d327
......@@ -56,6 +56,7 @@ CompilationInfo::CompilationInfo(Handle<Script> script)
flags_(0),
function_(NULL),
scope_(NULL),
global_scope_(NULL),
script_(script),
extension_(NULL),
pre_parse_data_(NULL),
......@@ -69,6 +70,7 @@ CompilationInfo::CompilationInfo(Handle<SharedFunctionInfo> shared_info)
flags_(IsLazy::encode(true)),
function_(NULL),
scope_(NULL),
global_scope_(NULL),
shared_info_(shared_info),
script_(Handle<Script>(Script::cast(shared_info->script()))),
extension_(NULL),
......@@ -83,6 +85,7 @@ CompilationInfo::CompilationInfo(Handle<JSFunction> closure)
flags_(IsLazy::encode(true)),
function_(NULL),
scope_(NULL),
global_scope_(NULL),
closure_(closure),
shared_info_(Handle<SharedFunctionInfo>(closure->shared())),
script_(Handle<Script>(Script::cast(shared_info_->script()))),
......
......@@ -59,6 +59,7 @@ class CompilationInfo BASE_EMBEDDED {
bool is_in_loop() const { return IsInLoop::decode(flags_); }
FunctionLiteral* function() const { return function_; }
Scope* scope() const { return scope_; }
Scope* global_scope() const { return global_scope_; }
Handle<Code> code() const { return code_; }
Handle<JSFunction> closure() const { return closure_; }
Handle<SharedFunctionInfo> shared_info() const { return shared_info_; }
......@@ -99,6 +100,10 @@ class CompilationInfo BASE_EMBEDDED {
ASSERT(scope_ == NULL);
scope_ = scope;
}
void SetGlobalScope(Scope* global_scope) {
ASSERT(global_scope_ == NULL);
global_scope_ = global_scope;
}
void SetCode(Handle<Code> code) { code_ = code; }
void SetExtension(v8::Extension* extension) {
ASSERT(!is_lazy());
......@@ -228,6 +233,8 @@ class CompilationInfo BASE_EMBEDDED {
// The scope of the function literal as a convenience. Set to indicate
// that scopes have been analyzed.
Scope* scope_;
// The global scope provided as a convenience.
Scope* global_scope_;
// The compiled code.
Handle<Code> code_;
......
......@@ -240,62 +240,6 @@ Handle<Object> Context::Lookup(Handle<String> name,
}
bool Context::GlobalIfNotShadowedByEval(Handle<String> name) {
Context* context = this;
// Check that there is no local with the given name in contexts
// before the global context and check that there are no context
// extension objects (conservative check for with statements).
while (!context->IsGlobalContext()) {
// Check if the context is a catch or with context, or has introduced
// bindings by calling non-strict eval.
if (context->has_extension()) return false;
// Not a with context so it must be a function context.
ASSERT(context->IsFunctionContext());
// Check non-parameter locals.
Handle<ScopeInfo> scope_info(context->closure()->shared()->scope_info());
VariableMode mode;
InitializationFlag init_flag;
int index = scope_info->ContextSlotIndex(*name, &mode, &init_flag);
ASSERT(index < 0 || index >= MIN_CONTEXT_SLOTS);
if (index >= 0) return false;
// Check parameter locals.
int param_index = scope_info->ParameterIndex(*name);
if (param_index >= 0) return false;
// Check context only holding the function name variable.
index = scope_info->FunctionContextSlotIndex(*name, &mode);
if (index >= 0) return false;
context = context->previous();
}
// No local or potential with statement found so the variable is
// global unless it is shadowed by an eval-introduced variable.
return true;
}
void Context::ComputeEvalScopeInfo(bool* outer_scope_calls_non_strict_eval) {
// Skip up the context chain checking all the function contexts to see
// whether they call eval.
Context* context = this;
while (!context->IsGlobalContext()) {
if (context->IsFunctionContext()) {
if (context->closure()->shared()->scope_info()->CallsNonStrictEval()) {
// No need to go further since the answers will not change from
// here.
*outer_scope_calls_non_strict_eval = true;
return;
}
}
context = context->previous();
}
}
void Context::AddOptimizedFunction(JSFunction* function) {
ASSERT(IsGlobalContext());
#ifdef DEBUG
......
......@@ -398,19 +398,6 @@ class Context: public FixedArray {
PropertyAttributes* attributes,
BindingFlags* binding_flags);
// Determine if a local variable with the given name exists in a
// context. Do not consider context extension objects. This is
// used for compiling code using eval. If the context surrounding
// the eval call does not have a local variable with this name and
// does not contain a with statement the property is global unless
// it is shadowed by a property in an extension object introduced by
// eval.
bool GlobalIfNotShadowedByEval(Handle<String> name);
// Determine if any function scope in the context call eval and if
// any of those calls are in non-strict mode.
void ComputeEvalScopeInfo(bool* outer_scope_calls_non_strict_eval);
// Code generation support.
static int SlotOffset(int index) {
return kHeaderSize + index * kPointerSize - kHeapObjectTag;
......
......@@ -607,12 +607,11 @@ Parser::Parser(Handle<Script> script,
}
FunctionLiteral* Parser::ParseProgram(Handle<String> source,
bool in_global_context,
StrictModeFlag strict_mode) {
FunctionLiteral* Parser::ParseProgram(CompilationInfo* info) {
ZoneScope zone_scope(isolate(), DONT_DELETE_ON_EXIT);
HistogramTimerScope timer(isolate()->counters()->parse());
Handle<String> source(String::cast(script_->source()));
isolate()->counters()->total_parse_size()->Increment(source->length());
fni_ = new(zone()) FuncNameInferrer(isolate());
......@@ -625,18 +624,17 @@ FunctionLiteral* Parser::ParseProgram(Handle<String> source,
ExternalTwoByteStringUC16CharacterStream stream(
Handle<ExternalTwoByteString>::cast(source), 0, source->length());
scanner_.Initialize(&stream);
return DoParseProgram(source, in_global_context, strict_mode, &zone_scope);
return DoParseProgram(info, source, &zone_scope);
} else {
GenericStringUC16CharacterStream stream(source, 0, source->length());
scanner_.Initialize(&stream);
return DoParseProgram(source, in_global_context, strict_mode, &zone_scope);
return DoParseProgram(info, source, &zone_scope);
}
}
FunctionLiteral* Parser::DoParseProgram(Handle<String> source,
bool in_global_context,
StrictModeFlag strict_mode,
FunctionLiteral* Parser::DoParseProgram(CompilationInfo* info,
Handle<String> source,
ZoneScope* zone_scope) {
ASSERT(top_scope_ == NULL);
ASSERT(target_stack_ == NULL);
......@@ -646,16 +644,19 @@ FunctionLiteral* Parser::DoParseProgram(Handle<String> source,
mode_ = FLAG_lazy ? PARSE_LAZILY : PARSE_EAGERLY;
if (allow_natives_syntax_ || extension_ != NULL) mode_ = PARSE_EAGERLY;
ScopeType type = in_global_context ? GLOBAL_SCOPE : EVAL_SCOPE;
Handle<String> no_name = isolate()->factory()->empty_symbol();
FunctionLiteral* result = NULL;
{ Scope* scope = NewScope(top_scope_, type);
{ Scope* scope = NewScope(top_scope_, GLOBAL_SCOPE);
info->SetGlobalScope(scope);
if (!info->is_global()) {
scope = Scope::DeserializeScopeChain(*info->calling_context(), scope);
scope = NewScope(scope, EVAL_SCOPE);
}
scope->set_start_position(0);
scope->set_end_position(source->length());
FunctionState function_state(this, scope, isolate());
ASSERT(top_scope_->strict_mode_flag() == kNonStrictMode);
top_scope_->SetStrictModeFlag(strict_mode);
top_scope_->SetStrictModeFlag(info->strict_mode_flag());
ZoneList<Statement*>* body = new(zone()) ZoneList<Statement*>(16);
bool ok = true;
int beg_loc = scanner().location().beg_pos;
......@@ -742,8 +743,9 @@ FunctionLiteral* Parser::ParseLazy(CompilationInfo* info,
{
// Parse the function literal.
Scope* scope = NewScope(top_scope_, GLOBAL_SCOPE);
info->SetGlobalScope(scope);
if (!info->closure().is_null()) {
scope = Scope::DeserializeScopeChain(info, scope);
scope = Scope::DeserializeScopeChain(info->closure()->context(), scope);
}
FunctionState function_state(this, scope, isolate());
ASSERT(scope->strict_mode_flag() == kNonStrictMode ||
......@@ -1370,6 +1372,8 @@ VariableProxy* Parser::Declare(Handle<String> name,
// enclosing scope.
Scope* declaration_scope = (mode == LET || mode == CONST_HARMONY)
? top_scope_ : top_scope_->DeclarationScope();
InitializationFlag init_flag = (fun != NULL || mode == VAR)
? kCreatedInitialized : kNeedsInitialization;
// If a function scope exists, then we can statically declare this
// variable and also set its mode. In any case, a Declaration node
......@@ -1388,8 +1392,6 @@ VariableProxy* Parser::Declare(Handle<String> name,
var = declaration_scope->LocalLookup(name);
if (var == NULL) {
// Declare the name.
InitializationFlag init_flag = (fun != NULL || mode == VAR)
? kCreatedInitialized : kNeedsInitialization;
var = declaration_scope->DeclareLocal(name, mode, init_flag);
} else {
// The name was declared in this scope before; check for conflicting
......@@ -1452,17 +1454,31 @@ VariableProxy* Parser::Declare(Handle<String> name,
declaration_scope->AddDeclaration(
new(zone()) Declaration(proxy, mode, fun, top_scope_));
// For global const variables we bind the proxy to a variable.
if ((mode == CONST || mode == CONST_HARMONY) &&
declaration_scope->is_global_scope()) {
// For global const variables we bind the proxy to a variable.
ASSERT(resolve); // should be set by all callers
Variable::Kind kind = Variable::NORMAL;
var = new(zone()) Variable(declaration_scope,
name,
CONST,
mode,
true,
kind,
kNeedsInitialization);
} else if (declaration_scope->is_eval_scope() &&
!declaration_scope->is_strict_mode()) {
// For variable declarations in a non-strict eval scope the proxy is bound
// to a lookup variable to force a dynamic declaration using the
// DeclareContextSlot runtime function.
Variable::Kind kind = Variable::NORMAL;
var = new(zone()) Variable(declaration_scope,
name,
mode,
true,
kind,
init_flag);
var->AllocateTo(Variable::LOOKUP, -1);
resolve = true;
}
// If requested and we have a local variable, bind the proxy to the variable
......@@ -1909,22 +1925,30 @@ Block* Parser::ParseVariableDeclarations(
}
block->AddStatement(new(zone()) ExpressionStatement(initialize));
} else if (needs_init) {
// Constant initializations always assign to the declared constant which
// is always at the function scope level. This is only relevant for
// dynamically looked-up variables and constants (the start context for
// constant lookups is always the function context, while it is the top
// context for var declared variables). Sigh...
// For 'let' and 'const' declared variables in harmony mode the
// initialization also always assigns to the declared variable.
ASSERT(proxy != NULL);
ASSERT(proxy->var() != NULL);
ASSERT(value != NULL);
Assignment* assignment =
new(zone()) Assignment(isolate(), init_op, proxy, value, position);
block->AddStatement(new(zone()) ExpressionStatement(assignment));
value = NULL;
}
// Add an assignment node to the initialization statement block if we still
// have a pending initialization value. We must distinguish between
// different kinds of declarations: 'var' initializations are simply
// assignments (with all the consequences if they are inside a 'with'
// statement - they may change a 'with' object property). Constant
// initializations always assign to the declared constant which is
// always at the function scope level. This is only relevant for
// dynamically looked-up variables and constants (the start context
// for constant lookups is always the function context, while it is
// the top context for var declared variables). Sigh...
// For 'let' and 'const' declared variables in harmony mode the
// initialization is in the same scope as the declaration. Thus dynamic
// lookups are unnecessary even if the block scope is inside a with.
// have a pending initialization value.
if (value != NULL) {
ASSERT(mode == VAR);
// 'var' initializations are simply assignments (with all the consequences
// if they are inside a 'with' statement - they may change a 'with' object
// property).
VariableProxy* proxy = initialization_scope->NewUnresolved(name);
Assignment* assignment =
new(zone()) Assignment(isolate(), init_op, proxy, value, position);
......@@ -5405,6 +5429,7 @@ bool ParserApi::Parse(CompilationInfo* info) {
Handle<Script> script = info->script();
bool harmony_scoping = !info->is_native() && FLAG_harmony_scoping;
if (info->is_lazy()) {
ASSERT(!info->is_eval());
bool allow_natives_syntax =
FLAG_allow_natives_syntax ||
info->is_native();
......@@ -5433,10 +5458,7 @@ bool ParserApi::Parse(CompilationInfo* info) {
DeleteArray(args.start());
ASSERT(info->isolate()->has_pending_exception());
} else {
Handle<String> source = Handle<String>(String::cast(script->source()));
result = parser.ParseProgram(source,
info->is_global(),
info->strict_mode_flag());
result = parser.ParseProgram(info);
}
}
info->SetFunction(result);
......
......@@ -430,10 +430,7 @@ class Parser {
virtual ~Parser() { }
// Returns NULL if parsing failed.
FunctionLiteral* ParseProgram(Handle<String> source,
bool in_global_context,
StrictModeFlag strict_mode);
FunctionLiteral* ParseProgram(CompilationInfo* info);
FunctionLiteral* ParseLazy(CompilationInfo* info);
void ReportMessageAt(Scanner::Location loc,
......@@ -480,9 +477,8 @@ class Parser {
Zone* zone() { return isolate_->zone(); }
// Called by ParseProgram after setting up the scanner.
FunctionLiteral* DoParseProgram(Handle<String> source,
bool in_global_context,
StrictModeFlag strict_mode,
FunctionLiteral* DoParseProgram(CompilationInfo* info,
Handle<String> source,
ZoneScope* zone_scope);
// Report syntax error
......
......@@ -12191,15 +12191,12 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_DebugEvaluateGlobal) {
bool is_global = true;
if (additional_context->IsJSObject()) {
// Create a function context first, than put 'with' context on top of it.
Handle<JSFunction> go_between = isolate->factory()->NewFunction(
isolate->factory()->empty_string(),
isolate->factory()->undefined_value());
go_between->set_context(*context);
context =
isolate->factory()->NewFunctionContext(
Context::MIN_CONTEXT_SLOTS, go_between);
context->set_extension(JSObject::cast(*additional_context));
// Create a new with context with the additional context information between
// the context of the debugged function and the eval code to be executed.
context = isolate->factory()->NewWithContext(
Handle<JSFunction>(context->closure()),
context,
Handle<JSObject>::cast(additional_context));
is_global = false;
}
......
This diff is collapsed.
......@@ -93,8 +93,7 @@ class Scope: public ZoneObject {
// doesn't re-allocate variables repeatedly.
static bool Analyze(CompilationInfo* info);
static Scope* DeserializeScopeChain(CompilationInfo* info,
Scope* innermost_scope);
static Scope* DeserializeScopeChain(Context* context, Scope* global_scope);
// The scope name is only used for printing/debugging.
void SetScopeName(Handle<String> scope_name) { scope_name_ = scope_name; }
......@@ -112,6 +111,12 @@ class Scope: public ZoneObject {
// Lookup a variable in this scope. Returns the variable or NULL if not found.
Variable* LocalLookup(Handle<String> name);
// This lookup corresponds to a lookup in the "intermediate" scope sitting
// between this scope and the outer scope. (ECMA-262, 3rd., requires that
// the name of named function literal is kept in an intermediate scope
// in between this scope and the next outer scope.)
Variable* LookupFunctionVar(Handle<String> name);
// Lookup a variable in this scope or outer scopes.
// Returns the variable or NULL if not found.
Variable* Lookup(Handle<String> name);
......@@ -317,7 +322,7 @@ class Scope: public ZoneObject {
// In the case of code compiled and run using 'eval', the context
// parameter is the context in which eval was called. In all other
// cases the context parameter is an empty handle.
void AllocateVariables(Handle<Context> context);
void AllocateVariables(Scope* global_scope);
// Current number of var or const locals.
int num_var_or_const() { return num_var_or_const_; }
......@@ -504,13 +509,10 @@ class Scope: public ZoneObject {
// scope. If the code is executed because of a call to 'eval', the context
// parameter should be set to the calling context of 'eval'.
Variable* LookupRecursive(Handle<String> name,
Handle<Context> context,
BindingKind* binding_kind);
void ResolveVariable(Scope* global_scope,
Handle<Context> context,
VariableProxy* proxy);
void ResolveVariablesRecursively(Scope* global_scope,
Handle<Context> context);
void ResolveVariablesRecursively(Scope* global_scope);
// Scope analysis.
bool PropagateScopeInfo(bool outer_scope_calls_non_strict_eval);
......
......@@ -32,6 +32,7 @@
#include "v8.h"
#include "cctest.h"
#include "compiler.h"
#include "execution.h"
#include "isolate.h"
#include "parser.h"
......@@ -856,9 +857,10 @@ TEST(ScopePositions) {
i::Handle<i::Script> script = FACTORY->NewScript(source);
i::Parser parser(script, false, NULL, NULL);
parser.SetHarmonyScoping(true);
i::FunctionLiteral* function =
parser.ParseProgram(source, true, i::kNonStrictMode);
ASSERT(function != NULL);
i::CompilationInfo info(script);
info.MarkAsGlobal();
i::FunctionLiteral* function = parser.ParseProgram(&info);
CHECK(function != NULL);
// Check scope types and positions.
i::Scope* scope = function->scope();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment