Skip to content

Commit 3683f6b

Browse files
XadillaXaddaleax
authored andcommitted
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: #13817 Fixes: #3136 Reviewed-By: Timothy Gu <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Alexey Orlenko <[email protected]>
1 parent f8d76dc commit 3683f6b

File tree

2 files changed

+97
-5
lines changed

2 files changed

+97
-5
lines changed

lib/repl.js

+32-5
Original file line numberDiff line numberDiff line change
@@ -688,8 +688,33 @@ function intFilter(item) {
688688
return /^[A-Za-z_$]/.test(item);
689689
}
690690

691+
const ARRAY_LENGTH_THRESHOLD = 1e6;
692+
693+
function mayBeLargeObject(obj) {
694+
if (Array.isArray(obj)) {
695+
return obj.length > ARRAY_LENGTH_THRESHOLD ? ['length'] : null;
696+
} else if (utilBinding.isTypedArray(obj)) {
697+
return obj.length > ARRAY_LENGTH_THRESHOLD ? [] : null;
698+
}
699+
700+
return null;
701+
}
702+
691703
function filteredOwnPropertyNames(obj) {
692704
if (!obj) return [];
705+
const fakeProperties = mayBeLargeObject(obj);
706+
if (fakeProperties !== null) {
707+
this.outputStream.write('\r\n');
708+
process.emitWarning(
709+
'The current array, Buffer or TypedArray has too many entries. ' +
710+
'Certain properties may be missing from completion output.',
711+
'REPLWarning',
712+
undefined,
713+
undefined,
714+
true);
715+
716+
return fakeProperties;
717+
}
693718
return Object.getOwnPropertyNames(obj).filter(intFilter);
694719
}
695720

@@ -843,9 +868,11 @@ function complete(line, callback) {
843868
if (this.useGlobal || vm.isContext(this.context)) {
844869
var contextProto = this.context;
845870
while (contextProto = Object.getPrototypeOf(contextProto)) {
846-
completionGroups.push(filteredOwnPropertyNames(contextProto));
871+
completionGroups.push(
872+
filteredOwnPropertyNames.call(this, contextProto));
847873
}
848-
completionGroups.push(filteredOwnPropertyNames(this.context));
874+
completionGroups.push(
875+
filteredOwnPropertyNames.call(this, this.context));
849876
addStandardGlobals(completionGroups, filter);
850877
completionGroupsLoaded();
851878
} else {
@@ -865,13 +892,13 @@ function complete(line, callback) {
865892
}
866893
} else {
867894
const evalExpr = `try { ${expr} } catch (e) {}`;
868-
this.eval(evalExpr, this.context, 'repl', function doEval(e, obj) {
895+
this.eval(evalExpr, this.context, 'repl', (e, obj) => {
869896
// if (e) console.log(e);
870897

871898
if (obj != null) {
872899
if (typeof obj === 'object' || typeof obj === 'function') {
873900
try {
874-
memberGroups.push(filteredOwnPropertyNames(obj));
901+
memberGroups.push(filteredOwnPropertyNames.call(this, obj));
875902
} catch (ex) {
876903
// Probably a Proxy object without `getOwnPropertyNames` trap.
877904
// We simply ignore it here, as we don't want to break the
@@ -889,7 +916,7 @@ function complete(line, callback) {
889916
p = obj.constructor ? obj.constructor.prototype : null;
890917
}
891918
while (p !== null) {
892-
memberGroups.push(filteredOwnPropertyNames(p));
919+
memberGroups.push(filteredOwnPropertyNames.call(this, p));
893920
p = Object.getPrototypeOf(p);
894921
// Circular refs possible? Let's guard against that.
895922
sentinel--;

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

+65
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,71 @@ testMe.complete('.b', common.mustCall((error, data) => {
305305
assert.deepStrictEqual(data, [['break'], 'b']);
306306
}));
307307

308+
// tab completion for large buffer
309+
const warningRegEx = new RegExp(
310+
'\\(node:\\d+\\) REPLWarning: The current array, Buffer or TypedArray has ' +
311+
'too many entries\\. Certain properties may be missing from completion ' +
312+
'output\\.');
313+
314+
[
315+
Array,
316+
Buffer,
317+
318+
Uint8Array,
319+
Uint16Array,
320+
Uint32Array,
321+
322+
Uint8ClampedArray,
323+
Int8Array,
324+
Int16Array,
325+
Int32Array,
326+
Float32Array,
327+
Float64Array,
328+
].forEach((type) => {
329+
putIn.run(['.clear']);
330+
331+
if (type === Array) {
332+
putIn.run([
333+
'var ele = [];',
334+
'for (let i = 0; i < 1e6 + 1; i++) ele[i] = 0;',
335+
'ele.biu = 1;'
336+
]);
337+
} else if (type === Buffer) {
338+
putIn.run(['var ele = Buffer.alloc(1e6 + 1); ele.biu = 1;']);
339+
} else {
340+
putIn.run([`var ele = new ${type.name}(1e6 + 1); ele.biu = 1;`]);
341+
}
342+
343+
common.hijackStderr(common.mustCall((err) => {
344+
process.nextTick(() => {
345+
assert.ok(warningRegEx.test(err));
346+
});
347+
}));
348+
testMe.complete('ele.', common.mustCall((err, data) => {
349+
common.restoreStderr();
350+
assert.ifError(err);
351+
352+
const ele = (type === Array) ?
353+
[] :
354+
(type === Buffer ?
355+
Buffer.alloc(0) :
356+
new type(0));
357+
358+
data[0].forEach((key) => {
359+
if (!key) return;
360+
assert.notStrictEqual(ele[key.substr(4)], undefined);
361+
});
362+
363+
// no `biu`
364+
assert.strictEqual(data.indexOf('ele.biu'), -1);
365+
}));
366+
});
367+
368+
// check Buffer.prototype.length not crashing.
369+
// Refs: https://github.com/nodejs/node/pull/11961
370+
putIn.run['.clear'];
371+
testMe.complete('Buffer.prototype.', common.mustCall());
372+
308373
const testNonGlobal = repl.start({
309374
input: putIn,
310375
output: putIn,

0 commit comments

Comments
 (0)