// var SolidityParser = require("solidity-parser"); var solparse = require("solparse"); var fs = require('fs'); var path = require("path"); module.exports = function(pathToFile, instrumentingActive){ // var result = SolidityParser.parseFile("./" + pathToFile); var contract = fs.readFileSync("./" + pathToFile).toString(); var result = solparse.parse(contract); var instrumented = ""; const __INDENTATION__ = " "; var parse = {}; var runnableLines=[]; var fnMap = {}; var fnId = 0; var linecount = 1; var fileName = path.basename(pathToFile); var injectionPoints = {}; function addInstrumentationEvent(charcount){ injectionPoints[charcount] = {type:"callEvent"}; return "Coverage('" + fileName + "'," + linecount + ");\n"; } parse["AssignmentExpression"] = function (expression, instrument){ if (instrument){ retval += addInstrumentationEvent(expression.start); } var retval = ""; retval += parse[expression.left.type](expression.left, instrument); retval += expression.operator; retval += parse[expression.right.type](expression.right, instrument) + ';'; return retval; } parse["ConditionalExpression"] = function(expression, instrument){ return parse[expression.test.left.type](expression.test.left) + expression.test.operator + parse[expression.test.right.type](expression.test.right) + '?' + parse[expression.consequent.type](expression.consequent) + ":" + parse[expression.alternate.type](expression.alternate); } parse["Identifier"] = function(expression, instrument){ return expression.name; } parse["InformalParameter"] = function(expression, instrument){ return expression.literal.literal; } parse["Literal"] = function(expression, instrument){ if (typeof expression.value==='string' && expression.value.slice(0,2)!=="0x"){ return '"' + expression.value + '"'; }else{ return expression.value; } } parse["ModifierName"] = function(expression, instrument){ var retvalue = expression.name if (expression.params && expression.params.length>0){ retvalue += '('; for (x in expression.params){ var param = expression.params[x]; retvalue += param.literal.literal + ', '; } retvalue = retvalue.slice(0,-2); retvalue += ')'; } return retvalue; }; parse["Modifiers"] = function(modifiers){ retvalue = ""; var retModifier = null; var constModifier = null; for (x in modifiers){ if (modifiers[x].name==='returns'){ retModifier = x; continue; }else if (modifiers[x].name==='constant'){ constModifier = x; }else{ retvalue+=parse[modifiers[x].type](modifiers[x]); retvalue += ' '; } } if (constModifier) {retvalue += 'constant '}; if (retModifier){ retvalue += ' returns '; retvalue += ' ('; for (p in modifiers[retModifier].params){ param = modifiers[retModifier].params[p]; retvalue+= parse[param.type](param); retvalue += ', '; } retvalue = retvalue.slice(0,-2); retvalue +=')'; } return retvalue; } parse["ThisExpression"] = function(expression, instrument){ return 'this' } parse["ReturnStatement"] = function(expression, instrument){ var retval = ""; if (instrument){ retval += addInstrumentationEvent(expression.start); } return retval + 'return ' + parse[expression.argument.type](expression.argument, instrument) + ';'; } parse["NewExpression"] = function(expression, instrument){ var retval = 'new ' + parse[expression.callee.type](expression.callee); retval += '('; for (x in expression.arguments){ retval += parse[expression.arguments[x].type](expression.arguments[x]) + ", " } if (expression.arguments && expression.arguments.length){ retval = retval.slice(0,-2); } retval+=")" return retval } parse["MemberExpression"] = function (expression){ if (!expression.computed){ return parse[expression.object.type](expression.object) + "." + parse[expression.property.type](expression.property); }else{ return parse[expression.object.type](expression.object) + "[" + parse[expression.property.type](expression.property) + "]"; } } parse["CallExpression"] = function (expression){ var retval = parse[expression.callee.type](expression.callee) + "("; for (x in expression.arguments){ retval += parse[expression.arguments[x].type](expression.arguments[x]) + ", " } if (expression.arguments && expression.arguments.length){ retval = retval.slice(0,-2); } retval+=")" return retval } parse["UnaryExpression"] = function(expression, instrument){ if (expression.operator==='delete'){ return expression.operator + ' ' + parse[expression.argument.type](expression.argument); } return expression.operator + parse[expression.argument.type](expression.argument); } parse["ThrowStatement"] = function(expression, instrument){ var retval = ""; if (instrument){ retval += addInstrumentationEvent(expression.start); } return retval + 'throw' } parse["BinaryExpression"] = function(expression, instrument){ return '(' + parse[expression.left.type](expression.left) + expression.operator + parse[expression.right.type](expression.right) + ')'; } parse["IfStatement"] = function(expression, instrument){ var retval = ""; if (instrument){ retval += addInstrumentationEvent(expression.start); } retval += "if ("; retval += parse[expression.test.type](expression.test, instrument) + "){" retval += newLine('{'); retval += parse[expression.consequent.type](expression.consequent, instrument) retval += newLine(retval.slice(-1)); retval += "}"; if (expression.alternate){ retval += 'else '; if (expression.alternate.type!=="IfStatement"){ retval += '{'; retval += newLine('{'); } retval += parse[expression.alternate.type](expression.alternate, instrument) if (expression.alternate.type!=="IfStatement"){ retval += '}'; retval += newLine('}'); } } return retval; } parse["SequenceExpression"] = function(expression, instrument){ retval = "("; for (x in expression.expressions){ retval += parse[expression.expressions[x].type](expression.expressions[x]) + ', '; } if (expression.expressions && expression.expressions.length>0){ //remove trailing comma and space if needed retval = retval.slice(0,-2); } retval += ')'; return retval; } parse["ImportStatement"] = function(expression, instrument){ return 'import "' + expression.from + '"'; } parse["DeclarativeExpression"] = function(expression, instrument){ return expression.literal.literal + ' ' + (expression.is_public ? "public " : "") + (expression.is_constant ? "constant " : "") + expression.name; } parse["ExpressionStatement"] = function(content, instrument){ var retval = ""; if (instrument){ retval += addInstrumentationEvent(content.start); } if (content.expression.literal && content.expression.literal.literal && content.expression.literal.literal.type==="MappingExpression"){ return retval + 'mapping (' + content.expression.literal.literal.from.literal + ' => ' + content.expression.literal.literal.to.literal + ') '+ content.expression.name; }else { return retval + parse[content.expression.type](content.expression); } } parse["EnumDeclaration"] = function(expression, instrument){ var retvalue = 'enum ' + expression.name + ' {'; for (x in expression.members){ retvalue += expression.members[x] + ', '; } retvalue = retvalue.slice(0,-2); retvalue += '}'; return retvalue; } parse["EventDeclaration"]=function(expression, instrument){ var retval = 'event ' + expression.name + '('; for (x in expression.params){ var param = expression.params[x]; retval += param.literal.literal + ' ' + (param.isIndexed ? 'true' : '') + param.id + ', '; } if (expression.params && expression.params.length>0){ //remove trailing comma and space if needed retval = retval.slice(0,-2); } retval += ')'; return retval } parse["VariableDeclarationTuple"] = function(expression, instrument){ var retval = ""; if (instrument){ retval += addInstrumentationEvent(expression.start); } retval += "var ("; for (x in expression.declarations){ retval += expression.declarations[x].id.name + ', '; } retval = retval.slice(0,-2); retval += ") = "; retval+=parse[expression.init.type](expression.init) return retval; } parse["BlockStatement"] = function(expression, instrument){ var retval = ""; for (var x=0; x < expression.body.length; x++){ retval += parse[expression.body[x].type](expression.body[x], instrument); retval += newLine(retval.slice(-1)); } return retval; } parse["VariableDeclaration"] = function(expression, instrument){ if (expression.declarations.length>1){ console.log('more than one declaration') } retval = ""; if (instrument){ retval += addInstrumentationEvent(expression.start); } return retval + "var " + parse[expression.declarations[0].id.type](expression.declarations[0].id) + " = " + parse[expression.declarations[0].init.type](expression.declarations[0].init); } parse["Type"] = function(expression, instrument){ return expression.literal; } parse["UsingStatement"] = function(expression, instrument){ return "using " + expression.library + " for " + parse[expression.for.type](expression.for) + ";"; } parse["FunctionDeclaration"] = function(expression, instrument){ retval = 'function ' + (expression.name ? expression.name : "") + '(' for (x in expression.params){ var param = expression.params[x]; retval += param.literal.literal + ' ' + (param.isIndexed ? 'true' : '') + param.id + ', '; } if (expression.params && expression.params.length>0){ retval = retval.slice(0,-2); } retval += ')'; retval += parse["Modifiers"](expression.modifiers); if (expression.body){ fnId+=1; linecount = (contract.slice(0,expression.start).match(/\n/g)||[]).length + 1; //We need to work out the lines and columns the function declaration starts and ends var startline = linecount; var startcol = expression.start - contract.slice(0,expression.start).lastIndexOf('\n') -1; var endlineDelta = contract.slice(expression.start).indexOf('{')+1; var functionDefinition = contract.slice(expression.start, expression.start + endlineDelta); var endline = startline + (functionDefinition.match(/\n/g)||[]).length; var endcol = functionDefinition.length - functionDefinition.lastIndexOf('\n') fnMap[fnId] = {name: expression.name, line: linecount, loc:{start:{line: startline, column:startcol},end:{line:endline, column:endcol}}} injectionPoints[expression.start + endlineDelta +1] = {type: "callFunctionEvent", fnId: fnId}; retval+='{' + newLine('{'); retval += parse[expression.body.type](expression.body, instrumentingActive); retval+='}' + newLine('}'); } return retval; } parse["ContractStatement"] = function(expression, instrument){ var retval = ""; retval += 'contract ' + expression.name if (expression.is.length>0){ retval += ' is ' for (x in expression.is){ retval += expression.is[x].name + ', ' } retval = retval.slice(0,-2); } retval += ' {' + newLine('{'); //Inject our coverage event if we're covering if (instrumentingActive){ //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. injectionPoints[expression.start + contract.slice(expression.start).indexOf('{')+2] = {type:"eventDefinition"}; retval += "event Coverage(string fileName, uint256 lineNumber);\n"; //We're injecting this, so don't count the newline } for (x in expression.body){ retval+=parse[expression.body[x].type](expression.body[x], instrument); retval += newLine(retval.slice(-1)); } retval += '}' + newLine('}'); return retval; } parse["LibraryStatement"] = function(expression, instrument){ var retval = ""; retval += 'library ' + expression.name + ' {' + newLine('{'); //Inject our coverage event; if(instrumentingActive){ //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. injectionPoints[expression.start + contract.slice(expression.start).indexOf('{')+2] = {type:"eventDefinition"}; retval += "event Coverage(string fileName, uint256 lineNumber);\n"; //We're injecting this, so don't count the newline } for (x in expression.body){ retval+=parse[expression.body[x].type](expression.body[x], instrument); retval += newLine(retval.slice(-1)); } retval += '}' + newLine('}'); return retval; } parse["ModifierDeclaration"] = function(expression, instrument){ var retval = ""; retval += 'modifier ' + expression.name + '('; for (x in expression.params){ var param = expression.params[x]; retval += param.literal.literal + ' ' + (param.isIndexed ? 'true' : '') + param.id + ', '; } if (expression.params && expression.params.length>0){ retval = retval.slice(0,-2); } retval += '){'; retval += newLine(retval.slice(-1)); retval += parse[expression.body.type](expression.body, instrumentingActive); retval += newLine(retval.slice(-1)); retval +='}'; retval += newLine(retval.slice(-1)); return retval; } parse["Program"] = function(expression, instrument){ retval = ""; for (x in expression.body){ retval+=parse[expression.body[x].type](expression.body[x], instrument); retval += newLine(retval.slice(-1)); } return retval; } function newLine(lastchar){ linecount+=1; if (['}','{',';','_','\n'].indexOf(lastchar)>-1){ return '\n'; }else{ return ';\n'; } } var instrumented = parse[result.type](result); //We have to iterate through these injection points in descending order to not mess up //the injection process. var sortedPoints = Object.keys(injectionPoints).sort(function(a,b){return a-b}); for (x = sortedPoints.length-1; x>=0; x--){ injectionPoint = sortedPoints[x]; if (injectionPoints[injectionPoint].type==='callEvent'){ linecount = (contract.slice(0, injectionPoint).match(/\n/g)||[]).length + 1; runnableLines.push(linecount); contract = contract.slice(0, injectionPoint) + "Coverage('" + fileName + "'," + linecount + ");\n" + contract.slice(injectionPoint); }else if (injectionPoints[injectionPoint].type==='callFunctionEvent'){ contract = contract.slice(0, injectionPoint) + "FunctionCoverage('" + fileName + "'," + injectionPoints[injectionPoint].fnId + ");\n" + contract.slice(injectionPoint); }else{ contract = contract.slice(0, injectionPoint) + "event Coverage(string fileName, uint256 lineNumber);\nevent FunctionCoverage(string fileName, uint256 fnId);\n" + contract.slice(injectionPoint); } } return {contract: contract, runnableLines: runnableLines, fnMap: fnMap}; }