From 3683f6b787d6eae8f6267953865e234008c2689b Mon Sep 17 00:00:00 2001 From: XadillaX Date: Tue, 20 Jun 2017 18:49:03 +0800 Subject: [PATCH] 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 Reviewed-By: James M Snell Reviewed-By: Alexey Orlenko --- lib/repl.js | 37 ++++++++++++-- test/parallel/test-repl-tab-complete.js | 65 +++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 5 deletions(-) diff --git a/lib/repl.js b/lib/repl.js index 31e51616016146..2b371ed7bcbb54 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -688,8 +688,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); } @@ -843,9 +868,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 { @@ -865,13 +892,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 @@ -889,7 +916,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--; diff --git a/test/parallel/test-repl-tab-complete.js b/test/parallel/test-repl-tab-complete.js index 87577c04e0dcb8..cf67e920f9528d 100644 --- a/test/parallel/test-repl-tab-complete.js +++ b/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,