Skip to content
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

repl: fix crash with large buffer tab completion #13817

Closed

Conversation

XadillaX
Copy link
Contributor

@XadillaX XadillaX commented Jun 20, 2017

If the buffer or array is too large to completion, make a dummy smallest
substitute object for it and emit a warning.

Fixes: #3136

Checklist
  • make -j4 test (UNIX)
  • tests and/or benchmarks are included
  • commit message follows commit guidelines
Affected core subsystem(s)

repl

If the buffer or array is too large to completion, make a dummy smallest
substitute object for it and emit a warning.

Fixes: nodejs#3136
@nodejs-github-bot nodejs-github-bot added the repl Issues and PRs related to the REPL subsystem. label Jun 20, 2017
lib/repl.js Outdated
warning.type,
undefined,
undefined,
true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no boolean fifth argument to process.emitWarning() so I'm not sure what this is doing.

Copy link
Contributor Author

@XadillaX XadillaX Jun 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refer https://github.com/nodejs/node/blob/v8.1.2/lib/internal/process/warning.js#L153

if it's true, it won't be emitted on next tick.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh... forgot about that change. It wasn't added to the docs and wasn't very keen on it in the first place. In this case, why does the warning need to be emitted immediately?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jasnell, For aesthetics.

With last parameter false:

image

With immediately:

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK.

lib/repl.js Outdated
warning.message =
'Instance is too large that the completion may missing ' +
'some customized properties.';
warning.type = 'REPLWarning';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just emit the warning here instead of building this warning object?

lib/repl.js Outdated
@@ -732,10 +751,12 @@ function complete(line, callback) {
}
}

var self = this;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would prefer not to introduce a new var self = this into this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I need use this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please use an arrow function so that you don't need the var self = this trick?

@XadillaX
Copy link
Contributor Author

@jasnell I've updated the code and added some test cases.

lib/repl.js Outdated
@@ -689,8 +690,31 @@ function intFilter(item) {
return /^[A-Za-z_$]/.test(item);
}

const DEFAULT_PROPERTIES = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if these should be uppercased. (I'd prefer them not to be, fwiw.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better by using uppercase on such a constant.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear: I'm fine with it either way, it just looks a bit weird to me in this specific case, especially the uppercased properties, not the object name itself.

lib/repl.js Outdated
@@ -732,10 +751,12 @@ function complete(line, callback) {
}
}

var self = this;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please use an arrow function so that you don't need the var self = this trick?

@@ -23,6 +23,7 @@

const common = require('../common');
const assert = require('assert');
const Buffer = require('buffer').Buffer;
Copy link
Contributor

@aqrln aqrln Jun 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure you don't need to import it in the test.

