From c9e97a47d0e093ffc5d0e277af4069f10844c0c9 Mon Sep 17 00:00:00 2001 From: Brenno Ferrari Date: Sun, 19 Apr 2026 19:44:28 +0200 Subject: [PATCH] fix(LCS): use Object.create(null) so prototype members don't shadow lookups LCS stores equivalence classes in a plain object, so input elements that match Object.prototype member names (`constructor`, `__proto__`, `toString`, `hasOwnProperty`, etc.) hit the prototype-inherited member on the lookup and crash with: TypeError: equivalenceClasses[item].push is not a function `Object.create(null)` produces a dictionary with no prototype chain, so the lookup sees the key only if we wrote it. Zero API change. Regression test in test/LCS.test.js covers the common prototype names. Closes #86 --- src/diff3.mjs | 5 ++++- test/LCS.test.js | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/diff3.mjs b/src/diff3.mjs index 0b2c536..7b43ba5 100644 --- a/src/diff3.mjs +++ b/src/diff3.mjs @@ -22,7 +22,10 @@ export { // // Expects two arrays, finds longest common sequence function LCS(buffer1, buffer2) { - let equivalenceClasses = {}; + // Use a prototype-less object so input elements that happen to match + // `Object.prototype` members (`constructor`, `__proto__`, `toString`, ...) + // don't shadow the lookup with inherited values. + let equivalenceClasses = Object.create(null); for (let j = 0; j < buffer2.length; j++) { const item = buffer2[j]; if (equivalenceClasses[item]) { diff --git a/test/LCS.test.js b/test/LCS.test.js index acda98c..51747e1 100644 --- a/test/LCS.test.js +++ b/test/LCS.test.js @@ -31,4 +31,26 @@ describe('LCS', () => { assert.deepEqual(result.chain.chain.chain.chain.chain.chain, NULLRESULT); }); + it('handles elements that match Object.prototype member names', () => { + // Regression: a plain `{}` lookup table treats elements like 'constructor' + // or '__proto__' as already-present (inherited from Object.prototype), + // which used to crash with "equivalenceClasses[item].push is not a function". + const prototypeNames = [ + 'constructor', + '__proto__', + 'toString', + 'hasOwnProperty', + 'valueOf', + 'isPrototypeOf', + 'propertyIsEnumerable', + '__defineGetter__', + ]; + + for (const name of prototypeNames) { + const result = Diff3.LCS([name], [name]); + assert.deepEqual(result.buffer1index, 0); + assert.deepEqual(result.buffer2index, 0); + } + }); + });