All files / solidity-coverage/lib collector.js

100% Statements 45/45
95.65% Branches 22/23
100% Functions 6/6
100% Lines 41/41

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115            116x   116x 116x 116x 116x 116x                 5563565x   5563565x 5563565x 1877548x 1877548x 1877548x                   1877548x   1877548x     243898x 19694x 19694x 19694x   243898x                             1877548x 458871x     458871x 236185x 236185x 236185x 236185x     1418677x 30863x 30864x 30863x   1877548x                 116x       116x 3596x 3596x     116x 1856x 1856x     116x               80x       1x  
/**
 * Writes data from the VM step to the in-memory
 * coverage map constructed by the Instrumenter.
 */
class DataCollector {
  constructor(instrumentationData={}, viaIR){
    this.instrumentationData = instrumentationData;
 
    this.validOpcodes = this._getOpcodes(viaIR);
    this.lastHash = null;
    this.viaIR = viaIR;
    this.pcZeroCounter = 0;
    this.lastPcZeroCount = 0;
  }
 
  /**
   * VM step event handler. Detects instrumentation hashes when they are pushed to the
   * top of the stack. This runs millions of times - trying to keep it fast.
   * @param  {Object} info  vm step info
   */
  step(info){
    if (info.pc === 0) this.pcZeroCounter++;
 
    try {
      if (this.validOpcodes[info.opcode.name] && info.stack.length > 0){
        const idx = info.stack.length - 1;
        let hash = '0x' +  info.stack[idx].toString(16);
        this._registerHash(hash);
      }
    } catch (err) { /*Ignore*/ };
  }
 
  /**
   * Normalizes has string and marks hit.
   * @param  {String} hash bytes32 hash
   */
  _registerHash(hash){
    hash = this._normalizeHash(hash);
 
    if(this.instrumentationData[hash]){
      // abi.encode (used to circumvent viaIR) sometimes puts the hash on the stack twice
      // We should only skip duplicate hashes *within* a transaction (see issue #863)
      if (this.lastHash !== hash || this.lastPcZeroCount !== this.pcZeroCounter) {
        this.lastHash = hash;
        this.lastPcZeroCount = this.pcZeroCounter;
        this.instrumentationData[hash].hits++
      }
      return;
    }
  }
 
  /**
   * Left-pads zero prefixed bytes8 hashes to length 18. The '11' in the
   * comparison below is arbitrary. It provides a margin for recurring zeros
   * but prevents left-padding shorter irrelevant hashes
   *
   * @param  {String} hash  data hash from evm stack.
   * @return {String}       0x prefixed hash of length 18.
   */
  _normalizeHash(hash){
    // viaIR sometimes right-pads the hashes out to 32 bytes
    // but it doesn't preserve leading zeroes when it does this
    if (this.viaIR && hash.length >= 18) {
      hash = hash.slice(0,18);
 
      // Detect and recover from viaIR mangled hashes by left-padding single `0`
      if(!this.instrumentationData[hash]) {
        hash = hash.slice(2);
        hash = '0' + hash;
        hash = hash.slice(0,16);
        hash = '0x' + hash;
      }
 
    } else if (hash.length < 18 && hash.length > 11){
      hash = hash.slice(2);
      while(hash.length < 16) hash = '0' + hash;
      hash = '0x' + hash
    }
    return hash;
  }
 
    /**
   * Generates a list of all the opcodes to inspect for instrumentation hashes
   * When viaIR is true, it includes all DUPs and PUSHs, so things are a little slower.
   * @param {boolean} viaIR
   */
  _getOpcodes(viaIR) {
    let opcodes = {
      "PUSH1": true
    };
 
    for (let i = 2; i <= 32; i++) {
      const key = "PUSH" + i;
      opcodes[key] = viaIR;
    };
 
    for (let i = 1; i <= 16; i++ ) {
      const key = "DUP" + i;
      opcodes[key] = viaIR;
    }
 
    return opcodes;
  }
 
  /**
   * Unit test helper
   * @param {Object} data  Instrumenter.instrumentationData
   */
  _setInstrumentationData(data){
    this.instrumentationData = data;
  }
}
 
module.exports = DataCollector;