Merge pull request #418 from ethereum/refactor

use API [WIP]
pull/1/head
chriseth 8 years ago committed by GitHub
commit d55a6dc43b
  1. 103
      src/app.js
  2. 77
      src/app/debugger.js
  3. 10
      src/app/editor.js
  4. 54
      src/app/renderer.js
  5. 10
      src/app/staticanalysis/staticAnalysisView.js
  6. 4
      src/lib/offsetToLineColumnConverter.js
  7. 25
      src/universal-dapp.js
  8. 2
      test-browser/tests/staticanalysis.js

@ -494,29 +494,122 @@ var run = function () {
var executionContext = new ExecutionContext() var executionContext = new ExecutionContext()
var compiler = new Compiler(handleImportCall) var compiler = new Compiler(handleImportCall)
var formalVerification = new FormalVerification($('#verificationView'), compiler.event) var formalVerification = new FormalVerification($('#verificationView'), compiler.event)
var offsetToLineColumnConverter = new OffsetToLineColumnConverter(compiler.event) var offsetToLineColumnConverter = new OffsetToLineColumnConverter(compiler.event)
var transactionDebugger = new Debugger('#debugger', editor, compiler, executionContext.event, switchToFile, offsetToLineColumnConverter) // ----------------- Debugger -----------------
var debugAPI = {
statementMarker: null,
fullLineMarker: null,
currentSourceLocation: (lineColumnPos, location) => {
if (this.statementMarker) editor.removeMarker(this.statementMarker)
if (this.fullLineMarker) editor.removeMarker(this.fullLineMarker)
this.statementMarker = null
this.fullLineMarker = null
if (lineColumnPos) {
var name = editor.getCacheFile() // current opened tab
var source = compiler.lastCompilationResult.data.sourceList[location.file] // auto switch to that tab
if (name !== source) {
switchToFile(source)
}
this.statementMarker = editor.addMarker(lineColumnPos, 'highlightcode')
if (lineColumnPos.start.line === lineColumnPos.end.line) {
this.fullLineMarker = editor.addMarker({
start: {
line: lineColumnPos.start.line,
column: 0
},
end: {
line: lineColumnPos.start.line + 1,
column: 0
}
}, 'highlightcode_fullLine')
}
}
},
lastCompilationResult: () => {
return compiler.lastCompilationResult
},
offsetToLineColumn: (location, file) => {
return offsetToLineColumnConverter.offsetToLineColumn(location, file, compiler.lastCompilationResult)
}
}
var transactionDebugger = new Debugger('#debugger', debugAPI, executionContext.event, editor.event)
transactionDebugger.addProvider('vm', executionContext.vm()) transactionDebugger.addProvider('vm', executionContext.vm())
transactionDebugger.addProvider('injected', executionContext.web3()) transactionDebugger.addProvider('injected', executionContext.web3())
transactionDebugger.addProvider('web3', executionContext.web3()) transactionDebugger.addProvider('web3', executionContext.web3())
transactionDebugger.switchProvider(executionContext.getProvider()) transactionDebugger.switchProvider(executionContext.getProvider())
// ----------------- UniversalDApp -----------------
var udapp = new UniversalDApp(executionContext, { var udapp = new UniversalDApp(executionContext, {
removable: false, removable: false,
removable_instances: true removable_instances: true
}, transactionDebugger) })
udapp.event.register('debugRequested', this, function (txResult) { udapp.event.register('debugRequested', this, function (txResult) {
startdebugging(txResult.transactionHash) startdebugging(txResult.transactionHash)
}) })
var renderer = new Renderer(editor, updateFiles, udapp, executionContext, formalVerification.event, compiler.event) // eslint-disable-line // ----------------- Renderer -----------------
var transactionContextAPI = {
getAddress: (cb) => {
cb(null, $('#txorigin').val())
},
getValue: (cb) => {
try {
var comp = $('#value').val().split(' ')
cb(null, executionContext.web3().toWei(comp[0], comp.slice(1).join(' ')))
} catch (e) {
cb(e)
}
},
getGasLimit: (cb) => {
cb(null, $('#gasLimit').val())
}
}
var staticanalysis = new StaticAnalysis(compiler.event, renderer, editor, offsetToLineColumnConverter) var rendererAPI = {
error: (file, error) => {
if (file === editor.getCacheFile()) {
editor.addAnnotation(error)
}
},
errorClick: (errFile, errLine, errCol) => {
if (errFile !== editor.getCacheFile() && editor.hasFile(errFile)) {
switchToFile(errFile)
}
editor.gotoLine(errLine, errCol)
},
currentCompiledSourceCode: () => {
if (compiler.lastCompilationResult.source) {
return compiler.lastCompilationResult.source.sources[compiler.lastCompilationResult.source.target]
}
return ''
},
resetDapp: (udappContracts, renderOutputModifier) => {
udapp.reset(udappContracts, transactionContextAPI, renderOutputModifier)
},
renderDapp: () => {
return udapp.render()
},
getAccounts: (callback) => {
udapp.getAccounts(callback)
}
}
var renderer = new Renderer(rendererAPI, formalVerification.event, compiler.event)
// ----------------- StaticAnalysis -----------------
var staticAnalysisAPI = {
renderWarning: (label, warningContainer, type) => {
return renderer.error(label, warningContainer, type)
},
offsetToLineColumn: (location, file) => {
return offsetToLineColumnConverter.offsetToLineColumn(location, file, compiler.lastCompilationResult)
}
}
var staticanalysis = new StaticAnalysis(staticAnalysisAPI, compiler.event)
$('#staticanalysisView').append(staticanalysis.render()) $('#staticanalysisView').append(staticanalysis.render())
// ----------------- autoCompile -----------------
var autoCompile = document.querySelector('#autoCompile').checked var autoCompile = document.querySelector('#autoCompile').checked
if (config.exists('autoCompile')) { if (config.exists('autoCompile')) {
autoCompile = config.get('autoCompile') autoCompile = config.get('autoCompile')

@ -1,22 +1,16 @@
'use strict' 'use strict'
var remix = require('ethereum-remix') var remix = require('ethereum-remix')
var ace = require('brace')
var Range = ace.acequire('ace/range').Range
/** /**
* Manage remix and source highlighting * Manage remix and source highlighting
*/ */
function Debugger (id, editor, compiler, executionContextEvent, switchToFile, offsetToLineColumnConverter) { function Debugger (id, appAPI, executionContextEvent, editorEvent) {
this.el = document.querySelector(id) this.el = document.querySelector(id)
this.offsetToLineColumnConverter = offsetToLineColumnConverter
this.debugger = new remix.ui.Debugger() this.debugger = new remix.ui.Debugger()
this.sourceMappingDecoder = new remix.util.SourceMappingDecoder() this.sourceMappingDecoder = new remix.util.SourceMappingDecoder()
this.el.appendChild(this.debugger.render()) this.el.appendChild(this.debugger.render())
this.editor = editor this.appAPI = appAPI
this.switchToFile = switchToFile
this.compiler = compiler
this.markers = {}
var self = this var self = this
executionContextEvent.register('contextChanged', this, function (context) { executionContextEvent.register('contextChanged', this, function (context) {
@ -24,23 +18,23 @@ function Debugger (id, editor, compiler, executionContextEvent, switchToFile, of
}) })
this.debugger.event.register('traceUnloaded', this, function () { this.debugger.event.register('traceUnloaded', this, function () {
self.removeMarkers() self.appAPI.currentSourceLocation(null)
}) })
// unload if a file has changed (but not if tabs were switched) // unload if a file has changed (but not if tabs were switched)
editor.event.register('contentChanged', function () { editorEvent.register('contentChanged', function () {
self.debugger.unLoad() self.debugger.unLoad()
}) })
// register selected code item, highlight the corresponding source location // register selected code item, highlight the corresponding source location
this.debugger.codeManager.event.register('changed', this, function (code, address, index) { this.debugger.codeManager.event.register('changed', this, function (code, address, index) {
if (self.compiler.lastCompilationResult) { if (self.appAPI.lastCompilationResult()) {
this.debugger.callTree.sourceLocationTracker.getSourceLocationFromInstructionIndex(address, index, self.compiler.lastCompilationResult.data.contracts, function (error, rawLocation) { this.debugger.callTree.sourceLocationTracker.getSourceLocationFromInstructionIndex(address, index, self.appAPI.lastCompilationResult().data.contracts, function (error, rawLocation) {
if (!error) { if (!error) {
var lineColumnPos = self.offsetToLineColumnConverter.offsetToLineColumn(rawLocation, rawLocation.file, self.editor, self.compiler.lastCompilationResult.data) var lineColumnPos = self.appAPI.offsetToLineColumn(rawLocation, rawLocation.file)
self.highlight(lineColumnPos, rawLocation) self.appAPI.currentSourceLocation(lineColumnPos, rawLocation)
} else { } else {
self.unhighlight() self.appAPI.currentSourceLocation(null)
} }
}) })
} }
@ -56,44 +50,12 @@ Debugger.prototype.debug = function (txHash) {
var self = this var self = this
this.debugger.web3().eth.getTransaction(txHash, function (error, tx) { this.debugger.web3().eth.getTransaction(txHash, function (error, tx) {
if (!error) { if (!error) {
self.debugger.setCompilationResult(self.compiler.lastCompilationResult.data) self.debugger.setCompilationResult(self.appAPI.lastCompilationResult().data)
self.debugger.debug(tx) self.debugger.debug(tx)
} }
}) })
} }
/**
* highlight the given @arg lineColumnPos
*
* @param {Object} lineColumnPos - position of the source code to hightlight {start: {line, column}, end: {line, column}}
* @param {Object} rawLocation - raw position of the source code to hightlight {start, length, file, jump}
*/
Debugger.prototype.highlight = function (lineColumnPos, rawLocation) {
this.unhighlight()
var name = this.editor.getCacheFile() // current opened tab
var source = this.compiler.lastCompilationResult.data.sourceList[rawLocation.file] // auto switch to that tab
if (name !== source) {
this.switchToFile(source) // command the app to swicth to the next file
}
var range = new Range(lineColumnPos.start.line, lineColumnPos.start.column, lineColumnPos.end.line, lineColumnPos.end.column)
this.markers['highlightcode'] = this.editor.addMarker(range, 'highlightcode')
if (lineColumnPos.start.line === lineColumnPos.end.line) {
var fullrange = new Range(lineColumnPos.start.line, 0, lineColumnPos.start.line + 1, 0)
this.markers['highlightcode_fullLine'] = this.editor.addMarker(fullrange, 'highlightcode_fullLine')
}
}
/**
* unhighlight the given @arg lineColumnPos
*
* @param {Object} lineColumnPos - position of the source code to hightlight {start: {line, column}, end: {line, column}}
* @param {Object} rawLocation - raw position of the source code to hightlight {start, length, file, jump}
*/
Debugger.prototype.unhighlight = function (lineColumnPos, rawLocation, cssCode) {
this.removeMarker('highlightcode')
this.removeMarker('highlightcode_fullLine')
}
/** /**
* add a new web3 provider to remix * add a new web3 provider to remix
* *
@ -120,23 +82,4 @@ Debugger.prototype.web3 = function (type) {
return this.debugger.web3() return this.debugger.web3()
} }
/**
* unhighlight highlighted statements
*/
Debugger.prototype.removeMarkers = function () {
for (var k in this.markers) {
this.removeMarker(k)
}
}
/**
* unhighlight the current highlighted statement
*/
Debugger.prototype.removeMarker = function (key) {
if (this.markers[key]) {
this.editor.removeMarker(this.markers[key])
this.markers[key] = null
}
}
module.exports = Debugger module.exports = Debugger

@ -5,6 +5,7 @@ var EventManager = require('../lib/eventManager')
var examples = require('./example-contracts') var examples = require('./example-contracts')
var ace = require('brace') var ace = require('brace')
var Range = ace.acequire('ace/range').Range
require('../mode-solidity.js') require('../mode-solidity.js')
function Editor (doNotLoadStorage, storage) { function Editor (doNotLoadStorage, storage) {
@ -17,8 +18,9 @@ function Editor (doNotLoadStorage, storage) {
var sessions = {} var sessions = {}
var sourceAnnotations = [] var sourceAnnotations = []
this.addMarker = function (range, cssClass) { this.addMarker = function (lineColumnPos, cssClass) {
return editor.session.addMarker(range, cssClass) var currentRange = new Range(lineColumnPos.start.line, lineColumnPos.start.column, lineColumnPos.end.line, lineColumnPos.end.column)
return editor.session.addMarker(currentRange, cssClass)
} }
this.removeMarker = function (markerId) { this.removeMarker = function (markerId) {
@ -149,9 +151,9 @@ function Editor (doNotLoadStorage, storage) {
editor.getSession().setAnnotations(sourceAnnotations) editor.getSession().setAnnotations(sourceAnnotations)
} }
this.handleErrorClick = function (errLine, errCol) { this.gotoLine = function (line, col) {
editor.focus() editor.focus()
editor.gotoLine(errLine + 1, errCol - 1, true) editor.gotoLine(line + 1, col - 1, true)
} }
function newEditorSession (filekey) { function newEditorSession (filekey) {

@ -4,11 +4,8 @@ var $ = require('jquery')
var utils = require('./utils') var utils = require('./utils')
function Renderer (editor, updateFiles, udapp, executionContext, formalVerificationEvent, compilerEvent) { function Renderer (appAPI, formalVerificationEvent, compilerEvent) {
this.editor = editor this.appAPI = appAPI
this.updateFiles = updateFiles
this.udapp = udapp
this.executionContext = executionContext
var self = this var self = this
formalVerificationEvent.register('compilationFinished', this, function (success, message, container, options) { formalVerificationEvent.register('compilationFinished', this, function (success, message, container, options) {
if (!success) { if (!success) {
@ -55,8 +52,8 @@ Renderer.prototype.error = function (message, container, options) {
var errFile = err[1] var errFile = err[1]
var errLine = parseInt(err[2], 10) - 1 var errLine = parseInt(err[2], 10) - 1
var errCol = err[4] ? parseInt(err[4], 10) : 0 var errCol = err[4] ? parseInt(err[4], 10) : 0
if (!opt.noAnnotations && (errFile === '' || errFile === self.editor.getCacheFile())) { if (!opt.noAnnotations) {
self.editor.addAnnotation({ self.appAPI.error(errFile, {
row: errLine, row: errLine,
column: errCol, column: errCol,
text: message, text: message,
@ -64,12 +61,7 @@ Renderer.prototype.error = function (message, container, options) {
}) })
} }
$error.click(function (ev) { $error.click(function (ev) {
if (errFile !== '' && errFile !== self.editor.getCacheFile() && self.editor.hasFile(errFile)) { self.appAPI.errorClick(errFile, errLine, errCol)
// Switch to file
self.editor.setCacheFile(errFile)
self.updateFiles()
}
self.editor.handleErrorClick(errLine, errCol)
}) })
} }
$error.find('.close').click(function (ev) { $error.find('.close').click(function (ev) {
@ -263,6 +255,7 @@ Renderer.prototype.contracts = function (data, source) {
return $('<div class="contractDetails"/>').append(button).append(details) return $('<div class="contractDetails"/>').append(button).append(details)
} }
var self = this
var renderOutputModifier = function (contractName, $contractOutput) { var renderOutputModifier = function (contractName, $contractOutput) {
var contract = data.contracts[contractName] var contract = data.contracts[contractName]
if (contract.bytecode) { if (contract.bytecode) {
@ -281,41 +274,20 @@ Renderer.prototype.contracts = function (data, source) {
} }
} }
var ctrSource = getSource(contractName, source, data) var ctrSource = self.appAPI.currentCompiledSourceCode()
return $contractOutput.append(getDetails(contract, ctrSource, contractName)) if (ctrSource) {
} $contractOutput.append(getDetails(contract, ctrSource, contractName))
var self = this
var getSource = function (contractName, source, data) {
var currentFile = self.editor.getCacheFile()
return source.sources[currentFile]
}
var getAddress = function (cb) {
cb(null, $('#txorigin').val())
}
var getValue = function (cb) {
try {
var comp = $('#value').val().split(' ')
cb(null, self.executionContext.web3().toWei(comp[0], comp.slice(1).join(' ')))
} catch (e) {
cb(e)
} }
return $contractOutput
} }
var getGasLimit = function (cb) { this.appAPI.resetDapp(udappContracts, renderOutputModifier)
cb(null, $('#gasLimit').val())
}
this.udapp.reset(udappContracts, getAddress, getValue, getGasLimit, renderOutputModifier)
var $contractOutput = this.udapp.render() var $contractOutput = this.appAPI.renderDapp()
var $txOrigin = $('#txorigin') var $txOrigin = $('#txorigin')
this.udapp.getAccounts(function (err, accounts) { this.appAPI.getAccounts(function (err, accounts) {
if (err) { if (err) {
self.error(err.message) self.error(err.message)
} }

@ -3,12 +3,10 @@ var StaticAnalysisRunner = require('./staticAnalysisRunner.js')
var yo = require('yo-yo') var yo = require('yo-yo')
var $ = require('jquery') var $ = require('jquery')
function staticAnalysisView (compilerEvent, renderer, editor, offsetToColumnConverter) { function staticAnalysisView (appAPI, compilerEvent) {
this.view = null this.view = null
this.renderer = renderer this.appAPI = appAPI
this.editor = editor
this.runner = new StaticAnalysisRunner() this.runner = new StaticAnalysisRunner()
this.offsetToColumnConverter = offsetToColumnConverter
this.modulesView = renderModules(this.runner.modules()) this.modulesView = renderModules(this.runner.modules())
this.lastCompilationResult = null this.lastCompilationResult = null
var self = this var self = this
@ -75,10 +73,10 @@ staticAnalysisView.prototype.run = function () {
start: parseInt(split[0]), start: parseInt(split[0]),
length: parseInt(split[1]) length: parseInt(split[1])
} }
location = self.offsetToColumnConverter.offsetToLineColumn(location, file, self.editor, self.lastCompilationResult) location = self.appAPI.offsetToLineColumn(location, file)
location = self.lastCompilationResult.sourceList[file] + ':' + (location.start.line + 1) + ':' + (location.start.column + 1) + ':' location = self.lastCompilationResult.sourceList[file] + ':' + (location.start.line + 1) + ':' + (location.start.column + 1) + ':'
} }
self.renderer.error(location + ' ' + item.warning, warningContainer, {type: 'warning', useSpan: true, isHTML: true}) self.appAPI.renderWarning(location + ' ' + item.warning, warningContainer, {type: 'warning', useSpan: true, isHTML: true})
}) })
}) })
if (warningContainer.html() === '') { if (warningContainer.html() === '') {

@ -10,9 +10,9 @@ function offsetToColumnConverter (compilerEvent) {
}) })
} }
offsetToColumnConverter.prototype.offsetToLineColumn = function (rawLocation, file, editor, compilationResult) { offsetToColumnConverter.prototype.offsetToLineColumn = function (rawLocation, file, compilationResult) {
if (!this.lineBreakPositionsByContent[file]) { if (!this.lineBreakPositionsByContent[file]) {
this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(editor.getFile(compilationResult.sourceList[file])) this.lineBreakPositionsByContent[file] = this.sourceMappingDecoder.getLinebreakPositions(compilationResult.source.sources[compilationResult.data.sourceList[file]])
} }
return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file]) return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file])
} }

@ -13,7 +13,7 @@ var TxRunner = require('./app/txRunner')
/* /*
trigger debugRequested trigger debugRequested
*/ */
function UniversalDApp (executionContext, options, txdebugger) { function UniversalDApp (executionContext, options) {
this.event = new EventManager() this.event = new EventManager()
var self = this var self = this
@ -21,10 +21,7 @@ function UniversalDApp (executionContext, options, txdebugger) {
self.$el = $('<div class="udapp" />') self.$el = $('<div class="udapp" />')
self.personalMode = self.options.personalMode || false self.personalMode = self.options.personalMode || false
self.contracts self.contracts
self.getAddress self.transactionContextAPI
self.getValue
self.getGasLimit
self.txdebugger = txdebugger // temporary: will not be needed anymore when we'll add memory support to the VM
var defaultRenderOutputModifier = function (name, content) { return content } var defaultRenderOutputModifier = function (name, content) { return content }
self.renderOutputModifier = defaultRenderOutputModifier self.renderOutputModifier = defaultRenderOutputModifier
self.web3 = executionContext.web3() self.web3 = executionContext.web3()
@ -39,12 +36,10 @@ function UniversalDApp (executionContext, options, txdebugger) {
}) })
} }
UniversalDApp.prototype.reset = function (contracts, getAddress, getValue, getGasLimit, renderer) { UniversalDApp.prototype.reset = function (contracts, transactionContextAPI, renderer) {
this.$el.empty() this.$el.empty()
this.contracts = contracts this.contracts = contracts
this.getAddress = getAddress this.transactionContextAPI = transactionContextAPI
this.getValue = getValue
this.getGasLimit = getGasLimit
this.renderOutputModifier = renderer this.renderOutputModifier = renderer
this.accounts = {} this.accounts = {}
if (this.executionContext.isVM()) { if (this.executionContext.isVM()) {
@ -734,8 +729,8 @@ UniversalDApp.prototype.runTx = function (args, cb) {
function (callback) { function (callback) {
tx.gasLimit = 3000000 tx.gasLimit = 3000000
if (self.getGasLimit) { if (self.transactionContextAPI.getGasLimit) {
self.getGasLimit(function (err, ret) { self.transactionContextAPI.getGasLimit(function (err, ret) {
if (err) { if (err) {
return callback(err) return callback(err)
} }
@ -751,8 +746,8 @@ UniversalDApp.prototype.runTx = function (args, cb) {
function (callback) { function (callback) {
tx.value = 0 tx.value = 0
if (self.getValue) { if (self.transactionContextAPI.getValue) {
self.getValue(function (err, ret) { self.transactionContextAPI.getValue(function (err, ret) {
if (err) { if (err) {
return callback(err) return callback(err)
} }
@ -766,8 +761,8 @@ UniversalDApp.prototype.runTx = function (args, cb) {
}, },
// query address // query address
function (callback) { function (callback) {
if (self.getAddress) { if (self.transactionContextAPI.getAddress) {
self.getAddress(function (err, ret) { self.transactionContextAPI.getAddress(function (err, ret) {
if (err) { if (err) {
return callback(err) return callback(err)
} }

@ -37,7 +37,7 @@ function runTests (browser) {
.click('.staticanalysisView') .click('.staticanalysisView')
.click('#staticanalysisView button') .click('#staticanalysisView button')
.waitForElementPresent('#staticanalysisresult .warning', 2000, true, function () { .waitForElementPresent('#staticanalysisresult .warning', 2000, true, function () {
dom.listSelectorContains(['Untitled:1:34: use of tx.origin', dom.listSelectorContains(['Untitled:2:33: use of tx.origin',
'Fallback function of contract Untitled:TooMuchGas requires too much gas'], 'Fallback function of contract Untitled:TooMuchGas requires too much gas'],
'#staticanalysisresult .warning span', '#staticanalysisresult .warning span',
browser, function () { browser, function () {

Loading…
Cancel
Save