more type definitions added

pull/7/head
aniket-engg 5 years ago committed by Aniket
parent 2cb6cf1cf5
commit 035082741e
  1. 39
      remix-analyzer/src/solidity-analyzer/index.ts
  2. 39
      remix-analyzer/src/solidity-analyzer/modules/abstractAstView.ts
  3. 3
      remix-analyzer/src/solidity-analyzer/modules/assignAndCompare.ts
  4. 3
      remix-analyzer/src/solidity-analyzer/modules/blockTimestamp.ts
  5. 18
      remix-analyzer/src/solidity-analyzer/modules/checksEffectsInteraction.ts
  6. 21
      remix-analyzer/src/solidity-analyzer/modules/constantFunctions.ts
  7. 20
      remix-analyzer/src/solidity-analyzer/modules/erc20Decimals.ts
  8. 5
      remix-analyzer/src/solidity-analyzer/modules/etherTransferInLoop.ts
  9. 32
      remix-analyzer/src/solidity-analyzer/modules/functionCallGraph.ts
  10. 57
      remix-analyzer/src/solidity-analyzer/modules/gasCosts.ts
  11. 12
      remix-analyzer/src/solidity-analyzer/modules/lowLevelCalls.ts
  12. 19
      remix-analyzer/src/solidity-analyzer/modules/noReturn.ts
  13. 7
      remix-analyzer/src/solidity-analyzer/modules/selfdestruct.ts
  14. 20
      remix-analyzer/src/solidity-analyzer/modules/similarVariableNames.ts
  15. 167
      remix-analyzer/src/solidity-analyzer/modules/staticAnalysisCommon.ts
  16. 1
      remix-analyzer/src/solidity-analyzer/modules/stringBytesLength.ts
  17. 5
      remix-analyzer/src/solidity-analyzer/modules/txOrigin.ts
  18. 29
      remix-analyzer/src/types.ts
  19. 6
      remix-analyzer/test/analysis/staticAnalysisCommon-test.ts

@ -1,27 +1,40 @@
'use strict'
import { AstWalker } from 'remix-astwalker'
import list from './modules/list'
import { CompilationResult, AnalyzerModule, ReportObj } from 'types'
type ModuleObj = {
name: string
mod: AnalyzerModule
}
interface AnalysisReportObj extends ReportObj {
error? : string
}
type AnalysisReport = {
name: string
report: AnalysisReportObj[]
}
export default class staticAnalysisRunner {
run (compilationResult, toRun, callback) {
const modules = toRun.map((i) => {
const m = this.modules()[i]
run (compilationResult: CompilationResult, toRun: any[], callback: ((reports: AnalysisReport[]) => void)): void {
const modules: ModuleObj[] = toRun.map((i) => {
const m: AnalyzerModule = this.modules()[i]
return { 'name': m.name, 'mod': m }
})
this.runWithModuleList(compilationResult, modules, callback)
}
runWithModuleList (compilationResult, modules, callback) {
let reports: any[] = []
runWithModuleList (compilationResult: CompilationResult, modules: ModuleObj[], callback: ((reports: AnalysisReport[]) => void)): void {
let reports: AnalysisReport[] = []
// Also provide convenience analysis via the AST walker.
const walker = new AstWalker()
const walker: AstWalker = new AstWalker()
for (let k in compilationResult.sources) {
// console.log('Ast in walker---', compilationResult.sources[k])
walker.walkFull(compilationResult.sources[k].ast,
(node) => {
modules.map((item, i) => {
(node: any) => {
modules.map((item: ModuleObj) => {
if (item.mod.visit !== undefined) {
try {
item.mod.visit(node)
@ -39,8 +52,8 @@ export default class staticAnalysisRunner {
// Here, modules can just collect the results from the AST walk,
// but also perform new analysis.
reports = reports.concat(modules.map((item, i) => {
let report: any = null
reports = reports.concat(modules.map((item: ModuleObj) => {
let report: AnalysisReportObj[] | null = null
try {
report = item.mod.report(compilationResult)
} catch (e) {
@ -51,7 +64,7 @@ export default class staticAnalysisRunner {
callback(reports)
}
modules () {
modules (): any[] {
return list
}
}

@ -1,9 +1,9 @@
import { getStateVariableDeclarationsFromContractNode,
getInheritsFromName, getContractName,
getFunctionOrModifierDefinitionParameterPart, getType, getDeclaredVariableName,
getFunctionDefinitionReturnParameterPart } from './staticAnalysisCommon'
import { getStateVariableDeclarationsFromContractNode, getInheritsFromName, getContractName,
getFunctionOrModifierDefinitionParameterPart, getType, getDeclaredVariableName, getFunctionDefinitionReturnParameterPart } from './staticAnalysisCommon'
import { AstWalker } from 'remix-astwalker'
import { CommonAstNode, FunctionDefinitionAstNode, ParameterListAstNode, ModifierDefinitionAstNode, ContractHLAst, VariableDeclarationStatementAstNode, VariableDeclarationAstNode, FunctionHLAst } from 'types'
import { FunctionDefinitionAstNode, ParameterListAstNode, ModifierDefinitionAstNode, ContractHLAst, VariableDeclarationAstNode, FunctionHLAst, ContractDefinitionAstNode, ReportObj, ReportFunction, VisitFunction, ModifierHLAst, CompilationResult } from 'types'
type WrapFunction = ((contracts: ContractHLAst[], isSameName: boolean) => ReportObj[])
export default class abstractAstView {
contracts: ContractHLAst[] = []
@ -21,7 +21,6 @@ export default class abstractAstView {
*/
multipleContractsWithSameName: boolean = false
/**
* Builds a higher level AST view. I creates a list with each contract as an object in it.
* Example contractsOut:
@ -47,8 +46,8 @@ export default class abstractAstView {
* @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.
*/
build_visit (relevantNodeFilter: Function): Function {
var that = this
build_visit (relevantNodeFilter: ((node:any) => boolean)): VisitFunction {
const that: abstractAstView = this
return function (node: any) {
if (node.nodeType === "ContractDefinition") {
that.setCurrentContract(that, {
@ -60,8 +59,8 @@ export default class abstractAstView {
stateVariables: getStateVariableDeclarationsFromContractNode(node)
})
} else if (node.nodeType === "InheritanceSpecifier") {
const currentContract = that.getCurrentContract(that)
const inheritsFromName = getInheritsFromName(node)
const currentContract: ContractHLAst = that.getCurrentContract(that)
const inheritsFromName: string = getInheritsFromName(node)
currentContract.inheritsFrom.push(inheritsFromName)
} else if (node.nodeType === "FunctionDefinition") {
that.setCurrentFunction(that, {
@ -89,7 +88,7 @@ export default class abstractAstView {
if (!that.isFunctionNotModifier) throw new Error('abstractAstView.js: Found modifier invocation outside of function scope.')
that.getCurrentFunction(that).modifierInvocations.push(node)
} else if (relevantNodeFilter(node)) {
let scope: any = (that.isFunctionNotModifier) ? that.getCurrentFunction(that) : that.getCurrentModifier(that)
let scope: FunctionHLAst | ModifierHLAst | ContractHLAst = (that.isFunctionNotModifier) ? that.getCurrentFunction(that) : that.getCurrentModifier(that)
if (scope) {
scope.relevantNodes.push(node)
} else {
@ -102,24 +101,24 @@ export default class abstractAstView {
}
}
build_report (wrap: Function): Function {
build_report (wrap: WrapFunction): ReportFunction {
const that: abstractAstView = this
return function (compilationResult) {
return function (compilationResult: CompilationResult) {
that.resolveStateVariablesInHierarchy(that.contracts)
return wrap(that.contracts, that.multipleContractsWithSameName)
}
}
private resolveStateVariablesInHierarchy (contracts: ContractHLAst[]): void {
contracts.map((c) => {
contracts.map((c: ContractHLAst) => {
this.resolveStateVariablesInHierarchyForContract(c, contracts)
})
}
private resolveStateVariablesInHierarchyForContract (currentContract: ContractHLAst, contracts: ContractHLAst[]): void {
currentContract.inheritsFrom.map((inheritsFromName) => {
currentContract.inheritsFrom.map((inheritsFromName: string) => {
// add variables from inherited contracts
const inheritsFrom = contracts.find((contract) => getContractName(contract.node) === inheritsFromName)
const inheritsFrom: ContractHLAst | undefined = contracts.find((contract: ContractHLAst) => getContractName(contract.node) === inheritsFromName)
if (inheritsFrom) {
currentContract.stateVariables = currentContract.stateVariables.concat(inheritsFrom.stateVariables)
} else {
@ -130,7 +129,7 @@ export default class abstractAstView {
private setCurrentContract (that: abstractAstView, contract: ContractHLAst): void {
const name: string = getContractName(contract.node)
if (that.contracts.map((c) => getContractName(c.node)).filter((n) => n === name).length > 0) {
if (that.contracts.map((c: ContractHLAst) => getContractName(c.node)).filter((n) => n === name).length > 0) {
console.log('abstractAstView.js: two or more contracts with the same name dectected, import aliases not supported at the moment')
that.multipleContractsWithSameName = true
}
@ -155,7 +154,7 @@ export default class abstractAstView {
return that.getCurrentContract(that).functions[that.currentFunctionIndex]
}
private getCurrentModifier (that:abstractAstView) {
private getCurrentModifier (that:abstractAstView): ModifierHLAst {
return that.getCurrentContract(that).modifiers[that.currentModifierIndex]
}
@ -164,7 +163,7 @@ export default class abstractAstView {
}
private getReturnParameters (funcNode: FunctionDefinitionAstNode): Record<string, string>[] {
return this.getLocalVariables(getFunctionDefinitionReturnParameterPart(funcNode)).map((n) => {
return this.getLocalVariables(getFunctionDefinitionReturnParameterPart(funcNode)).map((n: VariableDeclarationAstNode) => {
return {
type: getType(n),
name: getDeclaredVariableName(n)
@ -174,7 +173,7 @@ export default class abstractAstView {
private getLocalVariables (funcNode: ParameterListAstNode): VariableDeclarationAstNode[] {
const locals: VariableDeclarationAstNode[] = []
new AstWalker().walkFull(funcNode, (node) => {
new AstWalker().walkFull(funcNode, (node: any) => {
if (node.nodeType === "VariableDeclaration") locals.push(node)
return true
})

@ -1,7 +1,8 @@
import { default as category } from './categories'
import { isSubScopeWithTopLevelUnAssignedBinOp, getUnAssignedTopLevelBinOps } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, BlockAstNode, IfStatementAstNode, WhileStatementAstNode, ForStatementAstNode, CompilationResult, ExpressionStatementAstNode} from './../../types'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, BlockAstNode, IfStatementAstNode,
WhileStatementAstNode, ForStatementAstNode, CompilationResult, ExpressionStatementAstNode} from './../../types'
export default class assignAndCompare implements AnalyzerModule {
warningNodes: ExpressionStatementAstNode[] = []

@ -1,7 +1,8 @@
import { default as category } from './categories'
import { isNowAccess, isBlockTimestampAccess } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, IdentifierAstNode, MemberAccessAstNode} from './../../types'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, IdentifierAstNode,
MemberAccessAstNode} from './../../types'
export default class blockTimestamp implements AnalyzerModule {
warningNowNodes: IdentifierAstNode[] = []

@ -4,7 +4,9 @@ import { isInteraction, isEffect, isLocalCallGraphRelevantNode, getFullQuallyfie
import { default as algorithm } from './algorithmCategories'
import { buildGlobalFuncCallGraph, resolveCallGraphSymbol, analyseCallGraph } from './functionCallGraph'
import AbstractAst from './abstractAstView'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst, VariableDeclarationAstNode, FunctionHLAst, ContractCallGraph, Context, FunctionCallAstNode, AssignmentAstNode, UnaryOperationAstNode, InlineAssemblyAstNode} from './../../types'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst, VariableDeclarationAstNode,
FunctionHLAst, ContractCallGraph, Context, FunctionCallAstNode, AssignmentAstNode, UnaryOperationAstNode,
InlineAssemblyAstNode, ReportFunction, VisitFunction, FunctionCallGraph } from './../../types'
export default class checksEffectsInteraction implements AnalyzerModule {
name: string = 'Check effects: '
@ -14,11 +16,11 @@ export default class checksEffectsInteraction implements AnalyzerModule {
abstractAst: AbstractAst = new AbstractAst()
visit: Function = this.abstractAst.build_visit((node: FunctionCallAstNode | AssignmentAstNode | UnaryOperationAstNode | InlineAssemblyAstNode) => (
visit: VisitFunction = this.abstractAst.build_visit((node: FunctionCallAstNode | AssignmentAstNode | UnaryOperationAstNode | InlineAssemblyAstNode) => (
node.nodeType === 'FunctionCall' && (isInteraction(node) || isLocalCallGraphRelevantNode(node))) ||
((node.nodeType === 'Assignment' || node.nodeType === 'UnaryOperation' || node.nodeType === 'InlineAssembly') && isEffect(node)))
report: Function = this.abstractAst.build_report(this._report.bind(this))
report: ReportFunction = this.abstractAst.build_report(this._report.bind(this))
private _report (contracts: ContractHLAst[], multipleContractsWithSameName: boolean): ReportObj[] {
const warnings: ReportObj[] = []
@ -38,10 +40,10 @@ export default class checksEffectsInteraction implements AnalyzerModule {
func)
)
})
contract.functions.forEach((func) => {
contract.functions.forEach((func: FunctionHLAst) => {
if (this.isPotentialVulnerableFunction(func, this.getContext(callGraph, contract, func))) {
const funcName = getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters)
let comments = (hasModifiers) ? 'Note: Modifiers are currently not considered by this static analysis.' : ''
const funcName: string = getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters)
let comments: string = (hasModifiers) ? 'Note: Modifiers are currently not considered by this static analysis.' : ''
comments += (multipleContractsWithSameName) ? 'Note: Import aliases are currently not supported by this static analysis.' : ''
warnings.push({
warning: `Potential Violation of Checks-Effects-Interaction pattern in ${funcName}: Could potentially lead to re-entrancy vulnerability. ${comments}`,
@ -77,14 +79,14 @@ export default class checksEffectsInteraction implements AnalyzerModule {
private isLocalCallWithStateChange (node: FunctionCallAstNode, context: Context): boolean {
if (isLocalCallGraphRelevantNode(node)) {
const func = resolveCallGraphSymbol(context.callGraph, getFullQualifiedFunctionCallIdent(context.currentContract.node, node))
const func: FunctionCallGraph | undefined = resolveCallGraphSymbol(context.callGraph, getFullQualifiedFunctionCallIdent(context.currentContract.node, node))
return !func || (func && func.node['changesState'])
}
return false
}
private checkIfChangesState (startFuncName: string, context: Context): boolean {
return analyseCallGraph(context.callGraph, startFuncName, context, (node, context) => isWriteOnStateVariable(node, context.stateVariables))
return analyseCallGraph(context.callGraph, startFuncName, context, (node: any, context: Context) => isWriteOnStateVariable(node, context.stateVariables))
}
}

@ -1,12 +1,13 @@
import { default as category } from './categories'
import { isLowLevelCall, isTransfer, isExternalDirectCall, isEffect, isLocalCallGraphRelevantNode,
isSelfdestructCall, isDeleteUnaryOperation, isPayableFunction,
isConstructor, getFullQuallyfiedFuncDefinitionIdent, hasFunctionBody, isConstantFunction, isWriteOnStateVariable,
isStorageVariableDeclaration, isCallToNonConstLocalFunction, getFullQualifiedFunctionCallIdent} from './staticAnalysisCommon'
import { isLowLevelCall, isTransfer, isExternalDirectCall, isEffect, isLocalCallGraphRelevantNode, isSelfdestructCall,
isDeleteUnaryOperation, isPayableFunction, isConstructor, getFullQuallyfiedFuncDefinitionIdent, hasFunctionBody,
isConstantFunction, isWriteOnStateVariable, isStorageVariableDeclaration, isCallToNonConstLocalFunction,
getFullQualifiedFunctionCallIdent} from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import { buildGlobalFuncCallGraph, resolveCallGraphSymbol, analyseCallGraph } from './functionCallGraph'
import AbstractAst from './abstractAstView'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractCallGraph, Context, ContractHLAst, FunctionHLAst, VariableDeclarationAstNode, FunctionCallGraph, FunctionCallAstNode} from './../../types'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractCallGraph, Context, ContractHLAst,
FunctionHLAst, VariableDeclarationAstNode, FunctionCallGraph, FunctionCallAstNode, VisitFunction, ReportFunction} from './../../types'
export default class constantFunctions implements AnalyzerModule {
name: string = 'Constant functions: '
@ -16,7 +17,7 @@ export default class constantFunctions implements AnalyzerModule {
abstractAst: AbstractAst = new AbstractAst()
visit: Function = this.abstractAst.build_visit(
visit: VisitFunction = this.abstractAst.build_visit(
(node: any) => isLowLevelCall(node) ||
isTransfer(node) ||
isExternalDirectCall(node) ||
@ -28,7 +29,7 @@ export default class constantFunctions implements AnalyzerModule {
isDeleteUnaryOperation(node)
)
report: Function = this.abstractAst.build_report(this._report.bind(this))
report: ReportFunction = this.abstractAst.build_report(this._report.bind(this))
private _report (contracts: ContractHLAst[], multipleContractsWithSameName: boolean): ReportObj[] {
const warnings: ReportObj[] = []
@ -36,8 +37,8 @@ export default class constantFunctions implements AnalyzerModule {
const callGraph: Record<string, ContractCallGraph> = buildGlobalFuncCallGraph(contracts)
contracts.forEach((contract) => {
contract.functions.forEach((func) => {
contracts.forEach((contract: ContractHLAst) => {
contract.functions.forEach((func: FunctionHLAst) => {
if (isPayableFunction(func.node) || isConstructor(func.node)) {
func['potentiallyshouldBeConst'] = false
} else {
@ -55,7 +56,7 @@ export default class constantFunctions implements AnalyzerModule {
)
}
})
contract.functions.filter((func) => hasFunctionBody(func.node)).forEach((func) => {
contract.functions.filter((func: FunctionHLAst) => hasFunctionBody(func.node)).forEach((func: FunctionHLAst) => {
if (isConstantFunction(func.node) !== func['potentiallyshouldBeConst']) {
const funcName: string = getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters)
let comments: string = (hasModifiers) ? 'Note: Modifiers are currently not considered by this static analysis.' : ''

@ -2,7 +2,8 @@ import { default as category } from './categories'
import { getFunctionDefinitionName, helpers, getDeclaredVariableName, getDeclaredVariableType } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import AbstractAst from './abstractAstView'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, AstNodeLegacy, CompilationResult} from './../../types'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, VisitFunction, ReportFunction, ContractHLAst,
FunctionHLAst, VariableDeclarationAstNode} from './../../types'
export default class erc20Decimals implements AnalyzerModule {
name: string = 'ERC20: '
@ -11,21 +12,21 @@ export default class erc20Decimals implements AnalyzerModule {
algorithm: ModuleAlgorithm = algorithm.EXACT
abstractAst: AbstractAst = new AbstractAst()
visit = this.abstractAst.build_visit((node: AstNodeLegacy) => false)
report = this.abstractAst.build_report(this._report.bind(this))
visit: VisitFunction = this.abstractAst.build_visit((node: any) => false)
report: ReportFunction = this.abstractAst.build_report(this._report.bind(this))
private _report (contracts, multipleContractsWithSameName): ReportObj[] {
private _report (contracts: ContractHLAst[], multipleContractsWithSameName: boolean): ReportObj[] {
const warnings: ReportObj[] = []
contracts.forEach((contract) => {
const contractAbiSignatures = contract.functions.map((f) => helpers.buildAbiSignature(getFunctionDefinitionName(f.node), f.parameters))
contracts.forEach((contract: ContractHLAst) => {
const contractAbiSignatures: string[] = contract.functions.map((f: FunctionHLAst) => helpers.buildAbiSignature(getFunctionDefinitionName(f.node), f.parameters))
if (this.isERC20(contractAbiSignatures)) {
const decimalsVar = contract.stateVariables.filter((stateVar) => getDeclaredVariableName(stateVar) === 'decimals' && (getDeclaredVariableType(stateVar) !== 'uint8' || stateVar.attributes.visibility !== 'public'))
const decimalsFun = contract.functions.filter((f) => getFunctionDefinitionName(f.node) === 'decimals' &&
const decimalsVar: VariableDeclarationAstNode[] = contract.stateVariables.filter((stateVar: VariableDeclarationAstNode) => getDeclaredVariableName(stateVar) === 'decimals' && (getDeclaredVariableType(stateVar) !== 'uint8' || stateVar.visibility !== 'public'))
const decimalsFun: FunctionHLAst[] = contract.functions.filter((f: FunctionHLAst) => getFunctionDefinitionName(f.node) === 'decimals' &&
(
(f.returns.length === 0 || f.returns.length > 1) ||
(f.returns.length === 1 && (f.returns[0].type !== 'uint8' || f.node.attributes.visibility !== 'public'))
(f.returns.length === 1 && (f.returns[0].type !== 'uint8' || f.node.visibility !== 'public'))
)
)
@ -38,7 +39,6 @@ export default class erc20Decimals implements AnalyzerModule {
}
}
})
return warnings
}

@ -1,10 +1,11 @@
import { default as category } from './categories'
import { default as algorithm } from './algorithmCategories'
import { isLoop, isTransfer } from './staticAnalysisCommon'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, ForStatementAstNode, WhileStatementAstNode, CommonAstNode, ExpressionStatementAstNode} from './../../types'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, ForStatementAstNode,
WhileStatementAstNode, ExpressionStatementAstNode} from './../../types'
export default class etherTransferInLoop implements AnalyzerModule {
relevantNodes: CommonAstNode[] = []
relevantNodes: ExpressionStatementAstNode[] = []
name: string = 'Ether transfer in a loop: '
description: string = 'Avoid transferring Ether to multiple addresses in a loop'
category: ModuleCategory = category.GAS

@ -1,15 +1,20 @@
'use strict'
import { FunctionHLAst, ContractHLAst, FunctionCallGraph, ContractCallGraph, Context, FunctionCallAstNode } from "types"
import { isLocalCallGraphRelevantNode, isExternalDirectCall, getFullQualifiedFunctionCallIdent, getFullQuallyfiedFuncDefinitionIdent, getContractName } from './staticAnalysisCommon'
import { isLocalCallGraphRelevantNode, isExternalDirectCall, getFullQualifiedFunctionCallIdent,
getFullQuallyfiedFuncDefinitionIdent, getContractName } from './staticAnalysisCommon'
function buildLocalFuncCallGraphInternal (functions: FunctionHLAst[], nodeFilter: any , extractNodeIdent: any, extractFuncDefIdent: Function): Record<string, FunctionCallGraph> {
type filterNodesFunction = (node: FunctionCallAstNode) => boolean
type NodeIdentFunction = (node: FunctionCallAstNode) => string
type FunDefIdentFunction = (node: FunctionHLAst) => string
function buildLocalFuncCallGraphInternal (functions: FunctionHLAst[], nodeFilter: filterNodesFunction , extractNodeIdent: NodeIdentFunction, extractFuncDefIdent: FunDefIdentFunction): Record<string, FunctionCallGraph> {
const callGraph: Record<string, FunctionCallGraph> = {}
functions.forEach((func) => {
const calls = func.relevantNodes
functions.forEach((func: FunctionHLAst) => {
const calls: string[] = func.relevantNodes
.filter(nodeFilter)
.map(extractNodeIdent)
.filter((name) => name !== extractFuncDefIdent(func)) // filter self recursive call
.filter((name: string) => name !== extractFuncDefIdent(func)) // filter self recursive call
callGraph[extractFuncDefIdent(func)] = { node: func, calls: calls }
})
@ -43,13 +48,12 @@ function buildLocalFuncCallGraphInternal (functions: FunctionHLAst[], nodeFilter
export function buildGlobalFuncCallGraph (contracts: ContractHLAst[]): Record<string, ContractCallGraph> {
const callGraph: Record<string, ContractCallGraph> = {}
contracts.forEach((contract: ContractHLAst) => {
const filterNodes: Function = (node: FunctionCallAstNode) => { return isLocalCallGraphRelevantNode(node) || isExternalDirectCall(node) }
const getNodeIdent: Function = (node: FunctionCallAstNode) => { return getFullQualifiedFunctionCallIdent(contract.node, node) }
const getFunDefIdent: Function = (funcDef) => { return getFullQuallyfiedFuncDefinitionIdent(contract.node, funcDef.node, funcDef.parameters) }
const filterNodes: filterNodesFunction = (node: FunctionCallAstNode) => { return isLocalCallGraphRelevantNode(node) || isExternalDirectCall(node) }
const getNodeIdent: NodeIdentFunction = (node: FunctionCallAstNode) => { return getFullQualifiedFunctionCallIdent(contract.node, node) }
const getFunDefIdent: FunDefIdentFunction = (funcDef: FunctionHLAst) => { return getFullQuallyfiedFuncDefinitionIdent(contract.node, funcDef.node, funcDef.parameters) }
callGraph[getContractName(contract.node)] = { contract: contract, functions: buildLocalFuncCallGraphInternal(contract.functions, filterNodes, getNodeIdent, getFunDefIdent) }
})
return callGraph
}
@ -65,7 +69,7 @@ export function analyseCallGraph (callGraph: Record<string, ContractCallGraph>,
return analyseCallGraphInternal(callGraph, funcName, context, (a, b) => a || b, nodeCheck, {})
}
function analyseCallGraphInternal (callGraph: Record<string, ContractCallGraph>, funcName: string, context: Context, combinator: Function, nodeCheck, visited : object): boolean {
function analyseCallGraphInternal (callGraph: Record<string, ContractCallGraph>, funcName: string, context: Context, combinator: Function, nodeCheck: ((node: any, context: Context) => boolean), visited : Record<string, boolean>): boolean {
const current: FunctionCallGraph | undefined = resolveCallGraphSymbol(callGraph, funcName)
if (current === undefined || visited[funcName] === true) return true
@ -82,9 +86,9 @@ export function resolveCallGraphSymbol (callGraph: Record<string, ContractCallGr
function resolveCallGraphSymbolInternal (callGraph: Record<string, ContractCallGraph>, funcName: string, silent: boolean): FunctionCallGraph | undefined {
let current: FunctionCallGraph | null = null
if (funcName.includes('.')) {
const parts = funcName.split('.')
const contractPart = parts[0]
const functionPart = parts[1]
const parts: string[] = funcName.split('.')
const contractPart: string = parts[0]
const functionPart: string = parts[1]
const currentContract: ContractCallGraph = callGraph[contractPart]
if (!(currentContract === undefined)) {
current = currentContract.functions[funcName]
@ -93,7 +97,7 @@ function resolveCallGraphSymbolInternal (callGraph: Record<string, ContractCallG
// resolve inheritance lookup in linearized fashion
const inheritsFromNames: string[] = currentContract.contract.inheritsFrom.reverse()
for (let i = 0; i < inheritsFromNames.length; i++) {
const res = resolveCallGraphSymbolInternal(callGraph, inheritsFromNames[i] + '.' + functionPart, true)
const res: FunctionCallGraph | undefined = resolveCallGraphSymbolInternal(callGraph, inheritsFromNames[i] + '.' + functionPart, true)
if (!(res === undefined)) return res
}
}

@ -1,40 +1,35 @@
import { default as category } from './categories'
import { default as algorithm } from './algorithmCategories'
import { ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult} from './../../types'
import AbstractAst from './abstractAstView'
import { ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, CompiledContractObj, CompiledContract, VisitFunction, AnalyzerModule} from './../../types'
export default class gasCosts {
type VisitedContract = {
name: string
object: CompiledContract
file: string
}
export default class gasCosts implements AnalyzerModule {
name: string = 'Gas costs: '
description: string = 'Warn if the gas requirements of functions are too high.'
category: ModuleCategory = category.GAS
algorithm: ModuleAlgorithm = algorithm.EXACT
/**
* call the given @arg cb (function) for all the contracts. Uses last compilation result
* stop visiting when cb return true
* @param {Function} cb - callback
*/
// @TODO has been copied from remix-ide repo ! should fix that soon !
visitContracts (contracts, cb: Function) {
for (let file in contracts) {
for (let name in contracts[file]) {
if (cb({ name: name, object: contracts[file][name], file: file })) return
}
}
}
abstractAst: AbstractAst = new AbstractAst()
visit: VisitFunction = this.abstractAst.build_visit((node: any) => false)
report (compilationResults: CompilationResult): ReportObj[] {
const report: any[] = []
this.visitContracts(compilationResults.contracts, (contract) => {
const report: ReportObj[] = []
this.visitContracts(compilationResults.contracts, (contract: VisitedContract) => {
if (
!contract.object.evm.gasEstimates ||
!contract.object.evm.gasEstimates.external
) {
return
}
const fallback = contract.object.evm.gasEstimates.external['']
const fallback: string = contract.object.evm.gasEstimates.external['']
if (fallback !== undefined) {
if (fallback === null || fallback >= 2100 || fallback === 'infinite') {
if (fallback === null || parseInt(fallback) >= 2100 || fallback === 'infinite') {
report.push({
warning: `Fallback function of contract ${contract.name} requires too much gas (${fallback}).
If the fallback function requires more than 2300 gas, the contract cannot receive Ether.`
@ -46,9 +41,9 @@ export default class gasCosts {
if (functionName === '') {
continue
}
const gas = contract.object.evm.gasEstimates.external[functionName]
const gasString = gas === null ? 'unknown or not constant' : 'high: ' + gas
if (gas === null || gas >= 3000000 || gas === 'infinite') {
const gas: string = contract.object.evm.gasEstimates.external[functionName]
const gasString: string = gas === null ? 'unknown or not constant' : 'high: ' + gas
if (gas === null || parseInt(gas) >= 3000000 || gas === 'infinite') {
report.push({
warning: `Gas requirement of function ${contract.name}.${functionName} ${gasString}.
If the gas requirement of a function is higher than the block gas limit, it cannot be executed.
@ -60,4 +55,18 @@ export default class gasCosts {
})
return report
}
/**
* call the given @arg cb (function) for all the contracts. Uses last compilation result
* stop visiting when cb return true
* @param {Function} cb - callback
*/
// @TODO has been copied from remix-ide repo ! should fix that soon !
private visitContracts (contracts: CompiledContractObj | undefined, cb: ((contract: VisitedContract) => void | undefined)): void {
for (let file in contracts) {
for (let name in contracts[file]) {
if (cb({ name: name, object: contracts[file][name], file: file })) return
}
}
}
}

@ -1,15 +1,11 @@
import { default as category } from './categories'
import { isLLCall, isLLDelegatecall, isLLCallcode, isLLCall04, isLLDelegatecall04, isLLSend04,
isLLSend, lowLevelCallTypes } from './staticAnalysisCommon'
import { isLLCall, isLLDelegatecall, isLLCallcode, isLLCall04, isLLDelegatecall04, isLLSend04, isLLSend, lowLevelCallTypes } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, MemberAccessAstNode} from './../../types'
interface llcNode {
node: MemberAccessAstNode
type: {
ident: string,
type: string
}
type: Record<string, string>
}
export default class lowLevelCalls implements AnalyzerModule {
@ -39,8 +35,8 @@ export default class lowLevelCalls implements AnalyzerModule {
report (compilationResults: CompilationResult): ReportObj[] {
return this.llcNodes.map((item, i) => {
let text = ''
let morehref: any = null
let text: string = ''
let morehref: string = ''
switch (item.type) {
case lowLevelCallTypes.CALL:
text = `use of "call": the use of low level "call" should be avoided whenever possible.

@ -2,7 +2,8 @@ import { default as category } from './categories'
import { hasFunctionBody, getFullQuallyfiedFuncDefinitionIdent, getEffectedVariableName } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import AbstractAst from './abstractAstView'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, CommonAstNode, FunctionDefinitionAstNode, ContractHLAst, FunctionHLAst} from './../../types'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst, FunctionHLAst,
VisitFunction, ReportFunction, ReturnAstNode, AssignmentAstNode} from './../../types'
export default class noReturn implements AnalyzerModule {
name: string = 'no return: '
@ -12,16 +13,16 @@ export default class noReturn implements AnalyzerModule {
abstractAst: AbstractAst = new AbstractAst()
visit: Function = this.abstractAst.build_visit(
(node: CommonAstNode) => node.nodeType === "Return" || node.nodeType === "Assignment"
visit: VisitFunction = this.abstractAst.build_visit(
(node: ReturnAstNode | AssignmentAstNode) => node.nodeType === "Return" || node.nodeType === "Assignment"
)
report: Function = this.abstractAst.build_report(this._report.bind(this))
report: ReportFunction = this.abstractAst.build_report(this._report.bind(this))
private _report (contracts: ContractHLAst[], multipleContractsWithSameName: boolean): ReportObj[] {
const warnings: ReportObj[] = []
contracts.forEach((contract) => {
contract.functions.filter((func) => hasFunctionBody(func.node)).forEach((func) => {
contracts.forEach((contract: ContractHLAst) => {
contract.functions.filter((func: FunctionHLAst) => hasFunctionBody(func.node)).forEach((func: FunctionHLAst) => {
const funcName: string = getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters)
if (this.hasNamedAndUnnamedReturns(func)) {
warnings.push({
@ -36,7 +37,6 @@ export default class noReturn implements AnalyzerModule {
}
})
})
return warnings
}
@ -49,7 +49,7 @@ export default class noReturn implements AnalyzerModule {
}
private hasAssignToAllNamedReturns (func: FunctionHLAst): boolean {
const namedReturns: string[] = func.returns.filter((n) => n.name.length > 0).map((n) => n.name)
const namedReturns: string[] = func.returns.filter(n => n.name.length > 0).map((n) => n.name)
const assignedVars: string[] = func.relevantNodes.filter(n => n.nodeType === "Assignment").map(getEffectedVariableName)
const diff: string[] = namedReturns.filter(e => !assignedVars.includes(e))
return diff.length === 0
@ -60,7 +60,6 @@ export default class noReturn implements AnalyzerModule {
}
private hasNamedAndUnnamedReturns (func: FunctionHLAst): boolean {
return func.returns.filter((n) => n.name.length === 0).length > 0 &&
this.hasNamedReturns(func)
return func.returns.filter((n) => n.name.length === 0).length > 0 && this.hasNamedReturns(func)
}
}

@ -2,7 +2,7 @@ import { default as category } from './categories'
import { isStatement, isSelfdestructCall } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories'
import AbstractAst from './abstractAstView'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst} from './../../types'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst, VisitFunction, ReportFunction} from './../../types'
export default class selfdestruct implements AnalyzerModule {
name: string = 'Selfdestruct: '
@ -12,11 +12,11 @@ export default class selfdestruct implements AnalyzerModule {
abstractAst: AbstractAst = new AbstractAst()
visit: Function = this.abstractAst.build_visit(
visit: VisitFunction = this.abstractAst.build_visit(
(node: any) => isStatement(node) || (node.nodeType=== 'FunctionCall' && isSelfdestructCall(node))
)
report: Function = this.abstractAst.build_report(this._report.bind(this))
report: ReportFunction = this.abstractAst.build_report(this._report.bind(this))
private _report (contracts: ContractHLAst[], multipleContractsWithSameName: boolean): ReportObj[] {
const warnings: ReportObj[] = []
@ -43,7 +43,6 @@ export default class selfdestruct implements AnalyzerModule {
})
})
})
return warnings
}
}

@ -4,7 +4,13 @@ import { default as algorithm } from './algorithmCategories'
import AbstractAst from './abstractAstView'
import { get } from 'fast-levenshtein'
import { util } from 'remix-lib'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst, ContractCallGraph, FunctionHLAst, VariableDeclarationAstNode} from './../../types'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, ContractHLAst, FunctionHLAst, VariableDeclarationAstNode, VisitFunction, ReportFunction} from './../../types'
type SimilarRecord = {
var1: string
var2: string
distance: number
}
export default class similarVariableNames implements AnalyzerModule {
name: string = 'Similar variable names: '
@ -14,9 +20,9 @@ export default class similarVariableNames implements AnalyzerModule {
abstractAst:AbstractAst = new AbstractAst()
visit: Function = this.abstractAst.build_visit((node: any) => false)
visit: VisitFunction = this.abstractAst.build_visit((node: any) => false)
report: Function = this.abstractAst.build_report(this._report.bind(this))
report: ReportFunction = this.abstractAst.build_report(this._report.bind(this))
private _report (contracts: ContractHLAst[], multipleContractsWithSameName: boolean): ReportObj[] {
const warnings: ReportObj[] = []
@ -35,7 +41,6 @@ export default class similarVariableNames implements AnalyzerModule {
}
const vars: string[] = this.getFunctionVariables(contract, func).map(getDeclaredVariableName)
this.findSimilarVarNames(vars).map((sim) => {
warnings.push({
warning: `${funcName} : Variables have very similar names ${sim.var1} and ${sim.var2}. ${hasModifiersComments} ${multipleContractsWithSameNameComments}`,
@ -44,14 +49,13 @@ export default class similarVariableNames implements AnalyzerModule {
})
})
})
return warnings
}
private findSimilarVarNames (vars: string[]): Record<string, any>[] {
const similar: Record<string, any>[] = []
private findSimilarVarNames (vars: string[]): SimilarRecord[] {
const similar: SimilarRecord[] = []
const comb: Record<string, boolean> = {}
vars.map((varName1) => vars.map((varName2) => {
vars.map((varName1: string) => vars.map((varName2: string) => {
if (varName1.length > 1 && varName2.length > 1 &&
varName2 !== varName1 && !this.isCommonPrefixedVersion(varName1, varName2) &&
!this.isCommonNrSuffixVersion(varName1, varName2) &&

@ -1,9 +1,18 @@
'use strict'
import { FunctionDefinitionAstNode, ModifierDefinitionAstNode, ParameterListAstNode, ForStatementAstNode, WhileStatementAstNode, VariableDeclarationAstNode, ContractDefinitionAstNode, InheritanceSpecifierAstNode, MemberAccessAstNode, BinaryOperationAstNode, FunctionCallAstNode, ExpressionStatementAstNode, UnaryOperationAstNode, IdentifierAstNode, MappingAstNode, IndexAccessAstNode, UserDefinedTypeNameAstNode, BlockAstNode, AssignmentAstNode, InlineAssemblyAstNode, IfStatementAstNode } from "types"
import { FunctionDefinitionAstNode, ModifierDefinitionAstNode, ParameterListAstNode, ForStatementAstNode,
WhileStatementAstNode, VariableDeclarationAstNode, ContractDefinitionAstNode, InheritanceSpecifierAstNode,
MemberAccessAstNode, BinaryOperationAstNode, FunctionCallAstNode, ExpressionStatementAstNode, UnaryOperationAstNode,
IdentifierAstNode, IndexAccessAstNode, BlockAstNode, AssignmentAstNode, InlineAssemblyAstNode, IfStatementAstNode } from "types"
import { util } from 'remix-lib'
const nodeTypes = {
type SpecialObjDetail = {
obj: string
member: string
type: string
}
const nodeTypes: Record<string, string> = {
SOURCEUNIT: 'SourceUnit',
PRAGMADIRECTIVE: 'PragmaDirective',
IMPORTDIRECTIVE: 'ImportDirective',
@ -58,7 +67,7 @@ const nodeTypes = {
STRUCTUREDDOCUMENTATION: 'StructuredDocumentation'
}
const basicTypes = {
const basicTypes: Record<string, string> = {
UINT: 'uint256',
BOOL: 'bool',
ADDRESS: 'address',
@ -69,7 +78,7 @@ const basicTypes = {
BYTES4: 'bytes4'
}
const basicRegex = {
const basicRegex: Record<string, string> = {
CONTRACTTYPE: '^contract ',
FUNCTIONTYPE: '^function \\(',
EXTERNALFUNCTIONTYPE: '^function \\(.*\\).* external',
@ -79,7 +88,7 @@ const basicRegex = {
LIBRARYTYPE: '^type\\(library (.*)\\)'
}
const basicFunctionTypes = {
const basicFunctionTypes: Record<string, string> = {
SEND: buildFunctionSignature([basicTypes.UINT], [basicTypes.BOOL], false),
'CALL-0.4': buildFunctionSignature([], [basicTypes.BOOL], true),
CALL: buildFunctionSignature([basicTypes.BYTES_MEM], [basicTypes.BOOL, basicTypes.BYTES_MEM], true),
@ -88,7 +97,7 @@ const basicFunctionTypes = {
TRANSFER: buildFunctionSignature([basicTypes.UINT], [], false)
}
const builtinFunctions = {
const builtinFunctions: Record<string, boolean> = {
'keccak256()': true,
'sha3()': true,
'sha256()': true,
@ -108,7 +117,7 @@ const builtinFunctions = {
'address(address)': true
}
const lowLevelCallTypes = {
const lowLevelCallTypes: Record<string, Record<string, string>> = {
'CALL-0.4': { ident: 'call', type: basicFunctionTypes['CALL-0.4'] },
CALL: { ident: 'call', type: basicFunctionTypes.CALL },
CALLCODE: { ident: 'callcode', type: basicFunctionTypes['CALL-0.4'] },
@ -118,7 +127,7 @@ const lowLevelCallTypes = {
TRANSFER: { ident: 'transfer', type: basicFunctionTypes.TRANSFER }
}
const specialVariables = {
const specialVariables: Record<string, SpecialObjDetail> = {
BLOCKTIMESTAMP: { obj: 'block', member: 'timestamp', type: basicTypes.UINT },
BLOCKHASH: {
obj: 'block',
@ -127,7 +136,7 @@ const specialVariables = {
}
}
const abiNamespace = {
const abiNamespace: Record<string, SpecialObjDetail> = {
ENCODE: {
obj: 'abi',
member: 'encode',
@ -174,17 +183,21 @@ function getFunctionCallType (func: FunctionCallAstNode): string {
* @return {string} variable name written to
*/
function getEffectedVariableName (effectNode: AssignmentAstNode | UnaryOperationAstNode): string {
// console.log('getEffectedVariableName---effectNode---', effectNode)
if (!isEffect(effectNode)) throw new Error('staticAnalysisCommon.js: not an effect Node')
if(effectNode.nodeType === 'Assignment' || effectNode.nodeType === 'UnaryOperation') {
const IdentNode = findFirstSubNodeLTR(effectNode, exactMatch(nodeTypes.IDENTIFIER))
const IdentNode: IdentifierAstNode = findFirstSubNodeLTR(effectNode, exactMatch(nodeTypes.IDENTIFIER))
return IdentNode.name
} else throw new Error('staticAnalysisCommon.js: wrong node type')
}
// developed keeping identifier node search in mind
function findFirstSubNodeLTR (node, type) {
/**
* 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
* Note: developed keeping identifier node search in mind to get first identifier node from left in subscope
*/
function findFirstSubNodeLTR (node: any, type: string): any {
if(node.nodeType && nodeType(node, type))
return node
@ -198,7 +211,7 @@ function findFirstSubNodeLTR (node, type) {
return findFirstSubNodeLTR(node.baseExpression, type)
else if(node.nodeType && nodeType(node, exactMatch('UnaryOperation')))
return findFirstSubNodeLTR(node.subExpression, type)
return findFirstSubNodeLTR(node.subExpression, type)
}
/**
@ -294,7 +307,6 @@ function getContractName (contract: ContractDefinitionAstNode): string {
* @return {string} name of a function defined
*/
function getFunctionDefinitionName (funcDef: FunctionDefinitionAstNode): string {
// if (!isFunctionDefinition(funcDef)) throw new Error('staticAnalysisCommon.js: not an functionDefinition Node')
return funcDef.name
}
@ -353,7 +365,6 @@ function getStateVariableDeclarationsFromContractNode (contractNode: ContractDef
* @return {parameterlist node} parameterlist node
*/
function getFunctionOrModifierDefinitionParameterPart (funcNode: FunctionDefinitionAstNode | ModifierDefinitionAstNode): ParameterListAstNode {
// if (!isFunctionDefinition(funcNode) && !isModifierDefinition(funcNode)) throw new Error('staticAnalysisCommon.js: not a function definition')
return funcNode.parameters
}
@ -376,17 +387,16 @@ function getFunctionDefinitionReturnParameterPart (funcNode: FunctionDefinitionA
* @return {string} parameter signature
*/
function getFunctionCallTypeParameterType (func: FunctionCallAstNode): string | undefined {
const type = getFunctionCallType(func)
const type: string = getFunctionCallType(func)
if (type.startsWith('function (')) {
let paramTypes = ''
let openPar = 1
let paramTypes: string = ''
let openPar: number = 1
for (let x = 10; x < type.length; x++) {
const c = type.charAt(x)
const c: string = type.charAt(x)
if (c === '(') openPar++
else if (c === ')') openPar--
if (openPar === 0) return paramTypes
paramTypes += c
}
} else {
@ -471,25 +481,12 @@ function getUnAssignedTopLevelBinOps (subScope: BlockAstNode | IfStatementAstNod
return result
}
// function getLoopBlockStartIndex (node: ForStatementAstNode | WhileStatementAstNode): 3|1 {
// return node.nodeType === "ForStatement" ? 3 : 1
// }
// #################### Trivial Node Identification
function isStatement (node: any): boolean {
return nodeType(node, 'Statement$') || node.nodeType === "Block" || node.nodeType === "Return"
}
/**
* True if is binaryop
* @node {ASTNode} some AstNode
* @return {bool}
*/
// function isBinaryOperation (node) {
// return nodeType(node, exactMatch(nodeTypes.BINARYOPERATION))
// }
// #################### Complex Node Identification
/**
@ -538,15 +535,6 @@ function isDeleteFromDynamicArray (node: UnaryOperationAstNode): boolean {
return isDeleteUnaryOperation(node) && node.subExpression.nodeType === 'IndexAccess'
}
/**
* True if node is the access of an index
* @node {ASTNode} node to check for
* @return {bool}
*/
// function isIndexAccess (node) {
// return node && node.name === 'IndexAccess'
// }
/**
* True if node is the access of a mapping index
* @node {ASTNode} node to check for
@ -571,9 +559,6 @@ function isLocalCallGraphRelevantNode (node: FunctionCallAstNode): boolean {
* @return {bool}
*/
function isBuiltinFunctionCall (node: FunctionCallAstNode): boolean {
// console.log('isBuiltinFunctionCall isLocalCall', isLocalCall(node))
// console.log('isBuiltinFunctionCall getLocalCallName', getLocalCallName(node))
// console.log('isBuiltinFunctionCall getFunctionCallTypeParameterType', getFunctionCallTypeParameterType(node))
return (node.nodeType === 'FunctionCall' && isLocalCall(node) && builtinFunctions[getLocalCallName(node) + '(' + getFunctionCallTypeParameterType(node) + ')'] === true) || isAbiNamespaceCall(node)
}
@ -619,7 +604,6 @@ function isRequireCall (node: FunctionCallAstNode): boolean {
* @return {bool}
*/
function isStorageVariableDeclaration (node: VariableDeclarationAstNode): boolean {
// console.log('storage variable----------', new RegExp(basicRegex.REFTYPE).test(node.typeDescriptions.typeIdentifier))
return node.storageLocation === 'storage' && new RegExp(basicRegex.REFTYPE).test(node.typeDescriptions.typeIdentifier)
}
@ -629,7 +613,6 @@ function isStorageVariableDeclaration (node: VariableDeclarationAstNode): boolea
* @return {bool}
*/
function isInteraction (node: FunctionCallAstNode): boolean {
// console.log('Inside isInteraction----------', node)
return isLLCall(node.expression) || isLLSend(node.expression) || isExternalDirectCall(node) || isTransfer(node.expression) ||
isLLCall04(node.expression) || isLLSend04(node.expression) ||
// to cover case of address.call.value.gas , See: inheritance.sol
@ -692,7 +675,7 @@ function isPayableFunction (node: FunctionDefinitionAstNode): boolean {
* @return {bool}
*/
function isConstructor (node: FunctionDefinitionAstNode): boolean {
return node.kind === "constructor" // ||
return node.kind === "constructor"
}
/**
@ -871,10 +854,8 @@ function isSuperLocalCall (node: MemberAccessAstNode): boolean {
* @return {bool}
*/
function isLocalCall (node: FunctionCallAstNode): boolean {
return node.nodeType === 'FunctionCall' &&
node.kind === 'functionCall' &&
node.expression.nodeType === 'Identifier' &&
expressionTypeDescription(node, basicRegex.FUNCTIONTYPE) &&
return node.nodeType === 'FunctionCall' && node.kind === 'functionCall' &&
node.expression.nodeType === 'Identifier' && expressionTypeDescription(node, basicRegex.FUNCTIONTYPE) &&
!expressionTypeDescription(node, basicRegex.EXTERNALFUNCTIONTYPE)
}
@ -1009,51 +990,24 @@ function isLoop (node) {
nodeType(node, exactMatch(nodeTypes.DOWHILESTATEMENT))
}
/**
* True if it is a 'for' loop
* @node {ASTNode} some AstNode
* @return {bool}
* TODO: This should be removed once for loop iterates Over dynamic array fixed
*/
// function isForLoop (node) {
// return nodeType(node, exactMatch(nodeTypes.FORSTATEMENT))
// }
// #################### Complex Node Identification - Private
function isMemberAccess (node: MemberAccessAstNode, retType: string, accessor: string| undefined, accessorType: string, memberName: string | undefined): boolean {
if(node && nodeType(node, exactMatch('MemberAccess'))) {
// console.log('node inside memberaccess------', node)
const nodeTypeDef: boolean = typeDescription(node, retType)
// console.log('MemberAccess typeDef ->',nodeTypeDef)
const nodeMemName: boolean = memName(node, memberName)
// console.log('MemberAccess nodeMemName ->',nodeMemName)
const nodeExpMemName: boolean = memName(node.expression, accessor)
// console.log('MemberAccess nodeExpMemName ->',nodeExpMemName)
const nodeExpTypeDef: boolean = expressionTypeDescription(node, accessorType)
// console.log('MemberAccess nodeExpTypeDef ->',nodeExpTypeDef)
return nodeTypeDef && nodeMemName && nodeExpTypeDef && nodeExpMemName
} else return false
}
function isSpecialVariableAccess (node: MemberAccessAstNode, varType: any): boolean {
function isSpecialVariableAccess (node: MemberAccessAstNode, varType: SpecialObjDetail): boolean {
return isMemberAccess(node, exactMatch(util.escapeRegExp(varType.type)), varType.obj, varType.obj, varType.member)
}
// #################### Node Identification Primitives
// function nrOfChildren (node, nr) {
// return (node && (nr === undefined || nr === null)) || (node && nr === 0 && !node.children) || (node && node.children && node.children.length === nr)
// }
// function minNrOfChildren (node, nr) {
// return (node && (nr === undefined || nr === null)) || (node && nr === 0 && !node.children) || (node && node.children && node.children.length >= nr)
// }
// function expressionType (node, typeRegex) {
// return new RegExp(typeRegex).test(node.expression.typeDescriptions.typeString)
// }
function expressionTypeDescription (node: any, typeRegex: string): boolean {
return new RegExp(typeRegex).test(node.expression.typeDescriptions.typeString)
}
@ -1062,30 +1016,29 @@ function typeDescription (node: any, typeRegex: string): boolean {
return new RegExp(typeRegex).test(node.typeDescriptions.typeString)
}
function nodeType (node: any, typeRegex: string) {
function nodeType (node: any, typeRegex: string): boolean {
return new RegExp(typeRegex).test(node.nodeType)
}
function nodeTypeIn (node: any, typeRegex: string[]){
function nodeTypeIn (node: any, typeRegex: string[]): boolean {
return typeRegex.some((typeRegex) => nodeType (node, typeRegex))
}
function memName (node: any, memNameRegex: any) {
// const regex = new RegExp(memNameRegex)
function memName (node: any, memNameRegex: any): boolean {
return (node && !memNameRegex) || new RegExp(memNameRegex).test(node.name) || new RegExp(memNameRegex).test(node.memberName)
}
function operator (node, opRegex) {
function operator (node: any, opRegex: string): boolean {
return new RegExp(opRegex).test(node.operator)
}
// #################### Helpers
function exactMatch (regexStr) {
function exactMatch (regexStr: string): string {
return '^' + regexStr + '$'
}
function matches (...fnArgs) {
function matches (...fnArgs: any[]): string {
const args: any[] = []
for (let k = 0; k < fnArgs.length; k++) {
args.push(fnArgs[k])
@ -1109,28 +1062,7 @@ function buildAbiSignature (funName: string, paramTypes: any[]): string {
return funName + '(' + util.concatWithSeperator(paramTypes, ',') + ')'
}
// /**
// * 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) {
// const item = node.children[i]
// if (nodeType(item, type)) return item
// else {
// const res = findFirstSubNodeLTR(item, type)
// if (res) return res
// }
// }
// return null
// }
const helpers = {
// nrOfChildren,
// minNrOfChildren,
expressionTypeDescription,
nodeType,
memName,
@ -1165,7 +1097,6 @@ export {
getFunctionOrModifierDefinitionParameterPart,
getFunctionDefinitionReturnParameterPart,
getUnAssignedTopLevelBinOps,
// getLoopBlockStartIndex,
// #################### Complex Node Identification
isDeleteOfDynamicArray,
@ -1174,7 +1105,6 @@ export {
isSpecialVariableAccess,
isDynamicArrayAccess,
isDynamicArrayLengthAccess,
// isIndexAccess,
isMappingIndexAccess,
isSubScopeWithTopLevelUnAssignedBinOp,
hasFunctionBody,
@ -1217,24 +1147,11 @@ export {
// #################### Trivial Node Identification
isDeleteUnaryOperation,
// isFunctionDefinition,
// isModifierDefinition,
// isInheritanceSpecifier,
// isModifierInvocation,
// isVariableDeclaration,
isStorageVariableDeclaration,
// isAssignment,
// isContractDefinition,
isConstantFunction,
isPayableFunction,
isConstructor,
// isInlineAssembly,
// isNewExpression,
// isReturn,
isStatement,
// isExpressionStatement,
// isBlock,
// isBinaryOperation,
// #################### Constants
nodeTypes,

@ -12,7 +12,6 @@ export default class stringBytesLength implements AnalyzerModule {
stringToBytesConversions: FunctionCallAstNode[] = []
bytesLengthChecks: MemberAccessAstNode[] = []
visit (node: FunctionCallAstNode | MemberAccessAstNode): void {
if (node.nodeType === "FunctionCall" && isStringToBytesConversion(node)) this.stringToBytesConversions.push(node)
else if (node.nodeType === "MemberAccess" && isBytesLengthCheck(node)) this.bytesLengthChecks.push(node)

@ -11,9 +11,8 @@ export default class txOrigin implements AnalyzerModule {
algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node: MemberAccessAstNode): void {
if (isTxOriginAccess(node)) {
this.txOriginNodes.push(node)
}
if (isTxOriginAccess(node)) this.txOriginNodes.push(node)
}
report (compilationResults: CompilationResult): ReportObj[] {

@ -3,8 +3,8 @@ export interface AnalyzerModule {
description: string,
category: ModuleCategory
algorithm: ModuleAlgorithm
visit: Function
report: Function
visit: VisitFunction
report: ReportFunction
}
export interface ModuleAlgorithm {
@ -33,20 +33,27 @@ export interface CompilationResult {
[contractName: string]: CompilationSource
}
/** This contains the contract-level outputs. It can be limited/filtered by the outputSelection settings */
contracts?: {
/** If the language used has no contract names, this field should equal to an empty string. */
[fileName: string]: {
[contract: string]: CompiledContract
}
}
contracts?: CompiledContractObj /** If the language used has no contract names, this field should equal to an empty string. */
}
export interface CompiledContractObj {
[fileName: string]: {
[contract: string]: CompiledContract
}
}
export type VisitFunction = (node: any) => void
export type ReportFunction = (compilationResult: CompilationResult) => ReportObj[]
export interface ContractHLAst {
node: ContractDefinitionAstNode,
functions: FunctionHLAst[],
relevantNodes: any[],
relevantNodes: {
referencedDeclaration: number,
node: any
}[],
modifiers: ModifierHLAst[],
inheritsFrom: any[],
inheritsFrom: string[],
stateVariables: VariableDeclarationAstNode[]
}
@ -74,7 +81,7 @@ export interface Context {
export interface FunctionCallGraph {
node: FunctionHLAst
calls: any[]
calls: string[]
}
export interface ContractCallGraph {

@ -38,7 +38,7 @@ test('staticAnalysisCommon.helpers.buildFunctionSignature', function (t) {
t.equal(common.lowLevelCallTypes['CALL-0.4'].type,
'function () payable returns (bool)',
'check fixed call type for version before 0.5.0')
'check fixed call type for versions before 0.5.0')
t.equal(common.lowLevelCallTypes.CALLCODE.type,
'function () payable returns (bool)',
@ -50,11 +50,11 @@ t.equal(common.lowLevelCallTypes.CALLCODE.type,
t.equal(common.lowLevelCallTypes.DELEGATECALL.type,
'function (bytes memory) returns (bool,bytes memory)',
'check fixed call type')
'check fixed delegatecall type')
t.equal(common.lowLevelCallTypes['DELEGATECALL-0.4'].type,
'function () returns (bool)',
'check fixed call type')
'check fixed delegatecall type for version before 0.5.0')
})
// #################### Node Identification Primitives

Loading…
Cancel
Save