aboutsummaryrefslogtreecommitdiff
path: root/lib/compilers/solidity.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/compilers/solidity.js')
-rw-r--r--lib/compilers/solidity.js184
1 files changed, 155 insertions, 29 deletions
diff --git a/lib/compilers/solidity.js b/lib/compilers/solidity.js
index 6053de4ce..383177ff0 100644
--- a/lib/compilers/solidity.js
+++ b/lib/compilers/solidity.js
@@ -22,16 +22,17 @@
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
+import * as fs from 'fs';
import path from 'path';
-import _ from 'underscore';
+import {BaseCompiler} from '../base-compiler';
-import { BaseCompiler } from '../base-compiler';
-
-import { ClangParser } from './argument-parsers';
+import {ClangParser} from './argument-parsers';
export class SolidityCompiler extends BaseCompiler {
- static get key() { return 'solidity'; }
+ static get key() {
+ return 'solidity';
+ }
getSharedLibraryPathsAsArguments() {
return [];
@@ -43,8 +44,11 @@ export class SolidityCompiler extends BaseCompiler {
optionsForFilter(filters, outputFilename, userOptions) {
return [
- '--combined-json', 'asm', // We use it instead of `--asm-json` to have compacted json
- '-o', 'contracts',
+ // We use --combined-json instead of `--asm-json` to have compacted json
+ '--combined-json',
+ 'asm,ast,generated-sources,generated-sources-runtime',
+ '-o',
+ 'contracts',
];
}
@@ -62,36 +66,158 @@ export class SolidityCompiler extends BaseCompiler {
return {asm: [{text: result.asm}]};
}
+ // solc gives us a character range for each asm instruction,
+ // so open the input file and figure out what line each
+ // character is on
+ const inputFile = fs.readFileSync(result.inputFilename);
+ let currentLine = 1;
+ const charToLine = inputFile.map(c => {
+ const line = currentLine;
+ if (c === '\n'.codePointAt(0)) {
+ ++currentLine;
+ }
+ return line;
+ });
+
+ const asm = JSON.parse(result.asm);
return {
- asm: Object.entries(JSON.parse(result.asm).contracts)
- .sort(([_name1, data1], [_name2, data2]) =>
- data1.asm['.code'][0].begin - data2.asm['.code'][0].begin,
- )
+ asm: Object.entries(asm.contracts)
+ .sort(([_name1, data1], [_name2, data2]) => data1.asm['.code'][0].begin - data2.asm['.code'][0].begin)
.map(([name, data]) => {
- const processOpcodes = (opcodes, indent) => opcodes
- .map(opcode =>
- `${opcode.name}${opcode.value !== undefined ? ` ${opcode.value}` : ''}`,
- )
- .map(opcode =>
- (indent || '') + (opcode.startsWith('tag') ? opcode : `\t${opcode}`),
- );
+ // name is in the format of file:contract
+ // e.g. MyFile.sol:MyContract
+ const [sourceName, contractName] = name.split(':');
+
+ // to make the asm more readable, we rename the
+ // tags (jumpdests) to show what function they're
+ // part of. here we parse the AST so we know what
+ // range of characters belongs to each function.
+ const contractFunctions = asm.sources[sourceName].AST.nodes
+ .find(node => {
+ return node.nodeType === 'ContractDefinition' && node.name === contractName;
+ })
+ .nodes.filter(node => {
+ return node.nodeType === 'FunctionDefinition';
+ })
+ .map(node => {
+ const [begin, length] = node.src.split(':').map(x => parseInt(x));
+
+ let name = node.kind === 'constructor' ? 'constructor' : node.name;
+
+ // encode the args into the name so we can
+ // differentiate between overloads
+ if (node.parameters.parameters.length > 0) {
+ name +=
+ '_' +
+ node.parameters.parameters
+ .map(paramNode => {
+ return paramNode.typeName.name;
+ })
+ .join('_');
+ }
+
+ return {
+ name: name,
+ begin: begin,
+ end: begin + length,
+ tagCount: 0,
+ };
+ });
+
+ // solc generates some code, for things like detecting
+ // and reverting if a multiplication results in
+ // integer overflow, etc.
+ const processGeneratedSources = generatedSourcesData => {
+ let generatedSources = {};
+ for (const generatedSource of generatedSourcesData) {
+ generatedSources[generatedSource.id] = generatedSource.ast.statements.map(statement => {
+ const [begin, length] = statement.src.split(':').map(x => parseInt(x));
+ return {
+ name: statement.name,
+ begin: begin,
+ end: begin + length,
+ tagCount: 0,
+ };
+ });
+ }
+ return generatedSources;
+ };
+
+ // there are two sets of generated sources, one for the code which deploys
+ // the contract (i.e. the constructor) 'generated-sources', and the code
+ // which is deployed and stored on-chain 'generated-sources-runtime'
+ const generatedSources = processGeneratedSources(data['generated-sources']);
+ const generatedSourcesRuntime = processGeneratedSources(data['generated-sources-runtime']);
+
+ const processOpcodes = (opcodes, indent, generatedSources) => {
+ // first iterate the opcodes to find all the tags,
+ // and assign human-readable names to as many of
+ // them as we can
+ let tagNames = {};
+ const processPossibleTagOpcode = (opcode, funcList) => {
+ if (opcode.name === 'tag') {
+ const func = funcList.find(func => {
+ return opcode.begin >= func.begin && opcode.end <= func.end;
+ });
+
+ if (func !== undefined) {
+ // a function can have multiple tags, so append
+ // a number to each
+ const tagName = `${func.name}_${func.tagCount}`;
+
+ ++func.tagCount;
+
+ tagNames[opcode.value] = tagName;
+ opcode.value = tagName;
+ }
+ }
+ };
+
+ for (const opcode of opcodes) {
+ // source 0 is the .sol file the user is
+ // editing, everything else is generated
+ // sources
+ if (opcode.source === 0) {
+ opcode.line = charToLine[opcode.begin];
+
+ processPossibleTagOpcode(opcode, contractFunctions);
+ } else {
+ processPossibleTagOpcode(opcode, generatedSources[opcode.source]);
+ }
+ }
+
+ return opcodes.map(opcode => {
+ const name = `${opcode.name.startsWith('tag') ? indent : `${indent}\t`}${opcode.name}`;
+
+ let value = opcode.value || '';
+ if (opcode.name === 'PUSH [tag]') {
+ if (tagNames[value] !== undefined) {
+ value = tagNames[value];
+ }
+ }
+
+ return {
+ text: `${name} ${value}`,
+ source: {line: opcode.line, file: null},
+ };
+ });
+ };
return [
- `// ${_.last(name.split(':'))}`,
- '.code',
- processOpcodes(data.asm['.code']),
- '.data',
+ {text: `// ${contractName}`},
+ // .code section is the code only run when deploying - the constructor
+ {text: '.code'},
+ processOpcodes(data.asm['.code'], '', generatedSources),
+ {text: ''},
+ // .data section is deployed bytecode - everything else
+ {text: '.data'},
Object.entries(data.asm['.data']).map(([id, {'.code': code}]) => [
- `\t${id}:`,
- '\t\t.code',
- processOpcodes(code, '\t\t'),
+ {text: `\t${id}:`},
+ processOpcodes(code, '\t', generatedSourcesRuntime),
]),
- '\n',
];
})
- .flat(Infinity)
- .slice(0, -1)
- .map(line => ({text: line})),
+ .flat(Infinity),
};
}
}