From b1441a47b164f255e1613224c83e2ff68394b780 Mon Sep 17 00:00:00 2001 From: davidot Date: Fri, 25 Jun 2021 16:57:12 +0200 Subject: [PATCH] LibJS: Allow setting the length of an object with prototype Array Before this it would always go through the native setter thus modifying the array but now you can set length to anything --- Userland/Libraries/LibJS/Runtime/Array.cpp | 39 +++++++++--- .../builtins/Array/array-length-setter.js | 60 +++++++++++++++++++ 2 files changed, 92 insertions(+), 7 deletions(-) diff --git a/Userland/Libraries/LibJS/Runtime/Array.cpp b/Userland/Libraries/LibJS/Runtime/Array.cpp index 232a5a42e2..314688df30 100644 --- a/Userland/Libraries/LibJS/Runtime/Array.cpp +++ b/Userland/Libraries/LibJS/Runtime/Array.cpp @@ -65,25 +65,50 @@ Array* Array::typed_this(VM& vm, GlobalObject& global_object) JS_DEFINE_NATIVE_GETTER(Array::length_getter) { - auto* array = typed_this(vm, global_object); - if (!array) + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) return {}; - return Value(array->indexed_properties().array_like_size()); + + // TODO: could be incorrect if receiver/this_value is fixed or changed + if (!this_object->is_array()) { + Value val = this_object->get_own_property(vm.names.length.to_string_or_symbol(), this_object); + if (vm.exception()) + return {}; + return val; + } + + return Value(this_object->indexed_properties().array_like_size()); } JS_DEFINE_NATIVE_SETTER(Array::length_setter) { - auto* array = typed_this(vm, global_object); - if (!array) + auto* this_object = vm.this_value(global_object).to_object(global_object); + if (!this_object) return; + + // TODO: could be incorrect if receiver/this_value is fixed or changed + if (!this_object->is_array()) { + this_object->define_property(vm.names.length.to_string_or_symbol(), value, default_attributes); + if (vm.exception()) + return; + return; + } + auto length = value.to_number(global_object); if (vm.exception()) return; - if (length.is_nan() || length.is_infinity() || length.as_double() < 0) { + + u32 val = length.as_double(); + + if (val != length.as_double() + || length.is_nan() + || length.is_infinity() + || length.as_double() < 0 + || length.as_double() > NumericLimits::max()) { vm.throw_exception(global_object, ErrorType::InvalidLength, "array"); return; } - array->indexed_properties().set_array_like_size(length.as_double()); + this_object->indexed_properties().set_array_like_size(val); } } diff --git a/Userland/Libraries/LibJS/Tests/builtins/Array/array-length-setter.js b/Userland/Libraries/LibJS/Tests/builtins/Array/array-length-setter.js index 14a8269110..1ac49e125b 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Array/array-length-setter.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Array/array-length-setter.js @@ -55,3 +55,63 @@ describe("normal behavior", () => { expect(a[1]).toEqual(2); }); }); + +describe("behavior when obj has Array prototype", () => { + function ArrExtend() {} + ArrExtend.prototype = [10, 11, 12]; + + test("Has the properties from prototype", () => { + var arr = new ArrExtend(); + expect(arr.length).toEqual(3); + expect(arr[0]).toEqual(10); + expect(arr[1]).toEqual(11); + expect(arr[2]).toEqual(12); + }); + + test("Can override length to any value", () => { + [null, "Hello friends :^)", -6, 0].forEach(value => { + var arr = new ArrExtend(); + arr.length = value; + expect(arr.length).toEqual(value); + + // should not wipe high values + expect(arr[0]).toEqual(10); + expect(arr[1]).toEqual(11); + expect(arr[2]).toEqual(12); + }); + }); + + test("Can call array methods", () => { + var arr = new ArrExtend(); + arr.push(1); + expect(arr.length).toEqual(4); + expect(arr[3]).toEqual(1); + }); + + test("If length overwritten uses that value", () => { + [null, "Hello friends :^)", -6, 0].forEach(value => { + var arr = new ArrExtend(); + arr.length = value; + expect(arr.length).toEqual(value); + + arr.push(99); + expect(arr.length).toEqual(1); + expect(arr[0]).toEqual(99); + + // should not wipe higher value + expect(arr[1]).toEqual(11); + expect(arr[2]).toEqual(12); + + arr.push(100); + + expect(arr.length).toEqual(2); + expect(arr[1]).toEqual(100); + + arr.length = 0; + // should not wipe values since we are not an array + expect(arr[0]).toEqual(99); + expect(arr[1]).toEqual(100); + expect(arr[2]).toEqual(12); + }); + }); +});