Commit d0e2d123 authored by Michael Fröwis's avatar Michael Fröwis Committed by chriseth

Static Analysis: changes as suggested by chriseth

parent 6bb29630
...@@ -9,11 +9,36 @@ function abstractAstView () { ...@@ -9,11 +9,36 @@ function abstractAstView () {
this.isFunctionNotModifier = false this.isFunctionNotModifier = false
} }
/**
* Builds a higher level AST view. I creates a list with each contract as an object in it.
* Example contractsOut:
*
* {
* "node": {}, // actual AST Node of the contract
* "functions": [
* {
* "node": {}, // actual AST Node of the function
* "relevantNodes": [], // AST nodes in the function that are relevant for the anlysis of this function
* "modifierInvocations": [], // Modifier invocation AST nodes that are applied on this function
* "localVariables": [], // Local variable declaration nodes
* "parameters": [] // Parameter types of the function in order of definition
* }
* ],
* "modifiers": [], // Modifiers definded by the contract, format similar to functions
* "inheritsFrom": [], // Names of contract this one inherits from in order of definition
* "stateVariables": [] // AST nodes of all State variables
* }
*
* @relevantNodeFilter {ASTNode -> bool} function that selects relevant ast nodes for analysis on function level.
* @contractsOut {list} return list for high level AST view
* @return {ASTNode -> void} returns a function that can be used as visit function for static analysis modules, to build up a higher level AST view for further analysis.
*/
abstractAstView.prototype.builder = function (relevantNodeFilter, contractsOut) { abstractAstView.prototype.builder = function (relevantNodeFilter, contractsOut) {
this.contracts = contractsOut this.contracts = contractsOut
var that = this
return function (node) { return function (node) {
if (common.isContractDefinition(node)) { if (common.isContractDefinition(node)) {
setCurrentContract(this, { setCurrentContract(that, {
node: node, node: node,
functions: [], functions: [],
modifiers: [], modifiers: [],
...@@ -21,14 +46,14 @@ abstractAstView.prototype.builder = function (relevantNodeFilter, contractsOut) ...@@ -21,14 +46,14 @@ abstractAstView.prototype.builder = function (relevantNodeFilter, contractsOut)
stateVariables: common.getStateVariableDeclarationsFormContractNode(node) stateVariables: common.getStateVariableDeclarationsFormContractNode(node)
}) })
} else if (common.isInheritanceSpecifier(node)) { } else if (common.isInheritanceSpecifier(node)) {
var currentContract = getCurrentContract(this) var currentContract = getCurrentContract(that)
var inheritsFromName = common.getInheritsFromName(node) var inheritsFromName = common.getInheritsFromName(node)
currentContract.inheritsFrom.push(inheritsFromName) currentContract.inheritsFrom.push(inheritsFromName)
// add variables from inherited contracts // add variables from inherited contracts
var inheritsFrom = this.contracts.find((contract) => common.getContractName(contract.node) === inheritsFromName) var inheritsFrom = that.contracts.find((contract) => common.getContractName(contract.node) === inheritsFromName)
currentContract.stateVariables = currentContract.stateVariables.concat(inheritsFrom.stateVariables) currentContract.stateVariables = currentContract.stateVariables.concat(inheritsFrom.stateVariables)
} else if (common.isFunctionDefinition(node)) { } else if (common.isFunctionDefinition(node)) {
setCurrentFunction(this, { setCurrentFunction(that, {
node: node, node: node,
relevantNodes: [], relevantNodes: [],
modifierInvocations: [], modifierInvocations: [],
...@@ -36,17 +61,17 @@ abstractAstView.prototype.builder = function (relevantNodeFilter, contractsOut) ...@@ -36,17 +61,17 @@ abstractAstView.prototype.builder = function (relevantNodeFilter, contractsOut)
parameters: getLocalParameters(node) parameters: getLocalParameters(node)
}) })
} else if (common.isModifierDefinition(node)) { } else if (common.isModifierDefinition(node)) {
setCurrentModifier(this, { setCurrentModifier(that, {
node: node, node: node,
relevantNodes: [], relevantNodes: [],
localVariables: getLocalVariables(node), localVariables: getLocalVariables(node),
parameters: getLocalParameters(node) parameters: getLocalParameters(node)
}) })
} else if (common.isModifierInvocation(node)) { } else if (common.isModifierInvocation(node)) {
if (!this.isFunctionNotModifier) throw new Error('abstractAstView.js: Found modifier invocation outside of function scope.') if (!that.isFunctionNotModifier) throw new Error('abstractAstView.js: Found modifier invocation outside of function scope.')
getCurrentFunction(this).modifierInvocations.push(node) getCurrentFunction(that).modifierInvocations.push(node)
} else if (relevantNodeFilter(node)) { } else if (relevantNodeFilter(node)) {
((this.isFunctionNotModifier) ? getCurrentFunction(this) : getCurrentModifier(this)).relevantNodes.push(node) ((that.isFunctionNotModifier) ? getCurrentFunction(that) : getCurrentModifier(that)).relevantNodes.push(node)
} }
} }
} }
......
...@@ -2,15 +2,16 @@ var name = 'Checks-Effects-Interaction pattern' ...@@ -2,15 +2,16 @@ var name = 'Checks-Effects-Interaction pattern'
var desc = 'Avoid potential reentrancy bugs' var desc = 'Avoid potential reentrancy bugs'
var categories = require('./categories') var categories = require('./categories')
var common = require('./staticAnalysisCommon') var common = require('./staticAnalysisCommon')
var callGraph = require('./functionCallGraph') var fcallGraph = require('./functionCallGraph')
var AbstractAst = require('./abstractAstView') var AbstractAst = require('./abstractAstView')
function checksEffectsInteraction () { function checksEffectsInteraction () {
this.contracts = [] this.contracts = []
var that = this
checksEffectsInteraction.prototype.visit = new AbstractAst().builder( checksEffectsInteraction.prototype.visit = new AbstractAst().builder(
(node) => common.isInteraction(node) || common.isEffect(node) || common.isLocalCallGraphRelevantNode(node), (node) => common.isInteraction(node) || common.isEffect(node) || common.isLocalCallGraphRelevantNode(node),
this.contracts that.contracts
) )
} }
...@@ -18,16 +19,16 @@ checksEffectsInteraction.prototype.report = function (compilationResults) { ...@@ -18,16 +19,16 @@ checksEffectsInteraction.prototype.report = function (compilationResults) {
var warnings = [] var warnings = []
var hasModifiers = this.contracts.some((item) => item.modifiers.length > 0) var hasModifiers = this.contracts.some((item) => item.modifiers.length > 0)
var cg = callGraph.buildGlobalFuncCallGraph(this.contracts) var callGraph = fcallGraph.buildGlobalFuncCallGraph(this.contracts)
this.contracts.forEach((contract) => { this.contracts.forEach((contract) => {
contract.functions.forEach((func) => { contract.functions.forEach((func) => {
func.changesState = checkIfChangesState(common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters), func.changesState = checkIfChangesState(common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters),
getContext(cg, contract, func)) getContext(callGraph, contract, func))
}) })
contract.functions.forEach((func) => { contract.functions.forEach((func) => {
if (isPotentialVulnerableFunction(func, getContext(cg, contract, func))) { if (isPotentialVulnerableFunction(func, getContext(callGraph, contract, func))) {
var funcName = common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters) var funcName = common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters)
var comments = (hasModifiers) ? '<br/><i>Note:</i>Modifiers are currently not considered by the this static analysis.' : '' var comments = (hasModifiers) ? '<br/><i>Note:</i>Modifiers are currently not considered by the this static analysis.' : ''
warnings.push({ warnings.push({
...@@ -42,8 +43,8 @@ checksEffectsInteraction.prototype.report = function (compilationResults) { ...@@ -42,8 +43,8 @@ checksEffectsInteraction.prototype.report = function (compilationResults) {
return warnings return warnings
} }
function getContext (cg, currentContract, func) { function getContext (callGraph, currentContract, func) {
return { cg: cg, currentContract: currentContract, stateVariables: getStateVariables(currentContract, func) } return { callGraph: callGraph, currentContract: currentContract, stateVariables: getStateVariables(currentContract, func) }
} }
function getStateVariables (contract, func) { function getStateVariables (contract, func) {
...@@ -65,14 +66,14 @@ function isPotentialVulnerableFunction (func, context) { ...@@ -65,14 +66,14 @@ function isPotentialVulnerableFunction (func, context) {
function isLocalCallWithStateChange (node, context) { function isLocalCallWithStateChange (node, context) {
if (common.isLocalCallGraphRelevantNode(node)) { if (common.isLocalCallGraphRelevantNode(node)) {
var func = callGraph.resolveCallGraphSymbol(context.cg, common.getFullQualifiedFunctionCallIdent(context.currentContract.node, node)) var func = fcallGraph.resolveCallGraphSymbol(context.callGraph, common.getFullQualifiedFunctionCallIdent(context.currentContract.node, node))
return !func || (func && func.node.changesState) return !func || (func && func.node.changesState)
} }
return false return false
} }
function checkIfChangesState (startFuncName, context) { function checkIfChangesState (startFuncName, context) {
return callGraph.analyseCallGraph(context.cg, startFuncName, context, (node, context) => common.isWriteOnStateVariable(node, context.stateVariables)) return fcallGraph.analyseCallGraph(context.callGraph, startFuncName, context, (node, context) => common.isWriteOnStateVariable(node, context.stateVariables))
} }
module.exports = { module.exports = {
......
...@@ -2,15 +2,16 @@ var name = 'Constant functions' ...@@ -2,15 +2,16 @@ var name = 'Constant functions'
var desc = 'Check for potentially constant functions' var desc = 'Check for potentially constant functions'
var categories = require('./categories') var categories = require('./categories')
var common = require('./staticAnalysisCommon') var common = require('./staticAnalysisCommon')
var callGraph = require('./functionCallGraph') var fcallGraph = require('./functionCallGraph')
var AbstractAst = require('./abstractAstView') var AbstractAst = require('./abstractAstView')
function constantFunctions () { function constantFunctions () {
this.contracts = [] this.contracts = []
var that = this
constantFunctions.prototype.visit = new AbstractAst().builder( constantFunctions.prototype.visit = new AbstractAst().builder(
(node) => common.isLowLevelCall(node) || common.isExternalDirectCall(node) || common.isEffect(node) || common.isLocalCallGraphRelevantNode(node) || common.isInlineAssembly(node), (node) => common.isLowLevelCall(node) || common.isExternalDirectCall(node) || common.isEffect(node) || common.isLocalCallGraphRelevantNode(node) || common.isInlineAssembly(node),
this.contracts that.contracts
) )
} }
...@@ -18,17 +19,15 @@ constantFunctions.prototype.report = function (compilationResults) { ...@@ -18,17 +19,15 @@ constantFunctions.prototype.report = function (compilationResults) {
var warnings = [] var warnings = []
var hasModifiers = this.contracts.some((item) => item.modifiers.length > 0) var hasModifiers = this.contracts.some((item) => item.modifiers.length > 0)
var cg = callGraph.buildGlobalFuncCallGraph(this.contracts) var callGraph = fcallGraph.buildGlobalFuncCallGraph(this.contracts)
this.contracts.forEach((contract) => { this.contracts.forEach((contract) => {
if (!common.isFullyImplementedContract(contract.node)) return
contract.functions.forEach((func) => { contract.functions.forEach((func) => {
func.potentiallyshouldBeConst = checkIfShouldBeConstant(common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters), func.potentiallyshouldBeConst = checkIfShouldBeConstant(common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters),
getContext(cg, contract, func)) getContext(callGraph, contract, func))
}) })
contract.functions.forEach((func) => { contract.functions.filter((func) => common.hasFunctionBody(func.node)).forEach((func) => {
if (common.isConstantFunction(func.node) !== func.potentiallyshouldBeConst) { if (common.isConstantFunction(func.node) !== func.potentiallyshouldBeConst) {
var funcName = common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters) var funcName = common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters)
var comments = (hasModifiers) ? '<br/><i>Note:</i>Modifiers are currently not considered by the this static analysis.' : '' var comments = (hasModifiers) ? '<br/><i>Note:</i>Modifiers are currently not considered by the this static analysis.' : ''
...@@ -52,8 +51,8 @@ constantFunctions.prototype.report = function (compilationResults) { ...@@ -52,8 +51,8 @@ constantFunctions.prototype.report = function (compilationResults) {
return warnings return warnings
} }
function getContext (cg, currentContract, func) { function getContext (callGraph, currentContract, func) {
return { cg: cg, currentContract: currentContract, stateVariables: getStateVariables(currentContract, func) } return { callGraph: callGraph, currentContract: currentContract, stateVariables: getStateVariables(currentContract, func) }
} }
function getStateVariables (contract, func) { function getStateVariables (contract, func) {
...@@ -61,7 +60,7 @@ function getStateVariables (contract, func) { ...@@ -61,7 +60,7 @@ function getStateVariables (contract, func) {
} }
function checkIfShouldBeConstant (startFuncName, context) { function checkIfShouldBeConstant (startFuncName, context) {
return !callGraph.analyseCallGraph(context.cg, startFuncName, context, isConstBreaker) return !fcallGraph.analyseCallGraph(context.callGraph, startFuncName, context, isConstBreaker)
} }
function isConstBreaker (node, context) { function isConstBreaker (node, context) {
...@@ -74,7 +73,7 @@ function isConstBreaker (node, context) { ...@@ -74,7 +73,7 @@ function isConstBreaker (node, context) {
function isCallOnNonConstExternalInterfaceFunction (node, context) { function isCallOnNonConstExternalInterfaceFunction (node, context) {
if (common.isExternalDirectCall(node)) { if (common.isExternalDirectCall(node)) {
var func = callGraph.resolveCallGraphSymbol(context.cg, common.getFullQualifiedFunctionCallIdent(context.currentContract, node)) var func = fcallGraph.resolveCallGraphSymbol(context.callGraph, common.getFullQualifiedFunctionCallIdent(context.currentContract, node))
return !func || (func && !common.isConstantFunction(func.node.node)) return !func || (func && !common.isConstantFunction(func.node.node))
} }
return false return false
......
...@@ -16,6 +16,30 @@ function buildLocalFuncCallGraphInternal (functions, nodeFilter, extractNodeIden ...@@ -16,6 +16,30 @@ function buildLocalFuncCallGraphInternal (functions, nodeFilter, extractNodeIden
return callGraph return callGraph
} }
/**
* Builds a function call graph for the current contracts.
* Example Contract call graph:
*
* {
* "KingOfTheEtherThrone": {
* "contracts": {...}, // Contract node as defined in abstractAstView.js
* "functions": {
* "KingOfTheEtherThrone.claimThrone(string memory)": { // function in KingOfEtherThrone
* "node": {...}, // function node as defined in abstractAstView.js
* "calls": { // list of full qualified function names which are called form this function
* }
* }
* }
* },
* "foo": {
* "contract": {...}, // Contract node as definded in abstractAstView.js
* "functions": {} // map from full qualified function name to func node
* }
* }
*
* @contracts {list contracts} Expects as input the contract structure defined in abstractAstView.js
* @return {map (string -> Contract Call Graph)} returns map from contract name to contract call graph
*/
function buildGlobalFuncCallGraph (contracts) { function buildGlobalFuncCallGraph (contracts) {
var callGraph = {} var callGraph = {}
contracts.forEach((contract) => { contracts.forEach((contract) => {
...@@ -29,31 +53,39 @@ function buildGlobalFuncCallGraph (contracts) { ...@@ -29,31 +53,39 @@ function buildGlobalFuncCallGraph (contracts) {
return callGraph return callGraph
} }
function analyseCallGraph (cg, funcName, context, nodeCheck) { /**
return analyseCallGraphInternal(cg, funcName, context, (a, b) => a || b, nodeCheck, {}) * Walks through the call graph from a defined starting function, true if nodeCheck holds for every relevant node in the callgraph
* @callGraph {callGraph} As returned by buildGlobalFuncCallGraph
* @funcName {string} full qualified name of the starting function
* @context {Object} provides additional context information that can be used by the nodeCheck function
* @nodeCheck {(ASTNode, context) -> bool} applied on every relevant node in the call graph
* @return {bool} returns map from contract name to contract call graph
*/
function analyseCallGraph (callGraph, funcName, context, nodeCheck) {
return analyseCallGraphInternal(callGraph, funcName, context, (a, b) => a || b, nodeCheck, {})
} }
function analyseCallGraphInternal (cg, funcName, context, combinator, nodeCheck, visited) { function analyseCallGraphInternal (callGraph, funcName, context, combinator, nodeCheck, visited) {
var current = resolveCallGraphSymbol(cg, funcName) var current = resolveCallGraphSymbol(callGraph, funcName)
if (current === undefined || visited[funcName] === true) return true if (current === undefined || visited[funcName] === true) return true
visited[funcName] = true visited[funcName] = true
return combinator(current.node.relevantNodes.reduce((acc, val) => combinator(acc, nodeCheck(val, context)), false), return combinator(current.node.relevantNodes.reduce((acc, val) => combinator(acc, nodeCheck(val, context)), false),
current.calls.reduce((acc, val) => combinator(acc, analyseCallGraphInternal(cg, val, context, combinator, nodeCheck, visited)), false)) current.calls.reduce((acc, val) => combinator(acc, analyseCallGraphInternal(callGraph, val, context, combinator, nodeCheck, visited)), false))
} }
function resolveCallGraphSymbol (cg, funcName) { function resolveCallGraphSymbol (callGraph, funcName) {
return resolveCallGraphSymbolInternal(cg, funcName, false) return resolveCallGraphSymbolInternal(callGraph, funcName, false)
} }
function resolveCallGraphSymbolInternal (cg, funcName, silent) { function resolveCallGraphSymbolInternal (callGraph, funcName, silent) {
var current var current
if (funcName.includes('.')) { if (funcName.includes('.')) {
var parts = funcName.split('.') var parts = funcName.split('.')
var contractPart = parts[0] var contractPart = parts[0]
var functionPart = parts[1] var functionPart = parts[1]
var currentContract = cg[contractPart] var currentContract = callGraph[contractPart]
if (!(currentContract === undefined)) { if (!(currentContract === undefined)) {
current = currentContract.functions[funcName] current = currentContract.functions[funcName]
// resolve inheritance hierarchy // resolve inheritance hierarchy
...@@ -61,7 +93,7 @@ function resolveCallGraphSymbolInternal (cg, funcName, silent) { ...@@ -61,7 +93,7 @@ function resolveCallGraphSymbolInternal (cg, funcName, silent) {
// resolve inheritance lookup in linearized fashion // resolve inheritance lookup in linearized fashion
var inheritsFromNames = currentContract.contract.inheritsFrom.reverse() var inheritsFromNames = currentContract.contract.inheritsFrom.reverse()
for (var i = 0; i < inheritsFromNames.length; i++) { for (var i = 0; i < inheritsFromNames.length; i++) {
var res = resolveCallGraphSymbolInternal(cg, inheritsFromNames[i] + '.' + functionPart, true) var res = resolveCallGraphSymbolInternal(callGraph, inheritsFromNames[i] + '.' + functionPart, true)
if (!(res === undefined)) return res if (!(res === undefined)) return res
} }
} }
......
...@@ -3,8 +3,8 @@ module.exports = [ ...@@ -3,8 +3,8 @@ module.exports = [
require('./gasCosts'), require('./gasCosts'),
require('./thisLocal'), require('./thisLocal'),
require('./checksEffectsInteraction'), require('./checksEffectsInteraction'),
require('./constantFunctions'), require('./constantFunctions')
require('./inlineAssembly') // require('./inlineAssembly'),
// require('./blockTimestamp'), // require('./blockTimestamp'),
// require('./lowLevelCalls'), // require('./lowLevelCalls'),
// require('./blockBlockhash') // require('./blockBlockhash')
......
...@@ -16,7 +16,8 @@ var nodeTypes = { ...@@ -16,7 +16,8 @@ var nodeTypes = {
MODIFIERINVOCATION: 'ModifierInvocation', MODIFIERINVOCATION: 'ModifierInvocation',
INHERITANCESPECIFIER: 'InheritanceSpecifier', INHERITANCESPECIFIER: 'InheritanceSpecifier',
USERDEFINEDTYPENAME: 'UserDefinedTypeName', USERDEFINEDTYPENAME: 'UserDefinedTypeName',
INLINEASSEMBLY: 'InlineAssembly' INLINEASSEMBLY: 'InlineAssembly',
BLOCK: 'Block'
} }
var basicTypes = { var basicTypes = {
...@@ -50,7 +51,9 @@ var builtinFunctions = { ...@@ -50,7 +51,9 @@ var builtinFunctions = {
'ecrecover(bytes32,uint8,bytes32,bytes32)': true, 'ecrecover(bytes32,uint8,bytes32,bytes32)': true,
'addmod(uint256,uint256,uint256)': true, 'addmod(uint256,uint256,uint256)': true,
'mulmod(uint256,uint256,uint256)': true, 'mulmod(uint256,uint256,uint256)': true,
'selfdestruct(address)': true 'selfdestruct(address)': true,
'revert()': true,
'assert()': true
} }
var lowLevelCallTypes = { var lowLevelCallTypes = {
...@@ -77,91 +80,230 @@ function getType (node) { ...@@ -77,91 +80,230 @@ function getType (node) {
// #################### Complex Getters // #################### Complex Getters
/**
* Returns the type parameter of function call AST nodes. Throws if not a function call node
* @func {ASTNode} Function call node
* @return {string} type of function call
*/
function getFunctionCallType (func) { function getFunctionCallType (func) {
if (!(isExternalDirectCall(func) || isThisLocalCall(func) || isSuperLocalCall(func) || isLocalCall(func) || isLibraryCall(func))) throw new Error('staticAnalysisCommon.js: not function call Node') if (!(isExternalDirectCall(func) || isThisLocalCall(func) || isSuperLocalCall(func) || isLocalCall(func) || isLibraryCall(func))) throw new Error('staticAnalysisCommon.js: not function call Node')
if (isExternalDirectCall(func) || isThisLocalCall(func) || isSuperLocalCall(func) || isLibraryCall(func)) return func.attributes.type if (isExternalDirectCall(func) || isThisLocalCall(func) || isSuperLocalCall(func) || isLibraryCall(func)) return func.attributes.type
return findFirstSubNodeLTR(func, exactMatch(nodeTypes.IDENTIFIER)).attributes.type return findFirstSubNodeLTR(func, exactMatch(nodeTypes.IDENTIFIER)).attributes.type
} }
/**
* Get the variable name written to by a effect node, except for inline assembly because there is no information to find out where we write to. Trows if not a effect node or is inlineassmbly.
* Example: x = 10; => x
* @effectNode {ASTNode} Function call node
* @return {string} variable name written to
*/
function getEffectedVariableName (effectNode) { function getEffectedVariableName (effectNode) {
if (!isEffect(effectNode) || isInlineAssembly(effectNode)) throw new Error('staticAnalysisCommon.js: not an effect Node or inline assembly') if (!isEffect(effectNode) || isInlineAssembly(effectNode)) throw new Error('staticAnalysisCommon.js: not an effect Node or inline assembly')
return findFirstSubNodeLTR(effectNode, exactMatch(nodeTypes.IDENTIFIER)).attributes.value return findFirstSubNodeLTR(effectNode, exactMatch(nodeTypes.IDENTIFIER)).attributes.value
} }
/**
* Returns the identifier of a local call, Throws on wrong node.
* Example: f(103) => f
* @localCallNode {ASTNode} Function call node
* @return {string} name of the function called
*/
function getLocalCallName (localCallNode) { function getLocalCallName (localCallNode) {
if (!isLocalCall(localCallNode)) throw new Error('staticAnalysisCommon.js: not an local call Node') if (!isLocalCall(localCallNode)) throw new Error('staticAnalysisCommon.js: not an local call Node')
return localCallNode.children[0].attributes.value return localCallNode.children[0].attributes.value
} }
/**
* Returns the identifier of a this local call, Throws on wrong node.
* Example: this.f(103) => f
* @localCallNode {ASTNode} Function call node
* @return {string} name of the function called
*/
function getThisLocalCallName (localCallNode) { function getThisLocalCallName (localCallNode) {
if (!isThisLocalCall(localCallNode)) throw new Error('staticAnalysisCommon.js: not an this local call Node') if (!isThisLocalCall(localCallNode)) throw new Error('staticAnalysisCommon.js: not an this local call Node')
return localCallNode.attributes.value return localCallNode.attributes.value
} }
/**
* Returns the identifier of a super local call, Throws on wrong node.
* Example: super.f(103) => f
* @localCallNode {ASTNode} Function call node
* @return {string} name of the function called
*/
function getSuperLocalCallName (localCallNode) { function getSuperLocalCallName (localCallNode) {
if (!isSuperLocalCall(localCallNode)) throw new Error('staticAnalysisCommon.js: not an super local call Node') if (!isSuperLocalCall(localCallNode)) throw new Error('staticAnalysisCommon.js: not an super local call Node')
return localCallNode.attributes.member_name return localCallNode.attributes.member_name
} }
/**
* Returns the contract type of a external direct call, Throws on wrong node.
* Example:
* foo x = foo(0xdeadbeef...);
* x.f(103) => foo
* @extDirectCall {ASTNode} Function call node
* @return {string} name of the contract the function is defined in
*/
function getExternalDirectCallContractName (extDirectCall) { function getExternalDirectCallContractName (extDirectCall) {
if (!isExternalDirectCall(extDirectCall)) throw new Error('staticAnalysisCommon.js: not an external direct call Node') 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), '') return extDirectCall.children[0].attributes.type.replace(new RegExp(basicRegex.CONTRACTTYPE), '')
} }
/**
* Returns the name of the contract of a this local call (current contract), Throws on wrong node.
* Example:
* Contract foo {
* ...
* this.f(103) => foo
* ...
* @thisLocalCall {ASTNode} Function call node
* @return {string} name of the contract the function is defined in
*/
function getThisLocalCallContractName (thisLocalCall) { function getThisLocalCallContractName (thisLocalCall) {
if (!isThisLocalCall(thisLocalCall)) throw new Error('staticAnalysisCommon.js: not an this local call Node') if (!isThisLocalCall(thisLocalCall)) throw new Error('staticAnalysisCommon.js: not an this local call Node')
return thisLocalCall.children[0].attributes.type.replace(new RegExp(basicRegex.CONTRACTTYPE), '') return thisLocalCall.children[0].attributes.type.replace(new RegExp(basicRegex.CONTRACTTYPE), '')
} }
/**
* Returns the function identifier of a external direct call, Throws on wrong node.
* Example:
* foo x = foo(0xdeadbeef...);
* x.f(103) => f
* @extDirectCall {ASTNode} Function call node
* @return {string} name of the function called
*/
function getExternalDirectCallMemberName (extDirectCall) { function getExternalDirectCallMemberName (extDirectCall) {
if (!isExternalDirectCall(extDirectCall)) throw new Error('staticAnalysisCommon.js: not an external direct call Node') if (!isExternalDirectCall(extDirectCall)) throw new Error('staticAnalysisCommon.js: not an external direct call Node')
return extDirectCall.attributes.member_name return extDirectCall.attributes.member_name
} }
/**
* Returns the name of a contract, Throws on wrong node.
* Example:
* Contract foo { => foo
* @contract {ASTNode} Contract Definition node
* @return {string} name of a contract defined
*/
function getContractName (contract) { function getContractName (contract) {
if (!isContractDefinition(contract)) throw new Error('staticAnalysisCommon.js: not an contractDefinition Node') if (!isContractDefinition(contract)) throw new Error('staticAnalysisCommon.js: not an contractDefinition Node')
return contract.attributes.name return contract.attributes.name
} }
/**
* Returns the name of a function definition, Throws on wrong node.
* Example:
* func foo(uint bla) { => foo
* @funcDef {ASTNode} Function Definition node
* @return {string} name of a function defined
*/
function getFunctionDefinitionName (funcDef) { function getFunctionDefinitionName (funcDef) {
if (!isFunctionDefinition(funcDef)) throw new Error('staticAnalysisCommon.js: not an functionDefinition Node') if (!isFunctionDefinition(funcDef)) throw new Error('staticAnalysisCommon.js: not an functionDefinition Node')
return funcDef.attributes.name return funcDef.attributes.name
} }
/**
* Returns the identifier of an inheritance specifier, Throws on wrong node.
* Example:
* contract KingOfTheEtherThrone is b { => b
* @func {ASTNode} Inheritance specifier
* @return {string} name of contract inherited from
*/
function getInheritsFromName (inheritsNode) { function getInheritsFromName (inheritsNode) {
if (!isInheritanceSpecifier(inheritsNode)) throw new Error('staticAnalysisCommon.js: not an InheritanceSpecifier node Node') if (!isInheritanceSpecifier(inheritsNode)) throw new Error('staticAnalysisCommon.js: not an InheritanceSpecifier node Node')
return inheritsNode.children[0].attributes.name return inheritsNode.children[0].attributes.name
} }
/**
* Returns the identifier of a variable definition, Throws on wrong node.
* Example:
* var x = 10; => x
* @varDeclNode {ASTNode} Variable declaration node
* @return {string} variable name
*/
function getDeclaredVariableName (varDeclNode) { function getDeclaredVariableName (varDeclNode) {
if (!isVariableDeclaration(varDeclNode)) throw new Error('staticAnalysisCommon.js: not an variable declaration') if (!isVariableDeclaration(varDeclNode)) throw new Error('staticAnalysisCommon.js: not an variable declaration')
return varDeclNode.attributes.name return varDeclNode.attributes.name
} }
/**
* Returns state variable declaration nodes for a contract, Throws on wrong node.
* Example:
* contract foo {
* ...
* var y = true;
* var x = 10; => [y,x]
* @contractNode {ASTNode} Contract Definition node
* @return {list variable declaration} state variable node list
*/
function getStateVariableDeclarationsFormContractNode (contractNode) { function getStateVariableDeclarationsFormContractNode (contractNode) {
if (!isContractDefinition(contractNode)) throw new Error('staticAnalysisCommon.js: not an contract definition declaration') if (!isContractDefinition(contractNode)) throw new Error('staticAnalysisCommon.js: not an contract definition declaration')
return contractNode.children.filter((el) => isVariableDeclaration(el)) return contractNode.children.filter((el) => isVariableDeclaration(el))
} }
/**
* Returns parameter node for a function or modifier definition, Throws on wrong node.
* Example:
* function bar(uint a, uint b) => uint a, uint b
* @funcNode {ASTNode} Contract Definition node
* @return {parameterlist node} parameterlist node
*/
function getFunctionOrModifierDefinitionParameterPart (funcNode) { function getFunctionOrModifierDefinitionParameterPart (funcNode) {
if (!isFunctionDefinition(funcNode) && !isModifierDefinition(funcNode)) throw new Error('staticAnalysisCommon.js: not an function definition') if (!isFunctionDefinition(funcNode) && !isModifierDefinition(funcNode)) throw new Error('staticAnalysisCommon.js: not an function definition')
return funcNode.children[0] return funcNode.children[0]
} }
/**
* Extracts the parameter types for a function type signature
* Example:
* function(uint a, uint b) returns (bool) => uint a, uint b
* @func {ASTNode} function call node
* @return {string} parameter signature
*/
function getFunctionCallTypeParameterType (func) { function getFunctionCallTypeParameterType (func) {
return new RegExp(basicRegex.FUNCTIONSIGNATURE).exec(getFunctionCallType(func))[1] return new RegExp(basicRegex.FUNCTIONSIGNATURE).exec(getFunctionCallType(func))[1]
} }
/**
* Returns the name of the library called, Throws on wrong node.
* Example:
* library set{...}
* contract foo {
* ...
* function () { set.union() => set}
* @funcCall {ASTNode} function call node
* @return {string} name of the lib defined
*/
function getLibraryCallContractName (funcCall) { function getLibraryCallContractName (funcCall) {
if (!isLibraryCall(funcCall)) throw new Error('staticAnalysisCommon.js: not an this library call Node') if (!isLibraryCall(funcCall)) throw new Error('staticAnalysisCommon.js: not an this library call Node')
return new RegExp(basicRegex.LIBRARYTYPE).exec(funcCall.children[0].attributes.type)[1] return new RegExp(basicRegex.LIBRARYTYPE).exec(funcCall.children[0].attributes.type)[1]
} }
/**
* Returns the name of the function of a library call, Throws on wrong node.
* Example:
* library set{...}
* contract foo {
* ...
* function () { set.union() => uinion}
* @func {ASTNode} function call node
* @return {string} name of function called on the library
*/
function getLibraryCallMemberName (funcCall) { function getLibraryCallMemberName (funcCall) {
if (!isLibraryCall(funcCall)) throw new Error('staticAnalysisCommon.js: not an library call Node') if (!isLibraryCall(funcCall)) throw new Error('staticAnalysisCommon.js: not an library call Node')
return funcCall.attributes.member_name return funcCall.attributes.member_name
} }
/**
* Returns full qualified name for a function call, Throws on wrong node.
* Example:
* contract foo {
* ...
* function bar(uint b) { }
* function baz() {
* bar(10) => foo.bar(uint)
* @func {ASTNode} function call node
* @func {ASTNode} contract defintion
* @return {string} full qualified identifier for the function call
*/
function getFullQualifiedFunctionCallIdent (contract, func) { function getFullQualifiedFunctionCallIdent (contract, func) {
if (isLocalCall(func)) return getContractName(contract) + '.' + getLocalCallName(func) + '(' + getFunctionCallTypeParameterType(func) + ')' if (isLocalCall(func)) return getContractName(contract) + '.' + getLocalCallName(func) + '(' + getFunctionCallTypeParameterType(func) + ')'
else if (isThisLocalCall(func)) return getThisLocalCallContractName(func) + '.' + getThisLocalCallName(func) + '(' + getFunctionCallTypeParameterType(func) + ')' else if (isThisLocalCall(func)) return getThisLocalCallContractName(func) + '.' + getThisLocalCallName(func) + '(' + getFunctionCallTypeParameterType(func) + ')'
...@@ -211,88 +353,204 @@ function isInlineAssembly (node) { ...@@ -211,88 +353,204 @@ function isInlineAssembly (node) {
// #################### Complex Node Identification // #################### Complex Node Identification
/**
* True if function defintion has function body
* @funcNode {ASTNode} function defintion node
* @return {bool}
*/
function hasFunctionBody (funcNode) {
return findFirstSubNodeLTR(funcNode, exactMatch(nodeTypes.BLOCK)) != null
}
/**
* True if call to code within the current contracts context including (delegate) library call
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLocalCallGraphRelevantNode (node) { function isLocalCallGraphRelevantNode (node) {
return ((isLocalCall(node) || isSuperLocalCall(node) || isLibraryCall(node)) && !isBuiltinFunctionCall(node)) return ((isLocalCall(node) || isSuperLocalCall(node) || isLibraryCall(node)) && !isBuiltinFunctionCall(node))
} }
/**
* True if is builtin function like assert, sha3, erecover, ...
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isBuiltinFunctionCall (node) { function isBuiltinFunctionCall (node) {
return isLocalCall(node) && builtinFunctions[getLocalCallName(node) + '(' + getFunctionCallTypeParameterType(node) + ')'] === true return isLocalCall(node) && builtinFunctions[getLocalCallName(node) + '(' + getFunctionCallTypeParameterType(node) + ')'] === true
} }
/**
* True if is storage variable declaration
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isStorageVariableDeclaration (node) { function isStorageVariableDeclaration (node) {
return isVariableDeclaration(node) && expressionType(node, basicRegex.REFTYPE) return isVariableDeclaration(node) && expressionType(node, basicRegex.REFTYPE)
} }
/**
* True if is interaction with external contract (change in context, no delegate calls) (send, call of other contracts)
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isInteraction (node) { function isInteraction (node) {
return isLLCall(node) || isLLSend(node) || isExternalDirectCall(node) return isLLCall(node) || isLLSend(node) || isExternalDirectCall(node)
} }
/**
* True if node changes state of a variable or is inline assembly (does not include check if it is a global state change, on a state variable)
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isEffect (node) { function isEffect (node) {
return isAssignment(node) || isPlusPlusUnaryOperation(node) || isMinusMinusUnaryOperation(node) || isInlineAssembly(node) return isAssignment(node) || isPlusPlusUnaryOperation(node) || isMinusMinusUnaryOperation(node) || isInlineAssembly(node)
} }
/**
* True if node changes state of a variable or is inline assembly (Checks if variable is a state variable via provided list)
* @node {ASTNode} some AstNode
* @node {list Variable declaration} state variable declaration currently in scope
* @return {bool}
*/
function isWriteOnStateVariable (effectNode, stateVariables) { function isWriteOnStateVariable (effectNode, stateVariables) {
return isInlineAssembly(effectNode) || (isEffect(effectNode) && isStateVariable(getEffectedVariableName(effectNode), stateVariables)) return isInlineAssembly(effectNode) || (isEffect(effectNode) && isStateVariable(getEffectedVariableName(effectNode), stateVariables))
} }
/**
* True if there is a variable with name, name in stateVariables
* @node {ASTNode} some AstNode
* @node {list Variable declaration} state variable declaration currently in scope
* @return {bool}
*/
function isStateVariable (name, stateVariables) { function isStateVariable (name, stateVariables) {
return stateVariables.some((item) => name === getDeclaredVariableName(item)) return stateVariables.some((item) => name === getDeclaredVariableName(item))
} }
/**
* True if is function defintion that is flaged as constant
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isConstantFunction (node) { function isConstantFunction (node) {
return isFunctionDefinition(node) && node.attributes.constant === true return isFunctionDefinition(node) && node.attributes.constant === true
} }
/**
* True if unary increment operation
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isPlusPlusUnaryOperation (node) { function isPlusPlusUnaryOperation (node) {
return nodeType(node, exactMatch(nodeTypes.UNARYOPERATION)) && operator(node, exactMatch(utils.escapeRegExp('++'))) return nodeType(node, exactMatch(nodeTypes.UNARYOPERATION)) && operator(node, exactMatch(utils.escapeRegExp('++')))
} }
/**
* True if unary decrement operation
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isMinusMinusUnaryOperation (node) { function isMinusMinusUnaryOperation (node) {
return nodeType(node, exactMatch(nodeTypes.UNARYOPERATION)) && operator(node, exactMatch(utils.escapeRegExp('--'))) return nodeType(node, exactMatch(nodeTypes.UNARYOPERATION)) && operator(node, exactMatch(utils.escapeRegExp('--')))
} }
/**
* True if all functions on a contract are implemented
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isFullyImplementedContract (node) { function isFullyImplementedContract (node) {
return nodeType(node, exactMatch(nodeTypes.CONTRACTDEFINITION)) && node.attributes.fullyImplemented === true return nodeType(node, exactMatch(nodeTypes.CONTRACTDEFINITION)) && node.attributes.fullyImplemented === true
} }
/**
* True if it is a library contract defintion
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLibrary (node) { function isLibrary (node) {
return nodeType(node, exactMatch(nodeTypes.CONTRACTDEFINITION)) && node.attributes.isLibrary === true return nodeType(node, exactMatch(nodeTypes.CONTRACTDEFINITION)) && node.attributes.isLibrary === true
} }
/**
* True if it is a local call to non const function
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isCallToNonConstLocalFunction (node) { function isCallToNonConstLocalFunction (node) {
return isLocalCall(node) && !expressionType(node.children[0], basicRegex.CONSTANTFUNCTIONTYPE) return isLocalCall(node) && !expressionType(node.children[0], basicRegex.CONSTANTFUNCTIONTYPE)
} }
/**
* True if it is a call to a library
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLibraryCall (node) { function isLibraryCall (node) {
return isMemberAccess(node, basicRegex.FUNCTIONTYPE, undefined, basicRegex.LIBRARYTYPE, undefined) return isMemberAccess(node, basicRegex.FUNCTIONTYPE, undefined, basicRegex.LIBRARYTYPE, undefined)
} }
/**
* True if it is an external call via defined interface (not low level call)
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isExternalDirectCall (node) { function isExternalDirectCall (node) {
return isMemberAccess(node, basicRegex.EXTERNALFUNCTIONTYPE, undefined, basicRegex.CONTRACTTYPE, undefined) && !isThisLocalCall(node) && !isSuperLocalCall(node) return isMemberAccess(node, basicRegex.EXTERNALFUNCTIONTYPE, undefined, basicRegex.CONTRACTTYPE, undefined) && !isThisLocalCall(node) && !isSuperLocalCall(node)
} }
/**
* True if access to block.timestamp via now alias
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isNowAccess (node) { function isNowAccess (node) {
return nodeType(node, exactMatch(nodeTypes.IDENTIFIER)) && return nodeType(node, exactMatch(nodeTypes.IDENTIFIER)) &&
expressionType(node, exactMatch(basicTypes.UINT)) && expressionType(node, exactMatch(basicTypes.UINT)) &&
name(node, exactMatch('now')) name(node, exactMatch('now'))
} }
/**
* True if access to block.timestamp
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isBlockTimestampAccess (node) { function isBlockTimestampAccess (node) {
return isSpecialVariableAccess(node, specialVariables.BLOCKTIMESTAMP) return isSpecialVariableAccess(node, specialVariables.BLOCKTIMESTAMP)
} }
/**
* True if access to block.blockhash
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isBlockBlockHashAccess (node) { function isBlockBlockHashAccess (node) {
return isSpecialVariableAccess(node, specialVariables.BLOCKHASH) return isSpecialVariableAccess(node, specialVariables.BLOCKHASH)
} }
/**
* True if call to local function via this keyword
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isThisLocalCall (node) { function isThisLocalCall (node) {
return isMemberAccess(node, basicRegex.FUNCTIONTYPE, exactMatch('this'), basicRegex.CONTRACTTYPE, undefined) return isMemberAccess(node, basicRegex.FUNCTIONTYPE, exactMatch('this'), basicRegex.CONTRACTTYPE, undefined)
} }
/**
* True if access to local function via super keyword
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isSuperLocalCall (node) { function isSuperLocalCall (node) {
return isMemberAccess(node, basicRegex.FUNCTIONTYPE, exactMatch('super'), basicRegex.CONTRACTTYPE, undefined) return isMemberAccess(node, basicRegex.FUNCTIONTYPE, exactMatch('super'), basicRegex.CONTRACTTYPE, undefined)
} }
/**
* True if call to local function
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLocalCall (node) { function isLocalCall (node) {
return nodeType(node, exactMatch(nodeTypes.FUNCTIONCALL)) && return nodeType(node, exactMatch(nodeTypes.FUNCTIONCALL)) &&
minNrOfChildren(node, 1) && minNrOfChildren(node, 1) &&
...@@ -302,6 +560,11 @@ function isLocalCall (node) { ...@@ -302,6 +560,11 @@ function isLocalCall (node) {
nrOfChildren(node.children[0], 0) nrOfChildren(node.children[0], 0)
} }
/**
* True if low level call (send, call, delegatecall, callcode)
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLowLevelCall (node) { function isLowLevelCall (node) {
return isLLCall(node) || return isLLCall(node) ||
isLLCallcode(node) || isLLCallcode(node) ||
...@@ -309,24 +572,44 @@ function isLowLevelCall (node) { ...@@ -309,24 +572,44 @@ function isLowLevelCall (node) {
isLLSend(node) isLLSend(node)
} }
/**
* True if low level send
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLLSend (node) { function isLLSend (node) {
return isMemberAccess(node, return isMemberAccess(node,
exactMatch(utils.escapeRegExp(lowLevelCallTypes.SEND.type)), exactMatch(utils.escapeRegExp(lowLevelCallTypes.SEND.type)),
undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.SEND.ident)) undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.SEND.ident))
} }
/**
* True if low level call
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLLCall (node) { function isLLCall (node) {
return isMemberAccess(node, return isMemberAccess(node,
exactMatch(utils.escapeRegExp(lowLevelCallTypes.CALL.type)), exactMatch(utils.escapeRegExp(lowLevelCallTypes.CALL.type)),
undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.CALL.ident)) undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.CALL.ident))
} }
/**
* True if low level callcode
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLLCallcode (node) { function isLLCallcode (node) {
return isMemberAccess(node, return isMemberAccess(node,
exactMatch(utils.escapeRegExp(lowLevelCallTypes.CALLCODE.type)), exactMatch(utils.escapeRegExp(lowLevelCallTypes.CALLCODE.type)),
undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.CALLCODE.ident)) undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.CALLCODE.ident))
} }
/**
* True if low level delegatecall
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLLDelegatecall (node) { function isLLDelegatecall (node) {
return isMemberAccess(node, return isMemberAccess(node,
exactMatch(utils.escapeRegExp(lowLevelCallTypes.DELEGATECALL.type)), exactMatch(utils.escapeRegExp(lowLevelCallTypes.DELEGATECALL.type)),
...@@ -393,6 +676,12 @@ function buildFunctionSignature (paramTypes, returnTypes, isPayable) { ...@@ -393,6 +676,12 @@ function buildFunctionSignature (paramTypes, returnTypes, isPayable) {
return 'function (' + utils.concatWithSeperator(paramTypes, ',') + ')' + ((isPayable) ? ' payable' : '') + ((returnTypes.length) ? ' returns (' + utils.concatWithSeperator(returnTypes, ',') + ')' : '') return 'function (' + utils.concatWithSeperator(paramTypes, ',') + ')' + ((isPayable) ? ' payable' : '') + ((returnTypes.length) ? ' returns (' + utils.concatWithSeperator(returnTypes, ',') + ')' : '')
} }
/**
* Finds first node of a certain type under a specific node.
* @node {AstNode} node to start form
* @type {String} Type the ast node should have
* @return {AstNode} null or node found
*/
function findFirstSubNodeLTR (node, type) { function findFirstSubNodeLTR (node, type) {
if (!node || !node.children) return null if (!node || !node.children) return null
for (let i = 0; i < node.children.length; ++i) { for (let i = 0; i < node.children.length; ++i) {
...@@ -431,6 +720,7 @@ module.exports = { ...@@ -431,6 +720,7 @@ module.exports = {
getFunctionOrModifierDefinitionParameterPart: getFunctionOrModifierDefinitionParameterPart, getFunctionOrModifierDefinitionParameterPart: getFunctionOrModifierDefinitionParameterPart,
// #################### Complex Node Identification // #################### Complex Node Identification
hasFunctionBody: hasFunctionBody,
isInteraction: isInteraction, isInteraction: isInteraction,
isEffect: isEffect, isEffect: isEffect,
isNowAccess: isNowAccess, isNowAccess: isNowAccess,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment