Skip to content
This repository was archived by the owner on Jul 9, 2021. It is now read-only.

Commit 1808458

Browse files
authored
Merge pull request #1498 from 0xProject/fix/sol-cov
Sol tracing fixes
2 parents 16a2cf7 + 64d99dc commit 1808458

23 files changed

+226
-81
lines changed

packages/devnet/genesis.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"eip158Block": 3,
99
"byzantiumBlock": 4,
1010
"clique": {
11-
"period": 0,
11+
"period": 1,
1212
"epoch": 30000
1313
}
1414
},

packages/ethereum-types/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,8 @@ export interface TraceParams {
306306
disableMemory?: boolean;
307307
disableStack?: boolean;
308308
disableStorage?: boolean;
309+
tracer?: string;
310+
timeout?: string;
309311
}
310312

311313
export type OutputField =

packages/sol-coverage/src/coverage_subprovider.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,19 +74,19 @@ export const coverageHandler: SingleFileSubtraceHandler = (
7474

7575
let sourceRanges = _.map(subtrace, structLog => pcToSourceRange[structLog.pc]);
7676
sourceRanges = _.compact(sourceRanges); // Some PC's don't map to a source range and we just ignore them.
77-
// By default lodash does a shallow object comparasion. We JSON.stringify them and compare as strings.
77+
// By default lodash does a shallow object comparison. We JSON.stringify them and compare as strings.
7878
sourceRanges = _.uniqBy(sourceRanges, s => JSON.stringify(s)); // We don't care if one PC was covered multiple times within a single transaction
7979
sourceRanges = _.filter(sourceRanges, sourceRange => sourceRange.fileName === absoluteFileName);
8080
const branchCoverage: BranchCoverage = {};
8181
const branchIds = _.keys(coverageEntriesDescription.branchMap);
8282
for (const branchId of branchIds) {
8383
const branchDescription = coverageEntriesDescription.branchMap[branchId];
84-
const isBranchCoveredByBranchIndex = _.map(branchDescription.locations, location => {
84+
const branchIndexToIsBranchCovered = _.map(branchDescription.locations, location => {
8585
const isBranchCovered = _.some(sourceRanges, range => utils.isRangeInside(range.location, location));
8686
const timesBranchCovered = Number(isBranchCovered);
8787
return timesBranchCovered;
8888
});
89-
branchCoverage[branchId] = isBranchCoveredByBranchIndex;
89+
branchCoverage[branchId] = branchIndexToIsBranchCovered;
9090
}
9191
const statementCoverage: StatementCoverage = {};
9292
const statementIds = _.keys(coverageEntriesDescription.statementMap);

packages/sol-coverage/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ export {
44
TruffleArtifactAdapter,
55
AbstractArtifactAdapter,
66
ContractData,
7+
SourceCodes,
8+
Sources,
79
} from '@0x/sol-tracing-utils';
810

911
export {

packages/sol-profiler/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ export {
33
SolCompilerArtifactAdapter,
44
TruffleArtifactAdapter,
55
ContractData,
6+
SourceCodes,
7+
Sources,
68
} from '@0x/sol-tracing-utils';
79

810
// HACK: ProfilerSubprovider is a hacky way to do profiling using coverage tools. Not production ready

packages/sol-profiler/src/profiler_subprovider.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export const profilerHandler: SingleFileSubtraceHandler = (
6363
): Coverage => {
6464
const absoluteFileName = contractData.sources[fileIndex];
6565
const profilerEntriesDescription = collectCoverageEntries(contractData.sourceCodes[fileIndex]);
66-
const gasConsumedByStatement: { [statementId: string]: number } = {};
66+
const statementToGasConsumed: { [statementId: string]: number } = {};
6767
const statementIds = _.keys(profilerEntriesDescription.statementMap);
6868
for (const statementId of statementIds) {
6969
const statementDescription = profilerEntriesDescription.statementMap[statementId];
@@ -83,14 +83,14 @@ export const profilerHandler: SingleFileSubtraceHandler = (
8383
}
8484
}),
8585
);
86-
gasConsumedByStatement[statementId] = totalGasCost;
86+
statementToGasConsumed[statementId] = totalGasCost;
8787
}
8888
const partialProfilerOutput = {
8989
[absoluteFileName]: {
9090
...profilerEntriesDescription,
9191
path: absoluteFileName,
9292
f: {}, // I's meaningless in profiling context
93-
s: gasConsumedByStatement,
93+
s: statementToGasConsumed,
9494
b: {}, // I's meaningless in profiling context
9595
},
9696
};

packages/sol-trace/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ export {
33
TruffleArtifactAdapter,
44
SolCompilerArtifactAdapter,
55
ContractData,
6+
SourceCodes,
7+
Sources,
68
} from '@0x/sol-tracing-utils';
79

810
export { RevertTraceSubprovider } from './revert_trace_subprovider';

packages/sol-trace/src/revert_trace_subprovider.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ export class RevertTraceSubprovider extends TraceCollectionSubprovider {
106106
continue;
107107
}
108108

109-
const fileIndex = contractData.sources.indexOf(sourceRange.fileName);
109+
const fileNameToFileIndex = _.invert(contractData.sources);
110+
const fileIndex = _.parseInt(fileNameToFileIndex[sourceRange.fileName]);
110111
const sourceSnippet = getSourceRangeSnippet(sourceRange, contractData.sourceCodes[fileIndex]);
111112
if (sourceSnippet !== null) {
112113
sourceSnippets.push(sourceSnippet);

packages/sol-tracing-utils/CHANGELOG.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,29 @@
11
[
2+
{
3+
"version": "4.0.0",
4+
"changes": [
5+
{
6+
"note": "Fix a bug with incorrect parsing of `sourceMaps` due to sources being in an array instead of a map",
7+
"pr": 1498
8+
},
9+
{
10+
"note": "Change the types of `ContractData.sources` and `ContractData.sourceCodes` to be objects instead of arrays",
11+
"pr": 1498
12+
},
13+
{
14+
"note": "Use custom JS tracer to speed up tracing on clients that support it (e.g., Geth)",
15+
"pr": 1498
16+
},
17+
{
18+
"note": "Log errors encountered in `TraceCollectionSubprovider`",
19+
"pr": 1498
20+
},
21+
{
22+
"note": "Add support for assembly statements",
23+
"pr": 1498
24+
}
25+
]
26+
},
227
{
328
"version": "3.0.0",
429
"changes": [

packages/sol-tracing-utils/src/artifact_adapters/sol_compiler_artifact_adapter.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as glob from 'glob';
55
import * as _ from 'lodash';
66
import * as path from 'path';
77

8-
import { ContractData } from '../types';
8+
import { ContractData, SourceCodes, Sources } from '../types';
99

1010
import { AbstractArtifactAdapter } from './abstract_artifact_adapter';
1111

@@ -43,9 +43,14 @@ export class SolCompilerArtifactAdapter extends AbstractArtifactAdapter {
4343
logUtils.warn(`${artifactFileName} doesn't contain bytecode. Skipping...`);
4444
continue;
4545
}
46-
let sources = _.keys(artifact.sources);
47-
sources = _.map(sources, relativeFilePath => path.resolve(this._sourcesPath, relativeFilePath));
48-
const sourceCodes = _.map(sources, (source: string) => fs.readFileSync(source).toString());
46+
const sources: Sources = {};
47+
const sourceCodes: SourceCodes = {};
48+
_.map(artifact.sources, (value: { id: number }, relativeFilePath: string) => {
49+
const filePath = path.resolve(this._sourcesPath, relativeFilePath);
50+
const fileContent = fs.readFileSync(filePath).toString();
51+
sources[value.id] = filePath;
52+
sourceCodes[value.id] = fileContent;
53+
});
4954
const contractData = {
5055
sourceCodes,
5156
sources,

packages/sol-tracing-utils/src/ast_visitor.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as _ from 'lodash';
22
import * as Parser from 'solidity-parser-antlr';
33

4-
import { BranchMap, FnMap, LocationByOffset, SingleFileSourceRange, StatementMap } from './types';
4+
import { BranchMap, FnMap, OffsetToLocation, SingleFileSourceRange, StatementMap } from './types';
55

66
export interface CoverageEntriesDescription {
77
fnMap: FnMap;
@@ -22,13 +22,13 @@ export class ASTVisitor {
2222
private readonly _branchMap: BranchMap = {};
2323
private readonly _modifiersStatementIds: number[] = [];
2424
private readonly _statementMap: StatementMap = {};
25-
private readonly _locationByOffset: LocationByOffset;
25+
private readonly _offsetToLocation: OffsetToLocation;
2626
private readonly _ignoreRangesBeginningAt: number[];
2727
// keep track of contract/function ranges that are to be ignored
2828
// so we can also ignore any children nodes within the contract/function
2929
private readonly _ignoreRangesWithin: Array<[number, number]> = [];
30-
constructor(locationByOffset: LocationByOffset, ignoreRangesBeginningAt: number[] = []) {
31-
this._locationByOffset = locationByOffset;
30+
constructor(offsetToLocation: OffsetToLocation, ignoreRangesBeginningAt: number[] = []) {
31+
this._offsetToLocation = offsetToLocation;
3232
this._ignoreRangesBeginningAt = ignoreRangesBeginningAt;
3333
}
3434
public getCollectedCoverageEntries(): CoverageEntriesDescription {
@@ -94,6 +94,39 @@ export class ASTVisitor {
9494
public InlineAssemblyStatement(ast: Parser.InlineAssemblyStatement): void {
9595
this._visitStatement(ast);
9696
}
97+
public AssemblyLocalDefinition(ast: Parser.AssemblyLocalDefinition): void {
98+
this._visitStatement(ast);
99+
}
100+
public AssemblyCall(ast: Parser.AssemblyCall): void {
101+
this._visitStatement(ast);
102+
}
103+
public AssemblyIf(ast: Parser.AssemblyIf): void {
104+
this._visitStatement(ast);
105+
}
106+
public AssemblyBlock(ast: Parser.AssemblyBlock): void {
107+
this._visitStatement(ast);
108+
}
109+
public AssemblyExpression(ast: Parser.AssemblyExpression): void {
110+
this._visitStatement(ast);
111+
}
112+
public AssemblyAssignment(ast: Parser.AssemblyAssignment): void {
113+
this._visitStatement(ast);
114+
}
115+
public LabelDefinition(ast: Parser.LabelDefinition): void {
116+
this._visitStatement(ast);
117+
}
118+
public AssemblySwitch(ast: Parser.AssemblySwitch): void {
119+
this._visitStatement(ast);
120+
}
121+
public AssemblyFunctionDefinition(ast: Parser.AssemblyFunctionDefinition): void {
122+
this._visitStatement(ast);
123+
}
124+
public AssemblyFor(ast: Parser.AssemblyFor): void {
125+
this._visitStatement(ast);
126+
}
127+
public SubAssembly(ast: Parser.SubAssembly): void {
128+
this._visitStatement(ast);
129+
}
97130
public BinaryOperation(ast: Parser.BinaryOperation): void {
98131
const BRANCHING_BIN_OPS = ['&&', '||'];
99132
if (_.includes(BRANCHING_BIN_OPS, ast.operator)) {
@@ -136,8 +169,8 @@ export class ASTVisitor {
136169
}
137170
private _getExpressionRange(ast: Parser.ASTNode): SingleFileSourceRange {
138171
const astRange = ast.range as [number, number];
139-
const start = this._locationByOffset[astRange[0]];
140-
const end = this._locationByOffset[astRange[1] + 1];
172+
const start = this._offsetToLocation[astRange[0]];
173+
const end = this._offsetToLocation[astRange[1] + 1];
141174
const range = {
142175
start,
143176
end,

packages/sol-tracing-utils/src/collect_coverage_entries.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,24 @@ import * as _ from 'lodash';
33
import * as parser from 'solidity-parser-antlr';
44

55
import { ASTVisitor, CoverageEntriesDescription } from './ast_visitor';
6-
import { getLocationByOffset } from './source_maps';
6+
import { getOffsetToLocation } from './source_maps';
77

88
const IGNORE_RE = /\/\*\s*solcov\s+ignore\s+next\s*\*\/\s*/gm;
99

1010
// Parsing source code for each transaction/code is slow and therefore we cache it
11-
const coverageEntriesBySourceHash: { [sourceHash: string]: CoverageEntriesDescription } = {};
11+
const sourceHashToCoverageEntries: { [sourceHash: string]: CoverageEntriesDescription } = {};
1212

1313
export const collectCoverageEntries = (contractSource: string) => {
1414
const sourceHash = ethUtil.sha3(contractSource).toString('hex');
15-
if (_.isUndefined(coverageEntriesBySourceHash[sourceHash]) && !_.isUndefined(contractSource)) {
15+
if (_.isUndefined(sourceHashToCoverageEntries[sourceHash]) && !_.isUndefined(contractSource)) {
1616
const ast = parser.parse(contractSource, { range: true });
17-
const locationByOffset = getLocationByOffset(contractSource);
17+
const offsetToLocation = getOffsetToLocation(contractSource);
1818
const ignoreRangesBegingingAt = gatherRangesToIgnore(contractSource);
19-
const visitor = new ASTVisitor(locationByOffset, ignoreRangesBegingingAt);
19+
const visitor = new ASTVisitor(offsetToLocation, ignoreRangesBegingingAt);
2020
parser.visit(ast, visitor);
21-
coverageEntriesBySourceHash[sourceHash] = visitor.getCollectedCoverageEntries();
21+
sourceHashToCoverageEntries[sourceHash] = visitor.getCollectedCoverageEntries();
2222
}
23-
const coverageEntriesDescription = coverageEntriesBySourceHash[sourceHash];
23+
const coverageEntriesDescription = sourceHashToCoverageEntries[sourceHash];
2424
return coverageEntriesDescription;
2525
};
2626

packages/sol-tracing-utils/src/get_source_range_snippet.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ interface ASTInfo {
1313
}
1414

1515
// Parsing source code for each transaction/code is slow and therefore we cache it
16-
const parsedSourceByHash: { [sourceHash: string]: Parser.ASTNode } = {};
16+
const hashToParsedSource: { [sourceHash: string]: Parser.ASTNode } = {};
1717

1818
/**
1919
* Gets the source range snippet by source range to be used by revert trace.
@@ -22,10 +22,10 @@ const parsedSourceByHash: { [sourceHash: string]: Parser.ASTNode } = {};
2222
*/
2323
export function getSourceRangeSnippet(sourceRange: SourceRange, sourceCode: string): SourceSnippet | null {
2424
const sourceHash = ethUtil.sha3(sourceCode).toString('hex');
25-
if (_.isUndefined(parsedSourceByHash[sourceHash])) {
26-
parsedSourceByHash[sourceHash] = Parser.parse(sourceCode, { loc: true });
25+
if (_.isUndefined(hashToParsedSource[sourceHash])) {
26+
hashToParsedSource[sourceHash] = Parser.parse(sourceCode, { loc: true });
2727
}
28-
const astNode = parsedSourceByHash[sourceHash];
28+
const astNode = hashToParsedSource[sourceHash];
2929
const visitor = new ASTInfoVisitor();
3030
Parser.visit(astNode, visitor);
3131
const astInfo = visitor.getASTInfoForRange(sourceRange);

packages/sol-tracing-utils/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ export {
2222
BranchMap,
2323
EvmCallStackEntry,
2424
FnMap,
25-
LocationByOffset,
25+
OffsetToLocation,
2626
StatementMap,
2727
TraceInfoBase,
2828
TraceInfoExistingContract,
2929
TraceInfoNewContract,
30+
Sources,
31+
SourceCodes,
3032
} from './types';
3133
export { collectCoverageEntries } from './collect_coverage_entries';
3234
export { TraceCollector, SingleFileSubtraceHandler } from './trace_collector';

packages/sol-tracing-utils/src/source_maps.ts

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as _ from 'lodash';
22

33
import { getPcToInstructionIndexMapping } from './instructions';
4-
import { LocationByOffset, SourceRange } from './types';
4+
import { OffsetToLocation, SourceCodes, SourceRange, Sources } from './types';
55

66
const RADIX = 10;
77

@@ -15,38 +15,41 @@ export interface SourceLocation {
1515
* Receives a string with newlines and returns a map of byte offset to LineColumn
1616
* @param str A string to process
1717
*/
18-
export function getLocationByOffset(str: string): LocationByOffset {
19-
const locationByOffset: LocationByOffset = { 0: { line: 1, column: 0 } };
18+
export function getOffsetToLocation(str: string): OffsetToLocation {
19+
const offsetToLocation: OffsetToLocation = { 0: { line: 1, column: 0 } };
2020
let currentOffset = 0;
2121
for (const char of str.split('')) {
22-
const location = locationByOffset[currentOffset];
22+
const location = offsetToLocation[currentOffset];
2323
const isNewline = char === '\n';
24-
locationByOffset[currentOffset + 1] = {
24+
offsetToLocation[currentOffset + 1] = {
2525
line: location.line + (isNewline ? 1 : 0),
2626
column: isNewline ? 0 : location.column + 1,
2727
};
2828
currentOffset++;
2929
}
30-
return locationByOffset;
30+
return offsetToLocation;
3131
}
3232

3333
/**
3434
* Parses a sourcemap string.
3535
* The solidity sourcemap format is documented here: https://github.com/ethereum/solidity/blob/develop/docs/miscellaneous.rst#source-mappings
36-
* @param sourceCodes sources contents
36+
* @param indexToSourceCode index to source code
3737
* @param srcMap source map string
3838
* @param bytecodeHex contract bytecode
39-
* @param sources sources file names
39+
* @param indexToSource index to source file path
4040
*/
4141
export function parseSourceMap(
42-
sourceCodes: string[],
42+
sourceCodes: SourceCodes,
4343
srcMap: string,
4444
bytecodeHex: string,
45-
sources: string[],
45+
sources: Sources,
4646
): { [programCounter: number]: SourceRange } {
4747
const bytecode = Uint8Array.from(Buffer.from(bytecodeHex, 'hex'));
4848
const pcToInstructionIndex: { [programCounter: number]: number } = getPcToInstructionIndexMapping(bytecode);
49-
const locationByOffsetByFileIndex = _.map(sourceCodes, s => (_.isUndefined(s) ? {} : getLocationByOffset(s)));
49+
const fileIndexToOffsetToLocation: { [fileIndex: number]: OffsetToLocation } = {};
50+
_.map(sourceCodes, (sourceCode: string, fileIndex: number) => {
51+
fileIndexToOffsetToLocation[fileIndex] = _.isUndefined(sourceCode) ? {} : getOffsetToLocation(sourceCode);
52+
});
5053
const entries = srcMap.split(';');
5154
let lastParsedEntry: SourceLocation = {} as any;
5255
const instructionIndexToSourceRange: { [instructionIndex: number]: SourceRange } = {};
@@ -66,14 +69,18 @@ export function parseSourceMap(
6669
length,
6770
fileIndex,
6871
};
69-
if (parsedEntry.fileIndex !== -1 && !_.isUndefined(locationByOffsetByFileIndex[parsedEntry.fileIndex])) {
72+
if (parsedEntry.fileIndex !== -1 && !_.isUndefined(fileIndexToOffsetToLocation[parsedEntry.fileIndex])) {
73+
const offsetToLocation = fileIndexToOffsetToLocation[parsedEntry.fileIndex];
7074
const sourceRange = {
7175
location: {
72-
start: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset],
73-
end: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset + parsedEntry.length],
76+
start: offsetToLocation[parsedEntry.offset],
77+
end: offsetToLocation[parsedEntry.offset + parsedEntry.length],
7478
},
7579
fileName: sources[parsedEntry.fileIndex],
7680
};
81+
if (sourceRange.location.start === undefined || sourceRange.location.end === undefined) {
82+
throw new Error(`Error while processing sourcemap: location out of range in ${sourceRange.fileName}`);
83+
}
7784
instructionIndexToSourceRange[i] = sourceRange;
7885
} else {
7986
// Some assembly code generated by Solidity can't be mapped back to a line of source code.

0 commit comments

Comments
 (0)