Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions apps/remix-ide-e2e/src/tests/debugger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,27 @@ module.exports = {
.waitForElementPresent('*[data-id="treeViewLoadMore"]')
.click('*[data-id="treeViewLoadMore"]')
.assert.containsText('*[data-id="solidityLocals"]', '149: 0 uint256')
.notContainsText('*[data-id="solidityLocals"]', '150: 0 uint256')
.notContainsText('*[data-id="solidityLocals"]', '150: 0 uint256')
},

'Should debug using generated sources': function (browser: NightwatchBrowser) {
browser
.clickLaunchIcon('solidity')
.setSolidityCompilerVersion('soljson-v0.7.2+commit.51b20bc0.js')
.clickLaunchIcon('udapp')
.testContracts('withGeneratedSources.sol', sources[4]['browser/withGeneratedSources.sol'], ['A'])
.createContract('')
.clickInstance(4)
.clickFunction('f - transact (not payable)', {types: 'uint256[] ', values: '[]'})
.debugTransaction(8)
.pause(2000)
.click('*[data-id="debuggerTransactionStartButton"]') // stop debugging
.click('*[data-id="debugGeneratedSourcesLabel"]') // select debug with generated sources
.click('*[data-id="debuggerTransactionStartButton"]') // start debugging
.pause(2000)
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('if slt(sub(dataEnd, headStart), 32) { revert(0, 0) }') != -1, 'current displayed content is not a generated source')
})
.end()
},

Expand Down Expand Up @@ -227,6 +247,17 @@ const sources = [
}
`
}
},
{
'browser/withGeneratedSources.sol': {
content: `
// SPDX-License-Identifier: GPL-3.0
pragma experimental ABIEncoderV2;
contract A {
function f(uint[] memory) public returns (uint256) { }
}
`
}
}
]

Expand Down Expand Up @@ -293,4 +324,4 @@ const localVariable_step717_ABIEncoder = {
"error": "<decoding failed - no decoder for calldata>",
"type": "bytes"
}
}
}
63 changes: 56 additions & 7 deletions apps/remix-ide/src/app/tabs/debugger/debuggerUI.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,26 @@ var css = csjs`
.statusMessage {
margin-left: 15px;
}

.debuggerConfig {
display: flex;
align-items: center;
}

.debuggerConfig label {
margin: 0;
}

.debuggerSection {
padding: 12px 24px 16px;
}

.debuggerLabel {
margin-bottom: 2px;
font-size: 11px;
line-height: 12px;
text-transform: uppercase;
}
`

class DebuggerUI {
Expand All @@ -32,7 +52,9 @@ class DebuggerUI {
this.event = new EventManager()

this.isActive = false

this.opt = {
debugWithGeneratedSources: false
}
this.sourceHighlighter = new SourceHighlighter()

this.startTxBrowser()
Expand Down Expand Up @@ -72,12 +94,31 @@ class DebuggerUI {
this.isActive = isActive
})

this.debugger.event.register('newSourceLocation', async (lineColumnPos, rawLocation) => {
this.debugger.event.register('newSourceLocation', async (lineColumnPos, rawLocation, generatedSources) => {
if (!lineColumnPos) return
const contracts = await this.fetchContractAndCompile(
this.currentReceipt.contractAddress || this.currentReceipt.to,
this.currentReceipt)
if (contracts) {
const path = contracts.getSourceName(rawLocation.file)
let path = contracts.getSourceName(rawLocation.file)
if (!path) {
// check in generated sources
for (const source of generatedSources) {
if (source.id === rawLocation.file) {
path = `browser/.debugger/generated-sources/${source.name}`
let content
try {
content = await this.debuggerModule.call('fileManager', 'getFile', path, source.contents)
} catch (e) {
console.log('unable to fetch generated sources, the file probably doesn\'t exist yet', e)
}
if (content !== source.contents) {
await this.debuggerModule.call('fileManager', 'setFile', path, source.contents)
}
break
}
}
}
if (path) {
await this.debuggerModule.call('editor', 'discardHighlight')
await this.debuggerModule.call('editor', 'highlight', lineColumnPos, path)
Expand Down Expand Up @@ -137,7 +178,8 @@ class DebuggerUI {
console.error(e)
}
return null
}
},
debugWithGeneratedSources: this.opt.debugWithGeneratedSources
})

this.listenToEvents()
Expand Down Expand Up @@ -167,7 +209,8 @@ class DebuggerUI {
console.error(e)
}
return null
}
},
debugWithGeneratedSources: false
})
debug.debugger.traceManager.traceRetriever.getTrace(hash, (error, trace) => {
if (error) return reject(error)
Expand All @@ -188,10 +231,16 @@ class DebuggerUI {
var view = yo`
<div>
<div class="px-2">
<div class="mt-3">
<p class="mt-2 ${css.debuggerLabel}">Debugger Configuration</p>
<div class="mt-2 ${css.debuggerConfig} custom-control custom-checkbox">
<input class="custom-control-input" id="debugGeneratedSourcesInput" onchange=${(event) => { this.opt.debugWithGeneratedSources = event.target.checked }} type="checkbox" title="Debug with generated sources">
<label data-id="debugGeneratedSourcesLabel" class="form-check-label custom-control-label" for="debugGeneratedSourcesInput">Debug generated sources if available (from Solidity v0.7.2)</label>
</div>
</div>
${this.txBrowser.render()}
${this.stepManagerView}
${this.debuggerHeadPanelsView}
</div>
${this.debuggerHeadPanelsView}
<div class="${css.statusMessage}">${this.statusMessage}</div>
${this.debuggerPanelsView}
</div>
Expand Down
2 changes: 1 addition & 1 deletion apps/remix-ide/src/remixAppManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const requiredModules = [ // services + layout views + system views
'terminal', 'settings', 'pluginManager']

