Closed
Bug 1478503
Opened 6 years ago
Closed 6 years ago
Differential Testing: Different output message involving Object.preventExtensions
Categories
(Core :: JavaScript Engine: JIT, defect, P2)
Core
JavaScript Engine: JIT
Tracking
()
RESOLVED
FIXED
mozilla63
Tracking | Status | |
---|---|---|
firefox63 | --- | fixed |
People
(Reporter: gkw, Assigned: anba)
References
Details
(Keywords: testcase)
Attachments
(1 file, 2 obsolete files)
21.21 KB,
patch
|
anba
:
review+
|
Details | Diff | Splinter Review |
try {
x = [8, 0];
Object.preventExtensions(x);
x.sort(function() {
x.shift();
});
} catch (e) {
print(e);
};
print(x);
$ ./js-dbg-64-dm-linux-4c4abe35d808 --fuzzing-safe --no-threads --ion-eager testcase.js
8,0
$ ./js-dbg-64-dm-linux-4c4abe35d808 --fuzzing-safe --no-threads --baseline-eager --no-ion testcase.js
TypeError: can't define property 1: Array is not extensible
8
Tested this on m-c rev 4c4abe35d808.
My configure flags are:
'AR=ar' 'sh' './configure' '--enable-debug' '--enable-more-deterministic' '--with-ccache' '--enable-gczeal' '--enable-debug-symbols' '--disable-tests'
python -u -m funfuzz.js.compile_shell -b "--enable-debug --enable-more-deterministic" -r 4c4abe35d808
autobisectjs shows this is probably related to the following changeset:
The first bad revision is:
changeset: https://hg.mozilla.org/mozilla-central/rev/b46f3ba0c766
user: Jan de Mooij
date: Sat May 12 11:46:51 2018 +0200
summary: Bug 1460381 - Support sealed and non-extensible dense elements on native objects. r=anba
Jan, is bug 1460381 a likely regressor?
Flags: needinfo?(jdemooij)
Assignee | ||
Comment 1•6 years ago
|
||
Is the following analysis and its conclusion correct or can we do better somehow?
We only use MFallibleStoreElement when we store an element into a potentially non-extensible object [1]. But when an object is non-extensible, we shouldn't try to use this code [2] to store the element, just because there's enough elements-capacity. Instead we need to call into the VM and check that the object is actually extensible before adding the new element. We assumed non-extensible arrays always have a capacity equal to their initialized length, but as the test case shows, this is not necessarily the case when the array length is modified after calling PreventExtensions. So unless we always adjust the capacity to match the initialized length when an object is non-extensible, it seems inevitable to have this VM-call for MFallibleStoreElement.
I've also checked other callers which use ObjectElements::offsetOfCapacity() to see if there are similar issues. But that doesn't seem to be the case.
IonCacheIRCompiler::emitStoreDenseElement():
- Copied the comment from BaselineCacheIRCompiler [3], because it should also apply for IonCacheIRCompiler. (Is that correct?)
IonBuilder::inlineArrayPush():
- I don't think this change is strictly necessary, because we shouldn't end up inlining Array.prototype.push when the array is non-extensible, because calling Array.prototype.push on a non-extensible array always throws a TypeError, which means for IonBuilder the return-type is not a MIRType::Int32 [4]. But explicitly rejecting arrays with OBJECT_FLAG_NON_EXTENSIBLE_ELEMENTS seems more robust.
[1] https://searchfox.org/mozilla-central/rev/943a6cf31a96eb439db8f241ab4df25c14003bb8/js/src/jit/IonBuilder.cpp#9268,9273-9274
[2] https://searchfox.org/mozilla-central/rev/943a6cf31a96eb439db8f241ab4df25c14003bb8/js/src/jit/CodeGenerator.cpp#9504-9555
[3] https://searchfox.org/mozilla-central/rev/943a6cf31a96eb439db8f241ab4df25c14003bb8/js/src/jit/BaselineCacheIRCompiler.cpp#1416-1418
[4] https://searchfox.org/mozilla-central/rev/943a6cf31a96eb439db8f241ab4df25c14003bb8/js/src/jit/MCallOptimize.cpp#819-820
Attachment #8995138 -
Flags: feedback?(jdemooij)
Comment 2•6 years ago
|
||
Comment on attachment 8995138 [details] [diff] [review]
bug1478503.patch
Review of attachment 8995138 [details] [diff] [review]:
-----------------------------------------------------------------
Thanks for looking at this! Patch LGTM. There is a perf issue when the object *might* be non-extensible but it's actually extensible, then we can no longer bump the length inline and now have to call into the VM. We could avoid this by checking the BaseShape's NON_EXTENSIBLE flag, but that's probably not worth it.
::: js/src/jit-test/tests/basic/non-extensible-elements9.js
@@ +19,5 @@
> +for (var i = 0; i < 15; ++i)
> + testNonExtensibleStoreFallibleT();
> +
> +// Repeat testNonExtensibleStoreFallibleT for the MIRType::Value specialization.
> +function testNonExtensibleStoreFallibleV() {
Should this function have an |i| argument? It works now because |i| is a global variable, but the caller also passes |i| as argument.
::: js/src/jit/IonCacheIRCompiler.cpp
@@ +1915,5 @@
> masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch1);
>
> + // Assert no copy-on-write elements are present. Note that this stub is
> + // not attached for non-extensible objects, so the shape guard ensures
> + // there are no sealed or frozen elements.
Yes this is correct, https://searchfox.org/mozilla-central/rev/033d45ca70ff32acf04286244644d19308c359d5/js/src/jit/CacheIR.cpp#3634-3635
Attachment #8995138 -
Flags: feedback?(jdemooij) → review+
Comment 3•6 years ago
|
||
Another option is for ArrayObject::setLength{Int32} to call shrinkCapacityToInitializedLength() if !isExtensible() right? Or move this into the callers and have setLength{Int32} assert this. Do you think that's worth trying?
Flags: needinfo?(jdemooij)
Assignee | ||
Comment 4•6 years ago
|
||
For the alternative patch we always need to adjust the capacity when modifying the initialized length of a non-extensible object.
Most callers which modify `ObjectElements::initializedLength` are statically known to operate on extensible objects, so we don't need to worry about adjusting the capacity for them. To enforce this I've added additional assertions in all(?) functions which potentially modify the initialized length.
NativeObject::setDenseInitializedLength() was then changed, so it can only be called on extensible objects and NativeObject::setDenseInitializedLengthMaybeNonExtensible() was added for the case when modifying the initialized length of a potentially non-extensible object.
And finally also changed IonBuilder::inlineArrayPopShift() to disallow inlining Array.prototype.pop() on potentially non-extensible objects, because when the object is non-extensible, we'd need to adjust the capacity when removing the last element of the array.
Attachment #8996419 -
Flags: feedback?(jdemooij)
Comment 5•6 years ago
|
||
Comment on attachment 8996419 [details] [diff] [review]
bug1478503-alternative.patch
Review of attachment 8996419 [details] [diff] [review]:
-----------------------------------------------------------------
Thanks a lot! I'm not sure which one I like better: this one avoids the potential perf issue and JIT changes but it does add slight complexity to the NativeObject methods (however I like the isExtensible() assertions you added, it will catch problems there and fuzzers are good at finding such issues). I think I prefer this version but if you want to land the other patch that's also fine with me.
::: js/src/vm/NativeObject.h
@@ +1268,5 @@
> +
> + void setDenseInitializedLengthMaybeNonExtensible(JSContext* cx, uint32_t length) {
> + setDenseInitializedLengthInternal(length);
> + if (!isExtensible())
> + shrinkCapacityToInitializedLength(cx);
It would be nice to assert this invariant in some strategic location, to make sure we don't break it elsewhere (in JIT code or something). Something like:
MOZ_ASSERT_IF(!isExtensible(), getDenseInitializedLength() == getDenseCapacity());
Maybe in getDenseInitializedLength? We just have to be careful to avoid infinite recursion...
Attachment #8996419 -
Flags: feedback?(jdemooij) → review+
Comment 6•6 years ago
|
||
André, forgot to ask for checkin?
Assignee | ||
Updated•6 years ago
|
Assignee: nobody → andrebargull
Status: NEW → ASSIGNED
Flags: needinfo?(andrebargull)
Assignee | ||
Comment 7•6 years ago
|
||
Okay, then let's go with the second version and wait for the fuzzers to find new and exciting ways to break the internal invariants. :-)
Also added the assertion proposed in comment #5 to NativeObject::getDenseInitializedLength(). Carrying r+.
Attachment #8995138 -
Attachment is obsolete: true
Attachment #8996419 -
Attachment is obsolete: true
Attachment #8997155 -
Flags: review+
Assignee | ||
Comment 8•6 years ago
|
||
Try: https://treeherder.mozilla.org/#/jobs?repo=try&revision=dd030479d010982a9b60d48d39e8ba9731f4da97
Keywords: checkin-needed
Pushed by archaeopteryx@coole-files.de:
https://hg.mozilla.org/integration/mozilla-inbound/rev/c543368b25a6
Shrink capacity when modifying length on a non-extensible array. r=jandem
Keywords: checkin-needed
Comment 10•6 years ago
|
||
bugherder |
Status: ASSIGNED → RESOLVED
Closed: 6 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla63
You need to log in
before you can comment on or make changes to this bug.
Description
•