lib/repl.js Outdated
if (mayBeLargeObject(obj) && obj.length > 1e6) {
this._writeToOutput('\r\n');
process.emitWarning(
'Instance is too large that the completion may missing ' +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like "so" might be better than "that" here (but I'm not a native speaker).


common.hijackStderr(common.mustCall((err) => {
process.nextTick(function() {
assert.ok(/REPLWarning: Instance is too large that the/.test(err));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please test the full warning message here?

}

common.hijackStderr(common.mustCall((err) => {
process.nextTick(function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small and not essential nit: could you please use an arrow function here to be consistent with the surrounding code?

lib/repl.js Outdated
this._writeToOutput('\r\n');
process.emitWarning(
'Instance is too large that the completion may missing ' +
'some customized properties.',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And "custom" might be better than "customized" here. (As for me, "customized" implies properties that have been changed, not necessarily added.)

@XadillaX XadillaX force-pushed the fix/repl-large-buffer-completion branch from 221000d to 876ab6e Compare June 21, 2017 02:52
@@ -305,6 +305,36 @@ testMe.complete('.b', common.mustCall((error, data) => {
assert.deepStrictEqual(data, [['break'], 'b']);
}));

// tab completion for large buffer
const warningRegEx =
/\(node:\d+\) REPLWarning: Instance is too large so the completion may missing some custom properties\./;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is longer than 80 characters, but the linter passes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall I split it into two lines?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say yes.

@aqrln
Copy link
Contributor

aqrln commented Jun 21, 2017

Copy link
Member

@TimothyGu TimothyGu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should report this issue to V8. Specifically, Object.keys throws an error instead of crashing, which seems what Object.getOwnPropertyNames should do as well

Object.keys(Buffer.alloc(2147483647))
       ^

RangeError: Invalid array length
    at Function.keys (<anonymous>)
    at [eval]:1:8

lib/repl.js Outdated
@@ -689,8 +690,31 @@ function intFilter(item) {
return /^[A-Za-z_$]/.test(item);
}

const defaultProperties = {
ARRAY: Object.getOwnPropertyNames([]).filter(intFilter),
BUFFER: Object.getOwnPropertyNames(Buffer.alloc(1)).filter(intFilter)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not Buffer.alloc(0)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I don't know that we can alloc a zero-size buffer.

lib/repl.js Outdated
};

function mayBeLargeObject(obj) {
return (Array.isArray(obj) || Buffer.isBuffer(obj));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't address other TypedArray types.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, currently, Buffer.prototype passes the Buffer.isBuffer test. On the other hand, getting Buffer.prototype.length will throw the following error:

> Buffer.prototype.length
TypeError: Method get TypedArray.prototype.length called on incompatible receiver [object Object]
    at Uint8Array.get length [as length] (<anonymous>)
    at repl:1:17
    at ContextifyScript.Script.runInThisContext (vm.js:44:33)
    at REPLServer.defaultEval (repl.js:239:29)
    at bound (domain.js:301:14)
    at REPLServer.runBound [as eval] (domain.js:314:12)
    at REPLServer.onLine (repl.js:433:10)
    at emitOne (events.js:120:20)
    at REPLServer.emit (events.js:210:7)
    at REPLServer.Interface._onLine (readline.js:278:10)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, currently, Buffer.prototype passes the Buffer.isBuffer test.

How about obj instanceof Buffer?

lib/repl.js Outdated
function filteredOwnPropertyNames(obj) {
if (!obj) return [];
if (mayBeLargeObject(obj) && obj.length > 1e6) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use a constant for 1e6 like const ARRAY_LENGTH_THRESHOLD?

lib/repl.js Outdated
'REPLWarning',
undefined,
undefined,
true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I'm okay with not showing the warning. Autocomplete is not a feature that necessarily has to work 100% of the time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if there's no warning, I think after this PR landed, developers use Node.js may open several issue about this bug "why autocompletion is wrong", though it's not exactly a bug.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@XadillaX fwiw, the autocompletion in REPL is far from perfect and I, as a user, would not and do not expect it to always show all the completions (though it would be an awesome thing). Even IDEs don't do that reliably for JavaScript.

Copy link
Contributor Author

@XadillaX XadillaX Jun 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aqrln The point is:

> var ele = Buffer.alloc(1);
> ele.biu = 1;
> ...
> ele.<tab>
ele.__defineGetter__      ele.__defineSetter__      ele.__lookupGetter__      ele.__lookupSetter__
ele.__proto__             ele.constructor           ele.hasOwnProperty        ele.isPrototypeOf
ele.propertyIsEnumerable  ele.toLocaleString        ele.toString              ele.valueOf

...

ele.biu

> var ele = Buffer.alloc(1e6 + 1); ele.biu = 1;
> ele.<tab>
(node:3635) REPLWarning: Instance is too large so the completion may missing some custom properties.

ele.__defineGetter__      ele.__defineSetter__      ele.__lookupGetter__      ele.__lookupSetter__
ele.__proto__             ele.constructor           ele.hasOwnProperty        ele.isPrototypeOf
ele.propertyIsEnumerable  ele.toLocaleString        ele.toString              ele.valueOf

...

Without warning, the developers may be confused that why there's no biu, that completion is what truly they want to find.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. In that case, the message could be clearer:

The current object has too many own properties. Completion output may be truncated.

or

The current array, Buffer, or TypedArray has too many entries. Certain properties may be missing from completion output.

@XadillaX
Copy link
Contributor Author

We should report this issue to V8. Specifically, Object.keys throws an error instead of crashing, which seems what Object.getOwnPropertyNames should do as well

I think we may ask V8 to provide an API to get non-enumarable properties.

lib/repl.js Outdated
// `Buffer.prototype` passes the `Buffer.isBuffer` and
// `instanceof Uint8Array`.
//
// Refs: https://github.com/nodejs/node/pull/11961
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will still throw an error on e.g. Object.create(Uint8Array.prototype).

Instead of all the checks below, it could be implemented simply and reliably as:

const { isTypedArray } = process.binding('util');

function maybeLargeObject(obj) {
  return Array.isArray(obj) || isTypedArray(obj);
}

(Buffers pass the isTypedArray test.)

lib/repl.js Outdated
[ Float32Array,
Object.getOwnPropertyNames(new Float32Array()).filter(intFilter) ],
[ Float64Array,
Object.getOwnPropertyNames(new Float64Array()).filter(intFilter) ]
Copy link
Member

@TimothyGu TimothyGu Jun 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of all the types enumerated, only plain arrays have own properties by default ('length'). No TypedArray objects have own properties by default.

lib/repl.js Outdated
'REPLWarning',
undefined,
undefined,
true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. In that case, the message could be clearer:

The current object has too many own properties. Completion output may be truncated.

or

The current array, Buffer, or TypedArray has too many entries. Certain properties may be missing from completion output.

@XadillaX XadillaX force-pushed the fix/repl-large-buffer-completion branch from 1093500 to 573e252 Compare June 21, 2017 11:40
@XadillaX
Copy link
Contributor Author

XadillaX commented Jun 22, 2017

I think this PR needs CI again.

@aqrln
Copy link
Contributor

aqrln commented Jun 22, 2017

@XadillaX
Copy link
Contributor Author

could this be landed now?

if (Array.isArray(obj)) {
return obj.length > ARRAY_LENGTH_THRESHOLD ? ['length'] : null;
} else if (utilBinding.isTypedArray(obj)) {
return obj.length > ARRAY_LENGTH_THRESHOLD ? [] : null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But typed arrays have length too, don't they?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypedArrays' length is implemented as a getter on the prototype instead of an own property, as is the case with Arrays.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timothy is right.

Copy link
Contributor

@aqrln aqrln left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@XadillaX
Copy link
Contributor Author

XadillaX commented Jul 3, 2017

So I think this PR could be landed now. @jasnell

@XadillaX
Copy link
Contributor Author

XadillaX commented Jul 7, 2017

For landing, whom to /ping?

@TimothyGu
Copy link
Member

TimothyGu commented Jul 7, 2017

Replaced this._writeToOutput (a readline private API) with this.outputStream.write, and landed in 7d7ccf0.

@TimothyGu TimothyGu closed this Jul 7, 2017
TimothyGu pushed a commit that referenced this pull request Jul 7, 2017
If the buffer or array is too large to completion, make a dummy smallest
substitute object for it and emit a warning.

PR-URL: #13817
Fixes: #3136
Reviewed-By: Timothy Gu <timothygu99@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Alexey Orlenko <eaglexrlnk@gmail.com>
@aqrln
Copy link
Contributor

aqrln commented Jul 7, 2017

@XadillaX IRC is a good place for such pings too. And sorry that it took a while, as a person who constantly pinged people for CI and landing some months ago, I know how frustrating it feels sometimes. FWIW, I thought about landing it, but then I was on a little vacation and I did no GitHub, and today I've opened this tab just to see that Timothy beat me to it. I hope that soon you'll be able to land your patches yourself :)

@XadillaX XadillaX mentioned this pull request Jul 9, 2017
addaleax pushed a commit that referenced this pull request Jul 11, 2017
If the buffer or array is too large to completion, make a dummy smallest
substitute object for it and emit a warning.

PR-URL: #13817
Fixes: #3136
Reviewed-By: Timothy Gu <timothygu99@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Alexey Orlenko <eaglexrlnk@gmail.com>
addaleax pushed a commit that referenced this pull request Jul 18, 2017
If the buffer or array is too large to completion, make a dummy smallest
substitute object for it and emit a warning.

PR-URL: #13817
Fixes: #3136
Reviewed-By: Timothy Gu <timothygu99@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Alexey Orlenko <eaglexrlnk@gmail.com>
Fishrock123 pushed a commit that referenced this pull request Jul 19, 2017
If the buffer or array is too large to completion, make a dummy smallest
substitute object for it and emit a warning.

PR-URL: #13817
Fixes: #3136
Reviewed-By: Timothy Gu <timothygu99@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Alexey Orlenko <eaglexrlnk@gmail.com>
@MylesBorins
Copy link
Contributor

Should this be backported to v6.x-staging? If yes please follow the guide and raise a backport PR, if no let me know or add the dont-land-on label.

The test will need to be rewritten as common.hijackStderr doesn't exist on v6.x

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
repl Issues and PRs related to the REPL subsystem.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

oom/core dump in repl with buffer property tab completion
6 participants