Instrument with injected function calls (#381)

pull/383/head
cgewecke 5 years ago committed by GitHub
parent 462b82962a
commit 9aa52f8ae6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  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; const self = this;
if (typeof info !== 'object' || !info.opcode ) return; 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; const idx = info.stack.length - 1;
let hash = web3Utils.toHex(info.stack[idx]).toString(); let hash = web3Utils.toHex(info.stack[idx]).toString();
hash = self._normalizeHash(hash); hash = self._normalizeHash(hash);

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

@ -51,6 +51,16 @@ parse.ContractDefinition = function(contract, expression) {
}; };
parse.ContractOrLibraryStatement = 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) { if (expression.subNodes) {
expression.subNodes.forEach(construct => { expression.subNodes.forEach(construct => {
parse[construct.type] && parse[construct.type] &&

@ -27,7 +27,6 @@
"istanbul": "^0.4.5", "istanbul": "^0.4.5",
"node-dir": "^0.1.17", "node-dir": "^0.1.17",
"req-cwd": "^1.0.1", "req-cwd": "^1.0.1",
"sha1": "^1.1.1",
"shelljs": "^0.8.3", "shelljs": "^0.8.3",
"solidity-parser-antlr": "^0.4.7", "solidity-parser-antlr": "^0.4.7",
"web3": "1.2.1", "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); 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', () => { it('should NOT pass tests if the contract has a compilation error', () => {
const info = util.instrumentAndCompile('app/SimpleError'); const info = util.instrumentAndCompile('app/SimpleError');
try { try {

@ -1614,11 +1614,6 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.2:
escape-string-regexp "^1.0.5" escape-string-regexp "^1.0.5"
supports-color "^5.3.0" 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: check-error@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" 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" shebang-command "^1.2.0"
which "^1.2.9" 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: crypto-browserify@3.12.0:
version "3.12.0" version "3.12.0"
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" 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" inherits "^2.0.1"
safe-buffer "^5.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: sha3@^1.2.2:
version "1.2.3" version "1.2.3"
resolved "https://registry.yarnpkg.com/sha3/-/sha3-1.2.3.tgz#ed5958fa8331df1b1b8529ca9fdf225a340c5418" resolved "https://registry.yarnpkg.com/sha3/-/sha3-1.2.3.tgz#ed5958fa8331df1b1b8529ca9fdf225a340c5418"

Loading…
Cancel
Save