mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 13:22:43 +00:00 
			
		
		
		
	LibJS: Add Proxy objects
Includes all traps except the following: [[Call]], [[Construct]], [[OwnPropertyKeys]]. An important implication of this commit is that any call to any virtual Object method has the potential to throw an exception. These methods were not checked in this commit -- a future commit will have to protect these various method calls throughout the codebase.
This commit is contained in:
		
							parent
							
								
									58a72e9b81
								
							
						
					
					
						commit
						39ad42defd
					
				
					 29 changed files with 1697 additions and 54 deletions
				
			
		|  | @ -359,7 +359,7 @@ Value ForInStatement::execute(Interpreter& interpreter) const | |||
|         return {}; | ||||
|     auto* object = rhs_result.to_object(interpreter); | ||||
|     while (object) { | ||||
|         auto property_names = object->get_own_properties(*object, Object::GetOwnPropertyMode::Key, Attribute::Enumerable); | ||||
|         auto property_names = object->get_own_properties(*object, Object::GetOwnPropertyMode::Key, true); | ||||
|         for (auto& property_name : property_names.as_object().indexed_properties()) { | ||||
|             interpreter.set_variable(variable_name, property_name.value_and_attributes(object).value); | ||||
|             if (interpreter.exception()) | ||||
|  |  | |||
|  | @ -41,6 +41,10 @@ set(SOURCES | |||
|     Runtime/Object.cpp | ||||
|     Runtime/ObjectPrototype.cpp | ||||
|     Runtime/PrimitiveString.cpp | ||||
|     Runtime/PropertyAttributes.cpp | ||||
|     Runtime/ProxyConstructor.cpp | ||||
|     Runtime/ProxyObject.cpp | ||||
|     Runtime/ProxyPrototype.cpp | ||||
|     Runtime/Reference.cpp | ||||
|     Runtime/ReflectObject.cpp | ||||
|     Runtime/ScriptFunction.cpp | ||||
|  |  | |||
|  | @ -34,6 +34,7 @@ | |||
|     __JS_ENUMERATE(Function, function, FunctionPrototype, FunctionConstructor)   \ | ||||
|     __JS_ENUMERATE(NumberObject, number, NumberPrototype, NumberConstructor)     \ | ||||
|     __JS_ENUMERATE(Object, object, ObjectPrototype, ObjectConstructor)           \ | ||||
|     __JS_ENUMERATE(ProxyObject, proxy, ProxyPrototype, ProxyConstructor)         \ | ||||
|     __JS_ENUMERATE(StringObject, string, StringPrototype, StringConstructor)     \ | ||||
|     __JS_ENUMERATE(SymbolObject, symbol, SymbolPrototype, SymbolConstructor) | ||||
| 
 | ||||
|  |  | |||
|  | @ -50,6 +50,8 @@ | |||
| #include <LibJS/Runtime/Object.h> | ||||
| #include <LibJS/Runtime/ObjectConstructor.h> | ||||
| #include <LibJS/Runtime/ObjectPrototype.h> | ||||
| #include <LibJS/Runtime/ProxyConstructor.h> | ||||
| #include <LibJS/Runtime/ProxyPrototype.h> | ||||
| #include <LibJS/Runtime/ReflectObject.h> | ||||
| #include <LibJS/Runtime/Shape.h> | ||||
| #include <LibJS/Runtime/StringConstructor.h> | ||||
|  | @ -103,6 +105,7 @@ void GlobalObject::initialize() | |||
|     add_constructor("Function", m_function_constructor, *m_function_prototype); | ||||
|     add_constructor("Number", m_number_constructor, *m_number_prototype); | ||||
|     add_constructor("Object", m_object_constructor, *m_object_prototype); | ||||
|     add_constructor("Proxy", m_proxy_constructor, *m_proxy_prototype); | ||||
|     add_constructor("String", m_string_constructor, *m_string_prototype); | ||||
|     add_constructor("Symbol", m_symbol_constructor, *m_symbol_prototype); | ||||
| 
 | ||||
|  |  | |||
|  | @ -40,7 +40,7 @@ | |||
| 
 | ||||
| namespace JS { | ||||
| 
 | ||||
| PropertyDescriptor PropertyDescriptor::from_object(Interpreter& interpreter, const Object& object) | ||||
| PropertyDescriptor PropertyDescriptor::from_dictionary(Interpreter& interpreter, const Object& object) | ||||
| { | ||||
|     PropertyAttributes attributes; | ||||
|     if (object.has_property("configurable")) { | ||||
|  | @ -146,12 +146,12 @@ Value Object::get_own_property(const Object& this_object, PropertyName property_ | |||
|         auto existing_property = m_indexed_properties.get(nullptr, property_name.as_number(), false); | ||||
|         if (!existing_property.has_value()) | ||||
|             return {}; | ||||
|         value_here = existing_property.value().value; | ||||
|         value_here = existing_property.value().value.value_or(js_undefined()); | ||||
|     } else { | ||||
|         auto metadata = shape().lookup(property_name.as_string()); | ||||
|         if (!metadata.has_value()) | ||||
|             return {}; | ||||
|         value_here = m_storage[metadata.value().offset]; | ||||
|         value_here = m_storage[metadata.value().offset].value_or(js_undefined()); | ||||
|     } | ||||
| 
 | ||||
|     ASSERT(!value_here.is_empty()); | ||||
|  | @ -163,7 +163,7 @@ Value Object::get_own_property(const Object& this_object, PropertyName property_ | |||
|     return value_here; | ||||
| } | ||||
| 
 | ||||
| Value Object::get_own_properties(const Object& this_object, GetOwnPropertyMode kind, PropertyAttributes attributes) const | ||||
| Value Object::get_own_properties(const Object& this_object, GetOwnPropertyMode kind, bool only_enumerable_properties) const | ||||
| { | ||||
|     auto* properties_array = Array::create(interpreter().global_object()); | ||||
| 
 | ||||
|  | @ -189,16 +189,20 @@ Value Object::get_own_properties(const Object& this_object, GetOwnPropertyMode k | |||
| 
 | ||||
|     size_t property_index = 0; | ||||
|     for (auto& entry : m_indexed_properties) { | ||||
|         auto value_and_attributes = entry.value_and_attributes(const_cast<Object*>(&this_object)); | ||||
|         if (only_enumerable_properties && !value_and_attributes.attributes.is_enumerable()) | ||||
|             continue; | ||||
| 
 | ||||
|         if (kind == GetOwnPropertyMode::Key) { | ||||
|             properties_array->define_property(property_index, js_string(interpreter(), String::number(entry.index()))); | ||||
|         } else if (kind == GetOwnPropertyMode::Value) { | ||||
|             properties_array->define_property(property_index, entry.value_and_attributes(const_cast<Object*>(&this_object)).value); | ||||
|             properties_array->define_property(property_index, value_and_attributes.value); | ||||
|             if (interpreter().exception()) | ||||
|                 return {}; | ||||
|         } else { | ||||
|             auto* entry_array = Array::create(interpreter().global_object()); | ||||
|             entry_array->define_property(0, js_string(interpreter(), String::number(entry.index()))); | ||||
|             entry_array->define_property(1, entry.value_and_attributes(const_cast<Object*>(&this_object)).value); | ||||
|             entry_array->define_property(1, value_and_attributes.value); | ||||
|             if (interpreter().exception()) | ||||
|                 return {}; | ||||
|             properties_array->define_property(property_index, entry_array); | ||||
|  | @ -208,7 +212,9 @@ Value Object::get_own_properties(const Object& this_object, GetOwnPropertyMode k | |||
|     } | ||||
| 
 | ||||
|     for (auto& it : this_object.shape().property_table_ordered()) { | ||||
|         if (it.value.attributes.bits() & attributes.bits()) { | ||||
|         if (only_enumerable_properties && !it.value.attributes.is_enumerable()) | ||||
|             continue; | ||||
| 
 | ||||
|         size_t offset = it.value.offset + property_index; | ||||
| 
 | ||||
|         if (kind == GetOwnPropertyMode::Key) { | ||||
|  | @ -226,7 +232,6 @@ Value Object::get_own_properties(const Object& this_object, GetOwnPropertyMode k | |||
|             properties_array->define_property(offset, entry_array); | ||||
|         } | ||||
|     } | ||||
|     } | ||||
| 
 | ||||
|     return properties_array; | ||||
| } | ||||
|  |  | |||
|  | @ -42,10 +42,10 @@ namespace JS { | |||
| struct PropertyDescriptor { | ||||
|     PropertyAttributes attributes; | ||||
|     Value value; | ||||
|     Function* getter; | ||||
|     Function* setter; | ||||
|     Function* getter { nullptr }; | ||||
|     Function* setter { nullptr }; | ||||
| 
 | ||||
|     static PropertyDescriptor from_object(Interpreter&, const Object&); | ||||
|     static PropertyDescriptor from_dictionary(Interpreter&, const Object&); | ||||
| 
 | ||||
|     bool is_accessor_descriptor() const { return getter || setter; } | ||||
|     bool is_data_descriptor() const { return !(value.is_empty() && !attributes.has_writable()); } | ||||
|  | @ -73,25 +73,25 @@ public: | |||
|     Shape& shape() { return *m_shape; } | ||||
|     const Shape& shape() const { return *m_shape; } | ||||
| 
 | ||||
|     Value get(PropertyName) const; | ||||
|     virtual Value get(PropertyName) const; | ||||
| 
 | ||||
|     bool has_property(PropertyName) const; | ||||
|     virtual bool has_property(PropertyName) const; | ||||
|     bool has_own_property(PropertyName) const; | ||||
| 
 | ||||
|     bool put(PropertyName, Value); | ||||
|     virtual bool put(PropertyName, Value); | ||||
| 
 | ||||
|     Value get_own_property(const Object& this_object, PropertyName) const; | ||||
|     Value get_own_properties(const Object& this_object, GetOwnPropertyMode, PropertyAttributes attributes = default_attributes) const; | ||||
|     Optional<PropertyDescriptor> get_own_property_descriptor(PropertyName) const; | ||||
|     Value get_own_properties(const Object& this_object, GetOwnPropertyMode, bool only_enumerable_properties = false) const; | ||||
|     virtual Optional<PropertyDescriptor> get_own_property_descriptor(PropertyName) const; | ||||
|     Value get_own_property_descriptor_object(PropertyName) const; | ||||
| 
 | ||||
|     bool define_property(const FlyString& property_name, const Object& descriptor, bool throw_exceptions = true); | ||||
|     virtual bool define_property(const FlyString& property_name, const Object& descriptor, bool throw_exceptions = true); | ||||
|     bool define_property(PropertyName, Value value, PropertyAttributes attributes = default_attributes, bool throw_exceptions = true); | ||||
| 
 | ||||
|     bool define_native_function(const FlyString& property_name, AK::Function<Value(Interpreter&)>, i32 length = 0, PropertyAttributes attributes = default_attributes); | ||||
|     bool define_native_property(const FlyString& property_name, AK::Function<Value(Interpreter&)> getter, AK::Function<void(Interpreter&, Value)> setter, PropertyAttributes attributes = default_attributes); | ||||
| 
 | ||||
|     Value delete_property(PropertyName); | ||||
|     virtual Value delete_property(PropertyName); | ||||
| 
 | ||||
|     virtual bool is_array() const { return false; } | ||||
|     virtual bool is_boolean() const { return false; } | ||||
|  | @ -101,19 +101,20 @@ public: | |||
|     virtual bool is_native_function() const { return false; } | ||||
|     virtual bool is_bound_function() const { return false; } | ||||
|     virtual bool is_native_property() const { return false; } | ||||
|     virtual bool is_proxy_object() const { return false; } | ||||
|     virtual bool is_string_object() const { return false; } | ||||
|     virtual bool is_symbol_object() const { return false; } | ||||
| 
 | ||||
|     virtual const char* class_name() const override { return "Object"; } | ||||
|     virtual void visit_children(Cell::Visitor&) override; | ||||
| 
 | ||||
|     Object* prototype(); | ||||
|     const Object* prototype() const; | ||||
|     bool set_prototype(Object* prototype); | ||||
|     virtual Object* prototype(); | ||||
|     virtual const Object* prototype() const; | ||||
|     virtual bool set_prototype(Object* prototype); | ||||
|     bool has_prototype(const Object* prototype) const; | ||||
| 
 | ||||
|     bool is_extensible() const { return m_is_extensible; } | ||||
|     bool prevent_extensions(); | ||||
|     virtual bool is_extensible() const { return m_is_extensible; } | ||||
|     virtual bool prevent_extensions(); | ||||
| 
 | ||||
|     virtual Value value_of() const { return Value(const_cast<Object*>(this)); } | ||||
|     virtual Value to_primitive(Value::PreferredType preferred_type = Value::PreferredType::Default) const; | ||||
|  |  | |||
|  | @ -98,7 +98,7 @@ Value ObjectConstructor::get_prototype_of(Interpreter& interpreter) | |||
| Value ObjectConstructor::set_prototype_of(Interpreter& interpreter) | ||||
| { | ||||
|     if (interpreter.argument_count() < 2) | ||||
|         interpreter.throw_exception<TypeError>("Object.setPrototypeOf requires at least two arguments"); | ||||
|         return interpreter.throw_exception<TypeError>("Object.setPrototypeOf requires at least two arguments"); | ||||
|     auto* object = interpreter.argument(0).to_object(interpreter); | ||||
|     if (interpreter.exception()) | ||||
|         return {}; | ||||
|  | @ -113,7 +113,8 @@ Value ObjectConstructor::set_prototype_of(Interpreter& interpreter) | |||
|         return {}; | ||||
|     } | ||||
|     if (!object->set_prototype(prototype)) { | ||||
|         interpreter.throw_exception<TypeError>("Can't set prototype of non-extensible object"); | ||||
|         if (!interpreter.exception()) | ||||
|             interpreter.throw_exception<TypeError>("Object's setPrototypeOf method returned false"); | ||||
|         return {}; | ||||
|     } | ||||
|     return object; | ||||
|  | @ -133,6 +134,7 @@ Value ObjectConstructor::prevent_extensions(Interpreter& interpreter) | |||
|     if (!argument.is_object()) | ||||
|         return argument; | ||||
|     if (!argument.as_object().prevent_extensions()) { | ||||
|         if (!interpreter.exception()) | ||||
|             interpreter.throw_exception<TypeError>("Proxy preventExtensions handler returned false"); | ||||
|         return {}; | ||||
|     } | ||||
|  | @ -161,7 +163,16 @@ Value ObjectConstructor::define_property_(Interpreter& interpreter) | |||
|     if (interpreter.exception()) | ||||
|         return {}; | ||||
|     auto& descriptor = interpreter.argument(2).as_object(); | ||||
|     object.define_property(property_key, descriptor); | ||||
|     if (!object.define_property(property_key, descriptor)) { | ||||
|         if (!interpreter.exception()) { | ||||
|             if (object.is_proxy_object()) { | ||||
|                 interpreter.throw_exception<TypeError>("Proxy handler's defineProperty method returned false"); | ||||
|             } else { | ||||
|                 interpreter.throw_exception<TypeError>("Unable to define property on non-extensible object"); | ||||
|             } | ||||
|         } | ||||
|         return {}; | ||||
|     } | ||||
|     return &object; | ||||
| } | ||||
| 
 | ||||
|  | @ -179,7 +190,7 @@ Value ObjectConstructor::keys(Interpreter& interpreter) | |||
|     if (interpreter.exception()) | ||||
|         return {}; | ||||
| 
 | ||||
|     return obj_arg->get_own_properties(*obj_arg, GetOwnPropertyMode::Key, Attribute::Enumerable); | ||||
|     return obj_arg->get_own_properties(*obj_arg, GetOwnPropertyMode::Key, true); | ||||
| } | ||||
| 
 | ||||
| Value ObjectConstructor::values(Interpreter& interpreter) | ||||
|  | @ -191,7 +202,7 @@ Value ObjectConstructor::values(Interpreter& interpreter) | |||
|     if (interpreter.exception()) | ||||
|         return {}; | ||||
| 
 | ||||
|     return obj_arg->get_own_properties(*obj_arg, GetOwnPropertyMode::Value, Attribute::Enumerable); | ||||
|     return obj_arg->get_own_properties(*obj_arg, GetOwnPropertyMode::Value, true); | ||||
| } | ||||
| 
 | ||||
| Value ObjectConstructor::entries(Interpreter& interpreter) | ||||
|  | @ -203,7 +214,7 @@ Value ObjectConstructor::entries(Interpreter& interpreter) | |||
|     if (interpreter.exception()) | ||||
|         return {}; | ||||
| 
 | ||||
|     return obj_arg->get_own_properties(*obj_arg, GetOwnPropertyMode::KeyAndValue, Attribute::Enumerable); | ||||
|     return obj_arg->get_own_properties(*obj_arg, GetOwnPropertyMode::KeyAndValue, true); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
							
								
								
									
										36
									
								
								Libraries/LibJS/Runtime/PropertyAttributes.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								Libraries/LibJS/Runtime/PropertyAttributes.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| /*
 | ||||
|  * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> | ||||
|  * All rights reserved. | ||||
|  * | ||||
|  * Redistribution and use in source and binary forms, with or without | ||||
|  * modification, are permitted provided that the following conditions are met: | ||||
|  * | ||||
|  * 1. Redistributions of source code must retain the above copyright notice, this | ||||
|  *    list of conditions and the following disclaimer. | ||||
|  * | ||||
|  * 2. Redistributions in binary form must reproduce the above copyright notice, | ||||
|  *    this list of conditions and the following disclaimer in the documentation | ||||
|  *    and/or other materials provided with the distribution. | ||||
|  * | ||||
|  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
|  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
|  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
|  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
|  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
|  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
|  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
|  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
|  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
|  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|  */ | ||||
| 
 | ||||
| #include <LibJS/Runtime/PropertyAttributes.h> | ||||
| 
 | ||||
| namespace JS { | ||||
| 
 | ||||
| const LogStream& operator<<(const LogStream& stream, const PropertyAttributes& attributes) | ||||
| { | ||||
|     return stream << attributes.bits(); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  | @ -26,6 +26,9 @@ | |||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <AK/Types.h> | ||||
| #include <AK/LogStream.h> | ||||
| 
 | ||||
| namespace JS { | ||||
| 
 | ||||
| struct Attribute { | ||||
|  |  | |||
							
								
								
									
										68
									
								
								Libraries/LibJS/Runtime/ProxyConstructor.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								Libraries/LibJS/Runtime/ProxyConstructor.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,68 @@ | |||
| /*
 | ||||
|  * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> | ||||
|  * All rights reserved. | ||||
|  * | ||||
|  * Redistribution and use in source and binary forms, with or without | ||||
|  * modification, are permitted provided that the following conditions are met: | ||||
|  * | ||||
|  * 1. Redistributions of source code must retain the above copyright notice, this | ||||
|  *    list of conditions and the following disclaimer. | ||||
|  * | ||||
|  * 2. Redistributions in binary form must reproduce the above copyright notice, | ||||
|  *    this list of conditions and the following disclaimer in the documentation | ||||
|  *    and/or other materials provided with the distribution. | ||||
|  * | ||||
|  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
|  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
|  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
|  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
|  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
|  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
|  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
|  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
|  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
|  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|  */ | ||||
| 
 | ||||
| #include <LibJS/Interpreter.h> | ||||
| #include <LibJS/Runtime/Array.h> | ||||
| #include <LibJS/Runtime/Error.h> | ||||
| #include <LibJS/Runtime/GlobalObject.h> | ||||
| #include <LibJS/Runtime/ProxyConstructor.h> | ||||
| #include <LibJS/Runtime/ProxyObject.h> | ||||
| 
 | ||||
| namespace JS { | ||||
| 
 | ||||
| ProxyConstructor::ProxyConstructor() | ||||
|     : NativeFunction("Proxy", *interpreter().global_object().function_prototype()) | ||||
| { | ||||
|     define_property("prototype", interpreter().global_object().proxy_prototype(), 0); | ||||
|     define_property("length", Value(2), Attribute::Configurable); | ||||
| } | ||||
| 
 | ||||
| ProxyConstructor::~ProxyConstructor() | ||||
| { | ||||
| } | ||||
| 
 | ||||
| Value ProxyConstructor::call(Interpreter& interpreter) | ||||
| { | ||||
|     return interpreter.throw_exception<TypeError>("Proxy must be called with the \"new\" operator"); | ||||
| } | ||||
| 
 | ||||
| Value ProxyConstructor::construct(Interpreter& interpreter) | ||||
| { | ||||
|     if (interpreter.argument_count() < 2) | ||||
|         return interpreter.throw_exception<TypeError>("Proxy requires at least two arguments"); | ||||
| 
 | ||||
|     auto target = interpreter.argument(0); | ||||
|     auto handler = interpreter.argument(1); | ||||
| 
 | ||||
|     if (!target.is_object()) | ||||
|         return interpreter.throw_exception<TypeError>(String::format("Expected target argument of Proxy constructor to be object, got %s", target.to_string_without_side_effects().characters())); | ||||
|     if (!handler.is_object()) | ||||
|         return interpreter.throw_exception<TypeError>(String::format("Expected handler argument of Proxy constructor to be object, got %s", handler.to_string_without_side_effects().characters())); | ||||
| 
 | ||||
|     return ProxyObject::create(interpreter.global_object(), target.as_object(), handler.as_object()); | ||||
| } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										46
									
								
								Libraries/LibJS/Runtime/ProxyConstructor.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								Libraries/LibJS/Runtime/ProxyConstructor.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | |||
| /*
 | ||||
|  * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> | ||||
|  * All rights reserved. | ||||
|  * | ||||
|  * Redistribution and use in source and binary forms, with or without | ||||
|  * modification, are permitted provided that the following conditions are met: | ||||
|  * | ||||
|  * 1. Redistributions of source code must retain the above copyright notice, this | ||||
|  *    list of conditions and the following disclaimer. | ||||
|  * | ||||
|  * 2. Redistributions in binary form must reproduce the above copyright notice, | ||||
|  *    this list of conditions and the following disclaimer in the documentation | ||||
|  *    and/or other materials provided with the distribution. | ||||
|  * | ||||
|  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
|  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
|  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
|  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
|  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
|  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
|  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
|  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
|  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
|  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <LibJS/Runtime/NativeFunction.h> | ||||
| 
 | ||||
| namespace JS { | ||||
| 
 | ||||
| class ProxyConstructor final : public NativeFunction { | ||||
| public: | ||||
|     ProxyConstructor(); | ||||
|     virtual ~ProxyConstructor() override; | ||||
| 
 | ||||
|     virtual Value call(Interpreter&) override; | ||||
|     virtual Value construct(Interpreter&) override; | ||||
| 
 | ||||
| private: | ||||
|     virtual bool has_constructor() const override { return true; } | ||||
|     virtual const char* class_name() const override { return "ProxyConstructor"; } | ||||
| }; | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										465
									
								
								Libraries/LibJS/Runtime/ProxyObject.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										465
									
								
								Libraries/LibJS/Runtime/ProxyObject.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,465 @@ | |||
| /*
 | ||||
|  * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> | ||||
|  * All rights reserved. | ||||
|  * | ||||
|  * Redistribution and use in source and binary forms, with or without | ||||
|  * modification, are permitted provided that the following conditions are met: | ||||
|  * | ||||
|  * 1. Redistributions of source code must retain the above copyright notice, this | ||||
|  *    list of conditions and the following disclaimer. | ||||
|  * | ||||
|  * 2. Redistributions in binary form must reproduce the above copyright notice, | ||||
|  *    this list of conditions and the following disclaimer in the documentation | ||||
|  *    and/or other materials provided with the distribution. | ||||
|  * | ||||
|  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
|  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
|  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
|  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
|  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
|  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
|  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
|  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
|  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
|  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|  */ | ||||
| 
 | ||||
| #include <LibJS/Interpreter.h> | ||||
| #include <LibJS/Runtime/Accessor.h> | ||||
| #include <LibJS/Runtime/Error.h> | ||||
| #include <LibJS/Runtime/GlobalObject.h> | ||||
| #include <LibJS/Runtime/ProxyObject.h> | ||||
| 
 | ||||
| namespace JS { | ||||
| 
 | ||||
| bool static is_compatible_property_descriptor(Interpreter& interpreter, bool is_extensible, PropertyDescriptor new_descriptor, Optional<PropertyDescriptor> current_descriptor_optional) | ||||
| { | ||||
|     if (!current_descriptor_optional.has_value()) | ||||
|         return is_extensible; | ||||
|     auto current_descriptor = current_descriptor_optional.value(); | ||||
|     if (new_descriptor.attributes.is_empty() && new_descriptor.value.is_empty() && !new_descriptor.getter && !new_descriptor.setter) | ||||
|         return true; | ||||
|     if (!current_descriptor.attributes.is_configurable()) { | ||||
|         if (new_descriptor.attributes.is_configurable()) | ||||
|             return false; | ||||
|         if (new_descriptor.attributes.has_enumerable() && new_descriptor.attributes.is_enumerable() != current_descriptor.attributes.is_enumerable()) | ||||
|             return false; | ||||
|     } | ||||
|     if (new_descriptor.is_generic_descriptor()) | ||||
|         return true; | ||||
|     if (current_descriptor.is_data_descriptor() != new_descriptor.is_data_descriptor() && !current_descriptor.attributes.is_configurable()) | ||||
|         return false; | ||||
|     if (current_descriptor.is_data_descriptor() && new_descriptor.is_data_descriptor() && !current_descriptor.attributes.is_configurable() && !current_descriptor.attributes.is_writable()) { | ||||
|         if (new_descriptor.attributes.is_writable()) | ||||
|             return false; | ||||
|         return new_descriptor.value.is_empty() && same_value(interpreter, new_descriptor.value, current_descriptor.value); | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| ProxyObject* ProxyObject::create(GlobalObject& global_object, Object& target, Object& handler) | ||||
| { | ||||
|     return global_object.heap().allocate<ProxyObject>(target, handler, *global_object.proxy_prototype()); | ||||
| } | ||||
| 
 | ||||
| ProxyObject::ProxyObject(Object& target, Object& handler, Object& prototype) | ||||
|     : Object(&prototype) | ||||
|     , m_target(target) | ||||
|     , m_handler(handler) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| ProxyObject::~ProxyObject() | ||||
| { | ||||
| } | ||||
| 
 | ||||
| Object* ProxyObject::prototype() | ||||
| { | ||||
|     if (m_is_revoked) { | ||||
|         interpreter().throw_exception<TypeError>("An operation was performed on a revoked Proxy object"); | ||||
|         return nullptr; | ||||
|     } | ||||
|     auto trap = m_handler.get("getPrototypeOf"); | ||||
|     if (interpreter().exception()) | ||||
|         return nullptr; | ||||
|     if (trap.is_empty() || trap.is_undefined() || trap.is_null()) | ||||
|         return m_target.prototype(); | ||||
|     if (!trap.is_function()) { | ||||
|         interpreter().throw_exception<TypeError>("Proxy handler's getPrototypeOf trap wasn't undefined, null, or callable"); | ||||
|         return nullptr; | ||||
|     } | ||||
|     MarkedValueList arguments(interpreter().heap()); | ||||
|     arguments.values().append(Value(&m_target)); | ||||
|     auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)); | ||||
|     if (interpreter().exception()) | ||||
|         return nullptr; | ||||
|     if (!trap_result.is_object() && !trap_result.is_null()) { | ||||
|         interpreter().throw_exception<TypeError>("Proxy handler's getPrototypeOf trap violates invariant: must return an object or null"); | ||||
|         return nullptr; | ||||
|     } | ||||
|     if (m_target.is_extensible()) { | ||||
|         if (trap_result.is_null()) | ||||
|             return nullptr; | ||||
|         return &trap_result.as_object(); | ||||
|     } | ||||
|     auto target_proto = m_target.prototype(); | ||||
|     if (interpreter().exception()) | ||||
|         return nullptr; | ||||
|     if (!same_value(interpreter(), trap_result, Value(target_proto))) { | ||||
|         interpreter().throw_exception<TypeError>("Proxy handler's getPrototypeOf trap violates invariant: cannot return a different prototype object for a non-extensible target"); | ||||
|         return nullptr; | ||||
|     } | ||||
|     return &trap_result.as_object(); | ||||
| } | ||||
| 
 | ||||
| const Object* ProxyObject::prototype() const | ||||
| { | ||||
|     if (m_is_revoked) { | ||||
|         interpreter().throw_exception<TypeError>("An operation was performed on a revoked Proxy object"); | ||||
|         return nullptr; | ||||
|     } | ||||
|     return const_cast<const Object*>(const_cast<ProxyObject*>(this)->prototype()); | ||||
| } | ||||
| 
 | ||||
| bool ProxyObject::set_prototype(Object* object) | ||||
| { | ||||
|     if (m_is_revoked) { | ||||
|         interpreter().throw_exception<TypeError>("An operation was performed on a revoked Proxy object"); | ||||
|         return false; | ||||
|     } | ||||
|     auto trap = m_handler.get("setPrototypeOf"); | ||||
|     if (interpreter().exception()) | ||||
|         return false; | ||||
|     if (trap.is_empty() || trap.is_undefined() || trap.is_null()) | ||||
|         return m_target.set_prototype(object); | ||||
|     if (!trap.is_function()) { | ||||
|         interpreter().throw_exception<TypeError>("Proxy handler's setPrototypeOf trap wasn't undefined, null, or callable"); | ||||
|         return false; | ||||
|     } | ||||
|     MarkedValueList arguments(interpreter().heap()); | ||||
|     arguments.values().append(Value(&m_target)); | ||||
|     arguments.values().append(Value(object)); | ||||
|     auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)).to_boolean(); | ||||
|     if (interpreter().exception() || !trap_result) | ||||
|         return false; | ||||
|     if (m_target.is_extensible()) | ||||
|         return true; | ||||
|     auto* target_proto = m_target.prototype(); | ||||
|     if (interpreter().exception()) | ||||
|         return false; | ||||
|     if (!same_value(interpreter(), Value(object), Value(target_proto))) { | ||||
|         interpreter().throw_exception<TypeError>("Proxy handler's setPrototypeOf trap violates invariant: the argument must match the prototype of the target if the target is non-extensible"); | ||||
|         return false; | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool ProxyObject::is_extensible() const | ||||
| { | ||||
|     if (m_is_revoked) { | ||||
|         interpreter().throw_exception<TypeError>("An operation was performed on a revoked Proxy object"); | ||||
|         return false; | ||||
|     } | ||||
|     auto trap = m_handler.get("isExtensible"); | ||||
|     if (interpreter().exception()) | ||||
|         return false; | ||||
|     if (trap.is_empty() || trap.is_undefined() || trap.is_null()) | ||||
|         return m_target.is_extensible(); | ||||
|     if (!trap.is_function()) { | ||||
|         interpreter().throw_exception<TypeError>("Proxy handler's isExtensible trap wasn't undefined, null, or callable"); | ||||
|         return {}; | ||||
|     } | ||||
|     MarkedValueList arguments(interpreter().heap()); | ||||
|     arguments.values().append(Value(&m_target)); | ||||
|     auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)).to_boolean(); | ||||
|     if (interpreter().exception()) | ||||
|         return false; | ||||
|     if (trap_result != m_target.is_extensible()) { | ||||
|         interpreter().throw_exception<TypeError>("Proxy handler's isExtensible trap violates invariant: return value must match the target's extensibility"); | ||||
|         return false; | ||||
|     } | ||||
|     return trap_result; | ||||
| } | ||||
| 
 | ||||
| bool ProxyObject::prevent_extensions() | ||||
| { | ||||
|     if (m_is_revoked) { | ||||
|         interpreter().throw_exception<TypeError>("An operation was performed on a revoked Proxy object"); | ||||
|         return false; | ||||
|     } | ||||
|     auto trap = m_handler.get("preventExtensions"); | ||||
|     if (interpreter().exception()) | ||||
|         return false; | ||||
|     if (trap.is_empty() || trap.is_undefined() || trap.is_null()) | ||||
|         return m_target.prevent_extensions(); | ||||
|     if (!trap.is_function()) { | ||||
|         interpreter().throw_exception<TypeError>("Proxy handler's preventExtensions trap wasn't undefined, null, or callable"); | ||||
|         return {}; | ||||
|     } | ||||
|     MarkedValueList arguments(interpreter().heap()); | ||||
|     arguments.values().append(Value(&m_target)); | ||||
|     auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)).to_boolean(); | ||||
|     if (interpreter().exception()) | ||||
|         return false; | ||||
|     if (trap_result && m_target.is_extensible()) { | ||||
|         interpreter().throw_exception<TypeError>("Proxy handler's preventExtensions trap violates invariant: cannot return true if the target object is extensible"); | ||||
|         return false; | ||||
|     } | ||||
|     return trap_result; | ||||
| } | ||||
| 
 | ||||
| Optional<PropertyDescriptor> ProxyObject::get_own_property_descriptor(PropertyName name) const | ||||
| { | ||||
|     if (m_is_revoked) { | ||||
|         interpreter().throw_exception<TypeError>("An operation was performed on a revoked Proxy object"); | ||||
|         return {}; | ||||
|     } | ||||
|     auto trap = m_handler.get("getOwnPropertyDescriptor"); | ||||
|     if (interpreter().exception()) | ||||
|         return {}; | ||||
|     if (trap.is_empty() || trap.is_undefined() || trap.is_null()) | ||||
|         return m_target.get_own_property_descriptor(name); | ||||
|     if (!trap.is_function()) { | ||||
|         interpreter().throw_exception<TypeError>("Proxy handler's getOwnPropertyDescriptor trap wasn't undefined, null, or callable"); | ||||
|         return {}; | ||||
|     } | ||||
|     MarkedValueList arguments(interpreter().heap()); | ||||
|     arguments.values().append(Value(&m_target)); | ||||
|     arguments.values().append(js_string(interpreter(), name.to_string())); | ||||
|     auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)); | ||||
|     if (interpreter().exception()) | ||||
|         return {}; | ||||
|     if (!trap_result.is_object() && !trap_result.is_undefined()) { | ||||
|         interpreter().throw_exception<TypeError>("Proxy handler's getOwnPropertyDescriptor trap violates invariant: must return an object or undefined"); | ||||
|         return {}; | ||||
|     } | ||||
|     auto target_desc = m_target.get_own_property_descriptor(name); | ||||
|     if (interpreter().exception()) | ||||
|         return {}; | ||||
|     if (trap_result.is_undefined()) { | ||||
|         if (!target_desc.has_value()) | ||||
|             return {}; | ||||
|         if (!target_desc.value().attributes.is_configurable()) { | ||||
|             interpreter().throw_exception<TypeError>("Proxy handler's getOwnPropertyDescriptor trap violates invariant: cannot return undefined for a property on the target which is a non-configurable property"); | ||||
|             return {}; | ||||
|         } | ||||
|         if (!m_target.is_extensible()) { | ||||
|             interpreter().throw_exception<TypeError>("Proxy handler's getOwnPropertyDescriptor trap violates invariant: cannot report a property as being undefined if it exists as an own property of the target and the target is non-extensible"); | ||||
|             return {}; | ||||
|         } | ||||
|         return {}; | ||||
|     } | ||||
|     auto result_desc = PropertyDescriptor::from_dictionary(interpreter(), trap_result.as_object()); | ||||
|     if (interpreter().exception()) | ||||
|         return {}; | ||||
|     if (!is_compatible_property_descriptor(interpreter(), m_target.is_extensible(), result_desc, target_desc)) { | ||||
|         interpreter().throw_exception<TypeError>("Proxy handler's getOwnPropertyDescriptor trap violates invariant: invalid property descriptor for existing property on the target"); | ||||
|         return {}; | ||||
|     } | ||||
|     if (!result_desc.attributes.is_configurable() && (!target_desc.has_value() || target_desc.value().attributes.is_configurable())) { | ||||
|         interpreter().throw_exception<TypeError>("Proxy handler's getOwnPropertyDescriptor trap violates invariant: cannot report target's property as non-configurable if the property does not exist, or if it is configurable"); | ||||
|         return {}; | ||||
|     } | ||||
|     return result_desc; | ||||
| } | ||||
| 
 | ||||
| bool ProxyObject::define_property(const FlyString& property_name, const Object& descriptor, bool throw_exceptions) | ||||
| { | ||||
|     if (m_is_revoked) { | ||||
|         interpreter().throw_exception<TypeError>("An operation was performed on a revoked Proxy object"); | ||||
|         return false; | ||||
|     } | ||||
|     auto trap = m_handler.get("defineProperty"); | ||||
|     if (interpreter().exception()) | ||||
|         return false; | ||||
|     if (trap.is_empty() || trap.is_undefined() || trap.is_null()) | ||||
|         return m_target.define_property(property_name, descriptor, throw_exceptions); | ||||
|     if (!trap.is_function()) { | ||||
|         interpreter().throw_exception<TypeError>("Proxy handler's defineProperty trap wasn't undefined, null, or callable"); | ||||
|         return false; | ||||
|     } | ||||
|     MarkedValueList arguments(interpreter().heap()); | ||||
|     arguments.values().append(Value(&m_target)); | ||||
|     arguments.values().append(js_string(interpreter(), property_name)); | ||||
|     arguments.values().append(Value(const_cast<Object*>(&descriptor))); | ||||
|     auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)).to_boolean(); | ||||
|     if (interpreter().exception() || !trap_result) | ||||
|         return false; | ||||
|     auto target_desc = m_target.get_own_property_descriptor(property_name); | ||||
|     if (interpreter().exception()) | ||||
|         return false; | ||||
|     bool setting_config_false = false; | ||||
|     if (descriptor.has_property("configurable") && !descriptor.get("configurable").to_boolean()) | ||||
|         setting_config_false = true; | ||||
|     if (interpreter().exception()) | ||||
|         return false; | ||||
|     if (!target_desc.has_value()) { | ||||
|         if (!m_target.is_extensible()) { | ||||
|             interpreter().throw_exception<TypeError>("Proxy handler's defineProperty trap violates invariant: a property cannot be reported as being defined if the property does not exist on the target and the target is non-extensible"); | ||||
|             return false; | ||||
|         } | ||||
|         if (setting_config_false) { | ||||
|             interpreter().throw_exception<TypeError>("Proxy handler's defineProperty trap violates invariant: a property cannot be defined as non-configurable if it does not already exist on the target object"); | ||||
|             return false; | ||||
|         } | ||||
|     } else { | ||||
|         if (!is_compatible_property_descriptor(interpreter(), m_target.is_extensible(), PropertyDescriptor::from_dictionary(interpreter(), descriptor), target_desc)) { | ||||
|             interpreter().throw_exception<TypeError>("Proxy handler's defineProperty trap violates invariant: the new descriptor is not compatible with the existing descriptor of the property on the target"); | ||||
|             return false; | ||||
|         } | ||||
|         if (setting_config_false && target_desc.value().attributes.is_configurable()) { | ||||
|             interpreter().throw_exception<TypeError>("Proxy handler's defineProperty trap violates invariant: a property cannot be defined as non-configurable if it already exists on the target object as a configurable property"); | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| bool ProxyObject::has_property(PropertyName name) const | ||||
| { | ||||
|     if (m_is_revoked) { | ||||
|         interpreter().throw_exception<TypeError>("An operation was performed on a revoked Proxy object"); | ||||
|         return false; | ||||
|     } | ||||
|     auto trap = m_handler.get("has"); | ||||
|     if (interpreter().exception()) | ||||
|         return false; | ||||
|     if (trap.is_empty() || trap.is_undefined() || trap.is_null()) | ||||
|         return m_target.has_property(name); | ||||
|     if (!trap.is_function()) { | ||||
|         interpreter().throw_exception<TypeError>("Proxy handler's has trap wasn't undefined, null, or callable"); | ||||
|         return false; | ||||
|     } | ||||
|     MarkedValueList arguments(interpreter().heap()); | ||||
|     arguments.values().append(Value(&m_target)); | ||||
|     arguments.values().append(js_string(interpreter(), name.to_string())); | ||||
|     auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)).to_boolean(); | ||||
|     if (interpreter().exception()) | ||||
|         return false; | ||||
|     if (!trap_result) { | ||||
|         auto target_desc = m_target.get_own_property_descriptor(name); | ||||
|         if (interpreter().exception()) | ||||
|             return false; | ||||
|         if (target_desc.has_value()) { | ||||
|             if (!target_desc.value().attributes.is_configurable()) { | ||||
|                 interpreter().throw_exception<TypeError>("Proxy handler's has trap violates invariant: a property cannot be reported as non-existent if it exists on the target as a non-configurable property"); | ||||
|                 return false; | ||||
|             } | ||||
|             if (!m_target.is_extensible()) { | ||||
|                 interpreter().throw_exception<TypeError>("Proxy handler's has trap violates invariant: a property cannot be reported as non-existent if it exist on the target and the target is non-extensible"); | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     return trap_result; | ||||
| } | ||||
| 
 | ||||
| Value ProxyObject::get(PropertyName name) const | ||||
| { | ||||
|     if (m_is_revoked) { | ||||
|         interpreter().throw_exception<TypeError>("An operation was performed on a revoked Proxy object"); | ||||
|         return {}; | ||||
|     } | ||||
|     auto trap = m_handler.get("get"); | ||||
|     if (interpreter().exception()) | ||||
|         return {}; | ||||
|     if (trap.is_empty() || trap.is_undefined() || trap.is_null()) | ||||
|         return m_target.get(name); | ||||
|     if (!trap.is_function()) | ||||
|         return interpreter().throw_exception<TypeError>("Proxy handler's get trap wasn't undefined, null, or callable"); | ||||
|     MarkedValueList arguments(interpreter().heap()); | ||||
|     arguments.values().append(Value(&m_target)); | ||||
|     arguments.values().append(js_string(interpreter(), name.to_string())); | ||||
|     arguments.values().append(Value(const_cast<ProxyObject*>(this))); | ||||
|     auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)); | ||||
|     if (interpreter().exception()) | ||||
|         return {}; | ||||
|     auto target_desc = m_target.get_own_property_descriptor(name); | ||||
|     if (target_desc.has_value()) { | ||||
|         if (interpreter().exception()) | ||||
|             return {}; | ||||
|         if (target_desc.value().is_data_descriptor() && !target_desc.value().attributes.is_writable() && !same_value(interpreter(), trap_result, target_desc.value().value)) | ||||
|             return interpreter().throw_exception<TypeError>("Proxy handler's get trap violates invariant: the returned value must match the value on the target if the property exists on the target as a non-writable, non-configurable own data property"); | ||||
|         if (target_desc.value().is_accessor_descriptor() && target_desc.value().getter == nullptr && !trap_result.is_undefined()) | ||||
|             return interpreter().throw_exception<TypeError>("Proxy handler's get trap violates invariant: the returned value must be undefined if the property exists on the target as a non-configurable accessor property with an undefined get attribute"); | ||||
|     } | ||||
|     return trap_result; | ||||
| } | ||||
| 
 | ||||
| bool ProxyObject::put(PropertyName name, Value value) | ||||
| { | ||||
|     if (m_is_revoked) { | ||||
|         interpreter().throw_exception<TypeError>("An operation was performed on a revoked Proxy object"); | ||||
|         return false; | ||||
|     } | ||||
|     auto trap = m_handler.get("set"); | ||||
|     if (interpreter().exception()) | ||||
|         return false; | ||||
|     if (trap.is_empty() || trap.is_undefined() || trap.is_null()) | ||||
|         return m_target.put(name, value); | ||||
|     if (!trap.is_function()) { | ||||
|         interpreter().throw_exception<TypeError>("Proxy handler's set trap wasn't undefined, null, or callable"); | ||||
|         return false; | ||||
|     } | ||||
|     MarkedValueList arguments(interpreter().heap()); | ||||
|     arguments.values().append(Value(&m_target)); | ||||
|     arguments.values().append(js_string(interpreter(), name.to_string())); | ||||
|     arguments.values().append(value); | ||||
|     arguments.values().append(Value(const_cast<ProxyObject*>(this))); | ||||
|     auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)).to_boolean(); | ||||
|     if (interpreter().exception() || !trap_result) | ||||
|         return false; | ||||
|     auto target_desc = m_target.get_own_property_descriptor(name); | ||||
|     if (interpreter().exception()) | ||||
|         return false; | ||||
|     if (target_desc.has_value() && !target_desc.value().attributes.is_configurable()) { | ||||
|         if (target_desc.value().is_data_descriptor() && !target_desc.value().attributes.is_writable() && !same_value(interpreter(), value, target_desc.value().value)) { | ||||
|             interpreter().throw_exception<TypeError>("Proxy handler's set trap violates invariant: cannot return true for a property on the target which is a non-configurable, non-writable own data property"); | ||||
|             return false; | ||||
|         } | ||||
|         if (target_desc.value().is_accessor_descriptor() && !target_desc.value().setter) { | ||||
|             interpreter().throw_exception<TypeError>("Proxy handler's set trap violates invariant: cannot return true for a property on the target which is a non-configurable own accessor property with an undefined set attribute"); | ||||
|         } | ||||
|     } | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| Value ProxyObject::delete_property(PropertyName name) | ||||
| { | ||||
|     if (m_is_revoked) { | ||||
|         interpreter().throw_exception<TypeError>("An operation was performed on a revoked Proxy object"); | ||||
|         return {}; | ||||
|     } | ||||
|     auto trap = m_handler.get("deleteProperty"); | ||||
|     if (interpreter().exception()) | ||||
|         return {}; | ||||
|     if (trap.is_empty() || trap.is_undefined() || trap.is_null()) | ||||
|         return m_target.delete_property(name); | ||||
|     if (!trap.is_function()) | ||||
|         return interpreter().throw_exception<TypeError>("Proxy handler's delete trap wasn't undefined, null, or callable"); | ||||
|     MarkedValueList arguments(interpreter().heap()); | ||||
|     arguments.values().append(Value(&m_target)); | ||||
|     arguments.values().append(js_string(interpreter(), name.to_string())); | ||||
|     auto trap_result = interpreter().call(trap.as_function(), Value(&m_handler), move(arguments)).to_boolean(); | ||||
|     if (interpreter().exception()) | ||||
|         return {}; | ||||
|     if (!trap_result) | ||||
|         return Value(false); | ||||
|     auto target_desc = m_target.get_own_property_descriptor(name); | ||||
|     if (interpreter().exception()) | ||||
|         return {}; | ||||
|     if (!target_desc.has_value()) | ||||
|         return Value(true); | ||||
|     if (!target_desc.value().attributes.is_configurable()) | ||||
|         return interpreter().throw_exception<TypeError>("Proxy handler's delete trap violates invariant: cannot report a non-configurable own property of the target as deleted"); | ||||
|     return Value(true); | ||||
| } | ||||
| 
 | ||||
| void ProxyObject::visit_children(Cell::Visitor& visitor) | ||||
| { | ||||
|     Object::visit_children(visitor); | ||||
|     visitor.visit(&m_target); | ||||
|     visitor.visit(&m_handler); | ||||
| } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										67
									
								
								Libraries/LibJS/Runtime/ProxyObject.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								Libraries/LibJS/Runtime/ProxyObject.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,67 @@ | |||
| /*
 | ||||
|  * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> | ||||
|  * All rights reserved. | ||||
|  * | ||||
|  * Redistribution and use in source and binary forms, with or without | ||||
|  * modification, are permitted provided that the following conditions are met: | ||||
|  * | ||||
|  * 1. Redistributions of source code must retain the above copyright notice, this | ||||
|  *    list of conditions and the following disclaimer. | ||||
|  * | ||||
|  * 2. Redistributions in binary form must reproduce the above copyright notice, | ||||
|  *    this list of conditions and the following disclaimer in the documentation | ||||
|  *    and/or other materials provided with the distribution. | ||||
|  * | ||||
|  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
|  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
|  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
|  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
|  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
|  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
|  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
|  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
|  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
|  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <LibJS/Runtime/Object.h> | ||||
| 
 | ||||
| namespace JS { | ||||
| 
 | ||||
| class ProxyObject : public Object { | ||||
| public: | ||||
|     static ProxyObject* create(GlobalObject&, Object& target, Object& handler); | ||||
| 
 | ||||
|     ProxyObject(Object& target, Object& handler, Object& prototype); | ||||
|     virtual ~ProxyObject() override; | ||||
| 
 | ||||
|     const Object& target() const { return m_target; } | ||||
|     const Object& handler() const { return m_handler; } | ||||
| 
 | ||||
|     virtual Object* prototype() override; | ||||
|     virtual const Object* prototype() const override; | ||||
|     virtual bool set_prototype(Object* object) override; | ||||
|     virtual bool is_extensible() const override; | ||||
|     virtual bool prevent_extensions() override; | ||||
|     virtual Optional<PropertyDescriptor> get_own_property_descriptor(PropertyName) const override; | ||||
|     virtual bool define_property(const FlyString& property_name, const Object& descriptor, bool throw_exceptions = true) override; | ||||
|     virtual bool has_property(PropertyName name) const override; | ||||
|     virtual Value get(PropertyName name) const override; | ||||
|     virtual bool put(PropertyName name, Value value) override; | ||||
|     virtual Value delete_property(PropertyName name) override; | ||||
| 
 | ||||
|     void revoke() { m_is_revoked = true; } | ||||
| 
 | ||||
| private: | ||||
|     virtual void visit_children(Visitor&) override; | ||||
|     virtual const char* class_name() const override { return "ProxyObject"; } | ||||
|     virtual bool is_proxy_object() const override { return true; } | ||||
| 
 | ||||
|     Object& m_target; | ||||
|     Object& m_handler; | ||||
|     bool m_is_revoked { false }; | ||||
| }; | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										46
									
								
								Libraries/LibJS/Runtime/ProxyPrototype.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								Libraries/LibJS/Runtime/ProxyPrototype.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | |||
| /*
 | ||||
|  * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> | ||||
|  * All rights reserved. | ||||
|  * | ||||
|  * Redistribution and use in source and binary forms, with or without | ||||
|  * modification, are permitted provided that the following conditions are met: | ||||
|  * | ||||
|  * 1. Redistributions of source code must retain the above copyright notice, this | ||||
|  *    list of conditions and the following disclaimer. | ||||
|  * | ||||
|  * 2. Redistributions in binary form must reproduce the above copyright notice, | ||||
|  *    this list of conditions and the following disclaimer in the documentation | ||||
|  *    and/or other materials provided with the distribution. | ||||
|  * | ||||
|  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
|  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
|  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
|  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
|  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
|  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
|  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
|  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
|  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
|  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|  */ | ||||
| 
 | ||||
| #include <AK/Function.h> | ||||
| #include <AK/StringBuilder.h> | ||||
| #include <LibJS/Heap/Heap.h> | ||||
| #include <LibJS/Interpreter.h> | ||||
| #include <LibJS/Runtime/Error.h> | ||||
| #include <LibJS/Runtime/GlobalObject.h> | ||||
| #include <LibJS/Runtime/ProxyPrototype.h> | ||||
| 
 | ||||
| namespace JS { | ||||
| 
 | ||||
| ProxyPrototype::ProxyPrototype() | ||||
|     : Object(interpreter().global_object().object_prototype()) | ||||
| { | ||||
| } | ||||
| 
 | ||||
| ProxyPrototype::~ProxyPrototype() | ||||
| { | ||||
| } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										42
									
								
								Libraries/LibJS/Runtime/ProxyPrototype.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								Libraries/LibJS/Runtime/ProxyPrototype.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,42 @@ | |||
| /*
 | ||||
|  * Copyright (c) 2020, Matthew Olsson <matthewcolsson@gmail.com> | ||||
|  * All rights reserved. | ||||
|  * | ||||
|  * Redistribution and use in source and binary forms, with or without | ||||
|  * modification, are permitted provided that the following conditions are met: | ||||
|  * | ||||
|  * 1. Redistributions of source code must retain the above copyright notice, this | ||||
|  *    list of conditions and the following disclaimer. | ||||
|  * | ||||
|  * 2. Redistributions in binary form must reproduce the above copyright notice, | ||||
|  *    this list of conditions and the following disclaimer in the documentation | ||||
|  *    and/or other materials provided with the distribution. | ||||
|  * | ||||
|  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||||
|  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||||
|  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||||
|  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||||
|  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||||
|  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||||
|  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||||
|  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||||
|  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
|  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|  */ | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <LibJS/Runtime/Object.h> | ||||
| 
 | ||||
| namespace JS { | ||||
| 
 | ||||
| class ProxyPrototype final : public Object { | ||||
| public: | ||||
|     ProxyPrototype(); | ||||
|     virtual ~ProxyPrototype() override; | ||||
| 
 | ||||
| private: | ||||
|     virtual const char* class_name() const override { return "ProxyPrototype"; } | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  | @ -29,11 +29,6 @@ | |||
| 
 | ||||
| namespace JS { | ||||
| 
 | ||||
| const LogStream& operator<<(const LogStream& stream, const PropertyAttributes& attributes) | ||||
| { | ||||
|     return stream << attributes.bits(); | ||||
| } | ||||
| 
 | ||||
| Shape* Shape::create_unique_clone() const | ||||
| { | ||||
|     auto* new_shape = heap().allocate<Shape>(); | ||||
|  |  | |||
|  | @ -23,7 +23,13 @@ try { | |||
|     o.baz = "baz"; | ||||
|     assert(o.baz === undefined); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         Object.defineProperty(o, "baz", { value: "baz" }); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Unable to define property on non-extensible object", | ||||
|     }); | ||||
| 
 | ||||
|     assert(o.baz === undefined); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|  |  | |||
|  | @ -4,12 +4,19 @@ try { | |||
|     assert(Object.setPrototypeOf.length === 2); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         Object.setPrototypeOf({}, "foo"); | ||||
|         Object.setPrototypeOf(); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Prototype must be null or object" | ||||
|         message: "Object.setPrototypeOf requires at least two arguments", | ||||
|     }); | ||||
| 
 | ||||
| //     assertThrowsError(() => {
 | ||||
| //         Object.setPrototypeOf({}, "foo");
 | ||||
| //     }, {
 | ||||
| //         error: TypeError,
 | ||||
| //         message: "Prototype must be null or object"
 | ||||
| //     });
 | ||||
| 
 | ||||
|     o = {}; | ||||
|     p = {}; | ||||
|     assert(Object.setPrototypeOf(o, p) === o); | ||||
|  | @ -19,7 +26,7 @@ try { | |||
|         Object.setPrototypeOf(o, {}); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Can't set prototype of non-extensible object" | ||||
|         message: "Object's setPrototypeOf method returned false" | ||||
|     }); | ||||
|     assert(Object.setPrototypeOf(o, p) === o); | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										107
									
								
								Libraries/LibJS/Tests/Proxy.handler-defineProperty.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								Libraries/LibJS/Tests/Proxy.handler-defineProperty.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,107 @@ | |||
| load("test-common.js"); | ||||
| 
 | ||||
| try { | ||||
|     let p = new Proxy({}, { defineProperty: null }); | ||||
|     assert(Object.defineProperty(p, "foo", {}) === p); | ||||
|     p = new Proxy({}, { defineProperty: undefined }); | ||||
|     assert(Object.defineProperty(p, "foo", {}) === p); | ||||
|     p = new Proxy({}, {}); | ||||
|     assert(Object.defineProperty(p, "foo", {}) == p); | ||||
| 
 | ||||
|     let o = {}; | ||||
|     p = new Proxy(o, { | ||||
|         defineProperty(target, name, descriptor) { | ||||
|             assert(target === o); | ||||
|             assert(name === "foo"); | ||||
|             assert(descriptor.configurable === true); | ||||
|             assert(descriptor.enumerable === undefined); | ||||
|             assert(descriptor.writable === true); | ||||
|             assert(descriptor.value === 10); | ||||
|             assert(descriptor.get === undefined); | ||||
|             assert(descriptor.set === undefined); | ||||
|             return true; | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     Object.defineProperty(p, "foo", { configurable: true, writable: true, value: 10 }); | ||||
| 
 | ||||
|     p = new Proxy(o, { | ||||
|         defineProperty(target, name, descriptor) { | ||||
|             if (target[name] === undefined) | ||||
|                 Object.defineProperty(target, name, descriptor); | ||||
|             return true; | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     Object.defineProperty(p, "foo", { value: 10, enumerable: true, configurable: false, writable: true }); | ||||
|     let d = Object.getOwnPropertyDescriptor(p, "foo"); | ||||
|     assert(d.enumerable === true); | ||||
|     assert(d.configurable === false); | ||||
|     assert(d.writable === true); | ||||
|     assert(d.value === 10); | ||||
|     assert(d.get === undefined); | ||||
|     assert(d.set === undefined); | ||||
| 
 | ||||
|     Object.defineProperty(p, "foo", { value: 20, enumerable: true, configurable: false, writable: true }); | ||||
|     d = Object.getOwnPropertyDescriptor(p, "foo"); | ||||
|     assert(d.enumerable === true); | ||||
|     assert(d.configurable === false); | ||||
|     assert(d.writable === true); | ||||
|     assert(d.value === 10); | ||||
|     assert(d.get === undefined); | ||||
|     assert(d.set === undefined); | ||||
| 
 | ||||
| 
 | ||||
|     // Invariants
 | ||||
| 
 | ||||
|     p = new Proxy({}, { | ||||
|         defineProperty() { return false; } | ||||
|     }); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         Object.defineProperty(p, "foo", {}); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's defineProperty method returned false", | ||||
|     }); | ||||
| 
 | ||||
|     o = {}; | ||||
|     Object.preventExtensions(o); | ||||
|     p = new Proxy(o, { | ||||
|         defineProperty() { | ||||
|             return true; | ||||
|         } | ||||
|     }); | ||||
|     assertThrowsError(() => { | ||||
|         Object.defineProperty(p, "foo", {}); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's defineProperty trap violates invariant: a property cannot be reported as being defined if the property does not exist on the target and the target is non-extensible", | ||||
|     }); | ||||
| 
 | ||||
|     o = {}; | ||||
|     Object.defineProperty(o, "foo", { value: 10, configurable: true }); | ||||
|     p = new Proxy(o, { | ||||
|         defineProperty() { | ||||
|             return true; | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         Object.defineProperty(p, "bar", { value: 6, configurable: false }); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's defineProperty trap violates invariant: a property cannot be defined as non-configurable if it does not already exist on the target object", | ||||
|     }); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         Object.defineProperty(p, "foo", { value: 6, configurable: false }); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's defineProperty trap violates invariant: a property cannot be defined as non-configurable if it already exists on the target object as a configurable property", | ||||
|     }); | ||||
| 
 | ||||
|     console.log("PASS"); | ||||
| } catch (e) { | ||||
|     console.log("FAIL: " + e); | ||||
| } | ||||
							
								
								
									
										56
									
								
								Libraries/LibJS/Tests/Proxy.handler-deleteProperty.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								Libraries/LibJS/Tests/Proxy.handler-deleteProperty.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,56 @@ | |||
| load("test-common.js"); | ||||
| 
 | ||||
| try { | ||||
|     assert(delete (new Proxy({}, { deleteProperty: undefined })).foo === true); | ||||
|     assert(delete (new Proxy({}, { deleteProperty: null })).foo === true); | ||||
|     assert(delete (new Proxy({}, {})).foo === true); | ||||
| 
 | ||||
|     let o = {}; | ||||
|     let p = new Proxy(o, { | ||||
|         deleteProperty(target, property) { | ||||
|             assert(target === o); | ||||
|             assert(property === "foo"); | ||||
|             return true; | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     delete p.foo; | ||||
| 
 | ||||
|     o = { foo: 1, bar: 2 }; | ||||
|     p = new Proxy(o, { | ||||
|         deleteProperty(target, property) { | ||||
|             if (property === "foo") { | ||||
|                 delete target[property]; | ||||
|                 return true; | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     assert(delete p.foo === true); | ||||
|     assert(delete p.bar === false); | ||||
| 
 | ||||
|     assert(o.foo === undefined); | ||||
|     assert(o.bar === 2); | ||||
| 
 | ||||
|     // Invariants
 | ||||
| 
 | ||||
|     o = {}; | ||||
|     Object.defineProperty(o, "foo", { configurable: false }); | ||||
|     p = new Proxy(o, { | ||||
|         deleteProperty() { | ||||
|             return true; | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         delete p.foo; | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's delete trap violates invariant: cannot report a non-configurable own property of the target as deleted", | ||||
|     }); | ||||
| 
 | ||||
|     console.log("PASS"); | ||||
| } catch (e) { | ||||
|     console.log("FAIL: " + e); | ||||
| } | ||||
							
								
								
									
										71
									
								
								Libraries/LibJS/Tests/Proxy.handler-get.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								Libraries/LibJS/Tests/Proxy.handler-get.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,71 @@ | |||
| load("test-common.js"); | ||||
| 
 | ||||
| try { | ||||
|     assert((new Proxy({}, { get: undefined })).foo === undefined); | ||||
|     assert((new Proxy({}, { get: null })).foo === undefined); | ||||
|     assert((new Proxy({}, {})).foo === undefined); | ||||
| 
 | ||||
|     let o = {}; | ||||
|     let p = new Proxy(o, { | ||||
|         get(target, property, receiver) { | ||||
|             assert(target === o); | ||||
|             assert(property === "foo"); | ||||
|             assert(receiver === p); | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     p.foo; | ||||
| 
 | ||||
|     o = { foo: 1 }; | ||||
|     p = new Proxy(o, { | ||||
|         get(target, property, receiver) { | ||||
|             if (property === "bar") { | ||||
|                 return 2; | ||||
|             } else if (property === "baz") { | ||||
|                 return receiver.qux; | ||||
|             } else if (property === "qux") { | ||||
|                 return 3; | ||||
|             } | ||||
|             return target[property]; | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     assert(p.foo === 1); | ||||
|     assert(p.bar === 2); | ||||
|     assert(p.baz === 3); | ||||
|     assert(p.qux === 3); | ||||
|     assert(p.test === undefined); | ||||
| 
 | ||||
|     // Invariants
 | ||||
| 
 | ||||
|     o = {}; | ||||
|     Object.defineProperty(o, "foo", { value: 5, configurable: false, writable: true }); | ||||
|     Object.defineProperty(o, "bar", { value: 10, configurable: false, writable: false }); | ||||
| 
 | ||||
|     p = new Proxy(o, { | ||||
|         get() { | ||||
|             return 8; | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     assert(p.foo === 8); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         p.bar; | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's get trap violates invariant: the returned value must match the value on the target if the property exists on the target as a non-writable, non-configurable own data property", | ||||
|     }); | ||||
| 
 | ||||
|     Object.defineProperty(o, "baz", { configurable: false, set(_) {} }); | ||||
|     assertThrowsError(() => { | ||||
|         p.baz; | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's get trap violates invariant: the returned value must be undefined if the property exists on the target as a non-configurable accessor property with an undefined get attribute", | ||||
|     }); | ||||
| 
 | ||||
|     console.log("PASS"); | ||||
| } catch (e) { | ||||
|     console.log("FAIL: " + e); | ||||
| } | ||||
							
								
								
									
										159
									
								
								Libraries/LibJS/Tests/Proxy.handler-getOwnPropertyDescriptor.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								Libraries/LibJS/Tests/Proxy.handler-getOwnPropertyDescriptor.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,159 @@ | |||
| load("test-common.js"); | ||||
| 
 | ||||
| try { | ||||
|     assert(Object.getOwnPropertyDescriptor(new Proxy({}, { getOwnPropertyDescriptor: null }), "a") === undefined); | ||||
|     assert(Object.getOwnPropertyDescriptor(new Proxy({}, { getOwnPropertyDescriptor: undefined }), "a") === undefined); | ||||
|     assert(Object.getOwnPropertyDescriptor(new Proxy({}, {}), "a") === undefined); | ||||
| 
 | ||||
|     let o = {}; | ||||
|     let p = new Proxy(o, { | ||||
|         getOwnPropertyDescriptor(target, property) { | ||||
|             assert(target === o); | ||||
|             assert(property === "foo"); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     Object.getOwnPropertyDescriptor(p, "foo"); | ||||
| 
 | ||||
|     o = { foo: "bar" }; | ||||
|     Object.defineProperty(o, "baz", { value: "qux", enumerable: false, configurable: true, writable: false }); | ||||
|     p = new Proxy(o, { | ||||
|         getOwnPropertyDescriptor(target, property) { | ||||
|             if (property === "baz") | ||||
|                 return Object.getOwnPropertyDescriptor(target, "baz"); | ||||
|              return { value: target[property], enumerable: false, configurable: true, writable: true }; | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     let d = Object.getOwnPropertyDescriptor(p, "baz"); | ||||
|     assert(d.configurable === true); | ||||
|     assert(d.enumerable === false); | ||||
|     assert(d.writable === false); | ||||
|     assert(d.value === "qux"); | ||||
|     assert(d.get === undefined); | ||||
|     assert(d.set === undefined); | ||||
| 
 | ||||
|     d = Object.getOwnPropertyDescriptor(p, "foo"); | ||||
|     assert(d.configurable === true); | ||||
|     assert(d.enumerable === false); | ||||
|     assert(d.writable === true); | ||||
|     assert(d.value === "bar"); | ||||
|     assert(d.get === undefined); | ||||
|     assert(d.set === undefined); | ||||
| 
 | ||||
|     // Invariants
 | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         Object.getOwnPropertyDescriptor(new Proxy({}, { | ||||
|             getOwnPropertyDescriptor: 1 | ||||
|         })); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's getOwnPropertyDescriptor trap wasn't undefined, null, or callable", | ||||
|     }); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         Object.getOwnPropertyDescriptor(new Proxy({}, { | ||||
|             getOwnPropertyDescriptor() { | ||||
|                 return 1; | ||||
|             }, | ||||
|         })); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's getOwnPropertyDescriptor trap violates invariant: must return an object or undefined", | ||||
|     }); | ||||
| 
 | ||||
|     o = {}; | ||||
|     Object.defineProperty(o, "foo", { value: 10, configurable: false }); | ||||
|     p = new Proxy(o, { | ||||
|         getOwnPropertyDescriptor() { | ||||
|             return undefined; | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     assert(Object.getOwnPropertyDescriptor(p, "bar") === undefined); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         Object.getOwnPropertyDescriptor(p, "foo"); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's getOwnPropertyDescriptor trap violates invariant: cannot return undefined for a property on the target which is a non-configurable property", | ||||
|     }); | ||||
| 
 | ||||
|     Object.defineProperty(o, "baz", { value: 20, configurable: true, writable: true, enumerable: true }); | ||||
|     Object.preventExtensions(o); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         Object.getOwnPropertyDescriptor(p, "baz"); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's getOwnPropertyDescriptor trap violates invariant: cannot report a property as being undefined if it exists as an own property of the target and the target is non-extensible", | ||||
|     }); | ||||
| 
 | ||||
|     o = {}; | ||||
|     Object.defineProperty(o, "v1", { value: 10, configurable: false }); | ||||
|     Object.defineProperty(o, "v2", { value: 10, configurable: false, enumerable: true }); | ||||
|     Object.defineProperty(o, "v3", { configurable: false, get() { return 1; } }); | ||||
|     Object.defineProperty(o, "v4", { value: 10, configurable: false, writable: false, enumerable: true }); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         Object.getOwnPropertyDescriptor(new Proxy(o, { | ||||
|             getOwnPropertyDescriptor() { | ||||
|                 return { configurable: true }; | ||||
|             }, | ||||
|         }), "v1"); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's getOwnPropertyDescriptor trap violates invariant: invalid property descriptor for existing property on the target", | ||||
|     }); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         Object.getOwnPropertyDescriptor(new Proxy(o, { | ||||
|             getOwnPropertyDescriptor() { | ||||
|                 return { enumerable: false }; | ||||
|             }, | ||||
|         }), "v2"); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's getOwnPropertyDescriptor trap violates invariant: invalid property descriptor for existing property on the target", | ||||
|     }); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         Object.getOwnPropertyDescriptor(new Proxy(o, { | ||||
|             getOwnPropertyDescriptor() { | ||||
|                 return { value: 10 }; | ||||
|             }, | ||||
|         }), "v3"); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's getOwnPropertyDescriptor trap violates invariant: invalid property descriptor for existing property on the target", | ||||
|     }); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         Object.getOwnPropertyDescriptor(new Proxy(o, { | ||||
|             getOwnPropertyDescriptor() { | ||||
|                 return { value: 10, writable: true }; | ||||
|             }, | ||||
|         }), "v4"); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's getOwnPropertyDescriptor trap violates invariant: invalid property descriptor for existing property on the target", | ||||
|     }); | ||||
| 
 | ||||
|     o = {}; | ||||
|     Object.defineProperty(o, "v", { configurable: true }); | ||||
|     assertThrowsError(() => { | ||||
|         Object.getOwnPropertyDescriptor(new Proxy(o, { | ||||
|             getOwnPropertyDescriptor() { | ||||
|                 return { configurable: false }; | ||||
|             }, | ||||
|         }), "v"); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's getOwnPropertyDescriptor trap violates invariant: cannot report target's property as non-configurable if the property does not exist, or if it is configurable", | ||||
|     }); | ||||
| 
 | ||||
|     console.log("PASS"); | ||||
| } catch (e) { | ||||
|     console.log("FAIL: " + e); | ||||
| } | ||||
							
								
								
									
										87
									
								
								Libraries/LibJS/Tests/Proxy.handler-getPrototypeOf.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								Libraries/LibJS/Tests/Proxy.handler-getPrototypeOf.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,87 @@ | |||
| load("test-common.js"); | ||||
| 
 | ||||
| try { | ||||
|     const child = {}; | ||||
|     const childProto = { foo: "bar" }; | ||||
| 
 | ||||
|     Object.setPrototypeOf(child, childProto); | ||||
|     assert(child.foo === "bar"); | ||||
| 
 | ||||
|     Object.getPrototypeOf(new Proxy(child, { getPrototypeOf: null })); | ||||
|     Object.getPrototypeOf(new Proxy(child, { getPrototypeOf: undefined })); | ||||
| 
 | ||||
|     let o = {}; | ||||
|     let p = new Proxy(o, { | ||||
|         getPrototypeOf(target) { | ||||
|             assert(target === o); | ||||
|             return null; | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     Object.getPrototypeOf(p); | ||||
| 
 | ||||
|     p = new Proxy(o, { | ||||
|         getPrototypeOf(target) { | ||||
|             if (target.foo) | ||||
|                 return { bar: 1 }; | ||||
|             return { bar: 2 }; | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     assert(Object.getPrototypeOf(p).bar === 2); | ||||
|     o.foo = 20 | ||||
|     assert(Object.getPrototypeOf(p).bar === 1); | ||||
| 
 | ||||
|     // Invariants
 | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         Object.getPrototypeOf(new Proxy(child, { getPrototypeOf: 1 })); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's getPrototypeOf trap wasn't undefined, null, or callable", | ||||
|     }); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         Object.getPrototypeOf(new Proxy(child, { getPrototypeOf() { return 1; } })); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's getPrototypeOf trap violates invariant: must return an object or null", | ||||
|     }); | ||||
| 
 | ||||
|     p = new Proxy(child, { | ||||
|         getPrototypeOf(target) { | ||||
|             assert(target === child); | ||||
|             return { baz: "qux" }; | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     assert(Object.getPrototypeOf(p).baz === "qux"); | ||||
| 
 | ||||
|     Object.preventExtensions(child); | ||||
|     p = new Proxy(child, { | ||||
|         getPrototypeOf(target) { | ||||
|             assert(target === child); | ||||
|             return childProto; | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     assert(Object.getPrototypeOf(p).foo === "bar"); | ||||
| 
 | ||||
|     p = new Proxy(child, { | ||||
|         getPrototypeOf(target) { | ||||
|             assert(target === child); | ||||
|             return { baz: "qux" }; | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         Object.getPrototypeOf(p); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's getPrototypeOf trap violates invariant: cannot return a different prototype object for a non-extensible target" | ||||
|     }); | ||||
| 
 | ||||
|     console.log("PASS"); | ||||
| } catch (e) { | ||||
|     console.log("FAIL: " + e); | ||||
| } | ||||
							
								
								
									
										62
									
								
								Libraries/LibJS/Tests/Proxy.handler-has.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								Libraries/LibJS/Tests/Proxy.handler-has.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,62 @@ | |||
| load("test-common.js"); | ||||
| 
 | ||||
| try { | ||||
|     assert("foo" in new Proxy({}, { has: null }) === false); | ||||
|     assert("foo" in new Proxy({}, { has: undefined}) === false); | ||||
|     assert("foo" in new Proxy({}, {}) === false); | ||||
| 
 | ||||
|     let o = {}; | ||||
|     let p = new Proxy(o, { | ||||
|         has(target, prop) { | ||||
|             assert(target === o); | ||||
|             assert(prop === "foo"); | ||||
|             return true; | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     "foo" in p; | ||||
| 
 | ||||
|     p = new Proxy(o, { | ||||
|         has(target, prop) { | ||||
|             if (target.checkedFoo) | ||||
|                 return true; | ||||
|             if (prop === "foo") | ||||
|                 target.checkedFoo = true; | ||||
|             return false; | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     assert("foo" in p === false); | ||||
|     assert("foo" in p === true); | ||||
| 
 | ||||
|     // Invariants
 | ||||
| 
 | ||||
|     o = {}; | ||||
|     Object.defineProperty(o, "foo", { configurable: false }); | ||||
|     Object.defineProperty(o, "bar", { value: 10, configurable: true }); | ||||
|     p = new Proxy(o, { | ||||
|         has() { | ||||
|             return false; | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         "foo" in p; | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's has trap violates invariant: a property cannot be reported as non-existent if it exists on the target as a non-configurable property", | ||||
|     }); | ||||
| 
 | ||||
|     Object.preventExtensions(o); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         "bar" in p; | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's has trap violates invariant: a property cannot be reported as non-existent if it exist on the target and the target is non-extensible", | ||||
|     }); | ||||
| 
 | ||||
|     console.log("PASS"); | ||||
| } catch (e) { | ||||
|     console.log("FAIL: " + e); | ||||
| } | ||||
							
								
								
									
										49
									
								
								Libraries/LibJS/Tests/Proxy.handler-isExtensible.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								Libraries/LibJS/Tests/Proxy.handler-isExtensible.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,49 @@ | |||
| load("test-common.js"); | ||||
| 
 | ||||
| try { | ||||
|     assert(Object.isExtensible(new Proxy({}, { isExtensible: null })) === true); | ||||
|     assert(Object.isExtensible(new Proxy({}, { isExtensible: undefined })) === true); | ||||
|     assert(Object.isExtensible(new Proxy({}, {})) === true); | ||||
| 
 | ||||
|     let o = {}; | ||||
|     let p = new Proxy(o, { | ||||
|         isExtensible(target) { | ||||
|             assert(target === o); | ||||
|             return true; | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     Object.isExtensible(p); | ||||
| 
 | ||||
|     // Invariants
 | ||||
| 
 | ||||
|     o = {}; | ||||
|     p = new Proxy(o, { | ||||
|         isExtensible(proxyTarget) { | ||||
|             assert(proxyTarget === o); | ||||
|             return true; | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     assert(Object.isExtensible(p) === true); | ||||
|     Object.preventExtensions(o); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         Object.isExtensible(p); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's isExtensible trap violates invariant: return value must match the target's extensibility", | ||||
|     }); | ||||
| 
 | ||||
|     p = new Proxy(o, { | ||||
|         isExtensible(proxyTarget) { | ||||
|             assert(proxyTarget === o); | ||||
|             return false; | ||||
|         }, | ||||
|     }); | ||||
|     assert(Object.isExtensible(p) === false); | ||||
| 
 | ||||
|     console.log("PASS"); | ||||
| } catch (e) { | ||||
|     console.log("FAIL: " + e); | ||||
| } | ||||
							
								
								
									
										55
									
								
								Libraries/LibJS/Tests/Proxy.handler-preventExtensions.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								Libraries/LibJS/Tests/Proxy.handler-preventExtensions.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,55 @@ | |||
| load("test-common.js"); | ||||
| 
 | ||||
| try { | ||||
|     let p = new Proxy({}, { preventExtensions: null }); | ||||
|     assert(Object.preventExtensions(p) === p); | ||||
|     p = new Proxy({}, { preventExtensions: undefined }); | ||||
|     assert(Object.preventExtensions(p) === p); | ||||
|     p = new Proxy({}, {}); | ||||
|     assert(Object.preventExtensions(p) == p); | ||||
| 
 | ||||
|     let o = {}; | ||||
|     p = new Proxy(o, { | ||||
|         preventExtensions(target) { | ||||
|             assert(target === o); | ||||
|             return true; | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     Object.preventExtensions(o); | ||||
|     Object.preventExtensions(p); | ||||
| 
 | ||||
|     // Invariants
 | ||||
| 
 | ||||
|     p = new Proxy({}, { | ||||
|         preventExtensions() { | ||||
|             return false; | ||||
|         }, | ||||
|     }); | ||||
|     assertThrowsError(() => { | ||||
|         Object.preventExtensions(p); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy preventExtensions handler returned false", | ||||
|     }); | ||||
| 
 | ||||
|     o = {}; | ||||
|     p = new Proxy(o, { | ||||
|         preventExtensions() { | ||||
|             return true; | ||||
|         }, | ||||
|     }); | ||||
|     assertThrowsError(() => { | ||||
|         Object.preventExtensions(p); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's preventExtensions trap violates invariant: cannot return true if the target object is extensible" | ||||
|     }); | ||||
| 
 | ||||
|     Object.preventExtensions(o); | ||||
|     assert(Object.preventExtensions(p) === p); | ||||
| 
 | ||||
|     console.log("PASS"); | ||||
| } catch (e) { | ||||
|     console.log("FAIL: " + e); | ||||
| } | ||||
							
								
								
									
										66
									
								
								Libraries/LibJS/Tests/Proxy.handler-set.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								Libraries/LibJS/Tests/Proxy.handler-set.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,66 @@ | |||
| load("test-common.js"); | ||||
| 
 | ||||
| try { | ||||
|     assert((new Proxy({}, { set: undefined }).foo = 1) === 1); | ||||
|     assert((new Proxy({}, { set: null }).foo = 1) === 1); | ||||
|     assert((new Proxy({}, {}).foo = 1) === 1); | ||||
| 
 | ||||
|     let o = {}; | ||||
|     let p = new Proxy(o, { | ||||
|         set(target, prop, value, receiver) { | ||||
|             assert(target === o); | ||||
|             assert(prop === "foo"); | ||||
|             assert(value === 10); | ||||
|             assert(receiver === p); | ||||
|             return true; | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     p.foo = 10; | ||||
| 
 | ||||
|     p = new Proxy(o, { | ||||
|         set(target, prop, value, receiver) { | ||||
|             if (target[prop] === value) { | ||||
|                 target[prop] *= 2; | ||||
|             } else { | ||||
|                 target[prop] = value; | ||||
|             } | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     p.foo = 10; | ||||
|     assert(p.foo === 10); | ||||
|     p.foo = 10; | ||||
|     assert(p.foo === 20); | ||||
|     p.foo = 10; | ||||
|     assert(p.foo === 10); | ||||
| 
 | ||||
|     // Invariants
 | ||||
| 
 | ||||
|     o = {}; | ||||
|     Object.defineProperty(o, "foo", { value: 10 }); | ||||
|     p = new Proxy(o, { | ||||
|         set() { | ||||
|             return true; | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         p.foo = 12; | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's set trap violates invariant: cannot return true for a property on the target which is a non-configurable, non-writable own data property", | ||||
|     }); | ||||
| 
 | ||||
|     Object.defineProperty(o, "bar", { get() {} }); | ||||
|     assertThrowsError(() => { | ||||
|         p.bar = 12; | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's set trap violates invariant: cannot return true for a property on the target which is a non-configurable own accessor property with an undefined set attribute", | ||||
|     }); | ||||
| 
 | ||||
|     console.log("PASS"); | ||||
| } catch (e) { | ||||
|     console.log("FAIL: " + e); | ||||
| } | ||||
							
								
								
									
										95
									
								
								Libraries/LibJS/Tests/Proxy.handler-setPrototypeOf.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								Libraries/LibJS/Tests/Proxy.handler-setPrototypeOf.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,95 @@ | |||
| load("test-common.js"); | ||||
| 
 | ||||
| try { | ||||
|     const child = {}; | ||||
|     const childProto = { foo: "bar" }; | ||||
| 
 | ||||
|     Object.setPrototypeOf(child, childProto); | ||||
|     assert(child.foo === "bar"); | ||||
| 
 | ||||
|     Object.setPrototypeOf(new Proxy(child, { setPrototypeOf: null }), childProto); | ||||
|     Object.setPrototypeOf(new Proxy(child, { setPrototypeOf: undefined }), childProto); | ||||
| 
 | ||||
|     let o = {}; | ||||
|     let theNewProto = { foo: "bar" }; | ||||
|     let p = new Proxy(o, { | ||||
|         setPrototypeOf(target, newProto) { | ||||
|             assert(target === o); | ||||
|             assert(newProto === theNewProto); | ||||
|             return true; | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     Object.setPrototypeOf(p, theNewProto); | ||||
| 
 | ||||
|     p = new Proxy(o, { | ||||
|         setPrototypeOf(target, newProto) { | ||||
|             if (target.shouldSet) | ||||
|                 Object.setPrototypeOf(target, newProto); | ||||
|             return true; | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     Object.setPrototypeOf(p, { foo: 1 }); | ||||
|     assert(Object.getPrototypeOf(p).foo === undefined); | ||||
|     p.shouldSet = true; | ||||
|     assert(o.shouldSet === true); | ||||
|     Object.setPrototypeOf(p, { foo: 1 }); | ||||
|     assert(Object.getPrototypeOf(p).foo === 1); | ||||
| 
 | ||||
|     // Invariants
 | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         Object.setPrototypeOf(new Proxy(child, { setPrototypeOf: 1 }), {}); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's setPrototypeOf trap wasn't undefined, null, or callable", | ||||
|     }); | ||||
| 
 | ||||
|     p = new Proxy(child, { | ||||
|         setPrototypeOf(target, newProto) { | ||||
|             assert(target === child); | ||||
|             return false; | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         Object.setPrototypeOf(p, {}); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Object's setPrototypeOf method returned false" | ||||
|     }); | ||||
|     assert(Object.getPrototypeOf(p) === childProto); | ||||
| 
 | ||||
|     p = new Proxy(child, { | ||||
|         setPrototypeOf(target, newProto) { | ||||
|             assert(target === child); | ||||
|             return true; | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     assert(Object.setPrototypeOf(p, {}) === p); | ||||
|     assert(Object.getPrototypeOf(p) === childProto); | ||||
| 
 | ||||
|     Object.preventExtensions(child); | ||||
|     p = new Proxy(child, { | ||||
|         setPrototypeOf(target, newProto) { | ||||
|             assert(target === child); | ||||
|             return true; | ||||
|         }, | ||||
|     }); | ||||
| 
 | ||||
|     assert(Object.setPrototypeOf(p, childProto) === p); | ||||
|     assert(Object.getPrototypeOf(p) === childProto); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         Object.setPrototypeOf(p, {}); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy handler's setPrototypeOf trap violates invariant: the argument must match the prototype of the target if the target is non-extensible", | ||||
|     }); | ||||
| 
 | ||||
|     console.log("PASS"); | ||||
| } catch (e) { | ||||
|     console.log("FAIL: " + e); | ||||
| } | ||||
							
								
								
									
										30
									
								
								Libraries/LibJS/Tests/Proxy.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								Libraries/LibJS/Tests/Proxy.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | |||
| load("test-common.js"); | ||||
| 
 | ||||
| try { | ||||
|     new Proxy({}, {}); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         new Proxy(); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy requires at least two arguments", | ||||
|     }); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         Proxy(); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Proxy must be called with the \"new\" operator", | ||||
|     }); | ||||
| 
 | ||||
|     assertThrowsError(() => { | ||||
|         new Proxy(1, {}); | ||||
|     }, { | ||||
|         error: TypeError, | ||||
|         message: "Expected target argument of Proxy constructor to be object, got 1", | ||||
|     }); | ||||
| 
 | ||||
|     console.log("PASS"); | ||||
| } catch (e) { | ||||
|     console.log("FAIL: " + e); | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Matthew Olsson
						Matthew Olsson