Commit 03508274 authored by aniket-engg's avatar aniket-engg Committed by Aniket

more type definitions added

parent 2cb6cf1c
'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
}
}
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
})
......
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[] = []
......
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))
}
}
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
}
......
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
......
'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
}
}
......
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
}
}
}
}
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) &&
......
......@@ -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. */
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
......
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