From 4240b740aab7ed44c8b591411edd5a5393905f59 Mon Sep 17 00:00:00 2001 From: Szilagyi Adam Date: Fri, 27 Mar 2020 14:46:51 +0100 Subject: [PATCH] Implement Object.prototype.__proto__ accessor property (#3546) We are using the already existing Object.getPrototypeOf and Object.setProtoypeOf methods JerryScript-DCO-1.0-Signed-off-by: Adam Szilagyi aszilagy@inf.u-szeged.hu --- .../builtin-objects/ecma-builtin-helpers.c | 1 + ...a-builtin-internal-routines-template.inc.h | 22 ++++--- .../ecma-builtin-object-prototype.c | 18 ++++++ .../ecma-builtin-object-prototype.inc.h | 5 ++ .../builtin-objects/ecma-builtin-object.c | 62 +++++++++++++++++++ .../builtin-objects/ecma-builtin-object.h | 4 ++ jerry-core/lit/lit-magic-strings.inc.h | 1 + jerry-core/lit/lit-magic-strings.ini | 1 + jerry-core/parser/js/js-parser-expr.c | 18 ++++++ jerry-core/parser/js/js-parser-util.c | 4 ++ jerry-core/parser/js/js-parser.h | 1 + jerry-core/vm/vm.c | 17 +++++ tests/jerry/es2015/object-prototype-proto.js | 58 +++++++++++++++++ 13 files changed, 205 insertions(+), 7 deletions(-) create mode 100644 tests/jerry/es2015/object-prototype-proto.js diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-helpers.c b/jerry-core/ecma/builtin-objects/ecma-builtin-helpers.c index 3901a2ddb..42aba88d8 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-helpers.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-helpers.c @@ -18,6 +18,7 @@ #include "ecma-alloc.h" #include "ecma-array-object.h" #include "ecma-builtins.h" +#include "ecma-builtin-object.h" #include "ecma-conversion.h" #include "ecma-function-object.h" #include "ecma-exceptions.h" diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-internal-routines-template.inc.h b/jerry-core/ecma/builtin-objects/ecma-builtin-internal-routines-template.inc.h index 8a95d817d..ec082e8f2 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-internal-routines-template.inc.h +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-internal-routines-template.inc.h @@ -116,6 +116,14 @@ const ecma_builtin_property_descriptor_t PROPERTY_DESCRIPTOR_LIST_NAME[] = prop_attributes, \ ECMA_ACCESSOR_ ## name ## c_getter_func_name \ }, +#define ACCESSOR_READ_WRITE(name, c_getter_func_name, c_setter_func_name, prop_attributes) \ + { \ + name, \ + ECMA_BUILTIN_PROPERTY_ACCESSOR_READ_WRITE, \ + prop_attributes, \ + ECMA_ACCESSOR_READ_WRITE (ECMA_ACCESSOR_ ## name ## c_getter_func_name, \ + ECMA_ACCESSOR_ ## name ## c_setter_func_name) \ + }, #else /* BUILTIN_CUSTOM_DISPATCH */ #define ROUTINE(name, c_function_name, args_number, length_prop_value) \ { \ @@ -145,6 +153,13 @@ const ecma_builtin_property_descriptor_t PROPERTY_DESCRIPTOR_LIST_NAME[] = prop_attributes, \ c_getter_func_name \ }, +#define ACCESSOR_READ_WRITE(name, c_getter_func_name, c_setter_func_name, prop_attributes) \ + { \ + name, \ + ECMA_BUILTIN_PROPERTY_ACCESSOR_READ_WRITE, \ + prop_attributes, \ + ECMA_ACCESSOR_READ_WRITE (c_getter_func_name, c_setter_func_name) \ + }, #endif /* !BUILTIN_CUSTOM_DISPATCH */ #define OBJECT_VALUE(name, obj_builtin_id, prop_attributes) \ { \ @@ -197,13 +212,6 @@ const ecma_builtin_property_descriptor_t PROPERTY_DESCRIPTOR_LIST_NAME[] = ECMA_ACCESSOR_READ_WRITE (getter_builtin_id, setter_builtin_id) \ }, #endif /* ENABLED (JERRY_ES2015) */ -#define ACCESSOR_READ_WRITE(name, c_getter_name, c_setter_name, prop_attributes) \ - { \ - name, \ - ECMA_BUILTIN_PROPERTY_ACCESSOR_READ_WRITE, \ - prop_attributes, \ - ECMA_ACCESSOR_READ_WRITE (ECMA_ACCESSOR_ ## name ## c_getter_name, ECMA_ACCESSOR_ ## name ## c_setter_name) \ - }, #include BUILTIN_INC_HEADER_NAME { LIT_MAGIC_STRING__COUNT, diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-object-prototype.c b/jerry-core/ecma/builtin-objects/ecma-builtin-object-prototype.c index b03fcd30b..86f70e1da 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-object-prototype.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-object-prototype.c @@ -16,6 +16,7 @@ #include "ecma-alloc.h" #include "ecma-builtin-helpers.h" #include "ecma-builtins.h" +#include "ecma-builtin-object.h" #include "ecma-conversion.h" #include "ecma-exceptions.h" #include "ecma-function-object.h" @@ -46,9 +47,11 @@ enum ECMA_OBJECT_PROTOTYPE_TO_STRING, ECMA_OBJECT_PROTOTYPE_VALUE_OF, ECMA_OBJECT_PROTOTYPE_TO_LOCALE_STRING, + ECMA_OBJECT_PROTOTYPE_GET_PROTO, ECMA_OBJECT_PROTOTYPE_IS_PROTOTYPE_OF, ECMA_OBJECT_PROTOTYPE_HAS_OWN_PROPERTY, ECMA_OBJECT_PROTOTYPE_PROPERTY_IS_ENUMERABLE, + ECMA_OBJECT_PROTOTYPE_SET_PROTO }; #define BUILTIN_INC_HEADER_NAME "ecma-builtin-object-prototype.inc.h" @@ -276,6 +279,14 @@ ecma_builtin_object_prototype_dispatch_routine (uint16_t builtin_routine_id, /** { ret_value = ecma_builtin_object_prototype_object_is_prototype_of (obj_p, arguments_list_p[0]); } + +#if ENABLED (JERRY_ES2015) + else if (builtin_routine_id == ECMA_OBJECT_PROTOTYPE_GET_PROTO) + { + ret_value = ecma_builtin_object_object_get_prototype_of (obj_p); + } +#endif /* ENABLED (JERRY_ES2015)*/ + else { ret_value = ecma_builtin_object_prototype_object_to_locale_string (obj_p); @@ -288,6 +299,13 @@ ecma_builtin_object_prototype_dispatch_routine (uint16_t builtin_routine_id, /** JERRY_ASSERT (builtin_routine_id >= ECMA_OBJECT_PROTOTYPE_HAS_OWN_PROPERTY); +#if ENABLED (JERRY_ES2015) + if (builtin_routine_id == ECMA_OBJECT_PROTOTYPE_SET_PROTO) + { + return ecma_builtin_object_object_set_proto (this_arg, arguments_list_p[0]); + } +#endif /* ENABLED (JERRY_ES2015)*/ + ecma_string_t *prop_name_p = ecma_op_to_prop_name (arguments_list_p[0]); if (prop_name_p == NULL) diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-object-prototype.inc.h b/jerry-core/ecma/builtin-objects/ecma-builtin-object-prototype.inc.h index 5da1290be..fccab0ff1 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-object-prototype.inc.h +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-object-prototype.inc.h @@ -27,6 +27,11 @@ OBJECT_VALUE (LIT_MAGIC_STRING_CONSTRUCTOR, ECMA_BUILTIN_ID_OBJECT, ECMA_PROPERTY_CONFIGURABLE_WRITABLE) +ACCESSOR_READ_WRITE (LIT_MAGIC_STRING__PROTO__, + ECMA_OBJECT_PROTOTYPE_GET_PROTO, + ECMA_OBJECT_PROTOTYPE_SET_PROTO, + ECMA_PROPERTY_FLAG_CONFIGURABLE) + /* Routine properties: * (property name, C routine name, arguments number or NON_FIXED, value of the routine's length property) */ ROUTINE (LIT_MAGIC_STRING_TO_STRING_UL, ECMA_OBJECT_PROTOTYPE_TO_STRING, 0, 0) diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-object.c b/jerry-core/ecma/builtin-objects/ecma-builtin-object.c index 9b55b5980..249ea287c 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-object.c +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-object.c @@ -221,6 +221,68 @@ ecma_builtin_object_object_set_prototype_of (ecma_value_t arg1, /**< routine's f return arg1; } /* ecma_builtin_object_object_set_prototype_of */ +/** + * The Object object's set __proto__ routine + * + * See also: + * ECMA-262 v6, B.2.2.1.2 + * + * @return ecma value + * Returned value must be freed with ecma_free_value. + */ +ecma_value_t +ecma_builtin_object_object_set_proto (ecma_value_t arg1, /**< routine's first argument */ + ecma_value_t arg2) /**< routine's second argument */ +{ + /* 1., 2. */ + if (ECMA_IS_VALUE_ERROR (ecma_op_check_object_coercible (arg1))) + { + return ECMA_VALUE_ERROR; + } + + /* 3. */ + if (!ecma_is_value_object (arg2) && !ecma_is_value_null (arg2)) + { + return ECMA_VALUE_UNDEFINED; + } + + /* 4. */ + if (!ecma_is_value_object (arg1)) + { + return ECMA_VALUE_UNDEFINED; + } + + ecma_object_t *obj_p = ecma_get_object_from_value (arg1); + ecma_value_t status; + + /* 5. */ +#if ENABLED (JERRY_ES2015_BUILTIN_PROXY) + if (ECMA_OBJECT_IS_PROXY (obj_p)) + { + status = ecma_proxy_object_set_prototype_of (obj_p, arg2); + + if (ECMA_IS_VALUE_ERROR (status)) + { + return status; + } + } + else + { +#endif /* ENABLED (JERRY_ES2015_BUILTIN_PROXY) */ + status = ecma_op_ordinary_object_set_prototype_of (obj_p, arg2); +#if ENABLED (JERRY_ES2015_BUILTIN_PROXY) + } +#endif /* ENABLED (JERRY_ES2015_BUILTIN_PROXY) */ + + if (ecma_is_value_false (status)) + { + return ecma_raise_type_error (ECMA_ERR_MSG ("Cannot set [[Prototype]].")); + } + + JERRY_ASSERT (ecma_is_value_true (status)); + + return ECMA_VALUE_UNDEFINED; +} /* ecma_builtin_object_object_set_proto */ #endif /* ENABLED (JERRY_ES2015) */ /** diff --git a/jerry-core/ecma/builtin-objects/ecma-builtin-object.h b/jerry-core/ecma/builtin-objects/ecma-builtin-object.h index e11e9506a..489e9831d 100644 --- a/jerry-core/ecma/builtin-objects/ecma-builtin-object.h +++ b/jerry-core/ecma/builtin-objects/ecma-builtin-object.h @@ -19,6 +19,10 @@ ecma_value_t ecma_builtin_object_object_get_prototype_of (ecma_object_t *obj_p); ecma_value_t ecma_builtin_object_object_set_prototype_of (ecma_value_t arg1, ecma_value_t arg2); + +ecma_value_t ecma_builtin_object_object_set_proto (ecma_value_t arg1, + ecma_value_t arg2); + ecma_value_t ecma_builtin_object_object_prevent_extensions (ecma_object_t *obj_p); ecma_value_t ecma_builtin_object_object_is_extensible (ecma_object_t *obj_p); diff --git a/jerry-core/lit/lit-magic-strings.inc.h b/jerry-core/lit/lit-magic-strings.inc.h index ca335cabc..f01f173f2 100644 --- a/jerry-core/lit/lit-magic-strings.inc.h +++ b/jerry-core/lit/lit-magic-strings.inc.h @@ -587,6 +587,7 @@ LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_MIN_VALUE_U, "MIN_VALUE") LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_TYPE_ERROR_UL, "TypeError") #endif LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_UNDEFINED_UL, "Undefined") +LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING__PROTO__, "__proto__") LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_ARGUMENTS, "arguments") #if ENABLED (JERRY_ES2015_BUILTIN_REFLECT) LIT_MAGIC_STRING_DEF (LIT_MAGIC_STRING_CONSTRUCT, "construct") diff --git a/jerry-core/lit/lit-magic-strings.ini b/jerry-core/lit/lit-magic-strings.ini index f4ec0af6b..75180dba1 100644 --- a/jerry-core/lit/lit-magic-strings.ini +++ b/jerry-core/lit/lit-magic-strings.ini @@ -240,6 +240,7 @@ LIT_MAGIC_STRING_MAX_VALUE_U = "MAX_VALUE" LIT_MAGIC_STRING_MIN_VALUE_U = "MIN_VALUE" LIT_MAGIC_STRING_TYPE_ERROR_UL = "TypeError" LIT_MAGIC_STRING_UNDEFINED_UL = "Undefined" +LIT_MAGIC_STRING__PROTO__ = "__proto__" LIT_MAGIC_STRING_ARGUMENTS = "arguments" LIT_MAGIC_STRING_DECODE_URI = "decodeURI" LIT_MAGIC_STRING_ENCODE_URI = "encodeURI" diff --git a/jerry-core/parser/js/js-parser-expr.c b/jerry-core/parser/js/js-parser-expr.c index 1cac8aff9..e3025914e 100644 --- a/jerry-core/parser/js/js-parser-expr.c +++ b/jerry-core/parser/js/js-parser-expr.c @@ -847,6 +847,10 @@ parser_parse_object_literal (parser_context_t *context_p) /**< context */ parser_stack_push_uint8 (context_p, PARSER_OBJECT_PROPERTY_START); #endif /* !ENABLED (JERRY_ES2015) */ +#if ENABLED (JERRY_ES2015) + bool proto_seen = false; +#endif /* ENABLED (JERRY_ES2015) */ + while (true) { lexer_expect_object_literal_id (context_p, LEXER_OBJ_IDENT_NO_OPTS); @@ -991,6 +995,20 @@ parser_parse_object_literal (parser_context_t *context_p) /**< context */ #endif /* ENABLED (JERRY_ES2015) */ default: { +#if ENABLED (JERRY_ES2015) + if ((context_p->token.lit_location.type == LEXER_IDENT_LITERAL + || context_p->token.lit_location.type == LEXER_STRING_LITERAL) + && lexer_compare_literal_to_string (context_p, "__proto__", 9)) + { + if (proto_seen) + { + parser_raise_error (context_p, PARSER_ERR_DUPLICATED_PROTO); + } + + proto_seen = true; + } +#endif /* ENABLED (JERRY_ES2015) */ + uint16_t literal_index = context_p->lit_object.index; #if ENABLED (JERRY_ES2015) diff --git a/jerry-core/parser/js/js-parser-util.c b/jerry-core/parser/js/js-parser-util.c index ff38dd19d..352656e5e 100644 --- a/jerry-core/parser/js/js-parser-util.c +++ b/jerry-core/parser/js/js-parser-util.c @@ -966,6 +966,10 @@ parser_error_to_string (parser_error_t error) /**< error code */ { return "for in-of loop variable declaration may not have an initializer."; } + case PARSER_ERR_DUPLICATED_PROTO: + { + return "Duplicate __proto__ fields are not allowed in object literals."; + } #endif /* ENABLED (JERRY_ES2015) */ case PARSER_ERR_DELETE_IDENT_NOT_ALLOWED: { diff --git a/jerry-core/parser/js/js-parser.h b/jerry-core/parser/js/js-parser.h index c455fff5e..329db0a3b 100644 --- a/jerry-core/parser/js/js-parser.h +++ b/jerry-core/parser/js/js-parser.h @@ -81,6 +81,7 @@ typedef enum PARSER_ERR_USE_STRICT_NOT_ALLOWED, /**< use strict directive is not allowed */ PARSER_ERR_YIELD_NOT_ALLOWED, /**< yield keyword is not allowed */ PARSER_ERR_FOR_IN_OF_DECLARATION, /**< variable declaration in for-in or for-of loop */ + PARSER_ERR_DUPLICATED_PROTO, /**< duplicated __proto__ fields are not allowed */ #endif /* ENABLED (JERRY_ES2015) */ PARSER_ERR_DELETE_IDENT_NOT_ALLOWED, /**< identifier delete is not allowed in strict mode */ PARSER_ERR_EVAL_CANNOT_ASSIGNED, /**< eval cannot be assigned in strict mode */ diff --git a/jerry-core/vm/vm.c b/jerry-core/vm/vm.c index cd225078c..11cab5e5b 100644 --- a/jerry-core/vm/vm.c +++ b/jerry-core/vm/vm.c @@ -18,6 +18,7 @@ #include "ecma-alloc.h" #include "ecma-array-object.h" #include "ecma-builtins.h" +#include "ecma-builtin-object.h" #include "ecma-comparison.h" #include "ecma-conversion.h" #include "ecma-exceptions.h" @@ -91,6 +92,15 @@ vm_op_get_value (ecma_value_t object, /**< base object */ else if (ecma_is_value_string (property)) { property_name_p = ecma_get_string_from_value (property); + +#if ENABLED (JERRY_ES2015) + if (JERRY_UNLIKELY (ecma_compare_ecma_string_to_magic_id (property_name_p, LIT_MAGIC_STRING__PROTO__))) + { + ecma_object_t *obj_p = ecma_get_object_from_value (object); + + return ecma_builtin_object_object_get_prototype_of (obj_p); + } +#endif /* ENABLED (JERRY_ES2015) */ } #if ENABLED (JERRY_ES2015) @@ -1586,6 +1596,13 @@ vm_loop (vm_frame_ctx_t *frame_ctx_p) /**< frame context */ } const int index = (int) (opcode_data >> VM_OC_NON_STATIC_SHIFT) - 2; + + if (JERRY_UNLIKELY (ecma_compare_ecma_string_to_magic_id (prop_name_p, LIT_MAGIC_STRING__PROTO__))) + { + result = ecma_builtin_object_object_set_proto (stack_top_p[index], left_value); + ecma_deref_ecma_string (prop_name_p); + goto free_both_values; + } #else /* !ENABLED (JERRY_ES2015) */ const int index = -1; #endif /* ENABLED (JERRY_ES2015) */ diff --git a/tests/jerry/es2015/object-prototype-proto.js b/tests/jerry/es2015/object-prototype-proto.js new file mode 100644 index 000000000..9166b7b43 --- /dev/null +++ b/tests/jerry/es2015/object-prototype-proto.js @@ -0,0 +1,58 @@ +// Copyright JS Foundation and other contributors, http://js.foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +try { + eval('var o = { __proto__ : 5, __proto__ : 5 }'); + assert(false); +} catch (e) { + assert(e instanceof SyntaxError); +} + +var f = function(){}; +assert((new f()).__proto__ === f.prototype); + +var o = {}; +o.__proto__ = Array.prototype; +assert(o instanceof Array); + +var obj = Object.create(null) +p = {}; +obj.__proto__ = p; +assert(Object.getPrototypeOf(obj) !== p); + +var Circle = function () {}; +var shape = {}; +var circle = new Circle(); + +shape.__proto__ = circle; + +assert(Object.getPrototypeOf(shape) === circle); +assert(shape.__proto__ === circle); + +assert(Object.prototype.hasOwnProperty('__proto__') === true); + +var desc = Object.getOwnPropertyDescriptor(Object.prototype,"__proto__"); +assert((desc && "get" in desc && "set" in desc && desc.configurable && !desc.enumerable) === true); + +assert((Object.getOwnPropertyNames(Object.prototype).indexOf('__proto__') > -1) === true); + +try { + shape.__proto__ = shape; + assert(false); +} catch (e) { + assert(e instanceof TypeError); +} + +var o2 = { ["__proto__"] : null }; +assert(o2.__proto__ === null);