Instrument with injected function calls (#381)

truffle-plugin
cgewecke 5 years ago
parent d9353b6814
commit ec231a2328
  1. 2
      lib/collector.js
  2. 131
      lib/injector.js
  3. 10
      lib/parse.js
  4. 1
      package.json
  5. 26
      test/sources/solidity/contracts/statements/stack-too-deep.sol
  6. 5
      test/units/statements.js
  7. 18
      yarn.lock

@ -9,7 +9,7 @@ class DataCollector {
const self = this;
if (typeof info !== 'object' || !info.opcode ) return;
if (info.opcode.name.includes("PUSH") && info.stack.length > 0){
if (info.opcode.name.includes("PUSH1") && info.stack.length > 0){
const idx = info.stack.length - 1;
let hash = web3Utils.toHex(info.stack[idx]).toString();
hash = self._normalizeHash(hash);

@ -1,20 +1,19 @@
const sha1 = require("sha1");
const web3Utils = require("web3-utils");
class Injector {
constructor(){
this.hashCounter = 0;
this.definitionCounter = 0;
}
/**
* Generates solidity statement to inject for line, stmt, branch, fn 'events'
* @param {String} memoryVariable
* @param {String} hash hash key to an instrumentationData entry (see _getHash)
* @param {String} type instrumentation type, e.g. line, statement
// @return {String} ex: _sc_82e0891[0] = bytes32(0xdc08...08ed1); /* function */
_getInjectable(memoryVariable, hash, type){
return `${memoryVariable}[0] = bytes32(${hash}); /* ${type} */ \n`;
_split(contract, injectionPoint){
return {
start: contract.instrumented.slice(0, injectionPoint),
end: contract.instrumented.slice(injectionPoint)
}
}
_getInjectable(fileName, hash, type){
return `${this._getMethodIdentifier(fileName)}(${hash}); /* ${type} */ \n`;
}
_getHash(fileName) {
@ -22,34 +21,45 @@ class Injector {
return web3Utils.keccak256(`${fileName}:${this.hashCounter}`);
}
_getMethodIdentifier(fileName){
return `coverage_${web3Utils.keccak256(fileName).slice(0,10)}`
}
_getInjectionComponents(contract, injectionPoint, fileName, type){
const { start, end } = this._split(contract, injectionPoint);
const hash = this._getHash(fileName)
const injectable = this._getInjectable(fileName, hash, type);
return {
start: start,
end: end,
hash: hash,
injectable: injectable
}
}
/**
* Generates a solidity statement injection. Declared once per fn.
* Definition is the same for every fn in file.
* @param {String} fileName
* @return {String} ex: bytes32[1] memory _sc_82e0891
*/
_getMemoryVariableDefinition(fileName){
this.definitionCounter++;
return `\nbytes32[1] memory _sc_${sha1(fileName).slice(0,7)};\n`;
}
_getMemoryVariableAssignment(fileName){
return `\n_sc_${sha1(fileName).slice(0,7)}`;
_getHashMethodDefinition(fileName){
const hash = web3Utils.keccak256(fileName).slice(0,10);
const method = this._getMethodIdentifier(fileName);
return `\nfunction ${method}(bytes32 c__${hash}) public pure {}\n`;
}
injectLine(contract, fileName, injectionPoint, injection, instrumentation){
const type = 'line';
const start = contract.instrumented.slice(0, injectionPoint);
const end = contract.instrumented.slice(injectionPoint);
const { start, end } = this._split(contract, injectionPoint);
const newLines = start.match(/\n/g);
const linecount = ( newLines || []).length + 1;
contract.runnableLines.push(linecount);
const hash = this._getHash(fileName);
const memoryVariable = this._getMemoryVariableAssignment(fileName);
const injectable = this._getInjectable(memoryVariable, hash , type)
const hash = this._getHash(fileName)
const injectable = this._getInjectable(fileName, hash, type);
instrumentation[hash] = {
id: linecount,
@ -63,12 +73,13 @@ class Injector {
injectStatement(contract, fileName, injectionPoint, injection, instrumentation) {
const type = 'statement';
const start = contract.instrumented.slice(0, injectionPoint);
const end = contract.instrumented.slice(injectionPoint);
const hash = this._getHash(fileName);
const memoryVariable = this._getMemoryVariableAssignment(fileName);
const injectable = this._getInjectable(memoryVariable, hash, type)
const {
start,
end,
hash,
injectable
} = this._getInjectionComponents(contract, injectionPoint, fileName, type);
instrumentation[hash] = {
id: injection.statementId,
@ -82,13 +93,13 @@ class Injector {
injectFunction(contract, fileName, injectionPoint, injection, instrumentation){
const type = 'function';
const start = contract.instrumented.slice(0, injectionPoint);
const end = contract.instrumented.slice(injectionPoint);
const hash = this._getHash(fileName);
const memoryVariableDefinition = this._getMemoryVariableDefinition(fileName);
const memoryVariable = this._getMemoryVariableAssignment(fileName);
const injectable = this._getInjectable(memoryVariable, hash, type);
const {
start,
end,
hash,
injectable
} = this._getInjectionComponents(contract, injectionPoint, fileName, type);
instrumentation[hash] = {
id: injection.fnId,
@ -97,17 +108,18 @@ class Injector {
hits: 0
}
contract.instrumented = `${start}${memoryVariableDefinition}${injectable}${end}`;
contract.instrumented = `${start}${injectable}${end}`;
}
injectBranch(contract, fileName, injectionPoint, injection, instrumentation){
const type = 'branch';
const start = contract.instrumented.slice(0, injectionPoint);
const end = contract.instrumented.slice(injectionPoint);
const hash = this._getHash(fileName);
const memoryVariable = this._getMemoryVariableAssignment(fileName);
const injectable = this._getInjectable(memoryVariable, hash, type);
const {
start,
end,
hash,
injectable
} = this._getInjectionComponents(contract, injectionPoint, fileName, type);
instrumentation[hash] = {
id: injection.branchId,
@ -122,12 +134,13 @@ class Injector {
injectEmptyBranch(contract, fileName, injectionPoint, injection, instrumentation) {
const type = 'branch';
const start = contract.instrumented.slice(0, injectionPoint);
const end = contract.instrumented.slice(injectionPoint);
const hash = this._getHash(fileName);
const memoryVariable = this._getMemoryVariableAssignment(fileName);
const injectable = this._getInjectable(memoryVariable, hash, type);
const {
start,
end,
hash,
injectable
} = this._getInjectionComponents(contract, injectionPoint, fileName, type);
instrumentation[hash] = {
id: injection.branchId,
@ -142,12 +155,13 @@ class Injector {
injectAssertPre(contract, fileName, injectionPoint, injection, instrumentation) {
const type = 'assertPre';
const start = contract.instrumented.slice(0, injectionPoint);
const end = contract.instrumented.slice(injectionPoint);
const hash = this._getHash(fileName);
const memoryVariable = this._getMemoryVariableAssignment(fileName);
const injectable = this._getInjectable(memoryVariable, hash, type);
const {
start,
end,
hash,
injectable
} = this._getInjectionComponents(contract, injectionPoint, fileName, type);
instrumentation[hash] = {
id: injection.branchId,
@ -161,12 +175,13 @@ class Injector {
injectAssertPost(contract, fileName, injectionPoint, injection, instrumentation) {
const type = 'assertPost';
const start = contract.instrumented.slice(0, injectionPoint);
const end = contract.instrumented.slice(injectionPoint);
const hash = this._getHash(fileName);
const memoryVariable = this._getMemoryVariableAssignment(fileName);
const injectable = this._getInjectable(memoryVariable, hash, type);
const {
start,
end,
hash,
injectable
} = this._getInjectionComponents(contract, injectionPoint, fileName, type);
instrumentation[hash] = {
id: injection.branchId,
@ -177,6 +192,12 @@ class Injector {
contract.instrumented = `${start}${injectable}${end}`;
}
injectHashMethod(contract, fileName, injectionPoint, injection, instrumentation){
const start = contract.instrumented.slice(0, injectionPoint);
const end = contract.instrumented.slice(injectionPoint);
contract.instrumented = `${start}${this._getHashMethodDefinition(fileName)}${end}`;
}
};
module.exports = Injector;

@ -51,6 +51,16 @@ parse.ContractDefinition = function(contract, expression) {
};
parse.ContractOrLibraryStatement = function(contract, expression) {
// We need to define a method to pass coverage hashes into at top of each contract.
// This lets us get a fresh stack for the hash and avoid stack-too-deep errors.
const start = expression.range[0];
const end = contract.instrumented.slice(expression.range[0]).indexOf('{') + 1;
const loc = start + end;;
(contract.injectionPoints[loc])
? contract.injectionPoints[loc].push({ type: 'injectHashMethod'})
: contract.injectionPoints[loc] = [{ type: 'injectHashMethod'}];
if (expression.subNodes) {
expression.subNodes.forEach(construct => {
parse[construct.type] &&

@ -27,7 +27,6 @@
"istanbul": "^0.4.5",
"node-dir": "^0.1.17",
"req-cwd": "^1.0.1",
"sha1": "^1.1.1",
"shelljs": "^0.8.3",
"solidity-parser-antlr": "^0.4.7",
"web3": "1.2.1",

@ -0,0 +1,26 @@
pragma solidity ^0.5.0;
contract Test {
// 15 fn args + 1 local variable assignment
// will normally compile w/out stack too deep
// error.
function a(
uint _a,
uint _b,
uint _c,
uint _d,
uint _e,
uint _f,
uint _g,
uint _h,
uint _i,
uint _j,
uint _k,
uint _l,
uint _m,
uint _n,
uint _o
) public {
uint x = _a;
}
}

@ -48,6 +48,11 @@ describe('generic statements', () => {
util.report(info.solcOutput.errors);
});
it('should instrument without triggering stack-too-deep', () => {
const info = util.instrumentAndCompile('statements/stack-too-deep');
util.report(info.solcOutput.errors);
});
it('should NOT pass tests if the contract has a compilation error', () => {
const info = util.instrumentAndCompile('app/SimpleError');
try {

@ -1614,11 +1614,6 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.2:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
"charenc@>= 0.0.1":
version "0.0.2"
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=
check-error@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
@ -2028,11 +2023,6 @@ cross-spawn@^5.0.1:
shebang-command "^1.2.0"
which "^1.2.9"
"crypt@>= 0.0.1":
version "0.0.2"
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=
crypto-browserify@3.12.0:
version "3.12.0"
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
@ -7379,14 +7369,6 @@ sha.js@^2.4.0, sha.js@^2.4.8:
inherits "^2.0.1"
safe-buffer "^5.0.1"
sha1@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/sha1/-/sha1-1.1.1.tgz#addaa7a93168f393f19eb2b15091618e2700f848"
integrity sha1-rdqnqTFo85PxnrKxUJFhjicA+Eg=
dependencies:
charenc ">= 0.0.1"
crypt ">= 0.0.1"
sha3@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/sha3/-/sha3-1.2.3.tgz#ed5958fa8331df1b1b8529ca9fdf225a340c5418"

Loading…
Cancel
Save