Skip to content

Commit

Permalink
Merge pull request ethereum#298 from ethereum/staticAnalysisi
Browse files Browse the repository at this point in the history
static analysis framework
  • Loading branch information
chriseth authored Nov 8, 2016
2 parents 5718c5f + 6f3c634 commit 6b13540
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 7 deletions.
17 changes: 17 additions & 0 deletions assets/css/browser-solidity.css
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,10 @@ body {
display: block;
}

#header #optionViews.staticanalysisView #staticanalysisView {
display: block;
}

#header #optionViews.txView input,
#header #optionViews.txView select {
max-width: 13em;
Expand Down Expand Up @@ -283,6 +287,19 @@ body {
cursor: pointer;
}

#staticanalysisView button {
background-color: #C6CFF7;
font-size: 12px;
padding: 0.25em;
margin-bottom: .5em;
color: black;
border:0 none;
border-radius: 3px;
width: 8em;
margin-right: 1em;
cursor: pointer;
}

#header .origin,
#header #executionContext {
display: block;
Expand Down
3 changes: 3 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
<li class="publishView" title="Publish" ><i class="fa fa-cloud-upload"></i></li>
<li class="debugView" title="Debugger"><i class="fa fa-bug"></i></li>
<li class="verificationView" title="Formal Verification"><i class="fa fa-check"></i></li>
<li class="staticanalysisView" title="Static Analysis"><i class="fa fa-search"></i></li>
<li id="helpButton"><a href="https://solidity.readthedocs.org" target="_blank" title="Open Documentation" class="fa fa-question"></a></li>
</ul>
<img id="solIcon" title="Solidity realtime compiler and runtime" src="assets/img/sol.svg" alt="Solidity realtime compiler and runtime">
Expand Down Expand Up @@ -123,6 +124,8 @@
<div id="debugView">
<div id="debugger" ></div>
</div>
<div id="staticanalysisView">
</div>
<div id="verificationView">
<p>This tab provides support for <b>formal verification</b> of Solidity contracts.<br/>
This feature is still in development and thus also not yet well documented,
Expand Down
4 changes: 4 additions & 0 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ var UniversalDApp = require('./universal-dapp.js')
var Debugger = require('./app/debugger')
var FormalVerification = require('./app/formalVerification')
var EventManager = require('./lib/eventManager')
var StaticAnalysis = require('./app/staticanalysis/staticAnalysisView')

// The event listener needs to be registered as early as possible, because the
// parent will send the message upon the "load" event.
Expand Down Expand Up @@ -444,6 +445,9 @@ var run = function () {

var renderer = new Renderer(editor, executionContext.web3(), updateFiles, udapp, executionContext, formalVerification.event, compiler.event) // eslint-disable-line

var staticanalysis = new StaticAnalysis(compiler, renderer)
$('#staticanalysisView').append(staticanalysis.render())

var autoCompile = document.querySelector('#autoCompile').checked

document.querySelector('#autoCompile').addEventListener('change', function () {
Expand Down
16 changes: 9 additions & 7 deletions src/app/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ function Renderer (editor, web3, updateFiles, udapp, executionContext, formalVer
})
}

Renderer.prototype.error = function (message, container, noAnnotations) {
Renderer.prototype.error = function (message, container, noAnnotations, type) {
var self = this
var type = utils.errortype(message)
if (!type) {
type = utils.errortype(message)
}
var $pre = $('<pre />').text(message)
var $error = $('<div class="sol ' + type + '"><div class="close"><i class="fa fa-close"></i></div></div>').prepend($pre)
if (container === undefined) {
Expand Down Expand Up @@ -66,12 +68,12 @@ Renderer.prototype.error = function (message, container, noAnnotations) {
}
self.editor.handleErrorClick(errLine, errCol)
})
$error.find('.close').click(function (ev) {
ev.preventDefault()
$error.remove()
return false
})
}
$error.find('.close').click(function (ev) {
ev.preventDefault()
$error.remove()
return false
})
}

