diff --git a/instrumentSolidity.js b/instrumentSolidity.js index 51a983a..5eee7e5 100644 --- a/instrumentSolidity.js +++ b/instrumentSolidity.js @@ -1,24 +1,42 @@ -var SolidityParser = require("solidity-parser"); +// var SolidityParser = require("solidity-parser"); +var solparse = require("solparse"); +var fs = require('fs'); + var path = require("path"); module.exports = function(pathToFile, instrument){ - var result = SolidityParser.parseFile("./" + pathToFile); + // 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 linecount = 1; var fileName = path.basename(pathToFile); - var dottable = ['msg', 'tx', 'this']; + var dottable = ['msg', 'tx', 'this', 'bytes']; + + function addInstrumentationEvent(){ + runnableLines.push(linecount); + return "Coverage('" + fileName + "'," + linecount + ");\n"; + } - parse["AssignmentExpression"] = function (expression){ + parse["AssignmentExpression"] = function (expression, instrument){ + if (instrument){ retval += addInstrumentationEvent(); } var retval = ""; - retval += parse[expression.left.type](expression.left); + retval += parse[expression.left.type](expression.left, instrument); retval += expression.operator; - retval += parse[expression.right.type](expression.right); + retval += parse[expression.right.type](expression.right, instrument) + ';'; + if (dottable.indexOf(expression.right.name)>=0){ + dottable.push(expression.left.name); + } return retval; } + parse["ConditionalExpression"] = function(expression){ + 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){ return expression.name; } @@ -28,37 +46,91 @@ module.exports = function(pathToFile, instrument){ } parse["Literal"] = function(expression){ - return expression.value; + if (typeof expression.value==='string' && expression.value.slice(0,2)!=="0x"){ + return '"' + expression.value + '"'; + }else{ + return expression.value; + } } + + parse["ModifierName"] = function(expression){ + 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{ - console.log('unknown modifier: ', modifiers[x]); + retvalue+=parse[modifiers[x].type](modifiers[x]); + retvalue += ' '; } } + if (constModifier) {retvalue += 'constant '}; if (retModifier){ - retvalue += ' returns (' + 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["ReturnStatement"] = function(expression){ - return 'return ' + parse[expression.argument.type](expression.argument) + ';'; + parse["ThisExpression"] = function(expression){ + return 'this' + } + + parse["ReturnStatement"] = function(expression, instrument){ + var retval = ""; + if (instrument){ retval += addInstrumentationEvent(); } + + return retval + 'return ' + parse[expression.argument.type](expression.argument, instrument) + ';'; + } + + parse["NewExpression"] = function(expression){ + 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 (dottable.indexOf(expression.object.name)>-1){ + // return contract.slice(expression.start, expression.end); + // var shouldDot = false; + // if (dottable.indexOf(expression.object.name)>=0){shouldDot = true;} + // if (expression.object.callee){ + // if (dottable.indexOf(expression.object.callee.name)>=0){shouldDot=true;} + // } + 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) + "]"; @@ -78,13 +150,58 @@ module.exports = function(pathToFile, instrument){ return retval } + parse["UnaryExpression"] = function(expression){ + 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(); } + return retval + 'throw' + } + parse["BinaryExpression"] = function(expression){ - return parse[expression.left.type](expression.left) + expression.operator + parse[expression.right.type](expression.right); + return '(' + parse[expression.left.type](expression.left) + expression.operator + parse[expression.right.type](expression.right) + ')'; } - parse["IfStatement"] = function(expression){ - var retval = "if ("; - retval += parse[expression.test.type](expression.test) + "){" + parse[expression.consequent.type](expression.consequent) + "}"; + parse["IfStatement"] = function(expression, instrument){ + var retval = ""; + if (instrument){ retval += addInstrumentationEvent(); } + 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){ + 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; } @@ -93,18 +210,31 @@ module.exports = function(pathToFile, instrument){ return 'import "' + expression.from + '"'; } - parse["ExpressionStatement"] = function(content){ - if (content.expression.type==="AssignmentExpression"){ - return parse["AssignmentExpression"](content.expression); - }else if (content.expression.type==="CallExpression"){ - return parse["CallExpression"](content.expression); - }else if(content.expression.literal.literal.type==="MappingExpression"){ - return 'mapping (' + content.expression.literal.literal.from.literal + ' => ' + content.expression.literal.literal.to.literal + ') '+ content.expression.name; - }else{ - console.log(content); + parse["DeclarativeExpression"] = function(expression){ + 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(); } + 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){ + var retvalue = 'enum ' + expression.name + ' {'; + dottable.push(expression.name); + for (x in expression.members){ + retvalue += expression.members[x] + ', '; + } + retvalue = retvalue.slice(0,-2); + retvalue += '}'; + return retvalue; + } + parse["EventDeclaration"]=function(expression){ var retval = 'event ' + expression.name + '('; for (x in expression.params){ @@ -119,16 +249,68 @@ module.exports = function(pathToFile, instrument){ return retval } - parse["BlockStatement"] = function(expression){ - if (expression.body.length>1){ - console.log('bigger blockstatement...') + parse["VariableDeclarationTuple"] = function(expression){ + var retval = ""; + if (instrument){ retval += addInstrumentationEvent(); } + + 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(); } + 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){ + return expression.literal; + } + + parse["UsingStatement"] = function(expression){ + return "using " + expression.library + " for " + parse[expression.for.type](expression.for) + ";"; + } + + parse["FunctionDeclaration"] = function(expression){ + 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){ + retval+='{' + newLine('{'); + retval += parse[expression.body.type](expression.body, instrument); + retval+='}' + newLine('}'); } - return parse[expression.body[0].type](expression.body[0]); + return retval; } function newLine(lastchar){ linecount+=1; - if (['}','{',';'].indexOf(lastchar)>-1){ + if (['}','{',';','_','\n'].indexOf(lastchar)>-1){ return '\n'; }else{ return ';\n'; @@ -141,7 +323,46 @@ module.exports = function(pathToFile, instrument){ function printBody(content, indented, cover){ if (content.body){ if (content.type === "ContractStatement"){ - instrumented += 'contract ' + content.name + ' {' + newLine('{'); + instrumented += 'contract ' + content.name + + if (content.is.length>0){ + instrumented += ' is ' + for (x in content.is){ + instrumented += content.is[x].name + ', ' + } + instrumented = instrumented.slice(0,-2); + } + + instrumented += ' {' + newLine('{'); + //Inject our coverage event if we're covering + if (instrument){ + instrumented += "event Coverage(string fileName, uint256 lineNumber);\n"; //We're injecting this, so don't count the newline + } + + for (x in content.body){ + printBody(content.body[x], indented+1, cover); + } + instrumented += '}' + newLine('}'); + }else if (content.type === "FunctionDeclaration"){ + instrumented += parse[content.type](content); + // instrumented += __INDENTATION__.repeat(indented) + 'function ' + (content.name ? content.name : "") + '(' + // for (x in content.params){ + // var param = content.params[x]; + // instrumented += param.literal.literal + ' ' + (param.isIndexed ? 'true' : '') + param.id + ', '; + // if (param.literal.literal==='address'){ + // dottable.push(param.id); + // } + // } + // if (content.params && content.params.length>0){ + // instrumented = instrumented.slice(0,-2); + // } + // instrumented += ')'; + // instrumented += parse["Modifiers"](content.modifiers); + // instrumented+='{' + newLine('{'); + // printBody(content.body, indented+1, instrument); + // instrumented+=__INDENTATION__.repeat(indented) +'}' + newLine('}'); + }else if (content.type === "LibraryStatement"){ + instrumented += 'library ' + content.name + ' {' + newLine('{'); //Inject our coverage event; instrumented += "event Coverage(string fileName, uint256 lineNumber);\n"; //We're injecting this, so don't count the newline @@ -149,8 +370,8 @@ module.exports = function(pathToFile, instrument){ printBody(content.body[x], indented+1, cover); } instrumented += '}' + newLine('}'); - }else if (content.type === "FunctionDeclaration"){ - instrumented += __INDENTATION__.repeat(indented) + 'function ' + content.name + '(' + }else if (content.type === "ModifierDeclaration"){ + instrumented += 'modifier ' + content.name + '('; for (x in content.params){ var param = content.params[x]; instrumented += param.literal.literal + ' ' + (param.isIndexed ? 'true' : '') + param.id + ', '; @@ -158,21 +379,24 @@ module.exports = function(pathToFile, instrument){ if (content.params && content.params.length>0){ instrumented = instrumented.slice(0,-2); } - instrumented += ')'; - instrumented += parse["Modifiers"](content.modifiers); - instrumented+='{' + newLine('{'); - printBody(content.body, indented+1, instrument); - instrumented+=__INDENTATION__.repeat(indented) +'}' + newLine('}'); - }else if (content.type === "LibraryStatement"){ - instrumented += 'library ' + content.name + ' {' + newLine('{'); - //Inject our coverage event; - instrumented += "event Coverage(string fileName, uint256 lineNumber);\n"; //We're injecting this, so don't count the newline + instrumented += '){'; + instrumented += newLine(instrumented.slice(-1)); + instrumented += parse[content.body.type](content.body, instrument); + instrumented += newLine(instrumented.slice(-1)); + instrumented +='}'; + instrumented += newLine(instrumented.slice(-1)); + }else if (content.type === "BlockStatement"){ + instrumented += parse['BlockStatement'](content, cover); + }else if (content.type ==="Program"){ + //I don't think we need to do anything here... for (x in content.body){ - printBody(content.body[x], indented+1, cover); + printBody(content.body[x], indented, cover); } - instrumented += '}' + newLine('}'); + }else{ + console.log(content); + process.exit() for (x in content.body){ printBody(content.body[x], indented, cover); } diff --git a/runCoveredTests.js b/runCoveredTests.js index bc21f83..0b387b2 100644 --- a/runCoveredTests.js +++ b/runCoveredTests.js @@ -14,14 +14,18 @@ shell.mv('./contracts/', './originalContracts'); shell.mkdir('./contracts/'); //For each contract in originalContracts, get the canonical version and the instrumented version shell.ls('./originalContracts/*.sol').forEach(function(file) { - if (file !== './originalContracts/Migrations.sol') { + if (file !== 'originalContracts/Migrations.sol') { + console.log("=================") + console.log(file); + console.log("=================") var instrumentedContractInfo = getInstrumentedVersion(file, true); var canonicalContractInfo = getInstrumentedVersion(file, false); fs.writeFileSync('./canonicalContracts/' + path.basename(file), canonicalContractInfo.contract); fs.writeFileSync('./contracts/' + path.basename(file), instrumentedContractInfo.contract); } - shell.cp("./originalContracts/Migrations.sol", "./contracts/Migrations.sol"); }); +shell.cp("./originalContracts/Migrations.sol", "./contracts/Migrations.sol"); +shell.cp("./originalContracts/Migrations.sol", "./canonicalContracts/Migrations.sol"); var filter = web3.eth.filter('latest'); var res = web3.currentProvider.send({ @@ -31,10 +35,7 @@ var res = web3.currentProvider.send({ id: new Date().getTime() }); var filterid = res.result; - shell.exec("truffle test"); - - //Again, once that truffle issue gets solved, we don't have to call these again here shell.ls('./originalContracts/*.sol').forEach(function(file) { if (file !== './originalContracts/Migrations.sol') { @@ -68,7 +69,7 @@ for (idx in res.result) { fs.writeFileSync('./coverage.json', JSON.stringify(coverage)); -shell.exec("istanbul report text") +shell.exec("istanbul report html") shell.rm('-rf', './contracts'); shell.rm('-rf', './canonicalContracts');