From a6b696d8bd03c4027b52fe23745f066d158f1420 Mon Sep 17 00:00:00 2001 From: Richard Moore Date: Thu, 6 Feb 2020 18:07:17 -0500 Subject: [PATCH] Added explicit pop placeholders to ASM dialect. --- packages/asm/grammar.jison | 11 ++++- packages/asm/src.ts/assembler.ts | 53 +++++++++++++++++++--- packages/asm/src.ts/opcodes.ts | 76 +++++++++++++++++--------------- 3 files changed, 97 insertions(+), 43 deletions(-) diff --git a/packages/asm/grammar.jison b/packages/asm/grammar.jison index 43821d7ec0..8978c63e10 100644 --- a/packages/asm/grammar.jison +++ b/packages/asm/grammar.jison @@ -48,7 +48,10 @@ (0x([0-9a-fA-F][0-9a-fA-F])*) return "HEX" ([1-9][0-9]*|0) return "DECIMAL" //(0b[01]*) return "BINARY" + +// Pop Placeholders "$$" return "DOLLAR_DOLLAR" +([$][1-9][0-9]*) return "DOLLAR_INDEX" // Special <> return "EOF" @@ -99,7 +102,9 @@ opcode | DECIMAL { $$ = { type: "decimal", value: $1, loc: getLoc(yy, @1) }; } | DOLLAR_DOLLAR - { $$ = { type: "pop", loc: getLoc(yy, @1) }; } + { $$ = { type: "pop", index: 0, loc: getLoc(yy, @1) }; } + | DOLLAR_INDEX + { $$ = { type: "pop", index: parseInt(($1).substring(1)), loc: getLoc(yy, @1) }; } | SCRIPT_EVAL javascript { $$ = { type: "eval", script: $2, loc: getLoc(yy, @1, @2) }; } ; @@ -122,7 +127,9 @@ hex { { const value = parseInt($1); if (value >= 256) { throw new Error("decimal data values must be single bytes"); } - $$ = { type: "hex", verbatim: true, value: ("0x" + (value).toString(16)), loc: getLoc(yy, @1) }; + let hex = (value).toString(16); + while (hex.length < 2) { hex = "0" + hex; } + $$ = { type: "hex", verbatim: true, value: ("0x" + hex), loc: getLoc(yy, @1) }; } } | SCRIPT_EVAL javascript { $$ = { type: "eval", verbatim: true, script: $2, loc: getLoc(yy, @1, @2) }; } diff --git a/packages/asm/src.ts/assembler.ts b/packages/asm/src.ts/assembler.ts index 6ecad00523..17ae93f23f 100644 --- a/packages/asm/src.ts/assembler.ts +++ b/packages/asm/src.ts/assembler.ts @@ -246,8 +246,19 @@ export class LiteralNode extends ValueNode { } export class PopNode extends ValueNode { + readonly index: number; + + constructor(guard: any, location: Location, index: number) { + super(guard, location, { index }); + } + + get placeholder(): string { + if (this.index === 0) { return "$$"; } + return "$" + String(this.index); + } + static from(options: any): PopNode { - return new PopNode(Guard, options.loc, { }); + return new PopNode(Guard, options.loc, options.index); } } @@ -291,9 +302,10 @@ export class LinkNode extends ValueNode { const opcodes = [ ]; - // Have to jump backwards if (here > value) { - // Find a literal which can include its own length in the delta + // Jump backwards + + // Find a literal with length the encodes its own length in the delta let literal = "0x"; for (let w = 1; w <= 5; w++) { if (w > 4) { throw new Error("jump too large!"); } @@ -314,6 +326,8 @@ export class LinkNode extends ValueNode { //opcodes.push(Opcode.from("SWAP1")); //opcodes.push(Opcode.from("SUB")); } else { + // Jump forwards; this is easy to calculate since we can + // do PC firat. opcodes.push(Opcode.from("PC")); opcodes.push(pushLiteral(value - here)); opcodes.push(Opcode.from("ADD")); @@ -897,17 +911,44 @@ class SemanticChecker extends Assembler { }); // Allow any number of stack adjacent $$ + let foundZero = null; + let lastIndex = 0; + while (ordered.length && ordered[0] instanceof PopNode) { - ordered.shift(); + const popNode = ((ordered.shift())); + const index = popNode.index; + if (index === 0) { + foundZero = popNode; + } else if (index !== lastIndex + 1) { + errors.push({ + message: `out-of-order stack placeholder ${ popNode.placeholder }; expected $$${ lastIndex + 1 }`, + severity: SemanticErrorSeverity.error, + node: popNode + }); + while (ordered.length && ordered[0] instanceof PopNode) { + ordered.shift(); + } + break; + } else { + lastIndex = index; + } + } + + if (foundZero && lastIndex > 0) { + errors.push({ + message: "cannot mix $$ and $1 stack placeholder", + severity: SemanticErrorSeverity.error, + node: foundZero + }); } // If there are still any buried, we have a problem const pops = ordered.filter((n) => (n instanceof PopNode)); if (pops.length) { errors.push({ - message: `$$ must be stack adjacent`, + message: `stack placeholder ${ ((pops[0])).placeholder } must be stack adjacent`, severity: SemanticErrorSeverity.error, - node: node + node: pops[0] }); } } diff --git a/packages/asm/src.ts/opcodes.ts b/packages/asm/src.ts/opcodes.ts index fe41116191..2fcefcb300 100644 --- a/packages/asm/src.ts/opcodes.ts +++ b/packages/asm/src.ts/opcodes.ts @@ -12,6 +12,12 @@ import { ethers } from "ethers"; +export enum OpcodeMemoryAccess { + write = "write", + read = "read", + full = "full" +}; + export class Opcode { readonly value: number; readonly mnemonic: string @@ -47,13 +53,20 @@ export class Opcode { } // Returns true if this operation writes to memory contents (or if readOrWrite, reads memory) - isMemory(readOrWrite?: boolean): boolean { - throw new Error("@TODO: return true if modifies memory"); + // Unknown opcodes return null + isMemoryAccess(readOrWrite?: boolean): OpcodeMemoryAccess { + switch ((_Opcodes[this.mnemonic.toLowerCase()] || { memory: null }).memory) { + case "read": return OpcodeMemoryAccess.read; + case "write": return OpcodeMemoryAccess.write; + case "full": return OpcodeMemoryAccess.full; + } + return null; } // Returns true if this opcode does not affect state + // Unknown opcodes return false isStatic(): boolean { - throw new Error("@TODO: return true if certain non-state-changing"); + return !(_Opcodes[this.mnemonic.toLowerCase()] || { nonStatic: true }).nonStatic; } static from(valueOrMnemonic: number | string) { @@ -69,6 +82,8 @@ type _Opcode = { delta: number; alpha: number; doc?: string; + nonStatic?: boolean; + memory?: "read" | "write" | "full"; }; const _Opcodes: { [ name: string ]: _Opcode } = { @@ -103,7 +118,7 @@ const _Opcodes: { [ name: string ]: _Opcode } = { sar: { value: 0x1d, delta: 2, alpha: 1, doc: "v = sar(shiftBits, value)" }, // SHA3 - sha3: { value: 0x20, delta: 2, alpha: 1, doc: "v = sha3(offset, length)" }, + sha3: { value: 0x20, delta: 2, alpha: 1, doc: "v = sha3(offset, length)", memory: "read" }, // Environmental Information address: { value: 0x30, delta: 0, alpha: 1, doc: "myAddr = address" }, @@ -113,14 +128,14 @@ const _Opcodes: { [ name: string ]: _Opcode } = { callvalue: { value: 0x34, delta: 0, alpha: 1, doc: "msgValue = callvalue" }, calldataload: { value: 0x35, delta: 1, alpha: 1, doc: "calldataWordValue = calldataload(byteOffet)" }, calldatasize: { value: 0x36, delta: 0, alpha: 1, doc: "calldataLength = calldatasize" }, - calldatacopy: { value: 0x37, delta: 3, alpha: 0, doc: "calldatacopy(dstMemoryIndex, dataIndex, length)" }, + calldatacopy: { value: 0x37, delta: 3, alpha: 0, doc: "calldatacopy(dstMemoryIndex, dataIndex, length)", memory: "write" }, codesize: { value: 0x38, delta: 0, alpha: 1, doc: "myCodeLength = codesize" }, - codecopy: { value: 0x39, delta: 3, alpha: 0, doc: "codecopy(dstMemoryIndex, codeIndex, length)" }, + codecopy: { value: 0x39, delta: 3, alpha: 0, doc: "codecopy(dstMemoryIndex, codeIndex, length)", memory: "write" }, gasprice: { value: 0x3a, delta: 0, alpha: 1, doc: "txGasPrice = gasprice" }, extcodesize: { value: 0x3b, delta: 1, alpha: 1, doc: "otherCodeLength = extcodesize(address)" }, - extcodecopy: { value: 0x3c, delta: 4, alpha: 0, doc: "extcodecopy(address, dstMemoryIndex, extcodeIndex, length)" }, + extcodecopy: { value: 0x3c, delta: 4, alpha: 0, doc: "extcodecopy(address, dstMemoryIndex, extcodeIndex, length)", memory: "write" }, returndatasize: { value: 0x3d, delta: 0, alpha: 1, doc: "v = returndatasize" }, - returndatacopy: { value: 0x3e, delta: 3, alpha: 0, doc: "returndatacopy(dstMemoryOffset, returndataIndex, length)" }, + returndatacopy: { value: 0x3e, delta: 3, alpha: 0, doc: "returndatacopy(dstMemoryOffset, returndataIndex, length)", memory: "write" }, extcodehash: { value: 0x3f, delta: 1, alpha: 1, doc: "hash = extcodehash(address)" }, // Block Information @@ -133,11 +148,11 @@ const _Opcodes: { [ name: string ]: _Opcode } = { // Stack, Memory, Storage and Flow Operations pop: { value: 0x50, delta: 1, alpha: 0, doc: "stackTopValue = pop" }, - mload: { value: 0x51, delta: 1, alpha: 1, doc: "memoryWordValue = mload(memoryByteIndex)" }, - mstore: { value: 0x52, delta: 2, alpha: 0, doc: "mstore(memoryByteIndex, valueOut)" }, - mstore8: { value: 0x53, delta: 2, alpha: 0, doc: "mstore8(memoryByteIndex, valueOut [ & 0xff ])" }, + mload: { value: 0x51, delta: 1, alpha: 1, doc: "memoryWordValue = mload(memoryByteIndex)", memory: "read" }, + mstore: { value: 0x52, delta: 2, alpha: 0, doc: "mstore(memoryByteIndex, valueOut)", memory: "write" }, + mstore8: { value: 0x53, delta: 2, alpha: 0, doc: "mstore8(memoryByteIndex, valueOut [ & 0xff ])", memory: "write" }, sload: { value: 0x54, delta: 1, alpha: 1, doc: "storageWordValue = sload(storageWordIndex)" }, - sstore: { value: 0x55, delta: 2, alpha: 0, doc: "sstore(storageWordIndex, valueOut)" }, + sstore: { value: 0x55, delta: 2, alpha: 0, doc: "sstore(storageWordIndex, valueOut)", nonStatic: true }, jump: { value: 0x56, delta: 1, alpha: 0, doc: "jump(target)" }, jumpi: { value: 0x57, delta: 2, alpha: 0, doc: "jumpi(target, notZero)" }, pc: { value: 0x58, delta: 0, alpha: 1, doc: "programCounter = pc" }, @@ -216,23 +231,23 @@ const _Opcodes: { [ name: string ]: _Opcode } = { swap16: { value: 0x9f, delta: 0, alpha: 0 }, // Loggin Operations - log0: { value: 0xa0, delta: 2, alpha: 0 }, - log1: { value: 0xa1, delta: 3, alpha: 0 }, - log2: { value: 0xa2, delta: 4, alpha: 0 }, - log3: { value: 0xa3, delta: 5, alpha: 0 }, - log4: { value: 0xa4, delta: 6, alpha: 0 }, + log0: { value: 0xa0, delta: 2, alpha: 0, nonStatic: true, memory: "read" }, + log1: { value: 0xa1, delta: 3, alpha: 0, nonStatic: true, memory: "read" }, + log2: { value: 0xa2, delta: 4, alpha: 0, nonStatic: true, memory: "read" }, + log3: { value: 0xa3, delta: 5, alpha: 0, nonStatic: true, memory: "read" }, + log4: { value: 0xa4, delta: 6, alpha: 0, nonStatic: true, memory: "read" }, // System Operations - create: { value: 0xf0, delta: 3, alpha: 1, doc: "address = create(value, index, length)" }, - call: { value: 0xf1, delta: 7, alpha: 1, doc: "v = call(gasLimit, address, value, inputIndex, inputLength, outputIndex, outputLength)" }, - callcode: { value: 0xf2, delta: 7, alpha: 1, doc: "v = callcode(@TODO)" }, - "return": { value: 0xf3, delta: 2, alpha: 0, doc: "return(index, length)" }, - delegatecall: { value: 0xf4, delta: 6, alpha: 1, doc: "v = delegatecall(gasLimit, address, inputIndex, inputLength, outputIndex, outputLength)" }, - create2: { value: 0xf5, delta: 4, alpha: 1, doc: "address = create2(value, index, length, salt)" }, - staticcall: { value: 0xfa, delta: 6, alpha: 1, doc: "v = staticcall(gasLimit, address, inputIndex, inputLength, outputIndex, outputLength)" }, - revert: { value: 0xfd, delta: 2, alpha: 0, doc: "revert(returnDataOffset, returnDataLength)" }, + create: { value: 0xf0, delta: 3, alpha: 1, doc: "address = create(value, index, length)", nonStatic: true, memory: "read" }, + call: { value: 0xf1, delta: 7, alpha: 1, doc: "v = call(gasLimit, address, value, inputIndex, inputLength, outputIndex, outputLength)", nonStatic: true, memory: "full" }, + callcode: { value: 0xf2, delta: 7, alpha: 1, doc: "v = callcode(@TODO)", nonStatic: true, memory: "full" }, + "return": { value: 0xf3, delta: 2, alpha: 0, doc: "return(index, length)", memory: "read" }, + delegatecall: { value: 0xf4, delta: 6, alpha: 1, doc: "v = delegatecall(gasLimit, address, inputIndex, inputLength, outputIndex, outputLength)", nonStatic: true, memory: "full" }, + create2: { value: 0xf5, delta: 4, alpha: 1, doc: "address = create2(value, index, length, salt)", nonStatic: true, memory: "read" }, + staticcall: { value: 0xfa, delta: 6, alpha: 1, doc: "v = staticcall(gasLimit, address, inputIndex, inputLength, outputIndex, outputLength)", memory: "full" }, + revert: { value: 0xfd, delta: 2, alpha: 0, doc: "revert(returnDataOffset, returnDataLength)", memory: "read" }, invalid: { value: 0xfe, delta: 0, alpha: 0, doc: "invalid" }, - suicide: { value: 0xff, delta: 1, alpha: 0, doc: "suicide(targetAddress)" }, + suicide: { value: 0xff, delta: 1, alpha: 0, doc: "suicide(targetAddress)", nonStatic: true }, }; const OpcodeMap: { [ mnemonic: string ]: Opcode } = { }; @@ -256,12 +271,3 @@ Object.keys(_Opcodes).forEach((mnemonic) => { Opcodes[value] = opcode; }); Object.freeze(Opcodes); - -/* -function repeat(char: string, length: number): string { - let result = char; - while (result.length < length) { result += result; } - return result.substring(0, length); -} -*/ -