diff options
author | FalseHonesty <thefalsehonesty@gmail.com> | 2021-04-12 21:59:15 -0400 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2021-04-25 19:03:57 +0200 |
commit | 4f2c0e9968dea9a96b65d2f6de65e48d82c02d47 (patch) | |
tree | cd7effef5a827cf77794519145ef5dfdfa0192e1 | |
parent | 60d329a18624ffc1ce84498ce1378f078cc8baaa (diff) | |
download | serenity-4f2c0e9968dea9a96b65d2f6de65e48d82c02d47.zip |
HackStudio: Implement custom JS -> C++ "proxy" objects
This patch adds a custom JS Object type that will convert written
properties to their C++ equivalents, reflecting JS writes back
to the debugging session. This is better than a simple proxy because
printing this custom object works as expected because properties
still exist on the object as existing handlers expect.
5 files changed, 111 insertions, 36 deletions
diff --git a/Userland/DevTools/HackStudio/CMakeLists.txt b/Userland/DevTools/HackStudio/CMakeLists.txt index 7342dc1de8..3bfdf9d24f 100644 --- a/Userland/DevTools/HackStudio/CMakeLists.txt +++ b/Userland/DevTools/HackStudio/CMakeLists.txt @@ -10,7 +10,8 @@ set(SOURCES Debugger/BacktraceModel.cpp Debugger/DebugInfoWidget.cpp Debugger/Debugger.cpp - Debugger/DebuggerGlobalJSObject.cpp + Debugger/DebuggerGlobalJSObject.cpp + Debugger/DebuggerVariableJSObject.cpp Debugger/DisassemblyModel.cpp Debugger/DisassemblyWidget.cpp Debugger/EvaluateExpressionDialog.cpp diff --git a/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.cpp b/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.cpp index eda05da06f..5b1ba7e1b4 100644 --- a/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.cpp +++ b/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.cpp @@ -6,6 +6,7 @@ #include "DebuggerGlobalJSObject.h" #include "Debugger.h" +#include "DebuggerVariableJSObject.h" #include <LibJS/Runtime/Object.h> #include <LibJS/Runtime/ProxyObject.h> @@ -83,45 +84,16 @@ Optional<JS::Value> DebuggerGlobalJSObject::debugger_to_js(const Debug::DebugInf return JS::Value(value.value() != 0); } - auto& global = const_cast<DebuggerGlobalJSObject&>(*this); - - auto* object = JS::Object::create_empty(global); - auto* handler = JS::Object::create_empty(global); - auto proxy = JS::ProxyObject::create(global, *object, *handler); - - auto set = [&](JS::VM& vm, JS::GlobalObject&) { - auto property = vm.argument(1).value_or(JS::js_undefined()); - if (!property.is_string()) - return JS::Value(false); - auto property_name = property.as_string().string(); - - auto value = vm.argument(2).value_or(JS::js_undefined()); - dbgln("prop name {}", property_name); - - auto it = variable.members.find_if([&](auto& variable) { - dbgln("candidate debugger var name: {}", variable->name); - return variable->name == property_name; - }); - if (it.is_end()) - return JS::Value(false); - auto& member = **it; - dbgln("Found var {}", member.name); - - auto new_value = js_to_debugger(value, member); - Debugger::the().session()->poke((u32*)member.location_data.address, new_value.value()); - - return JS::Value(true); - }; - - handler->define_native_function("set", move(set), 4); - + auto* object = DebuggerVariableJSObject::create(const_cast<DebuggerGlobalJSObject&>(*this), variable); for (auto& member : variable.members) { auto member_value = debugger_to_js(member); if (!member_value.has_value()) continue; - object->put(member.name, member_value.value()); + object->put(member.name, member_value.value(), {}); } - return proxy; + object->finish_writing_properties(); + + return JS::Value(object); } Optional<u32> DebuggerGlobalJSObject::js_to_debugger(JS::Value value, const Debug::DebugInfo::VariableInfo& variable) const diff --git a/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.h b/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.h index f8412986e6..5c551d5091 100644 --- a/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.h +++ b/Userland/DevTools/HackStudio/Debugger/DebuggerGlobalJSObject.h @@ -23,7 +23,6 @@ public: JS::Value get(const JS::PropertyName& name, JS::Value receiver, bool without_side_effects) const override; bool put(const JS::PropertyName& name, JS::Value value, JS::Value receiver) override; -private: Optional<JS::Value> debugger_to_js(const Debug::DebugInfo::VariableInfo&) const; Optional<u32> js_to_debugger(JS::Value value, const Debug::DebugInfo::VariableInfo&) const; diff --git a/Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.cpp b/Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.cpp new file mode 100644 index 0000000000..ecf95de914 --- /dev/null +++ b/Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2021, Matthew Olsson <matthewcolsson@gmail.com> + * Copyright (c) 2021, Hunter Salyer <thefalsehonesty@gmail.com> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "DebuggerVariableJSObject.h" +#include "Debugger.h" +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/GlobalObject.h> +#include <LibJS/Runtime/PrimitiveString.h> +#include <LibJS/Runtime/PropertyName.h> + +namespace HackStudio { + +DebuggerVariableJSObject* DebuggerVariableJSObject::create(DebuggerGlobalJSObject& global_object, const Debug::DebugInfo::VariableInfo& variable_info) +{ + return global_object.heap().allocate<DebuggerVariableJSObject>(global_object, variable_info, *global_object.object_prototype()); +} + +DebuggerVariableJSObject::DebuggerVariableJSObject(const Debug::DebugInfo::VariableInfo& variable_info, JS::Object& prototype) + : JS::Object(prototype) + , m_variable_info(variable_info) +{ +} + +DebuggerVariableJSObject::~DebuggerVariableJSObject() +{ +} + +bool DebuggerVariableJSObject::put(const JS::PropertyName& name, JS::Value value, JS::Value receiver) +{ + if (m_is_writing_properties) + return JS::Object::put(name, value, receiver); + + if (!name.is_string()) { + vm().throw_exception<JS::TypeError>(global_object(), String::formatted("Invalid variable name {}", name.to_string())); + return false; + } + + auto property_name = name.as_string(); + auto it = m_variable_info.members.find_if([&](auto& variable) { + return variable->name == property_name; + }); + + if (it.is_end()) { + vm().throw_exception<JS::TypeError>(global_object(), String::formatted("Variable of type {} has no property {}", m_variable_info.type_name, property_name)); + return false; + } + + auto& member = **it; + auto new_value = debugger_object().js_to_debugger(value, member); + if (!new_value.has_value()) { + auto string_error = String::formatted("Cannot convert JS value {} to variable {} of type {}", value.to_string_without_side_effects(), name.as_string(), member.type_name); + vm().throw_exception<JS::TypeError>(global_object(), string_error); + return false; + } + Debugger::the().session()->poke((u32*)member.location_data.address, new_value.value()); + return true; +} + +DebuggerGlobalJSObject& DebuggerVariableJSObject::debugger_object() const +{ + return static_cast<DebuggerGlobalJSObject&>(global_object()); +} + +} diff --git a/Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.h b/Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.h new file mode 100644 index 0000000000..bdf06360e4 --- /dev/null +++ b/Userland/DevTools/HackStudio/Debugger/DebuggerVariableJSObject.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021, Matthew Olsson <matthewcolsson@gmail.com> + * Copyright (c) 2021, Hunter Salyer <thefalsehonesty@gmail.com> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "DebuggerGlobalJSObject.h" +#include <LibDebug/DebugInfo.h> +#include <LibJS/Runtime/Object.h> + +namespace HackStudio { + +class DebuggerVariableJSObject final : public JS::Object { + JS_OBJECT(DebuggerVariableJSObject, JS::Object); + +public: + static DebuggerVariableJSObject* create(DebuggerGlobalJSObject&, const Debug::DebugInfo::VariableInfo& variable_info); + + DebuggerVariableJSObject(const Debug::DebugInfo::VariableInfo& variable_info, JS::Object& prototype); + virtual ~DebuggerVariableJSObject() override; + + virtual bool put(const JS::PropertyName& name, JS::Value value, JS::Value) override; + void finish_writing_properties() { m_is_writing_properties = false; } + +private: + DebuggerGlobalJSObject& debugger_object() const; + + const Debug::DebugInfo::VariableInfo& m_variable_info; + bool m_is_writing_properties { true }; +}; + +} |