Code coverage for Solidity smart-contracts
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.
solidity-coverage/parse.js

479 lines
18 KiB

const parse = {};
function createOrAppendInjectionPoint(contract, key, value) {
if (contract.injectionPoints[key]) {
contract.injectionPoints[key].push(value);
} else {
contract.injectionPoints[key] = [value];
}
}
function instrumentAssignmentExpression(contract, expression) {
// The only time we instrument an assignment expression is if there's a conditional expression on
// the right
if (expression.right.type === 'ConditionalExpression') {
if (expression.left.type === 'DeclarativeExpression' || expression.left.type === 'Identifier') {
// Then we need to go from bytes32 varname = (conditional expression)
// to bytes32 varname; (,varname) = (conditional expression)
createOrAppendInjectionPoint(contract, expression.left.end, {
type: 'literal', string: '; (,' + expression.left.name + ')',
});
instrumentConditionalExpression(contract, expression.right);
} else {
console.log(contract, expression.left);
process.exit();
}
}
}
function instrumentConditionalExpression(contract, expression) {
contract.branchId += 1;
const startline = (contract.instrumented.slice(0, expression.start).match(/\n/g) || []).length + 1;
const startcol = expression.start - contract.instrumented.slice(0, expression.start).lastIndexOf('\n') - 1;
const consequentStartCol = startcol + (contract, expression.consequent.start - expression.start);
const consequentEndCol = consequentStartCol + (contract, expression.consequent.end - expression.consequent.start);
const alternateStartCol = startcol + (contract, expression.alternate.start - expression.start);
const alternateEndCol = alternateStartCol + (contract, expression.alternate.end - expression.alternate.start);
// NB locations for conditional branches in istanbul are length 1 and associated with the : and ?.
contract.branchMap[contract.branchId] = {
line: startline,
type: 'cond-expr',
locations: [{
start: {
line: startline, column: consequentStartCol,
},
end: {
line: startline, column: consequentEndCol,
},
}, {
start: {
line: startline, column: alternateStartCol,
},
end: {
line: startline, column: alternateEndCol,
},
}],
};
// Right, this could be being used just by itself or as an assignment. In the case of the latter, because
// the comma operator doesn't exist, we're going to have to get funky.
// if we're on a line by ourselves, this is easier
//
// Now if we've got to wrap the expression it's being set equal to, do that...
// Wrap the consequent
createOrAppendInjectionPoint(contract, expression.consequent.start, {
type: 'openParen',
});
createOrAppendInjectionPoint(contract, expression.consequent.start, {
type: 'callBranchEvent', comma: true, branchId: contract.branchId, locationIdx: 0,
});
createOrAppendInjectionPoint(contract, expression.consequent.end, {
type: 'closeParen',
});
// Wrap the alternate
createOrAppendInjectionPoint(contract, expression.alternate.start, {
type: 'openParen',
});
createOrAppendInjectionPoint(contract, expression.alternate.start, {
type: 'callBranchEvent', comma: true, branchId: contract.branchId, locationIdx: 1,
});
createOrAppendInjectionPoint(contract, expression.alternate.end, {
type: 'closeParen',
});
}
function instrumentStatement(contract, expression) {
contract.statementId += 1;
// We need to work out the lines and columns the expression starts and ends
const startline = (contract.instrumented.slice(0, expression.start).match(/\n/g) || []).length + 1;
const startcol = expression.start - contract.instrumented.slice(0, expression.start).lastIndexOf('\n') - 1;
const expressionContent = contract.instrumented.slice(expression.start, expression.end);
const endline = startline + (contract, expressionContent.match('/\n/g') || []).length;
let endcol;
if (expressionContent.lastIndexOf('\n') >= 0) {
endcol = contract.instrumented.slice(expressionContent.lastIndexOf('\n'), expression.end).length - 1;
} else {
endcol = startcol + (contract, expressionContent.length - 1);
}
contract.statementMap[contract.statementId] = {
start: {
line: startline, column: startcol,
},
end: {
line: endline, column: endcol,
},
};
createOrAppendInjectionPoint(contract, expression.start, {
type: 'statement', statementId: contract.statementId,
});
}
function instrumentLine(contract, expression) {
// what's the position of the most recent newline?
const startchar = expression.start;
const endchar = expression.end;
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);
if (contract.instrumented.slice(lastNewLine, startchar).trim().length === 0 &&
contract.instrumented.slice(endchar, nextNewLine).replace(';', '').trim().length === 0) {
createOrAppendInjectionPoint(contract, lastNewLine + 1, {
type: 'callEvent',
});
} else if (contract.instrumented.slice(lastNewLine, startchar).replace('{', '').trim().length === 0 &&
contract.instrumented.slice(endchar, nextNewLine).replace(/[;}]/g, '').trim().length === 0) {
createOrAppendInjectionPoint(contract, expression.start, {
type: 'callEvent',
});
}
// Is everything before us and after us on this line whitespace?
}
function instrumentFunctionDeclaration(contract, expression) {
contract.fnId += 1;
const startline = (contract.instrumented.slice(0, expression.start).match(/\n/g) || []).length + 1;
// We need to work out the lines and columns the function declaration starts and ends
const startcol = expression.start - contract.instrumented.slice(0, expression.start).lastIndexOf('\n') - 1;
const endlineDelta = contract.instrumented.slice(expression.start).indexOf('{') + 1;
const functionDefinition = contract.instrumented.slice(expression.start, expression.start + endlineDelta);
const lastChar = contract.instrumented.slice(expression.start, expression.start + endlineDelta + 1).slice(-1);
const endline = startline + (functionDefinition.match(/\n/g) || []).length;
const endcol = functionDefinition.length - functionDefinition.lastIndexOf('\n');
contract.fnMap[contract.fnId] = {
name: expression.name,
line: startline,
loc: {
start: {
line: startline, column: startcol,
},
end: {
line: endline, column: endcol,
},
},
};
if (lastChar === '}') {
createOrAppendInjectionPoint(contract, expression.start + endlineDelta, {
type: 'callFunctionEvent', fnId: contract.fnId,
});
} else {
createOrAppendInjectionPoint(contract, expression.start + endlineDelta + 1, {
type: 'callFunctionEvent', fnId: contract.fnId,
});
}
}
function instrumentIfStatement(contract, expression) {
contract.branchId += 1;
const startline = (contract.instrumented.slice(0, expression.start).match(/\n/g) || []).length + 1;
const startcol = expression.start - contract.instrumented.slice(0, expression.start).lastIndexOf('\n') - 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,
},
}],
};
if (expression.consequent.type === 'BlockStatement') {
createOrAppendInjectionPoint(contract, expression.consequent.start + 1, {
type: 'callBranchEvent', branchId: contract.branchId, locationIdx: 0,
});
}
if (expression.alternate && expression.alternate.type === 'IfStatement') {
// Do nothing - we must be pre-preprocessor, so don't bother instrumenting -
// when we're actually instrumenting, this will never happen (we've wrapped it in
// a block statement)
} else if (expression.alternate && expression.alternate.type === 'BlockStatement') {
createOrAppendInjectionPoint(contract, expression.alternate.start + 1, {
type: 'callBranchEvent', branchId: contract.branchId, locationIdx: 1,
});
} else {
createOrAppendInjectionPoint(contract, expression.consequent.end, {
type: 'callEmptyBranchEvent', branchId: contract.branchId, locationIdx: 1,
});
}
}
parse.AssignmentExpression = function parseAssignmentExpression(contract, expression) {
instrumentStatement(contract, expression);
instrumentAssignmentExpression(contract, expression);
// parse[expression.left.type](contract, expression.left);
// parse[expression.right.type](contract, expression.right);
};
parse.ConditionalExpression = function parseConditionalExpression(contract, expression) {
instrumentStatement(contract, expression);
instrumentConditionalExpression(contract, expression);
// parse[expression.test.left.type](contract, expression.test.left);
// parse[expression.test.right.type](contract, expression.test.right);
// parse[expression.consequent.type](contract, expression.consequent);
// parse[expression.alternate.type](contract, expression.alternate);
};
parse.Identifier = function parseIdentifier(contract, expression) {
};
parse.InformalParameter = function parseInformalParameter(contract, expression) {
};
parse.Literal = function ParseLiteral(contract, expression) {
};
parse.ModifierName = function ParseModifierName(contract, expression) {
};
parse.Modifiers = function parseModifier(contract, modifiers) {
if (modifiers) {
modifiers.forEach(modifier => {
parse[modifier.type](contract, modifier);
});
}
};
parse.ThisExpression = function parseThisExpression(contract, expression) {
};
parse.ReturnStatement = function parseReturnStatement(contract, expression) {
instrumentStatement(contract, expression);
// if (expression.argument){
// parse[expression.argument.type](contract, expression.argument);
// }
};
parse.NewExpression = function parseNewExpression(contract, expression) {
parse[expression.callee.type](contract, expression.callee);
// expression.arguments.forEach(construct => {
// parse[construct.type](construct);
// });
};
parse.MemberExpression = function parseMemberExpression(contract, expression) {
parse[expression.object.type](contract, expression.object);
// parse[expression.property.type](contract, expression.property);
};
parse.CallExpression = function parseCallExpression(contract, expression) {
instrumentStatement(contract, expression);
parse[expression.callee.type](contract, expression.callee);
// for (x in expression.arguments){
// parse[expression.arguments[x].type](contract, expression.arguments[x])
// }
};
parse.UnaryExpression = function parseUnaryExpression(contract, expression) {
parse[expression.argument.type](contract, expression.argument);
};
parse.ThrowStatement = function parseThrowStatement(contract, expression) {
instrumentStatement(contract, expression);
};
parse.DenominationLiteral = function parseDenominationLiteral(contract, expression) {
};
parse.BinaryExpression = function parseBinaryExpression(contract, expression) {
// parse[expression.left.type](contract, expression.left);
// parse[expression.right.type](contract, expression.right);
};
parse.IfStatement = function parseIfStatement(contract, expression) {
instrumentStatement(contract, expression);
instrumentIfStatement(contract, expression);
// We can't instrument
// if (x==1)
//
// So don't count x==1 as a statement - just the if as a whole.
// parse[expression.test.type](contract, expression.test)
parse[expression.consequent.type](contract, expression.consequent);
if (expression.alternate) {
parse[expression.alternate.type](contract, expression.alternate);
}
};
parse.SequenceExpression = function parseSequenceExpression(contract, expression) {
};
parse.ImportStatement = function parseImportStatemtn(contract, expression) {
};
parse.DeclarativeExpression = function parseDeclarativeExpression(contract, expression) {
};
parse.ExpressionStatement = function parseExpressionStatement(contract, content) {
// if (instrument){instrumentStatement(content.expression)}
parse[content.expression.type](contract, content.expression);
};
parse.EnumDeclaration = function parseEnumDeclaration(contract, expression) {
};
parse.EventDeclaration = function parseEventDeclaration(contract, expression) {
};
parse.VariableDeclarationTuple = function parseVariableDeclarationTuple(contract, expression) {
parse[expression.init.type](contract, expression.init);
};
parse.BlockStatement = function parseBlockStatement(contract, expression) {
for (let x = 0; x < expression.body.length; x++) {
instrumentLine(contract, expression.body[x]);
parse[expression.body[x].type](contract, expression.body[x]);
}
};
parse.VariableDeclaration = function parseVariableDeclaration(contract, expression) {
instrumentStatement(contract, expression);
if (expression.declarations.length > 1) {
console.log('more than one declaration');
}
parse[expression.declarations[0].id.type](contract, expression.declarations[0].id);
// parse[expression.declarations[0].init.type](contract, expression.declarations[0].init);
};
parse.Type = function parseType(contract, expression) {
};
parse.UsingStatement = function parseUsingStatement(contract, expression) {
parse[expression.for.type](contract, expression.for);
};
parse.FunctionDeclaration = function parseFunctionDeclaration(contract, expression) {
parse.Modifiers(contract, expression.modifiers);
if (expression.body) {
instrumentFunctionDeclaration(contract, expression);
parse[expression.body.type](contract, expression.body);
}
};
parse.ContractStatement = function parseContractStatement(contract, expression) {
// Inject our coverage event if we're covering
// This is harder because of where .start and .end represent, and how documented comments are validated
// by solc upon compilation. From the start of this contract statement, find the first '{', and inject
// there.
const injectionPoint = expression.start + contract.instrumented.slice(expression.start).indexOf('{') + 2;
if (contract.injectionPoints[injectionPoint]) {
contract.injectionPoints[expression.start + contract.instrumented.slice(expression.start).indexOf('{') + 2].push({
type: 'eventDefinition',
});
} else {
contract.injectionPoints[expression.start + contract.instrumented.slice(expression.start).indexOf('{') + 2] = [{
type: 'eventDefinition',
}];
}
expression.body.forEach(construct => {
if (!Array.isArray(construct)) {
parse[construct.type](contract, construct);
}
});
};
parse.LibraryStatement = function parseLibraryStatement(contract, expression) {
// Inject our coverage event;
// This is harder because of where .start and .end represent, and how documented comments are validated
// by solc upon compilation. From the start of this contract statement, find the first '{', and inject
// there.
const injectionPoint = expression.start + contract.instrumented.slice(expression.start).indexOf('{') + 2;
if (contract.injectionPoints[injectionPoint]) {
contract.injectionPoints[expression.start + contract.instrumented.slice(expression.start).indexOf('{') + 2].push({
type: 'eventDefinition',
});
} else {
contract.injectionPoints[expression.start + contract.instrumented.slice(expression.start).indexOf('{') + 2] = [{
type: 'eventDefinition',
}];
}
expression.body.forEach(construct => {
parse[construct.type](contract, construct);
});
};
parse.ModifierDeclaration = function parseModifierDeclaration(contract, expression) {
instrumentFunctionDeclaration(contract, expression);
parse[expression.body.type](contract, expression.body);
};
parse.Program = function parseProgram(contract, expression) {
expression.body.forEach(construct => {
parse[construct.type](contract, construct);
});
};
parse.WhileStatement = function parseWhileStatement(contract, expression) {
instrumentStatement(contract, expression);
parse[expression.body.type](contract, expression.body);
};
parse.ForStatement = function parseForStatement(contract, expression) {
instrumentStatement(contract, expression);
parse[expression.body.type](contract, expression.body);
};
parse.StructDeclaration = function parseStructDeclaration(contract, expression) {
};
parse.PragmaStatement = function parsePragmaStatement(contract, expression) {
};
parse.UpdateExpression = function parseUpdateExpression(contract, expression) {
};
parse.MappingExpression = function parseMappingExpression(contract, expression) {
};
parse.VariableDeclarator = function parseVariableDeclarator(contract, expression) {
};
parse.EmptyStatement = function parseEmptyStatement(contract, expression) {
};
parse.DebuggerStatement = function parseDebuggerStatement(contract, expression) {
};
parse.IsStatement = function parseIsStatement(contract, expression) {
};
parse.DeclarativeExpressionList = function parseDeclarativeExpressionList(contract, expression) {
};
parse.ModifierArgument = function parseModifierArgument(contract, expression) {
};
parse.PlaceholderStatement = function parsePlaceholderStatement(contract, expression) {
};
parse.FunctionName = function parseFunctionName(contract, expression) {
};
parse.DoWhileStatement = function parseDoWhileStatement(contract, expression) {
};
parse.ForInStatement = function parseForInStatement(contract, expression) {
};
parse.ContinueStatement = function ParseContinueStatement(contract, expression) {
};
parse.BreakStatement = function parseBreakStatement(contract, expression) {
};
module.exports = parse;