diff --git a/src/compilation/contractProcessor.ts b/src/compilation/contractProcessor.ts index 2b40a0b..c163952 100644 --- a/src/compilation/contractProcessor.ts +++ b/src/compilation/contractProcessor.ts @@ -40,6 +40,7 @@ export class ContractProcessor { // are determined before we start using it in variable type definition this.processContractChildType("InheritanceSpecifier"); this.processContractChildType("UsingForDirective"); + this.processContractChildType("EnumDefinition"); this.processContractChildType("StructDefinition"); this.processContractChildType("VariableDeclaration"); this.processContractChildType("FunctionDefinition"); @@ -79,22 +80,32 @@ export class ContractProcessor { private processContractStruct(node: any) { // save the scope that the struct is defined at - this._contract.structDefinitions.set(node.attributes.name, node.id); + let structDefinition: LibSdbTypes.VariableMap = new Map(); + const astWalker = new LibSdbUtils.AstWalker(); astWalker.walkDetail(node, null, 0, (node, parent, depth) => { if (node.id) { if (node.name === "VariableDeclaration") { const variable = this.createVariableFromAst(node, parent, depth, null); - // add the variable to the parent's scope - if (!this._contract.scopeVariableMap.has(variable.scope.id)) { - this._contract.scopeVariableMap.set(variable.scope.id, new Map()); - } - this._contract.scopeVariableMap.get(variable.scope.id)!.set(variable.name, variable); + structDefinition.set(variable.name, variable); } } return true; }); + + this._contract.structDefinitions.set(node.attributes.name, structDefinition); + } + + private processContractEnum(node: any) { + // save the scope that the enum is defined at + let enumDefintion = new LibSdbTypes.EnumDefinition(node.attributes.name); + + for (let i = 0; i < node.children.length; i++) { + enumDefintion.values.push(node.children[i].attributes.name); + } + + this._contract.enumDefinitions.set(enumDefintion.name, enumDefintion); } private processContractFunction(node: any) { @@ -145,6 +156,10 @@ export class ContractProcessor { this.processContractStruct(node); break; } + case "EnumDefinition": { + this.processContractEnum(node); + break; + } case "FunctionDefinition": { this.processContractFunction(node); break; diff --git a/src/runtime.ts b/src/runtime.ts index dd83e4c..ca9cb7f 100644 --- a/src/runtime.ts +++ b/src/runtime.ts @@ -167,90 +167,91 @@ export class LibSdbRuntime extends EventEmitter { else { const contract = this._contractsByAddress.get(address); - if (!contract) { - this.respondToDebugHook("skipEvent"); - return; - } + this._stepData = new LibSdbTypes.StepData(); + this._stepData.debuggerMessageId = data.id; - // get line number from pc - const index = contract.pcMap.get(pc)!.index; - const sourceLocation = LibSdbUtils.SourceMappingDecoder.atIndex(index, contract.srcmapRuntime); + if (contract && contract.pcMap.has(pc)) { + // get line number from pc + const index = contract.pcMap.get(pc)!.index; + const sourceLocation = LibSdbUtils.SourceMappingDecoder.atIndex(index, contract.srcmapRuntime); - if (data.content.specialEvents.indexOf("fnJumpDestination") >= 0) { - this.processJumpIn(sourceLocation, contract, data.content.stack, true); - } - else if (data.content.specialEvents.indexOf("jump") >= 0) { - let processedJump: boolean = false; - if (this._priorStepData && this._priorStepData.source) { - if (this._priorStepData.source.jump === "i") { - this.processJumpIn(sourceLocation, contract, data.content.stack); - processedJump = true; + if (data.content.specialEvents.indexOf("fnJumpDestination") >= 0) { + this.processJumpIn(sourceLocation, contract, data.content.stack, true); + } + else if (data.content.specialEvents.indexOf("jump") >= 0) { + let processedJump: boolean = false; + if (this._priorStepData && this._priorStepData.source) { + if (this._priorStepData.source.jump === "i") { + this.processJumpIn(sourceLocation, contract, data.content.stack); + processedJump = true; + } + else if (this._priorStepData.source.jump === "o") { + await this.processJumpOut(contract, data.content.stack, data.content.memory); + processedJump = true; + } } - else if (this._priorStepData.source.jump === "o") { - await this.processJumpOut(contract, data.content.stack, data.content.memory); - processedJump = true; + + if (!processedJump && pc in contract.functionNames) { + // jump in to external function + // this is the JUMPDEST of a function we just entered + let frame = new LibSdbTypes.StackFrame(); + frame.name = contract.functionNames[pc]; + frame.file = contract.sourcePath; + frame.line = 0 //currentLocation.start === null ? null : currentLocation.start.line; + this._callStack.unshift(frame); } } - - if (!processedJump && pc in contract.functionNames) { - // jump in to external function - // this is the JUMPDEST of a function we just entered - let frame = new LibSdbTypes.StackFrame(); - frame.name = contract.functionNames[pc]; - frame.file = contract.sourcePath; - frame.line = 0 //currentLocation.start === null ? null : currentLocation.start.line; - this._callStack.unshift(frame); + else if (data.content.specialEvents.indexOf("declaration") >= 0) { + this.processDeclaration(sourceLocation, contract, data.content.stack); } - } - else if (data.content.specialEvents.indexOf("declaration") >= 0) { - this.processDeclaration(sourceLocation, contract, data.content.stack); - } - const fileId = sourceLocation.file; - let file: LibSdbTypes.File; - if (!isNaN(fileId)) { - file = this._filesById.get(fileId)!; - } - else { - file = this._files.get(contract.sourcePath)!; - } + const fileId = sourceLocation.file; + let file: LibSdbTypes.File; + if (!isNaN(fileId)) { + file = this._filesById.get(fileId)!; + } + else { + file = this._files.get(contract.sourcePath)!; + } - // find current scope - const currentScope = LibSdbUtils.findScope(sourceLocation.start, contract.ast); + // find current scope + const currentScope = LibSdbUtils.findScope(sourceLocation.start, contract.ast); - let currentLocation = { - start: null - }; - if (file) { - currentLocation = LibSdbUtils.SourceMappingDecoder.convertOffsetToLineColumn(sourceLocation, file.lineBreaks); - } + let currentLocation = { + start: null + }; + if (file) { + currentLocation = LibSdbUtils.SourceMappingDecoder.convertOffsetToLineColumn(sourceLocation, file.lineBreaks); + } - this._stepData = new LibSdbTypes.StepData(); - this._stepData.debuggerMessageId = data.id; - this._stepData.source = sourceLocation; - this._stepData.location = currentLocation; - this._stepData.contractAddress = address; - this._stepData.vmData = CircularJSON.parse(CircularJSON.stringify(data.content)); // make a deep copy TODO: make this better - this._stepData.vmData.gasLeft = new BN(data.content.gasLeft); - this._stepData.vmData.stack = []; - for (let i = 0; i < data.content.stack.length; i++) { - this._stepData.vmData.stack.push(new BN(data.content.stack[i])); - } - this._stepData.scope = currentScope; - this._stepData.events = data.content.specialEvents; - if (data.exceptionError !== undefined) { - this._stepData.exception = data.exceptionError; - } + this._stepData.source = sourceLocation; + this._stepData.location = currentLocation; + this._stepData.scope = currentScope; + this._stepData.contractAddress = address; + this._stepData.vmData = CircularJSON.parse(CircularJSON.stringify(data.content)); // make a deep copy TODO: make this better + this._stepData.vmData.gasLeft = new BN(data.content.gasLeft); + this._stepData.vmData.stack = []; + for (let i = 0; i < data.content.stack.length; i++) { + this._stepData.vmData.stack.push(new BN(data.content.stack[i])); + } + this._stepData.events = data.content.specialEvents; + if (data.exceptionError !== undefined) { + this._stepData.exception = data.exceptionError; + } - if (!file) { - this.respondToDebugHook("skipEvent"); - } - else if (data.content.specialEvents.length > 0 && data.content.specialEvents.indexOf("breakpoint") === -1 && data.exceptionError === undefined) { - // if there were any special events, none of them were a breakpoint, and there was no exception, skip the event - this.respondToDebugHook("skipEvent"); + if (!file) { + this.respondToDebugHook("skipEvent"); + } + else if (data.content.specialEvents.length > 0 && data.content.specialEvents.indexOf("breakpoint") === -1 && data.exceptionError === undefined) { + // if there were any special events, none of them were a breakpoint, and there was no exception, skip the event + this.respondToDebugHook("skipEvent"); + } + else { + this.sendEvent("step"); + } } else { - this.sendEvent("step"); + this.respondToDebugHook("skipEvent"); } } } diff --git a/src/types/barrel.ts b/src/types/barrel.ts index e21525f..7cfac0d 100644 --- a/src/types/barrel.ts +++ b/src/types/barrel.ts @@ -1,4 +1,4 @@ -import { ValueDetail, ArrayDetail, StructDetail, MappingDetail, ContractDetail } from "./barrel"; +import { ValueDetail, ArrayDetail, StructDetail, MappingDetail, ContractDetail, EnumDetail } from "./barrel"; export * from "./astScope"; export * from "./breakpoint"; @@ -11,8 +11,11 @@ export * from "./stackFrame"; export * from "./stepData"; export * from "./variable/variable"; export * from "./variable/detail/value"; +export * from "./variable/detail/enum"; export * from "./variable/detail/array"; export * from "./variable/detail/struct"; export * from "./variable/detail/mapping"; export * from "./variable/detail/contract"; -export type VariableDetailType = ValueDetail | ArrayDetail | StructDetail | MappingDetail | ContractDetail; \ No newline at end of file +export * from "./enum"; + +export type VariableDetailType = ValueDetail | EnumDetail | ArrayDetail | StructDetail | MappingDetail | ContractDetail; \ No newline at end of file diff --git a/src/types/contract.ts b/src/types/contract.ts index 8499b15..02b391c 100644 --- a/src/types/contract.ts +++ b/src/types/contract.ts @@ -17,7 +17,8 @@ export class Contract { ast: Ast; stateVariables: Variable[]; breakpoints: Map; - structDefinitions: Map; + structDefinitions: Map; + enumDefinitions: Map; inheritedContracts: Contract[]; constructor() { @@ -27,7 +28,8 @@ export class Contract { this.stateVariables = []; this.addresses = []; this.breakpoints = new Map(); - this.structDefinitions = new Map(); + this.structDefinitions = new Map>(); + this.enumDefinitions = new Map(); this.inheritedContracts = []; } @@ -69,7 +71,17 @@ export class Contract { } for (const v of this.structDefinitions) { - clone.structDefinitions.set(v[0], v[1]); + let definitionClone: LibSdbTypes.VariableMap = new Map(); + + for (const v2 of v[1]) { + definitionClone.set(v2[0], v2[1].clone()); + } + + clone.structDefinitions.set(v[0], definitionClone); + } + + for (const v of this.enumDefinitions) { + clone.enumDefinitions.set(v[0], v[1].clone()); } for (let i = 0; i < this.inheritedContracts.length; i++) { diff --git a/src/types/enum.ts b/src/types/enum.ts new file mode 100644 index 0000000..8ddc86c --- /dev/null +++ b/src/types/enum.ts @@ -0,0 +1,20 @@ + +export class EnumDefinition { + public name: string; + public values: string[]; + + constructor(name: string) { + this.name = name; + this.values = []; + } + + public clone(): EnumDefinition { + let clone = new EnumDefinition(this.name); + + for (let i = 0; i < this.values.length; i++) { + clone.values.push(this.values[i]); + } + + return clone; + } +} \ No newline at end of file diff --git a/src/types/variable/decode/memory.ts b/src/types/variable/decode/memory.ts index 8981220..8ed770e 100644 --- a/src/types/variable/decode/memory.ts +++ b/src/types/variable/decode/memory.ts @@ -1,8 +1,8 @@ -import { VariableType } from "../variable"; +import { LibSdbTypes } from "../../types"; import { decode as decodeValue } from "./value"; import { BN } from "bn.js"; -export function decode(stackPosition: number, memoryOffset: number, type: VariableType, stack: BN[], memory: (number | null)[]): string { +export function decode(stackPosition: number, memoryOffset: number, detail: LibSdbTypes.ValueDetail | LibSdbTypes.EnumDetail, stack: BN[], memory: (number | null)[]): string { let v = ""; if (stackPosition !== null && stack.length > stackPosition) { @@ -23,7 +23,7 @@ export function decode(stackPosition: number, memoryOffset: number, type: Variab } }).join(""); if (element) { - v = decodeValue(type, new BN(element, 16)); + v = decodeValue(detail, new BN(element, 16)); } } diff --git a/src/types/variable/decode/stack.ts b/src/types/variable/decode/stack.ts index c42ad4d..4a09765 100644 --- a/src/types/variable/decode/stack.ts +++ b/src/types/variable/decode/stack.ts @@ -1,11 +1,11 @@ -import { VariableType } from "../variable"; +import { LibSdbTypes } from "../../types"; import { decode as decodeValue } from "./value"; import { BN } from "bn.js"; -export function decode(position: number, type: VariableType, stack: BN[]): string { +export function decode(position: number, detail: LibSdbTypes.ValueDetail | LibSdbTypes.EnumDetail, stack: BN[]): string { if (position !== null && position >= 0 && position < stack.length) { // stack - return decodeValue(type, stack[position]); + return decodeValue(detail, stack[position]); } else { return ""; diff --git a/src/types/variable/decode/storage.ts b/src/types/variable/decode/storage.ts index 5b8868a..1468bf5 100644 --- a/src/types/variable/decode/storage.ts +++ b/src/types/variable/decode/storage.ts @@ -1,9 +1,9 @@ import { LibSdbInterface } from "../../../interface"; -import { VariableType } from "../variable"; +import { LibSdbTypes } from "../../types"; import { decode as decodeValue } from "./value"; import { BN } from "bn.js"; -export async function decode(position: number, offset: number, length: number, type: VariableType, _interface: LibSdbInterface, address: string): Promise { +export async function decode(position: number, offset: number, length: number, detail: LibSdbTypes.ValueDetail | LibSdbTypes.EnumDetail, _interface: LibSdbInterface, address: string): Promise { let value = ""; if (position === null) { @@ -21,7 +21,7 @@ export async function decode(position: number, offset: number, length: number, t if (end < 0) { end = 0; } - value = decodeValue(type, new BN(content.value.slice(start, end))); + value = decodeValue(detail, new BN(content.value.slice(start, end))); } return value; diff --git a/src/types/variable/decode/value.ts b/src/types/variable/decode/value.ts index a2fa325..a4482dc 100644 --- a/src/types/variable/decode/value.ts +++ b/src/types/variable/decode/value.ts @@ -1,26 +1,26 @@ -import { VariableType } from "../variable"; import { BN } from "bn.js"; +import { LibSdbTypes } from "../../types"; -export function decode(variableType: VariableType, value: BN) { +export function decode(detail: LibSdbTypes.ValueDetail | LibSdbTypes.EnumDetail, value: BN) { let v: string = ""; //let num; - switch (variableType) { - case VariableType.Boolean: + switch (detail.type) { + case LibSdbTypes.VariableType.Boolean: v = value.eqn(1) ? "true" : "false"; break; - case VariableType.UnsignedInteger: + case LibSdbTypes.VariableType.UnsignedInteger: v = value.toString(); break; - case VariableType.Integer: + case LibSdbTypes.VariableType.Integer: v = value.fromTwos(256).toString(); break; - case VariableType.FixedPoint: + case LibSdbTypes.VariableType.FixedPoint: // not supported yet in Solidity (2/21/2018) per solidity.readthedocs.io break; - case VariableType.Address: + case LibSdbTypes.VariableType.Address: v = "0x" + value.toString(16); break; - case VariableType.FixedByteArray: + case LibSdbTypes.VariableType.FixedByteArray: const byteArrayStr = value.toString(16).match(/.{2}/g); let byteArray: number[]; if (byteArrayStr !== null) { @@ -33,13 +33,22 @@ export function decode(variableType: VariableType, value: BN) { } v = JSON.stringify(byteArray); break; - case VariableType.Enum: - // TODO: + case LibSdbTypes.VariableType.Enum: + if (detail instanceof LibSdbTypes.EnumDetail) { + // really should be the case all the time + const index = Math.floor(value.toNumber()); + if (index < detail.definition.values.length) { + v = detail.definition.values[index]; + } + else { + v = "(invalid enum value) " + value.toString(); + } + } break; - case VariableType.Function: + case LibSdbTypes.VariableType.Function: // TODO: break; - case VariableType.None: + case LibSdbTypes.VariableType.None: default: v = ""; break; diff --git a/src/types/variable/definition/processor.ts b/src/types/variable/definition/processor.ts index 81ca637..a5623db 100644 --- a/src/types/variable/definition/processor.ts +++ b/src/types/variable/definition/processor.ts @@ -1,5 +1,6 @@ import { Variable, VariableLocation, VariableType } from "../variable"; import { ValueDetail } from "../detail/value"; +import { EnumDetail } from "../detail/enum"; import { ArrayDetail } from "../detail/array"; import { StructDetail } from "../detail/struct"; import { MappingDetail } from "../detail/mapping"; @@ -239,22 +240,19 @@ export class VariableProcessor { const contract = this._runtime._contractsByName.get(structContractName); if (contract) { - const structDefinitionScopeId = contract.structDefinitions.get(structName); - if (structDefinitionScopeId !== undefined) { - const structVariables = contract.scopeVariableMap.get(structDefinitionScopeId); - if (structVariables !== undefined) { - // fill out leaf members? - for (const structVariable of structVariables) { - let variable = structVariable[1].clone(); - variable.location = leaf.location; - if (variable.detail !== null) { - variable.detail.variable = leaf.variable; - } - leaf.members.push({ - name: variable.name, - detail: variable.detail - }); + const structVariables = contract.structDefinitions.get(structName); + if (structVariables !== undefined) { + // fill out leaf members? + for (const structVariable of structVariables) { + let variable = structVariable[1].clone(); + variable.location = leaf.location; + if (variable.detail !== null) { + variable.detail.variable = leaf.variable; } + leaf.members.push({ + name: variable.name, + detail: variable.detail + }); } } } @@ -280,6 +278,30 @@ export class VariableProcessor { leaf = new ContractDetail(this._variable); leaf.name = match[1]; } + else if ((match = /^enum ([\S]+)\.([^\r\n\t\f\v \[]+)/g.exec(typeName)) !== null || (match = /^enum/g.exec(typeName)) !== null) { + remainderTypeName = typeName.substr(match.index + match[0].length); + // last leaf is an enum + leaf = new EnumDetail(this._variable); + + if (leaf instanceof EnumDetail) { + const enumContractName = match[1]; + const enumName = match[2]; + + const contract = this._runtime._contractsByName.get(enumContractName); + if (contract) { + const enumDefinition = contract.enumDefinitions.get(enumName); + if (enumDefinition !== undefined) { + leaf.definition = enumDefinition; + + const numValues = leaf.definition.values.length; + // numValues <= 2^n - 1 + // numValues + 1 <= 2^n + // log(numValues + 1) <= n (n is bits) + leaf.storageLength = Math.ceil(Math.log2(numValues + 1) / 8); + } + } + } + } else { // unsupported leaf? as of now, this will get thrown (in the else of the below statement) // probably should handle this better diff --git a/src/types/variable/detail/enum.ts b/src/types/variable/detail/enum.ts new file mode 100644 index 0000000..0015a0b --- /dev/null +++ b/src/types/variable/detail/enum.ts @@ -0,0 +1,69 @@ +import { ValueDetail } from "./value"; +import { EnumDefinition } from "../../enum"; +import { Variable, DecodedVariable, VariableLocation, VariableType, VariableTypeToString } from "../variable"; +import { LibSdbInterface } from "../../../interface"; +import { BN } from "bn.js"; + +import { decode as decodeStack } from "../decode/stack"; +import { decode as decodeMemory } from "../decode/memory"; +import { decode as decodeStorage } from "../decode/storage"; + +export class EnumDetail extends ValueDetail { + public definition: EnumDefinition; + + constructor(variable: Variable) { + super(variable); + + this.type = VariableType.Enum; + } + + getStorageUsed(): number { + return super.getStorageUsed(); + } + + clone(variable: Variable = this.variable): EnumDetail { + let clone = new EnumDetail(this.variable); + + clone.position = this.position; + + clone.offset = this.offset; + + clone.type = this.type; + + clone.storageLength = this.storageLength; + + clone.memoryLength = this.memoryLength; + + clone.definition = this.definition.clone(); + + return clone; + } + + async decode(stack: BN[], memory: (number | null)[], _interface: LibSdbInterface, address: string): Promise { + let v: string = ""; + + switch (this.variable.location) { + case VariableLocation.Stack: + v = decodeStack((this.variable.position || 0) + this.position, this, stack); + break; + case VariableLocation.Memory: + v = decodeMemory((this.variable.position || 0), this.position, this, stack, memory); + break; + case VariableLocation.Storage: + v = await decodeStorage(this.position, this.offset || 0, this.storageLength, this, _interface, address); + break; + default: + break; + } + + let decodedVariable = { + name: this.variable.name, + type: VariableTypeToString(this.type), + variablesReference: 0, + value: v, + result: v + }; + + return decodedVariable; + } +} \ No newline at end of file diff --git a/src/types/variable/detail/value.ts b/src/types/variable/detail/value.ts index 7af7667..392c41a 100644 --- a/src/types/variable/detail/value.ts +++ b/src/types/variable/detail/value.ts @@ -44,13 +44,13 @@ export class ValueDetail { switch (this.variable.location) { case VariableLocation.Stack: - v = decodeStack((this.variable.position || 0) + this.position, this.type, stack); + v = decodeStack((this.variable.position || 0) + this.position, this, stack); break; case VariableLocation.Memory: - v = decodeMemory((this.variable.position || 0), this.position, this.type, stack, memory); + v = decodeMemory((this.variable.position || 0), this.position, this, stack, memory); break; case VariableLocation.Storage: - v = await decodeStorage(this.position, this.offset || 0, this.storageLength, this.type, _interface, address); + v = await decodeStorage(this.position, this.offset || 0, this.storageLength, this, _interface, address); break; default: break;