You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
243 lines
8.0 KiB
243 lines
8.0 KiB
'use strict'
|
|
const util = require('./util')
|
|
const AstWalker = require('./astWalker')
|
|
|
|
/**
|
|
* Decompress the source mapping given by solc-bin.js
|
|
*/
|
|
function SourceMappingDecoder () {
|
|
// s:l:f:j
|
|
}
|
|
|
|
/**
|
|
* get a list of nodes that are at the given @arg position
|
|
*
|
|
* @param {String} astNodeType - type of node to return
|
|
* @param {Int} position - cursor position
|
|
* @return {Object} ast object given by the compiler
|
|
*/
|
|
SourceMappingDecoder.prototype.nodesAtPosition = nodesAtPosition
|
|
|
|
/**
|
|
* Decode the source mapping for the given @arg index
|
|
*
|
|
* @param {Integer} index - source mapping index to decode
|
|
* @param {String} mapping - compressed source mapping given by solc-bin
|
|
* @return {Object} returns the decompressed source mapping for the given index {start, length, file, jump}
|
|
*/
|
|
SourceMappingDecoder.prototype.atIndex = atIndex
|
|
|
|
/**
|
|
* Decode the given @arg value
|
|
*
|
|
* @param {string} value - source location to decode ( should be start:length:file )
|
|
* @return {Object} returns the decompressed source mapping {start, length, file}
|
|
*/
|
|
SourceMappingDecoder.prototype.decode = function (value) {
|
|
if (value) {
|
|
value = value.split(':')
|
|
return {
|
|
start: parseInt(value[0]),
|
|
length: parseInt(value[1]),
|
|
file: parseInt(value[2])
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Decode the source mapping for the given compressed mapping
|
|
*
|
|
* @param {String} mapping - compressed source mapping given by solc-bin
|
|
* @return {Array} returns the decompressed source mapping. Array of {start, length, file, jump}
|
|
*/
|
|
SourceMappingDecoder.prototype.decompressAll = function (mapping) {
|
|
const map = mapping.split(';')
|
|
const ret = []
|
|
for (let k in map) {
|
|
const compressed = map[k].split(':')
|
|
const sourceMap = {
|
|
start: compressed[0] ? parseInt(compressed[0]) : ret[ret.length - 1].start,
|
|
length: compressed[1] ? parseInt(compressed[1]) : ret[ret.length - 1].length,
|
|
file: compressed[2] ? parseInt(compressed[2]) : ret[ret.length - 1].file,
|
|
jump: compressed[3] ? compressed[3] : ret[ret.length - 1].jump
|
|
}
|
|
ret.push(sourceMap)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
/**
|
|
* Retrieve line/column position of each source char
|
|
*
|
|
* @param {String} source - contract source code
|
|
* @return {Array} returns an array containing offset of line breaks
|
|
*/
|
|
SourceMappingDecoder.prototype.getLinebreakPositions = function (source) {
|
|
const ret = []
|
|
for (let pos = source.indexOf('\n'); pos >= 0; pos = source.indexOf('\n', pos + 1)) {
|
|
ret.push(pos)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
/**
|
|
* Retrieve the line/column position for the given source mapping
|
|
*
|
|
* @param {Object} sourceLocation - object containing attributes {source} and {length}
|
|
* @param {Array} lineBreakPositions - array returned by the function 'getLinebreakPositions'
|
|
* @return {Object} returns an object {start: {line, column}, end: {line, column}} (line/column count start at 0)
|
|
*/
|
|
SourceMappingDecoder.prototype.convertOffsetToLineColumn = function (sourceLocation, lineBreakPositions) {
|
|
if (sourceLocation.start >= 0 && sourceLocation.length >= 0) {
|
|
return {
|
|
start: convertFromCharPosition(sourceLocation.start, lineBreakPositions),
|
|
end: convertFromCharPosition(sourceLocation.start + sourceLocation.length, lineBreakPositions)
|
|
}
|
|
} else {
|
|
return {
|
|
start: null,
|
|
end: null
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve the first @arg astNodeType that include the source map at arg instIndex
|
|
*
|
|
* @param {String} astNodeType - node type that include the source map instIndex
|
|
* @param {String} instIndex - instruction index used to retrieve the source map
|
|
* @param {String} sourceMap - source map given by the compilation result
|
|
* @param {Object} ast - ast given by the compilation result
|
|
*/
|
|
SourceMappingDecoder.prototype.findNodeAtInstructionIndex = findNodeAtInstructionIndex
|
|
|
|
function convertFromCharPosition (pos, lineBreakPositions) {
|
|
let line = util.findLowerBound(pos, lineBreakPositions)
|
|
if (lineBreakPositions[line] !== pos) {
|
|
line = line + 1
|
|
}
|
|
const beginColumn = line === 0 ? 0 : (lineBreakPositions[line - 1] + 1)
|
|
const column = pos - beginColumn
|
|
return {
|
|
line: line,
|
|
column: column
|
|
}
|
|
}
|
|
|
|
function sourceLocationFromAstNode (astNode) {
|
|
if (astNode.src) {
|
|
const split = astNode.src.split(':')
|
|
return {
|
|
start: parseInt(split[0]),
|
|
length: parseInt(split[1]),
|
|
file: parseInt(split[2])
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
function findNodeAtInstructionIndex (astNodeType, instIndex, sourceMap, ast) {
|
|
const sourceLocation = atIndex(instIndex, sourceMap)
|
|
return findNodeAtSourceLocation(astNodeType, sourceLocation, ast)
|
|
}
|
|
|
|
function findNodeAtSourceLocation (astNodeType, sourceLocation, ast) {
|
|
const astWalker = new AstWalker()
|
|
const callback = {}
|
|
let found = null
|
|
callback['*'] = function (node) {
|
|
const nodeLocation = sourceLocationFromAstNode(node)
|
|
if (!nodeLocation) {
|
|
return true
|
|
}
|
|
if (nodeLocation.start <= sourceLocation.start && nodeLocation.start + nodeLocation.length >= sourceLocation.start + sourceLocation.length) {
|
|
if (astNodeType === node.name) {
|
|
found = node
|
|
return false
|
|
} else {
|
|
return true
|
|
}
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
astWalker.walk(ast.legacyAST, callback)
|
|
return found
|
|
}
|
|
|
|
function nodesAtPosition (astNodeType, position, ast) {
|
|
const astWalker = new AstWalker()
|
|
const callback = {}
|
|
const found = []
|
|
callback['*'] = function (node) {
|
|
var nodeLocation = sourceLocationFromAstNode(node)
|
|
if (!nodeLocation) {
|
|
return
|
|
}
|
|
if (nodeLocation.start <= position && nodeLocation.start + nodeLocation.length >= position) {
|
|
if (!astNodeType || astNodeType === node.name) {
|
|
found.push(node)
|
|
if (astNodeType) return false
|
|
}
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
astWalker.walk(ast.legacyAST, callback)
|
|
return found
|
|
}
|
|
|
|
/**
|
|
* starts with the given @arg index and move backward until it can find all the values for start, length, file, jump
|
|
* if `file === -1` then the value of the sourcemap should be taken from the previous step,
|
|
* because some steps are internal subroutine for the compiler and doesn't link to any high level code.
|
|
*
|
|
* Solidity source maps format is
|
|
* - start:length:file:jump
|
|
* - jump can be 'i', 'o' or '-' (jump 'in' or 'out' of a function)
|
|
* - if no value is specified ( e.g "5:2" - no mention of 'file' and 'jump' ), actual values are the one of the step before
|
|
* - if the file (3rd value) has -1, the source maps should be discarded
|
|
*
|
|
* @param Int index - index in the bytecode to decode source mapping from
|
|
* @param Array mapping - source maps returned by the compiler. e.g 121:3741:0:-:0;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;121:3741:0;;;;;;;
|
|
* @return Object { start, length, file, jump }
|
|
*/
|
|
function atIndex (index, mapping) {
|
|
let ret = {}
|
|
const map = mapping.split(';')
|
|
if (index >= map.length) {
|
|
index = map.length - 1
|
|
}
|
|
for (let k = index; k >= 0; k--) {
|
|
let current = map[k]
|
|
if (!current.length) {
|
|
continue
|
|
}
|
|
current = current.split(':')
|
|
if (current[2] === '-1') { // if the current step has -1 for the file attribute, we discard it
|
|
// case: 'file' is not yet assigned, while processing the srcmap (reverse looping) to find 'start', 'length' (etc..), we tumble on -1 for the file.
|
|
// in that case the step has to be discarded
|
|
if (ret.file === undefined) ret = {}
|
|
continue
|
|
}
|
|
if (ret.start === undefined && current[0] && current[0] !== '-1' && current[0].length) {
|
|
ret.start = parseInt(current[0])
|
|
}
|
|
if (ret.length === undefined && current[1] && current[1] !== '-1' && current[1].length) {
|
|
ret.length = parseInt(current[1])
|
|
}
|
|
if (ret.file === undefined && current[2] && current[2] !== '-1' && current[2].length) {
|
|
ret.file = parseInt(current[2])
|
|
}
|
|
if (ret.jump === undefined && current[3] && current[3].length) {
|
|
ret.jump = current[3]
|
|
}
|
|
if (ret.start !== undefined && ret.length !== undefined && ret.file !== undefined && ret.jump !== undefined) {
|
|
break
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
module.exports = SourceMappingDecoder
|
|
|