Skip to content

Commit

Permalink
repl: fix crash with large buffer tab completion
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
XadillaX authored and TimothyGu committed Jul 7, 2017
1 parent a965067 commit 7d7ccf0
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 5 deletions.
37 changes: 32 additions & 5 deletions lib/repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -689,8 +689,33 @@ function intFilter(item) {
return /^[A-Za-z_$]/.test(item);
}

const ARRAY_LENGTH_THRESHOLD = 1e6;

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

return null;
}

function filteredOwnPropertyNames(obj) {
if (!obj) return [];
const fakeProperties = mayBeLargeObject(obj);
if (fakeProperties !== null) {
this.outputStream.write('\r\n');
process.emitWarning(
'The current array, Buffer or TypedArray has too many entries. ' +
'Certain properties may be missing from completion output.',
'REPLWarning',
undefined,
undefined,
true);

return fakeProperties;
}
return Object.getOwnPropertyNames(obj).filter(intFilter);
}

Expand Down Expand Up @@ -844,9 +869,11 @@ function complete(line, callback) {
if (this.useGlobal || vm.isContext(this.context)) {
var contextProto = this.context;
while (contextProto = Object.getPrototypeOf(contextProto)) {
completionGroups.push(filteredOwnPropertyNames(contextProto));
completionGroups.push(
filteredOwnPropertyNames.call(this, contextProto));
}
completionGroups.push(filteredOwnPropertyNames(this.context));
completionGroups.push(
filteredOwnPropertyNames.call(this, this.context));
addStandardGlobals(completionGroups, filter);
completionGroupsLoaded();
} else {
Expand All @@ -866,13 +893,13 @@ function complete(line, callback) {
}
} else {
const evalExpr = `try { ${expr} } catch (e) {}`;
this.eval(evalExpr, this.context, 'repl', function doEval(e, obj) {
this.eval(evalExpr, this.context, 'repl', (e, obj) => {
// if (e) console.log(e);

if (obj != null) {
if (typeof obj === 'object' || typeof obj === 'function') {
try {
memberGroups.push(filteredOwnPropertyNames(obj));
memberGroups.push(filteredOwnPropertyNames.call(this, obj));
} catch (ex) {
// Probably a Proxy object without `getOwnPropertyNames` trap.
// We simply ignore it here, as we don't want to break the
Expand All @@ -890,7 +917,7 @@ function complete(line, callback) {
p = obj.constructor ? obj.constructor.prototype : null;
}
while (p !== null) {
memberGroups.push(filteredOwnPropertyNames(p));
memberGroups.push(filteredOwnPropertyNames.call(this, p));
p = Object.getPrototypeOf(p);
// Circular refs possible? Let's guard against that.
sentinel--;
Expand Down
65 changes: 65 additions & 0 deletions test/parallel/test-repl-tab-complete.js
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,71 @@ testMe.complete('.b', common.mustCall((error, data) => {
assert.deepStrictEqual(data, [['break'], 'b']);
}));

// tab completion for large buffer
const warningRegEx = new RegExp(
'\\(node:\\d+\\) REPLWarning: The current array, Buffer or TypedArray has ' +
'too many entries\\. Certain properties may be missing from completion ' +
'output\\.');

[
Array,
Buffer,

Uint8Array,
Uint16Array,
Uint32Array,

Uint8ClampedArray,
Int8Array,
Int16Array,
Int32Array,
Float32Array,
Float64Array,
].forEach((type) => {
putIn.run(['.clear']);

if (type === Array) {
putIn.run([
'var ele = [];',
'for (let i = 0; i < 1e6 + 1; i++) ele[i] = 0;',
'ele.biu = 1;'
]);
} else if (type === Buffer) {
putIn.run(['var ele = Buffer.alloc(1e6 + 1); ele.biu = 1;']);
} else {
putIn.run([`var ele = new ${type.name}(1e6 + 1); ele.biu = 1;`]);
}

common.hijackStderr(common.mustCall((err) => {
process.nextTick(() => {
assert.ok(warningRegEx.test(err));
});
}));
testMe.complete('ele.', common.mustCall((err, data) => {
common.restoreStderr();
assert.ifError(err);

const ele = (type === Array) ?
[] :
(type === Buffer ?
Buffer.alloc(0) :
new type(0));

data[0].forEach((key) => {
if (!key) return;
assert.notStrictEqual(ele[key.substr(4)], undefined);
});

// no `biu`
assert.strictEqual(data.indexOf('ele.biu'), -1);
}));
});

// check Buffer.prototype.length not crashing.
// Refs: https://github.com/nodejs/node/pull/11961
putIn.run['.clear'];
testMe.complete('Buffer.prototype.', common.mustCall());

const testNonGlobal = repl.start({
input: putIn,
output: putIn,
Expand Down

0 comments on commit 7d7ccf0

Please sign in to comment.