Browse Source

LibJS: Use correct `this` value when callee is a `with` binding

If we're inside of a `with` statement scope, we have to take care to
extract the correct `this` value for use in calls when calling a method
on the binding object via an Identifier instead of a MemberExpression.

This makes Vue.js work way better in the bytecode VM. :^)

Also, 1 new pass on test262.
Andreas Kling 1 year ago
parent
commit
e91bdedc93

+ 13 - 0
Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp

@@ -1526,6 +1526,19 @@ Bytecode::CodeGenerationErrorOr<void> CallExpression::generate_bytecode(Bytecode
     } else if (is<OptionalChain>(*m_callee)) {
         auto& optional_chain = static_cast<OptionalChain const&>(*m_callee);
         TRY(generate_optional_chain(generator, optional_chain, callee_reg, this_reg));
+    } else if (is<Identifier>(*m_callee)) {
+        // If the callee is an identifier, we may need to extract a `this` value.
+        // This is important when we're inside a `with` statement and calling a method on
+        // the environment's binding object.
+        // NOTE: If the identifier refers to a known "local" or "global", we know it can't be
+        //       a `with` binding, so we can skip this.
+        auto& identifier = static_cast<Identifier const&>(*m_callee);
+        if (!identifier.is_local() && !identifier.is_global()) {
+            generator.emit<Bytecode::Op::GetCalleeAndThisFromEnvironment>(generator.intern_identifier(identifier.string()), callee_reg, this_reg);
+        } else {
+            TRY(m_callee->generate_bytecode(generator));
+            generator.emit<Bytecode::Op::Store>(callee_reg);
+        }
     } else {
         // FIXME: this = global object in sloppy mode.
         TRY(m_callee->generate_bytecode(generator));

+ 105 - 104
Userland/Libraries/LibJS/Bytecode/Instruction.h

@@ -10,110 +10,111 @@
 #include <AK/Span.h>
 #include <LibJS/Forward.h>
 
-#define ENUMERATE_BYTECODE_OPS(O)    \
-    O(Add)                           \
-    O(Append)                        \
-    O(AsyncIteratorClose)            \
-    O(Await)                         \
-    O(BitwiseAnd)                    \
-    O(BitwiseNot)                    \
-    O(BitwiseOr)                     \
-    O(BitwiseXor)                    \
-    O(BlockDeclarationInstantiation) \
-    O(Call)                          \
-    O(CallWithArgumentArray)         \
-    O(ConcatString)                  \
-    O(ContinuePendingUnwind)         \
-    O(CopyObjectExcludingProperties) \
-    O(CreateLexicalEnvironment)      \
-    O(CreateVariable)                \
-    O(Decrement)                     \
-    O(DeleteById)                    \
-    O(DeleteByIdWithThis)            \
-    O(DeleteByValue)                 \
-    O(DeleteByValueWithThis)         \
-    O(DeleteVariable)                \
-    O(Div)                           \
-    O(EnterUnwindContext)            \
-    O(EnterObjectEnvironment)        \
-    O(Exp)                           \
-    O(GetById)                       \
-    O(GetByIdWithThis)               \
-    O(GetByValue)                    \
-    O(GetByValueWithThis)            \
-    O(GetIterator)                   \
-    O(GetMethod)                     \
-    O(GetNewTarget)                  \
-    O(GetImportMeta)                 \
-    O(GetObjectPropertyIterator)     \
-    O(GetPrivateById)                \
-    O(GetVariable)                   \
-    O(GetGlobal)                     \
-    O(GetLocal)                      \
-    O(GreaterThan)                   \
-    O(GreaterThanEquals)             \
-    O(HasPrivateId)                  \
-    O(ImportCall)                    \
-    O(In)                            \
-    O(Increment)                     \
-    O(InstanceOf)                    \
-    O(IteratorClose)                 \
-    O(IteratorNext)                  \
-    O(IteratorResultDone)            \
-    O(IteratorResultValue)           \
-    O(IteratorToArray)               \
-    O(Jump)                          \
-    O(JumpConditional)               \
-    O(JumpNullish)                   \
-    O(JumpUndefined)                 \
-    O(LeaveLexicalEnvironment)       \
-    O(LeaveUnwindContext)            \
-    O(LeftShift)                     \
-    O(LessThan)                      \
-    O(LessThanEquals)                \
-    O(Load)                          \
-    O(LoadImmediate)                 \
-    O(LooselyEquals)                 \
-    O(LooselyInequals)               \
-    O(Mod)                           \
-    O(Mul)                           \
-    O(NewArray)                      \
-    O(NewBigInt)                     \
-    O(NewClass)                      \
-    O(NewFunction)                   \
-    O(NewObject)                     \
-    O(NewRegExp)                     \
-    O(NewString)                     \
-    O(NewTypeError)                  \
-    O(Not)                           \
-    O(PushDeclarativeEnvironment)    \
-    O(PutById)                       \
-    O(PutByIdWithThis)               \
-    O(PutByValue)                    \
-    O(PutByValueWithThis)            \
-    O(PutPrivateById)                \
-    O(ResolveThisBinding)            \
-    O(ResolveSuperBase)              \
-    O(Return)                        \
-    O(RightShift)                    \
-    O(ScheduleJump)                  \
-    O(SetVariable)                   \
-    O(SetLocal)                      \
-    O(Store)                         \
-    O(StrictlyEquals)                \
-    O(StrictlyInequals)              \
-    O(Sub)                           \
-    O(SuperCallWithArgumentArray)    \
-    O(Throw)                         \
-    O(ThrowIfNotObject)              \
-    O(ThrowIfNullish)                \
-    O(ToNumeric)                     \
-    O(Typeof)                        \
-    O(TypeofVariable)                \
-    O(TypeofLocal)                   \
-    O(UnaryMinus)                    \
-    O(UnaryPlus)                     \
-    O(UnsignedRightShift)            \
+#define ENUMERATE_BYTECODE_OPS(O)      \
+    O(Add)                             \
+    O(Append)                          \
+    O(AsyncIteratorClose)              \
+    O(Await)                           \
+    O(BitwiseAnd)                      \
+    O(BitwiseNot)                      \
+    O(BitwiseOr)                       \
+    O(BitwiseXor)                      \
+    O(BlockDeclarationInstantiation)   \
+    O(Call)                            \
+    O(CallWithArgumentArray)           \
+    O(ConcatString)                    \
+    O(ContinuePendingUnwind)           \
+    O(CopyObjectExcludingProperties)   \
+    O(CreateLexicalEnvironment)        \
+    O(CreateVariable)                  \
+    O(Decrement)                       \
+    O(DeleteById)                      \
+    O(DeleteByIdWithThis)              \
+    O(DeleteByValue)                   \
+    O(DeleteByValueWithThis)           \
+    O(DeleteVariable)                  \
+    O(Div)                             \
+    O(EnterUnwindContext)              \
+    O(EnterObjectEnvironment)          \
+    O(Exp)                             \
+    O(GetById)                         \
+    O(GetByIdWithThis)                 \
+    O(GetByValue)                      \
+    O(GetByValueWithThis)              \
+    O(GetCalleeAndThisFromEnvironment) \
+    O(GetIterator)                     \
+    O(GetMethod)                       \
+    O(GetNewTarget)                    \
+    O(GetImportMeta)                   \
+    O(GetObjectPropertyIterator)       \
+    O(GetPrivateById)                  \
+    O(GetVariable)                     \
+    O(GetGlobal)                       \
+    O(GetLocal)                        \
+    O(GreaterThan)                     \
+    O(GreaterThanEquals)               \
+    O(HasPrivateId)                    \
+    O(ImportCall)                      \
+    O(In)                              \
+    O(Increment)                       \
+    O(InstanceOf)                      \
+    O(IteratorClose)                   \
+    O(IteratorNext)                    \
+    O(IteratorResultDone)              \
+    O(IteratorResultValue)             \
+    O(IteratorToArray)                 \
+    O(Jump)                            \
+    O(JumpConditional)                 \
+    O(JumpNullish)                     \
+    O(JumpUndefined)                   \
+    O(LeaveLexicalEnvironment)         \
+    O(LeaveUnwindContext)              \
+    O(LeftShift)                       \
+    O(LessThan)                        \
+    O(LessThanEquals)                  \
+    O(Load)                            \
+    O(LoadImmediate)                   \
+    O(LooselyEquals)                   \
+    O(LooselyInequals)                 \
+    O(Mod)                             \
+    O(Mul)                             \
+    O(NewArray)                        \
+    O(NewBigInt)                       \
+    O(NewClass)                        \
+    O(NewFunction)                     \
+    O(NewObject)                       \
+    O(NewRegExp)                       \
+    O(NewString)                       \
+    O(NewTypeError)                    \
+    O(Not)                             \
+    O(PushDeclarativeEnvironment)      \
+    O(PutById)                         \
+    O(PutByIdWithThis)                 \
+    O(PutByValue)                      \
+    O(PutByValueWithThis)              \
+    O(PutPrivateById)                  \
+    O(ResolveThisBinding)              \
+    O(ResolveSuperBase)                \
+    O(Return)                          \
+    O(RightShift)                      \
+    O(ScheduleJump)                    \
+    O(SetVariable)                     \
+    O(SetLocal)                        \
+    O(Store)                           \
+    O(StrictlyEquals)                  \
+    O(StrictlyInequals)                \
+    O(Sub)                             \
+    O(SuperCallWithArgumentArray)      \
+    O(Throw)                           \
+    O(ThrowIfNotObject)                \
+    O(ThrowIfNullish)                  \
+    O(ToNumeric)                       \
+    O(Typeof)                          \
+    O(TypeofVariable)                  \
+    O(TypeofLocal)                     \
+    O(UnaryMinus)                      \
+    O(UnaryPlus)                       \
+    O(UnsignedRightShift)              \
     O(Yield)
 
 namespace JS::Bytecode {

+ 45 - 0
Userland/Libraries/LibJS/Bytecode/Op.cpp

@@ -418,6 +418,46 @@ ThrowCompletionOr<void> GetVariable::execute_impl(Bytecode::Interpreter& interpr
     return {};
 }
 
+ThrowCompletionOr<void> GetCalleeAndThisFromEnvironment::execute_impl(Bytecode::Interpreter& interpreter) const
+{
+    auto& vm = interpreter.vm();
+
+    auto get_reference = [&]() -> ThrowCompletionOr<Reference> {
+        auto const& string = interpreter.current_executable().get_identifier(m_identifier);
+        if (m_cached_environment_coordinate.has_value()) {
+            auto environment = vm.running_execution_context().lexical_environment;
+            for (size_t i = 0; i < m_cached_environment_coordinate->hops; ++i)
+                environment = environment->outer_environment();
+            VERIFY(environment);
+            VERIFY(environment->is_declarative_environment());
+            if (!environment->is_permanently_screwed_by_eval()) {
+                return Reference { *environment, string, vm.in_strict_mode(), m_cached_environment_coordinate };
+            }
+            m_cached_environment_coordinate = {};
+        }
+
+        auto reference = TRY(vm.resolve_binding(string));
+        if (reference.environment_coordinate().has_value())
+            m_cached_environment_coordinate = reference.environment_coordinate();
+        return reference;
+    };
+    auto reference = TRY(get_reference());
+    interpreter.reg(m_callee_reg) = TRY(reference.get_value(vm));
+
+    Value this_value = js_undefined();
+    if (reference.is_property_reference()) {
+        this_value = reference.get_this_value();
+    } else {
+        if (reference.is_environment_reference()) {
+            if (auto base_object = reference.base_environment().with_base_object(); base_object != nullptr)
+                this_value = base_object;
+        }
+    }
+    interpreter.reg(m_this_reg) = this_value;
+
+    return {};
+}
+
 ThrowCompletionOr<void> GetGlobal::execute_impl(Bytecode::Interpreter& interpreter) const
 {
     auto& vm = interpreter.vm();
@@ -1498,6 +1538,11 @@ DeprecatedString ConcatString::to_deprecated_string_impl(Bytecode::Executable co
     return DeprecatedString::formatted("ConcatString {}", m_lhs);
 }
 
+DeprecatedString GetCalleeAndThisFromEnvironment::to_deprecated_string_impl(Bytecode::Executable const& executable) const
+{
+    return DeprecatedString::formatted("GetCalleeAndThisFromEnvironment {} -> callee: {}, this:{} ", executable.identifier_table->get(m_identifier), m_callee_reg, m_this_reg);
+}
+
 DeprecatedString GetVariable::to_deprecated_string_impl(Bytecode::Executable const& executable) const
 {
     return DeprecatedString::formatted("GetVariable {} ({})", m_identifier, executable.identifier_table->get(m_identifier));

+ 23 - 0
Userland/Libraries/LibJS/Bytecode/Op.h

@@ -441,6 +441,29 @@ private:
     size_t m_index;
 };
 
+class GetCalleeAndThisFromEnvironment final : public Instruction {
+public:
+    explicit GetCalleeAndThisFromEnvironment(IdentifierTableIndex identifier, Register callee_reg, Register this_reg)
+        : Instruction(Type::GetCalleeAndThisFromEnvironment)
+        , m_identifier(identifier)
+        , m_callee_reg(callee_reg)
+        , m_this_reg(this_reg)
+    {
+    }
+
+    ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
+    DeprecatedString to_deprecated_string_impl(Bytecode::Executable const&) const;
+
+    IdentifierTableIndex identifier() const { return m_identifier; }
+
+private:
+    IdentifierTableIndex m_identifier;
+    Register m_callee_reg;
+    Register m_this_reg;
+
+    Optional<EnvironmentCoordinate> mutable m_cached_environment_coordinate;
+};
+
 class GetVariable final : public Instruction {
 public:
     explicit GetVariable(IdentifierTableIndex identifier)