You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
510 lines
15 KiB
510 lines
15 KiB
/**
|
|
* Registers injections points (e.g source location, type) and their associated data with
|
|
* a contract / instrumentation target. Run during the `parse` step. This data is
|
|
* consumed by the Injector as it modifies the source code in instrumentation's final step.
|
|
*/
|
|
class Registrar {
|
|
|
|
constructor(){
|
|
// When, at *certain nodes* we don't want to inject statements
|
|
// because they're an unnecessary expense. ex: `receive`, this
|
|
// can be toggled on/off in the parser
|
|
this.trackStatements = true;
|
|
|
|
// These are set by user option and enable/disable the measurement completely
|
|
this.enabled = {
|
|
statements: true,
|
|
functions: true,
|
|
modifiers: true,
|
|
branches: true,
|
|
lines: true
|
|
}
|
|
|
|
this.modifierWhitelist = [];
|
|
}
|
|
|
|
_seekSemiColon(contract, pos) {
|
|
const end = pos + 5;
|
|
for(pos; pos <= end; pos++) {
|
|
if (contract[pos] === ';') break;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
/**
|
|
* Adds injection point to injection points map
|
|
* @param {Object} contract instrumentation target
|
|
* @param {String} key injection point `type`
|
|
* @param {Number} value injection point `id`
|
|
*/
|
|
_createInjectionPoint(contract, key, value) {
|
|
value.contractName = contract.contractName;
|
|
|
|
(contract.injectionPoints[key])
|
|
? contract.injectionPoints[key].push(value)
|
|
: contract.injectionPoints[key] = [value];
|
|
}
|
|
|
|
/**
|
|
* Registers injections for statement measurements
|
|
* @param {Object} contract instrumentation target
|
|
* @param {Object} expression AST node
|
|
*/
|
|
statement(contract, expression) {
|
|
if (!this.trackStatements || !this.enabled.statements) return;
|
|
|
|
const startContract = contract.instrumented.slice(0, expression.range[0]);
|
|
const startline = ( startContract.match(/\n/g) || [] ).length + 1;
|
|
const startcol = expression.range[0] - startContract.lastIndexOf('\n') - 1;
|
|
|
|
const expressionContent = contract.instrumented.slice(
|
|
expression.range[0],
|
|
expression.range[1] + 1
|
|
);
|
|
|
|
const endline = startline + (contract, expressionContent.match('/\n/g') || []).length;
|
|
|
|
let endcol;
|
|
if (expressionContent.lastIndexOf('\n') >= 0) {
|
|
|
|
endcol = contract.instrumented.slice(
|
|
expressionContent.lastIndexOf('\n'),
|
|
expression.range[1]
|
|
).length;
|
|
|
|
} else endcol = startcol + (contract, expressionContent.length - 1);
|
|
|
|
contract.statementId += 1;
|
|
contract.statementMap[contract.statementId] = {
|
|
start: { line: startline, column: startcol },
|
|
end: { line: endline, column: endcol },
|
|
};
|
|
|
|
this._createInjectionPoint(contract, expression.range[0], {
|
|
type: 'injectStatement', statementId: contract.statementId,
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Registers injections for line measurements
|
|
* @param {Object} contract instrumentation target
|
|
* @param {Object} expression AST node
|
|
*/
|
|
line(contract, expression) {
|
|
if (!this.enabled.lines) return;
|
|
|
|
const startchar = expression.range[0];
|
|
const endchar = expression.range[1] + 1;
|
|
const lastNewLine = contract.instrumented.slice(0, startchar).lastIndexOf('\n');
|
|
const nextNewLine = startchar + contract.instrumented.slice(startchar).indexOf('\n');
|
|
const contractSnipped = contract.instrumented.slice(lastNewLine, nextNewLine);
|
|
const restOfLine = contract.instrumented.slice(endchar, nextNewLine);
|
|
|
|
if (
|
|
contract.instrumented.slice(lastNewLine, startchar).trim().length === 0 &&
|
|
(
|
|
restOfLine.replace(';', '').trim().length === 0 ||
|
|
restOfLine.replace(';', '').trim().substring(0, 2) === '//'
|
|
)
|
|
)
|
|
{
|
|
this._createInjectionPoint(contract, lastNewLine + 1, { type: 'injectLine' });
|
|
|
|
} else if (
|
|
contract.instrumented.slice(lastNewLine, startchar).replace('{', '').trim().length === 0 &&
|
|
contract.instrumented.slice(endchar, nextNewLine).replace(/[;}]/g, '').trim().length === 0)
|
|
{
|
|
this._createInjectionPoint(contract, expression.range[0], { type: 'injectLine' });
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Registers injections for function measurements
|
|
* @param {Object} contract instrumentation target
|
|
* @param {Object} expression AST node
|
|
*/
|
|
functionDeclaration(contract, expression) {
|
|
if (!this.enabled.functions) return;
|
|
|
|
let start = 0;
|
|
contract.fnId += 1;
|
|
|
|
if (expression.modifiers && expression.modifiers.length){
|
|
for (let modifier of expression.modifiers ){
|
|
|
|
// It's possible functions will have modifiers that take string args
|
|
// which contains an open curly brace. Skip ahead...
|
|
if (modifier.range[1] > start){
|
|
start = modifier.range[1];
|
|
}
|
|
|
|
// Add modifier branch coverage
|
|
if (
|
|
!this.enabled.modifiers ||
|
|
expression.isConstructor ||
|
|
this.modifierWhitelist.includes(modifier.name)
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
this.addNewBranch(contract, modifier);
|
|
this._createInjectionPoint(
|
|
contract,
|
|
modifier.range[0],
|
|
{
|
|
type: 'injectModifier',
|
|
branchId: contract.branchId,
|
|
modifierName: modifier.name,
|
|
fnId: contract.fnId,
|
|
condition: 'pre'
|
|
}
|
|
);
|
|
this._createInjectionPoint(
|
|
contract,
|
|
modifier.range[1] + 1,
|
|
{
|
|
type: 'injectModifier',
|
|
branchId: contract.branchId,
|
|
modifierName: modifier.name,
|
|
fnId: contract.fnId,
|
|
condition: 'post'
|
|
}
|
|
);
|
|
}
|
|
} else {
|
|
start = expression.range[0];
|
|
}
|
|
|
|
const startContract = contract.instrumented.slice(0, start);
|
|
const startline = ( startContract.match(/\n/g) || [] ).length + 1;
|
|
const startcol = start - startContract.lastIndexOf('\n') - 1;
|
|
|
|
const endlineDelta = contract.instrumented.slice(start).indexOf('{');
|
|
const functionDefinition = contract.instrumented.slice(
|
|
start,
|
|
start + endlineDelta
|
|
);
|
|
|
|
contract.fnMap[contract.fnId] = {
|
|
name: expression.isConstructor ? 'constructor' : expression.name,
|
|
line: startline,
|
|
loc: expression.loc
|
|
};
|
|
|
|
this._createInjectionPoint(
|
|
contract,
|
|
start + endlineDelta + 1,
|
|
{
|
|
type: 'injectFunction',
|
|
fnId: contract.fnId,
|
|
}
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Registers injections for branch measurements. This generic is consumed by
|
|
* the `require` and `if` registration methods.
|
|
* @param {Object} contract instrumentation target
|
|
* @param {Object} expression AST node
|
|
*/
|
|
addNewBranch(contract, expression) {
|
|
const startContract = contract.instrumented.slice(0, expression.range[0]);
|
|
const startline = ( startContract.match(/\n/g) || [] ).length + 1;
|
|
const startcol = expression.range[0] - startContract.lastIndexOf('\n') - 1;
|
|
|
|
contract.branchId += 1;
|
|
|
|
// NB locations for if branches in istanbul are zero
|
|
// length and associated with the start of the if.
|
|
contract.branchMap[contract.branchId] = {
|
|
line: startline,
|
|
type: 'if',
|
|
locations: [{
|
|
start: {
|
|
line: startline, column: startcol,
|
|
},
|
|
end: {
|
|
line: startline, column: startcol,
|
|
},
|
|
}, {
|
|
start: {
|
|
line: startline, column: startcol,
|
|
},
|
|
end: {
|
|
line: startline, column: startcol,
|
|
},
|
|
}],
|
|
};
|
|
};
|
|
|
|
addNewConditionalBranch(contract, expression){
|
|
let start;
|
|
// Instabul HTML highlighting location data...
|
|
const trueZero = expression.trueExpression.range[0];
|
|
const trueOne = expression.trueExpression.range[1];
|
|
const falseZero = expression.falseExpression.range[0];
|
|
const falseOne = expression.falseExpression.range[1];
|
|
|
|
start = contract.instrumented.slice(0, trueZero);
|
|
const trueStartLine = ( start.match(/\n/g) || [] ).length + 1;
|
|
const trueStartCol = trueZero - start.lastIndexOf('\n') - 1;
|
|
|
|
start = contract.instrumented.slice(0, trueOne);
|
|
const trueEndLine = ( start.match(/\n/g) || [] ).length + 1;
|
|
const trueEndCol = trueOne - start.lastIndexOf('\n') - 1;
|
|
|
|
start = contract.instrumented.slice(0, falseZero);
|
|
const falseStartLine = ( start.match(/\n/g) || [] ).length + 1;
|
|
const falseStartCol = falseZero - start.lastIndexOf('\n') - 1;
|
|
|
|
start = contract.instrumented.slice(0, falseOne);
|
|
const falseEndLine = ( start.match(/\n/g) || [] ).length + 1;
|
|
const falseEndCol = falseOne - start.lastIndexOf('\n') - 1;
|
|
|
|
contract.branchId += 1;
|
|
|
|
contract.branchMap[contract.branchId] = {
|
|
line: trueStartLine,
|
|
type: 'if',
|
|
locations: [{
|
|
start: {
|
|
line: trueStartLine, column: trueStartCol,
|
|
},
|
|
end: {
|
|
line: trueEndLine, column: trueEndCol,
|
|
},
|
|
}, {
|
|
start: {
|
|
line: falseStartLine, column: falseStartCol,
|
|
},
|
|
end: {
|
|
line: falseEndLine, column: falseEndCol,
|
|
},
|
|
}],
|
|
};
|
|
|
|
}
|
|
|
|
/**
|
|
* Registers injections for branch measurements. Used by logicalOR registration method.
|
|
* @param {Object} contract instrumentation target
|
|
* @param {Object} expression AST node
|
|
*/
|
|
addNewLogicalORBranch(contract, expression) {
|
|
let start;
|
|
|
|
// Instabul HTML highlighting location data...
|
|
const leftZero = expression.left.range[0];
|
|
const leftOne = expression.left.range[1];
|
|
const rightZero = expression.right.range[0];
|
|
const rightOne = expression.right.range[1];
|
|
|
|
start = contract.instrumented.slice(0, leftZero);
|
|
const leftStartLine = ( start.match(/\n/g) || [] ).length + 1;
|
|
const leftStartCol = leftZero - start.lastIndexOf('\n') - 1;
|
|
|
|
start = contract.instrumented.slice(0, leftOne);
|
|
const leftEndLine = ( start.match(/\n/g) || [] ).length + 1;
|
|
const leftEndCol = leftOne - start.lastIndexOf('\n') - 1;
|
|
|
|
start = contract.instrumented.slice(0, rightZero);
|
|
const rightStartLine = ( start.match(/\n/g) || [] ).length + 1;
|
|
const rightStartCol = rightZero - start.lastIndexOf('\n') - 1;
|
|
|
|
start = contract.instrumented.slice(0, rightOne);
|
|
const rightEndLine = ( start.match(/\n/g) || [] ).length + 1;
|
|
const rightEndCol = rightOne - start.lastIndexOf('\n') - 1;
|
|
|
|
contract.branchId += 1;
|
|
|
|
// NB locations for if branches in istanbul are zero
|
|
// length and associated with the start of the if.
|
|
contract.branchMap[contract.branchId] = {
|
|
line: leftStartLine,
|
|
type: 'cond-expr',
|
|
locations: [{
|
|
start: {
|
|
line: leftStartLine, column: leftStartCol,
|
|
},
|
|
end: {
|
|
line: leftEndLine, column: leftEndCol,
|
|
},
|
|
}, {
|
|
start: {
|
|
line: rightStartLine, column: rightStartCol,
|
|
},
|
|
end: {
|
|
line: rightEndLine, column: rightEndCol,
|
|
},
|
|
}],
|
|
};
|
|
};
|
|
|
|
conditional(contract, expression){
|
|
if (!this.enabled.branches) return;
|
|
|
|
this.addNewConditionalBranch(contract, expression);
|
|
|
|
// Double open parens
|
|
this._createInjectionPoint(
|
|
contract,
|
|
expression.condition.range[0],
|
|
{
|
|
type: 'injectOpenParen',
|
|
}
|
|
);
|
|
this._createInjectionPoint(
|
|
contract,
|
|
expression.condition.range[0],
|
|
{
|
|
type: 'injectOpenParen',
|
|
}
|
|
);
|
|
// False condition: (these get sorted in reverse order, so create in reversed order)
|
|
this._createInjectionPoint(
|
|
contract,
|
|
expression.condition.range[1] + 1,
|
|
{
|
|
type: 'injectOrFalse',
|
|
branchId: contract.branchId,
|
|
locationIdx: 1
|
|
}
|
|
);
|
|
// True condition
|
|
this._createInjectionPoint(
|
|
contract,
|
|
expression.condition.range[1] + 1,
|
|
{
|
|
type: 'injectAndTrue',
|
|
branchId: contract.branchId,
|
|
locationIdx: 0
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Registers injections for logicalOR clause measurements (branches)
|
|
* @param {Object} contract instrumentation target
|
|
* @param {Object} expression AST node
|
|
* @param {Number} injectionIdx pre/post branch index (left=0, right=1)
|
|
*/
|
|
logicalOR(contract, expression) {
|
|
if (!this.enabled.branches) return;
|
|
|
|
this.addNewLogicalORBranch(contract, expression);
|
|
|
|
// Left
|
|
this._createInjectionPoint(
|
|
contract,
|
|
expression.left.range[0],
|
|
{
|
|
type: 'injectOpenParen',
|
|
}
|
|
);
|
|
this._createInjectionPoint(
|
|
contract,
|
|
expression.left.range[1] + 1,
|
|
{
|
|
type: 'injectAndTrue',
|
|
branchId: contract.branchId,
|
|
locationIdx: 0
|
|
}
|
|
);
|
|
|
|
// Right
|
|
this._createInjectionPoint(
|
|
contract,
|
|
expression.right.range[0],
|
|
{
|
|
type: 'injectOpenParen',
|
|
}
|
|
);
|
|
this._createInjectionPoint(
|
|
contract,
|
|
expression.right.range[1] + 1,
|
|
{
|
|
type: 'injectAndTrue',
|
|
branchId: contract.branchId,
|
|
locationIdx: 1
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Registers injections for require statement measurements (branches)
|
|
* @param {Object} contract instrumentation target
|
|
* @param {Object} expression AST node
|
|
*/
|
|
requireBranch(contract, expression) {
|
|
if (!this.enabled.branches) return;
|
|
|
|
this.addNewBranch(contract, expression);
|
|
this._createInjectionPoint(
|
|
contract,
|
|
expression.range[0],
|
|
{
|
|
type: 'injectRequirePre',
|
|
branchId: contract.branchId,
|
|
}
|
|
);
|
|
this._createInjectionPoint(
|
|
contract,
|
|
this._seekSemiColon(contract.instrumented, expression.range[1] + 1) + 1,
|
|
{
|
|
type: 'injectRequirePost',
|
|
branchId: contract.branchId,
|
|
}
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Registers injections for if statement measurements (branches)
|
|
* @param {Object} contract instrumentation target
|
|
* @param {Object} expression AST node
|
|
*/
|
|
ifStatement(contract, expression) {
|
|
if (!this.enabled.branches) return;
|
|
|
|
this.addNewBranch(contract, expression);
|
|
|
|
if (expression.trueBody.type === 'Block') {
|
|
this._createInjectionPoint(
|
|
contract,
|
|
expression.trueBody.range[0] + 1,
|
|
{
|
|
type: 'injectBranch',
|
|
branchId: contract.branchId,
|
|
locationIdx: 0,
|
|
}
|
|
);
|
|
}
|
|
|
|
if (expression.falseBody && expression.falseBody.type === 'IfStatement') {
|
|
|
|
// Do nothing - we must be pre-preprocessing
|
|
|
|
} else if (expression.falseBody && expression.falseBody.type === 'Block') {
|
|
this._createInjectionPoint(
|
|
contract,
|
|
expression.falseBody.range[0] + 1,
|
|
{
|
|
type: 'injectBranch',
|
|
branchId: contract.branchId,
|
|
locationIdx: 1,
|
|
}
|
|
);
|
|
} else {
|
|
this._createInjectionPoint(
|
|
contract,
|
|
expression.trueBody.range[1] + 1,
|
|
{
|
|
type: 'injectEmptyBranch',
|
|
branchId: contract.branchId,
|
|
locationIdx: 1,
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = Registrar;
|
|
|