From c4895bb418c55700182f481b914b74b2865a9bea Mon Sep 17 00:00:00 2001 From: isaacs Date: Tue, 7 Nov 2023 14:14:53 -0800 Subject: [PATCH] fix: Proper data validation in findNearestContainer (#754) This prevents a thrown when the statement map needs to be processed for container hits, because the statement map entries do not contain a "loc" field, they just have this inforation at the top level. Fix: #753 --- .../istanbul-lib-coverage/lib/file-coverage.js | 12 ++++++++++-- .../istanbul-lib-coverage/test/file.test.js | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/packages/istanbul-lib-coverage/lib/file-coverage.js b/packages/istanbul-lib-coverage/lib/file-coverage.js index 7257101f..4ed4c096 100644 --- a/packages/istanbul-lib-coverage/lib/file-coverage.js +++ b/packages/istanbul-lib-coverage/lib/file-coverage.js @@ -45,6 +45,12 @@ function assertValidObject(obj) { const keyFromLoc = ({ start, end }) => `${start.line}|${start.column}|${end.line}|${end.column}`; +const isObj = o => !!o && typeof o === 'object'; +const isLineCol = o => + isObj(o) && typeof o.line === 'number' && typeof o.column === 'number'; +const isLoc = o => isObj(o) && isLineCol(o.start) && isLineCol(o.end); +const getLoc = o => (isLoc(o) ? o : isLoc(o.loc) ? o.loc : null); + // When merging, we can have a case where two ranges cover // the same block of code with `hits=1`, and each carve out a // different range with `hits=0` to indicate it's uncovered. @@ -52,6 +58,8 @@ const keyFromLoc = ({ start, end }) => // that both sections are hit. // Returns null if no containing item is found. const findNearestContainer = (item, map) => { + const itemLoc = getLoc(item); + if (!itemLoc) return null; // the B item is not an identified range in the A set, BUT // it may be contained by an identified A range. If so, then // any hit of that containing A range counts as a hit of this @@ -62,8 +70,8 @@ const findNearestContainer = (item, map) => { let containerDistance = null; let containerKey = null; for (const [i, mapItem] of Object.entries(map)) { - const mapLoc = mapItem.loc; - const itemLoc = item.loc; + const mapLoc = getLoc(mapItem); + if (!mapLoc) continue; // contained if all of line distances are > 0 // or line distance is 0 and col dist is >= 0 const distance = [ diff --git a/packages/istanbul-lib-coverage/test/file.test.js b/packages/istanbul-lib-coverage/test/file.test.js index 98f3fc14..c8a74c5e 100644 --- a/packages/istanbul-lib-coverage/test/file.test.js +++ b/packages/istanbul-lib-coverage/test/file.test.js @@ -1123,3 +1123,20 @@ describe('addNearestContainerHits unit coverage', () => { ); }); }); + +describe('findNearestContainer missing loc defense', () => { + it('does not throw if loc is missing', () => { + const loc = (sl, sc, el, ec) => ({ + start: { line: sl, column: sc }, + end: { line: el, column: ec } + }); + const map = { + 0: { loc: loc(1, 1, 100, 100) }, + 1: {}, + 2: loc(10, 10, 50, 50), + 3: { loc: loc(20, 20, 40, 40) } + }; + assert.equal(findNearestContainer({ no: 'loc' }, map), null); + assert.equal(findNearestContainer(loc(30, 30, 35, 35), '3')); + }); +});