Renderer.prototype.contracts = function (data, source) {
Expand Down
3 changes: 3 additions & 0 deletions src/app/staticanalysis/modules/list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = [
require('./txOrigin')
]
34 changes: 34 additions & 0 deletions src/app/staticanalysis/modules/txOrigin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
var name = 'tx origin'
var desc = 'warn if tx.origin is used'

function txOrigin () {
this.txOriginNode = []
}

txOrigin.prototype.visit = function (node) {
if (node.name === 'MemberAccess' &&
node.attributes.member_name === 'origin' &&
node.attributes.type === 'address' &&
node.children && node.children.length &&
node.children[0].attributes.type === 'tx' &&
node.children[0].attributes.value === 'tx') {
this.txOriginNode.push(node)
}
}

txOrigin.prototype.report = function (node) {
var report = this.txOriginNode.length + ' use of tx.origin\n'
this.txOriginNode.map(function (item, i) {
report += item.src + '\n'
})
return {
name: name,
report: report
}
}

module.exports = {
name: name,
description: desc,
Module: txOrigin
}
30 changes: 30 additions & 0 deletions src/app/staticanalysis/staticAnalysisRunner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict'
var AstWalker = require('ethereum-remix').util.AstWalker
var list = require('./modules/list')

function staticAnalysisRunner () {
}

staticAnalysisRunner.prototype.run = function (ast, toRun, callback) {
var walker = new AstWalker()
for (var k in ast) {
walker.walk(ast[k].AST, {'*': function (node) {
toRun.map(function (item, i) {
item.visit(node)
})
return true
}})
}

var reports = []
toRun.map(function (item, i) {
reports.push(item.report())
})
callback(reports)
}

staticAnalysisRunner.prototype.modules = function () {
return list
}

module.exports = staticAnalysisRunner
79 changes: 79 additions & 0 deletions src/app/staticanalysis/staticAnalysisView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use strict'
var StaticAnalysisRunner = require('./staticAnalysisRunner.js')
var yo = require('yo-yo')
var $ = require('jquery')

function staticAnalysisView (compiler, renderer) {
this.view = null
this.renderer = renderer
this.runner = new StaticAnalysisRunner()
this.modulesView = renderModules(this.runner.modules())
this.lastASTs = null
var self = this
compiler.event.register('compilationFinished', function (success, data, source) {
self.lastASTs = null
if (success) {
self.lastASTs = data.sources
}
})
}

staticAnalysisView.prototype.render = function () {
var self = this
var view = yo`<div>
<strong>Static Analysis</strong>
<div>Select analyser to run against current compiled contracts</div>
${this.modulesView}
<div>
<button onclick=${function () { self.run() }} >Run</button>
</div>
<div id='staticanalysisresult'></div>
</div>`
if (!this.view) {
this.view = view
}
return view
}

staticAnalysisView.prototype.selectedModules = function () {
if (!this.view) {
return []
}
var selected = this.view.querySelectorAll('[name="staticanalysismodule"]')
var toRun = []
for (var i = 0; i < selected.length; i++) {
var el = selected[i]
if (el.checked) {
var analyser = this.runner.modules()[el.attributes['index'].value]
toRun.push(new analyser.Module())
}
}
return toRun
}

staticAnalysisView.prototype.run = function () {
if (!this.view) {
return
}
var selected = this.selectedModules()
var warningContainer = $('#staticanalysisresult')
warningContainer.empty()
if (this.lastASTs) {
var self = this
this.runner.run(this.lastASTs, selected, function (results) {
results.map(function (item, i) {
self.renderer.error(item.name + ':\n\n' + item.report, warningContainer, null, 'warning')
})
})
} else {
warningContainer.html('No compiled AST available')
}
}

function renderModules (modules) {
return modules.map(function (item, i) {
return yo`<div><input type="checkbox" name="staticanalysismodule" checked='true' index=${i} >${item.name} (${item.description})</div>`
})
}

module.exports = staticAnalysisView
36 changes: 36 additions & 0 deletions test-browser/tests/staticanalysis.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use strict'
var contractHelper = require('../helpers/contracts')
var init = require('../helpers/init')
var sauce = require('./sauce')

var sources = {
'sources': {
'Untitled': `contract test1 { address test = tx.origin; } contract test2 {}`
}
}

module.exports = {
before: function (browser, done) {
init(browser, done)
},
'@sources': function () {
return sources
},
'Static Analysis': function (browser) {
runTests(browser)
},
tearDown: sauce
}

function runTests (browser) {
browser
.waitForElementVisible('.newFile', 10000)
contractHelper.testContracts(browser, sources.sources.Untitled, ['test1', 'test2'], function () {
browser
.click('.staticanalysisView')
.click('#staticanalysisView button')
.waitForElementPresent('#staticanalysisresult .warning')
.assert.containsText('#staticanalysisresult .warning pre', '1 use of tx.origin')
.end()
})
}

0 comments on commit 6b13540

Please sign in to comment.