Ver código fonte

LibJS: Fix "use strict" directive false positives

By having the "is this a use strict directive?" logic in
parse_string_literal() we would apply it to *any* string literal, which
is incorrect and would lead to false positives - e.g.:

    "use strict" + 1
    `"use strict"`
    "\123"; ({"use strict": ...})

Relevant part from the spec which is now implemented properly:

[...] and where each ExpressionStatement in the sequence consists
entirely of a StringLiteral token [...]

I also got rid of UseStrictDirectiveState which is not needed anymore.

Fixes #3903.
Linus Groh 4 anos atrás
pai
commit
9e80c67608

+ 15 - 5
Libraries/LibJS/AST.h

@@ -64,6 +64,8 @@ public:
     virtual bool is_call_expression() const { return false; }
     virtual bool is_call_expression() const { return false; }
     virtual bool is_new_expression() const { return false; }
     virtual bool is_new_expression() const { return false; }
     virtual bool is_super_expression() const { return false; }
     virtual bool is_super_expression() const { return false; }
+    virtual bool is_expression_statement() const { return false; };
+    virtual bool is_string_literal() const { return false; };
 
 
 protected:
 protected:
     ASTNode() { }
     ASTNode() { }
@@ -99,11 +101,15 @@ public:
     {
     {
     }
     }
 
 
-    Value execute(Interpreter&, GlobalObject&) const override;
-    const char* class_name() const override { return "ExpressionStatement"; }
+    virtual Value execute(Interpreter&, GlobalObject&) const override;
     virtual void dump(int indent) const override;
     virtual void dump(int indent) const override;
+    virtual bool is_expression_statement() const override { return true; }
+
+    const Expression& expression() const { return m_expression; };
 
 
 private:
 private:
+    virtual const char* class_name() const override { return "ExpressionStatement"; }
+
     NonnullRefPtr<Expression> m_expression;
     NonnullRefPtr<Expression> m_expression;
 };
 };
 
 
@@ -590,20 +596,24 @@ private:
 
 
 class StringLiteral final : public Literal {
 class StringLiteral final : public Literal {
 public:
 public:
-    explicit StringLiteral(String value)
+    explicit StringLiteral(String value, bool is_use_strict_directive = false)
         : m_value(move(value))
         : m_value(move(value))
+        , m_is_use_strict_directive(is_use_strict_directive)
     {
     {
     }
     }
 
 
-    StringView value() const { return m_value; }
-
     virtual Value execute(Interpreter&, GlobalObject&) const override;
     virtual Value execute(Interpreter&, GlobalObject&) const override;
     virtual void dump(int indent) const override;
     virtual void dump(int indent) const override;
+    virtual bool is_string_literal() const override { return true; };
+
+    StringView value() const { return m_value; }
+    bool is_use_strict_directive() const { return m_is_use_strict_directive; };
 
 
 private:
 private:
     virtual const char* class_name() const override { return "StringLiteral"; }
     virtual const char* class_name() const override { return "StringLiteral"; }
 
 
     String m_value;
     String m_value;
+    bool m_is_use_strict_directive;
 };
 };
 
 
 class NullLiteral final : public Literal {
 class NullLiteral final : public Literal {

+ 27 - 43
Libraries/LibJS/Parser.cpp

@@ -33,6 +33,17 @@
 
 
 namespace JS {
 namespace JS {
 
 
+static bool statement_is_use_strict_directive(NonnullRefPtr<Statement> statement)
+{
+    if (!statement->is_expression_statement())
+        return false;
+    auto& expression_statement = static_cast<ExpressionStatement&>(*statement);
+    auto& expression = expression_statement.expression();
+    if (!expression.is_string_literal())
+        return false;
+    return static_cast<const StringLiteral&>(expression).is_use_strict_directive();
+}
+
 class ScopePusher {
 class ScopePusher {
 public:
 public:
     enum Type {
     enum Type {
@@ -239,28 +250,25 @@ NonnullRefPtr<Program> Parser::parse_program()
     auto program = adopt(*new Program);
     auto program = adopt(*new Program);
 
 
     bool first = true;
     bool first = true;
-    m_parser_state.m_use_strict_directive = UseStrictDirectiveState::Looking;
     while (!done()) {
     while (!done()) {
         if (match_declaration()) {
         if (match_declaration()) {
             program->append(parse_declaration());
             program->append(parse_declaration());
-            if (first) {
-                first = false;
-                m_parser_state.m_use_strict_directive = UseStrictDirectiveState::None;
-            }
         } else if (match_statement()) {
         } else if (match_statement()) {
-            program->append(parse_statement());
-            if (first) {
-                if (m_parser_state.m_use_strict_directive == UseStrictDirectiveState::Found) {
+            auto statement = parse_statement();
+            program->append(statement);
+            if (statement_is_use_strict_directive(statement)) {
+                if (first) {
                     program->set_strict_mode();
                     program->set_strict_mode();
                     m_parser_state.m_strict_mode = true;
                     m_parser_state.m_strict_mode = true;
                 }
                 }
-                first = false;
-                m_parser_state.m_use_strict_directive = UseStrictDirectiveState::None;
+                if (m_parser_state.m_string_legacy_octal_escape_sequence_in_scope)
+                    syntax_error("Octal escape sequence in string literal not allowed in strict mode");
             }
             }
         } else {
         } else {
             expected("statement or declaration");
             expected("statement or declaration");
             consume();
             consume();
         }
         }
+        first = false;
     }
     }
     if (m_parser_state.m_var_scopes.size() == 1) {
     if (m_parser_state.m_var_scopes.size() == 1) {
         program->add_variables(m_parser_state.m_var_scopes.last());
         program->add_variables(m_parser_state.m_var_scopes.last());
@@ -862,26 +870,9 @@ NonnullRefPtr<StringLiteral> Parser::parse_string_literal(Token token, bool in_t
             syntax_error(message, token.line_number(), token.line_column());
             syntax_error(message, token.line_number(), token.line_column());
     }
     }
 
 
-    auto is_use_strict_directive = token.value() == "'use strict'" || token.value() == "\"use strict\"";
-
-    // It is possible for string literals to precede a Use Strict Directive that places the
-    // enclosing code in strict mode, and implementations must take care to not use this
-    // extended definition of EscapeSequence with such literals. For example, attempting to
-    // parse the following source text must fail:
-    //
-    // function invalid() { "\7"; "use strict"; }
-
-    if (m_parser_state.m_string_legacy_octal_escape_sequence_in_scope && is_use_strict_directive)
-        syntax_error("Octal escape sequence in string literal not allowed in strict mode");
+    auto is_use_strict_directive = !in_template_literal && (token.value() == "'use strict'" || token.value() == "\"use strict\"");
 
 
-    if (m_parser_state.m_use_strict_directive == UseStrictDirectiveState::Looking) {
-        if (is_use_strict_directive)
-            m_parser_state.m_use_strict_directive = UseStrictDirectiveState::Found;
-        else
-            m_parser_state.m_use_strict_directive = UseStrictDirectiveState::None;
-    }
-
-    return create_ast_node<StringLiteral>(string);
+    return create_ast_node<StringLiteral>(string, is_use_strict_directive);
 }
 }
 
 
 NonnullRefPtr<TemplateLiteral> Parser::parse_template_literal(bool is_tagged)
 NonnullRefPtr<TemplateLiteral> Parser::parse_template_literal(bool is_tagged)
@@ -1238,34 +1229,27 @@ NonnullRefPtr<BlockStatement> Parser::parse_block_statement(bool& is_strict)
 
 
     bool first = true;
     bool first = true;
     bool initial_strict_mode_state = m_parser_state.m_strict_mode;
     bool initial_strict_mode_state = m_parser_state.m_strict_mode;
-    if (initial_strict_mode_state) {
-        m_parser_state.m_use_strict_directive = UseStrictDirectiveState::None;
+    if (initial_strict_mode_state)
         is_strict = true;
         is_strict = true;
-    } else {
-        m_parser_state.m_use_strict_directive = UseStrictDirectiveState::Looking;
-    }
 
 
     while (!done() && !match(TokenType::CurlyClose)) {
     while (!done() && !match(TokenType::CurlyClose)) {
         if (match_declaration()) {
         if (match_declaration()) {
             block->append(parse_declaration());
             block->append(parse_declaration());
-
-            if (first && !initial_strict_mode_state)
-                m_parser_state.m_use_strict_directive = UseStrictDirectiveState::None;
         } else if (match_statement()) {
         } else if (match_statement()) {
-            block->append(parse_statement());
-
-            if (first && !initial_strict_mode_state) {
-                if (m_parser_state.m_use_strict_directive == UseStrictDirectiveState::Found) {
+            auto statement = parse_statement();
+            block->append(statement);
+            if (statement_is_use_strict_directive(statement)) {
+                if (first && !initial_strict_mode_state) {
                     is_strict = true;
                     is_strict = true;
                     m_parser_state.m_strict_mode = true;
                     m_parser_state.m_strict_mode = true;
                 }
                 }
-                m_parser_state.m_use_strict_directive = UseStrictDirectiveState::None;
+                if (m_parser_state.m_string_legacy_octal_escape_sequence_in_scope)
+                    syntax_error("Octal escape sequence in string literal not allowed in strict mode");
             }
             }
         } else {
         } else {
             expected("statement or declaration");
             expected("statement or declaration");
             consume();
             consume();
         }
         }
-
         first = false;
         first = false;
     }
     }
     m_parser_state.m_strict_mode = initial_strict_mode_state;
     m_parser_state.m_strict_mode = initial_strict_mode_state;

+ 0 - 7
Libraries/LibJS/Parser.h

@@ -164,12 +164,6 @@ private:
     void save_state();
     void save_state();
     void load_state();
     void load_state();
 
 
-    enum class UseStrictDirectiveState {
-        None,
-        Looking,
-        Found,
-    };
-
     struct ParserState {
     struct ParserState {
         Lexer m_lexer;
         Lexer m_lexer;
         Token m_current_token;
         Token m_current_token;
@@ -177,7 +171,6 @@ private:
         Vector<NonnullRefPtrVector<VariableDeclaration>> m_var_scopes;
         Vector<NonnullRefPtrVector<VariableDeclaration>> m_var_scopes;
         Vector<NonnullRefPtrVector<VariableDeclaration>> m_let_scopes;
         Vector<NonnullRefPtrVector<VariableDeclaration>> m_let_scopes;
         Vector<NonnullRefPtrVector<FunctionDeclaration>> m_function_scopes;
         Vector<NonnullRefPtrVector<FunctionDeclaration>> m_function_scopes;
-        UseStrictDirectiveState m_use_strict_directive { UseStrictDirectiveState::None };
         HashTable<StringView> m_labels_in_scope;
         HashTable<StringView> m_labels_in_scope;
         bool m_strict_mode { false };
         bool m_strict_mode { false };
         bool m_allow_super_property_lookup { false };
         bool m_allow_super_property_lookup { false };

+ 12 - 0
Libraries/LibJS/Tests/use-strict-directive.js

@@ -45,4 +45,16 @@ test("invalid 'use strict; directive", () => {
             return isStrictMode();
             return isStrictMode();
         })()
         })()
     ).toBeFalse();
     ).toBeFalse();
+    expect(
+        (() => {
+            `"use strict"`;
+            return isStrictMode();
+        })()
+    ).toBeFalse();
+    expect(
+        (() => {
+            "use strict" + 1;
+            return isStrictMode();
+        })()
+    ).toBeFalse();
 });
 });