Browse Source

repl: fix crash with large buffer tab completion

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

PR-URL: https://github.com/nodejs/node/pull/13817
Fixes: https://github.com/nodejs/node/issues/3136
Reviewed-By: Timothy Gu <timothygu99@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Alexey Orlenko <eaglexrlnk@gmail.com>
v6
XadillaX 7 years ago
committed by Timothy Gu
parent
commit
7d7ccf0631
No known key found for this signature in database GPG Key ID: 7FE6B095B582B0D4
  1. 37
      lib/repl.js
  2. 65
      test/parallel/test-repl-tab-complete.js

37
lib/repl.js

@ -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);
}
@ -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 {
@ -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
@ -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--;

65
test/parallel/test-repl-tab-complete.js

@ -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,

Loading…
Cancel
Save