diff options
-rw-r--r-- | packages/sol-cov/src/ast_visitor.ts | 4 | ||||
-rw-r--r-- | packages/sol-cov/src/coverage_manager.ts | 24 |
2 files changed, 28 insertions, 0 deletions
diff --git a/packages/sol-cov/src/ast_visitor.ts b/packages/sol-cov/src/ast_visitor.ts index 9193f6a18..701cbbc88 100644 --- a/packages/sol-cov/src/ast_visitor.ts +++ b/packages/sol-cov/src/ast_visitor.ts @@ -7,6 +7,7 @@ export interface CoverageEntriesDescription { fnMap: FnMap; branchMap: BranchMap; statementMap: StatementMap; + modifiersStatementIds: number[]; } enum BranchType { @@ -19,6 +20,7 @@ export class ASTVisitor { private _entryId = 0; private _fnMap: FnMap = {}; private _branchMap: BranchMap = {}; + private _modifiersStatementIds: number[] = []; private _statementMap: StatementMap = {}; private _locationByOffset: LocationByOffset; private static _doesLookLikeAnASTNode(ast: any): boolean { @@ -49,6 +51,7 @@ export class ASTVisitor { fnMap: this._fnMap, branchMap: this._branchMap, statementMap: this._statementMap, + modifiersStatementIds: this._modifiersStatementIds, }; return coverageEntriesDescription; } @@ -91,6 +94,7 @@ export class ASTVisitor { private _visitModifierArgument(ast: SolidityParser.AST): void { const BUILTIN_MODIFIERS = ['public', 'view', 'payable', 'external', 'internal', 'pure', 'constant']; if (!_.includes(BUILTIN_MODIFIERS, ast.name)) { + this._modifiersStatementIds.push(this._entryId); this._visitStatement(ast); } } diff --git a/packages/sol-cov/src/coverage_manager.ts b/packages/sol-cov/src/coverage_manager.ts index 4ca6b0ec8..b70ca6f3f 100644 --- a/packages/sol-cov/src/coverage_manager.ts +++ b/packages/sol-cov/src/coverage_manager.ts @@ -69,6 +69,30 @@ export class CoverageManager { ); functionCoverage[fnId] = isCovered; } + // HACK: Solidity doesn't emit any opcodes that map back to modifiers with no args, that's why we map back to the + // function range and check if there is any covered statement within that range. + for (const modifierStatementId of coverageEntriesDescription.modifiersStatementIds) { + if (statementCoverage[modifierStatementId]) { + // Already detected as covered + continue; + } + const modifierDescription = coverageEntriesDescription.statementMap[modifierStatementId]; + const enclosingFunction = _.find(coverageEntriesDescription.fnMap, functionDescription => + utils.isRangeInside(modifierDescription, functionDescription.loc), + ) as FunctionDescription; + const isModifierCovered = _.some( + coverageEntriesDescription.statementMap, + (statementDescription: StatementDescription, statementId: number) => { + const isInsideTheModifierEnclosingFunction = utils.isRangeInside( + statementDescription, + enclosingFunction.loc, + ); + const isCovered = statementCoverage[statementId]; + return isInsideTheModifierEnclosingFunction && isCovered; + }, + ); + statementCoverage[modifierStatementId] = isModifierCovered; + } const partialCoverage = { [contractData.sources[fileIndex]]: { ...coverageEntriesDescription, |