-
Notifications
You must be signed in to change notification settings - Fork 29.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
util: Handle null prototype on inspect #22331
Conversation
test/parallel/test-util-inspect.js
Outdated
@@ -1487,7 +1487,6 @@ assert.strictEqual(util.inspect('"\'${a}'), "'\"\\'${a}'"); | |||
[() => {}, '[Function]'], | |||
[[1, 2], '[ 1, 2 ]'], | |||
[[, , 5, , , , ], '[ <2 empty items>, 5, <3 empty items> ]'], | |||
[{ a: 5 }, '{ a: 5 }'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had to remove this test case as this we are checking the message for both cases (with prototype
and without prototype
). So it was failing with this change. May be I need to do the same test case outside this loop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None of these tests should work as before if we detect a null prototype on each of these data types. So the test has to be rewritten anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good start but it should not only check for objects but for any type that we explicitly test for and mark it as having a null prototype.
test/parallel/test-util-inspect.js
Outdated
@@ -1487,7 +1487,6 @@ assert.strictEqual(util.inspect('"\'${a}'), "'\"\\'${a}'"); | |||
[() => {}, '[Function]'], | |||
[[1, 2], '[ 1, 2 ]'], | |||
[[, , 5, , , , ], '[ <2 empty items>, 5, <3 empty items> ]'], | |||
[{ a: 5 }, '{ a: 5 }'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None of these tests should work as before if we detect a null prototype on each of these data types. So the test has to be rewritten anyway.
I think this solves the superficial case of #22141 where a prototype is null, but not the general problem of My 2¢. |
@TimothyGu Yes, but when using
I'm not sure, what do you mean by:
Am I missing something here? |
@antsman whatever changes you make here should be made to assertion, not inspection, is Timothy's point. I feel the same way. |
I guess the real problem here is that, when assert fails with May be I'm wrong here too. Open for thoughts. |
@TimothyGu @devsnek for me this is not about
|
I would still say that a solution that works for all prototypes rather than special casing |
@TimothyGu that is the plan. Currently all prototypes are handled in inspect besides the null prototype. |
For reference, Chrome DevTools use a pseudo- |
@BridgeAR @TimothyGu Just an update. I'm working on the same for handling all prototypes. Will update the PR in a day or two.. :) |
b6fccaa
to
7ddca64
Compare
@BridgeAR I have implemented the check on all the types. Can you please have a look at it? |
7ddca64
to
cd98c73
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is already looking really good! Thanks a lot for looking into it!
Just a few comments and we should make sure the null prototype is also printed in case a tag exists but no constructor.
test/parallel/test-util-inspect.js
Outdated
[new SharedArrayBuffer(2), 'SharedArrayBuffer { byteLength: undefined }'] | ||
'[DataView: null prototype] {\n byteLength: undefined,\n ' + | ||
'byteOffset: undefined,\n buffer: undefined }'], | ||
[new SharedArrayBuffer(2), '[SharedArrayBuffer : null prototype] ' + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like a typo after SharedArrayBuffer
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed all other comments, but I couldn't able to see any typo 🤔 Sorry could you please explain me, whats wrong here? cc @BridgeAR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my comment above :-)
test/parallel/test-util-inspect.js
Outdated
assert.strictEqual( | ||
util.inspect(Object.setPrototypeOf(value, null)), | ||
expected | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add https://github.com/nodejs/node/blob/cd98c735e879e1218b1c8514ab4218efa841bba4/test/parallel/test-util-inspect.js#L1616-L1620 as well. Otherwise important things would not be tested anymore.
value.foo = 'bar'; | ||
assert.notStrictEqual(util.inspect(value), expected); | ||
delete value.foo; | ||
value[Symbol('foo')] = 'yeah'; | ||
assert.notStrictEqual(util.inspect(value), expected); | ||
}); | ||
|
||
[ | ||
[new Set([1, 2]), '[Set: null prototype] { 1, 2 }'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you please also add Arrays, Dataviews and ArrayBuffers? Errors, Dates and regular expressions should be handled in a different PR as they do not show subclassing at all and that should be fixed as well.
lib/util.js
Outdated
function checkNullPrototype(constructor, type) { | ||
if (constructor === '') | ||
return `[${type}: null prototype]`; | ||
return constructor; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please inline this check into the getPrefix
check. That way the code should be simpler and pretty straight forward.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, thought about it. Will work on that and update the PR.
cd98c73
to
09c796c
Compare
lib/util.js
Outdated
@@ -752,9 +784,11 @@ function formatValue(ctx, value, recurseTimes) { | |||
// Fast path for ArrayBuffer and SharedArrayBuffer. | |||
// Can't do the same for DataView because it has a non-primitive | |||
// .buffer property that we need to recurse for. | |||
let prefix = getPrefix(constructor, tag); | |||
const arrayType = isArrayBuffer(value) ? 'ArrayBuffer ' : | |||
'SharedArrayBuffer '; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Due to the changes the types have to be 'ArrayBuffer'
and 'SharedArrayBuffer'
(without the whitespace at the end).
test/parallel/test-util-inspect.js
Outdated
[new SharedArrayBuffer(2), 'SharedArrayBuffer { byteLength: undefined }'] | ||
'[DataView: null prototype] {\n byteLength: undefined,\n ' + | ||
'byteOffset: undefined,\n buffer: undefined }'], | ||
[new SharedArrayBuffer(2), '[SharedArrayBuffer : null prototype] ' + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my comment above :-)
lib/util.js
Outdated
if (prefix === '') { | ||
prefix = isArrayBuffer(value) ? 'ArrayBuffer ' : 'SharedArrayBuffer '; | ||
prefix = arrayType; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This condition should now be obsolete.
lib/util.js
Outdated
newVal = new clazz(value.length || 0); | ||
} else if (isTypedArray(value)) { | ||
const clazz = findTypedConstructor(value) || Uint8Array; | ||
let clazz; | ||
if (Object.getPrototypeOf(value)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the value has a prototype, we do not have to search for it anymore. Therefore it would be best to write this as:
const clazz = Object.getPrototypeOf(value) || findTypedConstructor(value) || clazzWithNullPrototype(Uint8Array, 'Uint8Array')
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand this correctly. If we write like the following:
const clazz = Object.getPrototypeOf(value) || findTypedConstructor(value) || clazzWithNullPrototype(Uint8Array, 'Uint8Array')
when there is no prototype, then findTypedConstructor
will definitely return the typed array constructor. So we won't reach clazzWithNullPrototype
(when there is no prototype) -- missing the subclass stuff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are correct, findTypedConstructor
should always return the actual type.
Therefore it should likely be:
let clazz = Object.getPrototypeOf(value);
if (!clazz) {
const constructor = findTypedConstructor(value);
clazz = clazzWithNullProrotype(constructor, constructor.name);
}
lib/util.js
Outdated
} | ||
return `[${fallback}: null prototype] `; | ||
} | ||
|
||
if (constructor !== '') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This condition tests for constructor !== ''
so instead of testing in another if for the other case, we can just move the code above below this if. In that case we already know, that the constructor is an empty string and don't have to check again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well this is true, but the line:
return `[${fallback}: null prototype] `;
definitely needs a check whether a constructor
is empty or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I mean is:
if (constructor !== '') {
... as before ...
}
if (tag !== '' && fallback !== 'tag') {
return `[${fallback}: null prototype] [${tag}] `;
}
return `[${fallback}: null prototype] `;
That way we are sure the constructor is definitely empty.
@BridgeAR Sorry to trouble you, may be you missed my last comments? :) |
09c796c
to
9dc530c
Compare
Ping @BridgeAR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Great work :-)
lib/util.js
Outdated
@@ -497,20 +497,19 @@ function getConstructorName(obj) { | |||
} | |||
|
|||
function getPrefix(constructor, tag, fallback) { | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking nit: adding the line seems obsolete.
lib/util.js
Outdated
// Creates a subclass and name | ||
// the constructor as `${clazz} : null prototype` | ||
function clazzWithNullPrototype(clazz, name) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking nit: I would remove the newline here.
test/parallel/test-util-inspect.js
Outdated
'[DataView: null prototype] {\n byteLength: undefined,\n ' + | ||
'byteOffset: undefined,\n buffer: undefined }'], | ||
[new SharedArrayBuffer(2), '[SharedArrayBuffer: null prototype] ' + | ||
'{ byteLength: undefined }'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: this now seems to be a duplicated entry with the test below. Since the test below tests more than this one, it would be best to move this entry (or just keep it).
9dc530c
to
9112c00
Compare
This makes sure the `null` prototype is always detected properly.
4f52f0f
to
215700c
Compare
@BridgeAR I have moved the logic to |
@BridgeAR Anything else blocking this PR from getting merged? |
This makes sure the `null` prototype is always detected properly. PR-URL: nodejs#22331 Fixes: nodejs#22141 Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: John-David Dalton <[email protected]>
Landed in f4e4ef5 🎉 @antsmartian thanks for sticking to it and being patient! |
@BridgeAR Sorry to ping on the closed thread. If you remember, one of the early comments you mentioned that:
I was looking to implement for
Also as said in the issue, we need to handle
But looks like |
@antsmartian feel free to chat with me on IRC (even though I am not always around). It is indeed not a good idea to mutate the input at all. Instead, we should be able to just create the output as we expect it to look like and we only change to change a single string: the stack after accessing it. We also already have the constructor name and don't have to get it again. So it should be similar to: function formatError(value, constructorName) {
if (value.stack) {
let stack = value.stack;
if (!stack.startsWith(constructorName)) { // Note: this check is likely not sufficient.
return stack.replace(..., constructorName);
}
return stack;
}
return errorToString(value);
} |
@BridgeAR Thanks. (can you just tell me your IRC handle, I searched for bridgear, doesn't look like its correct) |
This makes sure the prototype is always detected properly. PR-URL: nodejs#22331 Fixes: nodejs#22141 Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: John-David Dalton <[email protected]>
This makes sure the prototype is always detected properly. Backport-PR-URL: #23655 PR-URL: #22331 Fixes: #22141 Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: John-David Dalton <[email protected]>
This makes sure the prototype is always detected properly. Backport-PR-URL: #23655 PR-URL: #22331 Fixes: #22141 Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: John-David Dalton <[email protected]>
This makes sure the prototype is always detected properly. Backport-PR-URL: #23655 PR-URL: #22331 Fixes: #22141 Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: John-David Dalton <[email protected]>
Fixes : #22141
The implementation and texts are taken from the discussion over here:
#22141 (comment), #22141 (comment)
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes