mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 06:02:44 +00:00 
			
		
		
		
	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.
This commit is contained in:
		
							parent
							
								
									60d329a186
								
							
						
					
					
						commit
						4f2c0e9968
					
				
					 5 changed files with 111 additions and 36 deletions
				
			
		|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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()); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  | @ -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 }; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 FalseHonesty
						FalseHonesty