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); + } + }); + });