Commit 5a72c6b6 authored by Toon Verwaest's avatar Toon Verwaest Committed by Commit Bot

[parser] Use Token::INIT for hoisted sloppy block functions when possible

Change-Id: I83dc3bed644361be1b94063daefd890b10ba50cd
Reviewed-on: https://chromium-review.googlesource.com/c/1433772
Commit-Queue: Toon Verwaest <verwaest@chromium.org>
Reviewed-by: 's avatarLeszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/master@{#59095}
parent 592aeefa
......@@ -491,26 +491,14 @@ inline NestedVariableDeclaration* VariableDeclaration::AsNested() {
class FunctionDeclaration final : public Declaration {
public:
FunctionLiteral* fun() const { return fun_; }
bool declares_sloppy_block_function() const {
return DeclaresSloppyBlockFunction::decode(bit_field_);
}
private:
friend class AstNodeFactory;
class DeclaresSloppyBlockFunction
: public BitField<bool, Declaration::kNextBitFieldIndex, 1> {};
FunctionDeclaration(FunctionLiteral* fun, bool declares_sloppy_block_function,
int pos)
: Declaration(pos, kFunctionDeclaration), fun_(fun) {
bit_field_ = DeclaresSloppyBlockFunction::update(
bit_field_, declares_sloppy_block_function);
}
FunctionDeclaration(FunctionLiteral* fun, int pos)
: Declaration(pos, kFunctionDeclaration), fun_(fun) {}
FunctionLiteral* fun_;
static const uint8_t kNextBitFieldIndex = DeclaresSloppyBlockFunction::kNext;
};
......@@ -994,14 +982,30 @@ class SloppyBlockFunctionStatement final : public Statement {
public:
Statement* statement() const { return statement_; }
void set_statement(Statement* statement) { statement_ = statement; }
Scope* scope() const { return var_->scope(); }
Variable* var() const { return var_; }
Token::Value init() const { return TokenField::decode(bit_field_); }
const AstRawString* name() const { return var_->raw_name(); }
SloppyBlockFunctionStatement** next() { return &next_; }
private:
friend class AstNodeFactory;
SloppyBlockFunctionStatement(int pos, Statement* statement)
: Statement(pos, kSloppyBlockFunctionStatement), statement_(statement) {}
class TokenField
: public BitField<Token::Value, Statement::kNextBitFieldIndex, 8> {};
SloppyBlockFunctionStatement(int pos, Variable* var, Token::Value init,
Statement* statement)
: Statement(pos, kSloppyBlockFunctionStatement),
var_(var),
statement_(statement),
next_(nullptr) {
bit_field_ = TokenField::update(bit_field_, init);
}
Variable* var_;
Statement* statement_;
SloppyBlockFunctionStatement* next_;
};
......@@ -2812,10 +2816,8 @@ class AstNodeFactory final {
return new (zone_) NestedVariableDeclaration(scope, pos);
}
FunctionDeclaration* NewFunctionDeclaration(FunctionLiteral* fun,
bool is_sloppy_block_function,
int pos) {
return new (zone_) FunctionDeclaration(fun, is_sloppy_block_function, pos);
FunctionDeclaration* NewFunctionDeclaration(FunctionLiteral* fun, int pos) {
return new (zone_) FunctionDeclaration(fun, pos);
}
Block* NewBlock(int capacity, bool ignore_completion_value) {
......@@ -2958,8 +2960,10 @@ class AstNodeFactory final {
return failure_expression_;
}
SloppyBlockFunctionStatement* NewSloppyBlockFunctionStatement(int pos) {
return new (zone_) SloppyBlockFunctionStatement(pos, EmptyStatement());
SloppyBlockFunctionStatement* NewSloppyBlockFunctionStatement(
int pos, Variable* var, Token::Value init) {
return new (zone_)
SloppyBlockFunctionStatement(pos, var, init, EmptyStatement());
}
CaseClause* NewCaseClause(Expression* label,
......
......@@ -83,28 +83,6 @@ Variable* VariableMap::Lookup(const AstRawString* name) {
return nullptr;
}
void SloppyBlockFunctionMap::Delegate::set_statement(Statement* statement) {
if (statement_ != nullptr) {
statement_->set_statement(statement);
}
}
SloppyBlockFunctionMap::SloppyBlockFunctionMap(Zone* zone)
: ZoneHashMap(8, ZoneAllocationPolicy(zone)), count_(0) {}
void SloppyBlockFunctionMap::Declare(Zone* zone, const AstRawString* name,
Scope* scope,
SloppyBlockFunctionStatement* statement) {
auto* delegate = new (zone) Delegate(scope, statement, count_++);
// AstRawStrings are unambiguous, i.e., the same string is always represented
// by the same AstRawString*.
Entry* p =
ZoneHashMap::LookupOrInsert(const_cast<AstRawString*>(name), name->Hash(),
ZoneAllocationPolicy(zone));
delegate->set_next(static_cast<SloppyBlockFunctionMap::Delegate*>(p->value));
p->value = delegate;
}
// ----------------------------------------------------------------------------
// Implementation of Scope
......@@ -263,7 +241,6 @@ void DeclarationScope::SetDefaults() {
has_arguments_parameter_ = false;
scope_uses_super_property_ = false;
has_rest_ = false;
sloppy_block_function_map_ = nullptr;
receiver_ = nullptr;
new_target_ = nullptr;
function_ = nullptr;
......@@ -456,14 +433,8 @@ int Scope::num_parameters() const {
}
void DeclarationScope::DeclareSloppyBlockFunction(
const AstRawString* name, Scope* scope,
SloppyBlockFunctionStatement* statement) {
if (sloppy_block_function_map_ == nullptr) {
sloppy_block_function_map_ =
new (zone()->New(sizeof(SloppyBlockFunctionMap)))
SloppyBlockFunctionMap(zone());
}
sloppy_block_function_map_->Declare(zone(), name, scope, statement);
SloppyBlockFunctionStatement* sloppy_block_function) {
sloppy_block_functions_.Add(sloppy_block_function);
}
void DeclarationScope::HoistSloppyBlockFunctions(AstNodeFactory* factory) {
......@@ -473,8 +444,7 @@ void DeclarationScope::HoistSloppyBlockFunctions(AstNodeFactory* factory) {
DCHECK(HasSimpleParameters() || is_block_scope() || is_being_lazily_parsed_);
DCHECK_EQ(factory == nullptr, is_being_lazily_parsed_);
SloppyBlockFunctionMap* map = sloppy_block_function_map();
if (map == nullptr) return;
if (sloppy_block_functions_.is_empty()) return;
// In case of complex parameters the current scope is the body scope and the
// parameters are stored in the outer scope.
......@@ -482,14 +452,17 @@ void DeclarationScope::HoistSloppyBlockFunctions(AstNodeFactory* factory) {
DCHECK(parameter_scope->is_function_scope() || is_eval_scope() ||
is_script_scope());
// The declarations need to be added in the order they were seen,
// so accumulate declared names sorted by index.
ZoneMap<int, const AstRawString*> names_to_declare(zone());
DeclarationScope* decl_scope = this;
while (decl_scope->is_eval_scope()) {
decl_scope = decl_scope->outer_scope()->GetDeclarationScope();
}
Scope* outer_scope = decl_scope->outer_scope();
// For each variable which is used as a function declaration in a sloppy
// block,
for (ZoneHashMap::Entry* p = map->Start(); p != nullptr; p = map->Next(p)) {
const AstRawString* name = static_cast<AstRawString*>(p->key);
for (SloppyBlockFunctionStatement* sloppy_block_function :
sloppy_block_functions_) {
const AstRawString* name = sloppy_block_function->name();
// If the variable wouldn't conflict with a lexical declaration
// or parameter,
......@@ -500,79 +473,52 @@ void DeclarationScope::HoistSloppyBlockFunctions(AstNodeFactory* factory) {
continue;
}
bool declaration_queued = false;
// Write in assignments to var for each block-scoped function declaration
auto delegates = static_cast<SloppyBlockFunctionMap::Delegate*>(p->value);
// Check if there's a conflict with a lexical declaration
Scope* query_scope = sloppy_block_function->scope()->outer_scope();
Variable* var = nullptr;
bool should_hoist = true;
DeclarationScope* decl_scope = this;
while (decl_scope->is_eval_scope()) {
decl_scope = decl_scope->outer_scope()->GetDeclarationScope();
}
Scope* outer_scope = decl_scope->outer_scope();
for (SloppyBlockFunctionMap::Delegate* delegate = delegates;
delegate != nullptr; delegate = delegate->next()) {
// Check if there's a conflict with a lexical declaration
Scope* query_scope = delegate->scope()->outer_scope();
Variable* var = nullptr;
bool should_hoist = true;
// Note that we perform this loop for each delegate named 'name',
// which may duplicate work if those delegates share scopes.
// It is not sufficient to just do a Lookup on query_scope: for
// example, that does not prevent hoisting of the function in
// `{ let e; try {} catch (e) { function e(){} } }`
do {
var = query_scope->LookupInScopeOrScopeInfo(name);
if (var != nullptr && IsLexicalVariableMode(var->mode())) {
should_hoist = false;
break;
}
query_scope = query_scope->outer_scope();
} while (query_scope != outer_scope);
if (!should_hoist) continue;
if (!declaration_queued) {
declaration_queued = true;
names_to_declare.insert({delegate->index(), name});
}
if (factory) {
DCHECK(!is_being_lazily_parsed_);
int pos = delegate->position();
Assignment* assignment = factory->NewAssignment(
Token::ASSIGN, NewUnresolved(factory, name, pos),
delegate->scope()->NewUnresolved(factory, name, pos), pos);
assignment->set_lookup_hoisting_mode(LookupHoistingMode::kLegacySloppy);
Statement* statement = factory->NewExpressionStatement(assignment, pos);
delegate->set_statement(statement);
// It is not sufficient to just do a Lookup on query_scope: for
// example, that does not prevent hoisting of the function in
// `{ let e; try {} catch (e) { function e(){} } }`
do {
var = query_scope->LookupInScopeOrScopeInfo(name);
if (var != nullptr && IsLexicalVariableMode(var->mode())) {
should_hoist = false;
break;
}
}
}
query_scope = query_scope->outer_scope();
} while (query_scope != outer_scope);
if (names_to_declare.empty()) return;
if (!should_hoist) continue;
for (const auto& index_and_name : names_to_declare) {
const AstRawString* name = index_and_name.second;
if (factory) {
DCHECK(!is_being_lazily_parsed_);
auto declaration = factory->NewVariableDeclaration(kNoSourcePosition);
int pos = sloppy_block_function->position();
bool ok = true;
bool was_added;
auto declaration = factory->NewVariableDeclaration(pos);
// Based on the preceding checks, it doesn't matter what we pass as
// sloppy_mode_block_scope_function_redefinition.
bool ok = true;
DeclareVariable(declaration, name, kNoSourcePosition, VariableMode::kVar,
NORMAL_VARIABLE,
Variable::DefaultInitializationFlag(VariableMode::kVar),
&was_added, nullptr, &ok);
Variable* var = DeclareVariable(
declaration, name, pos, VariableMode::kVar, NORMAL_VARIABLE,
Variable::DefaultInitializationFlag(VariableMode::kVar), &was_added,
nullptr, &ok);
DCHECK(ok);
VariableProxy* source =
factory->NewVariableProxy(sloppy_block_function->var());
VariableProxy* target = factory->NewVariableProxy(var);
Assignment* assignment = factory->NewAssignment(
sloppy_block_function->init(), target, source, pos);
assignment->set_lookup_hoisting_mode(LookupHoistingMode::kLegacySloppy);
Statement* statement = factory->NewExpressionStatement(assignment, pos);
sloppy_block_function->set_statement(statement);
} else {
DCHECK(is_being_lazily_parsed_);
bool was_added;
Variable* var = DeclareVariableName(name, VariableMode::kVar, &was_added);
var->set_maybe_assigned();
if (sloppy_block_function->init() == Token::ASSIGN)
var->set_maybe_assigned();
}
}
}
......@@ -1029,16 +975,9 @@ Variable* Scope::DeclareVariable(
// In harmony we treat re-declarations as early errors. See ES5 16 for a
// definition of early errors.
//
// Allow duplicate function decls for web compat, see bug 4693. If the
// duplication is allowed, then the var will show up in the
// SloppyBlockFunctionMap.
SloppyBlockFunctionMap* map =
GetDeclarationScope()->sloppy_block_function_map();
*ok =
map != nullptr && declaration->IsFunctionDeclaration() &&
declaration->AsFunctionDeclaration()
->declares_sloppy_block_function() &&
map->Lookup(const_cast<AstRawString*>(name), name->Hash()) != nullptr;
// Allow duplicate function decls for web compat, see bug 4693.
*ok = var->is_sloppy_block_function() &&
kind == SLOPPY_BLOCK_FUNCTION_VARIABLE;
*sloppy_mode_block_scope_function_redefinition = *ok;
}
}
......@@ -1078,12 +1017,16 @@ Variable* Scope::DeclareVariableName(const AstRawString* name,
Variable* var = DeclareLocal(name, mode, kind, was_added);
if (!*was_added) {
if (IsLexicalVariableMode(mode) || IsLexicalVariableMode(var->mode())) {
// Duplicate functions are allowed in the sloppy mode, but if this is not
// a function declaration, it's an error. This is an error PreParser
// hasn't previously detected.
return nullptr;
if (!var->is_sloppy_block_function() ||
kind != SLOPPY_BLOCK_FUNCTION_VARIABLE) {
// Duplicate functions are allowed in the sloppy mode, but if this is
// not a function declaration, it's an error. This is an error PreParser
// hasn't previously detected.
return nullptr;
}
// Sloppy block function redefinition.
}
if (mode == VariableMode::kVar) var->set_maybe_assigned();
var->set_maybe_assigned();
}
var->set_is_used();
return var;
......@@ -1428,7 +1371,7 @@ void DeclarationScope::ResetAfterPreparsing(AstValueFactory* ast_value_factory,
locals_.Clear();
inner_scope_ = nullptr;
unresolved_list_.Clear();
sloppy_block_function_map_ = nullptr;
sloppy_block_functions_.Clear();
rare_data_ = nullptr;
has_rest_ = false;
......
......@@ -43,37 +43,6 @@ class VariableMap: public ZoneHashMap {
void Add(Zone* zone, Variable* var);
};
// Sloppy block-scoped function declarations to var-bind
class SloppyBlockFunctionMap : public ZoneHashMap {
public:
class Delegate : public ZoneObject {
public:
Delegate(Scope* scope, SloppyBlockFunctionStatement* statement, int index)
: scope_(scope), statement_(statement), next_(nullptr), index_(index) {}
void set_statement(Statement* statement);
void set_next(Delegate* next) { next_ = next; }
Delegate* next() const { return next_; }
Scope* scope() const { return scope_; }
int index() const { return index_; }
int position() const { return statement_->position(); }
private:
Scope* scope_;
SloppyBlockFunctionStatement* statement_;
Delegate* next_;
int index_;
};
explicit SloppyBlockFunctionMap(Zone* zone);
void Declare(Zone* zone, const AstRawString* name, Scope* scope,
SloppyBlockFunctionStatement* statement);
private:
int count_;
};
class Scope;
template <>
......@@ -940,17 +909,12 @@ class V8_EXPORT_PRIVATE DeclarationScope : public Scope {
void AddLocal(Variable* var);
void DeclareSloppyBlockFunction(
const AstRawString* name, Scope* scope,
SloppyBlockFunctionStatement* statement = nullptr);
SloppyBlockFunctionStatement* sloppy_block_function);
// Go through sloppy_block_function_map_ and hoist those (into this scope)
// Go through sloppy_block_functions_ and hoist those (into this scope)
// which should be hoisted.
void HoistSloppyBlockFunctions(AstNodeFactory* factory);
SloppyBlockFunctionMap* sloppy_block_function_map() {
return sloppy_block_function_map_;
}
// Compute top scope and allocate variables. For lazy compilation the top
// scope only contains the single lazily compiled function, so this
// doesn't re-allocate variables repeatedly.
......@@ -1069,7 +1033,7 @@ class V8_EXPORT_PRIVATE DeclarationScope : public Scope {
// Parameter list in source order.
ZonePtrList<Variable> params_;
// Map of function names to lists of functions defined in sloppy blocks
SloppyBlockFunctionMap* sloppy_block_function_map_;
base::ThreadedList<SloppyBlockFunctionStatement> sloppy_block_functions_;
// Convenience variable.
Variable* receiver_;
// Function variable, if any; function scopes only.
......
......@@ -137,6 +137,9 @@ class Variable final : public ZoneObject {
}
bool is_parameter() const { return kind() == PARAMETER_VARIABLE; }
bool is_sloppy_block_function() {
return kind() == SLOPPY_BLOCK_FUNCTION_VARIABLE;
}
Variable* local_if_not_shadowed() const {
DCHECK(mode() == VariableMode::kDynamicLocal &&
......@@ -207,7 +210,7 @@ class Variable final : public ZoneObject {
class VariableModeField : public BitField16<VariableMode, 0, 3> {};
class VariableKindField
: public BitField16<VariableKind, VariableModeField::kNext, 2> {};
: public BitField16<VariableKind, VariableModeField::kNext, 3> {};
class LocationField
: public BitField16<VariableLocation, VariableKindField::kNext, 3> {};
class ForceContextAllocationField
......
......@@ -1099,6 +1099,7 @@ enum VariableKind : uint8_t {
NORMAL_VARIABLE,
PARAMETER_VARIABLE,
THIS_VARIABLE,
SLOPPY_BLOCK_FUNCTION_VARIABLE,
SLOPPY_FUNCTION_NAME_VARIABLE
};
......
......@@ -3648,10 +3648,10 @@ ParserBase<Impl>::ParseHoistableDeclaration(
FuncNameInferrerState fni_state(&fni_);
impl()->PushEnclosingName(name);
FunctionKind kind = FunctionKindFor(flags);
FunctionKind function_kind = FunctionKindFor(flags);
FunctionLiteralT function = impl()->ParseFunctionLiteral(
name, scanner()->location(), name_validity, kind, pos,
name, scanner()->location(), name_validity, function_kind, pos,
FunctionLiteral::kDeclaration, language_mode(), nullptr);
// In ES6, a function behaves as a lexical binding, except in
......@@ -3662,16 +3662,17 @@ ParserBase<Impl>::ParseHoistableDeclaration(
: VariableMode::kVar;
// Async functions don't undergo sloppy mode block scoped hoisting, and don't
// allow duplicates in a block. Both are represented by the
// sloppy_block_function_map. Don't add them to the map for async functions.
// sloppy_block_functions_. Don't add them to the map for async functions.
// Generators are also supposed to be prohibited; currently doing this behind
// a flag and UseCounting violations to assess web compatibility.
bool is_sloppy_block_function = is_sloppy(language_mode()) &&
!scope()->is_declaration_scope() &&
flags == ParseFunctionFlag::kIsNormal;
return impl()->DeclareFunction(variable_name, function, mode, pos,
end_position(), is_sloppy_block_function,
names);
VariableKind kind = is_sloppy(language_mode()) &&
!scope()->is_declaration_scope() &&
flags == ParseFunctionFlag::kIsNormal
? SLOPPY_BLOCK_FUNCTION_VARIABLE
: NORMAL_VARIABLE;
return impl()->DeclareFunction(variable_name, function, mode, kind, pos,
end_position(), names);
}
template <typename Impl>
......
......@@ -1438,20 +1438,20 @@ Statement* Parser::BuildInitializationBlock(
Statement* Parser::DeclareFunction(const AstRawString* variable_name,
FunctionLiteral* function, VariableMode mode,
int beg_pos, int end_pos,
bool is_sloppy_block_function,
VariableKind kind, int beg_pos, int end_pos,
ZonePtrList<const AstRawString>* names) {
Declaration* declaration = factory()->NewFunctionDeclaration(
function, is_sloppy_block_function, beg_pos);
Declaration* declaration =
factory()->NewFunctionDeclaration(function, beg_pos);
bool was_added;
Declare(declaration, variable_name, NORMAL_VARIABLE, mode,
kCreatedInitialized, scope(), &was_added, beg_pos);
Declare(declaration, variable_name, kind, mode, kCreatedInitialized, scope(),
&was_added, beg_pos);
if (names) names->Add(variable_name, zone());
if (is_sloppy_block_function) {
if (kind == SLOPPY_BLOCK_FUNCTION_VARIABLE) {
Token::Value init = loop_nesting_depth() > 0 ? Token::ASSIGN : Token::INIT;
SloppyBlockFunctionStatement* statement =
factory()->NewSloppyBlockFunctionStatement(end_pos);
GetDeclarationScope()->DeclareSloppyBlockFunction(variable_name, scope(),
statement);
factory()->NewSloppyBlockFunctionStatement(end_pos, declaration->var(),
init);
GetDeclarationScope()->DeclareSloppyBlockFunction(statement);
return statement;
}
return factory()->EmptyStatement();
......
......@@ -341,8 +341,7 @@ class V8_EXPORT_PRIVATE Parser : public NON_EXPORTED_BASE(ParserBase<Parser>) {
Statement* DeclareFunction(const AstRawString* variable_name,
FunctionLiteral* function, VariableMode mode,
int beg_pos, int end_pos,
bool is_sloppy_block_function,
VariableKind kind, int beg_pos, int end_pos,
ZonePtrList<const AstRawString>* names);
Variable* CreateSyntheticContextVariable(const AstRawString* synthetic_name);
FunctionLiteral* CreateInitializerFunction(
......
......@@ -1101,16 +1101,14 @@ class PreParser : public ParserBase<PreParser> {
// Don't bother actually binding the proxy.
}
void DeclareVariableName(const AstRawString* name, VariableMode mode,
Scope* scope, bool* was_added,
int position = kNoSourcePosition,
VariableKind kind = NORMAL_VARIABLE) {
Variable* DeclareVariableName(const AstRawString* name, VariableMode mode,
Scope* scope, bool* was_added,
int position = kNoSourcePosition,
VariableKind kind = NORMAL_VARIABLE) {
Variable* var = scope->DeclareVariableName(name, mode, was_added, kind);
if (var == nullptr) {
ReportUnidentifiableError();
return;
}
if (var->scope() != scope) {
} else if (var->scope() != scope) {
DCHECK_NE(kNoSourcePosition, position);
DCHECK_EQ(VariableMode::kVar, mode);
Declaration* nested_declaration =
......@@ -1119,6 +1117,7 @@ class PreParser : public ParserBase<PreParser> {
nested_declaration->set_var(var);
var->scope()->declarations()->Add(nested_declaration);
}
return var;
}
V8_INLINE PreParserBlock RewriteCatchPattern(CatchInfo* catch_info) {
......@@ -1183,22 +1182,22 @@ class PreParser : public ParserBase<PreParser> {
return PreParserStatement::Default();
}
V8_INLINE PreParserStatement
DeclareFunction(const PreParserIdentifier& variable_name,
const PreParserExpression& function, VariableMode mode,
int beg_pos, int end_pos, bool is_sloppy_block_function,
ZonePtrList<const AstRawString>* names) {
V8_INLINE PreParserStatement DeclareFunction(
const PreParserIdentifier& variable_name,
const PreParserExpression& function, VariableMode mode, VariableKind kind,
int beg_pos, int end_pos, ZonePtrList<const AstRawString>* names) {
DCHECK_NULL(names);
if (variable_name.string_ != nullptr) {
bool was_added;
if (is_strict(language_mode())) {
DeclareVariableName(variable_name.string_, mode, scope(), &was_added);
} else {
scope()->DeclareVariableName(variable_name.string_, mode, &was_added);
}
if (is_sloppy_block_function) {
GetDeclarationScope()->DeclareSloppyBlockFunction(variable_name.string_,
scope());
Variable* var = DeclareVariableName(variable_name.string_, mode, scope(),
&was_added, beg_pos, kind);
if (kind == SLOPPY_BLOCK_FUNCTION_VARIABLE) {
Token::Value init =
loop_nesting_depth() > 0 ? Token::ASSIGN : Token::INIT;
SloppyBlockFunctionStatement* statement =
factory()->ast_node_factory()->NewSloppyBlockFunctionStatement(
end_pos, var, init);
GetDeclarationScope()->DeclareSloppyBlockFunction(statement);
}
}
return Statement::Default();
......
......@@ -2,11 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
assertThrows(`
{
function a() {}
}
{
// Duplicate lexical declarations are only allowed if they are both sloppy
// block functions (see bug 4693). In this case the sloppy block function
// conflicts with the lexical variable declaration, causing a syntax error.
let a;
function a() {};
}
`, SyntaxError)
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