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. 55
      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. 165
      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. 23
      remix-analyzer/src/types.ts
  19. 6
      remix-analyzer/test/analysis/staticAnalysisCommon-test.ts

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

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

@ -1,7 +1,8 @@
import { default as category } from './categories' import { default as category } from './categories'
import { isSubScopeWithTopLevelUnAssignedBinOp, getUnAssignedTopLevelBinOps } from './staticAnalysisCommon' import { isSubScopeWithTopLevelUnAssignedBinOp, getUnAssignedTopLevelBinOps } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' 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 { export default class assignAndCompare implements AnalyzerModule {
warningNodes: ExpressionStatementAstNode[] = [] warningNodes: ExpressionStatementAstNode[] = []

@ -1,7 +1,8 @@
import { default as category } from './categories' import { default as category } from './categories'
import { isNowAccess, isBlockTimestampAccess } from './staticAnalysisCommon' import { isNowAccess, isBlockTimestampAccess } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' 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 { export default class blockTimestamp implements AnalyzerModule {
warningNowNodes: IdentifierAstNode[] = [] warningNowNodes: IdentifierAstNode[] = []

@ -4,7 +4,9 @@ import { isInteraction, isEffect, isLocalCallGraphRelevantNode, getFullQuallyfie
import { default as algorithm } from './algorithmCategories' import { default as algorithm } from './algorithmCategories'
import { buildGlobalFuncCallGraph, resolveCallGraphSymbol, analyseCallGraph } from './functionCallGraph' import { buildGlobalFuncCallGraph, resolveCallGraphSymbol, analyseCallGraph } from './functionCallGraph'
import AbstractAst from './abstractAstView' 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 { export default class checksEffectsInteraction implements AnalyzerModule {
name: string = 'Check effects: ' name: string = 'Check effects: '
@ -14,11 +16,11 @@ export default class checksEffectsInteraction implements AnalyzerModule {
abstractAst: AbstractAst = new AbstractAst() 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 === 'FunctionCall' && (isInteraction(node) || isLocalCallGraphRelevantNode(node))) ||
((node.nodeType === 'Assignment' || node.nodeType === 'UnaryOperation' || node.nodeType === 'InlineAssembly') && isEffect(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[] { private _report (contracts: ContractHLAst[], multipleContractsWithSameName: boolean): ReportObj[] {
const warnings: ReportObj[] = [] const warnings: ReportObj[] = []
@ -38,10 +40,10 @@ export default class checksEffectsInteraction implements AnalyzerModule {
func) func)
) )
}) })
contract.functions.forEach((func) => { contract.functions.forEach((func: FunctionHLAst) => {
if (this.isPotentialVulnerableFunction(func, this.getContext(callGraph, contract, func))) { if (this.isPotentialVulnerableFunction(func, this.getContext(callGraph, contract, func))) {
const funcName = getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters) const funcName: string = getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters)
let comments = (hasModifiers) ? 'Note: Modifiers are currently not considered by this static analysis.' : '' 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.' : '' comments += (multipleContractsWithSameName) ? 'Note: Import aliases are currently not supported by this static analysis.' : ''
warnings.push({ warnings.push({
warning: `Potential Violation of Checks-Effects-Interaction pattern in ${funcName}: Could potentially lead to re-entrancy vulnerability. ${comments}`, 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 { private isLocalCallWithStateChange (node: FunctionCallAstNode, context: Context): boolean {
if (isLocalCallGraphRelevantNode(node)) { 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 !func || (func && func.node['changesState'])
} }
return false return false
} }
private checkIfChangesState (startFuncName: string, context: Context): boolean { 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 { default as category } from './categories'
import { isLowLevelCall, isTransfer, isExternalDirectCall, isEffect, isLocalCallGraphRelevantNode, import { isLowLevelCall, isTransfer, isExternalDirectCall, isEffect, isLocalCallGraphRelevantNode, isSelfdestructCall,
isSelfdestructCall, isDeleteUnaryOperation, isPayableFunction, isDeleteUnaryOperation, isPayableFunction, isConstructor, getFullQuallyfiedFuncDefinitionIdent, hasFunctionBody,
isConstructor, getFullQuallyfiedFuncDefinitionIdent, hasFunctionBody, isConstantFunction, isWriteOnStateVariable, isConstantFunction, isWriteOnStateVariable, isStorageVariableDeclaration, isCallToNonConstLocalFunction,
isStorageVariableDeclaration, isCallToNonConstLocalFunction, getFullQualifiedFunctionCallIdent} from './staticAnalysisCommon' getFullQualifiedFunctionCallIdent} from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import { default as algorithm } from './algorithmCategories'
import { buildGlobalFuncCallGraph, resolveCallGraphSymbol, analyseCallGraph } from './functionCallGraph' import { buildGlobalFuncCallGraph, resolveCallGraphSymbol, analyseCallGraph } from './functionCallGraph'
import AbstractAst from './abstractAstView' 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 { export default class constantFunctions implements AnalyzerModule {
name: string = 'Constant functions: ' name: string = 'Constant functions: '
@ -16,7 +17,7 @@ export default class constantFunctions implements AnalyzerModule {
abstractAst: AbstractAst = new AbstractAst() abstractAst: AbstractAst = new AbstractAst()
visit: Function = this.abstractAst.build_visit( visit: VisitFunction = this.abstractAst.build_visit(
(node: any) => isLowLevelCall(node) || (node: any) => isLowLevelCall(node) ||
isTransfer(node) || isTransfer(node) ||
isExternalDirectCall(node) || isExternalDirectCall(node) ||
@ -28,7 +29,7 @@ export default class constantFunctions implements AnalyzerModule {
isDeleteUnaryOperation(node) 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[] { private _report (contracts: ContractHLAst[], multipleContractsWithSameName: boolean): ReportObj[] {
const warnings: ReportObj[] = [] const warnings: ReportObj[] = []
@ -36,8 +37,8 @@ export default class constantFunctions implements AnalyzerModule {
const callGraph: Record<string, ContractCallGraph> = buildGlobalFuncCallGraph(contracts) const callGraph: Record<string, ContractCallGraph> = buildGlobalFuncCallGraph(contracts)
contracts.forEach((contract) => { contracts.forEach((contract: ContractHLAst) => {
contract.functions.forEach((func) => { contract.functions.forEach((func: FunctionHLAst) => {
if (isPayableFunction(func.node) || isConstructor(func.node)) { if (isPayableFunction(func.node) || isConstructor(func.node)) {
func['potentiallyshouldBeConst'] = false func['potentiallyshouldBeConst'] = false
} else { } 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']) { if (isConstantFunction(func.node) !== func['potentiallyshouldBeConst']) {
const funcName: string = getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters) const funcName: string = getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters)
let comments: string = (hasModifiers) ? 'Note: Modifiers are currently not considered by this static analysis.' : '' 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 { getFunctionDefinitionName, helpers, getDeclaredVariableName, getDeclaredVariableType } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import { default as algorithm } from './algorithmCategories'
import AbstractAst from './abstractAstView' 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 { export default class erc20Decimals implements AnalyzerModule {
name: string = 'ERC20: ' name: string = 'ERC20: '
@ -11,21 +12,21 @@ export default class erc20Decimals implements AnalyzerModule {
algorithm: ModuleAlgorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
abstractAst: AbstractAst = new AbstractAst() abstractAst: AbstractAst = new AbstractAst()
visit = this.abstractAst.build_visit((node: AstNodeLegacy) => false) visit: VisitFunction = this.abstractAst.build_visit((node: any) => false)
report = this.abstractAst.build_report(this._report.bind(this)) 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[] = [] const warnings: ReportObj[] = []
contracts.forEach((contract) => { contracts.forEach((contract: ContractHLAst) => {
const contractAbiSignatures = contract.functions.map((f) => helpers.buildAbiSignature(getFunctionDefinitionName(f.node), f.parameters)) const contractAbiSignatures: string[] = contract.functions.map((f: FunctionHLAst) => helpers.buildAbiSignature(getFunctionDefinitionName(f.node), f.parameters))
if (this.isERC20(contractAbiSignatures)) { if (this.isERC20(contractAbiSignatures)) {
const decimalsVar = contract.stateVariables.filter((stateVar) => getDeclaredVariableName(stateVar) === 'decimals' && (getDeclaredVariableType(stateVar) !== 'uint8' || stateVar.attributes.visibility !== 'public')) const decimalsVar: VariableDeclarationAstNode[] = contract.stateVariables.filter((stateVar: VariableDeclarationAstNode) => getDeclaredVariableName(stateVar) === 'decimals' && (getDeclaredVariableType(stateVar) !== 'uint8' || stateVar.visibility !== 'public'))
const decimalsFun = contract.functions.filter((f) => getFunctionDefinitionName(f.node) === 'decimals' && const decimalsFun: FunctionHLAst[] = contract.functions.filter((f: FunctionHLAst) => getFunctionDefinitionName(f.node) === 'decimals' &&
( (
(f.returns.length === 0 || f.returns.length > 1) || (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 return warnings
} }

@ -1,10 +1,11 @@
import { default as category } from './categories' import { default as category } from './categories'
import { default as algorithm } from './algorithmCategories' import { default as algorithm } from './algorithmCategories'
import { isLoop, isTransfer } from './staticAnalysisCommon' 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 { export default class etherTransferInLoop implements AnalyzerModule {
relevantNodes: CommonAstNode[] = [] relevantNodes: ExpressionStatementAstNode[] = []
name: string = 'Ether transfer in a loop: ' name: string = 'Ether transfer in a loop: '
description: string = 'Avoid transferring Ether to multiple addresses in a loop' description: string = 'Avoid transferring Ether to multiple addresses in a loop'
category: ModuleCategory = category.GAS category: ModuleCategory = category.GAS

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

@ -1,40 +1,35 @@
import { default as category } from './categories' import { default as category } from './categories'
import { default as algorithm } from './algorithmCategories' 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: ' name: string = 'Gas costs: '
description: string = 'Warn if the gas requirements of functions are too high.' description: string = 'Warn if the gas requirements of functions are too high.'
category: ModuleCategory = category.GAS category: ModuleCategory = category.GAS
algorithm: ModuleAlgorithm = algorithm.EXACT algorithm: ModuleAlgorithm = algorithm.EXACT
abstractAst: AbstractAst = new AbstractAst()
/** visit: VisitFunction = this.abstractAst.build_visit((node: any) => false)
* 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
}
}
}
report (compilationResults: CompilationResult): ReportObj[] { report (compilationResults: CompilationResult): ReportObj[] {
const report: any[] = [] const report: ReportObj[] = []
this.visitContracts(compilationResults.contracts, (contract) => { this.visitContracts(compilationResults.contracts, (contract: VisitedContract) => {
if ( if (
!contract.object.evm.gasEstimates || !contract.object.evm.gasEstimates ||
!contract.object.evm.gasEstimates.external !contract.object.evm.gasEstimates.external
) { ) {
return return
} }
const fallback = contract.object.evm.gasEstimates.external[''] const fallback: string = contract.object.evm.gasEstimates.external['']
if (fallback !== undefined) { if (fallback !== undefined) {
if (fallback === null || fallback >= 2100 || fallback === 'infinite') { if (fallback === null || parseInt(fallback) >= 2100 || fallback === 'infinite') {
report.push({ report.push({
warning: `Fallback function of contract ${contract.name} requires too much gas (${fallback}). 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.` If the fallback function requires more than 2300 gas, the contract cannot receive Ether.`
@ -46,9 +41,9 @@ export default class gasCosts {
if (functionName === '') { if (functionName === '') {
continue continue
} }
const gas = contract.object.evm.gasEstimates.external[functionName] const gas: string = contract.object.evm.gasEstimates.external[functionName]
const gasString = gas === null ? 'unknown or not constant' : 'high: ' + gas const gasString: string = gas === null ? 'unknown or not constant' : 'high: ' + gas
if (gas === null || gas >= 3000000 || gas === 'infinite') { if (gas === null || parseInt(gas) >= 3000000 || gas === 'infinite') {
report.push({ report.push({
warning: `Gas requirement of function ${contract.name}.${functionName} ${gasString}. 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. 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 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 { default as category } from './categories'
import { isLLCall, isLLDelegatecall, isLLCallcode, isLLCall04, isLLDelegatecall04, isLLSend04, import { isLLCall, isLLDelegatecall, isLLCallcode, isLLCall04, isLLDelegatecall04, isLLSend04, isLLSend, lowLevelCallTypes } from './staticAnalysisCommon'
isLLSend, lowLevelCallTypes } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import { default as algorithm } from './algorithmCategories'
import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, MemberAccessAstNode} from './../../types' import { AnalyzerModule, ModuleAlgorithm, ModuleCategory, ReportObj, CompilationResult, MemberAccessAstNode} from './../../types'
interface llcNode { interface llcNode {
node: MemberAccessAstNode node: MemberAccessAstNode
type: { type: Record<string, string>
ident: string,
type: string
}
} }
export default class lowLevelCalls implements AnalyzerModule { export default class lowLevelCalls implements AnalyzerModule {
@ -39,8 +35,8 @@ export default class lowLevelCalls implements AnalyzerModule {
report (compilationResults: CompilationResult): ReportObj[] { report (compilationResults: CompilationResult): ReportObj[] {
return this.llcNodes.map((item, i) => { return this.llcNodes.map((item, i) => {
let text = '' let text: string = ''
let morehref: any = null let morehref: string = ''
switch (item.type) { switch (item.type) {
case lowLevelCallTypes.CALL: case lowLevelCallTypes.CALL:
text = `use of "call": the use of low level "call" should be avoided whenever possible. 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 { hasFunctionBody, getFullQuallyfiedFuncDefinitionIdent, getEffectedVariableName } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import { default as algorithm } from './algorithmCategories'
import AbstractAst from './abstractAstView' 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 { export default class noReturn implements AnalyzerModule {
name: string = 'no return: ' name: string = 'no return: '
@ -12,16 +13,16 @@ export default class noReturn implements AnalyzerModule {
abstractAst: AbstractAst = new AbstractAst() abstractAst: AbstractAst = new AbstractAst()
visit: Function = this.abstractAst.build_visit( visit: VisitFunction = this.abstractAst.build_visit(
(node: CommonAstNode) => node.nodeType === "Return" || node.nodeType === "Assignment" (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[] { private _report (contracts: ContractHLAst[], multipleContractsWithSameName: boolean): ReportObj[] {
const warnings: ReportObj[] = [] const warnings: ReportObj[] = []
contracts.forEach((contract) => { contracts.forEach((contract: ContractHLAst) => {
contract.functions.filter((func) => hasFunctionBody(func.node)).forEach((func) => { contract.functions.filter((func: FunctionHLAst) => hasFunctionBody(func.node)).forEach((func: FunctionHLAst) => {
const funcName: string = getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters) const funcName: string = getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters)
if (this.hasNamedAndUnnamedReturns(func)) { if (this.hasNamedAndUnnamedReturns(func)) {
warnings.push({ warnings.push({
@ -36,7 +37,6 @@ export default class noReturn implements AnalyzerModule {
} }
}) })
}) })
return warnings return warnings
} }
@ -49,7 +49,7 @@ export default class noReturn implements AnalyzerModule {
} }
private hasAssignToAllNamedReturns (func: FunctionHLAst): boolean { 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 assignedVars: string[] = func.relevantNodes.filter(n => n.nodeType === "Assignment").map(getEffectedVariableName)
const diff: string[] = namedReturns.filter(e => !assignedVars.includes(e)) const diff: string[] = namedReturns.filter(e => !assignedVars.includes(e))
return diff.length === 0 return diff.length === 0
@ -60,7 +60,6 @@ export default class noReturn implements AnalyzerModule {
} }
private hasNamedAndUnnamedReturns (func: FunctionHLAst): boolean { private hasNamedAndUnnamedReturns (func: FunctionHLAst): boolean {
return func.returns.filter((n) => n.name.length === 0).length > 0 && return func.returns.filter((n) => n.name.length === 0).length > 0 && this.hasNamedReturns(func)
this.hasNamedReturns(func)
} }
} }

@ -2,7 +2,7 @@ import { default as category } from './categories'
import { isStatement, isSelfdestructCall } from './staticAnalysisCommon' import { isStatement, isSelfdestructCall } from './staticAnalysisCommon'
import { default as algorithm } from './algorithmCategories' import { default as algorithm } from './algorithmCategories'
import AbstractAst from './abstractAstView' 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 { export default class selfdestruct implements AnalyzerModule {
name: string = 'Selfdestruct: ' name: string = 'Selfdestruct: '
@ -12,11 +12,11 @@ export default class selfdestruct implements AnalyzerModule {
abstractAst: AbstractAst = new AbstractAst() 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)) (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[] { private _report (contracts: ContractHLAst[], multipleContractsWithSameName: boolean): ReportObj[] {
const warnings: ReportObj[] = [] const warnings: ReportObj[] = []
@ -43,7 +43,6 @@ export default class selfdestruct implements AnalyzerModule {
}) })
}) })
}) })
return warnings return warnings
} }
} }

@ -4,7 +4,13 @@ import { default as algorithm } from './algorithmCategories'
import AbstractAst from './abstractAstView' import AbstractAst from './abstractAstView'
import { get } from 'fast-levenshtein' import { get } from 'fast-levenshtein'
import { util } from 'remix-lib' 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 { export default class similarVariableNames implements AnalyzerModule {
name: string = 'Similar variable names: ' name: string = 'Similar variable names: '
@ -14,9 +20,9 @@ export default class similarVariableNames implements AnalyzerModule {
abstractAst:AbstractAst = new AbstractAst() 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[] { private _report (contracts: ContractHLAst[], multipleContractsWithSameName: boolean): ReportObj[] {
const warnings: ReportObj[] = [] const warnings: ReportObj[] = []
@ -35,7 +41,6 @@ export default class similarVariableNames implements AnalyzerModule {
} }
const vars: string[] = this.getFunctionVariables(contract, func).map(getDeclaredVariableName) const vars: string[] = this.getFunctionVariables(contract, func).map(getDeclaredVariableName)
this.findSimilarVarNames(vars).map((sim) => { this.findSimilarVarNames(vars).map((sim) => {
warnings.push({ warnings.push({
warning: `${funcName} : Variables have very similar names ${sim.var1} and ${sim.var2}. ${hasModifiersComments} ${multipleContractsWithSameNameComments}`, 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 return warnings
} }
private findSimilarVarNames (vars: string[]): Record<string, any>[] { private findSimilarVarNames (vars: string[]): SimilarRecord[] {
const similar: Record<string, any>[] = [] const similar: SimilarRecord[] = []
const comb: Record<string, boolean> = {} 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 && if (varName1.length > 1 && varName2.length > 1 &&
varName2 !== varName1 && !this.isCommonPrefixedVersion(varName1, varName2) && varName2 !== varName1 && !this.isCommonPrefixedVersion(varName1, varName2) &&
!this.isCommonNrSuffixVersion(varName1, varName2) && !this.isCommonNrSuffixVersion(varName1, varName2) &&

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

@ -12,7 +12,6 @@ export default class stringBytesLength implements AnalyzerModule {
stringToBytesConversions: FunctionCallAstNode[] = [] stringToBytesConversions: FunctionCallAstNode[] = []
bytesLengthChecks: MemberAccessAstNode[] = [] bytesLengthChecks: MemberAccessAstNode[] = []
visit (node: FunctionCallAstNode | MemberAccessAstNode): void { visit (node: FunctionCallAstNode | MemberAccessAstNode): void {
if (node.nodeType === "FunctionCall" && isStringToBytesConversion(node)) this.stringToBytesConversions.push(node) if (node.nodeType === "FunctionCall" && isStringToBytesConversion(node)) this.stringToBytesConversions.push(node)
else if (node.nodeType === "MemberAccess" && isBytesLengthCheck(node)) this.bytesLengthChecks.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 algorithm: ModuleAlgorithm = algorithm.EXACT
visit (node: MemberAccessAstNode): void { visit (node: MemberAccessAstNode): void {
if (isTxOriginAccess(node)) { if (isTxOriginAccess(node)) this.txOriginNodes.push(node)
this.txOriginNodes.push(node)
}
} }
report (compilationResults: CompilationResult): ReportObj[] { report (compilationResults: CompilationResult): ReportObj[] {

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

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

Loading…
Cancel
Save