Home Reference Source

lib/srcmap.js

// Things involving the richer solc source map with its AST.
// We use this to filter out some MythX error messages.
//
'use strict';

const assert  = require('assert');
const remixUtil = require('remix-lib/src/util');
const SourceMappingDecoder = require('../compat/remix-lib/sourceMappingDecoder.js');
const opcodes = require('remix-lib/src/code/opcodes');

module.exports = {
    /**
     *  Return the VariableDeclaration AST node associated with instIndex
     *  if there is one. Otherwise return null.
     *  @param {sourceLocation} string  - solc srcmap used to associate the instruction
     *                                    with an ast node
     *  @param {ast}                    - solc root AST for contract
     *  @return {AST node or null}
     *
     */
    isVariableDeclaration: function (srcmap, ast) {
        const sourceMappingDecoder = new SourceMappingDecoder();
        const sourceLocation = sourceMappingDecoder.decode(srcmap);
        return sourceMappingDecoder.findNodeAtSourceLocation('VariableDeclaration',
            sourceLocation, ast);
    },

    /**
     *  Return the true is AST node is a public array.
     *  @param {node} AST node     - bytecode offset of instruction
     *  @return {boolean}
     *
     */
    isDynamicArray: function (node) {
        // FIXME: do we want to check:
        // constant: false
        // storageLocation: 'default'
        return (node.stateVariable &&
            node.visibility === 'public' &&
            node.typeName.nodeType === 'ArrayTypeName');
    },

    /**
      *  Takes a bytecode hexstring and returns a map indexed by offset
      *  that give the instruction number for that offset.
      *
      *  @param {hexstr} string     - bytecode hexstring
      *  @return {array mapping bytecode offset to an instruction number}
      *
      */
    makeOffset2InstNum: function(hexstr) {
        const bytecode = remixUtil.hexToIntArray(hexstr);
        const instMap = {};
        let j = -1;
        for (let i = 0; i < bytecode.length; i++) {
            j++;
            const opcode = opcodes(bytecode[i], true);
            if (opcode.name.slice(0, 4) === 'PUSH') {
                let length = bytecode[i] - 0x5f;
                i += length;
            }
            instMap[i] = j;
        }
        return instMap;
    },

    // FIXME: seenIndices is not being used. Remove it?
    /**
     *  @param {String} sourceMap     - solc-type sourceMap
     *  @return {Set} a set containing the "file" indices seen in a sourceMap
    */
    seenIndices: function(sourceMap) {
        const seen = new Set();
        const srcArray = sourceMap.split(';');
        for (const src of srcArray) {
            const fields = src.split(':');
            if (fields.length >= 3) {
                const index = fields[2];
                // File index -1 means no file exists.
                // Value '' means that the field is empty but present
                // to be able to give a 4th value.
                // Skip either of these.
                if (index !== '-1' && index !== '') {
                    seen.add(index);
                }
            }
        }
        return seen;
    },

    // FIXME: this is just a stopgap measure.
    // The caller in mythx should be fixed to we don't need this.
    /**
     *  @param {String} sourceMap     - solc-type sourceMap
     *  @return take sourceMap entries and turn them into file index 0
    */
    zeroedSourceMap: function(sourceMap) {
        const srcArray = sourceMap.split(';');
        let modArray = [];
        let indexSeen = -2;
        for (const src of srcArray) {
            const fields = src.split(':');
            if (fields.length >= 3) {
                const index = fields[2];
                if (index !== '-1' && index !== '') {
                    if (indexSeen !== -2) {
                        assert(indexSeen === index,
                            `assuming only one index ${indexSeen} needs moving; saw ${index} as well`);
                    }
                    fields[2] = '0';
                }
            }
            const modFields = fields.join(':');
            modArray.push(modFields);
        }
        return modArray.join(';');
    },
};