diff options
Diffstat (limited to 'packages/sol-tracing-utils/src')
8 files changed, 110 insertions, 24 deletions
diff --git a/packages/sol-tracing-utils/src/artifact_adapters/sol_compiler_artifact_adapter.ts b/packages/sol-tracing-utils/src/artifact_adapters/sol_compiler_artifact_adapter.ts index 57391abbe..7d85f6c68 100644 --- a/packages/sol-tracing-utils/src/artifact_adapters/sol_compiler_artifact_adapter.ts +++ b/packages/sol-tracing-utils/src/artifact_adapters/sol_compiler_artifact_adapter.ts @@ -43,9 +43,14 @@ export class SolCompilerArtifactAdapter extends AbstractArtifactAdapter { logUtils.warn(`${artifactFileName} doesn't contain bytecode. Skipping...`); continue; } - let sources = _.keys(artifact.sources); - sources = _.map(sources, relativeFilePath => path.resolve(this._sourcesPath, relativeFilePath)); - const sourceCodes = _.map(sources, (source: string) => fs.readFileSync(source).toString()); + const sources: { [sourceId: number]: string } = {}; + const sourceCodes: { [sourceId: number]: string } = {}; + _.map(artifact.sources, (value: { id: number }, relativeFilePath: string) => { + const filePath = path.resolve(this._sourcesPath, relativeFilePath); + const fileContent = fs.readFileSync(filePath).toString(); + sources[value.id] = filePath; + sourceCodes[value.id] = fileContent; + }); const contractData = { sourceCodes, sources, diff --git a/packages/sol-tracing-utils/src/ast_visitor.ts b/packages/sol-tracing-utils/src/ast_visitor.ts index e55cdf6ec..fe71c974b 100644 --- a/packages/sol-tracing-utils/src/ast_visitor.ts +++ b/packages/sol-tracing-utils/src/ast_visitor.ts @@ -94,6 +94,39 @@ export class ASTVisitor { public InlineAssemblyStatement(ast: Parser.InlineAssemblyStatement): void { this._visitStatement(ast); } + public AssemblyLocalDefinition(ast: Parser.AssemblyLocalDefinition): void { + this._visitStatement(ast); + } + public AssemblyCall(ast: Parser.AssemblyCall): void { + this._visitStatement(ast); + } + public AssemblyIf(ast: Parser.AssemblyIf): void { + this._visitStatement(ast); + } + public AssemblyBlock(ast: Parser.AssemblyBlock): void { + this._visitStatement(ast); + } + public AssemblyExpression(ast: Parser.AssemblyExpression): void { + this._visitStatement(ast); + } + public AssemblyAssignment(ast: Parser.AssemblyAssignment): void { + this._visitStatement(ast); + } + public LabelDefinition(ast: Parser.LabelDefinition): void { + this._visitStatement(ast); + } + public AssemblySwitch(ast: Parser.AssemblySwitch): void { + this._visitStatement(ast); + } + public AssemblyFunctionDefinition(ast: Parser.AssemblyFunctionDefinition): void { + this._visitStatement(ast); + } + public AssemblyFor(ast: Parser.AssemblyFor): void { + this._visitStatement(ast); + } + public SubAssembly(ast: Parser.SubAssembly): void { + this._visitStatement(ast); + } public BinaryOperation(ast: Parser.BinaryOperation): void { const BRANCHING_BIN_OPS = ['&&', '||']; if (_.includes(BRANCHING_BIN_OPS, ast.operator)) { diff --git a/packages/sol-tracing-utils/src/source_maps.ts b/packages/sol-tracing-utils/src/source_maps.ts index af0fb4035..c674d32a3 100644 --- a/packages/sol-tracing-utils/src/source_maps.ts +++ b/packages/sol-tracing-utils/src/source_maps.ts @@ -33,20 +33,23 @@ export function getLocationByOffset(str: string): LocationByOffset { /** * Parses a sourcemap string. * The solidity sourcemap format is documented here: https://github.com/ethereum/solidity/blob/develop/docs/miscellaneous.rst#source-mappings - * @param sourceCodes sources contents + * @param sourceCodes sources contents by index * @param srcMap source map string * @param bytecodeHex contract bytecode - * @param sources sources file names + * @param sources sources file names by index */ export function parseSourceMap( - sourceCodes: string[], + sourceCodes: { [fileIndex: number]: string }, srcMap: string, bytecodeHex: string, - sources: string[], + sources: { [fileIndex: number]: string }, ): { [programCounter: number]: SourceRange } { const bytecode = Uint8Array.from(Buffer.from(bytecodeHex, 'hex')); const pcToInstructionIndex: { [programCounter: number]: number } = getPcToInstructionIndexMapping(bytecode); - const locationByOffsetByFileIndex = _.map(sourceCodes, s => (_.isUndefined(s) ? {} : getLocationByOffset(s))); + const locationByOffsetByFileIndex: { [fileIndex: number]: LocationByOffset } = {}; + _.map(sourceCodes, (sourceCode: string, fileIndex: number) => { + locationByOffsetByFileIndex[fileIndex] = _.isUndefined(sourceCode) ? {} : getLocationByOffset(sourceCode); + }); const entries = srcMap.split(';'); let lastParsedEntry: SourceLocation = {} as any; const instructionIndexToSourceRange: { [instructionIndex: number]: SourceRange } = {}; @@ -67,13 +70,17 @@ export function parseSourceMap( fileIndex, }; if (parsedEntry.fileIndex !== -1 && !_.isUndefined(locationByOffsetByFileIndex[parsedEntry.fileIndex])) { + const locationByOffset = locationByOffsetByFileIndex[parsedEntry.fileIndex]; const sourceRange = { location: { - start: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset], - end: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset + parsedEntry.length], + start: locationByOffset[parsedEntry.offset], + end: locationByOffset[parsedEntry.offset + parsedEntry.length], }, fileName: sources[parsedEntry.fileIndex], }; + if (sourceRange.location.start === undefined || sourceRange.location.end === undefined) { + throw new Error(`Error while processing sourcemap: location out of range in ${sourceRange.fileName}`); + } instructionIndexToSourceRange[i] = sourceRange; } else { // Some assembly code generated by Solidity can't be mapped back to a line of source code. diff --git a/packages/sol-tracing-utils/src/trace_collection_subprovider.ts b/packages/sol-tracing-utils/src/trace_collection_subprovider.ts index 25e38768d..3ae8566f9 100644 --- a/packages/sol-tracing-utils/src/trace_collection_subprovider.ts +++ b/packages/sol-tracing-utils/src/trace_collection_subprovider.ts @@ -20,6 +20,24 @@ export interface TraceCollectionSubproviderConfig { shouldCollectGasEstimateTraces: boolean; } +type AsyncFunc = (...args: any[]) => Promise<void>; + +// This wrapper outputs errors to console even if the promise gets ignored +// we need this because web3-provider-engine does not handler promises in +// the after function of next(after). +function logErrors(fn: AsyncFunc): AsyncFunc { + async function wrappedAsync(...args: any[]): Promise<void> { + try { + await fn(...args); + } catch (e) { + // tslint:disable-next-line no-console + console.error(e); + throw e; + } + } + return wrappedAsync; +} + // Because there is no notion of a call trace in the Ethereum rpc - we collect them in a rather non-obvious/hacky way. // On each call - we create a snapshot, execute the call as a transaction, get the trace, revert the snapshot. // That allows us to avoid influencing test behaviour. @@ -74,7 +92,7 @@ export abstract class TraceCollectionSubprovider extends Subprovider { next(); } else { const txData = payload.params[0]; - next(this._onTransactionSentAsync.bind(this, txData)); + next(logErrors(this._onTransactionSentAsync.bind(this, txData))); } return; @@ -83,7 +101,7 @@ export abstract class TraceCollectionSubprovider extends Subprovider { next(); } else { const callData = payload.params[0]; - next(this._onCallOrGasEstimateExecutedAsync.bind(this, callData)); + next(logErrors(this._onCallOrGasEstimateExecutedAsync.bind(this, callData))); } return; @@ -92,7 +110,7 @@ export abstract class TraceCollectionSubprovider extends Subprovider { next(); } else { const estimateGasData = payload.params[0]; - next(this._onCallOrGasEstimateExecutedAsync.bind(this, estimateGasData)); + next(logErrors(this._onCallOrGasEstimateExecutedAsync.bind(this, estimateGasData))); } return; diff --git a/packages/sol-tracing-utils/src/trace_collector.ts b/packages/sol-tracing-utils/src/trace_collector.ts index 943e208cf..f5dde8762 100644 --- a/packages/sol-tracing-utils/src/trace_collector.ts +++ b/packages/sol-tracing-utils/src/trace_collector.ts @@ -56,7 +56,7 @@ export class TraceCollector { this._singleFileSubtraceHandler = singleFileSubtraceHandler; } public async writeOutputAsync(): Promise<void> { - const finalCoverage = this._collector.getFinalCoverage(); + const finalCoverage: Coverage = this._collector.getFinalCoverage(); const stringifiedCoverage = JSON.stringify(finalCoverage, null, '\t'); await mkdirpAsync('coverage'); fs.writeFileSync('coverage/coverage.json', stringifiedCoverage); @@ -80,14 +80,14 @@ export class TraceCollector { const bytecodeHex = stripHexPrefix(bytecode); const sourceMap = isContractCreation ? contractData.sourceMap : contractData.sourceMapRuntime; const pcToSourceRange = parseSourceMap(contractData.sourceCodes, sourceMap, bytecodeHex, contractData.sources); - for (let fileIndex = 0; fileIndex < contractData.sources.length; fileIndex++) { + _.map(contractData.sources, (_sourcePath: string, fileIndex: string) => { const singleFileCoverageForTrace = this._singleFileSubtraceHandler( contractData, traceInfo.subtrace, pcToSourceRange, - fileIndex, + _.parseInt(fileIndex), ); this._collector.add(singleFileCoverageForTrace); - } + }); } } diff --git a/packages/sol-tracing-utils/src/trace_info_subprovider.ts b/packages/sol-tracing-utils/src/trace_info_subprovider.ts index 635a68f58..43853e152 100644 --- a/packages/sol-tracing-utils/src/trace_info_subprovider.ts +++ b/packages/sol-tracing-utils/src/trace_info_subprovider.ts @@ -12,11 +12,28 @@ export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider { protected abstract _handleTraceInfoAsync(traceInfo: TraceInfo): Promise<void>; protected async _recordTxTraceAsync(address: string, data: string | undefined, txHash: string): Promise<void> { await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0); - const trace = await this._web3Wrapper.getTransactionTraceAsync(txHash, { - disableMemory: true, - disableStack: false, - disableStorage: true, - }); + // For very large traces we use a custom tracer that outputs a format compatible with a + // regular trace. We only need the 2nd item on the stack when the instruction is a call. + // By not including othe stack values, we severly limit the amount of data to be collectd. + const tracer = ` + { + data: [], + step: function(log) { + const op = log.op.toString(); + const opn = 0 | log.op.toNumber(); + const pc = 0 | log.getPC(); + const depth = 0 | log.getDepth(); + const gasCost = 0 | log.getCost(); + const gas = 0 | log.getGas(); + const isCall = opn == 0xf1 || opn == 0xf2 || opn == 0xf4 || opn == 0xf5; + const stack = isCall ? ['0x'+log.stack.peek(1).toString(16), null] : null; + this.data.push({ pc, gasCost, depth, op, stack, gas }); + }, + fault: function() { }, + result: function() { return {structLogs: this.data}; } + } + `; + const trace = await this._web3Wrapper.getTransactionTraceAsync(txHash, { tracer, timeout: '600s' }); const tracesByContractAddress = getTracesByContractAddress(trace.structLogs, address); const subcallAddresses = _.keys(tracesByContractAddress); if (address === constants.NEW_CONTRACT) { diff --git a/packages/sol-tracing-utils/src/types.ts b/packages/sol-tracing-utils/src/types.ts index 54ade0400..fa10a93d6 100644 --- a/packages/sol-tracing-utils/src/types.ts +++ b/packages/sol-tracing-utils/src/types.ts @@ -81,8 +81,8 @@ export interface ContractData { sourceMap: string; runtimeBytecode: string; sourceMapRuntime: string; - sourceCodes: string[]; - sources: string[]; + sourceCodes: { [sourceId: number]: string }; + sources: { [sourceId: number]: string }; } // Part of the trace executed within the same context diff --git a/packages/sol-tracing-utils/src/utils.ts b/packages/sol-tracing-utils/src/utils.ts index d8bc65e73..644321f32 100644 --- a/packages/sol-tracing-utils/src/utils.ts +++ b/packages/sol-tracing-utils/src/utils.ts @@ -23,6 +23,12 @@ export const utils = { utils.compareLineColumn(childRange.end, parentRange.end) <= 0 ); }, + isRangeEqual(childRange: SingleFileSourceRange, parentRange: SingleFileSourceRange): boolean { + return ( + utils.compareLineColumn(parentRange.start, childRange.start) === 0 && + utils.compareLineColumn(childRange.end, parentRange.end) === 0 + ); + }, bytecodeToBytecodeRegex(bytecode: string): string { const bytecodeRegex = bytecode // Library linking placeholder: __ConvertLib____________________________ |