Skip to content

Commit 7d7ccf0

Browse files
XadillaXTimothyGu
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 <timothygu99@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Alexey Orlenko <eaglexrlnk@gmail.com>
1 parent a965067 commit 7d7ccf0

File tree

2 files changed

+97
-5
lines changed

2 files changed

+97
-5
lines changed

lib/repl.js

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -689,8 +689,33 @@ function intFilter(item) {
689689
return /^[A-Za-z_$]/.test(item);
690690
}
691691

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

@@ -844,9 +869,11 @@ function complete(line, callback) {
844869
if (this.useGlobal || vm.isContext(this.context)) {
845870
var contextProto = this.context;
846871
while (contextProto = Object.getPrototypeOf(contextProto)) {
847-
completionGroups.push(filteredOwnPropertyNames(contextProto));
872+
completionGroups.push(
873+
filteredOwnPropertyNames.call(this, contextProto));
848874
}
849-
completionGroups.push(filteredOwnPropertyNames(this.context));
875+
completionGroups.push(
876+
filteredOwnPropertyNames.call(this, this.context));
850877
addStandardGlobals(completionGroups, filter);
851878
completionGroupsLoaded();
852879
} else {
@@ -866,13 +893,13 @@ function complete(line, callback) {
866893
}
867894
} else {
868895
const evalExpr = `try { ${expr} } catch (e) {}`;
869-
this.eval(evalExpr, this.context, 'repl', function doEval(e, obj) {
896+
this.eval(evalExpr, this.context, 'repl', (e, obj) => {
870897
// if (e) console.log(e);
871898

872899
if (obj != null) {
873900
if (typeof obj === 'object' || typeof obj === 'function') {
874901
try {
875-
memberGroups.push(filteredOwnPropertyNames(obj));
902+
memberGroups.push(filteredOwnPropertyNames.call(this, obj));
876903
} catch (ex) {
877904
// Probably a Proxy object without `getOwnPropertyNames` trap.
878905
// We simply ignore it here, as we don't want to break the
@@ -890,7 +917,7 @@ function complete(line, callback) {
890917
p = obj.constructor ? obj.constructor.prototype : null;
891918
}
892919
while (p !== null) {
893-
memberGroups.push(filteredOwnPropertyNames(p));
920+
memberGroups.push(filteredOwnPropertyNames.call(this, p));
894921
p = Object.getPrototypeOf(p);
895922
// Circular refs possible? Let's guard against that.
896923
sentinel--;

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

Lines changed: 65 additions & 0 deletions
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)