diff --git a/remix-analyzer/package.json b/remix-analyzer/package.json index 6d089088d9..e1a0cd52ea 100644 --- a/remix-analyzer/package.json +++ b/remix-analyzer/package.json @@ -25,11 +25,12 @@ "scripts": { "build": "tsc", "lint": "standard", - "test": "npm run lint && tape ./test/tests.js" + "test": "tsc && npm run lint && tape ./test/tests.js" }, "standard": { "ignore": [ - "node_modules/*" + "node_modules/*", + "dist/" ], "parser": "babel-eslint" }, diff --git a/remix-analyzer/src/solidity-analyzer/index.ts b/remix-analyzer/src/solidity-analyzer/index.ts index 35e474ecb4..29a474b723 100644 --- a/remix-analyzer/src/solidity-analyzer/index.ts +++ b/remix-analyzer/src/solidity-analyzer/index.ts @@ -7,7 +7,7 @@ export class staticAnalysisRunner { run (compilationResult, toRun, callback) { const modules = toRun.map((i) => { const m = this.modules()[i] - return { 'name': m.name, 'mod': new m.Module() } + return { 'name': m.name, 'mod': m } }) this.runWithModuleList(compilationResult, modules, callback) diff --git a/remix-analyzer/src/solidity-analyzer/modules/abstractAstView.ts b/remix-analyzer/src/solidity-analyzer/modules/abstractAstView.ts index 6b96077269..2fcd83c57f 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/abstractAstView.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/abstractAstView.ts @@ -1,7 +1,10 @@ -import common from './staticAnalysisCommon' +import { isContractDefinition, getStateVariableDeclarationsFormContractNode, isInheritanceSpecifier, + getInheritsFromName, isFunctionDefinition, isModifierDefinition, isModifierInvocation, getContractName, + getFunctionOrModifierDefinitionParameterPart, getType, getDeclaredVariableName, isVariableDeclaration, + getFunctionOrModifierDefinitionReturnParameterPart } from './staticAnalysisCommon' import { AstWalker } from 'remix-astwalker' -export class abstractAstView { +export default class abstractAstView { contracts = [] currentContractIndex = null currentFunctionIndex = null @@ -46,20 +49,20 @@ export class abstractAstView { build_visit (relevantNodeFilter) { var that = this return function (node) { - if (common.isContractDefinition(node)) { + if (isContractDefinition(node)) { that.setCurrentContract(that, { node: node, functions: [], relevantNodes: [], modifiers: [], inheritsFrom: [], - stateVariables: common.getStateVariableDeclarationsFormContractNode(node) + stateVariables: getStateVariableDeclarationsFormContractNode(node) }) - } else if (common.isInheritanceSpecifier(node)) { + } else if (isInheritanceSpecifier(node)) { const currentContract = that.getCurrentContract(that) - const inheritsFromName = common.getInheritsFromName(node) + const inheritsFromName = getInheritsFromName(node) currentContract.inheritsFrom.push(inheritsFromName) - } else if (common.isFunctionDefinition(node)) { + } else if (isFunctionDefinition(node)) { that.setCurrentFunction(that, { node: node, relevantNodes: [], @@ -74,14 +77,14 @@ export class abstractAstView { that.getCurrentFunction(that).relevantNodes.push(item.node) } }) - } else if (common.isModifierDefinition(node)) { + } else if (isModifierDefinition(node)) { that.setCurrentModifier(that, { node: node, relevantNodes: [], localVariables: that.getLocalVariables(node), parameters: that.getLocalParameters(node) }) - } else if (common.isModifierInvocation(node)) { + } else if (isModifierInvocation(node)) { 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)) { @@ -115,7 +118,7 @@ export class abstractAstView { private resolveStateVariablesInHierarchyForContract (currentContract, contracts) { currentContract.inheritsFrom.map((inheritsFromName) => { // add variables from inherited contracts - const inheritsFrom = contracts.find((contract) => common.getContractName(contract.node) === inheritsFromName) + const inheritsFrom = contracts.find((contract) => getContractName(contract.node) === inheritsFromName) if (inheritsFrom) { currentContract.stateVariables = currentContract.stateVariables.concat(inheritsFrom.stateVariables) } else { @@ -125,8 +128,8 @@ export class abstractAstView { } private setCurrentContract (that, contract) { - const name = common.getContractName(contract.node) - if (that.contracts.map((c) => common.getContractName(c.node)).filter((n) => n === name).length > 0) { + const name = getContractName(contract.node) + if (that.contracts.map((c) => 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 } @@ -156,14 +159,14 @@ export class abstractAstView { } private getLocalParameters (funcNode) { - return this.getLocalVariables(common.getFunctionOrModifierDefinitionParameterPart(funcNode)).map(common.getType) + return this.getLocalVariables(getFunctionOrModifierDefinitionParameterPart(funcNode)).map(getType) } private getReturnParameters (funcNode) { - return this.getLocalVariables(common.getFunctionOrModifierDefinitionReturnParameterPart(funcNode)).map((n) => { + return this.getLocalVariables(getFunctionOrModifierDefinitionReturnParameterPart(funcNode)).map((n) => { return { - type: common.getType(n), - name: common.getDeclaredVariableName(n) + type: getType(n), + name: getDeclaredVariableName(n) } }) } @@ -171,7 +174,7 @@ export class abstractAstView { private getLocalVariables (funcNode) { const locals: any[] = [] new AstWalker().walk(funcNode, {'*': function (node) { - if (common.isVariableDeclaration(node)) locals.push(node) + if (isVariableDeclaration(node)) locals.push(node) return true }}) return locals diff --git a/remix-analyzer/src/solidity-analyzer/modules/assignAndCompare.ts b/remix-analyzer/src/solidity-analyzer/modules/assignAndCompare.ts index be12461e2b..1067e2f263 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/assignAndCompare.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/assignAndCompare.ts @@ -2,7 +2,7 @@ import { default as category } from './categories' import { isSubScopeWithTopLevelUnAssignedBinOp, getUnAssignedTopLevelBinOps } from './staticAnalysisCommon' import { default as algorithm } from './algorithmCategories' -export class assignAndCompare { +export default class assignAndCompare { warningNodes: any[] = [] name = 'Result not used: ' description = 'The result of an operation was not used.' diff --git a/remix-analyzer/src/solidity-analyzer/modules/blockBlockhash.ts b/remix-analyzer/src/solidity-analyzer/modules/blockBlockhash.ts index 3f5163d6d9..59e59a6b08 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/blockBlockhash.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/blockBlockhash.ts @@ -2,7 +2,7 @@ import { default as category } from './categories' import { isBlockBlockHashAccess } from './staticAnalysisCommon' import { default as algorithm } from './algorithmCategories' -export class blockBlockhash { +export default class blockBlockhash { warningNodes: any[] = [] name = 'Block.blockhash usage: ' desc = 'Semantics maybe unclear' diff --git a/remix-analyzer/src/solidity-analyzer/modules/blockTimestamp.ts b/remix-analyzer/src/solidity-analyzer/modules/blockTimestamp.ts index 9acf24a55d..060fa2ca28 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/blockTimestamp.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/blockTimestamp.ts @@ -2,7 +2,7 @@ import { default as category } from './categories' import { isNowAccess, isBlockTimestampAccess } from './staticAnalysisCommon' import { default as algorithm } from './algorithmCategories' -export class blockTimestamp { +export default class blockTimestamp { warningNowNodes: any[] = [] warningblockTimestampNodes: any[] = [] name = 'Block timestamp: ' diff --git a/remix-analyzer/src/solidity-analyzer/modules/checksEffectsInteraction.js b/remix-analyzer/src/solidity-analyzer/modules/checksEffectsInteraction.js deleted file mode 100644 index ee9860a77c..0000000000 --- a/remix-analyzer/src/solidity-analyzer/modules/checksEffectsInteraction.js +++ /dev/null @@ -1,90 +0,0 @@ -const name = 'Check effects: ' -const desc = 'Avoid potential reentrancy bugs' -const categories = require('./categories') -const common = require('./staticAnalysisCommon') -const fcallGraph = require('./functionCallGraph') -const AbstractAst = require('./abstractAstView') -const algo = require('./algorithmCategories') - -function checksEffectsInteraction () { - this.abstractAst = new AbstractAst() - this.visit = this.abstractAst.build_visit( - (node) => common.isInteraction(node) || common.isEffect(node) || common.isLocalCallGraphRelevantNode(node) - ) - - this.report = this.abstractAst.build_report(report) -} - -checksEffectsInteraction.prototype.visit = function () { throw new Error('checksEffectsInteraction.js no visit function set upon construction') } - -checksEffectsInteraction.prototype.report = function () { throw new Error('checksEffectsInteraction.js no report function set upon construction') } - -function report (contracts, multipleContractsWithSameName) { - const warnings = [] - const hasModifiers = contracts.some((item) => item.modifiers.length > 0) - - const callGraph = fcallGraph.buildGlobalFuncCallGraph(contracts) - - contracts.forEach((contract) => { - contract.functions.forEach((func) => { - func.changesState = checkIfChangesState(common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters), - getContext(callGraph, contract, func)) - }) - - contract.functions.forEach((func) => { - if (isPotentialVulnerableFunction(func, getContext(callGraph, contract, func))) { - const funcName = common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters) - let comments = (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}`, - location: func.src, - more: 'http://solidity.readthedocs.io/en/develop/security-considerations.html#re-entrancy' - }) - } - }) - }) - - return warnings -} - -function getContext (callGraph, currentContract, func) { - return { callGraph: callGraph, currentContract: currentContract, stateVariables: getStateVariables(currentContract, func) } -} - -function getStateVariables (contract, func) { - return contract.stateVariables.concat(func.localVariables.filter(common.isStorageVariableDeclaration)) -} - -function isPotentialVulnerableFunction (func, context) { - let isPotentialVulnerable = false - let interaction = false - func.relevantNodes.forEach((node) => { - if (common.isInteraction(node)) { - interaction = true - } else if (interaction && (common.isWriteOnStateVariable(node, context.stateVariables) || isLocalCallWithStateChange(node, context))) { - isPotentialVulnerable = true - } - }) - return isPotentialVulnerable -} - -function isLocalCallWithStateChange (node, context) { - if (common.isLocalCallGraphRelevantNode(node)) { - const func = fcallGraph.resolveCallGraphSymbol(context.callGraph, common.getFullQualifiedFunctionCallIdent(context.currentContract.node, node)) - return !func || (func && func.node.changesState) - } - return false -} - -function checkIfChangesState (startFuncName, context) { - return fcallGraph.analyseCallGraph(context.callGraph, startFuncName, context, (node, context) => common.isWriteOnStateVariable(node, context.stateVariables)) -} - -module.exports = { - name: name, - description: desc, - category: categories.SECURITY, - algorithm: algo.HEURISTIC, - Module: checksEffectsInteraction -} diff --git a/remix-analyzer/src/solidity-analyzer/modules/checksEffectsInteraction.ts b/remix-analyzer/src/solidity-analyzer/modules/checksEffectsInteraction.ts new file mode 100644 index 0000000000..2655e1c653 --- /dev/null +++ b/remix-analyzer/src/solidity-analyzer/modules/checksEffectsInteraction.ts @@ -0,0 +1,89 @@ +import { default as category } from './categories' +import { isInteraction, isEffect, isLocalCallGraphRelevantNode, getFullQuallyfiedFuncDefinitionIdent, + isWriteOnStateVariable, isStorageVariableDeclaration, getFullQualifiedFunctionCallIdent } from './staticAnalysisCommon' +import { default as algorithm } from './algorithmCategories' +import { buildGlobalFuncCallGraph, resolveCallGraphSymbol, analyseCallGraph } from './functionCallGraph' +import AbstractAst from './abstractAstView' + +export default class checksEffectsInteraction { + + name = 'Check effects: ' + desc = 'Avoid potential reentrancy bugs' + categories = category.SECURITY + algorithm = algorithm.HEURISTIC + Module = this + + abstractAst = new AbstractAst() + + visit = this.abstractAst.build_visit((node) => isInteraction(node) || isEffect(node) || isLocalCallGraphRelevantNode(node)) + + report = this.abstractAst.build_report(this._report) + + private _report (contracts, multipleContractsWithSameName) { + const warnings: any = [] + const hasModifiers = contracts.some((item) => item.modifiers.length > 0) + const callGraph = buildGlobalFuncCallGraph(contracts) + contracts.forEach((contract) => { + contract.functions.forEach((func) => { + func.changesState = this.checkIfChangesState( + getFullQuallyfiedFuncDefinitionIdent( + contract.node, + func.node, + func.parameters + ), + this.getContext( + callGraph, + contract, + func) + ) + }) + contract.functions.forEach((func) => { + 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.' : '' + 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}`, + location: func.src, + more: 'http://solidity.readthedocs.io/en/develop/security-considerations.html#re-entrancy' + }) + } + }) + }) + return warnings + } + + private getContext (callGraph, currentContract, func) { + return { callGraph: callGraph, currentContract: currentContract, stateVariables: this.getStateVariables(currentContract, func) } + } + + private getStateVariables (contract, func) { + return contract.stateVariables.concat(func.localVariables.filter(isStorageVariableDeclaration)) + } + + private isPotentialVulnerableFunction (func, context) { + let isPotentialVulnerable = false + let interaction = false + func.relevantNodes.forEach((node) => { + if (isInteraction(node)) { + interaction = true + } else if (interaction && (isWriteOnStateVariable(node, context.stateVariables) || this.isLocalCallWithStateChange(node, context))) { + isPotentialVulnerable = true + } + }) + return isPotentialVulnerable + } + + private isLocalCallWithStateChange (node, context) { + if (isLocalCallGraphRelevantNode(node)) { + const func = resolveCallGraphSymbol(context.callGraph, getFullQualifiedFunctionCallIdent(context.currentContract.node, node)) + return !func || (func && func.node.changesState) + } + return false + } + + private checkIfChangesState (startFuncName, context) { + return analyseCallGraph(context.callGraph, startFuncName, context, (node, context) => isWriteOnStateVariable(node, context.stateVariables)) + } +} + diff --git a/remix-analyzer/src/solidity-analyzer/modules/constantFunctions.js b/remix-analyzer/src/solidity-analyzer/modules/constantFunctions.js deleted file mode 100644 index 07d4190d92..0000000000 --- a/remix-analyzer/src/solidity-analyzer/modules/constantFunctions.js +++ /dev/null @@ -1,110 +0,0 @@ -const name = 'Constant functions: ' -const desc = 'Check for potentially constant functions' -const categories = require('./categories') -const common = require('./staticAnalysisCommon') -const fcallGraph = require('./functionCallGraph') -const AbstractAst = require('./abstractAstView') -const algo = require('./algorithmCategories') - -function constantFunctions () { - this.abstractAst = new AbstractAst() - - this.visit = this.abstractAst.build_visit( - (node) => common.isLowLevelCall(node) || - common.isTransfer(node) || - common.isExternalDirectCall(node) || - common.isEffect(node) || - common.isLocalCallGraphRelevantNode(node) || - common.isInlineAssembly(node) || - common.isNewExpression(node) || - common.isSelfdestructCall(node) || - common.isDeleteUnaryOperation(node) - ) - - this.report = this.abstractAst.build_report(report) -} - -constantFunctions.prototype.visit = function () { throw new Error('constantFunctions.js no visit function set upon construction') } - -constantFunctions.prototype.report = function () { throw new Error('constantFunctions.js no report function set upon construction') } - -function report (contracts, multipleContractsWithSameName) { - const warnings = [] - const hasModifiers = contracts.some((item) => item.modifiers.length > 0) - - const callGraph = fcallGraph.buildGlobalFuncCallGraph(contracts) - - contracts.forEach((contract) => { - contract.functions.forEach((func) => { - if (common.isPayableFunction(func.node) || common.isConstructor(func.node)) { - func.potentiallyshouldBeConst = false - } else { - func.potentiallyshouldBeConst = checkIfShouldBeConstant(common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters), - getContext(callGraph, contract, func)) - } - }) - - contract.functions.filter((func) => common.hasFunctionBody(func.node)).forEach((func) => { - if (common.isConstantFunction(func.node) !== func.potentiallyshouldBeConst) { - const funcName = common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters) - let comments = (hasModifiers) ? 'Note: Modifiers are currently not considered by this static analysis.' : '' - comments += (multipleContractsWithSameName) ? 'Note: Import aliases are currently not supported by this static analysis.' : '' - if (func.potentiallyshouldBeConst) { - warnings.push({ - warning: `${funcName} : Potentially should be constant but is not. ${comments}`, - location: func.src, - more: 'http://solidity.readthedocs.io/en/develop/contracts.html#constant-functions' - }) - } else { - warnings.push({ - warning: `${funcName} : Is constant but potentially should not be. ${comments}`, - location: func.src, - more: 'http://solidity.readthedocs.io/en/develop/contracts.html#constant-functions' - }) - } - } - }) - }) - - return warnings -} - -function getContext (callGraph, currentContract, func) { - return { callGraph: callGraph, currentContract: currentContract, stateVariables: getStateVariables(currentContract, func) } -} - -function getStateVariables (contract, func) { - return contract.stateVariables.concat(func.localVariables.filter(common.isStorageVariableDeclaration)) -} - -function checkIfShouldBeConstant (startFuncName, context) { - return !fcallGraph.analyseCallGraph(context.callGraph, startFuncName, context, isConstBreaker) -} - -function isConstBreaker (node, context) { - return common.isWriteOnStateVariable(node, context.stateVariables) || - common.isLowLevelCall(node) || - common.isTransfer(node) || - isCallOnNonConstExternalInterfaceFunction(node, context) || - common.isCallToNonConstLocalFunction(node) || - common.isInlineAssembly(node) || - common.isNewExpression(node) || - common.isSelfdestructCall(node) || - common.isDeleteUnaryOperation(node) -} - -function isCallOnNonConstExternalInterfaceFunction (node, context) { - if (common.isExternalDirectCall(node)) { - const func = fcallGraph.resolveCallGraphSymbol(context.callGraph, common.getFullQualifiedFunctionCallIdent(context.currentContract, node)) - return !func || (func && !common.isConstantFunction(func.node.node)) - } - return false -} - -module.exports = { - name: name, - description: desc, - category: categories.MISC, - algorithm: algo.HEURISTIC, - Module: constantFunctions -} diff --git a/remix-analyzer/src/solidity-analyzer/modules/constantFunctions.ts b/remix-analyzer/src/solidity-analyzer/modules/constantFunctions.ts new file mode 100644 index 0000000000..45c837381d --- /dev/null +++ b/remix-analyzer/src/solidity-analyzer/modules/constantFunctions.ts @@ -0,0 +1,114 @@ +import { default as category } from './categories' +import { isLowLevelCall, isTransfer, isExternalDirectCall, isEffect, isLocalCallGraphRelevantNode, + isInlineAssembly, isNewExpression, 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' + +export default class constantFunctions { + name = 'Constant functions: ' + desc = 'Check for potentially constant functions' + categories = category.MISC + algorithm = algorithm.HEURISTIC + Module = this + + abstractAst = new AbstractAst() + + visit = this.abstractAst.build_visit( + (node) => isLowLevelCall(node) || + isTransfer(node) || + isExternalDirectCall(node) || + isEffect(node) || + isLocalCallGraphRelevantNode(node) || + isInlineAssembly(node) || + isNewExpression(node) || + isSelfdestructCall(node) || + isDeleteUnaryOperation(node) + ) + + report = this.abstractAst.build_report(this._report) + + private _report (contracts, multipleContractsWithSameName) { + const warnings: any = [] + const hasModifiers = contracts.some((item) => item.modifiers.length > 0) + + const callGraph = buildGlobalFuncCallGraph(contracts) + + contracts.forEach((contract) => { + contract.functions.forEach((func) => { + if (isPayableFunction(func.node) || isConstructor(func.node)) { + func.potentiallyshouldBeConst = false + } else { + func.potentiallyshouldBeConst = this.checkIfShouldBeConstant( + getFullQuallyfiedFuncDefinitionIdent( + contract.node, + func.node, + func.parameters + ), + this.getContext( + callGraph, + contract, + func + ) + ) + } + }) + + contract.functions.filter((func) => hasFunctionBody(func.node)).forEach((func) => { + if (isConstantFunction(func.node) !== func.potentiallyshouldBeConst) { + const funcName = getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters) + let comments = (hasModifiers) ? 'Note: Modifiers are currently not considered by this static analysis.' : '' + comments += (multipleContractsWithSameName) ? 'Note: Import aliases are currently not supported by this static analysis.' : '' + if (func.potentiallyshouldBeConst) { + warnings.push({ + warning: `${funcName} : Potentially should be constant but is not. ${comments}`, + location: func.src, + more: 'http://solidity.readthedocs.io/en/develop/contracts.html#constant-functions' + }) + } else { + warnings.push({ + warning: `${funcName} : Is constant but potentially should not be. ${comments}`, + location: func.src, + more: 'http://solidity.readthedocs.io/en/develop/contracts.html#constant-functions' + }) + } + } + }) + }) + return warnings + } + + private getContext (callGraph, currentContract, func) { + return { callGraph: callGraph, currentContract: currentContract, stateVariables: this.getStateVariables(currentContract, func) } + } + + private getStateVariables (contract, func) { + return contract.stateVariables.concat(func.localVariables.filter(isStorageVariableDeclaration)) + } + + private checkIfShouldBeConstant (startFuncName, context) { + return !analyseCallGraph(context.callGraph, startFuncName, context, this.isConstBreaker) + } + + private isConstBreaker (node, context) { + return isWriteOnStateVariable(node, context.stateVariables) || + isLowLevelCall(node) || + isTransfer(node) || + this.isCallOnNonConstExternalInterfaceFunction(node, context) || + isCallToNonConstLocalFunction(node) || + isInlineAssembly(node) || + isNewExpression(node) || + isSelfdestructCall(node) || + isDeleteUnaryOperation(node) + } + + private isCallOnNonConstExternalInterfaceFunction (node, context) { + if (isExternalDirectCall(node)) { + const func = resolveCallGraphSymbol(context.callGraph, getFullQualifiedFunctionCallIdent(context.currentContract, node)) + return !func || (func && !isConstantFunction(func.node.node)) + } + return false + } +} diff --git a/remix-analyzer/src/solidity-analyzer/modules/deleteDynamicArrays.ts b/remix-analyzer/src/solidity-analyzer/modules/deleteDynamicArrays.ts index 9ddbc32ede..87efdb5a78 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/deleteDynamicArrays.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/deleteDynamicArrays.ts @@ -2,7 +2,7 @@ import { default as category } from './categories' import { isDeleteOfDynamicArray } from './staticAnalysisCommon' import { default as algorithm } from './algorithmCategories' -export class deleteDynamicArrays { +export default class deleteDynamicArrays { rel: any = [] name = 'Delete on dynamic Array: ' desc = 'Use require and appropriately' diff --git a/remix-analyzer/src/solidity-analyzer/modules/deleteFromDynamicArray.ts b/remix-analyzer/src/solidity-analyzer/modules/deleteFromDynamicArray.ts index 12de067073..1e7b612cdc 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/deleteFromDynamicArray.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/deleteFromDynamicArray.ts @@ -1,7 +1,7 @@ import { default as category } from './categories' import { isDeleteFromDynamicArray, isMappingIndexAccess } from './staticAnalysisCommon' -export class deleteFromDynamicArray { +export default class deleteFromDynamicArray { relevantNodes: any[] = [] name = 'Delete from dynamic Array: ' desc = 'Using delete on an array leaves a gap' diff --git a/remix-analyzer/src/solidity-analyzer/modules/erc20Decimals.js b/remix-analyzer/src/solidity-analyzer/modules/erc20Decimals.js deleted file mode 100644 index f2b2400bea..0000000000 --- a/remix-analyzer/src/solidity-analyzer/modules/erc20Decimals.js +++ /dev/null @@ -1,63 +0,0 @@ -const name = 'ERC20: ' -const desc = 'Decimal should be uint8' -const categories = require('./categories') -const common = require('./staticAnalysisCommon') -const AbstractAst = require('./abstractAstView') -const algo = require('./algorithmCategories') - -function erc20Decimals () { - this.abstractAst = new AbstractAst() - this.visit = this.abstractAst.build_visit( - (node) => false - ) - this.report = this.abstractAst.build_report(report) -} - -erc20Decimals.prototype.visit = function () { throw new Error('erc20Decimals.js no visit function set upon construction') } - -erc20Decimals.prototype.report = function () { throw new Error('erc20Decimals.js no report function set upon construction') } - -function report (contracts, multipleContractsWithSameName) { - const warnings = [] - - contracts.forEach((contract) => { - const contractAbiSignatures = contract.functions.map((f) => common.helpers.buildAbiSignature(common.getFunctionDefinitionName(f.node), f.parameters)) - - if (isERC20(contractAbiSignatures)) { - const decimalsVar = contract.stateVariables.filter((stateVar) => common.getDeclaredVariableName(stateVar) === 'decimals' && (common.getDeclaredVariableType(stateVar) !== 'uint8' || stateVar.attributes.visibility !== 'public')) - const decimalsFun = contract.functions.filter((f) => common.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')) - ) - ) - - if (decimalsVar.length > 0 || decimalsFun.length > 0) { - warnings.push({ - warning: 'ERC20 Contracts decimals function should have uint8 as return type', - location: null, - more: ' https://eips.ethereum.org/EIPS/eip-20' - }) - } - } - }) - - return warnings -} - -function isERC20 (funSignatures) { - return funSignatures.includes('totalSupply()') && - funSignatures.includes('balanceOf(address)') && - funSignatures.includes('transfer(address,uint256)') && - funSignatures.includes('transferFrom(address,address,uint256)') && - funSignatures.includes('approve(address,uint256)') && - funSignatures.includes('allowance(address,address)') -} - -module.exports = { - name: name, - description: desc, - category: categories.ERC, - algorithm: algo.EXACT, - Module: erc20Decimals -} diff --git a/remix-analyzer/src/solidity-analyzer/modules/erc20Decimals.ts b/remix-analyzer/src/solidity-analyzer/modules/erc20Decimals.ts new file mode 100644 index 0000000000..c4d4839634 --- /dev/null +++ b/remix-analyzer/src/solidity-analyzer/modules/erc20Decimals.ts @@ -0,0 +1,54 @@ +import { default as category } from './categories' +import { getFunctionDefinitionName, helpers, getDeclaredVariableName, getDeclaredVariableType } from './staticAnalysisCommon' +import { default as algorithm } from './algorithmCategories' +import AbstractAst from './abstractAstView' + +export default class erc20Decimals { + name = 'ERC20: ' + desc = 'Decimal should be uint8' + categories = category.ERC + algorithm = algorithm.EXACT + Module = this + + abstractAst = new AbstractAst() + visit = this.abstractAst.build_visit((node) => false) + report = this.abstractAst.build_report(this._report) + + private _report (contracts, multipleContractsWithSameName) { + const warnings: any = [] + + contracts.forEach((contract) => { + const contractAbiSignatures = contract.functions.map((f) => 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' && + ( + (f.returns.length === 0 || f.returns.length > 1) || + (f.returns.length === 1 && (f.returns[0].type !== 'uint8' || f.node.attributes.visibility !== 'public')) + ) + ) + + if (decimalsVar.length > 0 || decimalsFun.length > 0) { + warnings.push({ + warning: 'ERC20 Contracts decimals function should have uint8 as return type', + location: null, + more: ' https://eips.ethereum.org/EIPS/eip-20' + }) + } + } + }) + + return warnings + } + + private isERC20 (funSignatures) { + return funSignatures.includes('totalSupply()') && + funSignatures.includes('balanceOf(address)') && + funSignatures.includes('transfer(address,uint256)') && + funSignatures.includes('transferFrom(address,address,uint256)') && + funSignatures.includes('approve(address,uint256)') && + funSignatures.includes('allowance(address,address)') + } +} + diff --git a/remix-analyzer/src/solidity-analyzer/modules/etherTransferInLoop.ts b/remix-analyzer/src/solidity-analyzer/modules/etherTransferInLoop.ts index 769a18393c..97ba25d1fd 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/etherTransferInLoop.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/etherTransferInLoop.ts @@ -1,7 +1,7 @@ import { default as category } from './categories' import { isLoop, isBlock, getLoopBlockStartIndex, isExpressionStatement, isTransfer } from './staticAnalysisCommon' -export class etherTransferInLoop { +export default class etherTransferInLoop { relevantNodes: any[] = [] name = 'Ether transfer in a loop: ' desc = 'Avoid transferring Ether to multiple addresses in a loop' @@ -12,7 +12,7 @@ export class etherTransferInLoop { if (isLoop(node)) { let transferNodes = [] const loopBlockStartIndex = getLoopBlockStartIndex(node) - if (isBlock(node.children[loopBlockStartIndex])) { + if (loopBlockStartIndex && isBlock(node.children[loopBlockStartIndex])) { transferNodes = node.children[loopBlockStartIndex].children .filter(child => (isExpressionStatement(child) && child.children[0].name === 'FunctionCall' && diff --git a/remix-analyzer/src/solidity-analyzer/modules/forLoopIteratesOverDynamicArray.ts b/remix-analyzer/src/solidity-analyzer/modules/forLoopIteratesOverDynamicArray.ts index bca8cc4bae..5914b24da0 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/forLoopIteratesOverDynamicArray.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/forLoopIteratesOverDynamicArray.ts @@ -1,7 +1,7 @@ import { default as category } from './categories' const { isForLoop, isDynamicArrayLengthAccess, isBinaryOperation } = require('./staticAnalysisCommon') -export class forLoopIteratesOverDynamicArray { +export default class forLoopIteratesOverDynamicArray { relevantNodes: any[] = [] name = 'For loop iterates over dynamic array: ' desc = 'The number of \'for\' loop iterations depends on dynamic array\'s size' diff --git a/remix-analyzer/src/solidity-analyzer/modules/functionCallGraph.js b/remix-analyzer/src/solidity-analyzer/modules/functionCallGraph.ts similarity index 93% rename from remix-analyzer/src/solidity-analyzer/modules/functionCallGraph.js rename to remix-analyzer/src/solidity-analyzer/modules/functionCallGraph.ts index 3f1d03c483..92630318e1 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/functionCallGraph.js +++ b/remix-analyzer/src/solidity-analyzer/modules/functionCallGraph.ts @@ -12,7 +12,6 @@ function buildLocalFuncCallGraphInternal (functions, nodeFilter, extractNodeIden callGraph[extractFuncDefIdent(func)] = { node: func, calls: calls } }) - return callGraph } @@ -40,7 +39,7 @@ function buildLocalFuncCallGraphInternal (functions, nodeFilter, extractNodeIden * @contracts {list contracts} Expects as input the contract structure defined in abstractAstView.js * @return {map (string -> Contract Call Graph)} returns map from contract name to contract call graph */ -function buildGlobalFuncCallGraph (contracts) { +export function buildGlobalFuncCallGraph (contracts) { const callGraph = {} contracts.forEach((contract) => { const filterNodes = (node) => { return common.isLocalCallGraphRelevantNode(node) || common.isExternalDirectCall(node) } @@ -61,7 +60,7 @@ function buildGlobalFuncCallGraph (contracts) { * @nodeCheck {(ASTNode, context) -> bool} applied on every relevant node in the call graph * @return {bool} returns map from contract name to contract call graph */ -function analyseCallGraph (callGraph, funcName, context, nodeCheck) { +export function analyseCallGraph (callGraph, funcName, context, nodeCheck) { return analyseCallGraphInternal(callGraph, funcName, context, (a, b) => a || b, nodeCheck, {}) } @@ -75,7 +74,7 @@ function analyseCallGraphInternal (callGraph, funcName, context, combinator, nod current.calls.reduce((acc, val) => combinator(acc, analyseCallGraphInternal(callGraph, val, context, combinator, nodeCheck, visited)), false)) } -function resolveCallGraphSymbol (callGraph, funcName) { +export function resolveCallGraphSymbol (callGraph, funcName) { return resolveCallGraphSymbolInternal(callGraph, funcName, false) } @@ -106,9 +105,3 @@ function resolveCallGraphSymbolInternal (callGraph, funcName, silent) { if (current === undefined && !silent) console.log(`static analysis functionCallGraph.js: ${funcName} not found in function call graph.`) return current } - -module.exports = { - analyseCallGraph: analyseCallGraph, - buildGlobalFuncCallGraph: buildGlobalFuncCallGraph, - resolveCallGraphSymbol: resolveCallGraphSymbol -} diff --git a/remix-analyzer/src/solidity-analyzer/modules/gasCosts.js b/remix-analyzer/src/solidity-analyzer/modules/gasCosts.js deleted file mode 100644 index 15e0c20065..0000000000 --- a/remix-analyzer/src/solidity-analyzer/modules/gasCosts.js +++ /dev/null @@ -1,67 +0,0 @@ -const name = 'Gas costs: ' -const desc = 'Warn if the gas requirements of functions are too high.' -const categories = require('./categories') -const algo = require('./algorithmCategories') - -function gasCosts () { -} - -/** - * 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 ! -function visitContracts (contracts, cb) { - for (let file in contracts) { - for (let name in contracts[file]) { - if (cb({ name: name, object: contracts[file][name], file: file })) return - } - } -} - -gasCosts.prototype.report = function (compilationResults) { - const report = [] - visitContracts(compilationResults.contracts, (contract) => { - if ( - !contract.object.evm.gasEstimates || - !contract.object.evm.gasEstimates.external - ) { - return - } - const fallback = contract.object.evm.gasEstimates.external[''] - if (fallback !== undefined) { - if (fallback === null || 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.` - }) - } - } - - for (var functionName in contract.object.evm.gasEstimates.external) { - 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') { - 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. - Please avoid loops in your functions or actions that modify large areas of storage - (this includes clearing or copying arrays in storage)` - }) - } - } - }) - return report -} - -module.exports = { - name: name, - description: desc, - category: categories.GAS, - algorithm: algo.EXACT, - Module: gasCosts -} diff --git a/remix-analyzer/src/solidity-analyzer/modules/gasCosts.ts b/remix-analyzer/src/solidity-analyzer/modules/gasCosts.ts new file mode 100644 index 0000000000..26b299e301 --- /dev/null +++ b/remix-analyzer/src/solidity-analyzer/modules/gasCosts.ts @@ -0,0 +1,63 @@ +import { default as category } from './categories' +import { default as algorithm } from './algorithmCategories' + +export default class gasCosts { + name = 'Gas costs: ' + desc = 'Warn if the gas requirements of functions are too high.' + categories = category.GAS + algorithm = algorithm.EXACT + Module = this + + +/** + * 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) { + for (let file in contracts) { + for (let name in contracts[file]) { + if (cb({ name: name, object: contracts[file][name], file: file })) return + } + } + } + + report (compilationResults) { + const report: any[] = [] + this.visitContracts(compilationResults.contracts, (contract) => { + if ( + !contract.object.evm.gasEstimates || + !contract.object.evm.gasEstimates.external + ) { + return + } + const fallback = contract.object.evm.gasEstimates.external[''] + if (fallback !== undefined) { + if (fallback === null || 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.` + }) + } + } + + for (var functionName in contract.object.evm.gasEstimates.external) { + 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') { + 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. + Please avoid loops in your functions or actions that modify large areas of storage + (this includes clearing or copying arrays in storage)` + }) + } + } + }) + return report + } +} diff --git a/remix-analyzer/src/solidity-analyzer/modules/guardConditions.ts b/remix-analyzer/src/solidity-analyzer/modules/guardConditions.ts index 919b08d3dd..cd4ac6e247 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/guardConditions.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/guardConditions.ts @@ -2,7 +2,7 @@ import { default as category } from './categories' import { isRequireCall, isAssertCall } from './staticAnalysisCommon' import { default as algorithm } from './algorithmCategories' -export class guardConditions { +export default class guardConditions { guards: any[] = [] name = 'Guard Conditions: ' desc = 'Use require and appropriately' diff --git a/remix-analyzer/src/solidity-analyzer/modules/inlineAssembly.ts b/remix-analyzer/src/solidity-analyzer/modules/inlineAssembly.ts index 3b49193015..570c3c8541 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/inlineAssembly.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/inlineAssembly.ts @@ -2,7 +2,7 @@ import { default as category } from './categories' import { isInlineAssembly } from './staticAnalysisCommon' import { default as algorithm } from './algorithmCategories' -export class inlineAssembly { +export default class inlineAssembly { inlineAssNodes: any[] = [] name = 'Inline assembly: ' desc = 'Use of Inline Assembly' diff --git a/remix-analyzer/src/solidity-analyzer/modules/intDivisionTruncate.ts b/remix-analyzer/src/solidity-analyzer/modules/intDivisionTruncate.ts index aff9276ced..93d6c67fcb 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/intDivisionTruncate.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/intDivisionTruncate.ts @@ -2,7 +2,7 @@ import { default as category } from './categories' import { isIntDivision } from './staticAnalysisCommon' import { default as algorithm } from './algorithmCategories' -export class intDivitionTruncate { +export default class intDivitionTruncate { warningNodes: any[] = [] name = 'Data Trucated: ' desc = 'Division on int/uint values truncates the result.' diff --git a/remix-analyzer/src/solidity-analyzer/modules/list.js b/remix-analyzer/src/solidity-analyzer/modules/list.js deleted file mode 100644 index 6c7be2d630..0000000000 --- a/remix-analyzer/src/solidity-analyzer/modules/list.js +++ /dev/null @@ -1,21 +0,0 @@ -module.exports = [ - require('./txOrigin'), - require('./gasCosts'), - require('./thisLocal'), - require('./checksEffectsInteraction'), - require('./constantFunctions'), - require('./similarVariableNames.js'), - require('./inlineAssembly'), - require('./blockTimestamp'), - require('./lowLevelCalls'), - require('./blockBlockhash'), - require('./noReturn'), - require('./selfdestruct'), - require('./guardConditions'), - require('./deleteDynamicArrays'), - require('./assignAndCompare'), - require('./erc20Decimals'), - require('./stringBytesLength'), - require('./deleteFromDynamicArray'), - require('./forLoopIteratesOverDynamicArray') -] diff --git a/remix-analyzer/src/solidity-analyzer/modules/list.ts b/remix-analyzer/src/solidity-analyzer/modules/list.ts new file mode 100644 index 0000000000..3bb9c99c55 --- /dev/null +++ b/remix-analyzer/src/solidity-analyzer/modules/list.ts @@ -0,0 +1,41 @@ +import txOrigin from './txOrigin' +import gasCosts from './gasCosts' +import thisLocal from './thisLocal' +import checksEffectsInteraction from './checksEffectsInteraction' +import constantFunctions from './constantFunctions' +import similarVariableNames from './similarVariableNames' +import inlineAssembly from './inlineAssembly' +import blockTimestamp from './blockTimestamp' +import lowLevelCalls from './lowLevelCalls' +import blockBlockhash from './blockBlockhash' +import noReturn from './noReturn' +import selfdestruct from './selfdestruct' +import guardConditions from './guardConditions' +import deleteDynamicArrays from './deleteDynamicArrays' +import assignAndCompare from './assignAndCompare' +import erc20Decimals from './erc20Decimals' +import stringBytesLength from './stringBytesLength' +import deleteFromDynamicArray from './deleteFromDynamicArray' +import forLoopIteratesOverDynamicArray from './forLoopIteratesOverDynamicArray' + +export default [ + new txOrigin(), + new gasCosts(), + new thisLocal(), + new checksEffectsInteraction(), + new constantFunctions(), + new similarVariableNames(), + new inlineAssembly(), + new blockTimestamp(), + new lowLevelCalls(), + new blockBlockhash(), + new noReturn(), + new selfdestruct(), + new guardConditions(), + new deleteDynamicArrays(), + new assignAndCompare(), + new erc20Decimals(), + new stringBytesLength(), + new deleteFromDynamicArray(), + new forLoopIteratesOverDynamicArray() +] diff --git a/remix-analyzer/src/solidity-analyzer/modules/lowLevelCalls.ts b/remix-analyzer/src/solidity-analyzer/modules/lowLevelCalls.ts index 889c751ae5..a1fd6c17b0 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/lowLevelCalls.ts +++ b/remix-analyzer/src/solidity-analyzer/modules/lowLevelCalls.ts @@ -3,7 +3,7 @@ import { isLowLevelCallInst, isLowLevelCallInst050, isLowLevelCallcodeInst, isLo isLowLevelSendInst, isLowLevelSendInst050, isLLDelegatecallInst050, lowLevelCallTypes } from './staticAnalysisCommon' import { default as algorithm } from './algorithmCategories' -export class lowLevelCalls { +export default class lowLevelCalls { llcNodes: any[] = [] name = 'Low level calls: ' desc = 'Semantics maybe unclear' diff --git a/remix-analyzer/src/solidity-analyzer/modules/noReturn.js b/remix-analyzer/src/solidity-analyzer/modules/noReturn.js deleted file mode 100644 index 5c83c060fe..0000000000 --- a/remix-analyzer/src/solidity-analyzer/modules/noReturn.js +++ /dev/null @@ -1,75 +0,0 @@ -const name = 'no return: ' -const desc = 'Function with return type is not returning' -const categories = require('./categories') -const common = require('./staticAnalysisCommon') -const AbstractAst = require('./abstractAstView') -const algo = require('./algorithmCategories') - -function noReturn () { - this.abstractAst = new AbstractAst() - - this.visit = this.abstractAst.build_visit( - (node) => common.isReturn(node) || common.isAssignment(node) - ) - - this.report = this.abstractAst.build_report(report) -} - -noReturn.prototype.visit = function () { throw new Error('noReturn.js no visit function set upon construction') } - -noReturn.prototype.report = function () { throw new Error('noReturn.js no report function set upon construction') } - -function report (contracts, multipleContractsWithSameName) { - const warnings = [] - - contracts.forEach((contract) => { - contract.functions.filter((func) => common.hasFunctionBody(func.node)).forEach((func) => { - const funcName = common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters) - if (hasNamedAndUnnamedReturns(func)) { - warnings.push({ - warning: `${funcName}: Mixing of named and unnamed return parameters is not advised.`, - location: func.src - }) - } else if (shouldReturn(func) && !(hasReturnStatement(func) || (hasNamedReturns(func) && hasAssignToAllNamedReturns(func)))) { - warnings.push({ - warning: `${funcName}: Defines a return type but never explicitly returns a value.`, - location: func.src - }) - } - }) - }) - - return warnings -} - -function shouldReturn (func) { - return func.returns.length > 0 -} - -function hasReturnStatement (func) { - return func.relevantNodes.filter(common.isReturn).length > 0 -} - -function hasAssignToAllNamedReturns (func) { - const namedReturns = func.returns.filter((n) => n.name.length > 0).map((n) => n.name) - const assignedVars = func.relevantNodes.filter(common.isAssignment).map(common.getEffectedVariableName) - const diff = namedReturns.filter(e => !assignedVars.includes(e)) - return diff.length === 0 -} - -function hasNamedReturns (func) { - return func.returns.filter((n) => n.name.length > 0).length > 0 -} - -function hasNamedAndUnnamedReturns (func) { - return func.returns.filter((n) => n.name.length === 0).length > 0 && - hasNamedReturns(func) -} - -module.exports = { - name: name, - description: desc, - category: categories.MISC, - algorithm: algo.EXACT, - Module: noReturn -} diff --git a/remix-analyzer/src/solidity-analyzer/modules/noReturn.ts b/remix-analyzer/src/solidity-analyzer/modules/noReturn.ts new file mode 100644 index 0000000000..e689262bff --- /dev/null +++ b/remix-analyzer/src/solidity-analyzer/modules/noReturn.ts @@ -0,0 +1,66 @@ +import { default as category } from './categories' +import { isReturn, isAssignment, hasFunctionBody, getFullQuallyfiedFuncDefinitionIdent, getEffectedVariableName } from './staticAnalysisCommon' +import { default as algorithm } from './algorithmCategories' +import AbstractAst from './abstractAstView' + +export default class noReturn { + name = 'no return: ' + desc = 'Function with return type is not returning' + categories = category.MISC + algorithm = algorithm.EXACT + Module = this + + abstractAst = new AbstractAst() + + visit = this.abstractAst.build_visit( + (node) => isReturn(node) || isAssignment(node) + ) + + report = this.abstractAst.build_report(this._report) + private _report (contracts, multipleContractsWithSameName) { + const warnings: any[] = [] + + contracts.forEach((contract) => { + contract.functions.filter((func) => hasFunctionBody(func.node)).forEach((func) => { + const funcName = getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters) + if (this.hasNamedAndUnnamedReturns(func)) { + warnings.push({ + warning: `${funcName}: Mixing of named and unnamed return parameters is not advised.`, + location: func.src + }) + } else if (this.shouldReturn(func) && !(this.hasReturnStatement(func) || (this.hasNamedReturns(func) && this.hasAssignToAllNamedReturns(func)))) { + warnings.push({ + warning: `${funcName}: Defines a return type but never explicitly returns a value.`, + location: func.src + }) + } + }) + }) + + return warnings + } + + private shouldReturn (func) { + return func.returns.length > 0 + } + + private hasReturnStatement (func) { + return func.relevantNodes.filter(isReturn).length > 0 + } + + private hasAssignToAllNamedReturns (func) { + const namedReturns = func.returns.filter((n) => n.name.length > 0).map((n) => n.name) + const assignedVars = func.relevantNodes.filter(isAssignment).map(getEffectedVariableName) + const diff = namedReturns.filter(e => !assignedVars.includes(e)) + return diff.length === 0 + } + + private hasNamedReturns (func) { + return func.returns.filter((n) => n.name.length > 0).length > 0 + } + + private hasNamedAndUnnamedReturns (func) { + return func.returns.filter((n) => n.name.length === 0).length > 0 && + this.hasNamedReturns(func) + } +} diff --git a/remix-analyzer/src/solidity-analyzer/modules/selfdestruct.js b/remix-analyzer/src/solidity-analyzer/modules/selfdestruct.js deleted file mode 100644 index 50a5694d29..0000000000 --- a/remix-analyzer/src/solidity-analyzer/modules/selfdestruct.js +++ /dev/null @@ -1,59 +0,0 @@ -const name = 'Selfdestruct: ' -const desc = 'Be aware of caller contracts.' -const categories = require('./categories') -const common = require('./staticAnalysisCommon') -const AbstractAst = require('./abstractAstView') -const algo = require('./algorithmCategories') - -function selfdestruct () { - this.abstractAst = new AbstractAst() - - this.visit = this.abstractAst.build_visit( - (node) => common.isStatement(node) || - common.isSelfdestructCall(node) - ) - - this.report = this.abstractAst.build_report(report) -} - -selfdestruct.prototype.visit = function () { throw new Error('selfdestruct.js no visit function set upon construction') } - -selfdestruct.prototype.report = function () { throw new Error('selfdestruct.js no report function set upon construction') } - -function report (contracts, multipleContractsWithSameName) { - const warnings = [] - - contracts.forEach((contract) => { - contract.functions.forEach((func) => { - let hasSelf = false - func.relevantNodes.forEach((node) => { - if (common.isSelfdestructCall(node)) { - warnings.push({ - warning: 'Use of selfdestruct: can block calling contracts unexpectedly. Be especially careful if this contract is planned to be used by other contracts (i.e. library contracts, interactions). Selfdestruction of the callee contract can leave callers in an inoperable state.', - location: node.src, - more: 'https://paritytech.io/blog/security-alert.html' - }) - hasSelf = true - } - if (common.isStatement(node) && hasSelf) { - warnings.push({ - warning: 'Use of selfdestruct: No code after selfdestruct is executed. Selfdestruct is a terminal.', - location: node.src, - more: 'http://solidity.readthedocs.io/en/develop/introduction-to-smart-contracts.html#self-destruct' - }) - hasSelf = false - } - }) - }) - }) - - return warnings -} - -module.exports = { - name: name, - description: desc, - category: categories.SECURITY, - algorithm: algo.HEURISTIC, - Module: selfdestruct -} diff --git a/remix-analyzer/src/solidity-analyzer/modules/selfdestruct.ts b/remix-analyzer/src/solidity-analyzer/modules/selfdestruct.ts new file mode 100644 index 0000000000..8a1c453d8c --- /dev/null +++ b/remix-analyzer/src/solidity-analyzer/modules/selfdestruct.ts @@ -0,0 +1,50 @@ +import { default as category } from './categories' +import { isStatement, isSelfdestructCall } from './staticAnalysisCommon' +import { default as algorithm } from './algorithmCategories' +import AbstractAst from './abstractAstView' + +export default class selfdestruct { + name = 'Selfdestruct: ' + desc = 'Be aware of caller contracts.' + categories = category.SECURITY + algorithm = algorithm.HEURISTIC + Module = this + + abstractAst = new AbstractAst() + + visit = this.abstractAst.build_visit( + (node) => isStatement(node) || + isSelfdestructCall(node) + ) + + report = this.abstractAst.build_report(this._report) + private _report (contracts, multipleContractsWithSameName) { + const warnings: any[] = [] + + contracts.forEach((contract) => { + contract.functions.forEach((func) => { + let hasSelf = false + func.relevantNodes.forEach((node) => { + if (isSelfdestructCall(node)) { + warnings.push({ + warning: 'Use of selfdestruct: can block calling contracts unexpectedly. Be especially careful if this contract is planned to be used by other contracts (i.e. library contracts, interactions). Selfdestruction of the callee contract can leave callers in an inoperable state.', + location: node.src, + more: 'https://paritytech.io/blog/security-alert.html' + }) + hasSelf = true + } + if (isStatement(node) && hasSelf) { + warnings.push({ + warning: 'Use of selfdestruct: No code after selfdestruct is executed. Selfdestruct is a terminal.', + location: node.src, + more: 'http://solidity.readthedocs.io/en/develop/introduction-to-smart-contracts.html#self-destruct' + }) + hasSelf = false + } + }) + }) + }) + + return warnings + } +} diff --git a/remix-analyzer/src/solidity-analyzer/modules/similarVariableNames.js b/remix-analyzer/src/solidity-analyzer/modules/similarVariableNames.js deleted file mode 100644 index 5ee79ec661..0000000000 --- a/remix-analyzer/src/solidity-analyzer/modules/similarVariableNames.js +++ /dev/null @@ -1,87 +0,0 @@ -const name = 'Similar variable names: ' -const desc = 'Check if variable names are too similar' -const categories = require('./categories') -const common = require('./staticAnalysisCommon') -const AbstractAst = require('./abstractAstView') -const levenshtein = require('fast-levenshtein') -const remixLib = require('remix-lib') -const util = remixLib.util -const algo = require('./algorithmCategories') - -function similarVariableNames () { - this.abstractAst = new AbstractAst() - - this.visit = this.abstractAst.build_visit( - (node) => false - ) - - this.report = this.abstractAst.build_report(report) -} - -similarVariableNames.prototype.visit = function () { throw new Error('similarVariableNames.js no visit function set upon construction') } - -similarVariableNames.prototype.report = function () { throw new Error('similarVariableNames.js no report function set upon construction') } - -function report (contracts, multipleContractsWithSameName) { - const warnings = [] - const hasModifiers = contracts.some((item) => item.modifiers.length > 0) - - contracts.forEach((contract) => { - contract.functions.forEach((func) => { - const funcName = common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters) - let hasModifiersComments = '' - if (hasModifiers) { - hasModifiersComments = 'Note: Modifiers are currently not considered by this static analysis.' - } - let multipleContractsWithSameNameComments = '' - if (multipleContractsWithSameName) { - multipleContractsWithSameNameComments = 'Note: Import aliases are currently not supported by this static analysis.' - } - - const vars = getFunctionVariables(contract, func).map(common.getDeclaredVariableName) - - findSimilarVarNames(vars).map((sim) => { - warnings.push({ - warning: `${funcName} : Variables have very similar names ${sim.var1} and ${sim.var2}. ${hasModifiersComments} ${multipleContractsWithSameNameComments}`, - location: func.src - }) - }) - }) - }) - - return warnings -} - -function findSimilarVarNames (vars) { - const similar = [] - const comb = {} - vars.map((varName1) => vars.map((varName2) => { - if (varName1.length > 1 && varName2.length > 1 && varName2 !== varName1 && !isCommonPrefixedVersion(varName1, varName2) && !isCommonNrSuffixVersion(varName1, varName2) && !(comb[varName1 + ';' + varName2] || comb[varName2 + ';' + varName1])) { - comb[varName1 + ';' + varName2] = true - const distance = levenshtein.get(varName1, varName2) - if (distance <= 2) similar.push({ var1: varName1, var2: varName2, distance: distance }) - } - })) - return similar -} - -function isCommonPrefixedVersion (varName1, varName2) { - return (varName1.startsWith('_') && varName1.slice(1) === varName2) || (varName2.startsWith('_') && varName2.slice(1) === varName1) -} - -function isCommonNrSuffixVersion (varName1, varName2) { - const ref = '^' + util.escapeRegExp(varName1.slice(0, -1)) + '[0-9]*$' - return varName2.match(ref) != null -} - -function getFunctionVariables (contract, func) { - return contract.stateVariables.concat(func.localVariables) -} - -module.exports = { - name: name, - description: desc, - category: categories.MISC, - algorithm: algo.EXACT, - Module: similarVariableNames -} diff --git a/remix-analyzer/src/solidity-analyzer/modules/similarVariableNames.ts b/remix-analyzer/src/solidity-analyzer/modules/similarVariableNames.ts new file mode 100644 index 0000000000..9c2f10939e --- /dev/null +++ b/remix-analyzer/src/solidity-analyzer/modules/similarVariableNames.ts @@ -0,0 +1,77 @@ +import { default as category } from './categories' +import { getDeclaredVariableName, getFullQuallyfiedFuncDefinitionIdent } from './staticAnalysisCommon' +import { default as algorithm } from './algorithmCategories' +import AbstractAst from './abstractAstView' +import { get } from 'fast-levenshtein' +import { util } from 'remix-lib' + +export default class similarVariableNames { + name = 'Similar variable names: ' + desc = 'Check if variable names are too similar' + abstractAst = new AbstractAst() + categories = category.MISC + algorithm = algorithm.EXACT + Module = this + + visit = this.abstractAst.build_visit( + (node) => false + ) + + report = this.abstractAst.build_report(this._report) + + private _report (contracts, multipleContractsWithSameName) { + const warnings: any[] = [] + const hasModifiers = contracts.some((item) => item.modifiers.length > 0) + + contracts.forEach((contract) => { + contract.functions.forEach((func) => { + const funcName = getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters) + let hasModifiersComments = '' + if (hasModifiers) { + hasModifiersComments = 'Note: Modifiers are currently not considered by this static analysis.' + } + let multipleContractsWithSameNameComments = '' + if (multipleContractsWithSameName) { + multipleContractsWithSameNameComments = 'Note: Import aliases are currently not supported by this static analysis.' + } + + const vars = 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}`, + location: func.src + }) + }) + }) + }) + + return warnings + } + + private findSimilarVarNames (vars) { + const similar: any[] = [] + const comb = {} + vars.map((varName1) => vars.map((varName2) => { + if (varName1.length > 1 && varName2.length > 1 && varName2 !== varName1 && !this.isCommonPrefixedVersion(varName1, varName2) && !this.isCommonNrSuffixVersion(varName1, varName2) && !(comb[varName1 + ';' + varName2] || comb[varName2 + ';' + varName1])) { + comb[varName1 + ';' + varName2] = true + const distance = get(varName1, varName2) + if (distance <= 2) similar.push({ var1: varName1, var2: varName2, distance: distance }) + } + })) + return similar + } + + private isCommonPrefixedVersion (varName1, varName2) { + return (varName1.startsWith('_') && varName1.slice(1) === varName2) || (varName2.startsWith('_') && varName2.slice(1) === varName1) + } + + private isCommonNrSuffixVersion (varName1, varName2) { + const ref = '^' + util.escapeRegExp(varName1.slice(0, -1)) + '[0-9]*$' + return varName2.match(ref) != null + } + + private getFunctionVariables (contract, func) { + return contract.stateVariables.concat(func.localVariables) + } +} diff --git a/remix-analyzer/src/solidity-analyzer/modules/staticAnalysisCommon.js b/remix-analyzer/src/solidity-analyzer/modules/staticAnalysisCommon.ts similarity index 87% rename from remix-analyzer/src/solidity-analyzer/modules/staticAnalysisCommon.js rename to remix-analyzer/src/solidity-analyzer/modules/staticAnalysisCommon.ts index 7881e27a80..717ef0274e 100644 --- a/remix-analyzer/src/solidity-analyzer/modules/staticAnalysisCommon.js +++ b/remix-analyzer/src/solidity-analyzer/modules/staticAnalysisCommon.ts @@ -363,7 +363,9 @@ function getFunctionCallTypeParameterType (func) { */ function getLibraryCallContractName (funcCall) { if (!isLibraryCall(funcCall)) throw new Error('staticAnalysisCommon.js: not an this library call Node') - return new RegExp(basicRegex.LIBRARYTYPE).exec(funcCall.children[0].attributes.type)[1] + const types = new RegExp(basicRegex.LIBRARYTYPE).exec(funcCall.children[0].attributes.type) + if(types) + return types[1] } /** @@ -807,7 +809,7 @@ function isExternalDirectCall (node) { function isNowAccess (node) { return nodeType(node, exactMatch(nodeTypes.IDENTIFIER)) && expressionType(node, exactMatch(basicTypes.UINT)) && - name(node, exactMatch('now')) + memName(node, exactMatch('now')) } /** @@ -969,7 +971,7 @@ function isStringToBytesConversion (node) { function isExplicitCast (node, castFromType, castToType) { return nodeType(node, exactMatch(nodeTypes.FUNCTIONCALL)) && nrOfChildren(node, 2) && - nodeType(node.children[0], exactMatch(nodeTypes.ELEMENTARYTYPENAMEEXPRESSION)) && name(node.children[0], castToType) && + nodeType(node.children[0], exactMatch(nodeTypes.ELEMENTARYTYPENAMEEXPRESSION)) && memName(node.children[0], castToType) && nodeType(node.children[1], exactMatch(nodeTypes.IDENTIFIER)) && expressionType(node.children[1], castFromType) } @@ -1002,9 +1004,9 @@ function isForLoop (node) { function isMemberAccess (node, retType, accessor, accessorType, memberName) { return nodeType(node, exactMatch(nodeTypes.MEMBERACCESS)) && expressionType(node, retType) && - name(node, memberName) && + memName(node, memberName) && nrOfChildren(node, 1) && - name(node.children[0], accessor) && + memName(node.children[0], accessor) && expressionType(node.children[0], accessorType) } @@ -1030,9 +1032,9 @@ function nodeType (node, typeRegex) { return (node && !typeRegex) || (node && new RegExp(typeRegex).test(node.name)) } -function name (node, nameRegex) { - const regex = new RegExp(nameRegex) - return (node && !nameRegex) || (node && node.attributes && (regex.test(node.attributes.value) || regex.test(node.attributes.member_name))) +function memName (node, memNameRegex) { + const regex = new RegExp(memNameRegex) + return (node && !memNameRegex) || (node && node.attributes && (regex.test(node.attributes.value) || regex.test(node.attributes.member_name))) } function operator (node, opRegex) { @@ -1045,10 +1047,10 @@ function exactMatch (regexStr) { return '^' + regexStr + '$' } -function matches () { - const args = [] - for (let k = 0; k < arguments.length; k++) { - args.push(arguments[k]) +function matches (...fnArgs) { + const args: any[] = [] + for (let k = 0; k < fnArgs.length; k++) { + args.push(fnArgs[k]) } return '(' + args.join('|') + ')' } @@ -1061,7 +1063,7 @@ function matches () { * list of return type names * @return {Boolean} isPayable */ -function buildFunctionSignature (paramTypes, returnTypes, isPayable, additionalMods) { +function buildFunctionSignature (paramTypes, returnTypes, isPayable, additionalMods?) { return 'function (' + util.concatWithSeperator(paramTypes, ',') + ')' + ((isPayable) ? ' payable' : '') + ((additionalMods) ? ' ' + additionalMods : '') + ((returnTypes.length) ? ' returns (' + util.concatWithSeperator(returnTypes, ',') + ')' : '') } @@ -1088,117 +1090,119 @@ function findFirstSubNodeLTR (node, type) { return null } -module.exports = { +const helpers = { + nrOfChildren, + minNrOfChildren, + expressionType, + nodeType, + memName, + operator, + buildFunctionSignature, + buildAbiSignature +} + +export { // #################### Trivial Getters - getType: getType, + getType, // #################### Complex Getters - getThisLocalCallName: getThisLocalCallName, - getSuperLocalCallName: getSuperLocalCallName, - getFunctionCallType: getFunctionCallType, - getContractName: getContractName, - getEffectedVariableName: getEffectedVariableName, - getDeclaredVariableName: getDeclaredVariableName, - getDeclaredVariableType: getDeclaredVariableType, - getLocalCallName: getLocalCallName, - getInheritsFromName: getInheritsFromName, - getExternalDirectCallContractName: getExternalDirectCallContractName, - getThisLocalCallContractName: getThisLocalCallContractName, - getExternalDirectCallMemberName: getExternalDirectCallMemberName, - getFunctionDefinitionName: getFunctionDefinitionName, - getFunctionCallTypeParameterType: getFunctionCallTypeParameterType, - getLibraryCallContractName: getLibraryCallContractName, - getLibraryCallMemberName: getLibraryCallMemberName, - getFullQualifiedFunctionCallIdent: getFullQualifiedFunctionCallIdent, - getFullQuallyfiedFuncDefinitionIdent: getFullQuallyfiedFuncDefinitionIdent, - getStateVariableDeclarationsFormContractNode: getStateVariableDeclarationsFormContractNode, - getFunctionOrModifierDefinitionParameterPart: getFunctionOrModifierDefinitionParameterPart, - getFunctionOrModifierDefinitionReturnParameterPart: getFunctionOrModifierDefinitionReturnParameterPart, - getUnAssignedTopLevelBinOps: getUnAssignedTopLevelBinOps, - getLoopBlockStartIndex: getLoopBlockStartIndex, + getThisLocalCallName, + getSuperLocalCallName, + getFunctionCallType, + getContractName, + getEffectedVariableName, + getDeclaredVariableName, + getDeclaredVariableType, + getLocalCallName, + getInheritsFromName, + getExternalDirectCallContractName, + getThisLocalCallContractName, + getExternalDirectCallMemberName, + getFunctionDefinitionName, + getFunctionCallTypeParameterType, + getLibraryCallContractName, + getLibraryCallMemberName, + getFullQualifiedFunctionCallIdent, + getFullQuallyfiedFuncDefinitionIdent, + getStateVariableDeclarationsFormContractNode, + getFunctionOrModifierDefinitionParameterPart, + getFunctionOrModifierDefinitionReturnParameterPart, + getUnAssignedTopLevelBinOps, + getLoopBlockStartIndex, // #################### Complex Node Identification - isDeleteOfDynamicArray: isDeleteOfDynamicArray, - isDeleteFromDynamicArray: isDeleteFromDynamicArray, - isAbiNamespaceCall: isAbiNamespaceCall, - isSpecialVariableAccess: isSpecialVariableAccess, - isDynamicArrayAccess: isDynamicArrayAccess, - isDynamicArrayLengthAccess: isDynamicArrayLengthAccess, - isIndexAccess: isIndexAccess, - isMappingIndexAccess: isMappingIndexAccess, - isSubScopeWithTopLevelUnAssignedBinOp: isSubScopeWithTopLevelUnAssignedBinOp, - hasFunctionBody: hasFunctionBody, - isInteraction: isInteraction, - isEffect: isEffect, - isNowAccess: isNowAccess, - isBlockTimestampAccess: isBlockTimestampAccess, - isBlockBlockHashAccess: isBlockBlockHashAccess, - isThisLocalCall: isThisLocalCall, - isSuperLocalCall: isSuperLocalCall, - isLibraryCall: isLibraryCall, - isLocalCallGraphRelevantNode: isLocalCallGraphRelevantNode, - isLocalCall: isLocalCall, - isWriteOnStateVariable: isWriteOnStateVariable, - isStateVariable: isStateVariable, - isTransfer: isTransfer, - isLowLevelCall: isLowLevelCall, - isLowLevelCallInst: isLLCall, - isLowLevelCallInst050: isLLCall050, - isLowLevelSendInst050: isLLSend050, - isLLDelegatecallInst050: isLLDelegatecall050, - isLowLevelCallcodeInst: isLLCallcode, - isLowLevelDelegatecallInst: isLLDelegatecall, - isLowLevelSendInst: isLLSend, - isExternalDirectCall: isExternalDirectCall, - isFullyImplementedContract: isFullyImplementedContract, - isLibrary: isLibrary, - isCallToNonConstLocalFunction: isCallToNonConstLocalFunction, - isPlusPlusUnaryOperation: isPlusPlusUnaryOperation, - isMinusMinusUnaryOperation: isMinusMinusUnaryOperation, - isBuiltinFunctionCall: isBuiltinFunctionCall, - isSelfdestructCall: isSelfdestructCall, - isAssertCall: isAssertCall, - isRequireCall: isRequireCall, - isIntDivision: isIntDivision, - isStringToBytesConversion: isStringToBytesConversion, - isBytesLengthCheck: isBytesLengthCheck, - isLoop: isLoop, - isForLoop: isForLoop, + isDeleteOfDynamicArray, + isDeleteFromDynamicArray, + isAbiNamespaceCall, + isSpecialVariableAccess, + isDynamicArrayAccess, + isDynamicArrayLengthAccess, + isIndexAccess, + isMappingIndexAccess, + isSubScopeWithTopLevelUnAssignedBinOp, + hasFunctionBody, + isInteraction, + isEffect, + isNowAccess, + isBlockTimestampAccess, + isBlockBlockHashAccess, + isThisLocalCall, + isSuperLocalCall, + isLibraryCall, + isLocalCallGraphRelevantNode, + isLocalCall, + isWriteOnStateVariable, + isStateVariable, + isTransfer, + isLowLevelCall, + isLLCall as isLowLevelCallInst, + isLLCall050 as isLowLevelCallInst050, + isLLSend050 as isLowLevelSendInst050, + isLLDelegatecall050 as isLLDelegatecallInst050, + isLLCallcode as isLowLevelCallcodeInst, + isLLDelegatecall as isLowLevelDelegatecallInst, + isLLSend as isLowLevelSendInst, + isExternalDirectCall, + isFullyImplementedContract, + isLibrary, + isCallToNonConstLocalFunction, + isPlusPlusUnaryOperation, + isMinusMinusUnaryOperation, + isBuiltinFunctionCall, + isSelfdestructCall, + isAssertCall, + isRequireCall, + isIntDivision, + isStringToBytesConversion, + isBytesLengthCheck, + isLoop, + isForLoop, // #################### Trivial Node Identification - isDeleteUnaryOperation: isDeleteUnaryOperation, - isFunctionDefinition: isFunctionDefinition, - isModifierDefinition: isModifierDefinition, - isInheritanceSpecifier: isInheritanceSpecifier, - isModifierInvocation: isModifierInvocation, - isVariableDeclaration: isVariableDeclaration, - isStorageVariableDeclaration: isStorageVariableDeclaration, - isAssignment: isAssignment, - isContractDefinition: isContractDefinition, - isConstantFunction: isConstantFunction, - isPayableFunction: isPayableFunction, - isConstructor: isConstructor, - isInlineAssembly: isInlineAssembly, - isNewExpression: isNewExpression, - isReturn: isReturn, - isStatement: isStatement, - isExpressionStatement: isExpressionStatement, - isBlock: isBlock, - isBinaryOperation: isBinaryOperation, + isDeleteUnaryOperation, + isFunctionDefinition, + isModifierDefinition, + isInheritanceSpecifier, + isModifierInvocation, + isVariableDeclaration, + isStorageVariableDeclaration, + isAssignment, + isContractDefinition, + isConstantFunction, + isPayableFunction, + isConstructor, + isInlineAssembly, + isNewExpression, + isReturn, + isStatement, + isExpressionStatement, + isBlock, + isBinaryOperation, // #################### Constants - nodeTypes: nodeTypes, - basicTypes: basicTypes, - basicFunctionTypes: basicFunctionTypes, - lowLevelCallTypes: lowLevelCallTypes, - specialVariables: specialVariables, - helpers: { - nrOfChildren: nrOfChildren, - minNrOfChildren: minNrOfChildren, - expressionType: expressionType, - nodeType: nodeType, - name: name, - operator: operator, - buildFunctionSignature: buildFunctionSignature, - buildAbiSignature: buildAbiSignature - } + nodeTypes, + basicTypes, + basicFunctionTypes, + lowLevelCallTypes, + specialVariables, + helpers } diff --git a/remix-analyzer/src/solidity-analyzer/modules/stringBytesLength.js b/remix-analyzer/src/solidity-analyzer/modules/stringBytesLength.js deleted file mode 100644 index a28b3d255a..0000000000 --- a/remix-analyzer/src/solidity-analyzer/modules/stringBytesLength.js +++ /dev/null @@ -1,33 +0,0 @@ -const name = 'String Length: ' -const desc = 'Bytes length != String length' -const categories = require('./categories') -const common = require('./staticAnalysisCommon') - -function stringBytesLength () { - this.stringToBytesConversions = [] - this.bytesLengthChecks = [] -} - -stringBytesLength.prototype.visit = function (node) { - if (common.isStringToBytesConversion(node)) this.stringToBytesConversions.push(node) - else if (common.isBytesLengthCheck(node)) this.bytesLengthChecks.push(node) -} - -stringBytesLength.prototype.report = function (compilationResults) { - if (this.stringToBytesConversions.length > 0 && this.bytesLengthChecks.length > 0) { - return [{ - warning: 'Bytes and string length are not the same since strings are assumed to be UTF-8 encoded (according to the ABI defintion) therefore one character is not nessesarily encoded in one byte of data.', - location: this.bytesLengthChecks[0].src, - more: 'https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#argument-encoding' - }] - } else { - return [] - } -} - -module.exports = { - name: name, - description: desc, - category: categories.MISC, - Module: stringBytesLength -} diff --git a/remix-analyzer/src/solidity-analyzer/modules/stringBytesLength.ts b/remix-analyzer/src/solidity-analyzer/modules/stringBytesLength.ts new file mode 100644 index 0000000000..b38ae34af6 --- /dev/null +++ b/remix-analyzer/src/solidity-analyzer/modules/stringBytesLength.ts @@ -0,0 +1,30 @@ +import { default as category } from './categories' +import { isStringToBytesConversion, isBytesLengthCheck } from './staticAnalysisCommon' + +export default class stringBytesLength { + name = 'String Length: ' + desc = 'Bytes length != String length' + categories = category.MISC + Module = this + + stringToBytesConversions: any[] = [] + bytesLengthChecks: any[] = [] + + + visit (node) { + if (isStringToBytesConversion(node)) this.stringToBytesConversions.push(node) + else if (isBytesLengthCheck(node)) this.bytesLengthChecks.push(node) + } + + report (compilationResults) { + if (this.stringToBytesConversions.length > 0 && this.bytesLengthChecks.length > 0) { + return [{ + warning: 'Bytes and string length are not the same since strings are assumed to be UTF-8 encoded (according to the ABI defintion) therefore one character is not nessesarily encoded in one byte of data.', + location: this.bytesLengthChecks[0].src, + more: 'https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#argument-encoding' + }] + } else { + return [] + } + } +} diff --git a/remix-analyzer/src/solidity-analyzer/modules/thisLocal.js b/remix-analyzer/src/solidity-analyzer/modules/thisLocal.js deleted file mode 100644 index c5f2ae38de..0000000000 --- a/remix-analyzer/src/solidity-analyzer/modules/thisLocal.js +++ /dev/null @@ -1,31 +0,0 @@ -const name = 'This on local calls: ' -const desc = 'Invocation of local functions via this' -const categories = require('./categories') -const common = require('./staticAnalysisCommon') -const algo = require('./algorithmCategories') - -function thisLocal () { - this.warningNodes = [] -} - -thisLocal.prototype.visit = function (node) { - if (common.isThisLocalCall(node)) this.warningNodes.push(node) -} - -thisLocal.prototype.report = function (compilationResults) { - return this.warningNodes.map(function (item, i) { - return { - warning: 'Use of "this" for local functions: Never use this to call functions in the same contract, it only consumes more gas than normal local calls.', - location: item.src, - more: 'http://solidity.readthedocs.io/en/develop/control-structures.html#external-function-calls' - } - }) -} - -module.exports = { - name: name, - description: desc, - category: categories.GAS, - algorithm: algo.EXACT, - Module: thisLocal -} diff --git a/remix-analyzer/src/solidity-analyzer/modules/thisLocal.ts b/remix-analyzer/src/solidity-analyzer/modules/thisLocal.ts new file mode 100644 index 0000000000..7d83318a09 --- /dev/null +++ b/remix-analyzer/src/solidity-analyzer/modules/thisLocal.ts @@ -0,0 +1,26 @@ +import { default as category } from './categories' +import { isThisLocalCall } from './staticAnalysisCommon' +import { default as algorithm } from './algorithmCategories' + +export default class thisLocal { + warningNodes: any[] = [] + name = 'This on local calls: ' + desc = 'Invocation of local functions via this' + categories = category.GAS + algorithm = algorithm.EXACT + Module = this + + visit (node) { + if (isThisLocalCall(node)) this.warningNodes.push(node) + } + + report (compilationResults) { + return this.warningNodes.map(function (item, i) { + return { + warning: 'Use of "this" for local functions: Never use this to call functions in the same contract, it only consumes more gas than normal local calls.', + location: item.src, + more: 'http://solidity.readthedocs.io/en/develop/control-structures.html#external-function-calls' + } + }) + } +} diff --git a/remix-analyzer/src/solidity-analyzer/modules/txOrigin.js b/remix-analyzer/src/solidity-analyzer/modules/txOrigin.js deleted file mode 100644 index f76b54fbd0..0000000000 --- a/remix-analyzer/src/solidity-analyzer/modules/txOrigin.js +++ /dev/null @@ -1,37 +0,0 @@ -const name = 'Transaction origin: ' -const desc = 'Warn if tx.origin is used' -const categories = require('./categories') -const algo = require('./algorithmCategories') - -function txOrigin () { - this.txOriginNodes = [] -} - -txOrigin.prototype.visit = function (node) { - if (node.name === 'MemberAccess' && - node.attributes.member_name === 'origin' && - (node.attributes.type === 'address' || node.attributes.type === 'address payable') && - node.children && node.children.length && - node.children[0].attributes.type === 'tx' && - node.children[0].attributes.value === 'tx') { - this.txOriginNodes.push(node) - } -} - -txOrigin.prototype.report = function () { - return this.txOriginNodes.map((item, i) => { - return { - warning: `Use of tx.origin: "tx.origin" is useful only in very exceptional cases. - If you use it for authentication, you usually want to replace it by "msg.sender", because otherwise any contract you call can act on your behalf.`, - location: item.src - } - }) -} - -module.exports = { - name: name, - description: desc, - category: categories.SECURITY, - algorithm: algo.EXACT, - Module: txOrigin -} diff --git a/remix-analyzer/src/solidity-analyzer/modules/txOrigin.ts b/remix-analyzer/src/solidity-analyzer/modules/txOrigin.ts new file mode 100644 index 0000000000..c56089e2ec --- /dev/null +++ b/remix-analyzer/src/solidity-analyzer/modules/txOrigin.ts @@ -0,0 +1,32 @@ +import { default as category } from './categories' +import { default as algorithm } from './algorithmCategories' + +export default class txOrigin { + txOriginNodes: any[] = [] + name = 'Transaction origin: ' + desc = 'Warn if tx.origin is used' + categories = category.SECURITY + algorithm = algorithm.EXACT + Module = this + + visit (node) { + if (node.name === 'MemberAccess' && + node.attributes.member_name === 'origin' && + (node.attributes.type === 'address' || node.attributes.type === 'address payable') && + node.children && node.children.length && + node.children[0].attributes.type === 'tx' && + node.children[0].attributes.value === 'tx') { + this.txOriginNodes.push(node) + } + } + + report () { + return this.txOriginNodes.map((item, i) => { + return { + warning: `Use of tx.origin: "tx.origin" is useful only in very exceptional cases. + If you use it for authentication, you usually want to replace it by "msg.sender", because otherwise any contract you call can act on your behalf.`, + location: item.src + } + }) + } +} diff --git a/remix-analyzer/test/analysis/staticAnalysisCommon-test.js b/remix-analyzer/test/analysis/staticAnalysisCommon-test.js index d924b3cf52..633753cb88 100644 --- a/remix-analyzer/test/analysis/staticAnalysisCommon-test.js +++ b/remix-analyzer/test/analysis/staticAnalysisCommon-test.js @@ -1,5 +1,5 @@ var test = require('tape') -var common = require('../../src/solidity-analyzer/modules/staticAnalysisCommon') +var common = require('../../dist/src/solidity-analyzer/modules/staticAnalysisCommon') var { localCall, thisLocalCall, libCall, externalDirect, superLocal, assignment, inlineAssembly, forLoopNode, whileLoopNode, doWhileLoopNode, stateVariableContractNode, functionDefinition, fullyQualifiedFunctionDefinition, selfdestruct, storageVariableNodes, @@ -56,11 +56,11 @@ test('staticAnalysisCommon.helpers.name', function (t) { var node = { attributes: { value: 'now' } } var node2 = { attributes: { member_name: 'call' } } - t.ok(common.helpers.name(node, 'now'), 'should work for values') - t.ok(common.helpers.name(node2, 'call'), 'should work for member_name') - t.ok(common.helpers.name(node2, '.all'), 'regex should work') + t.ok(common.helpers.memName(node, 'now'), 'should work for values') + t.ok(common.helpers.memName(node2, 'call'), 'should work for member_name') + t.ok(common.helpers.memName(node2, '.all'), 'regex should work') - lowlevelAccessersCommon(t, common.helpers.name, node) + lowlevelAccessersCommon(t, common.helpers.memName, node) }) test('staticAnalysisCommon.helpers.operator', function (t) { diff --git a/remix-analyzer/test/analysis/staticAnalysisIntegration-test-0.4.24.js b/remix-analyzer/test/analysis/staticAnalysisIntegration-test-0.4.24.js index 284f9423f3..6fe703a08f 100644 --- a/remix-analyzer/test/analysis/staticAnalysisIntegration-test-0.4.24.js +++ b/remix-analyzer/test/analysis/staticAnalysisIntegration-test-0.4.24.js @@ -1,7 +1,7 @@ var test = require('tape') var remixLib = require('remix-lib') -var StatRunner = require('../../src/solidity-analyzer') +var StatRunner = require('../../dist/src/solidity-analyzer') var compilerInput = remixLib.helpers.compiler.compilerInput const niv = require('npm-install-version') @@ -51,7 +51,7 @@ testFiles.forEach((fileName) => { test('Integration test thisLocal.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/thisLocal') + var module = require('../../dist/src/solidity-analyzer/modules/thisLocal') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -90,7 +90,7 @@ test('Integration test thisLocal.js', function (t) { test('Integration test checksEffectsInteraction.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/checksEffectsInteraction') + var module = require('../../dist/src/solidity-analyzer/modules/checksEffectsInteraction') var lengthCheck = { 'KingOfTheEtherThrone.sol': 1, @@ -129,7 +129,7 @@ test('Integration test checksEffectsInteraction.js', function (t) { test('Integration test constantFunctions.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/constantFunctions') + var module = require('../../dist/src/solidity-analyzer/modules/constantFunctions') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -168,7 +168,7 @@ test('Integration test constantFunctions.js', function (t) { test('Integration test inlineAssembly.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/inlineAssembly') + var module = require('../../dist/src/solidity-analyzer/modules/inlineAssembly') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -207,7 +207,7 @@ test('Integration test inlineAssembly.js', function (t) { test('Integration test txOrigin.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/txOrigin') + var module = require('../../dist/src/solidity-analyzer/modules/txOrigin') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -246,7 +246,7 @@ test('Integration test txOrigin.js', function (t) { test('Integration test gasCosts.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/gasCosts') + var module = require('../../dist/src/solidity-analyzer/modules/gasCosts') var lengthCheck = { 'KingOfTheEtherThrone.sol': 2, @@ -285,7 +285,7 @@ test('Integration test gasCosts.js', function (t) { test('Integration test similarVariableNames.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/similarVariableNames') + var module = require('../../dist/src/solidity-analyzer/modules/similarVariableNames') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -324,7 +324,7 @@ test('Integration test similarVariableNames.js', function (t) { test('Integration test inlineAssembly.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/inlineAssembly') + var module = require('../../dist/src/solidity-analyzer/modules/inlineAssembly') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -363,7 +363,7 @@ test('Integration test inlineAssembly.js', function (t) { test('Integration test blockTimestamp.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/blockTimestamp') + var module = require('../../dist/src/solidity-analyzer/modules/blockTimestamp') var lengthCheck = { 'KingOfTheEtherThrone.sol': 1, @@ -402,7 +402,7 @@ test('Integration test blockTimestamp.js', function (t) { test('Integration test lowLevelCalls.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/lowLevelCalls') + var module = require('../../dist/src/solidity-analyzer/modules/lowLevelCalls') var lengthCheck = { 'KingOfTheEtherThrone.sol': 1, @@ -441,7 +441,7 @@ test('Integration test lowLevelCalls.js', function (t) { test('Integration test blockBlockhash.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/blockBlockhash') + var module = require('../../dist/src/solidity-analyzer/modules/blockBlockhash') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -480,7 +480,7 @@ test('Integration test blockBlockhash.js', function (t) { test('Integration test noReturn.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/noReturn') + var module = require('../../dist/src/solidity-analyzer/modules/noReturn') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -519,7 +519,7 @@ test('Integration test noReturn.js', function (t) { test('Integration test selfdestruct.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/selfdestruct') + var module = require('../../dist/src/solidity-analyzer/modules/selfdestruct') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -558,7 +558,7 @@ test('Integration test selfdestruct.js', function (t) { test('Integration test guardConditions.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/guardConditions') + var module = require('../../dist/src/solidity-analyzer/modules/guardConditions') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -597,7 +597,7 @@ test('Integration test guardConditions.js', function (t) { test('Integration test deleteDynamicArrays.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/deleteDynamicArrays') + var module = require('../../dist/src/solidity-analyzer/modules/deleteDynamicArrays') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -636,7 +636,7 @@ test('Integration test deleteDynamicArrays.js', function (t) { test('Integration test deleteFromDynamicArray.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/deleteFromDynamicArray') + var module = require('../../dist/src/solidity-analyzer/modules/deleteFromDynamicArray') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -675,7 +675,7 @@ test('Integration test deleteFromDynamicArray.js', function (t) { test('Integration test assignAndCompare.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/assignAndCompare') + var module = require('../../dist/src/solidity-analyzer/modules/assignAndCompare') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -714,7 +714,7 @@ test('Integration test assignAndCompare.js', function (t) { test('Integration test intDivisionTruncate.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/intDivisionTruncate') + var module = require('../../dist/src/solidity-analyzer/modules/intDivisionTruncate') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -753,7 +753,7 @@ test('Integration test intDivisionTruncate.js', function (t) { test('Integration test erc20Decimal.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/erc20Decimals') + var module = require('../../dist/src/solidity-analyzer/modules/erc20Decimals') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -792,7 +792,7 @@ test('Integration test erc20Decimal.js', function (t) { test('Integration test stringBytesLength.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/stringBytesLength') + var module = require('../../dist/src/solidity-analyzer/modules/stringBytesLength') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -831,7 +831,7 @@ test('Integration test stringBytesLength.js', function (t) { test('Integration test etherTransferInLoop.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/etherTransferInLoop') + var module = require('../../dist/src/solidity-analyzer/modules/etherTransferInLoop') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -870,7 +870,7 @@ test('Integration test etherTransferInLoop.js', function (t) { test('Integration test forLoopIteratesOverDynamicArray.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/forLoopIteratesOverDynamicArray') + var module = require('../../dist/src/solidity-analyzer/modules/forLoopIteratesOverDynamicArray') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, diff --git a/remix-analyzer/test/analysis/staticAnalysisIntegration-test-0.5.0.js b/remix-analyzer/test/analysis/staticAnalysisIntegration-test-0.5.0.js index b132dae606..f5673962ec 100644 --- a/remix-analyzer/test/analysis/staticAnalysisIntegration-test-0.5.0.js +++ b/remix-analyzer/test/analysis/staticAnalysisIntegration-test-0.5.0.js @@ -1,7 +1,7 @@ var test = require('tape') var remixLib = require('remix-lib') -var StatRunner = require('../../src/solidity-analyzer') +var StatRunner = require('../../dist/src/solidity-analyzer') var compilerInput = remixLib.helpers.compiler.compilerInput const niv = require('npm-install-version') @@ -51,7 +51,7 @@ testFiles.forEach((fileName) => { test('Integration test thisLocal.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/thisLocal') + var module = require('../../dist/src/solidity-analyzer/modules/thisLocal') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -90,7 +90,7 @@ test('Integration test thisLocal.js', function (t) { test('Integration test checksEffectsInteraction.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/checksEffectsInteraction') + var module = require('../../dist/src/solidity-analyzer/modules/checksEffectsInteraction') var lengthCheck = { 'KingOfTheEtherThrone.sol': 1, @@ -129,7 +129,7 @@ test('Integration test checksEffectsInteraction.js', function (t) { test('Integration test constantFunctions.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/constantFunctions') + var module = require('../../dist/src/solidity-analyzer/modules/constantFunctions') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -168,7 +168,7 @@ test('Integration test constantFunctions.js', function (t) { test('Integration test inlineAssembly.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/inlineAssembly') + var module = require('../../dist/src/solidity-analyzer/modules/inlineAssembly') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -207,7 +207,7 @@ test('Integration test inlineAssembly.js', function (t) { test('Integration test txOrigin.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/txOrigin') + var module = require('../../dist/src/solidity-analyzer/modules/txOrigin') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -246,7 +246,7 @@ test('Integration test txOrigin.js', function (t) { test('Integration test gasCosts.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/gasCosts') + var module = require('../../dist/src/solidity-analyzer/modules/gasCosts') var lengthCheck = { 'KingOfTheEtherThrone.sol': 2, @@ -285,7 +285,7 @@ test('Integration test gasCosts.js', function (t) { test('Integration test similarVariableNames.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/similarVariableNames') + var module = require('../../dist/src/solidity-analyzer/modules/similarVariableNames') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -324,7 +324,7 @@ test('Integration test similarVariableNames.js', function (t) { test('Integration test inlineAssembly.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/inlineAssembly') + var module = require('../../dist/src/solidity-analyzer/modules/inlineAssembly') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -363,7 +363,7 @@ test('Integration test inlineAssembly.js', function (t) { test('Integration test blockTimestamp.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/blockTimestamp') + var module = require('../../dist/src/solidity-analyzer/modules/blockTimestamp') var lengthCheck = { 'KingOfTheEtherThrone.sol': 1, @@ -402,7 +402,7 @@ test('Integration test blockTimestamp.js', function (t) { test('Integration test lowLevelCalls.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/lowLevelCalls') + var module = require('../../dist/src/solidity-analyzer/modules/lowLevelCalls') var lengthCheck = { 'KingOfTheEtherThrone.sol': 1, @@ -441,7 +441,7 @@ test('Integration test lowLevelCalls.js', function (t) { test('Integration test blockBlockhash.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/blockBlockhash') + var module = require('../../dist/src/solidity-analyzer/modules/blockBlockhash') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -484,7 +484,7 @@ test('Integration test blockBlockhash.js', function (t) { test('Integration test noReturn.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/noReturn') + var module = require('../../dist/src/solidity-analyzer/modules/noReturn') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -523,7 +523,7 @@ test('Integration test noReturn.js', function (t) { test('Integration test selfdestruct.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/selfdestruct') + var module = require('../../dist/src/solidity-analyzer/modules/selfdestruct') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -562,7 +562,7 @@ test('Integration test selfdestruct.js', function (t) { test('Integration test guardConditions.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/guardConditions') + var module = require('../../dist/src/solidity-analyzer/modules/guardConditions') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -601,7 +601,7 @@ test('Integration test guardConditions.js', function (t) { test('Integration test deleteDynamicArrays.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/deleteDynamicArrays') + var module = require('../../dist/src/solidity-analyzer/modules/deleteDynamicArrays') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -640,7 +640,7 @@ test('Integration test deleteDynamicArrays.js', function (t) { test('Integration test deleteFromDynamicArray.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/deleteFromDynamicArray') + var module = require('../../dist/src/solidity-analyzer/modules/deleteFromDynamicArray') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -679,7 +679,7 @@ test('Integration test deleteFromDynamicArray.js', function (t) { test('Integration test assignAndCompare.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/assignAndCompare') + var module = require('../../dist/src/solidity-analyzer/modules/assignAndCompare') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -718,7 +718,7 @@ test('Integration test assignAndCompare.js', function (t) { test('Integration test intDivisionTruncate.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/intDivisionTruncate') + var module = require('../../dist/src/solidity-analyzer/modules/intDivisionTruncate') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -757,7 +757,7 @@ test('Integration test intDivisionTruncate.js', function (t) { test('Integration test erc20Decimal.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/erc20Decimals') + var module = require('../../dist/src/solidity-analyzer/modules/erc20Decimals') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -796,7 +796,7 @@ test('Integration test erc20Decimal.js', function (t) { test('Integration test stringBytesLength.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/stringBytesLength') + var module = require('../../dist/src/solidity-analyzer/modules/stringBytesLength') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -835,7 +835,7 @@ test('Integration test stringBytesLength.js', function (t) { test('Integration test etherTransferInLoop.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/etherTransferInLoop') + var module = require('../../dist/src/solidity-analyzer/modules/etherTransferInLoop') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, @@ -874,7 +874,7 @@ test('Integration test etherTransferInLoop.js', function (t) { test('Integration test forLoopIteratesOverDynamicArray.js', function (t) { t.plan(testFiles.length) - var module = require('../../src/solidity-analyzer/modules/forLoopIteratesOverDynamicArray') + var module = require('../../dist/src/solidity-analyzer/modules/forLoopIteratesOverDynamicArray') var lengthCheck = { 'KingOfTheEtherThrone.sol': 0, diff --git a/remix-analyzer/test/analysis/staticAnalysisIssues-test-0.4.24.js b/remix-analyzer/test/analysis/staticAnalysisIssues-test-0.4.24.js index e0c9ccf18b..06785122dd 100644 --- a/remix-analyzer/test/analysis/staticAnalysisIssues-test-0.4.24.js +++ b/remix-analyzer/test/analysis/staticAnalysisIssues-test-0.4.24.js @@ -1,7 +1,7 @@ var test = require('tape') var remixLib = require('remix-lib') -var StatRunner = require('../../src/solidity-analyzer') +var StatRunner = require('../../dist/src/solidity-analyzer') var compilerInput = remixLib.helpers.compiler.compilerInput const niv = require('npm-install-version') @@ -22,7 +22,7 @@ test('staticAnalysisIssues.functionParameterPassingError', function (t) { t.plan(2) var res = compile('functionParameters.sol') - var module = require('../../src/solidity-analyzer/modules/checksEffectsInteraction') + var module = require('../../dist/src/solidity-analyzer/modules/checksEffectsInteraction') var statRunner = new StatRunner() diff --git a/remix-analyzer/test/analysis/staticAnalysisIssues-test-0.5.0.js b/remix-analyzer/test/analysis/staticAnalysisIssues-test-0.5.0.js index bc9e98c049..641b979215 100644 --- a/remix-analyzer/test/analysis/staticAnalysisIssues-test-0.5.0.js +++ b/remix-analyzer/test/analysis/staticAnalysisIssues-test-0.5.0.js @@ -1,7 +1,7 @@ var test = require('tape') var remixLib = require('remix-lib') -var StatRunner = require('../../src/solidity-analyzer') +var StatRunner = require('../../dist/src/solidity-analyzer') var compilerInput = remixLib.helpers.compiler.compilerInput const niv = require('npm-install-version') @@ -22,7 +22,7 @@ test('staticAnalysisIssues.functionParameterPassingError', function (t) { t.plan(2) var res = compile('functionParameters.sol') - var module = require('../../src/solidity-analyzer/modules/checksEffectsInteraction') + var module = require('../../dist/src/solidity-analyzer/modules/checksEffectsInteraction') var statRunner = new StatRunner() diff --git a/remix-analyzer/tsconfig.json b/remix-analyzer/tsconfig.json index f0cf6721bf..2f75f3dae9 100644 --- a/remix-analyzer/tsconfig.json +++ b/remix-analyzer/tsconfig.json @@ -12,7 +12,7 @@ "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */ /* Module Resolution Options */ "baseUrl": "./src", /* Base directory to resolve non-absolute module names. */ - "paths": { "remix-solidity": ["./"] }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + "paths": { "remix-analyzer": ["./"] }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ "typeRoots": [ "./@types", "./node_modules/@types"