export function isNative (name) {
const nativePlugins = ['vyper', 'workshops']
const nativePlugins = ['vyper', 'workshops', 'debugger']
return nativePlugins.includes(name) || requiredModules.includes(name)
}

Expand Down
12 changes: 10 additions & 2 deletions libs/remix-astwalker/src/astWalker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ export function isAstNode(node: Record<string, unknown>): boolean {
)
}

export function isYulAstNode(node: Record<string, unknown>): boolean {
return (
isObject(node) &&
'nodeType' in node &&
'src' in node
)
}


/**
* Crawl the given AST through the function walk(ast, callback)
Expand Down Expand Up @@ -200,7 +208,7 @@ export class AstWalker extends EventEmitter {
}
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/explicit-module-boundary-types
walkFullInternal(ast: AstNode, callback: Function) {
if (isAstNode(ast)) {
if (isAstNode(ast) || isYulAstNode(ast)) {
// console.log(`XXX id ${ast.id}, nodeType: ${ast.nodeType}, src: ${ast.src}`);
callback(ast);
for (const k of Object.keys(ast)) {
Expand All @@ -223,7 +231,7 @@ export class AstWalker extends EventEmitter {
// Normalizes parameter callback and calls walkFullInternal
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
walkFull(ast: AstNode, callback: any) {
if (isAstNode(ast)) return this.walkFullInternal(ast, callback);
if (isAstNode(ast) || isYulAstNode(ast)) return this.walkFullInternal(ast, callback);
}

// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/explicit-module-boundary-types
Expand Down
4 changes: 2 additions & 2 deletions libs/remix-astwalker/src/sourceMappings.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isAstNode, AstWalker } from './astWalker';
import { isAstNode, isYulAstNode, AstWalker } from './astWalker';
import { AstNode, LineColPosition, LineColRange, Location } from "./types";
import { util } from "@remix-project/remix-lib";

Expand Down Expand Up @@ -31,7 +31,7 @@ export function lineColPositionFromOffset(offset: number, lineBreaks: Array<numb
* @param astNode The object to convert.
*/
export function sourceLocationFromAstNode(astNode: AstNode): Location | null {
if (isAstNode(astNode) && astNode.src) {
if (isAstNode(astNode) && isYulAstNode(astNode) && astNode.src) {
return sourceLocationFromSrc(astNode.src)
}
return null;
Expand Down
15 changes: 13 additions & 2 deletions libs/remix-debug/src/Ethdebugger.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const {SolidityProxy, stateDecoder, localDecoder, InternalCallTree} = require('.
function Ethdebugger (opts) {
this.compilationResult = opts.compilationResult || function (contractAddress) { return null }
this.web3 = opts.web3
this.opts = opts

this.event = new EventManager()

Expand All @@ -36,16 +37,26 @@ function Ethdebugger (opts) {
this.solidityProxy = new SolidityProxy({getCurrentCalledAddressAt: this.traceManager.getCurrentCalledAddressAt.bind(this.traceManager), getCode: this.codeManager.getCode.bind(this.codeManager)})
this.storageResolver = null

this.callTree = new InternalCallTree(this.event, this.traceManager, this.solidityProxy, this.codeManager, { includeLocalVariables: true })
const includeLocalVariables = true
this.callTree = new InternalCallTree(this.event,
this.traceManager,
this.solidityProxy,
this.codeManager,
{ ...opts, includeLocalVariables})
}

Ethdebugger.prototype.setManagers = function () {
this.traceManager = new TraceManager({web3: this.web3})
this.codeManager = new CodeManager(this.traceManager)
this.solidityProxy = new SolidityProxy({getCurrentCalledAddressAt: this.traceManager.getCurrentCalledAddressAt.bind(this.traceManager), getCode: this.codeManager.getCode.bind(this.codeManager)})
this.storageResolver = null
const includeLocalVariables = true

this.callTree = new InternalCallTree(this.event, this.traceManager, this.solidityProxy, this.codeManager, { includeLocalVariables: true })
this.callTree = new InternalCallTree(this.event,
this.traceManager,
this.solidityProxy,
this.codeManager,
{ ...this.opts, includeLocalVariables})
this.event.trigger('managersChanged')
}

Expand Down
1 change: 1 addition & 0 deletions libs/remix-debug/src/cmdline/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class CmdLine {
this.txHash = txNumber
this.debugger.debug(null, txNumber, null, () => {
this.debugger.event.register('newSourceLocation', (lineColumnPos, rawLocation) => {
if (!lineColumnPos) return
this.lineColumnPos = lineColumnPos
this.rawLocation = rawLocation
this.events.emit('source', [lineColumnPos, rawLocation])
Expand Down
16 changes: 13 additions & 3 deletions libs/remix-debug/src/debugger/debugger.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ function Debugger (options) {

this.debugger = new Ethdebugger({
web3: options.web3,
compilationResult: this.compilationResult
debugWithGeneratedSources: options.debugWithGeneratedSources,
compilationResult: this.compilationResult,
})

const {traceManager, callTree, solidityProxy} = this.debugger
Expand Down Expand Up @@ -59,8 +60,17 @@ Debugger.prototype.registerAndHighlightCodeItem = async function (index) {

this.debugger.callTree.sourceLocationTracker.getValidSourceLocationFromVMTraceIndex(address, index, compilationResultForAddress.data.contracts).then((rawLocation) => {
if (compilationResultForAddress && compilationResultForAddress.data) {
var lineColumnPos = this.offsetToLineColumnConverter.offsetToLineColumn(rawLocation, rawLocation.file, compilationResultForAddress.source.sources, compilationResultForAddress.data.sources)
this.event.trigger('newSourceLocation', [lineColumnPos, rawLocation])
const generatedSources = this.debugger.callTree.sourceLocationTracker.getGeneratedSourcesFromAddress(address)
const astSources = Object.assign({}, compilationResultForAddress.data.sources)
const sources = Object.assign({}, compilationResultForAddress.source.sources)
if (generatedSources) {
for (const genSource of generatedSources) {
astSources[genSource.name] = { id: genSource.id, ast: genSource.ast }
sources[genSource.name] = { content: genSource.contents }
}
}
var lineColumnPos = this.offsetToLineColumnConverter.offsetToLineColumn(rawLocation, rawLocation.file, sources, astSources)
this.event.trigger('newSourceLocation', [lineColumnPos, rawLocation, generatedSources])
} else {
this.event.trigger('newSourceLocation', [null])
}
Expand Down
Loading