Merge pull request #298 from ethereum/staticAnalysisi

static analysis framework
pull/1/head
chriseth 8 years ago committed by GitHub
commit 6b135401f6
  1. 17
      assets/css/browser-solidity.css
  2. 3
      index.html
  3. 4
      src/app.js
  4. 16
      src/app/renderer.js
  5. 3
      src/app/staticanalysis/modules/list.js
  6. 34
      src/app/staticanalysis/modules/txOrigin.js
  7. 30
      src/app/staticanalysis/staticAnalysisRunner.js
  8. 79
      src/app/staticanalysis/staticAnalysisView.js
  9. 36
      test-browser/tests/staticanalysis.js

@ -228,6 +228,10 @@ body {
display: block;
}
#header #optionViews.staticanalysisView #staticanalysisView {
display: block;
}
#header #optionViews.txView input,
#header #optionViews.txView select {
max-width: 13em;
@ -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;

@ -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">
@ -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,

@ -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.
@ -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 () {

@ -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) {
@ -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) {

@ -0,0 +1,3 @@
module.exports = [
require('./txOrigin')
]

@ -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
}

@ -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

@ -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

@ -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()
})
}
Loading…
Cancel
Save