Commit cb2419c6 authored by rossberg@chromium.org's avatar rossberg@chromium.org

Infer whether a variable is assigned in inner functions

R=titzer@chromium.org
BUG=

Review URL: https://codereview.chromium.org/345573002

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@22039 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 1824edd6
......@@ -278,6 +278,10 @@ class AstValueFactory {
}
const AstRawString* GetOneByteString(Vector<const uint8_t> literal);
const AstRawString* GetOneByteString(const char* string) {
return GetOneByteString(Vector<const uint8_t>(
reinterpret_cast<const uint8_t*>(string), StrLength(string)));
}
const AstRawString* GetTwoByteString(Vector<const uint16_t> literal);
const AstRawString* GetString(Handle<String> literal);
const AstConsString* NewConsString(const AstString* left,
......
......@@ -64,8 +64,7 @@ VariableProxy::VariableProxy(Zone* zone, Variable* var, int position)
name_(var->raw_name()),
var_(NULL), // Will be set by the call to BindTo.
is_this_(var->is_this()),
is_trivial_(false),
is_lvalue_(false),
is_assigned_(false),
interface_(var->interface()) {
BindTo(var);
}
......@@ -80,8 +79,7 @@ VariableProxy::VariableProxy(Zone* zone,
name_(name),
var_(NULL),
is_this_(is_this),
is_trivial_(false),
is_lvalue_(false),
is_assigned_(false),
interface_(interface) {
}
......@@ -97,7 +95,7 @@ void VariableProxy::BindTo(Variable* var) {
// eval() etc. Const-ness and variable declarations are a complete mess
// in JS. Sigh...
var_ = var;
var->set_is_used(true);
var->set_is_used();
}
......
......@@ -1628,17 +1628,14 @@ class VariableProxy V8_FINAL : public Expression {
bool IsArguments() const { return var_ != NULL && var_->is_arguments(); }
bool IsLValue() const { return is_lvalue_; }
Handle<String> name() const { return name_->string(); }
const AstRawString* raw_name() const { return name_; }
Variable* var() const { return var_; }
bool is_this() const { return is_this_; }
Interface* interface() const { return interface_; }
void MarkAsTrivial() { is_trivial_ = true; }
void MarkAsLValue() { is_lvalue_ = true; }
bool is_assigned() const { return is_assigned_; }
void set_is_assigned() { is_assigned_ = true; }
// Bind this proxy to the variable var. Interfaces must match.
void BindTo(Variable* var);
......@@ -1655,10 +1652,7 @@ class VariableProxy V8_FINAL : public Expression {
const AstRawString* name_;
Variable* var_; // resolved variable, or NULL
bool is_this_;
bool is_trivial_;
// True if this variable proxy is being used in an assignment
// or with a increment/decrement operator.
bool is_lvalue_;
bool is_assigned_;
Interface* interface_;
};
......
......@@ -453,11 +453,10 @@ void ParserTraits::CheckPossibleEvalCall(Expression* expression,
}
Expression* ParserTraits::MarkExpressionAsLValue(Expression* expression) {
VariableProxy* proxy = expression != NULL
? expression->AsVariableProxy()
: NULL;
if (proxy != NULL) proxy->MarkAsLValue();
Expression* ParserTraits::MarkExpressionAsAssigned(Expression* expression) {
VariableProxy* proxy =
expression != NULL ? expression->AsVariableProxy() : NULL;
if (proxy != NULL) proxy->set_is_assigned();
return expression;
}
......@@ -593,8 +592,7 @@ Expression* ParserTraits::NewThrowError(
Zone* zone = parser_->zone();
int argc = arg != NULL ? 1 : 0;
const AstRawString* type =
parser_->ast_value_factory_->GetOneByteString(Vector<const uint8_t>(
reinterpret_cast<const uint8_t*>(message), StrLength(message)));
parser_->ast_value_factory_->GetOneByteString(message);
ZoneList<const AstRawString*>* array =
new (zone) ZoneList<const AstRawString*>(argc, zone);
if (arg != NULL) {
......@@ -1722,6 +1720,8 @@ void Parser::Declare(Declaration* declaration, bool resolve, bool* ok) {
Expression* expression = NewThrowTypeError(
"var_redeclaration", name, declaration->position());
declaration_scope->SetIllegalRedeclaration(expression);
} else if (mode == VAR) {
var->set_maybe_assigned();
}
}
......
......@@ -468,9 +468,8 @@ class ParserTraits {
void CheckPossibleEvalCall(Expression* expression, Scope* scope);
// Determine if the expression is a variable proxy and mark it as being used
// in an assignment or with a increment/decrement operator. This is currently
// used on for the statically checking assignments to harmony const bindings.
static Expression* MarkExpressionAsLValue(Expression* expression);
// in an assignment or with a increment/decrement operator.
static Expression* MarkExpressionAsAssigned(Expression* expression);
// Returns true if we have a binary expression between two numeric
// literals. In that case, *x will be changed to an expression which is the
......
......@@ -973,10 +973,10 @@ class PreParserTraits {
static void CheckPossibleEvalCall(PreParserExpression expression,
PreParserScope* scope) {}
static PreParserExpression MarkExpressionAsLValue(
static PreParserExpression MarkExpressionAsAssigned(
PreParserExpression expression) {
// TODO(marja): To be able to produce the same errors, the preparser needs
// to start tracking which expressions are variables and which are lvalues.
// to start tracking which expressions are variables and which are assigned.
return expression;
}
......@@ -1753,7 +1753,7 @@ ParserBase<Traits>::ParseAssignmentExpression(bool accept_IN, bool* ok) {
expression = this->CheckAndRewriteReferenceExpression(
expression, lhs_location, "invalid_lhs_in_assignment", CHECK_OK);
expression = this->MarkExpressionAsLValue(expression);
expression = this->MarkExpressionAsAssigned(expression);
Token::Value op = Next(); // Get assignment operator.
int pos = position();
......@@ -1915,7 +1915,7 @@ ParserBase<Traits>::ParseUnaryExpression(bool* ok) {
ExpressionT expression = this->ParseUnaryExpression(CHECK_OK);
expression = this->CheckAndRewriteReferenceExpression(
expression, lhs_location, "invalid_lhs_in_prefix_op", CHECK_OK);
this->MarkExpressionAsLValue(expression);
this->MarkExpressionAsAssigned(expression);
return factory()->NewCountOperation(op,
true /* prefix */,
......@@ -1940,7 +1940,7 @@ ParserBase<Traits>::ParsePostfixExpression(bool* ok) {
Token::IsCountOp(peek())) {
expression = this->CheckAndRewriteReferenceExpression(
expression, lhs_location, "invalid_lhs_in_postfix_op", CHECK_OK);
expression = this->MarkExpressionAsLValue(expression);
expression = this->MarkExpressionAsAssigned(expression);
Token::Value next = Next();
expression =
......
......@@ -170,6 +170,7 @@ void Scope::SetDefaults(ScopeType scope_type,
strict_mode_ = outer_scope != NULL ? outer_scope->strict_mode_ : SLOPPY;
outer_scope_calls_sloppy_eval_ = false;
inner_scope_calls_eval_ = false;
inner_scope_contains_with_ = false;
force_eager_compilation_ = false;
force_context_allocation_ = (outer_scope != NULL && !is_function_scope())
? outer_scope->has_forced_context_allocation() : false;
......@@ -194,6 +195,7 @@ Scope* Scope::DeserializeScopeChain(Context* context, Scope* global_scope,
Scope* current_scope = NULL;
Scope* innermost_scope = NULL;
bool contains_with = false;
bool inner_contains_with = false;
while (!context->IsNativeContext()) {
if (context->IsWithContext()) {
Scope* with_scope = new(zone) Scope(current_scope,
......@@ -243,7 +245,11 @@ Scope* Scope::DeserializeScopeChain(Context* context, Scope* global_scope,
global_scope->ast_value_factory_->GetString(Handle<String>(name)),
global_scope->ast_value_factory_, zone);
}
if (contains_with) current_scope->RecordWithStatement();
if (inner_contains_with) current_scope->inner_scope_contains_with_ = true;
if (contains_with) {
current_scope->RecordWithStatement();
inner_contains_with = true;
}
if (innermost_scope == NULL) innermost_scope = current_scope;
// Forget about a with when we move to a context for a different function.
......@@ -819,9 +825,15 @@ static void PrintVar(int indent, Variable* var) {
PrintName(var->raw_name());
PrintF("; // ");
PrintLocation(var);
bool comma = !var->IsUnallocated();
if (var->has_forced_context_allocation()) {
if (!var->IsUnallocated()) PrintF(", ");
if (comma) PrintF(", ");
PrintF("forced context allocation");
comma = true;
}
if (var->maybe_assigned()) {
if (comma) PrintF(", ");
PrintF("maybe assigned");
}
PrintF("\n");
}
......@@ -875,6 +887,9 @@ void Scope::Print(int n) {
}
if (scope_inside_with_) Indent(n1, "// scope inside 'with'\n");
if (scope_contains_with_) Indent(n1, "// scope contains 'with'\n");
if (inner_scope_contains_with_) {
Indent(n1, "// inner scope contains 'with'\n");
}
if (scope_calls_eval_) Indent(n1, "// scope calls 'eval'\n");
if (outer_scope_calls_sloppy_eval_) {
Indent(n1, "// outer scope calls 'eval' in sloppy context\n");
......@@ -951,7 +966,7 @@ Variable* Scope::NonLocal(const AstRawString* name, VariableMode mode) {
}
Variable* Scope::LookupRecursive(const AstRawString* name,
Variable* Scope::LookupRecursive(VariableProxy* proxy,
BindingKind* binding_kind,
AstNodeFactory<AstNullVisitor>* factory) {
ASSERT(binding_kind != NULL);
......@@ -963,7 +978,7 @@ Variable* Scope::LookupRecursive(const AstRawString* name,
}
// Try to find the variable in this scope.
Variable* var = LookupLocal(name);
Variable* var = LookupLocal(proxy->raw_name());
// We found a variable and we are done. (Even if there is an 'eval' in
// this scope which introduces the same variable again, the resulting
......@@ -977,11 +992,11 @@ Variable* Scope::LookupRecursive(const AstRawString* name,
// if any. We can do this for all scopes, since the function variable is
// only present - if at all - for function scopes.
*binding_kind = UNBOUND;
var = LookupFunctionVar(name, factory);
var = LookupFunctionVar(proxy->raw_name(), factory);
if (var != NULL) {
*binding_kind = BOUND;
} else if (outer_scope_ != NULL) {
var = outer_scope_->LookupRecursive(name, binding_kind, factory);
var = outer_scope_->LookupRecursive(proxy, binding_kind, factory);
if (*binding_kind == BOUND && (is_function_scope() || is_with_scope())) {
var->ForceContextAllocation();
}
......@@ -997,6 +1012,7 @@ Variable* Scope::LookupRecursive(const AstRawString* name,
// the associated variable has to be marked as potentially being accessed
// from inside of an inner with scope (the property may not be in the 'with'
// object).
if (var != NULL && proxy->is_assigned()) var->set_maybe_assigned();
*binding_kind = DYNAMIC_LOOKUP;
return NULL;
} else if (calls_sloppy_eval()) {
......@@ -1025,7 +1041,7 @@ bool Scope::ResolveVariable(CompilationInfo* info,
// Otherwise, try to resolve the variable.
BindingKind binding_kind;
Variable* var = LookupRecursive(proxy->raw_name(), &binding_kind, factory);
Variable* var = LookupRecursive(proxy, &binding_kind, factory);
switch (binding_kind) {
case BOUND:
// We found a variable binding.
......@@ -1064,9 +1080,10 @@ bool Scope::ResolveVariable(CompilationInfo* info,
}
ASSERT(var != NULL);
if (proxy->is_assigned()) var->set_maybe_assigned();
if (FLAG_harmony_scoping && strict_mode() == STRICT &&
var->is_const_mode() && proxy->IsLValue()) {
var->is_const_mode() && proxy->is_assigned()) {
// Assignment to const. Throw a syntax error.
MessageLocation location(
info->script(), proxy->position(), proxy->position());
......@@ -1140,7 +1157,7 @@ bool Scope::ResolveVariablesRecursively(
}
bool Scope::PropagateScopeInfo(bool outer_scope_calls_sloppy_eval ) {
void Scope::PropagateScopeInfo(bool outer_scope_calls_sloppy_eval ) {
if (outer_scope_calls_sloppy_eval) {
outer_scope_calls_sloppy_eval_ = true;
}
......@@ -1148,16 +1165,18 @@ bool Scope::PropagateScopeInfo(bool outer_scope_calls_sloppy_eval ) {
bool calls_sloppy_eval =
this->calls_sloppy_eval() || outer_scope_calls_sloppy_eval_;
for (int i = 0; i < inner_scopes_.length(); i++) {
Scope* inner_scope = inner_scopes_[i];
if (inner_scope->PropagateScopeInfo(calls_sloppy_eval)) {
Scope* inner = inner_scopes_[i];
inner->PropagateScopeInfo(calls_sloppy_eval);
if (inner->scope_calls_eval_ || inner->inner_scope_calls_eval_) {
inner_scope_calls_eval_ = true;
}
if (inner_scope->force_eager_compilation_) {
if (inner->scope_contains_with_ || inner->inner_scope_contains_with_) {
inner_scope_contains_with_ = true;
}
if (inner->force_eager_compilation_) {
force_eager_compilation_ = true;
}
}
return scope_calls_eval_ || inner_scope_calls_eval_;
}
......@@ -1174,7 +1193,8 @@ bool Scope::MustAllocate(Variable* var) {
is_block_scope() ||
is_module_scope() ||
is_global_scope())) {
var->set_is_used(true);
var->set_is_used();
if (scope_calls_eval_ || inner_scope_calls_eval_) var->set_maybe_assigned();
}
// Global variables do not need to be allocated.
return !var->IsGlobalObjectProperty() && var->is_used();
......
......@@ -474,6 +474,7 @@ class Scope: public ZoneObject {
// Computed via PropagateScopeInfo.
bool outer_scope_calls_sloppy_eval_;
bool inner_scope_calls_eval_;
bool inner_scope_contains_with_;
bool force_eager_compilation_;
bool force_context_allocation_;
......@@ -551,7 +552,7 @@ class Scope: public ZoneObject {
// Lookup a variable reference given by name recursively starting with this
// 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(const AstRawString* name,
Variable* LookupRecursive(VariableProxy* proxy,
BindingKind* binding_kind,
AstNodeFactory<AstNullVisitor>* factory);
MUST_USE_RESULT
......@@ -563,7 +564,7 @@ class Scope: public ZoneObject {
AstNodeFactory<AstNullVisitor>* factory);
// Scope analysis.
bool PropagateScopeInfo(bool outer_scope_calls_sloppy_eval);
void PropagateScopeInfo(bool outer_scope_calls_sloppy_eval);
bool HasTrivialContext() const;
// Predicates.
......
......@@ -50,6 +50,7 @@ Variable::Variable(Scope* scope,
is_valid_ref_(is_valid_ref),
force_context_allocation_(false),
is_used_(false),
maybe_assigned_(false),
initialization_flag_(initialization_flag),
interface_(interface) {
// Var declared variables never need initialization.
......
......@@ -82,7 +82,9 @@ class Variable: public ZoneObject {
force_context_allocation_ = true;
}
bool is_used() { return is_used_; }
void set_is_used(bool flag) { is_used_ = flag; }
void set_is_used() { is_used_ = true; }
bool maybe_assigned() { return maybe_assigned_; }
void set_maybe_assigned() { maybe_assigned_ = true; }
int initializer_position() { return initializer_position_; }
void set_initializer_position(int pos) { initializer_position_ = pos; }
......@@ -157,6 +159,7 @@ class Variable: public ZoneObject {
// Usage info.
bool force_context_allocation_; // set by variable resolver
bool is_used_;
bool maybe_assigned_;
InitializationFlag initialization_flag_;
// Module type info.
......
......@@ -38,6 +38,7 @@
#include "src/objects.h"
#include "src/parser.h"
#include "src/preparser.h"
#include "src/rewriter.h"
#include "src/scanner-character-streams.h"
#include "src/token.h"
#include "src/utils.h"
......@@ -2614,3 +2615,133 @@ TEST(RegressionLazyFunctionWithErrorWithArg) {
"}\n"
"this_is_lazy();\n");
}
TEST(InnerAssignment) {
i::Isolate* isolate = CcTest::i_isolate();
i::Factory* factory = isolate->factory();
i::HandleScope scope(isolate);
LocalContext env;
const char* prefix = "function f() {";
const char* midfix = " function g() {";
const char* suffix = "}}";
struct { const char* source; bool assigned; bool strict; } outers[] = {
// Actual assignments.
{ "var x; var x = 5;", true, false },
{ "var x; { var x = 5; }", true, false },
{ "'use strict'; let x; x = 6;", true, true },
{ "var x = 5; function x() {}", true, false },
// Actual non-assignments.
{ "var x;", false, false },
{ "var x = 5;", false, false },
{ "'use strict'; let x;", false, true },
{ "'use strict'; let x = 6;", false, true },
{ "'use strict'; var x = 0; { let x = 6; }", false, true },
{ "'use strict'; var x = 0; { let x; x = 6; }", false, true },
{ "'use strict'; let x = 0; { let x = 6; }", false, true },
{ "'use strict'; let x = 0; { let x; x = 6; }", false, true },
{ "var x; try {} catch (x) { x = 5; }", false, false },
{ "function x() {}", false, false },
// Eval approximation.
{ "var x; eval('');", true, false },
{ "eval(''); var x;", true, false },
{ "'use strict'; let x; eval('');", true, true },
{ "'use strict'; eval(''); let x;", true, true },
// Non-assignments not recognized, because the analysis is approximative.
{ "var x; var x;", true, false },
{ "var x = 5; var x;", true, false },
{ "var x; { var x; }", true, false },
{ "var x; function x() {}", true, false },
{ "function x() {}; var x;", true, false },
{ "var x; try {} catch (x) { var x = 5; }", true, false },
};
struct { const char* source; bool assigned; bool with; } inners[] = {
// Actual assignments.
{ "x = 1;", true, false },
{ "x++;", true, false },
{ "++x;", true, false },
{ "x--;", true, false },
{ "--x;", true, false },
{ "{ x = 1; }", true, false },
{ "'use strict'; { let x; }; x = 0;", true, false },
{ "'use strict'; { const x = 1; }; x = 0;", true, false },
{ "'use strict'; { function x() {} }; x = 0;", true, false },
{ "with ({}) { x = 1; }", true, true },
{ "eval('');", true, false },
{ "'use strict'; { let y; eval('') }", true, false },
{ "function h() { x = 0; }", true, false },
{ "(function() { x = 0; })", true, false },
{ "(function() { x = 0; })", true, false },
{ "with ({}) (function() { x = 0; })", true, true },
// Actual non-assignments.
{ "", false, false },
{ "x;", false, false },
{ "var x;", false, false },
{ "var x = 8;", false, false },
{ "var x; x = 8;", false, false },
{ "'use strict'; let x;", false, false },
{ "'use strict'; let x = 8;", false, false },
{ "'use strict'; let x; x = 8;", false, false },
{ "'use strict'; const x = 8;", false, false },
{ "function x() {}", false, false },
{ "function x() { x = 0; }", false, false },
{ "function h(x) { x = 0; }", false, false },
{ "'use strict'; { let x; x = 0; }", false, false },
{ "{ var x; }; x = 0;", false, false },
{ "with ({}) {}", false, true },
{ "var x; { with ({}) { x = 1; } }", false, true },
{ "try {} catch(x) { x = 0; }", false, false },
{ "try {} catch(x) { with ({}) { x = 1; } }", false, true },
// Eval approximation.
{ "eval('');", true, false },
{ "function h() { eval(''); }", true, false },
{ "(function() { eval(''); })", true, false },
// Shadowing not recognized because of eval approximation.
{ "var x; eval('');", true, false },
{ "'use strict'; let x; eval('');", true, false },
{ "try {} catch(x) { eval(''); }", true, false },
{ "function x() { eval(''); }", true, false },
{ "(function(x) { eval(''); })", true, false },
};
int prefix_len = Utf8LengthHelper(prefix);
int midfix_len = Utf8LengthHelper(midfix);
int suffix_len = Utf8LengthHelper(suffix);
for (unsigned i = 0; i < ARRAY_SIZE(outers); ++i) {
const char* outer = outers[i].source;
int outer_len = Utf8LengthHelper(outer);
for (unsigned j = 0; j < ARRAY_SIZE(inners); ++j) {
if (outers[i].strict && inners[j].with) continue;
const char* inner = inners[j].source;
int inner_len = Utf8LengthHelper(inner);
int len = prefix_len + outer_len + midfix_len + inner_len + suffix_len;
i::ScopedVector<char> program(len + 1);
i::SNPrintF(program, "%s%s%s%s%s", prefix, outer, midfix, inner, suffix);
i::Handle<i::String> source =
factory->InternalizeUtf8String(program.start());
source->PrintOn(stdout);
printf("\n");
i::Handle<i::Script> script = factory->NewScript(source);
i::CompilationInfoWithZone info(script);
i::Parser parser(&info);
parser.set_allow_harmony_scoping(true);
CHECK(parser.Parse());
CHECK(i::Rewriter::Rewrite(&info));
CHECK(i::Scope::Analyze(&info));
CHECK(info.function() != NULL);
i::Scope* scope = info.function()->scope();
CHECK_EQ(scope->inner_scopes()->length(), 1);
i::Scope* inner_scope = scope->inner_scopes()->at(0);
const i::AstRawString* var_name =
info.ast_value_factory()->GetOneByteString("x");
i::Variable* var = inner_scope->Lookup(var_name);
bool expected = outers[i].assigned || inners[j].assigned;
CHECK(var != NULL);
CHECK(var->is_used() || !expected);
CHECK(var->maybe_assigned() == expected);
}
}
}
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