From b4c88d5de44ca127e0f5a778577ff49e54c7c637 Mon Sep 17 00:00:00 2001 From: "mmaly@chromium.org" Date: Tue, 18 Jan 2011 16:43:53 +0000 Subject: [PATCH] First part of ES5 strict mode. - var eval | arguments - catch (eval | arguments) - 'with' is disabled - function can't be named eval or arguments - function parameter name cannot be eval or arguments - no duplicate parameter names allowed Add FLAG_strict_mode git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@6369 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/ast.h | 6 ++- src/flag-definitions.h | 1 + src/heap.h | 3 +- src/messages.js | 8 ++- src/parser.cc | 127 +++++++++++++++++++++++++++++++++++++++++--- src/scopes.cc | 12 +++++ src/scopes.h | 4 ++ test/mjsunit/strict-mode.js | 117 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 269 insertions(+), 9 deletions(-) create mode 100644 test/mjsunit/strict-mode.js diff --git a/src/ast.h b/src/ast.h index f55ddcd..a897e88 100644 --- a/src/ast.h +++ b/src/ast.h @@ -1675,7 +1675,8 @@ class FunctionLiteral: public Expression { int start_position, int end_position, bool is_expression, - bool contains_loops) + bool contains_loops, + bool strict_mode) : name_(name), scope_(scope), body_(body), @@ -1689,6 +1690,7 @@ class FunctionLiteral: public Expression { end_position_(end_position), is_expression_(is_expression), contains_loops_(contains_loops), + strict_mode_(strict_mode), function_token_position_(RelocInfo::kNoPosition), inferred_name_(Heap::empty_string()), try_full_codegen_(false), @@ -1705,6 +1707,7 @@ class FunctionLiteral: public Expression { int end_position() const { return end_position_; } bool is_expression() const { return is_expression_; } bool contains_loops() const { return contains_loops_; } + bool strict_mode() const { return strict_mode_; } int materialized_literal_count() { return materialized_literal_count_; } int expected_property_count() { return expected_property_count_; } @@ -1747,6 +1750,7 @@ class FunctionLiteral: public Expression { int end_position_; bool is_expression_; bool contains_loops_; + bool strict_mode_; int function_token_position_; Handle inferred_name_; bool try_full_codegen_; diff --git a/src/flag-definitions.h b/src/flag-definitions.h index fb892d6..b90534c 100644 --- a/src/flag-definitions.h +++ b/src/flag-definitions.h @@ -301,6 +301,7 @@ DEFINE_bool(use_verbose_printer, true, "allows verbose printing") // parser.cc DEFINE_bool(allow_natives_syntax, false, "allow natives syntax") +DEFINE_bool(strict_mode, true, "allow strict mode directives") // rewriter.cc DEFINE_bool(optimize_ast, true, "optimize the ast") diff --git a/src/heap.h b/src/heap.h index 0d79081..c52ec02 100644 --- a/src/heap.h +++ b/src/heap.h @@ -203,7 +203,8 @@ namespace internal { V(zero_symbol, "0") \ V(global_eval_symbol, "GlobalEval") \ V(identity_hash_symbol, "v8::IdentityHash") \ - V(closure_symbol, "(closure)") + V(closure_symbol, "(closure)") \ + V(use_strict, "use strict") // Forward declarations. diff --git a/src/messages.js b/src/messages.js index a30ef8a..e62f06f 100644 --- a/src/messages.js +++ b/src/messages.js @@ -202,7 +202,13 @@ function FormatMessage(message) { array_indexof_not_defined: "Array.getIndexOf: Argument undefined", object_not_extensible: "Can't add property %0, object is not extensible", illegal_access: "Illegal access", - invalid_preparser_data: "Invalid preparser data for function %0" + invalid_preparser_data: "Invalid preparser data for function %0", + strict_mode_with: "Strict mode code may not include a with statement", + strict_catch_variable: "Catch variable may not be eval or arguments in strict mode", + strict_param_name: "Parameter name eval or arguments is not allowed in strict mode", + strict_param_dupe: "Strict mode function may not have duplicate parameter names", + strict_var_name: "Variable name may not be eval or arguments in strict mode", + strict_function_name: "Function name may not be eval or arguments in strict mode", }; } var format = kMessages[message.type]; diff --git a/src/parser.cc b/src/parser.cc index 6ad9ab3..af2010b 100644 --- a/src/parser.cc +++ b/src/parser.cc @@ -283,6 +283,11 @@ class TemporaryScope BASE_EMBEDDED { void AddLoop() { loop_count_++; } bool ContainsLoops() const { return loop_count_ > 0; } + bool StrictMode() { return strict_mode_; } + void EnableStrictMode() { + strict_mode_ = FLAG_strict_mode; + } + private: // Captures the number of literals that need materialization in the // function. Includes regexp literals, and boilerplate for object @@ -300,6 +305,9 @@ class TemporaryScope BASE_EMBEDDED { // Captures the number of loops inside the scope. int loop_count_; + // Parsing strict mode code. + bool strict_mode_; + // Bookkeeping TemporaryScope** variable_; TemporaryScope* parent_; @@ -314,6 +322,8 @@ TemporaryScope::TemporaryScope(TemporaryScope** variable) loop_count_(0), variable_(variable), parent_(*variable) { + // Inherit the strict mode from the parent scope. + strict_mode_ = (parent_ != NULL) && parent_->strict_mode_; *variable = this; } @@ -561,7 +571,6 @@ class LexicalScope BASE_EMBEDDED { int prev_level_; }; - // ---------------------------------------------------------------------------- // The CHECK_OK macro is a convenient macro to enforce error // handling for functions that may fail (by returning !*ok). @@ -669,7 +678,8 @@ FunctionLiteral* Parser::DoParseProgram(Handle source, 0, source->length(), false, - temp_scope.ContainsLoops()); + temp_scope.ContainsLoops(), + temp_scope.StrictMode()); } else if (stack_overflow_) { Top::StackOverflow(); } @@ -1075,9 +1085,46 @@ void* Parser::ParseSourceElements(ZoneList* processor, ASSERT(processor != NULL); InitializationBlockFinder block_finder; ThisNamedPropertyAssigmentFinder this_property_assignment_finder; + bool directive_prologue = true; // Parsing directive prologue. + while (peek() != end_token) { + if (directive_prologue && peek() != Token::STRING) { + directive_prologue = false; + } + + Scanner::Location token_loc = scanner().peek_location(); Statement* stat = ParseStatement(NULL, CHECK_OK); - if (stat == NULL || stat->IsEmpty()) continue; + + if (stat == NULL || stat->IsEmpty()) { + directive_prologue = false; // End of directive prologue. + continue; + } + + if (directive_prologue) { + // A shot at a directive. + ExpressionStatement *e_stat; + Literal *literal; + // Still processing directive prologue? + if ((e_stat = stat->AsExpressionStatement()) != NULL && + (literal = e_stat->expression()->AsLiteral()) != NULL && + literal->handle()->IsString()) { + Handle directive = Handle::cast(literal->handle()); + + // Check "use strict" directive (ES5 14.1). + if (!temp_scope_->StrictMode() && + directive->Equals(Heap::use_strict()) && + token_loc.end_pos - token_loc.beg_pos == + Heap::use_strict()->length() + 2) { + temp_scope_->EnableStrictMode(); + // "use strict" is the only directive for now. + directive_prologue = false; + } + } else { + // End of the directive prologue. + directive_prologue = false; + } + } + // We find and mark the initialization blocks on top level code only. // This is because the optimization prevents reuse of the map transitions, // so it should be used only for code that will only be run once. @@ -1431,6 +1478,10 @@ Block* Parser::ParseVariableStatement(bool* ok) { return result; } +static bool IsEvalOrArguments(Handle string) { + return string.is_identical_to(Factory::eval_symbol()) || + string.is_identical_to(Factory::arguments_symbol()); +} // If the variable declaration declares exactly one non-const // variable, then *var is set to that variable. In all other cases, @@ -1479,6 +1530,13 @@ Block* Parser::ParseVariableDeclarations(bool accept_IN, Handle name = ParseIdentifier(CHECK_OK); if (fni_ != NULL) fni_->PushVariableName(name); + // Strict mode variables may not be named eval or arguments + if (temp_scope_->StrictMode() && IsEvalOrArguments(name)) { + ReportMessage("strict_var_name", Vector::empty()); + *ok = false; + return NULL; + } + // Declare variable. // Note that we *always* must treat the initial value via a separate init // assignment for variables and constants because the value must be assigned @@ -1839,6 +1897,13 @@ Statement* Parser::ParseWithStatement(ZoneStringList* labels, bool* ok) { // 'with' '(' Expression ')' Statement Expect(Token::WITH, CHECK_OK); + + if (temp_scope_->StrictMode()) { + ReportMessage("strict_mode_with", Vector::empty()); + *ok = false; + return NULL; + } + Expect(Token::LPAREN, CHECK_OK); Expression* expr = ParseExpression(true, CHECK_OK); Expect(Token::RPAREN, CHECK_OK); @@ -1971,6 +2036,13 @@ TryStatement* Parser::ParseTryStatement(bool* ok) { Expect(Token::LPAREN, CHECK_OK); Handle name = ParseIdentifier(CHECK_OK); + + if (temp_scope_->StrictMode() && IsEvalOrArguments(name)) { + ReportMessage("strict_catch_variable", Vector::empty()); + *ok = false; + return NULL; + } + Expect(Token::RPAREN, CHECK_OK); if (peek() == Token::LBRACE) { @@ -3224,11 +3296,27 @@ FunctionLiteral* Parser::ParseFunctionLiteral(Handle var_name, // '(' (Identifier)*[','] ')' Expect(Token::LPAREN, CHECK_OK); int start_pos = scanner().location().beg_pos; + Scanner::Location eval_loc(RelocInfo::kNoPosition, RelocInfo::kNoPosition); + Scanner::Location dupe_loc(RelocInfo::kNoPosition, RelocInfo::kNoPosition); + bool done = (peek() == Token::RPAREN); while (!done) { Handle param_name = ParseIdentifier(CHECK_OK); - top_scope_->AddParameter(top_scope_->DeclareLocal(param_name, - Variable::VAR)); + Variable* parameter = top_scope_->DeclareLocal(param_name, Variable::VAR); + + // Store locations for possible future error reports. + if (eval_loc.beg_pos == RelocInfo::kNoPosition && + IsEvalOrArguments(param_name)) { + // Store location for later + eval_loc = scanner().location(); + } + if (dupe_loc.beg_pos == RelocInfo::kNoPosition && + top_scope_->IsParameterDeclared(param_name)) { + // Store location for later + dupe_loc = scanner().location(); + } + + top_scope_->AddParameter(parameter); num_parameters++; done = (peek() == Token::RPAREN); if (!done) Expect(Token::COMMA, CHECK_OK); @@ -3300,6 +3388,32 @@ FunctionLiteral* Parser::ParseFunctionLiteral(Handle var_name, end_pos = scanner().location().end_pos; } + // Validate strict mode. + if (temp_scope_->StrictMode()) { + if (IsEvalOrArguments(name)) { + int position = function_token_position != RelocInfo::kNoPosition + ? function_token_position + : (start_pos > 0 ? start_pos - 1 : start_pos); + ReportMessageAt(Scanner::Location(position, start_pos), + "strict_function_name", Vector::empty()); + *ok = false; + return NULL; + } + if (eval_loc.beg_pos != RelocInfo::kNoPosition) { + ReportMessageAt(eval_loc, "strict_param_name", + Vector::empty()); + *ok = false; + return NULL; + } + if (dupe_loc.beg_pos != RelocInfo::kNoPosition) { + ReportMessageAt(dupe_loc, "strict_param_dupe", + Vector::empty()); + *ok = false; + return NULL; + } + // TODO(mmaly): Check for octal escape sequence here. + } + FunctionLiteral* function_literal = new FunctionLiteral(name, top_scope_, @@ -3312,7 +3426,8 @@ FunctionLiteral* Parser::ParseFunctionLiteral(Handle var_name, start_pos, end_pos, function_name->length() > 0, - temp_scope.ContainsLoops()); + temp_scope.ContainsLoops(), + temp_scope.StrictMode()); function_literal->set_function_token_position(function_token_position); if (fni_ != NULL && !is_named) fni_->AddFunction(function_literal); diff --git a/src/scopes.cc b/src/scopes.cc index 58a10ee..e96ca99 100644 --- a/src/scopes.cc +++ b/src/scopes.cc @@ -445,6 +445,18 @@ int Scope::ContextChainLength(Scope* scope) { } +bool Scope::IsParameterDeclared(Handle name) { + ASSERT(name->IsSymbol()); + for (int i = 0, length = num_parameters(); i < length; i ++) { + ASSERT(parameter(i)->name()->IsSymbol()); + if (name.is_identical_to(parameter(i)->name())) { + return true; + } + } + return false; +} + + #ifdef DEBUG static const char* Header(Scope::Type type) { switch (type) { diff --git a/src/scopes.h b/src/scopes.h index 09901ad..85b059c 100644 --- a/src/scopes.h +++ b/src/scopes.h @@ -289,6 +289,10 @@ class Scope: public ZoneObject { int ContextChainLength(Scope* scope); // --------------------------------------------------------------------------- + // Strict mode support. + bool IsParameterDeclared(Handle name); + + // --------------------------------------------------------------------------- // Debugging. #ifdef DEBUG diff --git a/test/mjsunit/strict-mode.js b/test/mjsunit/strict-mode.js new file mode 100644 index 0000000..68a0d7d --- /dev/null +++ b/test/mjsunit/strict-mode.js @@ -0,0 +1,117 @@ +// Copyright 2011 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "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 THE COPYRIGHT +// OWNER 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. + +function CheckStrictMode(code, exception) { + assertDoesNotThrow(code); + assertThrows("'use strict';\n" + code, exception); + assertThrows('"use strict";\n' + code, exception); + assertDoesNotThrow("\ + function outer() {\ + function inner() {\n" + + code + + "\n}\ + }"); + assertThrows("\ + function outer() {\ + 'use strict';\ + function inner() {\n" + + code + + "\n}\ + }", exception); +} + +// Incorrect 'use strict' directive. +function UseStrictEscape() { + "use\\x20strict"; + with ({}) {}; +} + +// 'use strict' in non-directive position. +function UseStrictNonDirective() { + void(0); + "use strict"; + with ({}) {}; +} + +// Multiple directives, including "use strict". +assertThrows('\ +"directive 1";\ +"another directive";\ +"use strict";\ +"directive after strict";\ +"and one more";\ +with({}) {}', SyntaxError); + +// 'with' disallowed in strict mode. +CheckStrictMode("with({}) {}", SyntaxError); + +// Function named 'eval'. +CheckStrictMode("function eval() {}", SyntaxError) + +// Function named 'arguments'. +CheckStrictMode("function arguments() {}", SyntaxError) + +// Function parameter named 'eval'. +CheckStrictMode("function foo(a, b, eval, c, d) {}", SyntaxError) + +// Function parameter named 'arguments'. +CheckStrictMode("function foo(a, b, arguments, c, d) {}", SyntaxError) + +// Property accessor parameter named 'eval'. +CheckStrictMode("var o = { set foo(eval) {} }", SyntaxError) + +// Property accessor parameter named 'arguments'. +CheckStrictMode("var o = { set foo(arguments) {} }", SyntaxError) + +// Duplicate function parameter name. +CheckStrictMode("function foo(a, b, c, d, b) {}", SyntaxError) + +// catch(eval) +CheckStrictMode("try{}catch(eval){};", SyntaxError) + +// catch(arguments) +CheckStrictMode("try{}catch(arguments){};", SyntaxError) + +// var eval +CheckStrictMode("var eval;", SyntaxError) + +// var arguments +CheckStrictMode("var arguments;", SyntaxError) + +// Strict mode applies to the function in which the directive is used.. +assertThrows('\ +function foo(eval) {\ + "use strict";\ +}', SyntaxError); + +// Strict mode doesn't affect the outer stop of strict code. +function NotStrict(eval) { + function Strict() { + "use strict"; + } + with ({}) {}; +} -- 2.7.4