Static Analysis: Builtin Functions, Super keyword

pull/1/head
Michael Fröwis 8 years ago committed by chriseth
parent b2a5eafa21
commit f9f03813d0
  1. 4
      src/app/staticanalysis/modules/checksEffectsInteraction.js
  2. 2
      src/app/staticanalysis/modules/constantFunctions.js
  3. 22
      src/app/staticanalysis/modules/functionCallGraph.js
  4. 4
      src/app/staticanalysis/modules/gasCosts.js
  5. 23
      src/app/staticanalysis/modules/staticAnalysisCommon.js
  6. 4
      src/app/staticanalysis/modules/thisLocal.js
  7. 6
      src/app/staticanalysis/modules/txOrigin.js
  8. 99
      test/staticanalysis/staticAnalysisCommon-test.js
  9. 25
      test/staticanalysis/staticAnalysisIntegration-test.js
  10. 57
      test/staticanalysis/test-contracts/globals.sol

@ -9,7 +9,7 @@ function checksEffectsInteraction () {
this.contracts = []
checksEffectsInteraction.prototype.visit = new AbstractAst().builder(
(node) => common.isInteraction(node) || common.isEffect(node) || (common.isLocalCall(node) && !common.isBuiltinFunctionCall(node)),
(node) => common.isInteraction(node) || common.isEffect(node) || common.isLocalCallGraphRelevantNode(node),
this.contracts
)
}
@ -64,7 +64,7 @@ function isPotentialVulnerableFunction (func, context) {
}
function isLocalCallWithStateChange (node, context) {
if (common.isLocalCall(node)) {
if (common.isLocalCallGraphRelevantNode(node)) {
var func = callGraph.resolveCallGraphSymbol(context.cg, common.getFullQualifiedFunctionCallIdent(context.currentContract.node, node))
return !func || (func && func.node.changesState)
}

@ -9,7 +9,7 @@ function constantFunctions () {
this.contracts = []
constantFunctions.prototype.visit = new AbstractAst().builder(
(node) => common.isLowLevelCall(node) || common.isExternalDirectCall(node) || common.isEffect(node) || (common.isLocalCall(node) && !common.isBuiltinFunctionCall(node)) || common.isInlineAssembly(node),
(node) => common.isLowLevelCall(node) || common.isExternalDirectCall(node) || common.isEffect(node) || common.isLocalCallGraphRelevantNode(node) || common.isInlineAssembly(node),
this.contracts
)
}

@ -19,7 +19,7 @@ function buildLocalFuncCallGraphInternal (functions, nodeFilter, extractNodeIden
function buildGlobalFuncCallGraph (contracts) {
var callGraph = {}
contracts.forEach((contract) => {
var filterNodes = (node) => { return common.isLocalCall(node) || common.isThisLocalCall(node) || common.isExternalDirectCall(node) }
var filterNodes = (node) => { return common.isLocalCallGraphRelevantNode(node) || common.isExternalDirectCall(node) }
var getNodeIdent = (node) => { return common.getFullQualifiedFunctionCallIdent(contract.node, node) }
var getFunDefIdent = (funcDef) => { return common.getFullQuallyfiedFuncDefinitionIdent(contract.node, funcDef.node, funcDef.parameters) }
@ -36,7 +36,7 @@ function analyseCallGraph (cg, funcName, context, nodeCheck) {
function analyseCallGraphInternal (cg, funcName, context, combinator, nodeCheck, visited) {
var current = resolveCallGraphSymbol(cg, funcName)
if (!current || visited[funcName]) return true
if (current === undefined || visited[funcName] === true) return true
visited[funcName] = true
return combinator(current.node.relevantNodes.reduce((acc, val) => combinator(acc, nodeCheck(val, context)), false),
@ -48,33 +48,31 @@ function resolveCallGraphSymbol (cg, funcName) {
}
function resolveCallGraphSymbolInternal (cg, funcName, silent) {
var current = null
var current
if (funcName.includes('.')) {
var parts = funcName.split('.')
var contractPart = parts[0]
var functionPart = parts[1]
var currentContract = cg[contractPart]
if (currentContract) {
if (!(currentContract === undefined)) {
current = currentContract.functions[funcName]
// resolve inheritance hierarchy
if (!current) {
if (current === undefined) {
// resolve inheritance lookup in linearized fashion
var inheritsFromNames = currentContract.contract.inheritsFrom.reverse()
for (var i = 0; i < inheritsFromNames.length; i++) {
var res = resolveCallGraphSymbolInternal(cg, inheritsFromNames[i] + '.' + functionPart, true)
if (res) return res
if (!(res === undefined)) return res
}
}
} else {
if (!silent) console.log(`static analysis functionCallGraph.js: Contract ${contractPart} not found in function call graph.`)
}
} else {
throw new Error('functionCallGraph.js: function does not have full qualified name.')
}
if (!current) {
if (!silent) console.log(`static analysis functionCallGraph.js: ${funcName} not found in function call graph.`)
return null
} else {
return current
}
if (current === undefined && !silent) console.log(`static analysis functionCallGraph.js: ${funcName} not found in function call graph.`)
return current
}
module.exports = {

@ -1,5 +1,5 @@
var name = 'gas costs: '
var desc = 'warn if the gas requiremets of functions are too high'
var name = 'Gas Costs: '
var desc = 'Warn if the gas requiremets of functions are too high.'
var categories = require('./categories')
function gasCosts () {

@ -77,8 +77,8 @@ function getType (node) {
// #################### Complex Getters
function getFunctionCallType (func) {
if (!(isExternalDirectCall(func) || isThisLocalCall(func) || isLocalCall(func))) throw new Error('staticAnalysisCommon.js: not function call Node')
if (isExternalDirectCall(func) || isThisLocalCall(func)) return func.attributes.type
if (!(isExternalDirectCall(func) || isThisLocalCall(func) || isSuperLocalCall(func) || isLocalCall(func))) throw new Error('staticAnalysisCommon.js: not function call Node')
if (isExternalDirectCall(func) || isThisLocalCall(func) || isSuperLocalCall(func)) return func.attributes.type
return findFirstSubNodeLTR(func, exactMatch(nodeTypes.IDENTIFIER)).attributes.type
}
@ -97,6 +97,11 @@ function getThisLocalCallName (localCallNode) {
return localCallNode.attributes.value
}
function getSuperLocalCallName (localCallNode) {
if (!isSuperLocalCall(localCallNode)) throw new Error('staticAnalysisCommon.js: not an super local call Node')
return localCallNode.attributes.member_name
}
function getExternalDirectCallContractName (extDirectCall) {
if (!isExternalDirectCall(extDirectCall)) throw new Error('staticAnalysisCommon.js: not an external direct call Node')
return extDirectCall.children[0].attributes.type.replace(new RegExp(basicRegex.CONTRACTTYPE), '')
@ -149,6 +154,7 @@ function getFunctionCallTypeParameterType (func) {
function getFullQualifiedFunctionCallIdent (contract, func) {
if (isLocalCall(func)) return getContractName(contract) + '.' + getLocalCallName(func) + '(' + getFunctionCallTypeParameterType(func) + ')'
else if (isThisLocalCall(func)) return getThisLocalCallContractName(func) + '.' + getThisLocalCallName(func) + '(' + getFunctionCallTypeParameterType(func) + ')'
else if (isSuperLocalCall(func)) return getContractName(contract) + '.' + getSuperLocalCallName(func) + '(' + getFunctionCallTypeParameterType(func) + ')'
else if (isExternalDirectCall(func)) return getExternalDirectCallContractName(func) + '.' + getExternalDirectCallMemberName(func) + '(' + getFunctionCallTypeParameterType(func) + ')'
else throw new Error('staticAnalysisCommon.js: Can not get function name form non function call node')
}
@ -193,6 +199,10 @@ function isInlineAssembly (node) {
// #################### Complex Node Identification
function isLocalCallGraphRelevantNode (node) {
return ((isLocalCall(node) || isSuperLocalCall(node)) && !isBuiltinFunctionCall(node))
}
function isBuiltinFunctionCall (node) {
return isLocalCall(node) && builtinFunctions[getLocalCallName(node) + '(' + getFunctionCallTypeParameterType(node) + ')'] === true
}
@ -238,7 +248,7 @@ function isCallToNonConstLocalFunction (node) {
}
function isExternalDirectCall (node) {
return isMemberAccess(node, basicRegex.EXTERNALFUNCTIONTYPE, undefined, basicRegex.CONTRACTTYPE, undefined) && !isThisLocalCall(node)
return isMemberAccess(node, basicRegex.EXTERNALFUNCTIONTYPE, undefined, basicRegex.CONTRACTTYPE, undefined) && !isThisLocalCall(node) && !isSuperLocalCall(node)
}
function isNowAccess (node) {
@ -259,6 +269,10 @@ function isThisLocalCall (node) {
return isMemberAccess(node, basicRegex.FUNCTIONTYPE, exactMatch('this'), basicRegex.CONTRACTTYPE, undefined)
}
function isSuperLocalCall (node) {
return isMemberAccess(node, basicRegex.FUNCTIONTYPE, exactMatch('super'), basicRegex.CONTRACTTYPE, undefined)
}
function isLocalCall (node) {
return nodeType(node, exactMatch(nodeTypes.FUNCTIONCALL)) &&
minNrOfChildren(node, 1) &&
@ -377,6 +391,7 @@ module.exports = {
getType: getType,
// #################### Complex Getters
getThisLocalCallName: getThisLocalCallName,
getSuperLocalCallName: getSuperLocalCallName,
getFunctionCallType: getFunctionCallType,
getContractName: getContractName,
getEffectedVariableName: getEffectedVariableName,
@ -400,6 +415,8 @@ module.exports = {
isBlockTimestampAccess: isBlockTimestampAccess,
isBlockBlockHashAccess: isBlockBlockHashAccess,
isThisLocalCall: isThisLocalCall,
isSuperLocalCall: isSuperLocalCall,
isLocalCallGraphRelevantNode: isLocalCallGraphRelevantNode,
isLocalCall: isLocalCall,
isWriteOnStateVariable: isWriteOnStateVariable,
isStateVariable: isStateVariable,

@ -1,5 +1,5 @@
var name = 'this on local: '
var desc = 'invocation of local functions via this'
var name = 'this on local calls: '
var desc = 'Invocation of local functions via this'
var categories = require('./categories')
var common = require('./staticAnalysisCommon')

@ -1,5 +1,5 @@
var name = 'tx origin: '
var desc = 'warn if tx.origin is used'
var name = 'tx.origin: '
var desc = 'Warn if tx.origin is used'
var categories = require('./categories')
function txOrigin () {
@ -20,7 +20,7 @@ txOrigin.prototype.visit = function (node) {
txOrigin.prototype.report = function () {
return this.txOriginNodes.map(function (item, i) {
return {
warning: `use of tx.origin: "tx.origin" is useful only in very exceptional cases.<br />
warning: `Use of tx.origin: "tx.origin" is useful only in very exceptional cases.<br />
If you use it for authentication, you usually want to replace it by "msg.sender", because otherwise any contract you call can act on your behalf.`,
location: item.src
}

@ -394,6 +394,80 @@ test('staticAnalysisCommon.getThisLocalCallName', function (t) {
t.throws(() => common.getThisLocalCallName(localCall), undefined, 'throws on other nodes')
})
test('staticAnalysisCommon.getSuperLocalCallName', function (t) {
t.plan(4)
var superLocal = {
'attributes': {
'member_name': 'duper',
'type': 'function ()'
},
'children': [
{
'attributes': {
'type': 'contract super a',
'value': 'super'
},
'name': 'Identifier'
}
],
'name': 'MemberAccess'
}
var localCall = {
'attributes': {
'type': 'tuple()',
'type_conversion': false
},
'children': [
{
'attributes': {
'type': 'function (struct Ballot.Voter storage pointer)',
'value': 'bli'
},
'id': 37,
'name': 'Identifier',
'src': '540:3:0'
},
{
'attributes': {
'type': 'struct Ballot.Voter storage pointer',
'value': 'x'
},
'id': 38,
'name': 'Identifier',
'src': '544:1:0'
}
],
'id': 39,
'name': 'FunctionCall',
'src': '540:6:0'
}
var thisLocalCall = { name: 'MemberAccess', children: [ { attributes: { value: 'this', type: 'contract test' }, name: 'Identifier' } ], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } }
var externalDirect = {
attributes: {
member_name: 'info',
type: 'function () payable external returns (uint256)'
},
children: [
{
attributes: {
type: 'contract InfoFeed',
value: 'f'
},
id: 30,
name: 'Identifier',
src: '405:1:0'
}
],
id: 32,
name: 'MemberAccess',
src: '405:6:0'
}
t.equal(common.getSuperLocalCallName(superLocal), 'duper', 'get local name from super local call')
t.throws(() => common.getSuperLocalCallName(thisLocalCall), 'throws on other nodes')
t.throws(() => common.getSuperLocalCallName(externalDirect), undefined, 'throws on other nodes')
t.throws(() => common.getSuperLocalCallName(localCall), undefined, 'throws on other nodes')
})
test('staticAnalysisCommon.getExternalDirectCallContractName', function (t) {
t.plan(3)
var localCall = {
@ -737,7 +811,6 @@ test('staticAnalysisCommon.getStateVariableDeclarationsFormContractNode', functi
'name': 'ContractDefinition'
}
var res = common.getStateVariableDeclarationsFormContractNode(contract).map(common.getDeclaredVariableName)
t.comment(res)
t.ok(res[0] === 'chairperson', 'var 1 should be ')
t.ok(res[1] === 'voters', 'var 2 should be ')
t.ok(res[2] === 'proposals', 'var 3 should be ')
@ -1673,6 +1746,30 @@ test('staticAnalysisCommon.isThisLocalCall', function (t) {
t.notOk(common.isNowAccess(node), 'is now used should not work')
})
test('staticAnalysisCommon.isSuperLocalCall', function (t) {
t.plan(4)
var node = {
'attributes': {
'member_name': 'duper',
'type': 'function ()'
},
'children': [
{
'attributes': {
'type': 'contract super a',
'value': 'super'
},
'name': 'Identifier'
}
],
'name': 'MemberAccess'
}
t.ok(common.isSuperLocalCall(node), 'is super.local_method() used should work')
t.notOk(common.isThisLocalCall(node), 'is this.local_method() used should not work')
t.notOk(common.isBlockTimestampAccess(node), 'is block.timestamp used should not work')
t.notOk(common.isNowAccess(node), 'is now used should not work')
})
test('staticAnalysisCommon.isLocalCall', function (t) {
t.plan(5)
var node1 = {

@ -20,7 +20,8 @@ var testFiles = [
'modifier2.sol',
'notReentrant.sol',
'structReentrant.sol',
'thisLocal.sol'
'thisLocal.sol',
'globals.sol'
]
var testFileAsts = {}
@ -47,7 +48,8 @@ test('Integration test thisLocal.js', function (t) {
'modifier2.sol': 0,
'notReentrant.sol': 0,
'structReentrant.sol': 0,
'thisLocal.sol': 1
'thisLocal.sol': 1,
'globals.sol': 0
}
runModuleOnFiles(module, t, (file, report) => {
@ -72,7 +74,8 @@ test('Integration test checksEffectsInteraction.js', function (t) {
'modifier2.sol': 1,
'notReentrant.sol': 0,
'structReentrant.sol': 1,
'thisLocal.sol': 0
'thisLocal.sol': 0,
'globals.sol': 1
}
runModuleOnFiles(module, t, (file, report) => {
@ -80,7 +83,7 @@ test('Integration test checksEffectsInteraction.js', function (t) {
})
})
test('Integration test constatnFunctions.js', function (t) {
test('Integration test constantFunctions.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/app/staticanalysis/modules/constantFunctions')
@ -97,7 +100,8 @@ test('Integration test constatnFunctions.js', function (t) {
'modifier2.sol': 1,
'notReentrant.sol': 0,
'structReentrant.sol': 0,
'thisLocal.sol': 1
'thisLocal.sol': 1,
'globals.sol': 0
}
runModuleOnFiles(module, t, (file, report) => {
@ -105,7 +109,7 @@ test('Integration test constatnFunctions.js', function (t) {
})
})
test('Integration test constantFunctions.js', function (t) {
test('Integration test inlineAssembly.js', function (t) {
t.plan(testFiles.length)
var module = require('../../src/app/staticanalysis/modules/inlineAssembly')
@ -122,7 +126,8 @@ test('Integration test constantFunctions.js', function (t) {
'modifier2.sol': 0,
'notReentrant.sol': 0,
'structReentrant.sol': 0,
'thisLocal.sol': 0
'thisLocal.sol': 0,
'globals.sol': 0
}
runModuleOnFiles(module, t, (file, report) => {
@ -147,7 +152,8 @@ test('Integration test txOrigin.js', function (t) {
'modifier2.sol': 0,
'notReentrant.sol': 0,
'structReentrant.sol': 0,
'thisLocal.sol': 0
'thisLocal.sol': 0,
'globals.sol': 1
}
runModuleOnFiles(module, t, (file, report) => {
@ -172,7 +178,8 @@ test('Integration test gasCosts.js', function (t) {
'modifier2.sol': 1,
'notReentrant.sol': 1,
'structReentrant.sol': 1,
'thisLocal.sol': 2
'thisLocal.sol': 2,
'globals.sol': 1
}
runModuleOnFiles(module, t, (file, report) => {

@ -0,0 +1,57 @@
pragma solidity ^0.4.9;
contract bla {
uint brr;
function duper() {
brr++;
}
}
contract a is bla {
function blub() {
brr++;
}
function r () {
address a;
bytes32 hash;
uint8 v;
bytes32 r;
bytes32 s;
block.blockhash(1);
block.coinbase;
block.difficulty;
block.gaslimit;
block.number;
block.timestamp;
msg.data;
msg.gas;
msg.sender;
msg.value;
now;
tx.gasprice;
tx.origin;
//assert(now == block.timestamp);
//require(now == block.timestamp);
keccak256(a);
sha3(a);
sha256(a);
ripemd160(a);
ecrecover(hash, v, r, s);
addmod(1, 2, 2);
mulmod(4,4,12);
a.balance;
blub();
a.send(a.balance);
super.duper();
//a.transfer(a.balance);
selfdestruct(a);
//revert();
}
}
Loading…
Cancel
Save