From e8359fb1ccb402f2ce83dfc46626dd5e41ac23b7 Mon Sep 17 00:00:00 2001 From: Zhihao Wu Date: Thu, 1 Oct 2026 22:17:55 +0700 Subject: [PATCH] fix: preserve __proto__ groups --- lib/groupByLimit.js | 8 +++++++- test/groupBy.js | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/lib/groupByLimit.js b/lib/groupByLimit.js index a5d20ccd4..5999182f1 100644 --- a/lib/groupByLimit.js +++ b/lib/groupByLimit.js @@ -42,7 +42,13 @@ function groupByLimit(coll, limit, iteratee, callback) { if (hasOwnProperty.call(result, key)) { result[key].push(val); } else { - result[key] = [val]; + // Avoid invoking the inherited setter for a group named '__proto__'. + Object.defineProperty(result, key, { + value: [val], + writable: true, + enumerable: true, + configurable: true + }); } } } diff --git a/test/groupBy.js b/test/groupBy.js index b00484c3c..53b97eaf7 100644 --- a/test/groupBy.js +++ b/test/groupBy.js @@ -5,6 +5,38 @@ var assert = require('assert'); describe('groupBy', function() { this.timeout(250); + ['groupBy', 'groupByLimit', 'groupBySeries'].forEach((method) => { + var values = ['__proto__', 'constructor', 'toString', '__proto__', 'hasOwnProperty']; + + function checkGroups(result) { + expect(Object.getPrototypeOf(result)).to.equal(Object.prototype); + expect(Object.keys(result).sort()).to.eql([ + '__proto__', 'constructor', 'hasOwnProperty', 'toString' + ]); + expect(result.__proto__).to.eql(['__proto__', '__proto__']); + expect(result.constructor).to.eql(['constructor']); + expect(result.toString).to.eql(['toString']); + expect(result.hasOwnProperty).to.eql(['hasOwnProperty']); + } + + it(`${method} groups values under prototype property names`, (done) => { + var args = [values, (value, next) => { + async.setImmediate(() => next(null, value)); + }, (err, result) => { + checkGroups(result); + done(err); + }]; + if (method === 'groupByLimit') args.splice(1, 0, 2); + async[method](...args); + }); + + it(`${method} resolves groups under prototype property names`, async () => { + var args = [values, async value => value]; + if (method === 'groupByLimit') args.splice(1, 0, 2); + checkGroups(await async[method](...args)); + }); + }); + function groupByIteratee(callOrder, val, next) { setTimeout(() => { callOrder.push(val);