diff --git a/src/objects.h b/src/objects.h index 274da54417..78902f63a2 100644 --- a/src/objects.h +++ b/src/objects.h @@ -4268,6 +4268,8 @@ class ScopeInfo : public FixedArray { // must be an internalized string. int FunctionContextSlotIndex(String* name, VariableMode* mode); + bool block_scope_is_class_scope(); + FunctionKind function_kind(); // Copies all the context locals into an object used to materialize a scope. static bool CopyContextLocalsToScopeObject(Handle scope_info, @@ -4373,6 +4375,10 @@ class ScopeInfo : public FixedArray { class AsmFunctionField : public BitField {}; class IsSimpleParameterListField : public BitField {}; + class BlockScopeIsClassScopeField + : public BitField {}; + class FunctionKindField + : public BitField {}; // BitFields representing the encoded information for context locals in the // ContextLocalInfoEntries part. diff --git a/src/parser.cc b/src/parser.cc index 1345a790eb..2b5d5f2dbf 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -4100,7 +4100,11 @@ ClassLiteral* Parser::ParseClassLiteral(const AstRawString* name, return NULL; } + // Create a block scope which is additionally tagged as class scope; this is + // important for resolving variable references to the class name in the strong + // mode. Scope* block_scope = NewScope(scope_, BLOCK_SCOPE); + block_scope->tag_as_class_scope(); BlockState block_state(&scope_, block_scope); scope_->SetLanguageMode( static_cast(scope_->language_mode() | STRICT_BIT)); @@ -4164,12 +4168,14 @@ ClassLiteral* Parser::ParseClassLiteral(const AstRawString* name, } block_scope->set_end_position(end_pos); - block_scope = block_scope->FinalizeBlockScope(); if (name != NULL) { DCHECK_NOT_NULL(proxy); - DCHECK_NOT_NULL(block_scope); proxy->var()->set_initializer_position(end_pos); + } else { + // Unnamed classes should not have scopes (the scope will be empty). + DCHECK_EQ(block_scope->num_var_or_const(), 0); + block_scope = nullptr; } return factory()->NewClassLiteral(name, block_scope, proxy, extends, diff --git a/src/preparser.h b/src/preparser.h index 7df271f6ba..1b9af9e732 100644 --- a/src/preparser.h +++ b/src/preparser.h @@ -317,10 +317,9 @@ class ParserBase : public Traits { DCHECK(scope_type != MODULE_SCOPE || allow_harmony_modules()); DCHECK((scope_type == FUNCTION_SCOPE && IsValidFunctionKind(kind)) || kind == kNormalFunction); - Scope* result = - new (zone()) Scope(zone(), parent, scope_type, ast_value_factory()); - bool uninitialized_this = IsSubclassConstructor(kind); - result->Initialize(uninitialized_this); + Scope* result = new (zone()) + Scope(zone(), parent, scope_type, ast_value_factory(), kind); + result->Initialize(); return result; } diff --git a/src/scopeinfo.cc b/src/scopeinfo.cc index 10a493f5d6..d62bc41295 100644 --- a/src/scopeinfo.cc +++ b/src/scopeinfo.cc @@ -64,7 +64,9 @@ Handle ScopeInfo::Create(Isolate* isolate, Zone* zone, FunctionVariableMode::encode(function_variable_mode) | AsmModuleField::encode(scope->asm_module()) | AsmFunctionField::encode(scope->asm_function()) | - IsSimpleParameterListField::encode(simple_parameter_list); + IsSimpleParameterListField::encode(simple_parameter_list) | + BlockScopeIsClassScopeField::encode(scope->is_class_scope()) | + FunctionKindField::encode(scope->function_kind()); scope_info->SetFlags(flags); scope_info->SetParameterCount(parameter_count); scope_info->SetStackLocalCount(stack_local_count); @@ -373,6 +375,16 @@ int ScopeInfo::FunctionContextSlotIndex(String* name, VariableMode* mode) { } +bool ScopeInfo::block_scope_is_class_scope() { + return BlockScopeIsClassScopeField::decode(Flags()); +} + + +FunctionKind ScopeInfo::function_kind() { + return FunctionKindField::decode(Flags()); +} + + bool ScopeInfo::CopyContextLocalsToScopeObject(Handle scope_info, Handle context, Handle scope_object) { diff --git a/src/scopes.cc b/src/scopes.cc index 97a89431f3..70b1e0ecbe 100644 --- a/src/scopes.cc +++ b/src/scopes.cc @@ -66,7 +66,7 @@ Variable* VariableMap::Lookup(const AstRawString* name) { // Implementation of Scope Scope::Scope(Zone* zone, Scope* outer_scope, ScopeType scope_type, - AstValueFactory* ast_value_factory) + AstValueFactory* ast_value_factory, FunctionKind function_kind) : inner_scopes_(4, zone), variables_(zone), internals_(4, zone), @@ -79,7 +79,8 @@ Scope::Scope(Zone* zone, Scope* outer_scope, ScopeType scope_type, already_resolved_(false), ast_value_factory_(ast_value_factory), zone_(zone) { - SetDefaults(scope_type, outer_scope, Handle::null()); + SetDefaults(scope_type, outer_scope, Handle::null(), + function_kind); // The outermost scope must be a script scope. DCHECK(scope_type == SCRIPT_SCOPE || outer_scope != NULL); DCHECK(!HasIllegalRedeclaration()); @@ -138,11 +139,13 @@ Scope::Scope(Zone* zone, Scope* inner_scope, } -void Scope::SetDefaults(ScopeType scope_type, - Scope* outer_scope, - Handle scope_info) { +void Scope::SetDefaults(ScopeType scope_type, Scope* outer_scope, + Handle scope_info, + FunctionKind function_kind) { outer_scope_ = outer_scope; scope_type_ = scope_type; + function_kind_ = function_kind; + block_scope_is_class_scope_ = false; scope_name_ = ast_value_factory_->empty_string(); dynamics_ = NULL; receiver_ = NULL; @@ -181,6 +184,8 @@ void Scope::SetDefaults(ScopeType scope_type, if (!scope_info.is_null()) { scope_calls_eval_ = scope_info->CallsEval(); language_mode_ = scope_info->language_mode(); + block_scope_is_class_scope_ = scope_info->block_scope_is_class_scope(); + function_kind_ = scope_info->function_kind(); } } @@ -284,7 +289,8 @@ bool Scope::Analyze(CompilationInfo* info) { } -void Scope::Initialize(bool subclass_constructor) { +void Scope::Initialize() { + bool subclass_constructor = IsSubclassConstructor(function_kind_); DCHECK(!already_resolved()); // Add this scope as a new inner scope of the outer scope. @@ -1070,29 +1076,7 @@ bool Scope::ResolveVariable(CompilationInfo* info, VariableProxy* proxy, case BOUND: // We found a variable binding. if (is_strong(language_mode())) { - // Check for declaration-after use (for variables) in strong mode. Note - // that we can only do this in the case where we have seen the - // declaration. And we always allow referencing functions (for now). - - // If both the use and the declaration are inside an eval scope - // (possibly indirectly), or one of them is, we need to check whether - // they are inside the same eval scope or different - // ones. - - // TODO(marja,rossberg): Detect errors across different evals (depends - // on the future of eval in strong mode). - const Scope* eval_for_use = NearestOuterEvalScope(); - const Scope* eval_for_declaration = - var->scope()->NearestOuterEvalScope(); - - if (proxy->position() != RelocInfo::kNoPosition && - proxy->position() < var->initializer_position() && - !var->is_function() && eval_for_use == eval_for_declaration) { - DCHECK(proxy->end_position() != RelocInfo::kNoPosition); - ReportMessage(proxy->position(), proxy->end_position(), - "strong_use_before_declaration", proxy->raw_name()); - return false; - } + if (!CheckStrongModeDeclaration(proxy, var)) return false; } break; @@ -1137,6 +1121,56 @@ bool Scope::ResolveVariable(CompilationInfo* info, VariableProxy* proxy, } +bool Scope::CheckStrongModeDeclaration(VariableProxy* proxy, Variable* var) { + // Check for declaration-after use (for variables) in strong mode. Note that + // we can only do this in the case where we have seen the declaration. And we + // always allow referencing functions (for now). + + // Allow referencing the class name from methods of that class, even though + // the initializer position for class names is only after the body. + Scope* scope = this; + while (scope) { + if (scope->ClassVariableForMethod() == var) return true; + scope = scope->outer_scope(); + } + + // If both the use and the declaration are inside an eval scope (possibly + // indirectly), or one of them is, we need to check whether they are inside + // the same eval scope or different ones. + + // TODO(marja,rossberg): Detect errors across different evals (depends on the + // future of eval in strong mode). + const Scope* eval_for_use = NearestOuterEvalScope(); + const Scope* eval_for_declaration = var->scope()->NearestOuterEvalScope(); + + if (proxy->position() != RelocInfo::kNoPosition && + proxy->position() < var->initializer_position() && !var->is_function() && + eval_for_use == eval_for_declaration) { + DCHECK(proxy->end_position() != RelocInfo::kNoPosition); + ReportMessage(proxy->position(), proxy->end_position(), + "strong_use_before_declaration", proxy->raw_name()); + return false; + } + return true; +} + + +Variable* Scope::ClassVariableForMethod() const { + if (!is_function_scope()) return nullptr; + if (!IsConciseMethod(function_kind_) && !IsConstructor(function_kind_) && + !IsAccessorFunction(function_kind_)) { + return nullptr; + } + DCHECK_NOT_NULL(outer_scope_); + DCHECK(outer_scope_->is_class_scope()); + // The class scope contains at most one variable, the class name. + DCHECK(outer_scope_->variables_.occupancy() <= 1); + if (outer_scope_->variables_.occupancy() == 0) return nullptr; + VariableMap::Entry* p = outer_scope_->variables_.Start(); + return reinterpret_cast(p->value); +} + + bool Scope::ResolveVariablesRecursively(CompilationInfo* info, AstNodeFactory* factory) { DCHECK(info->script_scope()->is_script_scope()); @@ -1430,5 +1464,4 @@ int Scope::ContextLocalCount() const { return num_heap_slots() - Context::MIN_CONTEXT_SLOTS - (function_ != NULL && function_->proxy()->var()->IsContextSlot() ? 1 : 0); } - } } // namespace v8::internal diff --git a/src/scopes.h b/src/scopes.h index 186530ceab..284386d854 100644 --- a/src/scopes.h +++ b/src/scopes.h @@ -73,7 +73,8 @@ class Scope: public ZoneObject { // Construction Scope(Zone* zone, Scope* outer_scope, ScopeType scope_type, - AstValueFactory* value_factory); + AstValueFactory* value_factory, + FunctionKind function_kind = kNormalFunction); // Compute top scope and allocate variables. For lazy compilation the top // scope only contains the single lazily compiled function, so this @@ -88,7 +89,7 @@ class Scope: public ZoneObject { scope_name_ = scope_name; } - void Initialize(bool subclass_constructor = false); + void Initialize(); // Checks if the block scope is redundant, i.e. it does not contain any // block scoped declarations. In that case it is removed from the scope @@ -281,6 +282,13 @@ class Scope: public ZoneObject { bool is_block_scope() const { return scope_type_ == BLOCK_SCOPE; } bool is_with_scope() const { return scope_type_ == WITH_SCOPE; } bool is_arrow_scope() const { return scope_type_ == ARROW_SCOPE; } + void tag_as_class_scope() { + DCHECK(is_block_scope()); + block_scope_is_class_scope_ = true; + } + bool is_class_scope() const { + return is_block_scope() && block_scope_is_class_scope_; + } bool is_declaration_scope() const { return is_eval_scope() || is_function_scope() || is_module_scope() || is_script_scope(); @@ -332,6 +340,8 @@ class Scope: public ZoneObject { // The type of this scope. ScopeType scope_type() const { return scope_type_; } + FunctionKind function_kind() const { return function_kind_; } + // The language mode of this scope. LanguageMode language_mode() const { return language_mode_; } @@ -501,6 +511,10 @@ class Scope: public ZoneObject { // The scope type. ScopeType scope_type_; + // Some block scopes are tagged as class scopes. + bool block_scope_is_class_scope_; + // If the scope is a function scope, this is the function kind. + FunctionKind function_kind_; // Debugging support. const AstRawString* scope_name_; @@ -658,6 +672,12 @@ class Scope: public ZoneObject { bool ResolveVariablesRecursively(CompilationInfo* info, AstNodeFactory* factory); + bool CheckStrongModeDeclaration(VariableProxy* proxy, Variable* var); + + // If this scope is a method scope of a class, return the corresponding + // class variable, otherwise nullptr. + Variable* ClassVariableForMethod() const; + // Scope analysis. void PropagateScopeInfo(bool outer_scope_calls_sloppy_eval); bool HasTrivialContext() const; @@ -703,9 +723,9 @@ class Scope: public ZoneObject { } } - void SetDefaults(ScopeType type, - Scope* outer_scope, - Handle scope_info); + void SetDefaults(ScopeType type, Scope* outer_scope, + Handle scope_info, + FunctionKind function_kind = kNormalFunction); AstValueFactory* ast_value_factory_; Zone* zone_; diff --git a/test/mjsunit/strong/declaration-after-use.js b/test/mjsunit/strong/declaration-after-use.js index 0559cdeffb..b51b2f7938 100644 --- a/test/mjsunit/strong/declaration-after-use.js +++ b/test/mjsunit/strong/declaration-after-use.js @@ -110,13 +110,70 @@ function assertThrowsHelper(code, error) { "static a() { return 'A'; } [C.a()]() { return 'B'; } }; }", ReferenceError); - // TODO(marja, rossberg): More tests related to computed property names in - // classes + recognize more errors. This one is not recognized as an error - // yet: - // let C = class C2 { - // static a() { return 'A'; } - // [C2.a()]() { return 'B'; } << C2 should not be allowed here - // }; + assertThrowsHelper( + "'use strong'; if (false) { let C = class C2 { " + + "static a() { return 'A'; } [C2.a()]() { return 'B'; } }; }", + ReferenceError); + + assertThrowsHelper( + "'use strong'; if (false) { let C = class C2 { " + + "[(function() { C; return 'A';})()]() { return 'B'; } }; }", + ReferenceError); + + // The reference to C or C2 is inside a function, but not a method. + assertThrowsHelper( + "'use strong'; if (false) { let C = class C2 { " + + "[(function() { C2; return 'A';})()]() { return 'B'; } }; }", + ReferenceError); + + assertThrowsHelper( + "'use strong'; if (false) { let C = class C2 { " + + "[(function() { C; return 'A';})()]() { return 'B'; } }; }", + ReferenceError); + + // The reference to C or C2 is inside a method, but it's not a method of the + // relevant class (C2). + assertThrowsHelper( + "'use strong'; if (false) { let C = class C2 { " + + "[(new (class D { m() { C2; return 'A'; } })).m()]() " + + "{ return 'B'; } } }", + ReferenceError); + + assertThrowsHelper( + "'use strong'; if (false) { let C = class C2 { " + + "[(new (class D { m() { C; return 'A'; } })).m()]() " + + "{ return 'B'; } } }", + ReferenceError); + + // Methods inside object literals are not sufficiently distinguished from + // methods inside classes. + // https://code.google.com/p/v8/issues/detail?id=3948 + // assertThrowsHelper( + // "'use strong'; if (false) { let C = class C2 { " + + // "[({m() { C2; return 'A'; }}).m()]() " + + // "{ return 'B'; } } }", + // ReferenceError); + + assertThrowsHelper( + "'use strong'; if (false) { let C = class C2 { " + + "[({m() { C; return 'A'; }}).m()]() " + + "{ return 'B'; } } }", + ReferenceError); + + // assertThrowsHelper( + // "'use strong';\n" + + // "if (false) {\n" + + // " class COuter {\n" + + // " m() {\n" + + // " class CInner {\n" + + // " [({ m() { CInner; return 'A'; } }).m()]() {\n" + + // " return 'B';\n" + + // " }\n" + + // " }\n" + + // " }\n" + + // " }\n" + + // "}", + // ReferenceError); })(); @@ -180,10 +237,33 @@ function assertThrowsHelper(code, error) { eval("var7;"); })(); - // https://code.google.com/p/v8/issues/detail?id=3927 - // class C1 { constructor() { C1; } }; new C1(); - // let C2 = class C3 { constructor() { C3; } }; new C2(); - // class C4 { method() { C4; method; } }; new C4(); - // let C5 = class C6 { method() { C6; method; } }; new C5(); + class C1 { constructor() { C1; } }; new C1(); + let C2 = class C3 { constructor() { C3; } }; new C2(); + + class C4 { method() { C4; } }; new C4(); + let C5 = class C6 { method() { C6; } }; new C5(); + + class C7 { static method() { C7; } }; new C7(); + let C8 = class C9 { static method() { C9; } }; new C8(); + + class C10 { get x() { C10; } }; new C10(); + let C11 = class C12 { get x() { C12; } }; new C11(); + + // Regression test for unnamed classes. + let C12 = class { m() { var1; } }; + + class COuter { + m() { + class CInner { + // Here we can refer to COuter but not to CInner (see corresponding + // assertion test): + [({ m() { COuter; return 'A'; } }).m()]() { return 'B'; } + // And here we can refer to both: + n() { COuter; CInner; } + } + return new CInner(); + } + } + (new COuter()).m().n(); })();