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 () {
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) {
this.contracts = contractsOut
var that = this
return function (node) {
if (common.isContractDefinition(node)) {
setCurrentContract(this, {
setCurrentContract(that, {
node: node,
functions: [],
modifiers: [],
......@@ -21,14 +46,14 @@ abstractAstView.prototype.builder = function (relevantNodeFilter, contractsOut)
stateVariables: common.getStateVariableDeclarationsFormContractNode(node)
})
} else if (common.isInheritanceSpecifier(node)) {
var currentContract = getCurrentContract(this)
var currentContract = getCurrentContract(that)
var inheritsFromName = common.getInheritsFromName(node)
currentContract.inheritsFrom.push(inheritsFromName)
// 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)
} else if (common.isFunctionDefinition(node)) {
setCurrentFunction(this, {
setCurrentFunction(that, {
node: node,
relevantNodes: [],
modifierInvocations: [],
......@@ -36,17 +61,17 @@ abstractAstView.prototype.builder = function (relevantNodeFilter, contractsOut)
parameters: getLocalParameters(node)
})
} else if (common.isModifierDefinition(node)) {
setCurrentModifier(this, {
setCurrentModifier(that, {
node: node,
relevantNodes: [],
localVariables: getLocalVariables(node),
parameters: getLocalParameters(node)
})
} else if (common.isModifierInvocation(node)) {
if (!this.isFunctionNotModifier) throw new Error('abstractAstView.js: Found modifier invocation outside of function scope.')
getCurrentFunction(this).modifierInvocations.push(node)
if (!that.isFunctionNotModifier) throw new Error('abstractAstView.js: Found modifier invocation outside of function scope.')
getCurrentFunction(that).modifierInvocations.push(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'
var desc = 'Avoid potential reentrancy bugs'
var categories = require('./categories')
var common = require('./staticAnalysisCommon')
var callGraph = require('./functionCallGraph')
var fcallGraph = require('./functionCallGraph')
var AbstractAst = require('./abstractAstView')
function checksEffectsInteraction () {
this.contracts = []
var that = this
checksEffectsInteraction.prototype.visit = new AbstractAst().builder(
(node) => common.isInteraction(node) || common.isEffect(node) || common.isLocalCallGraphRelevantNode(node),
this.contracts
that.contracts
)
}
......@@ -18,16 +19,16 @@ checksEffectsInteraction.prototype.report = function (compilationResults) {
var warnings = []
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) => {
contract.functions.forEach((func) => {
func.changesState = checkIfChangesState(common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters),
getContext(cg, contract, func))
getContext(callGraph, contract, 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 comments = (hasModifiers) ? '<br/><i>Note:</i>Modifiers are currently not considered by the this static analysis.' : ''
warnings.push({
......@@ -42,8 +43,8 @@ checksEffectsInteraction.prototype.report = function (compilationResults) {
return warnings
}
function getContext (cg, currentContract, func) {
return { cg: cg, currentContract: currentContract, stateVariables: getStateVariables(currentContract, func) }
function getContext (callGraph, currentContract, func) {
return { callGraph: callGraph, currentContract: currentContract, stateVariables: getStateVariables(currentContract, func) }
}
function getStateVariables (contract, func) {
......@@ -65,14 +66,14 @@ function isPotentialVulnerableFunction (func, context) {
function isLocalCallWithStateChange (node, context) {
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 false
}
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 = {
......
......@@ -2,15 +2,16 @@ var name = 'Constant functions'
var desc = 'Check for potentially constant functions'
var categories = require('./categories')
var common = require('./staticAnalysisCommon')
var callGraph = require('./functionCallGraph')
var fcallGraph = require('./functionCallGraph')
var AbstractAst = require('./abstractAstView')
function constantFunctions () {
this.contracts = []
var that = this
constantFunctions.prototype.visit = new AbstractAst().builder(
(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) {
var warnings = []
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) => {
if (!common.isFullyImplementedContract(contract.node)) return
contract.functions.forEach((func) => {
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) {
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.' : ''
......@@ -52,8 +51,8 @@ constantFunctions.prototype.report = function (compilationResults) {
return warnings
}
function getContext (cg, currentContract, func) {
return { cg: cg, currentContract: currentContract, stateVariables: getStateVariables(currentContract, func) }
function getContext (callGraph, currentContract, func) {
return { callGraph: callGraph, currentContract: currentContract, stateVariables: getStateVariables(currentContract, func) }
}
function getStateVariables (contract, func) {
......@@ -61,7 +60,7 @@ function getStateVariables (contract, func) {
}
function checkIfShouldBeConstant (startFuncName, context) {
return !callGraph.analyseCallGraph(context.cg, startFuncName, context, isConstBreaker)
return !fcallGraph.analyseCallGraph(context.callGraph, startFuncName, context, isConstBreaker)
}
function isConstBreaker (node, context) {
......@@ -74,7 +73,7 @@ function isConstBreaker (node, context) {
function isCallOnNonConstExternalInterfaceFunction (node, context) {
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 false
......
......@@ -16,6 +16,30 @@ function buildLocalFuncCallGraphInternal (functions, nodeFilter, extractNodeIden
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) {
var callGraph = {}
contracts.forEach((contract) => {
......@@ -29,31 +53,39 @@ function buildGlobalFuncCallGraph (contracts) {
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) {
var current = resolveCallGraphSymbol(cg, funcName)
function analyseCallGraphInternal (callGraph, funcName, context, combinator, nodeCheck, visited) {
var current = resolveCallGraphSymbol(callGraph, funcName)
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),
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) {
return resolveCallGraphSymbolInternal(cg, funcName, false)
function resolveCallGraphSymbol (callGraph, funcName) {
return resolveCallGraphSymbolInternal(callGraph, funcName, false)
}
function resolveCallGraphSymbolInternal (cg, funcName, silent) {
function resolveCallGraphSymbolInternal (callGraph, funcName, silent) {
var current
if (funcName.includes('.')) {
var parts = funcName.split('.')
var contractPart = parts[0]
var functionPart = parts[1]
var currentContract = cg[contractPart]
var currentContract = callGraph[contractPart]
if (!(currentContract === undefined)) {
current = currentContract.functions[funcName]
// resolve inheritance hierarchy
......@@ -61,7 +93,7 @@ function resolveCallGraphSymbolInternal (cg, funcName, silent) {
// 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)
var res = resolveCallGraphSymbolInternal(callGraph, inheritsFromNames[i] + '.' + functionPart, true)
if (!(res === undefined)) return res
}
}
......
......@@ -3,8 +3,8 @@ module.exports = [
require('./gasCosts'),
require('./thisLocal'),
require('./checksEffectsInteraction'),
require('./constantFunctions'),
require('./inlineAssembly')
require('./constantFunctions')
// require('./inlineAssembly'),
// require('./blockTimestamp'),
// require('./lowLevelCalls'),
// require('./blockBlockhash')
......
......@@ -16,7 +16,8 @@ var nodeTypes = {
MODIFIERINVOCATION: 'ModifierInvocation',
INHERITANCESPECIFIER: 'InheritanceSpecifier',
USERDEFINEDTYPENAME: 'UserDefinedTypeName',
INLINEASSEMBLY: 'InlineAssembly'
INLINEASSEMBLY: 'InlineAssembly',
BLOCK: 'Block'
}
var basicTypes = {
......@@ -50,7 +51,9 @@ var builtinFunctions = {
'ecrecover(bytes32,uint8,bytes32,bytes32)': true,
'addmod(uint256,uint256,uint256)': true,
'mulmod(uint256,uint256,uint256)': true,
'selfdestruct(address)': true
'selfdestruct(address)': true,
'revert()': true,
'assert()': true
}
var lowLevelCallTypes = {
......@@ -77,91 +80,230 @@ function getType (node) {
// #################### 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) {
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
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) {
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
}
/**
* 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) {
if (!isLocalCall(localCallNode)) throw new Error('staticAnalysisCommon.js: not an local call Node')
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) {
if (!isThisLocalCall(localCallNode)) throw new Error('staticAnalysisCommon.js: not an this local call Node')
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) {
if (!isSuperLocalCall(localCallNode)) throw new Error('staticAnalysisCommon.js: not an super local call Node')
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) {
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), '')
}
/**
* 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) {
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), '')
}
/**
* 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) {
if (!isExternalDirectCall(extDirectCall)) throw new Error('staticAnalysisCommon.js: not an external direct call Node')
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) {
if (!isContractDefinition(contract)) throw new Error('staticAnalysisCommon.js: not an contractDefinition Node')
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) {
if (!isFunctionDefinition(funcDef)) throw new Error('staticAnalysisCommon.js: not an functionDefinition Node')
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) {
if (!isInheritanceSpecifier(inheritsNode)) throw new Error('staticAnalysisCommon.js: not an InheritanceSpecifier node Node')
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) {
if (!isVariableDeclaration(varDeclNode)) throw new Error('staticAnalysisCommon.js: not an variable declaration')
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) {
if (!isContractDefinition(contractNode)) throw new Error('staticAnalysisCommon.js: not an contract definition declaration')
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) {
if (!isFunctionDefinition(funcNode) && !isModifierDefinition(funcNode)) throw new Error('staticAnalysisCommon.js: not an function definition')
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) {
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) {
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]
}
/**
* 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) {
if (!isLibraryCall(funcCall)) throw new Error('staticAnalysisCommon.js: not an library call Node')
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) {
if (isLocalCall(func)) return getContractName(contract) + '.' + getLocalCallName(func) + '(' + getFunctionCallTypeParameterType(func) + ')'
else if (isThisLocalCall(func)) return getThisLocalCallContractName(func) + '.' + getThisLocalCallName(func) + '(' + getFunctionCallTypeParameterType(func) + ')'
......@@ -211,88 +353,204 @@ function isInlineAssembly (node) {
// #################### 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) {
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) {
return isLocalCall(node) && builtinFunctions[getLocalCallName(node) + '(' + getFunctionCallTypeParameterType(node) + ')'] === true
}
/**
* True if is storage variable declaration
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isStorageVariableDeclaration (node) {
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) {
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) {
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) {
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) {
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) {
return isFunctionDefinition(node) && node.attributes.constant === true
}
/**
* True if unary increment operation
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isPlusPlusUnaryOperation (node) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
return nodeType(node, exactMatch(nodeTypes.IDENTIFIER)) &&
expressionType(node, exactMatch(basicTypes.UINT)) &&
name(node, exactMatch('now'))
}
/**
* True if access to block.timestamp
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isBlockTimestampAccess (node) {
return isSpecialVariableAccess(node, specialVariables.BLOCKTIMESTAMP)
}
/**
* True if access to block.blockhash
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isBlockBlockHashAccess (node) {
return isSpecialVariableAccess(node, specialVariables.BLOCKHASH)
}
/**
* True if call to local function via this keyword
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isThisLocalCall (node) {
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) {
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) {
return nodeType(node, exactMatch(nodeTypes.FUNCTIONCALL)) &&
minNrOfChildren(node, 1) &&
......@@ -302,6 +560,11 @@ function isLocalCall (node) {
nrOfChildren(node.children[0], 0)
}
/**
* True if low level call (send, call, delegatecall, callcode)
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLowLevelCall (node) {
return isLLCall(node) ||
isLLCallcode(node) ||
......@@ -309,24 +572,44 @@ function isLowLevelCall (node) {
isLLSend(node)
}
/**
* True if low level send
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLLSend (node) {
return isMemberAccess(node,
exactMatch(utils.escapeRegExp(lowLevelCallTypes.SEND.type)),
undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.SEND.ident))
}
/**
* True if low level call
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLLCall (node) {
return isMemberAccess(node,
exactMatch(utils.escapeRegExp(lowLevelCallTypes.CALL.type)),
undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.CALL.ident))
}
/**
* True if low level callcode
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLLCallcode (node) {
return isMemberAccess(node,
exactMatch(utils.escapeRegExp(lowLevelCallTypes.CALLCODE.type)),
undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.CALLCODE.ident))
}
/**
* True if low level delegatecall
* @node {ASTNode} some AstNode
* @return {bool}
*/
function isLLDelegatecall (node) {
return isMemberAccess(node,
exactMatch(utils.escapeRegExp(lowLevelCallTypes.DELEGATECALL.type)),
......@@ -393,6 +676,12 @@ function buildFunctionSignature (paramTypes, returnTypes, isPayable) {
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) {
if (!node || !node.children) return null
for (let i = 0; i < node.children.length; ++i) {
......@@ -431,6 +720,7 @@ module.exports = {
getFunctionOrModifierDefinitionParameterPart: getFunctionOrModifierDefinitionParameterPart,
// #################### Complex Node Identification
hasFunctionBody: hasFunctionBody,
isInteraction: isInteraction,
isEffect: isEffect,
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