diff --git a/Libraries/LibJS/AST.h b/Libraries/LibJS/AST.h index 4dd15e5723..be605409c9 100644 --- a/Libraries/LibJS/AST.h +++ b/Libraries/LibJS/AST.h @@ -120,6 +120,9 @@ public: void add_variables(NonnullRefPtrVector); const NonnullRefPtrVector& variables() const { return m_variables; } + bool in_strict_mode() const { return m_strict_mode; } + void set_strict_mode() { m_strict_mode = true; } + protected: ScopeNode() { } @@ -127,6 +130,7 @@ private: virtual bool is_scope_node() const final { return true; } NonnullRefPtrVector m_children; NonnullRefPtrVector m_variables; + bool m_strict_mode { false }; }; class Program : public ScopeNode { diff --git a/Libraries/LibJS/Interpreter.h b/Libraries/LibJS/Interpreter.h index 0fe178eeb6..e0ffbb6722 100644 --- a/Libraries/LibJS/Interpreter.h +++ b/Libraries/LibJS/Interpreter.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -123,6 +124,8 @@ public: const LexicalEnvironment* current_environment() const { return m_call_stack.last().environment; } LexicalEnvironment* current_environment() { return m_call_stack.last().environment; } + bool in_strict_mode() const { return m_scope_stack.last().scope_node->in_strict_mode(); } + size_t argument_count() const { if (m_call_stack.is_empty()) diff --git a/Libraries/LibJS/Parser.cpp b/Libraries/LibJS/Parser.cpp index 09eb4fc239..2e72f07717 100644 --- a/Libraries/LibJS/Parser.cpp +++ b/Libraries/LibJS/Parser.cpp @@ -206,9 +206,20 @@ NonnullRefPtr Parser::parse_program() { ScopePusher scope(*this, ScopePusher::Var | ScopePusher::Let); auto program = adopt(*new Program); + + bool first = true; + m_parser_state.m_use_strict_directive = UseStrictDirectiveState::Looking; while (!done()) { if (match_statement()) { program->append(parse_statement()); + if (first) { + if (m_parser_state.m_use_strict_directive == UseStrictDirectiveState::Found) { + program->set_strict_mode(); + m_parser_state.m_strict_mode = true; + } + first = false; + m_parser_state.m_use_strict_directive = UseStrictDirectiveState::None; + } } else { expected("statement"); consume(); @@ -594,6 +605,15 @@ NonnullRefPtr Parser::parse_string_literal(Token token) m_parser_state.m_current_token.line_number(), m_parser_state.m_current_token.line_column()); } + + if (m_parser_state.m_use_strict_directive == UseStrictDirectiveState::Looking) { + if (string == "use strict" && token.type() != TokenType::TemplateLiteralString) { + m_parser_state.m_use_strict_directive = UseStrictDirectiveState::Found; + } else { + m_parser_state.m_use_strict_directive = UseStrictDirectiveState::None; + } + } + return create_ast_node(string); } @@ -910,16 +930,37 @@ NonnullRefPtr Parser::parse_block_statement() ScopePusher scope(*this, ScopePusher::Let); auto block = create_ast_node(); consume(TokenType::CurlyOpen); + + bool first = true; + 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; + block->set_strict_mode(); + } else { + m_parser_state.m_use_strict_directive = UseStrictDirectiveState::Looking; + } + while (!done() && !match(TokenType::CurlyClose)) { if (match(TokenType::Semicolon)) { consume(); } else if (match_statement()) { block->append(parse_statement()); + + if (first && !initial_strict_mode_state) { + if (m_parser_state.m_use_strict_directive == UseStrictDirectiveState::Found) { + block->set_strict_mode(); + m_parser_state.m_strict_mode = true; + } + m_parser_state.m_use_strict_directive = UseStrictDirectiveState::None; + } } else { expected("statement"); consume(); } + + first = false; } + m_parser_state.m_strict_mode = initial_strict_mode_state; consume(TokenType::CurlyClose); block->add_variables(m_parser_state.m_let_scopes.last()); return block; diff --git a/Libraries/LibJS/Parser.h b/Libraries/LibJS/Parser.h index 259b7439e3..2c861d1969 100644 --- a/Libraries/LibJS/Parser.h +++ b/Libraries/LibJS/Parser.h @@ -26,10 +26,10 @@ #pragma once -#include "AST.h" -#include "Lexer.h" #include #include +#include +#include #include namespace JS { @@ -134,12 +134,20 @@ private: void save_state(); void load_state(); + enum class UseStrictDirectiveState { + None, + Looking, + Found, + }; + struct ParserState { Lexer m_lexer; Token m_current_token; Vector m_errors; Vector> m_var_scopes; Vector> m_let_scopes; + UseStrictDirectiveState m_use_strict_directive { UseStrictDirectiveState::None }; + bool m_strict_mode { false }; explicit ParserState(Lexer); }; diff --git a/Libraries/LibJS/Tests/arrow-functions.js b/Libraries/LibJS/Tests/arrow-functions.js index a0461caa97..2edadd1342 100644 --- a/Libraries/LibJS/Tests/arrow-functions.js +++ b/Libraries/LibJS/Tests/arrow-functions.js @@ -60,6 +60,31 @@ try { assert(foo === undefined); assert(bar === undefined); + (() => { + "use strict"; + assert(isStrictMode()); + + (() => { + assert(isStrictMode()); + })(); + })(); + + (() => { + 'use strict'; + assert(isStrictMode()); + })(); + + (() => { + assert(!isStrictMode()); + + (() => { + "use strict"; + assert(isStrictMode()); + })(); + + assert(!isStrictMode()); + })(); + console.log("PASS"); } catch { console.log("FAIL"); diff --git a/Libraries/LibJS/Tests/function-strict-mode.js b/Libraries/LibJS/Tests/function-strict-mode.js new file mode 100644 index 0000000000..382327d9bb --- /dev/null +++ b/Libraries/LibJS/Tests/function-strict-mode.js @@ -0,0 +1,52 @@ +load("test-common.js"); + +try { + (function() { + assert(!isStrictMode()); + })(); + + (function() { + 'use strict'; + assert(isStrictMode()); + })(); + + (function() { + "use strict"; + assert(isStrictMode()); + })(); + + (function() { + `use strict`; + assert(!isStrictMode()); + })(); + + (function() { + ;'use strict'; + assert(!isStrictMode()); + })(); + + (function() { + ;"use strict"; + assert(!isStrictMode()); + })(); + + (function() { + "use strict"; + (function() { + assert(isStrictMode()); + })(); + })(); + + (function() { + assert(!isStrictMode()); + (function(){ + "use strict"; + assert(isStrictMode()); + })(); + assert(!isStrictMode()); + })(); + + console.log("PASS"); +} catch (e) { + console.log("FAIL: " + e); +} diff --git a/Libraries/LibJS/Tests/program-strict-mode.js b/Libraries/LibJS/Tests/program-strict-mode.js new file mode 100644 index 0000000000..3bc3859915 --- /dev/null +++ b/Libraries/LibJS/Tests/program-strict-mode.js @@ -0,0 +1,30 @@ +"use strict"; + +load("test-common.js"); + +try { + assert(isStrictMode()); + + (function() { + assert(isStrictMode()); + })(); + + (function() { + "use strict"; + assert(isStrictMode()); + })(); + + + (() => { + assert(isStrictMode()); + })(); + + (() => { + "use strict"; + assert(isStrictMode()); + })(); + + console.log("PASS"); +} catch (e) { + console.log("FAIL: " + e); +} diff --git a/Userland/js.cpp b/Userland/js.cpp index ccf6c37117..d04b5ad2e3 100644 --- a/Userland/js.cpp +++ b/Userland/js.cpp @@ -55,6 +55,7 @@ public: virtual ~ReplObject() override; static JS::Value load_file(JS::Interpreter&); + static JS::Value is_strict_mode(JS::Interpreter&); private: virtual const char* class_name() const override { return "ReplObject"; } @@ -391,6 +392,11 @@ JS::Value ReplObject::load_file(JS::Interpreter& interpreter) return JS::Value(true); } +JS::Value ReplObject::is_strict_mode(JS::Interpreter& interpreter) +{ + return JS::Value(interpreter.in_strict_mode()); +} + void repl(JS::Interpreter& interpreter) { while (!s_fail_repl) { @@ -405,6 +411,7 @@ void repl(JS::Interpreter& interpreter) void enable_test_mode(JS::Interpreter& interpreter) { interpreter.global_object().define_native_function("load", ReplObject::load_file); + interpreter.global_object().define_native_function("isStrictMode", ReplObject::is_strict_mode); } static Function interrupt_interpreter;