diff --git a/package.json b/package.json index ed6cca2d55..e85a2cc897 100644 --- a/package.json +++ b/package.json @@ -19,10 +19,10 @@ "csjs-inject": "^1.0.1", "csslint": "^1.0.2", "deep-equal": "^1.0.1", - "remix-core": "^0.0.3", - "remix-lib": "^0.0.3", - "remix-solidity": "^0.0.2", - "remix-debugger": "^0.0.3", + "remix-core": "latest", + "remix-lib": "latest", + "remix-solidity": "latest", + "remix-debugger": "latest", "ethereumjs-abi": "https://github.com/ethereumjs/ethereumjs-abi", "ethereumjs-block": "^1.6.0", "ethereumjs-tx": "^1.3.3", diff --git a/src/app/staticanalysis/modules/abstractAstView.js b/src/app/staticanalysis/modules/abstractAstView.js deleted file mode 100644 index ea28f77b32..0000000000 --- a/src/app/staticanalysis/modules/abstractAstView.js +++ /dev/null @@ -1,177 +0,0 @@ -var common = require('./staticAnalysisCommon') -var AstWalker = require('remix-lib').AstWalker - -function abstractAstView () { - this.contracts = [] - this.currentContractIndex = null - this.currentFunctionIndex = null - this.currentModifierIndex = null - this.isFunctionNotModifier = false - /* - file1: contract c{} - file2: import "file1" as x; contract c{} - therefore we have two contracts with the same name c. At the moment this is not handled because alias name "x" is not - available in the current AST implementation thus can not be resolved. - Additionally the fullQuallified function names e.g. [contractName].[functionName](param1Type, param2Type, ... ) must be prefixed to - fully support this and when inheritance is resolved it must include alias resolving e.g x.c = file1.c - */ - this.multipleContractsWithSameName = false -} - -/** - * Builds a higher level AST view. I creates a list with each contract as an object in it. - * Example contractsOut: - * - * { - * "node": {}, // actual AST Node of the contract - * "functions": [ - * { - * "node": {}, // actual AST Node of the function - * "relevantNodes": [], // AST nodes in the function that are relevant for the anlysis of this function - * "modifierInvocations": [], // Modifier invocation AST nodes that are applied on this function - * "localVariables": [], // Local variable declaration nodes - * "parameters": [] // Parameter types of the function in order of definition - * } - * ], - * "modifiers": [], // Modifiers definded by the contract, format similar to functions - * "inheritsFrom": [], // Names of contract this one inherits from in order of definition - * "stateVariables": [] // AST nodes of all State variables - * } - * - * @relevantNodeFilter {ASTNode -> bool} function that selects relevant ast nodes for analysis on function level. - * @contractsOut {list} return list for high level AST view - * @return {ASTNode -> void} returns a function that can be used as visit function for static analysis modules, to build up a higher level AST view for further analysis. - */ -abstractAstView.prototype.build_visit = function (relevantNodeFilter) { - var that = this - return function (node) { - if (common.isContractDefinition(node)) { - setCurrentContract(that, { - node: node, - functions: [], - relevantNodes: [], - modifiers: [], - inheritsFrom: [], - stateVariables: common.getStateVariableDeclarationsFormContractNode(node) - }) - } else if (common.isInheritanceSpecifier(node)) { - var currentContract = getCurrentContract(that) - var inheritsFromName = common.getInheritsFromName(node) - currentContract.inheritsFrom.push(inheritsFromName) - } else if (common.isFunctionDefinition(node)) { - setCurrentFunction(that, { - node: node, - relevantNodes: [], - modifierInvocations: [], - localVariables: getLocalVariables(node), - parameters: getLocalParameters(node), - returns: getReturnParameters(node) - }) - // push back relevant nodes to their the current fn if any - getCurrentContract(that).relevantNodes.map((item) => { - if (item.referencedDeclaration === node.id) { - getCurrentFunction(that).relevantNodes.push(item.node) - } - }) - } else if (common.isModifierDefinition(node)) { - setCurrentModifier(that, { - node: node, - relevantNodes: [], - localVariables: getLocalVariables(node), - parameters: getLocalParameters(node) - }) - } else if (common.isModifierInvocation(node)) { - if (!that.isFunctionNotModifier) throw new Error('abstractAstView.js: Found modifier invocation outside of function scope.') - getCurrentFunction(that).modifierInvocations.push(node) - } else if (relevantNodeFilter(node)) { - var scope = (that.isFunctionNotModifier) ? getCurrentFunction(that) : getCurrentModifier(that) - if (scope) { - scope.relevantNodes.push(node) - } else { - scope = getCurrentContract(that) // if we are not in a function scope, add the node to the contract scope - if (scope && node.children[0] && node.children[0].attributes && node.children[0].attributes.referencedDeclaration) { - scope.relevantNodes.push({ referencedDeclaration: node.children[0].attributes.referencedDeclaration, node: node }) - } - } - } - } -} - -abstractAstView.prototype.build_report = function (wrap) { - var that = this - return function (compilationResult) { - resolveStateVariablesInHierarchy(that.contracts) - return wrap(that.contracts, that.multipleContractsWithSameName) - } -} - -function resolveStateVariablesInHierarchy (contracts) { - contracts.map((c) => { - resolveStateVariablesInHierarchyForContract(c, contracts) - }) -} -function resolveStateVariablesInHierarchyForContract (currentContract, contracts) { - currentContract.inheritsFrom.map((inheritsFromName) => { - // add variables from inherited contracts - var inheritsFrom = contracts.find((contract) => common.getContractName(contract.node) === inheritsFromName) - if (inheritsFrom) { - currentContract.stateVariables = currentContract.stateVariables.concat(inheritsFrom.stateVariables) - } else { - console.log('abstractAstView.js: could not find contract defintion inherited from ' + inheritsFromName) - } - }) -} -function setCurrentContract (that, contract) { - var name = common.getContractName(contract.node) - if (that.contracts.map((c) => common.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 - } - that.currentContractIndex = (that.contracts.push(contract) - 1) -} - -function setCurrentFunction (that, func) { - that.isFunctionNotModifier = true - that.currentFunctionIndex = (getCurrentContract(that).functions.push(func) - 1) -} - -function setCurrentModifier (that, modi) { - that.isFunctionNotModifier = false - that.currentModifierIndex = (getCurrentContract(that).modifiers.push(modi) - 1) -} - -function getCurrentContract (that) { - return that.contracts[that.currentContractIndex] -} - -function getCurrentFunction (that) { - return getCurrentContract(that).functions[that.currentFunctionIndex] -} - -function getCurrentModifier (that) { - return getCurrentContract(that).modifiers[that.currentModifierIndex] -} - -function getLocalParameters (funcNode) { - return getLocalVariables(common.getFunctionOrModifierDefinitionParameterPart(funcNode)).map(common.getType) -} - -function getReturnParameters (funcNode) { - return getLocalVariables(common.getFunctionOrModifierDefinitionReturnParameterPart(funcNode)).map((n) => { - return { - type: common.getType(n), - name: common.getDeclaredVariableName(n) - } - }) -} - -function getLocalVariables (funcNode) { - var locals = [] - new AstWalker().walk(funcNode, {'*': function (node) { - if (common.isVariableDeclaration(node)) locals.push(node) - return true - }}) - return locals -} - -module.exports = abstractAstView diff --git a/src/app/staticanalysis/modules/blockBlockhash.js b/src/app/staticanalysis/modules/blockBlockhash.js deleted file mode 100644 index 17f761ae3e..0000000000 --- a/src/app/staticanalysis/modules/blockBlockhash.js +++ /dev/null @@ -1,33 +0,0 @@ -var name = 'Block.blockhash usage: ' -var desc = 'Semantics maybe unclear' -var categories = require('./categories') -var common = require('./staticAnalysisCommon') -var yo = require('yo-yo') - -function blockBlockhash () { - this.warningNodes = [] -} - -blockBlockhash.prototype.visit = function (node) { - if (common.isBlockBlockHashAccess(node)) this.warningNodes.push(node) -} - -blockBlockhash.prototype.report = function (compilationResults) { - return this.warningNodes.map(function (item, i) { - return { - warning: yo`use of "block.blockhash": "block.blockhash" is used to access the last 256 block hashes. - A miner computes the block hash by "summing up" the information in the current block mined. - By "summing up" the information in a clever way a miner can try to influence the outcome of a transaction in the current block. - This is especially easy if there are only a small number of equally likely outcomes.`, - location: item.src - } - }) -} - -module.exports = { - name: name, - description: desc, - category: categories.SECURITY, - Module: blockBlockhash -} - diff --git a/src/app/staticanalysis/modules/blockTimestamp.js b/src/app/staticanalysis/modules/blockTimestamp.js deleted file mode 100644 index 0c542509d0..0000000000 --- a/src/app/staticanalysis/modules/blockTimestamp.js +++ /dev/null @@ -1,41 +0,0 @@ -var name = 'Block timestamp: ' -var desc = 'Semantics maybe unclear' -var categories = require('./categories') -var common = require('./staticAnalysisCommon') -var yo = require('yo-yo') - -function blockTimestamp () { - this.warningNowNodes = [] - this.warningblockTimestampNodes = [] -} - -blockTimestamp.prototype.visit = function (node) { - if (common.isNowAccess(node)) this.warningNowNodes.push(node) - else if (common.isBlockTimestampAccess(node)) this.warningblockTimestampNodes.push(node) -} - -blockTimestamp.prototype.report = function (compilationResults) { - return this.warningNowNodes.map(function (item, i) { - return { - warning: yo`use of "now": "now" does not mean current time. Now is an alias for block.timestamp. - Block.timestamp can be influenced by miners to a certain degree, be careful.`, - location: item.src, - more: 'http://solidity.readthedocs.io/en/develop/frequently-asked-questions.html#are-timestamps-now-block-timestamp-reliable' - } - }).concat(this.warningblockTimestampNodes.map(function (item, i) { - return { - warning: yo`use of "block.timestamp": "block.timestamp" can be influenced by miners to a certain degree. - That means that a miner can "choose" the block.timestamp, to a certain degree, to change the outcome of a transaction in the mined block.`, - location: item.src, - more: 'http://solidity.readthedocs.io/en/develop/frequently-asked-questions.html#are-timestamps-now-block-timestamp-reliable' - } - })) -} - -module.exports = { - name: name, - description: desc, - category: categories.SECURITY, - Module: blockTimestamp -} - diff --git a/src/app/staticanalysis/modules/categories.js b/src/app/staticanalysis/modules/categories.js deleted file mode 100644 index 2b0e8d3414..0000000000 --- a/src/app/staticanalysis/modules/categories.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - SECURITY: {displayName: 'Security', id: 'SEC'}, - GAS: {displayName: 'Gas & Economy', id: 'GAS'}, - MISC: {displayName: 'Miscellaneous', id: 'MISC'} -} diff --git a/src/app/staticanalysis/modules/checksEffectsInteraction.js b/src/app/staticanalysis/modules/checksEffectsInteraction.js deleted file mode 100644 index 4d0f113351..0000000000 --- a/src/app/staticanalysis/modules/checksEffectsInteraction.js +++ /dev/null @@ -1,89 +0,0 @@ -var name = 'Check effects: ' -var desc = 'Avoid potential reentrancy bugs' -var categories = require('./categories') -var common = require('./staticAnalysisCommon') -var fcallGraph = require('./functionCallGraph') -var AbstractAst = require('./abstractAstView') -var yo = require('yo-yo') - -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) { - var warnings = [] - var hasModifiers = contracts.some((item) => item.modifiers.length > 0) - - var 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))) { - var funcName = common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters) - var 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: yo`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) { - var isPotentialVulnerable = false - var 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)) { - var func = fcallGraph.resolveCallGraphSymbol(context.callGraph, common.getFullQualifiedFunctionCallIdent(context.currentContract.node, node)) - return !func || (func && func.node.changesState) - } - return false -} - -function checkIfChangesState (startFuncName, context) { - return fcallGraph.analyseCallGraph(context.callGraph, startFuncName, context, (node, context) => common.isWriteOnStateVariable(node, context.stateVariables)) -} - -module.exports = { - name: name, - description: desc, - category: categories.SECURITY, - Module: checksEffectsInteraction -} diff --git a/src/app/staticanalysis/modules/constantFunctions.js b/src/app/staticanalysis/modules/constantFunctions.js deleted file mode 100644 index 24c237c433..0000000000 --- a/src/app/staticanalysis/modules/constantFunctions.js +++ /dev/null @@ -1,107 +0,0 @@ -var name = 'Constant functions: ' -var desc = 'Check for potentially constant functions' -var categories = require('./categories') -var common = require('./staticAnalysisCommon') -var fcallGraph = require('./functionCallGraph') -var AbstractAst = require('./abstractAstView') -var yo = require('yo-yo') - -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) - ) - - 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) { - var warnings = [] - var hasModifiers = contracts.some((item) => item.modifiers.length > 0) - - var callGraph = fcallGraph.buildGlobalFuncCallGraph(contracts) - - contracts.forEach((contract) => { - contract.functions.forEach((func) => { - if (common.isPayableFunction(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) { - var funcName = common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters) - var 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: yo`${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: yo`${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) -} - -function isCallOnNonConstExternalInterfaceFunction (node, context) { - if (common.isExternalDirectCall(node)) { - var 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, - Module: constantFunctions -} diff --git a/src/app/staticanalysis/modules/functionCallGraph.js b/src/app/staticanalysis/modules/functionCallGraph.js deleted file mode 100644 index 748e2a9b43..0000000000 --- a/src/app/staticanalysis/modules/functionCallGraph.js +++ /dev/null @@ -1,114 +0,0 @@ -'use strict' - -var common = require('./staticAnalysisCommon') - -function buildLocalFuncCallGraphInternal (functions, nodeFilter, extractNodeIdent, extractFuncDefIdent) { - var callGraph = {} - functions.forEach((func) => { - var calls = func.relevantNodes - .filter(nodeFilter) - .map(extractNodeIdent) - .filter((name) => name !== extractFuncDefIdent(func)) // filter self recursive call - - callGraph[extractFuncDefIdent(func)] = { node: func, calls: calls } - }) - - return callGraph -} - -/** - * Builds a function call graph for the current contracts. - * Example Contract call graph: - * - * { - * "KingOfTheEtherThrone": { - * "contracts": {...}, // Contract node as defined in abstractAstView.js - * "functions": { - * "KingOfTheEtherThrone.claimThrone(string memory)": { // function in KingOfEtherThrone - * "node": {...}, // function node as defined in abstractAstView.js - * "calls": { // list of full qualified function names which are called form this function - * } - * } - * } - * }, - * "foo": { - * "contract": {...}, // Contract node as definded in abstractAstView.js - * "functions": {} // map from full qualified function name to func node - * } - * } - * - * @contracts {list contracts} Expects as input the contract structure defined in abstractAstView.js - * @return {map (string -> Contract Call Graph)} returns map from contract name to contract call graph - */ -function buildGlobalFuncCallGraph (contracts) { - var callGraph = {} - contracts.forEach((contract) => { - var filterNodes = (node) => { return common.isLocalCallGraphRelevantNode(node) || common.isExternalDirectCall(node) } - var getNodeIdent = (node) => { return common.getFullQualifiedFunctionCallIdent(contract.node, node) } - var getFunDefIdent = (funcDef) => { return common.getFullQuallyfiedFuncDefinitionIdent(contract.node, funcDef.node, funcDef.parameters) } - - callGraph[common.getContractName(contract.node)] = { contract: contract, functions: buildLocalFuncCallGraphInternal(contract.functions, filterNodes, getNodeIdent, getFunDefIdent) } - }) - - return callGraph -} - -/** - * Walks through the call graph from a defined starting function, true if nodeCheck holds for every relevant node in the callgraph - * @callGraph {callGraph} As returned by buildGlobalFuncCallGraph - * @funcName {string} full qualified name of the starting function - * @context {Object} provides additional context information that can be used by the nodeCheck function - * @nodeCheck {(ASTNode, context) -> bool} applied on every relevant node in the call graph - * @return {bool} returns map from contract name to contract call graph - */ -function analyseCallGraph (callGraph, funcName, context, nodeCheck) { - return analyseCallGraphInternal(callGraph, funcName, context, (a, b) => a || b, nodeCheck, {}) -} - -function analyseCallGraphInternal (callGraph, funcName, context, combinator, nodeCheck, visited) { - var current = resolveCallGraphSymbol(callGraph, funcName) - - if (current === undefined || visited[funcName] === true) return true - visited[funcName] = true - - return combinator(current.node.relevantNodes.reduce((acc, val) => combinator(acc, nodeCheck(val, context)), false), - current.calls.reduce((acc, val) => combinator(acc, analyseCallGraphInternal(callGraph, val, context, combinator, nodeCheck, visited)), false)) -} - -function resolveCallGraphSymbol (callGraph, funcName) { - return resolveCallGraphSymbolInternal(callGraph, funcName, false) -} - -function resolveCallGraphSymbolInternal (callGraph, funcName, silent) { - var current - if (funcName.includes('.')) { - var parts = funcName.split('.') - var contractPart = parts[0] - var functionPart = parts[1] - var currentContract = callGraph[contractPart] - if (!(currentContract === undefined)) { - current = currentContract.functions[funcName] - // resolve inheritance hierarchy - if (current === undefined) { - // resolve inheritance lookup in linearized fashion - var inheritsFromNames = currentContract.contract.inheritsFrom.reverse() - for (var i = 0; i < inheritsFromNames.length; i++) { - var res = resolveCallGraphSymbolInternal(callGraph, inheritsFromNames[i] + '.' + functionPart, true) - if (!(res === undefined)) return res - } - } - } else { - if (!silent) console.log(`static analysis functionCallGraph.js: Contract ${contractPart} not found in function call graph.`) - } - } else { - throw new Error('functionCallGraph.js: function does not have full qualified name.') - } - 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/src/app/staticanalysis/modules/gasCosts.js b/src/app/staticanalysis/modules/gasCosts.js deleted file mode 100644 index 853e418958..0000000000 --- a/src/app/staticanalysis/modules/gasCosts.js +++ /dev/null @@ -1,53 +0,0 @@ -var name = 'Gas costs: ' -var desc = 'Warn if the gas requirements of functions are too high.' -var categories = require('./categories') -var yo = require('yo-yo') -var txHelper = require('../../execution/txHelper') - -function gasCosts () { -} - -gasCosts.prototype.report = function (compilationResults) { - var report = [] - txHelper.visitContracts(compilationResults.contracts, (contract) => { - if ( - !contract.object.evm.gasEstimates || - !contract.object.evm.gasEstimates.external - ) { - return - } - var fallback = contract.object.evm.gasEstimates.external[''] - if (fallback !== undefined) { - if (fallback === null || fallback >= 2100 || fallback === 'infinite') { - report.push({ - warning: yo`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 - } - var gas = contract.object.evm.gasEstimates.external[functionName] - var gasString = gas === null ? 'unknown or not constant' : 'high: ' + gas - if (gas === null || gas >= 3000000 || gas === 'infinite') { - report.push({ - warning: yo`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, - Module: gasCosts -} diff --git a/src/app/staticanalysis/modules/inlineAssembly.js b/src/app/staticanalysis/modules/inlineAssembly.js deleted file mode 100644 index 44eef006f6..0000000000 --- a/src/app/staticanalysis/modules/inlineAssembly.js +++ /dev/null @@ -1,31 +0,0 @@ -var name = 'Inline assembly: ' -var desc = 'Use of Inline Assembly' -var categories = require('./categories') -var common = require('./staticAnalysisCommon') -var yo = require('yo-yo') - -function inlineAssembly () { - this.inlineAssNodes = [] -} - -inlineAssembly.prototype.visit = function (node) { - if (common.isInlineAssembly(node)) this.inlineAssNodes.push(node) -} - -inlineAssembly.prototype.report = function (compilationResults) { - return this.inlineAssNodes.map((node) => { - return { - warning: yo`CAUTION: The Contract uses inline assembly, this is only advised in rare cases. - Additionally static analysis modules do not parse inline Assembly, this can lead to wrong analysis results.`, - location: node.src, - more: 'http://solidity.readthedocs.io/en/develop/assembly.html#solidity-assembly' - } - }) -} - -module.exports = { - name: name, - description: desc, - category: categories.SECURITY, - Module: inlineAssembly -} diff --git a/src/app/staticanalysis/modules/list.js b/src/app/staticanalysis/modules/list.js deleted file mode 100644 index e510f323f6..0000000000 --- a/src/app/staticanalysis/modules/list.js +++ /dev/null @@ -1,14 +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') -] diff --git a/src/app/staticanalysis/modules/lowLevelCalls.js b/src/app/staticanalysis/modules/lowLevelCalls.js deleted file mode 100644 index 3910156070..0000000000 --- a/src/app/staticanalysis/modules/lowLevelCalls.js +++ /dev/null @@ -1,65 +0,0 @@ -var name = 'Low level calls: ' -var desc = 'Semantics maybe unclear' -var categories = require('./categories') -var common = require('./staticAnalysisCommon') -var yo = require('yo-yo') - -function lowLevelCalls () { - this.llcNodes = [] -} - -lowLevelCalls.prototype.visit = function (node) { - if (common.isLowLevelCallInst(node)) { - this.llcNodes.push({node: node, type: common.lowLevelCallTypes.CALL}) - } else if (common.isLowLevelCallcodeInst(node)) { - this.llcNodes.push({node: node, type: common.lowLevelCallTypes.CALLCODE}) - } else if (common.isLowLevelDelegatecallInst(node)) { - this.llcNodes.push({node: node, type: common.lowLevelCallTypes.DELEGATECALL}) - } else if (common.isLowLevelSendInst(node)) { - this.llcNodes.push({node: node, type: common.lowLevelCallTypes.SEND}) - } -} - -lowLevelCalls.prototype.report = function (compilationResults) { - return this.llcNodes.map(function (item, i) { - var text = '' - var morehref = null - switch (item.type) { - case common.lowLevelCallTypes.CALL: - text = yo`use of "call": the use of low level "call" should be avoided whenever possible. - It can lead to unexpected behavior if return value is not handled properly. - Please use Direct Calls via specifying the called contract's interface.
` - morehref = 'http://solidity.readthedocs.io/en/develop/control-structures.html?#external-function-calls' - // http://solidity.readthedocs.io/en/develop/frequently-asked-questions.html?#why-is-the-low-level-function-call-less-favorable-than-instantiating-a-contract-with-a-variable-contractb-b-and-executing-its-functions-b-dosomething - break - case common.lowLevelCallTypes.CALLCODE: - text = yo`use of "callcode": the use of low level "callcode" should be avoided whenever possible. - External code that is called can change the state of the calling contract and send ether form the caller's balance. - If this is wantend behaviour use the Solidity library feature if possible.
` - morehref = 'http://solidity.readthedocs.io/en/develop/contracts.html#libraries' - break - case common.lowLevelCallTypes.DELEGATECALL: - text = yo`use of "delegatecall": the use of low level "delegatecall" should be avoided whenever possible. - External code that is called can change the state of the calling contract and send ether form the caller's balance. - If this is wantend behaviour use the Solidity library feature if possible.
` - morehref = 'http://solidity.readthedocs.io/en/develop/contracts.html#libraries' - break - case common.lowLevelCallTypes.SEND: - text = yo`use of "send": "send" does not throw an exception when not successful, make sure you deal with the failure case accordingly. - Use "transfer" whenever failure of the ether transfer should rollback the whole transaction. - Note: if you "send/transfer" ether to a contract the fallback function is called, the callees fallback function is very limited due to the limited amount of gas provided by "send/transfer". - No state changes are possible but the callee can log the event or revert the transfer. "send/transfer" is syntactic sugar for a "call" to the fallback function with 2300 gas and a specified ether value.
` - morehref = 'http://solidity.readthedocs.io/en/develop/security-considerations.html#sending-and-receiving-ether' - break - } - return { warning: text, more: morehref, location: item.node.src } - }) -} - -module.exports = { - name: name, - description: desc, - category: categories.SECURITY, - Module: lowLevelCalls -} - diff --git a/src/app/staticanalysis/modules/noReturn.js b/src/app/staticanalysis/modules/noReturn.js deleted file mode 100644 index dbd212ead5..0000000000 --- a/src/app/staticanalysis/modules/noReturn.js +++ /dev/null @@ -1,73 +0,0 @@ -var name = 'no return: ' -var desc = 'Function with return type is not returning' -var categories = require('./categories') -var common = require('./staticAnalysisCommon') -var AbstractAst = require('./abstractAstView') - -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) { - var warnings = [] - - contracts.forEach((contract) => { - contract.functions.filter((func) => common.hasFunctionBody(func.node)).forEach((func) => { - var 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) { - var namedReturns = func.returns.filter((n) => n.name.length > 0).map((n) => n.name) - var assignedVars = func.relevantNodes.filter(common.isAssignment).map(common.getEffectedVariableName) - var 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, - Module: noReturn -} diff --git a/src/app/staticanalysis/modules/selfdestruct.js b/src/app/staticanalysis/modules/selfdestruct.js deleted file mode 100644 index 27e03a0ec7..0000000000 --- a/src/app/staticanalysis/modules/selfdestruct.js +++ /dev/null @@ -1,32 +0,0 @@ -var name = 'Selfdestruct: ' -var desc = 'Be aware of caller contracts.' -var categories = require('./categories') -var common = require('./staticAnalysisCommon') -var yo = require('yo-yo') - -function selfdestruct () { - this.relevantNodes = [] -} - -selfdestruct.prototype.visit = function (node) { - if (common.isSelfdestructCall(node)) { - this.relevantNodes.push(node) - } -} - -selfdestruct.prototype.report = function () { - return this.relevantNodes.map(function (item, i) { - return { - warning: yo`Use of selfdestruct: can block calling contracts unexpectedly. Be especially careful if this contract is planed to be used by other contracts (i.e. library contracts, interactions). Selfdestruction of the callee contract can leave callers in an inoperable state.`, - location: item.src, - more: 'https://paritytech.io/blog/security-alert.html' - } - }) -} - -module.exports = { - name: name, - description: desc, - category: categories.SECURITY, - Module: selfdestruct -} diff --git a/src/app/staticanalysis/modules/similarVariableNames.js b/src/app/staticanalysis/modules/similarVariableNames.js deleted file mode 100644 index 955b8f0223..0000000000 --- a/src/app/staticanalysis/modules/similarVariableNames.js +++ /dev/null @@ -1,79 +0,0 @@ -var name = 'Similar variable names: ' -var desc = 'Check if variable names are too similar' -var categories = require('./categories') -var common = require('./staticAnalysisCommon') -var AbstractAst = require('./abstractAstView') -var levenshtein = require('fast-levenshtein') -var yo = require('yo-yo') - -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) { - var warnings = [] - var hasModifiers = contracts.some((item) => item.modifiers.length > 0) - - contracts.forEach((contract) => { - contract.functions.forEach((func) => { - var funcName = common.getFullQuallyfiedFuncDefinitionIdent(contract.node, func.node, func.parameters) - var hasModifiersComments = '' - if (hasModifiers) { - hasModifiersComments = yo`
Note: Modifiers are currently not considered by this static analysis.
` - } - var multipleContractsWithSameNameComments = '' - if (multipleContractsWithSameName) { - multipleContractsWithSameNameComments = yo`
Note: Import aliases are currently not supported by this static analysis.
` - } - - var vars = getFunctionVariables(contract, func).map(common.getDeclaredVariableName) - - findSimilarVarNames(vars).map((sim) => { - warnings.push({ - warning: yo`${funcName}: Variables have very similar names ${sim.var1} and ${sim.var2}. ${hasModifiersComments} ${multipleContractsWithSameNameComments}`, - location: func.src - }) - }) - }) - }) - - return warnings -} - -function findSimilarVarNames (vars) { - var similar = [] - var comb = {} - vars.map((varName1) => vars.map((varName2) => { - if (varName1.length > 1 && varName2.length > 1 && varName2 !== varName1 && !isCommonPrefixedVersion(varName1, varName2) && !(comb[varName1 + ';' + varName2] || comb[varName2 + ';' + varName1])) { - comb[varName1 + ';' + varName2] = true - var 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 getFunctionVariables (contract, func) { - return contract.stateVariables.concat(func.localVariables) -} - -module.exports = { - name: name, - description: desc, - category: categories.MISC, - Module: similarVariableNames -} diff --git a/src/app/staticanalysis/modules/staticAnalysisCommon.js b/src/app/staticanalysis/modules/staticAnalysisCommon.js deleted file mode 100644 index 2ab76ba6ba..0000000000 --- a/src/app/staticanalysis/modules/staticAnalysisCommon.js +++ /dev/null @@ -1,843 +0,0 @@ -'use strict' - -var utils = require('../../../lib/utils') - -var nodeTypes = { - IDENTIFIER: 'Identifier', - MEMBERACCESS: 'MemberAccess', - FUNCTIONCALL: 'FunctionCall', - FUNCTIONDEFINITION: 'FunctionDefinition', - VARIABLEDECLARATION: 'VariableDeclaration', - ASSIGNMENT: 'Assignment', - CONTRACTDEFINITION: 'ContractDefinition', - UNARYOPERATION: 'UnaryOperation', - EXPRESSIONSTATEMENT: 'ExpressionStatement', - MODIFIERDEFINITION: 'ModifierDefinition', - MODIFIERINVOCATION: 'ModifierInvocation', - INHERITANCESPECIFIER: 'InheritanceSpecifier', - USERDEFINEDTYPENAME: 'UserDefinedTypeName', - INLINEASSEMBLY: 'InlineAssembly', - BLOCK: 'Block', - NEWEXPRESSION: 'NewExpression', - RETURN: 'Return' -} - -var basicTypes = { - UINT: 'uint256', - BOOL: 'bool', - ADDRESS: 'address', - BYTES32: 'bytes32' -} - -var basicRegex = { - CONTRACTTYPE: '^contract ', - FUNCTIONTYPE: '^function \\(', - EXTERNALFUNCTIONTYPE: '^function \\(.*\\).* external', - CONSTANTFUNCTIONTYPE: '^function \\(.*\\).* (constant|view|pure)', - REFTYPE: '( storage )|(mapping\\()|(\\[\\])', - FUNCTIONSIGNATURE: '^function \\(([^\\(]*)\\)', - LIBRARYTYPE: '^type\\(library (.*)\\)' -} - -var basicFunctionTypes = { - SEND: buildFunctionSignature([basicTypes.UINT], [basicTypes.BOOL], false), - CALL: buildFunctionSignature([], [basicTypes.BOOL], true), - DELEGATECALL: buildFunctionSignature([], [basicTypes.BOOL], false), - TRANSFER: buildFunctionSignature([basicTypes.UINT], [], false) -} - -var builtinFunctions = { - 'keccak256()': true, - 'sha3()': true, - 'sha256()': true, - 'ripemd160()': true, - 'ecrecover(bytes32,uint8,bytes32,bytes32)': true, - 'addmod(uint256,uint256,uint256)': true, - 'mulmod(uint256,uint256,uint256)': true, - 'selfdestruct(address)': true, - 'revert()': true, - 'assert(bool)': true, - 'require(bool)': true -} - -var lowLevelCallTypes = { - CALL: { ident: 'call', type: basicFunctionTypes.CALL }, - CALLCODE: { ident: 'callcode', type: basicFunctionTypes.CALL }, - DELEGATECALL: { ident: 'delegatecall', type: basicFunctionTypes.DELEGATECALL }, - SEND: { ident: 'send', type: basicFunctionTypes.SEND }, - TRANSFER: { ident: 'transfer', type: basicFunctionTypes.TRANSFER } -} - -var specialVariables = { - BLOCKTIMESTAMP: { obj: 'block', member: 'timestamp', type: basicTypes.UINT }, - BLOCKHASH: { - obj: 'block', - member: 'blockhash', - type: buildFunctionSignature([basicTypes.UINT], [basicTypes.BYTES32], false) - } -} - -// #################### Trivial Getters - -function getType (node) { - return node.attributes.type -} - -// #################### Complex Getters - -/** - * Returns the type parameter of function call AST nodes. Throws if not a function call node - * @func {ASTNode} Function call node - * @return {string} type of function call - */ -function getFunctionCallType (func) { - if (!(isExternalDirectCall(func) || isThisLocalCall(func) || isSuperLocalCall(func) || isLocalCall(func) || isLibraryCall(func))) throw new Error('staticAnalysisCommon.js: not function call Node') - if (isExternalDirectCall(func) || isThisLocalCall(func) || isSuperLocalCall(func) || isLibraryCall(func)) return func.attributes.type - return findFirstSubNodeLTR(func, exactMatch(nodeTypes.IDENTIFIER)).attributes.type -} - -/** - * Get the variable name written to by a effect node, except for inline assembly because there is no information to find out where we write to. Trows if not a effect node or is inlineassmbly. - * Example: x = 10; => x - * @effectNode {ASTNode} Function call node - * @return {string} variable name written to - */ -function getEffectedVariableName (effectNode) { - if (!isEffect(effectNode) || isInlineAssembly(effectNode)) throw new Error('staticAnalysisCommon.js: not an effect Node or inline assembly') - return findFirstSubNodeLTR(effectNode, exactMatch(nodeTypes.IDENTIFIER)).attributes.value -} - -/** - * Returns the identifier of a local call, Throws on wrong node. - * Example: f(103) => f - * @localCallNode {ASTNode} Function call node - * @return {string} name of the function called - */ -function getLocalCallName (localCallNode) { - if (!isLocalCall(localCallNode)) throw new Error('staticAnalysisCommon.js: not an local call Node') - return localCallNode.children[0].attributes.value -} - -/** - * Returns the identifier of a this local call, Throws on wrong node. - * Example: this.f(103) => f - * @localCallNode {ASTNode} Function call node - * @return {string} name of the function called - */ -function getThisLocalCallName (localCallNode) { - if (!isThisLocalCall(localCallNode)) throw new Error('staticAnalysisCommon.js: not an this local call Node') - return localCallNode.attributes.value -} - -/** - * Returns the identifier of a super local call, Throws on wrong node. - * Example: super.f(103) => f - * @localCallNode {ASTNode} Function call node - * @return {string} name of the function called - */ -function getSuperLocalCallName (localCallNode) { - if (!isSuperLocalCall(localCallNode)) throw new Error('staticAnalysisCommon.js: not an super local call Node') - return localCallNode.attributes.member_name -} - -/** - * Returns the contract type of a external direct call, Throws on wrong node. - * Example: - * foo x = foo(0xdeadbeef...); - * x.f(103) => foo - * @extDirectCall {ASTNode} Function call node - * @return {string} name of the contract the function is defined in - */ -function getExternalDirectCallContractName (extDirectCall) { - if (!isExternalDirectCall(extDirectCall)) throw new Error('staticAnalysisCommon.js: not an external direct call Node') - return extDirectCall.children[0].attributes.type.replace(new RegExp(basicRegex.CONTRACTTYPE), '') -} - -/** - * Returns the name of the contract of a this local call (current contract), Throws on wrong node. - * Example: - * Contract foo { - * ... - * this.f(103) => foo - * ... - * @thisLocalCall {ASTNode} Function call node - * @return {string} name of the contract the function is defined in - */ -function getThisLocalCallContractName (thisLocalCall) { - if (!isThisLocalCall(thisLocalCall)) throw new Error('staticAnalysisCommon.js: not an this local call Node') - return thisLocalCall.children[0].attributes.type.replace(new RegExp(basicRegex.CONTRACTTYPE), '') -} - -/** - * Returns the function identifier of a external direct call, Throws on wrong node. - * Example: - * foo x = foo(0xdeadbeef...); - * x.f(103) => f - * @extDirectCall {ASTNode} Function call node - * @return {string} name of the function called - */ -function getExternalDirectCallMemberName (extDirectCall) { - if (!isExternalDirectCall(extDirectCall)) throw new Error('staticAnalysisCommon.js: not an external direct call Node') - return extDirectCall.attributes.member_name -} - -/** - * Returns the name of a contract, Throws on wrong node. - * Example: - * Contract foo { => foo - * @contract {ASTNode} Contract Definition node - * @return {string} name of a contract defined - */ -function getContractName (contract) { - if (!isContractDefinition(contract)) throw new Error('staticAnalysisCommon.js: not an contractDefinition Node') - return contract.attributes.name -} - -/** - * Returns the name of a function definition, Throws on wrong node. - * Example: - * func foo(uint bla) { => foo - * @funcDef {ASTNode} Function Definition node - * @return {string} name of a function defined - */ -function getFunctionDefinitionName (funcDef) { - if (!isFunctionDefinition(funcDef)) throw new Error('staticAnalysisCommon.js: not an functionDefinition Node') - return funcDef.attributes.name -} - -/** - * Returns the identifier of an inheritance specifier, Throws on wrong node. - * Example: - * contract KingOfTheEtherThrone is b { => b - * @func {ASTNode} Inheritance specifier - * @return {string} name of contract inherited from - */ -function getInheritsFromName (inheritsNode) { - if (!isInheritanceSpecifier(inheritsNode)) throw new Error('staticAnalysisCommon.js: not an InheritanceSpecifier Node') - return inheritsNode.children[0].attributes.name -} - -/** - * Returns the identifier of a variable definition, Throws on wrong node. - * Example: - * var x = 10; => x - * @varDeclNode {ASTNode} Variable declaration node - * @return {string} variable name - */ -function getDeclaredVariableName (varDeclNode) { - if (!isVariableDeclaration(varDeclNode)) throw new Error('staticAnalysisCommon.js: not a variable declaration') - return varDeclNode.attributes.name -} - -/** - * Returns state variable declaration nodes for a contract, Throws on wrong node. - * Example: - * contract foo { - * ... - * var y = true; - * var x = 10; => [y,x] - * @contractNode {ASTNode} Contract Definition node - * @return {list variable declaration} state variable node list - */ -function getStateVariableDeclarationsFormContractNode (contractNode) { - if (!isContractDefinition(contractNode)) throw new Error('staticAnalysisCommon.js: not a contract definition declaration') - if (!contractNode.children) return [] - return contractNode.children.filter((el) => isVariableDeclaration(el)) -} - -/** - * Returns parameter node for a function or modifier definition, Throws on wrong node. - * Example: - * function bar(uint a, uint b) => uint a, uint b - * @funcNode {ASTNode} Contract Definition node - * @return {parameterlist node} parameterlist node - */ -function getFunctionOrModifierDefinitionParameterPart (funcNode) { - if (!isFunctionDefinition(funcNode) && !isModifierDefinition(funcNode)) throw new Error('staticAnalysisCommon.js: not a function definition') - return funcNode.children[0] -} - -/** - * Returns return parameter node for a function or modifier definition, Throws on wrong node. - * Example: - * function bar(uint a, uint b) returns (bool a, bool b) => bool a, bool b - * @funcNode {ASTNode} Contract Definition node - * @return {parameterlist node} parameterlist node - */ -function getFunctionOrModifierDefinitionReturnParameterPart (funcNode) { - if (!isFunctionDefinition(funcNode) && !isModifierDefinition(funcNode)) throw new Error('staticAnalysisCommon.js: not a function definition') - return funcNode.children[1] -} - -/** - * Extracts the parameter types for a function type signature - * Example: - * function(uint a, uint b) returns (bool) => uint a, uint b - * @func {ASTNode} function call node - * @return {string} parameter signature - */ -function getFunctionCallTypeParameterType (func) { - return new RegExp(basicRegex.FUNCTIONSIGNATURE).exec(getFunctionCallType(func))[1] -} - -/** - * Returns the name of the library called, Throws on wrong node. - * Example: - * library set{...} - * contract foo { - * ... - * function () { set.union() => set} - * @funcCall {ASTNode} function call node - * @return {string} name of the lib defined - */ -function getLibraryCallContractName (funcCall) { - if (!isLibraryCall(funcCall)) throw new Error('staticAnalysisCommon.js: not an this library call Node') - return new RegExp(basicRegex.LIBRARYTYPE).exec(funcCall.children[0].attributes.type)[1] -} - -/** - * Returns the name of the function of a library call, Throws on wrong node. - * Example: - * library set{...} - * contract foo { - * ... - * function () { set.union() => uinion} - * @func {ASTNode} function call node - * @return {string} name of function called on the library - */ -function getLibraryCallMemberName (funcCall) { - if (!isLibraryCall(funcCall)) throw new Error('staticAnalysisCommon.js: not an library call Node') - return funcCall.attributes.member_name -} - -/** - * Returns full qualified name for a function call, Throws on wrong node. - * Example: - * contract foo { - * ... - * function bar(uint b) { } - * function baz() { - * bar(10) => foo.bar(uint) - * @func {ASTNode} function call node - * @func {ASTNode} contract defintion - * @return {string} full qualified identifier for the function call - */ -function getFullQualifiedFunctionCallIdent (contract, func) { - if (isLocalCall(func)) return getContractName(contract) + '.' + getLocalCallName(func) + '(' + getFunctionCallTypeParameterType(func) + ')' - else if (isThisLocalCall(func)) return getThisLocalCallContractName(func) + '.' + getThisLocalCallName(func) + '(' + getFunctionCallTypeParameterType(func) + ')' - else if (isSuperLocalCall(func)) return getContractName(contract) + '.' + getSuperLocalCallName(func) + '(' + getFunctionCallTypeParameterType(func) + ')' - else if (isExternalDirectCall(func)) return getExternalDirectCallContractName(func) + '.' + getExternalDirectCallMemberName(func) + '(' + getFunctionCallTypeParameterType(func) + ')' - else if (isLibraryCall(func)) return getLibraryCallContractName(func) + '.' + getLibraryCallMemberName(func) + '(' + getFunctionCallTypeParameterType(func) + ')' - else throw new Error('staticAnalysisCommon.js: Can not get function name form non function call node') -} - -function getFullQuallyfiedFuncDefinitionIdent (contract, func, paramTypes) { - return getContractName(contract) + '.' + getFunctionDefinitionName(func) + '(' + utils.concatWithSeperator(paramTypes, ',') + ')' -} - -// #################### Trivial Node Identification - -function isFunctionDefinition (node) { - return nodeType(node, exactMatch(nodeTypes.FUNCTIONDEFINITION)) -} - -function isModifierDefinition (node) { - return nodeType(node, exactMatch(nodeTypes.MODIFIERDEFINITION)) -} - -function isModifierInvocation (node) { - return nodeType(node, exactMatch(nodeTypes.MODIFIERINVOCATION)) -} - -function isVariableDeclaration (node) { - return nodeType(node, exactMatch(nodeTypes.VARIABLEDECLARATION)) -} - -function isReturn (node) { - return nodeType(node, exactMatch(nodeTypes.RETURN)) -} - -function isInheritanceSpecifier (node) { - return nodeType(node, exactMatch(nodeTypes.INHERITANCESPECIFIER)) -} - -function isAssignment (node) { - return nodeType(node, exactMatch(nodeTypes.ASSIGNMENT)) -} - -function isContractDefinition (node) { - return nodeType(node, exactMatch(nodeTypes.CONTRACTDEFINITION)) -} - -function isInlineAssembly (node) { - return nodeType(node, exactMatch(nodeTypes.INLINEASSEMBLY)) -} - -function isNewExpression (node) { - return nodeType(node, exactMatch(nodeTypes.NEWEXPRESSION)) -} - -// #################### Complex Node Identification - -/** - * True if function defintion has function body - * @funcNode {ASTNode} function defintion node - * @return {bool} - */ -function hasFunctionBody (funcNode) { - return findFirstSubNodeLTR(funcNode, exactMatch(nodeTypes.BLOCK)) != null -} - -/** - * True if call to code within the current contracts context including (delegate) library call - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isLocalCallGraphRelevantNode (node) { - return ((isLocalCall(node) || isSuperLocalCall(node) || isLibraryCall(node)) && !isBuiltinFunctionCall(node)) -} - -/** - * True if is builtin function like assert, sha3, erecover, ... - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isBuiltinFunctionCall (node) { - return isLocalCall(node) && builtinFunctions[getLocalCallName(node) + '(' + getFunctionCallTypeParameterType(node) + ')'] === true -} - -/** - * True if node is a call to selfdestruct - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isSelfdestructCall (node) { - return isBuiltinFunctionCall(node) && getLocalCallName(node) === 'selfdestruct' -} - -/** - * True if is storage variable declaration - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isStorageVariableDeclaration (node) { - return isVariableDeclaration(node) && expressionType(node, basicRegex.REFTYPE) -} - -/** - * True if is interaction with external contract (change in context, no delegate calls) (send, call of other contracts) - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isInteraction (node) { - return isLLCall(node) || isLLSend(node) || isExternalDirectCall(node) || isTransfer(node) -} - -/** - * True if node changes state of a variable or is inline assembly (does not include check if it is a global state change, on a state variable) - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isEffect (node) { - return isAssignment(node) || isPlusPlusUnaryOperation(node) || isMinusMinusUnaryOperation(node) || isInlineAssembly(node) -} - -/** - * True if node changes state of a variable or is inline assembly (Checks if variable is a state variable via provided list) - * @node {ASTNode} some AstNode - * @node {list Variable declaration} state variable declaration currently in scope - * @return {bool} - */ -function isWriteOnStateVariable (effectNode, stateVariables) { - return isInlineAssembly(effectNode) || (isEffect(effectNode) && isStateVariable(getEffectedVariableName(effectNode), stateVariables)) -} - -/** - * True if there is a variable with name, name in stateVariables - * @node {ASTNode} some AstNode - * @node {list Variable declaration} state variable declaration currently in scope - * @return {bool} - */ -function isStateVariable (name, stateVariables) { - return stateVariables.some((item) => name === getDeclaredVariableName(item)) -} - -/** - * True if is function defintion that is flaged as constant - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isConstantFunction (node) { - return isFunctionDefinition(node) && ( - node.attributes.constant === true || - node.attributes.stateMutability === 'view' || - node.attributes.stateMutability === 'pure' - ) -} - -/** - * True if is function defintion has payable modifier - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isPayableFunction (node) { - return isFunctionDefinition(node) && ( - node.attributes.stateMutability === 'payable' - ) -} - -/** - * True if unary increment operation - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isPlusPlusUnaryOperation (node) { - return nodeType(node, exactMatch(nodeTypes.UNARYOPERATION)) && operator(node, exactMatch(utils.escapeRegExp('++'))) -} - -/** - * True if unary decrement operation - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isMinusMinusUnaryOperation (node) { - return nodeType(node, exactMatch(nodeTypes.UNARYOPERATION)) && operator(node, exactMatch(utils.escapeRegExp('--'))) -} - -/** - * True if all functions on a contract are implemented - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isFullyImplementedContract (node) { - return nodeType(node, exactMatch(nodeTypes.CONTRACTDEFINITION)) && node.attributes.fullyImplemented === true -} - -/** - * True if it is a library contract defintion - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isLibrary (node) { - return nodeType(node, exactMatch(nodeTypes.CONTRACTDEFINITION)) && node.attributes.isLibrary === true -} - -/** - * True if it is a local call to non const function - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isCallToNonConstLocalFunction (node) { - return isLocalCall(node) && !expressionType(node.children[0], basicRegex.CONSTANTFUNCTIONTYPE) -} - -/** - * True if it is a call to a library - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isLibraryCall (node) { - return isMemberAccess(node, basicRegex.FUNCTIONTYPE, undefined, basicRegex.LIBRARYTYPE, undefined) -} - -/** - * True if it is an external call via defined interface (not low level call) - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isExternalDirectCall (node) { - return isMemberAccess(node, basicRegex.EXTERNALFUNCTIONTYPE, undefined, basicRegex.CONTRACTTYPE, undefined) && !isThisLocalCall(node) && !isSuperLocalCall(node) -} - -/** - * True if access to block.timestamp via now alias - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isNowAccess (node) { - return nodeType(node, exactMatch(nodeTypes.IDENTIFIER)) && - expressionType(node, exactMatch(basicTypes.UINT)) && - name(node, exactMatch('now')) -} - -/** - * True if access to block.timestamp - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isBlockTimestampAccess (node) { - return isSpecialVariableAccess(node, specialVariables.BLOCKTIMESTAMP) -} - -/** - * True if access to block.blockhash - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isBlockBlockHashAccess (node) { - return isSpecialVariableAccess(node, specialVariables.BLOCKHASH) -} - -/** - * True if call to local function via this keyword - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isThisLocalCall (node) { - return isMemberAccess(node, basicRegex.FUNCTIONTYPE, exactMatch('this'), basicRegex.CONTRACTTYPE, undefined) -} - -/** - * True if access to local function via super keyword - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isSuperLocalCall (node) { - return isMemberAccess(node, basicRegex.FUNCTIONTYPE, exactMatch('super'), basicRegex.CONTRACTTYPE, undefined) -} - -/** - * True if call to local function - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isLocalCall (node) { - return nodeType(node, exactMatch(nodeTypes.FUNCTIONCALL)) && - minNrOfChildren(node, 1) && - nodeType(node.children[0], exactMatch(nodeTypes.IDENTIFIER)) && - expressionType(node.children[0], basicRegex.FUNCTIONTYPE) && - !expressionType(node.children[0], basicRegex.EXTERNALFUNCTIONTYPE) && - nrOfChildren(node.children[0], 0) -} - -/** - * True if low level call (send, call, delegatecall, callcode) - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isLowLevelCall (node) { - return isLLCall(node) || - isLLCallcode(node) || - isLLDelegatecall(node) || - isLLSend(node) -} - -/** - * True if low level send - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isLLSend (node) { - return isMemberAccess(node, - exactMatch(utils.escapeRegExp(lowLevelCallTypes.SEND.type)), - undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.SEND.ident)) -} - -/** - * True if low level call - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isLLCall (node) { - return isMemberAccess(node, - exactMatch(utils.escapeRegExp(lowLevelCallTypes.CALL.type)), - undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.CALL.ident)) -} - -/** - * True if low level callcode - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isLLCallcode (node) { - return isMemberAccess(node, - exactMatch(utils.escapeRegExp(lowLevelCallTypes.CALLCODE.type)), - undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.CALLCODE.ident)) -} - -/** - * True if low level delegatecall - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isLLDelegatecall (node) { - return isMemberAccess(node, - exactMatch(utils.escapeRegExp(lowLevelCallTypes.DELEGATECALL.type)), - undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.DELEGATECALL.ident)) -} - -/** - * True if transfer call - * @node {ASTNode} some AstNode - * @return {bool} - */ -function isTransfer (node) { - return isMemberAccess(node, - exactMatch(utils.escapeRegExp(lowLevelCallTypes.TRANSFER.type)), - undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.TRANSFER.ident)) -} - -// #################### Complex Node Identification - Private - -function isMemberAccess (node, retType, accessor, accessorType, memberName) { - return nodeType(node, exactMatch(nodeTypes.MEMBERACCESS)) && - expressionType(node, retType) && - name(node, memberName) && - nrOfChildren(node, 1) && - name(node.children[0], accessor) && - expressionType(node.children[0], accessorType) -} - -function isSpecialVariableAccess (node, varType) { - return isMemberAccess(node, exactMatch(utils.escapeRegExp(varType.type)), varType.obj, varType.obj, varType.member) -} - -// #################### Node Identification Primitives - -function nrOfChildren (node, nr) { - return (node && (nr === undefined || nr === null)) || (node && nr === 0 && !node.children) || (node && node.children && node.children.length === nr) -} - -function minNrOfChildren (node, nr) { - return (node && (nr === undefined || nr === null)) || (node && nr === 0 && !node.children) || (node && node.children && node.children.length >= nr) -} - -function expressionType (node, typeRegex) { - return (node && !typeRegex) || (node && node.attributes && new RegExp(typeRegex).test(node.attributes.type)) -} - -function nodeType (node, typeRegex) { - return (node && !typeRegex) || (node && new RegExp(typeRegex).test(node.name)) -} - -function name (node, nameRegex) { - var regex = new RegExp(nameRegex) - return (node && !nameRegex) || (node && node.attributes && (regex.test(node.attributes.value) || regex.test(node.attributes.member_name))) -} - -function operator (node, opRegex) { - return (node && !opRegex) || (node && new RegExp(opRegex).test(node.attributes.operator)) -} - -// #################### Helpers - -function exactMatch (regexStr) { - return '^' + regexStr + '$' -} - -/** - * Builds an function signature as used in the AST of the solc-json AST - * @param {Array} paramTypes - * list of parameter type names - * @param {Array} returnTypes - * list of return type names - * @return {Boolean} isPayable - */ -function buildFunctionSignature (paramTypes, returnTypes, isPayable) { - return 'function (' + utils.concatWithSeperator(paramTypes, ',') + ')' + ((isPayable) ? ' payable' : '') + ((returnTypes.length) ? ' returns (' + utils.concatWithSeperator(returnTypes, ',') + ')' : '') -} - -/** - * Finds first node of a certain type under a specific node. - * @node {AstNode} node to start form - * @type {String} Type the ast node should have - * @return {AstNode} null or node found - */ -function findFirstSubNodeLTR (node, type) { - if (!node || !node.children) return null - for (let i = 0; i < node.children.length; ++i) { - var item = node.children[i] - if (nodeType(item, type)) return item - else { - var res = findFirstSubNodeLTR(item, type) - if (res) return res - } - } - return null -} - -module.exports = { - // #################### Trivial Getters - getType: getType, - // #################### Complex Getters - getThisLocalCallName: getThisLocalCallName, - getSuperLocalCallName: getSuperLocalCallName, - getFunctionCallType: getFunctionCallType, - getContractName: getContractName, - getEffectedVariableName: getEffectedVariableName, - getDeclaredVariableName: getDeclaredVariableName, - 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, - - // #################### Complex Node Identification - 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, - isLowLevelCallcodeInst: isLLCallcode, - isLowLevelDelegatecallInst: isLLDelegatecall, - isLowLevelSendInst: isLLSend, - isExternalDirectCall: isExternalDirectCall, - isFullyImplementedContract: isFullyImplementedContract, - isLibrary: isLibrary, - isCallToNonConstLocalFunction: isCallToNonConstLocalFunction, - isPlusPlusUnaryOperation: isPlusPlusUnaryOperation, - isMinusMinusUnaryOperation: isMinusMinusUnaryOperation, - isBuiltinFunctionCall: isBuiltinFunctionCall, - isSelfdestructCall: isSelfdestructCall, - - // #################### Trivial Node Identification - isFunctionDefinition: isFunctionDefinition, - isModifierDefinition: isModifierDefinition, - isInheritanceSpecifier: isInheritanceSpecifier, - isModifierInvocation: isModifierInvocation, - isVariableDeclaration: isVariableDeclaration, - isStorageVariableDeclaration: isStorageVariableDeclaration, - isAssignment: isAssignment, - isContractDefinition: isContractDefinition, - isConstantFunction: isConstantFunction, - isPayableFunction: isPayableFunction, - isInlineAssembly: isInlineAssembly, - isNewExpression: isNewExpression, - isReturn: isReturn, - - // #################### 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 - } -} diff --git a/src/app/staticanalysis/modules/thisLocal.js b/src/app/staticanalysis/modules/thisLocal.js deleted file mode 100644 index 6a43c65035..0000000000 --- a/src/app/staticanalysis/modules/thisLocal.js +++ /dev/null @@ -1,30 +0,0 @@ -var name = 'This on local calls: ' -var desc = 'Invocation of local functions via this' -var categories = require('./categories') -var common = require('./staticAnalysisCommon') -var yo = require('yo-yo') - -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: yo`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, - Module: thisLocal -} diff --git a/src/app/staticanalysis/modules/txOrigin.js b/src/app/staticanalysis/modules/txOrigin.js deleted file mode 100644 index df6346f449..0000000000 --- a/src/app/staticanalysis/modules/txOrigin.js +++ /dev/null @@ -1,36 +0,0 @@ -var name = 'Transaction origin: ' -var desc = 'Warn if tx.origin is used' -var categories = require('./categories') -var yo = require('yo-yo') - -function txOrigin () { - this.txOriginNodes = [] -} - -txOrigin.prototype.visit = function (node) { - if (node.name === 'MemberAccess' && - node.attributes.member_name === 'origin' && - node.attributes.type === 'address' && - 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(function (item, i) { - return { - warning: yo`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, - Module: txOrigin -} diff --git a/src/app/staticanalysis/staticAnalysisRunner.js b/src/app/staticanalysis/staticAnalysisRunner.js deleted file mode 100644 index 95fce3d013..0000000000 --- a/src/app/staticanalysis/staticAnalysisRunner.js +++ /dev/null @@ -1,44 +0,0 @@ -'use strict' -var AstWalker = require('remix-lib').AstWalker -var list = require('./modules/list') - -function staticAnalysisRunner () { -} - -staticAnalysisRunner.prototype.run = function (compilationResult, toRun, callback) { - var self = this - var modules = toRun.map(function (i) { - var m = self.modules()[i] - return { 'name': m.name, 'mod': new m.Module() } - }) - - this.runWithModuleList(compilationResult, modules, callback) -} - -staticAnalysisRunner.prototype.runWithModuleList = function (compilationResult, modules, callback) { - // Also provide convenience analysis via the AST walker. - var walker = new AstWalker() - for (var k in compilationResult.sources) { - walker.walk(compilationResult.sources[k].legacyAST, {'*': function (node) { - modules.map(function (item, i) { - if (item.mod.visit !== undefined) { - item.mod.visit(node) - } - }) - return true - }}) - } - - // Here, modules can just collect the results from the AST walk, - // but also perform new analysis. - var reports = modules.map(function (item, i) { - return { name: item.name, report: item.mod.report(compilationResult) } - }) - callback(reports) -} - -staticAnalysisRunner.prototype.modules = function () { - return list -} - -module.exports = staticAnalysisRunner diff --git a/src/app/staticanalysis/staticAnalysisView.js b/src/app/staticanalysis/staticAnalysisView.js index 6f9b96d328..1805f43e60 100644 --- a/src/app/staticanalysis/staticAnalysisView.js +++ b/src/app/staticanalysis/staticAnalysisView.js @@ -1,11 +1,11 @@ 'use strict' -var StaticAnalysisRunner = require('./staticAnalysisRunner.js') +var StaticAnalysisRunner = require('remix-solidity').CodeAnalysis var yo = require('yo-yo') var $ = require('jquery') -var utils = require('../../lib/utils') +var remixLib = require('remix-lib') +var utils = remixLib.util var csjs = require('csjs-inject') -var remixLib = require('remix-lib') var styleGuide = remixLib.ui.styleGuide var styles = styleGuide() diff --git a/src/lib/utils.js b/src/lib/utils.js deleted file mode 100644 index 678d288561..0000000000 --- a/src/lib/utils.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict' - -function groupBy (arr, key) { - return arr.reduce((sum, item) => { - const groupByVal = item[key] - var groupedItems = sum[groupByVal] || [] - groupedItems.push(item) - sum[groupByVal] = groupedItems - return sum - }, {}) -} - -function concatWithSeperator (list, seperator) { - return list.reduce((sum, item) => sum + item + seperator, '').slice(0, -seperator.length) -} - -function escapeRegExp (str) { - return str.replace(/[-[\]/{}()+?.\\^$|]/g, '\\$&') -} - -module.exports = { - groupBy: groupBy, - concatWithSeperator: concatWithSeperator, - escapeRegExp: escapeRegExp -} diff --git a/test-browser/tests/staticanalysis.js b/test-browser/tests/staticanalysis.js index d721a492f6..158de4b112 100644 --- a/test-browser/tests/staticanalysis.js +++ b/test-browser/tests/staticanalysis.js @@ -43,7 +43,7 @@ function runTests (browser) { .waitForElementPresent('#staticanalysisresult .warning', 2000, true, function () { dom.listSelectorContains(['browser/Untitled.sol:2:33:Use of tx.origin', 'Fallback function of contract TooMuchGas requires too much gas', - 'TooMuchGas.(): Variables have very similar names test and test1.'], + 'TooMuchGas.() : Variables have very similar names test and test1.'], '#staticanalysisresult .warning', browser, function () { browser.end() diff --git a/test/index.js b/test/index.js index 012067fe17..84d8a5c68f 100644 --- a/test/index.js +++ b/test/index.js @@ -3,6 +3,3 @@ require('./compiler-test') require('./gist-handler-test') require('./query-params-test') -require('./util-test') -require('./staticanalysis/staticAnalysisCommon-test') -require('./staticanalysis/staticAnalysisIntegration-test') diff --git a/test/staticanalysis/staticAnalysisCommon-test.js b/test/staticanalysis/staticAnalysisCommon-test.js deleted file mode 100644 index b9966326ef..0000000000 --- a/test/staticanalysis/staticAnalysisCommon-test.js +++ /dev/null @@ -1,1911 +0,0 @@ -var test = require('tape') - -var common = require('../../src/app/staticanalysis/modules/staticAnalysisCommon') -var utils = require('../../src/lib/utils') - -test('staticAnalysisCommon.helpers.buildFunctionSignature', function (t) { - t.plan(7) - - t.equal(common.helpers.buildFunctionSignature([common.basicTypes.UINT, common.basicTypes.ADDRESS], [common.basicTypes.BOOL], false), - 'function (uint256,address) returns (bool)', - 'two params and return value without payable') - - t.equal(common.helpers.buildFunctionSignature([common.basicTypes.UINT, common.basicTypes.BYTES32, common.basicTypes.BYTES32], [], true), - 'function (uint256,bytes32,bytes32) payable', - 'three params and no return with payable') - - t.equal(common.helpers.buildFunctionSignature([common.basicTypes.BOOL], [common.basicTypes.BYTES32, common.basicTypes.ADDRESS], true), - 'function (bool) payable returns (bytes32,address)', - 'one param and two return values with payable') - - t.equal(common.lowLevelCallTypes.CALL.type, - 'function () payable returns (bool)', - 'check fixed call type') - - t.equal(common.lowLevelCallTypes.CALLCODE.type, - 'function () payable returns (bool)', - 'check fixed callcode type') - - t.equal(common.lowLevelCallTypes.SEND.type, - 'function (uint256) returns (bool)', - 'check fixed send type') - - t.equal(common.lowLevelCallTypes.DELEGATECALL.type, - 'function () returns (bool)', - 'check fixed call type') -}) - -// #################### Node Identification Primitives - -test('staticAnalysisCommon.helpers.name', function (t) { - t.plan(9) - 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') - - lowlevelAccessersCommon(t, common.helpers.name, node) -}) - -test('staticAnalysisCommon.helpers.operator', function (t) { - t.plan(10) - var node = { attributes: { operator: '++' } } - var node2 = { attributes: { operator: '+++' } } - - var escapedPP = utils.escapeRegExp('++') - var escapedPPExact = `^${escapedPP}$` - - t.ok(common.helpers.operator(node, escapedPPExact), 'should work for ++') - t.notOk(common.helpers.operator(node2, escapedPPExact), 'should not work for +++') - t.ok(common.helpers.operator(node, escapedPP), 'should work for ++') - t.ok(common.helpers.operator(node2, escapedPP), 'should work for +++') - - lowlevelAccessersCommon(t, common.helpers.operator, node) -}) - -test('staticAnalysisCommon.helpers.nodeType', function (t) { - t.plan(9) - var node = { name: 'Identifier', attributes: { name: 'now' } } - var node2 = { name: 'FunctionCall', attributes: { member_name: 'call' } } - - t.ok(common.helpers.nodeType(node, common.nodeTypes.IDENTIFIER), 'should work for ident') - t.ok(common.helpers.nodeType(node2, common.nodeTypes.FUNCTIONCALL), 'should work for funcall') - t.ok(common.helpers.nodeType(node2, '^F'), 'regex should work for funcall') - - lowlevelAccessersCommon(t, common.helpers.nodeType, node) -}) - -test('staticAnalysisCommon.helpers.expressionType', function (t) { - t.plan(9) - var node = { name: 'Identifier', attributes: { value: 'now', type: 'uint256' } } - var node2 = { name: 'FunctionCall', attributes: { member_name: 'call', type: 'function () payable returns (bool)' } } - - t.ok(common.helpers.expressionType(node, common.basicTypes.UINT), 'should work for ident') - t.ok(common.helpers.expressionType(node2, utils.escapeRegExp(common.basicFunctionTypes.CALL)), 'should work for funcall') - t.ok(common.helpers.expressionType(node2, '^function \\('), 'regex should work') - - lowlevelAccessersCommon(t, common.helpers.expressionType, node) -}) - -test('staticAnalysisCommon.helpers.nrOfChildren', function (t) { - t.plan(10) - var node = { name: 'Identifier', children: ['a', 'b'], attributes: { value: 'now', type: 'uint256' } } - var node2 = { name: 'FunctionCall', children: [], attributes: { member_name: 'call', type: 'function () payable returns (bool)' } } - var node3 = { name: 'FunctionCall', attributes: { member_name: 'call', type: 'function () payable returns (bool)' } } - - t.ok(common.helpers.nrOfChildren(node, 2), 'should work for 2 children') - t.notOk(common.helpers.nrOfChildren(node, '1+2'), 'regex should not work') - t.ok(common.helpers.nrOfChildren(node2, 0), 'should work for 0 children') - t.ok(common.helpers.nrOfChildren(node3, 0), 'should work without children arr') - - lowlevelAccessersCommon(t, common.helpers.nrOfChildren, node) -}) - -test('staticAnalysisCommon.helpers.minNrOfChildren', function (t) { - t.plan(13) - var node = { name: 'Identifier', children: ['a', 'b'], attributes: { value: 'now', type: 'uint256' } } - var node2 = { name: 'FunctionCall', children: [], attributes: { member_name: 'call', type: 'function () payable returns (bool)' } } - var node3 = { name: 'FunctionCall', attributes: { member_name: 'call', type: 'function () payable returns (bool)' } } - - t.ok(common.helpers.minNrOfChildren(node, 2), 'should work for 2 children') - t.ok(common.helpers.minNrOfChildren(node, 1), 'should work for 1 children') - t.ok(common.helpers.minNrOfChildren(node, 0), 'should work for 0 children') - t.notOk(common.helpers.minNrOfChildren(node, 3), 'has less than 3 children') - t.notOk(common.helpers.minNrOfChildren(node, '1+2'), 'regex should not work') - t.ok(common.helpers.minNrOfChildren(node2, 0), 'should work for 0 children') - t.ok(common.helpers.minNrOfChildren(node3, 0), 'should work without children arr') - - lowlevelAccessersCommon(t, common.helpers.minNrOfChildren, node) -}) - -function lowlevelAccessersCommon (t, f, someNode) { - t.ok(f(someNode), 'always ok if type is undefinded') - t.ok(f(someNode, undefined), 'always ok if name is undefinded 2') - t.notOk(f(null, undefined), 'false on no node') - t.notOk(f(null, 'call'), 'false on no node') - t.notOk(f(undefined, null), 'false on no node') - t.notOk(f(), 'false on no params') -} - -// #################### Trivial Getter Test - -test('staticAnalysisCommon.getType', function (t) { - t.plan(3) - var node = { name: 'Identifier', children: ['a', 'b'], attributes: { value: 'now', type: 'uint256' } } - var node2 = { name: 'FunctionCall', children: [], attributes: { member_name: 'call', type: 'function () payable returns (bool)' } } - var node3 = { name: 'FunctionCall', attributes: { member_name: 'call', type: 'function () payable returns (bool)' } } - - t.ok(common.getType(node) === 'uint256', 'gettype should work for different nodes') - t.ok(common.getType(node2) === 'function () payable returns (bool)', 'gettype should work for different nodes') - t.ok(common.getType(node3) === 'function () payable returns (bool)', 'gettype should work for different nodes') -}) - -// #################### Complex Getter Test - -test('staticAnalysisCommon.getFunctionCallType', function (t) { - t.plan(5) - var localCall = { - 'attributes': { - 'type': 'tuple()', - 'type_conversion': false - }, - 'children': [ - { - 'attributes': { - 'type': 'function (struct Ballot.Voter storage pointer)', - 'value': 'bli' - }, - 'id': 37, - 'name': 'Identifier', - 'src': '540:3:0' - }, - { - 'attributes': { - 'type': 'struct Ballot.Voter storage pointer', - 'value': 'x' - }, - 'id': 38, - 'name': 'Identifier', - 'src': '544:1:0' - } - ], - 'id': 39, - 'name': 'FunctionCall', - 'src': '540:6:0' - } - var thisLocalCall = { name: 'MemberAccess', children: [ { attributes: { value: 'this', type: 'contract test' }, name: 'Identifier' } ], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } - var externalDirect = { - attributes: { - member_name: 'info', - type: 'function () payable external returns (uint256)' - }, - children: [ - { - attributes: { - type: 'contract InfoFeed', - value: 'f' - }, - id: 30, - name: 'Identifier', - src: '405:1:0' - } - ], - id: 32, - name: 'MemberAccess', - src: '405:6:0' - } - var libCall = { - 'attributes': { - 'member_name': 'insert', - 'type': 'function (struct Set.Data storage pointer,uint256) returns (bool)' - }, - 'children': [ - { - 'attributes': { - 'type': 'type(library Set)', - 'value': 'Set' - }, - 'name': 'Identifier' - } - ], - 'name': 'MemberAccess' - } - t.equal(common.getFunctionCallType(libCall), 'function (struct Set.Data storage pointer,uint256) returns (bool)', 'this lib call returns correct type') - t.equal(common.getFunctionCallType(thisLocalCall), 'function (bytes32,address) returns (bool)', 'this local call returns correct type') - t.equal(common.getFunctionCallType(externalDirect), 'function () payable external returns (uint256)', 'external direct call returns correct type') - t.equal(common.getFunctionCallType(localCall), 'function (struct Ballot.Voter storage pointer)', 'local call returns correct type') - t.throws(() => common.getFunctionCallType({ name: 'MemberAccess' }), undefined, 'throws on wrong type') -}) - -test('staticAnalysisCommon.getEffectedVariableName', function (t) { - t.plan(3) - var assignment = { - 'attributes': { - 'operator': '=', - 'type': 'uint256' - }, - 'children': [ - { - 'attributes': { - 'type': 'uint256' - }, - 'children': [ - { - 'attributes': { - 'type': 'mapping(address => uint256)', - 'value': 'c' - }, - 'id': 61, - 'name': 'Identifier', - 'src': '873:1:0' - }, - { - 'attributes': { - 'member_name': 'sender', - 'type': 'address' - }, - 'children': [ - { - 'attributes': { - 'type': 'msg', - 'value': 'msg' - }, - 'id': 62, - 'name': 'Identifier', - 'src': '875:3:0' - } - ], - 'id': 63, - 'name': 'MemberAccess', - 'src': '875:10:0' - } - ], - 'id': 64, - 'name': 'IndexAccess', - 'src': '873:13:0' - }, - { - 'attributes': { - 'hexvalue': '30', - 'subdenomination': null, - 'token': null, - 'type': 'int_const 0', - 'value': '0' - }, - 'id': 65, - 'name': 'Literal', - 'src': '889:1:0' - } - ], - 'id': 66, - 'name': 'Assignment', - 'src': '873:17:0' - } - var inlineAssembly = { - 'children': [ - ], - 'id': 21, - 'name': 'InlineAssembly', - 'src': '809:41:0' - } - t.throws(() => common.getEffectedVariableName(inlineAssembly), 'staticAnalysisCommon.js: not an effect Node or inline assembly', 'get from inline assembly should throw') - t.ok(common.getEffectedVariableName(assignment) === 'c', 'get right name for assignment') - t.throws(() => common.getEffectedVariableName({ name: 'MemberAccess' }), undefined, 'should throw on all other nodes') -}) - -test('staticAnalysisCommon.getLocalCallName', function (t) { - t.plan(3) - var localCall = { - 'attributes': { - 'type': 'tuple()', - 'type_conversion': false - }, - 'children': [ - { - 'attributes': { - 'type': 'function (struct Ballot.Voter storage pointer)', - 'value': 'bli' - }, - 'id': 37, - 'name': 'Identifier', - 'src': '540:3:0' - }, - { - 'attributes': { - 'type': 'struct Ballot.Voter storage pointer', - 'value': 'x' - }, - 'id': 38, - 'name': 'Identifier', - 'src': '544:1:0' - } - ], - 'id': 39, - 'name': 'FunctionCall', - 'src': '540:6:0' - } - var thisLocalCall = { name: 'MemberAccess', children: [ { attributes: { value: 'this', type: 'contract test' }, name: 'Identifier' } ], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } - var externalDirect = { - attributes: { - member_name: 'info', - type: 'function () payable external returns (uint256)' - }, - children: [ - { - attributes: { - type: 'contract InfoFeed', - value: 'f' - }, - id: 30, - name: 'Identifier', - src: '405:1:0' - } - ], - id: 32, - name: 'MemberAccess', - src: '405:6:0' - } - t.ok(common.getLocalCallName(localCall) === 'bli', 'getLocal call name from node') - t.throws(() => common.getLocalCallName(externalDirect), undefined, 'throws on other nodes') - t.throws(() => common.getLocalCallName(thisLocalCall), undefined, 'throws on other nodes') -}) - -test('staticAnalysisCommon.getThisLocalCallName', function (t) { - t.plan(3) - var localCall = { - 'attributes': { - 'type': 'tuple()', - 'type_conversion': false - }, - 'children': [ - { - 'attributes': { - 'type': 'function (struct Ballot.Voter storage pointer)', - 'value': 'bli' - }, - 'id': 37, - 'name': 'Identifier', - 'src': '540:3:0' - }, - { - 'attributes': { - 'type': 'struct Ballot.Voter storage pointer', - 'value': 'x' - }, - 'id': 38, - 'name': 'Identifier', - 'src': '544:1:0' - } - ], - 'id': 39, - 'name': 'FunctionCall', - 'src': '540:6:0' - } - var thisLocalCall = { name: 'MemberAccess', children: [ { attributes: { value: 'this', type: 'contract test' }, name: 'Identifier' } ], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } - var externalDirect = { - attributes: { - member_name: 'info', - type: 'function () payable external returns (uint256)' - }, - children: [ - { - attributes: { - type: 'contract InfoFeed', - value: 'f' - }, - id: 30, - name: 'Identifier', - src: '405:1:0' - } - ], - id: 32, - name: 'MemberAccess', - src: '405:6:0' - } - t.ok(common.getThisLocalCallName(thisLocalCall) === 'b', 'get this Local call name from node') - t.throws(() => common.getThisLocalCallName(externalDirect), undefined, 'throws on other nodes') - t.throws(() => common.getThisLocalCallName(localCall), undefined, 'throws on other nodes') -}) - -test('staticAnalysisCommon.getSuperLocalCallName', function (t) { - t.plan(4) - var superLocal = { - 'attributes': { - 'member_name': 'duper', - 'type': 'function ()' - }, - 'children': [ - { - 'attributes': { - 'type': 'contract super a', - 'value': 'super' - }, - 'name': 'Identifier' - } - ], - 'name': 'MemberAccess' - } - var localCall = { - 'attributes': { - 'type': 'tuple()', - 'type_conversion': false - }, - 'children': [ - { - 'attributes': { - 'type': 'function (struct Ballot.Voter storage pointer)', - 'value': 'bli' - }, - 'id': 37, - 'name': 'Identifier', - 'src': '540:3:0' - }, - { - 'attributes': { - 'type': 'struct Ballot.Voter storage pointer', - 'value': 'x' - }, - 'id': 38, - 'name': 'Identifier', - 'src': '544:1:0' - } - ], - 'id': 39, - 'name': 'FunctionCall', - 'src': '540:6:0' - } - var thisLocalCall = { name: 'MemberAccess', children: [ { attributes: { value: 'this', type: 'contract test' }, name: 'Identifier' } ], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } - var externalDirect = { - attributes: { - member_name: 'info', - type: 'function () payable external returns (uint256)' - }, - children: [ - { - attributes: { - type: 'contract InfoFeed', - value: 'f' - }, - id: 30, - name: 'Identifier', - src: '405:1:0' - } - ], - id: 32, - name: 'MemberAccess', - src: '405:6:0' - } - t.equal(common.getSuperLocalCallName(superLocal), 'duper', 'get local name from super local call') - t.throws(() => common.getSuperLocalCallName(thisLocalCall), 'throws on other nodes') - t.throws(() => common.getSuperLocalCallName(externalDirect), undefined, 'throws on other nodes') - t.throws(() => common.getSuperLocalCallName(localCall), undefined, 'throws on other nodes') -}) - -test('staticAnalysisCommon.getExternalDirectCallContractName', function (t) { - t.plan(3) - var localCall = { - 'attributes': { - 'type': 'tuple()', - 'type_conversion': false - }, - 'children': [ - { - 'attributes': { - 'type': 'function (struct Ballot.Voter storage pointer)', - 'value': 'bli' - }, - 'id': 37, - 'name': 'Identifier', - 'src': '540:3:0' - }, - { - 'attributes': { - 'type': 'struct Ballot.Voter storage pointer', - 'value': 'x' - }, - 'id': 38, - 'name': 'Identifier', - 'src': '544:1:0' - } - ], - 'id': 39, - 'name': 'FunctionCall', - 'src': '540:6:0' - } - var thisLocalCall = { name: 'MemberAccess', children: [ { attributes: { value: 'this', type: 'contract test' }, name: 'Identifier' } ], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } - var externalDirect = { - attributes: { - member_name: 'info', - type: 'function () payable external returns (uint256)' - }, - children: [ - { - attributes: { - type: 'contract InfoFeed', - value: 'f' - }, - id: 30, - name: 'Identifier', - src: '405:1:0' - } - ], - id: 32, - name: 'MemberAccess', - src: '405:6:0' - } - - t.ok(common.getExternalDirectCallContractName(externalDirect) === 'InfoFeed', 'external direct call contract name from node') - t.throws(() => common.getExternalDirectCallContractName(thisLocalCall), undefined, 'throws on other nodes') - t.throws(() => common.getExternalDirectCallContractName(localCall), undefined, 'throws on other nodes') -}) - -test('staticAnalysisCommon.getThisLocalCallContractName', function (t) { - t.plan(3) - var localCall = { - 'attributes': { - 'type': 'tuple()', - 'type_conversion': false - }, - 'children': [ - { - 'attributes': { - 'type': 'function (struct Ballot.Voter storage pointer)', - 'value': 'bli' - }, - 'id': 37, - 'name': 'Identifier', - 'src': '540:3:0' - }, - { - 'attributes': { - 'type': 'struct Ballot.Voter storage pointer', - 'value': 'x' - }, - 'id': 38, - 'name': 'Identifier', - 'src': '544:1:0' - } - ], - 'id': 39, - 'name': 'FunctionCall', - 'src': '540:6:0' - } - var thisLocalCall = { name: 'MemberAccess', children: [ { attributes: { value: 'this', type: 'contract test' }, name: 'Identifier' } ], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } - var externalDirect = { - attributes: { - member_name: 'info', - type: 'function () payable external returns (uint256)' - }, - children: [ - { - attributes: { - type: 'contract InfoFeed', - value: 'f' - }, - id: 30, - name: 'Identifier', - src: '405:1:0' - } - ], - id: 32, - name: 'MemberAccess', - src: '405:6:0' - } - t.ok(common.getThisLocalCallContractName(thisLocalCall) === 'test', 'this local call contract name from node') - t.throws(() => common.getThisLocalCallContractName(localCall), undefined, 'throws on other nodes') - t.throws(() => common.getThisLocalCallContractName(externalDirect), undefined, 'throws on other nodes') -}) - -test('staticAnalysisCommon.getExternalDirectCallMemberName', function (t) { - t.plan(3) - var localCall = { - 'attributes': { - 'type': 'tuple()', - 'type_conversion': false - }, - 'children': [ - { - 'attributes': { - 'type': 'function (struct Ballot.Voter storage pointer)', - 'value': 'bli' - }, - 'id': 37, - 'name': 'Identifier', - 'src': '540:3:0' - }, - { - 'attributes': { - 'type': 'struct Ballot.Voter storage pointer', - 'value': 'x' - }, - 'id': 38, - 'name': 'Identifier', - 'src': '544:1:0' - } - ], - 'id': 39, - 'name': 'FunctionCall', - 'src': '540:6:0' - } - var thisLocalCall = { name: 'MemberAccess', children: [ { attributes: { value: 'this', type: 'contract test' }, name: 'Identifier' } ], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } - var externalDirect = { - attributes: { - member_name: 'info', - type: 'function () payable external returns (uint256)' - }, - children: [ - { - attributes: { - type: 'contract InfoFeed', - value: 'f' - }, - id: 30, - name: 'Identifier', - src: '405:1:0' - } - ], - id: 32, - name: 'MemberAccess', - src: '405:6:0' - } - t.ok(common.getExternalDirectCallMemberName(externalDirect) === 'info', 'external direct call name from node') - t.throws(() => common.getExternalDirectCallMemberName(thisLocalCall), undefined, 'throws on other nodes') - t.throws(() => common.getExternalDirectCallMemberName(localCall), undefined, 'throws on other nodes') -}) - -test('staticAnalysisCommon.getContractName', function (t) { - t.plan(2) - var contract = { name: 'ContractDefinition', attributes: { name: 'baz' } } - t.ok(common.getContractName(contract) === 'baz', 'returns right contract name') - t.throws(() => common.getContractName({ name: 'InheritanceSpecifier' }), undefined, 'throws on other nodes') -}) - -test('staticAnalysisCommon.getFunctionDefinitionName', function (t) { - t.plan(2) - var func = { name: 'FunctionDefinition', attributes: { name: 'foo' } } - t.ok(common.getFunctionDefinitionName(func) === 'foo', 'returns right contract name') - t.throws(() => common.getFunctionDefinitionName({ name: 'InlineAssembly' }), undefined, 'throws on other nodes') -}) - -test('staticAnalysisCommon.getInheritsFromName', function (t) { - t.plan(2) - var inh = { - 'children': [ - { - 'attributes': { - 'name': 'r' - }, - 'id': 7, - 'name': 'UserDefinedTypeName', - 'src': '84:1:0' - } - ], - 'id': 8, - 'name': 'InheritanceSpecifier', - 'src': '84:1:0' - } - t.ok(common.getInheritsFromName(inh) === 'r', 'returns right contract name') - t.throws(() => common.getInheritsFromName({ name: 'ElementaryTypeName' }), undefined, 'throws on other nodes') -}) - -test('staticAnalysisCommon.getDeclaredVariableName', function (t) { - t.plan(2) - var node1 = { - 'attributes': { - 'name': 'x', - 'type': 'struct Ballot.Voter storage pointer' - }, - 'children': [ - { - 'attributes': { - 'name': 'Voter' - }, - 'id': 43, - 'name': 'UserDefinedTypeName', - 'src': '604:5:0' - } - ], - 'id': 44, - 'name': 'VariableDeclaration', - 'src': '604:15:0' - } - t.ok(common.getDeclaredVariableName(node1) === 'x', 'extract right variable name') - node1.name = 'FunctionCall' - t.throws(() => common.getDeclaredVariableName(node1) === 'x', undefined, 'throw if wrong node') -}) - -test('staticAnalysisCommon.getStateVariableDeclarationsFormContractNode', function (t) { - t.plan(4) - var contract = { - 'attributes': { - 'fullyImplemented': true, - 'isLibrary': false, - 'linearizedBaseContracts': [ - 274 - ], - 'name': 'Ballot' - }, - 'children': [ - { - 'attributes': { - 'name': 'Voter' - }, - 'children': [], - 'name': 'StructDefinition' - }, - { - 'attributes': { - 'name': 'Proposal' - }, - 'children': [], - 'name': 'StructDefinition' - }, - { - 'attributes': { - 'name': 'chairperson', - 'type': 'address' - }, - 'children': [ - { - 'attributes': { - 'name': 'address' - }, - 'name': 'ElementaryTypeName' - } - ], - 'name': 'VariableDeclaration' - }, - { - 'attributes': { - 'name': 'voters', - 'type': 'mapping(address => struct Ballot.Voter storage ref)' - }, - 'children': [ - { - 'children': [ - { - 'attributes': { - 'name': 'address' - }, - 'name': 'ElementaryTypeName' - }, - { - 'attributes': { - 'name': 'Voter' - }, - 'name': 'UserDefinedTypeName' - } - ], - 'name': 'Mapping' - } - ], - 'name': 'VariableDeclaration' - }, - { - 'attributes': { - 'name': 'proposals', - 'type': 'struct Ballot.Proposal storage ref[] storage ref' - }, - 'children': [ - { - 'children': [ - { - 'attributes': { - 'name': 'Proposal' - }, - 'name': 'UserDefinedTypeName' - } - ], - 'name': 'ArrayTypeName' - } - ], - 'name': 'VariableDeclaration' - }, - { - 'attributes': { - 'constant': false, - 'name': 'Ballot', - 'payable': false, - 'visibility': 'public' - }, - 'children': [], - 'name': 'FunctionDefinition' - }, - { - 'attributes': { - 'constant': false, - 'name': 'giveRightToVote', - 'payable': false, - 'visibility': 'public' - }, - 'children': [], - 'name': 'FunctionDefinition' - } - ], - 'name': 'ContractDefinition' - } - var res = common.getStateVariableDeclarationsFormContractNode(contract).map(common.getDeclaredVariableName) - - t.ok(res[0] === 'chairperson', 'var 1 should be ') - t.ok(res[1] === 'voters', 'var 2 should be ') - t.ok(res[2] === 'proposals', 'var 3 should be ') - t.ok(res[3] === undefined, 'var 4 should be undefined') -}) - -test('staticAnalysisCommon.getFunctionOrModifierDefinitionParameterPart', function (t) { - t.plan(2) - var funDef = { - 'attributes': { - 'constant': true, - 'name': 'winnerName', - 'payable': false, - 'visibility': 'public' - }, - 'children': [ - { - 'children': [ - ], - 'name': 'ParameterList' - }, - { - 'children': [], - 'name': 'ParameterList' - }, - { - 'children': [], - 'name': 'Block' - } - ], - 'name': 'FunctionDefinition' - } - t.ok(common.helpers.nodeType(common.getFunctionOrModifierDefinitionParameterPart(funDef), 'ParameterList'), 'should return a parameterList') - t.throws(() => common.getFunctionOrModifierDefinitionParameterPart({ name: 'SourceUnit' }), undefined, 'throws on other nodes') -}) - -test('staticAnalysisCommon.getFunctionCallTypeParameterType', function (t) { - t.plan(4) - var localCall = { - 'attributes': { - 'type': 'tuple()', - 'type_conversion': false - }, - 'children': [ - { - 'attributes': { - 'type': 'function (struct Ballot.Voter storage pointer)', - 'value': 'bli' - }, - 'id': 37, - 'name': 'Identifier', - 'src': '540:3:0' - }, - { - 'attributes': { - 'type': 'struct Ballot.Voter storage pointer', - 'value': 'x' - }, - 'id': 38, - 'name': 'Identifier', - 'src': '544:1:0' - } - ], - 'id': 39, - 'name': 'FunctionCall', - 'src': '540:6:0' - } - var thisLocalCall = { name: 'MemberAccess', children: [ { attributes: { value: 'this', type: 'contract test' }, name: 'Identifier' } ], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } - var externalDirect = { - attributes: { - member_name: 'info', - type: 'function () payable external returns (uint256)' - }, - children: [ - { - attributes: { - type: 'contract InfoFeed', - value: 'f' - }, - id: 30, - name: 'Identifier', - src: '405:1:0' - } - ], - id: 32, - name: 'MemberAccess', - src: '405:6:0' - } - t.ok(common.getFunctionCallTypeParameterType(thisLocalCall) === 'bytes32,address', 'this local call returns correct type') - t.ok(common.getFunctionCallTypeParameterType(externalDirect) === '', 'external direct call returns correct type') - t.ok(common.getFunctionCallTypeParameterType(localCall) === 'struct Ballot.Voter storage pointer', 'local call returns correct type') - t.throws(() => common.getFunctionCallTypeParameterType({ name: 'MemberAccess' }), undefined, 'throws on wrong type') -}) - -test('staticAnalysisCommon.getLibraryCallContractName', function (t) { - t.plan(2) - var node = { - 'attributes': { - 'member_name': 'insert', - 'type': 'function (struct Set.Data storage pointer,uint256) returns (bool)' - }, - 'children': [ - { - 'attributes': { - 'type': 'type(library Set)', - 'value': 'Set' - }, - 'name': 'Identifier' - } - ], - 'name': 'MemberAccess' - } - t.equal(common.getLibraryCallContractName(node), 'Set', 'should return correct contract name') - t.throws(() => common.getLibraryCallContractName({ name: 'Identifier' }), undefined, 'should throw on wrong node') -}) - -test('staticAnalysisCommon.getLibraryCallMemberName', function (t) { - t.plan(2) - var node = { - 'attributes': { - 'member_name': 'insert', - 'type': 'function (struct Set.Data storage pointer,uint256) returns (bool)' - }, - 'children': [ - { - 'attributes': { - 'type': 'type(library Set)', - 'value': 'Set' - }, - 'name': 'Identifier' - } - ], - 'name': 'MemberAccess' - } - t.equal(common.getLibraryCallMemberName(node), 'insert', 'should return correct member name') - t.throws(() => common.getLibraryCallMemberName({ name: 'Identifier' }), undefined, 'should throw on wrong node') -}) - -test('staticAnalysisCommon.getFullQualifiedFunctionCallIdent', function (t) { - t.plan(4) - var contract = { name: 'ContractDefinition', attributes: { name: 'baz' } } - var localCall = { - 'attributes': { - 'type': 'tuple()', - 'type_conversion': false - }, - 'children': [ - { - 'attributes': { - 'type': 'function (struct Ballot.Voter storage pointer)', - 'value': 'bli' - }, - 'id': 37, - 'name': 'Identifier', - 'src': '540:3:0' - }, - { - 'attributes': { - 'type': 'struct Ballot.Voter storage pointer', - 'value': 'x' - }, - 'id': 38, - 'name': 'Identifier', - 'src': '544:1:0' - } - ], - 'id': 39, - 'name': 'FunctionCall', - 'src': '540:6:0' - } - var thisLocalCall = { name: 'MemberAccess', children: [ { attributes: { value: 'this', type: 'contract test' }, name: 'Identifier' } ], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } - var externalDirect = { - attributes: { - member_name: 'info', - type: 'function () payable external returns (uint256)' - }, - children: [ - { - attributes: { - type: 'contract InfoFeed', - value: 'f' - }, - id: 30, - name: 'Identifier', - src: '405:1:0' - } - ], - id: 32, - name: 'MemberAccess', - src: '405:6:0' - } - - t.ok(common.getFullQualifiedFunctionCallIdent(contract, thisLocalCall) === 'test.b(bytes32,address)', 'this local call returns correct type') - t.ok(common.getFullQualifiedFunctionCallIdent(contract, externalDirect) === 'InfoFeed.info()', 'external direct call returns correct type') - t.ok(common.getFullQualifiedFunctionCallIdent(contract, localCall) === 'baz.bli(struct Ballot.Voter storage pointer)', 'local call returns correct type') - t.throws(() => common.getFullQualifiedFunctionCallIdent(contract, { name: 'MemberAccess' }), undefined, 'throws on wrong type') -}) - -test('staticAnalysisCommon.getFullQuallyfiedFuncDefinitionIdent', function (t) { - t.plan(3) - var contract = { name: 'ContractDefinition', attributes: { name: 'baz' } } - var funDef = { - 'attributes': { - 'constant': false, - 'name': 'getY', - 'payable': false, - 'visibility': 'public' - }, - 'children': [ - { - 'children': [ - { - 'attributes': { - 'name': 'z', - 'type': 'uint256' - }, - 'children': [ - { - 'attributes': { - 'name': 'uint' - }, - 'name': 'ElementaryTypeName' - } - ], - 'name': 'VariableDeclaration' - }, - { - 'attributes': { - 'name': 'r', - 'type': 'bool' - }, - 'children': [ - { - 'attributes': { - 'name': 'bool' - }, - 'name': 'ElementaryTypeName' - } - ], - 'name': 'VariableDeclaration' - } - ], - 'name': 'ParameterList' - }, - { - 'children': [ - { - 'attributes': { - 'name': '', - 'type': 'uint256' - }, - 'children': [ - { - 'attributes': { - 'name': 'uint' - }, - 'id': 34, - 'name': 'ElementaryTypeName', - 'src': '285:4:0' - } - ], - 'id': 35, - 'name': 'VariableDeclaration', - 'src': '285:4:0' - } - ], - 'name': 'ParameterList' - }, - { - 'children': [], - 'name': 'Block' - } - ], - 'name': 'FunctionDefinition' - } - t.ok(common.getFullQuallyfiedFuncDefinitionIdent(contract, funDef, ['uint256', 'bool']) === 'baz.getY(uint256,bool)', 'creates right signature') - t.throws(() => common.getFullQuallyfiedFuncDefinitionIdent(contract, { name: 'MemberAccess' }, ['uint256', 'bool']), undefined, 'throws on wrong nodes') - t.throws(() => common.getFullQuallyfiedFuncDefinitionIdent({ name: 'FunctionCall' }, funDef, ['uint256', 'bool']), undefined, 'throws on wrong nodes') -}) - -// #################### Trivial Node Identification - -test('staticAnalysisCommon.isFunctionDefinition', function (t) { - t.plan(3) - var node1 = { name: 'FunctionDefinition' } - var node2 = { name: 'MemberAccess' } - var node3 = { name: 'FunctionDefinitionBLABLA' } - - t.ok(common.isFunctionDefinition(node1), 'is exact match should work') - t.notOk(common.isFunctionDefinition(node2), 'different node should not work') - t.notOk(common.isFunctionDefinition(node3), 'substring should not work') -}) - -test('staticAnalysisCommon.isModifierDefinition', function (t) { - t.plan(3) - var node1 = { name: 'ModifierDefinition' } - var node2 = { name: 'MemberAccess' } - var node3 = { name: 'ModifierDefinitionBLABLA' } - - t.ok(common.isModifierDefinition(node1), 'is exact match should work') - t.notOk(common.isModifierDefinition(node2), 'different node should not work') - t.notOk(common.isModifierDefinition(node3), 'substring should not work') -}) - -test('staticAnalysisCommon.isModifierInvocation', function (t) { - t.plan(3) - var node1 = { name: 'ModifierInvocation' } - var node2 = { name: 'MemberAccess' } - var node3 = { name: 'ModifierInvocationBLABLA' } - - t.ok(common.isModifierInvocation(node1), 'is exact match should work') - t.notOk(common.isModifierInvocation(node2), 'different node should not work') - t.notOk(common.isModifierInvocation(node3), 'substring should not work') -}) - -test('staticAnalysisCommon.isVariableDeclaration', function (t) { - t.plan(3) - var node1 = { name: 'VariableDeclaration' } - var node2 = { name: 'MemberAccess' } - var node3 = { name: 'VariableDeclarationBLABLA' } - - t.ok(common.isVariableDeclaration(node1), 'is exact match should work') - t.notOk(common.isVariableDeclaration(node2), 'different node should not work') - t.notOk(common.isVariableDeclaration(node3), 'substring should not work') -}) - -test('staticAnalysisCommon.isInheritanceSpecifier', function (t) { - t.plan(3) - var node1 = { name: 'InheritanceSpecifier' } - var node2 = { name: 'MemberAccess' } - var node3 = { name: 'InheritanceSpecifierBLABLA' } - - t.ok(common.isInheritanceSpecifier(node1), 'is exact match should work') - t.notOk(common.isInheritanceSpecifier(node2), 'different node should not work') - t.notOk(common.isInheritanceSpecifier(node3), 'substring should not work') -}) - -test('staticAnalysisCommon.isAssignment', function (t) { - t.plan(3) - var node1 = { name: 'Assignment' } - var node2 = { name: 'MemberAccess' } - var node3 = { name: 'AssignmentBLABLA' } - - t.ok(common.isAssignment(node1), 'is exact match should work') - t.notOk(common.isAssignment(node2), 'different node should not work') - t.notOk(common.isAssignment(node3), 'substring should not work') -}) - -test('staticAnalysisCommon.isContractDefinition', function (t) { - t.plan(3) - var node1 = { name: 'ContractDefinition' } - var node2 = { name: 'MemberAccess' } - var node3 = { name: 'ContractDefinitionBLABLA' } - - t.ok(common.isContractDefinition(node1), 'is exact match should work') - t.notOk(common.isContractDefinition(node2), 'different node should not work') - t.notOk(common.isContractDefinition(node3), 'substring should not work') -}) - -test('staticAnalysisCommon.isInlineAssembly', function (t) { - t.plan(3) - var node1 = { name: 'InlineAssembly' } - var node2 = { name: 'MemberAccess' } - var node3 = { name: 'InlineAssemblyBLABLA' } - - t.ok(common.isInlineAssembly(node1), 'is exact match should work') - t.notOk(common.isInlineAssembly(node2), 'different node should not work') - t.notOk(common.isInlineAssembly(node3), 'substring should not work') -}) - -// #################### Complex Node Identification - -test('staticAnalysisCommon.isBuiltinFunctionCall', function (t) { - t.plan(2) - var selfdestruct = { - 'attributes': { - 'type': 'tuple()', - 'type_conversion': false - }, - 'children': [ - { - 'attributes': { - 'type': 'function (address)', - 'value': 'selfdestruct' - }, - 'name': 'Identifier' - }, - { - 'attributes': { - 'type': 'address', - 'value': 'a' - }, - 'name': 'Identifier' - } - ], - 'name': 'FunctionCall' - } - var localCall = { - 'attributes': { - 'type': 'tuple()', - 'type_conversion': false - }, - 'children': [ - { - 'attributes': { - 'type': 'function (struct Ballot.Voter storage pointer)', - 'value': 'bli' - }, - 'name': 'Identifier' - }, - { - 'attributes': { - 'type': 'struct Ballot.Voter storage pointer', - 'value': 'x' - }, - 'name': 'Identifier' - } - ], - 'name': 'FunctionCall' - } - - t.ok(common.isBuiltinFunctionCall(selfdestruct), 'selfdestruct is builtin') - t.notOk(common.isBuiltinFunctionCall(localCall), 'local call is not builtin') -}) - -test('staticAnalysisCommon.isStorageVariableDeclaration', function (t) { - t.plan(3) - var node1 = { - 'attributes': { - 'name': 'x', - 'type': 'struct Ballot.Voter storage pointer' - }, - 'children': [ - { - 'attributes': { - 'name': 'Voter' - }, - 'id': 43, - 'name': 'UserDefinedTypeName', - 'src': '604:5:0' - } - ], - 'id': 44, - 'name': 'VariableDeclaration', - 'src': '604:15:0' - } - var node2 = { - 'attributes': { - 'name': 'voters', - 'type': 'mapping(address => struct Ballot.Voter storage ref)' - }, - 'children': [ - { - 'children': [ - { - 'attributes': { - 'name': 'address' - }, - 'id': 16, - 'name': 'ElementaryTypeName', - 'src': '235:7:0' - }, - { - 'attributes': { - 'name': 'Voter' - }, - 'id': 17, - 'name': 'UserDefinedTypeName', - 'src': '246:5:0' - } - ], - 'id': 18, - 'name': 'Mapping', - 'src': '227:25:0' - } - ], - 'id': 19, - 'name': 'VariableDeclaration', - 'src': '227:32:0' - } - var node3 = { - 'attributes': { - 'name': 'voters', - 'type': 'bytes32' - }, - 'children': [ - { - 'attributes': { - 'name': 'bytes' - }, - 'id': 16, - 'name': 'ElementaryTypeName', - 'src': '235:7:0' - } - ], - 'id': 19, - 'name': 'VariableDeclaration', - 'src': '227:32:0' - } - - t.ok(common.isStorageVariableDeclaration(node1), 'struct storage pointer param is storage') - t.ok(common.isStorageVariableDeclaration(node2), 'struct storage pointer mapping param is storage') - t.notOk(common.isStorageVariableDeclaration(node3), 'bytes is not storage') -}) - -test('staticAnalysisCommon.isInteraction', function (t) { - t.plan(6) - var sendAst = { name: 'MemberAccess', children: [{attributes: { value: 'd', type: 'address' }}], attributes: { value: 'send', type: 'function (uint256) returns (bool)' } } - var callAst = { name: 'MemberAccess', children: [{attributes: { value: 'f', type: 'address' }}], attributes: { member_name: 'call', type: 'function () payable returns (bool)' } } - var callcodeAst = { name: 'MemberAccess', children: [{attributes: { value: 'f', type: 'address' }}], attributes: { member_name: 'callcode', type: 'function () payable returns (bool)' } } - var delegatecallAst = { name: 'MemberAccess', children: [{attributes: { value: 'g', type: 'address' }}], attributes: { member_name: 'delegatecall', type: 'function () returns (bool)' } } - var nodeExtDir = { - attributes: { - member_name: 'info', - type: 'function () payable external returns (uint256)' - }, - children: [ - { - attributes: { - type: 'contract InfoFeed', - value: 'f' - }, - id: 30, - name: 'Identifier', - src: '405:1:0' - } - ], - id: 32, - name: 'MemberAccess', - src: '405:6:0' - } - var nodeNot = { - 'attributes': { - 'type': 'tuple()', - 'type_conversion': false - }, - 'children': [ - { - 'attributes': { - 'type': 'function (struct Ballot.Voter storage pointer)', - 'value': 'bli' - }, - 'id': 37, - 'name': 'Identifier', - 'src': '540:3:0' - }, - { - 'attributes': { - 'type': 'struct Ballot.Voter storage pointer', - 'value': 'x' - }, - 'id': 38, - 'name': 'Identifier', - 'src': '544:1:0' - } - ], - 'id': 39, - 'name': 'FunctionCall', - 'src': '540:6:0' - } - - t.ok(common.isInteraction(sendAst), 'send is interaction') - t.ok(common.isInteraction(callAst), 'call is interaction') - t.ok(common.isInteraction(nodeExtDir), 'ExternalDirecCall is interaction') - t.notOk(common.isInteraction(callcodeAst), 'callcode is not interaction') - t.notOk(common.isInteraction(delegatecallAst), 'callcode is not interaction') - t.notOk(common.isInteraction(nodeNot), 'local call is not interaction') -}) - -test('staticAnalysisCommon.isEffect', function (t) { - t.plan(5) - var inlineAssembly = { - 'children': [ - ], - 'id': 21, - 'name': 'InlineAssembly', - 'src': '809:41:0' - } - var assignment = { - 'attributes': { - 'operator': '=', - 'type': 'uint256' - }, - 'children': [ - { - 'attributes': { - 'type': 'uint256' - }, - 'children': [ - { - 'attributes': { - 'type': 'mapping(address => uint256)', - 'value': 'c' - }, - 'id': 61, - 'name': 'Identifier', - 'src': '873:1:0' - }, - { - 'attributes': { - 'member_name': 'sender', - 'type': 'address' - }, - 'children': [ - { - 'attributes': { - 'type': 'msg', - 'value': 'msg' - }, - 'id': 62, - 'name': 'Identifier', - 'src': '875:3:0' - } - ], - 'id': 63, - 'name': 'MemberAccess', - 'src': '875:10:0' - } - ], - 'id': 64, - 'name': 'IndexAccess', - 'src': '873:13:0' - }, - { - 'attributes': { - 'hexvalue': '30', - 'subdenomination': null, - 'token': null, - 'type': 'int_const 0', - 'value': '0' - }, - 'id': 65, - 'name': 'Literal', - 'src': '889:1:0' - } - ], - 'id': 66, - 'name': 'Assignment', - 'src': '873:17:0' - } - var unaryOp = { name: 'UnaryOperation', attributes: { operator: '++' } } - t.ok(common.isEffect(inlineAssembly), 'inline assembly is treated as effect') - t.ok(common.isEffect(assignment), 'assignment is treated as effect') - t.ok(common.isEffect(unaryOp), '++ is treated as effect') - unaryOp.attributes.operator = '--' - t.ok(common.isEffect(unaryOp), '-- is treated as effect') - t.notOk(common.isEffect({ name: 'MemberAccess', attributes: { operator: '++' } }), 'MemberAccess not treated as effect') -}) - -test('staticAnalysisCommon.isWriteOnStateVariable', function (t) { - t.plan(3) - var inlineAssembly = { - 'children': [ - ], - 'id': 21, - 'name': 'InlineAssembly', - 'src': '809:41:0' - } - var assignment = { - 'attributes': { - 'operator': '=', - 'type': 'uint256' - }, - 'children': [ - { - 'attributes': { - 'type': 'uint256' - }, - 'children': [ - { - 'attributes': { - 'type': 'mapping(address => uint256)', - 'value': 'c' - }, - 'id': 61, - 'name': 'Identifier', - 'src': '873:1:0' - }, - { - 'attributes': { - 'member_name': 'sender', - 'type': 'address' - }, - 'children': [ - { - 'attributes': { - 'type': 'msg', - 'value': 'msg' - }, - 'id': 62, - 'name': 'Identifier', - 'src': '875:3:0' - } - ], - 'id': 63, - 'name': 'MemberAccess', - 'src': '875:10:0' - } - ], - 'id': 64, - 'name': 'IndexAccess', - 'src': '873:13:0' - }, - { - 'attributes': { - 'hexvalue': '30', - 'subdenomination': null, - 'token': null, - 'type': 'int_const 0', - 'value': '0' - }, - 'id': 65, - 'name': 'Literal', - 'src': '889:1:0' - } - ], - 'id': 66, - 'name': 'Assignment', - 'src': '873:17:0' - } - var node1 = { - 'attributes': { - 'name': 'x', - 'type': 'struct Ballot.Voter storage pointer' - }, - 'children': [ - { - 'attributes': { - 'name': 'Voter' - }, - 'name': 'UserDefinedTypeName' - } - ], - 'name': 'VariableDeclaration' - } - var node2 = { - 'attributes': { - 'name': 'y', - 'type': 'uint' - }, - 'children': [ - { - 'attributes': { - 'name': 'Voter' - }, - 'name': 'UserDefinedTypeName' - } - ], - 'name': 'VariableDeclaration' - } - var node3 = { - 'attributes': { - 'name': 'xx', - 'type': 'uint' - }, - 'children': [ - { - 'attributes': { - 'name': 'Voter' - }, - 'name': 'UserDefinedTypeName' - } - ], - 'name': 'VariableDeclaration' - } - t.ok(common.isWriteOnStateVariable(inlineAssembly, [node1, node2, node3]), 'inline Assembly is write on state') - t.notOk(common.isWriteOnStateVariable(assignment, [node1, node2, node3]), 'assignment on non state is not write on state') - node3.attributes.name = 'c' - t.ok(common.isWriteOnStateVariable(assignment, [node1, node2, node3]), 'assignment on state is not write on state') -}) - -test('staticAnalysisCommon.isStateVariable', function (t) { - t.plan(3) - var node1 = { - 'attributes': { - 'name': 'x', - 'type': 'struct Ballot.Voter storage pointer' - }, - 'children': [ - { - 'attributes': { - 'name': 'Voter' - }, - 'name': 'UserDefinedTypeName' - } - ], - 'name': 'VariableDeclaration' - } - var node2 = { - 'attributes': { - 'name': 'y', - 'type': 'uint' - }, - 'children': [ - { - 'attributes': { - 'name': 'Voter' - }, - 'name': 'UserDefinedTypeName' - } - ], - 'name': 'VariableDeclaration' - } - var node3 = { - 'attributes': { - 'name': 'xx', - 'type': 'uint' - }, - 'children': [ - { - 'attributes': { - 'name': 'Voter' - }, - 'name': 'UserDefinedTypeName' - } - ], - 'name': 'VariableDeclaration' - } - - t.ok(common.isStateVariable('x', [node1, node2]), 'is contained') - t.ok(common.isStateVariable('x', [node2, node1, node1]), 'is contained twice') - t.notOk(common.isStateVariable('x', [node2, node3]), 'not contained') -}) - -test('staticAnalysisCommon.isConstantFunction', function (t) { - t.plan(3) - var node1 = { name: 'FunctionDefinition', attributes: { constant: true } } - var node2 = { name: 'FunctionDefinition', attributes: { constant: false } } - var node3 = { name: 'MemberAccess', attributes: { constant: true } } - - t.ok(common.isConstantFunction(node1), 'should be const func definition') - t.notOk(common.isConstantFunction(node2), 'should not be const func definition') - t.notOk(common.isConstantFunction(node3), 'wrong node should not be const func definition') -}) - -test('staticAnalysisCommon.isPlusPlusUnaryOperation', function (t) { - t.plan(3) - var node1 = { name: 'UnaryOperation', attributes: { operator: '++' } } - var node2 = { name: 'UnaryOperation', attributes: { operator: '--' } } - var node3 = { name: 'FunctionDefinition', attributes: { operator: '++' } } - - t.ok(common.isPlusPlusUnaryOperation(node1), 'should be unary ++') - t.notOk(common.isPlusPlusUnaryOperation(node2), 'should not be unary ++') - t.notOk(common.isPlusPlusUnaryOperation(node3), 'wrong node should not be unary ++') -}) - -test('staticAnalysisCommon.isMinusMinusUnaryOperation', function (t) { - t.plan(3) - var node1 = { name: 'UnaryOperation', attributes: { operator: '--' } } - var node2 = { name: 'UnaryOperation', attributes: { operator: '++' } } - var node3 = { name: 'FunctionDefinition', attributes: { operator: '--' } } - - t.ok(common.isMinusMinusUnaryOperation(node1), 'should be unary --') - t.notOk(common.isMinusMinusUnaryOperation(node2), 'should not be unary --') - t.notOk(common.isMinusMinusUnaryOperation(node3), 'wrong node should not be unary --') -}) - -test('staticAnalysisCommon.isFullyImplementedContract', function (t) { - t.plan(3) - var node1 = { name: 'ContractDefinition', attributes: { fullyImplemented: true } } - var node2 = { name: 'ContractDefinition', attributes: { fullyImplemented: false } } - var node3 = { name: 'FunctionDefinition', attributes: { operator: '--' } } - - t.ok(common.isFullyImplementedContract(node1), 'should be fully implemented contract') - t.notOk(common.isFullyImplementedContract(node2), 'should not be fully implemented contract') - t.notOk(common.isFullyImplementedContract(node3), 'wrong node should not be fully implemented contract') -}) - -test('staticAnalysisCommon.isCallToNonConstLocalFunction', function (t) { - t.plan(2) - var node1 = { - 'attributes': { - 'type': 'tuple()', - 'type_conversion': false - }, - 'children': [ - { - 'attributes': { - 'type': 'function (struct Ballot.Voter storage pointer)', - 'value': 'bli' - }, - 'name': 'Identifier' - }, - { - 'attributes': { - 'type': 'struct Ballot.Voter storage pointer', - 'value': 'x' - }, - 'name': 'Identifier' - } - ], - 'name': 'FunctionCall' - } - - t.ok(common.isCallToNonConstLocalFunction(node1), 'should be call to non const Local func') - node1.children[0].attributes.type = 'function (struct Ballot.Voter storage pointer) constant payable (uint256)' - t.notok(common.isCallToNonConstLocalFunction(node1), 'should no longer be call to non const Local func') -}) - -test('staticAnalysisCommon.isExternalDirectCall', function (t) { - t.plan(5) - var node = { - attributes: { - member_name: 'info', - type: 'function () payable external returns (uint256)' - }, - children: [ - { - attributes: { - type: 'contract InfoFeed', - value: 'f' - }, - id: 30, - name: 'Identifier', - src: '405:1:0' - } - ], - id: 32, - name: 'MemberAccess', - src: '405:6:0' - } - - var node2 = { name: 'MemberAccess', children: [{attributes: { value: 'this', type: 'contract test' }}], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } - t.notOk(common.isThisLocalCall(node), 'is this.local_method() used should not work') - t.notOk(common.isBlockTimestampAccess(node), 'is block.timestamp used should not work') - t.notOk(common.isNowAccess(node), 'is now used should not work') - t.ok(common.isExternalDirectCall(node), 'f.info() should be external direct call') - t.notOk(common.isExternalDirectCall(node2), 'local call is not an exernal call') -}) - -test('staticAnalysisCommon.isNowAccess', function (t) { - t.plan(3) - var node = { name: 'Identifier', attributes: { value: 'now', type: 'uint256' } } - t.notOk(common.isThisLocalCall(node), 'is this.local_method() used should not work') - t.notOk(common.isBlockTimestampAccess(node), 'is block.timestamp used should not work') - t.ok(common.isNowAccess(node), 'is now used should work') -}) - -test('staticAnalysisCommon.isBlockTimestampAccess', function (t) { - t.plan(3) - var node = { name: 'MemberAccess', children: [{attributes: { value: 'block', type: 'block' }}], attributes: { value: 'timestamp', type: 'uint256' } } - t.notOk(common.isThisLocalCall(node), 'is this.local_method() used should not work') - t.ok(common.isBlockTimestampAccess(node), 'is block.timestamp used should work') - t.notOk(common.isNowAccess(node), 'is now used should not work') -}) - -test('staticAnalysisCommon.isBlockBlockhashAccess', function (t) { - t.plan(4) - var node = { - 'attributes': { - 'member_name': 'blockhash', - 'type': 'function (uint256) returns (bytes32)' - }, - 'children': [ - { - 'attributes': { - 'type': 'block', - 'value': 'block' - }, - 'name': 'Identifier' - } - ], - 'name': 'MemberAccess' - } - - t.notOk(common.isThisLocalCall(node), 'is this.local_method() used should not work') - t.notOk(common.isBlockTimestampAccess(node), 'is block.timestamp used should not work') - t.ok(common.isBlockBlockHashAccess(node), 'blockhash should work') // todo: - t.notOk(common.isNowAccess(node), 'is now used should not work') -}) - -test('staticAnalysisCommon.isThisLocalCall', function (t) { - t.plan(3) - var node = { name: 'MemberAccess', children: [{attributes: { value: 'this', type: 'contract test' }}], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } - t.ok(common.isThisLocalCall(node), 'is this.local_method() used should work') - t.notOk(common.isBlockTimestampAccess(node), 'is block.timestamp used should not work') - t.notOk(common.isNowAccess(node), 'is now used should not work') -}) - -test('staticAnalysisCommon.isSuperLocalCall', function (t) { - t.plan(4) - var node = { - 'attributes': { - 'member_name': 'duper', - 'type': 'function ()' - }, - 'children': [ - { - 'attributes': { - 'type': 'contract super a', - 'value': 'super' - }, - 'name': 'Identifier' - } - ], - 'name': 'MemberAccess' - } - t.ok(common.isSuperLocalCall(node), 'is super.local_method() used should work') - t.notOk(common.isThisLocalCall(node), 'is this.local_method() used should not work') - t.notOk(common.isBlockTimestampAccess(node), 'is block.timestamp used should not work') - t.notOk(common.isNowAccess(node), 'is now used should not work') -}) - -test('staticAnalysisCommon.isLibraryCall', function (t) { - t.plan(5) - var node = { - 'attributes': { - 'member_name': 'insert', - 'type': 'function (struct Set.Data storage pointer,uint256) returns (bool)' - }, - 'children': [ - { - 'attributes': { - 'type': 'type(library Set)', - 'value': 'Set' - }, - 'name': 'Identifier' - } - ], - 'name': 'MemberAccess' - } - t.ok(common.isLibraryCall(node), 'is lib call should not work') - t.notOk(common.isSuperLocalCall(node), 'is super.local_method() used should not work') - t.notOk(common.isThisLocalCall(node), 'is this.local_method() used should not work') - t.notOk(common.isBlockTimestampAccess(node), 'is block.timestamp used should not work') - t.notOk(common.isNowAccess(node), 'is now used should not work') -}) - -test('staticAnalysisCommon.isLocalCall', function (t) { - t.plan(5) - var node1 = { - 'attributes': { - 'type': 'tuple()', - 'type_conversion': false - }, - 'children': [ - { - 'attributes': { - 'type': 'function (struct Ballot.Voter storage pointer)', - 'value': 'bli' - }, - 'id': 37, - 'name': 'Identifier', - 'src': '540:3:0' - }, - { - 'attributes': { - 'type': 'struct Ballot.Voter storage pointer', - 'value': 'x' - }, - 'id': 38, - 'name': 'Identifier', - 'src': '544:1:0' - } - ], - 'id': 39, - 'name': 'FunctionCall', - 'src': '540:6:0' - } - - t.ok(common.isLocalCall(node1), 'isLocalCall') - t.notOk(common.isLowLevelCall(node1), 'is not low level call') - t.notOk(common.isExternalDirectCall(node1), 'is not external direct call') - t.notOk(common.isEffect(node1), 'is not effect') - t.notOk(common.isInteraction(node1), 'is not interaction') -}) - -test('staticAnalysisCommon.isLowLevelCall', function (t) { - t.plan(6) - var sendAst = { name: 'MemberAccess', children: [{attributes: { value: 'd', type: 'address' }}], attributes: { value: 'send', type: 'function (uint256) returns (bool)' } } - var callAst = { name: 'MemberAccess', children: [{attributes: { value: 'f', type: 'address' }}], attributes: { member_name: 'call', type: 'function () payable returns (bool)' } } - var callcodeAst = { name: 'MemberAccess', children: [{attributes: { value: 'f', type: 'address' }}], attributes: { member_name: 'callcode', type: 'function () payable returns (bool)' } } - var delegatecallAst = { name: 'MemberAccess', children: [{attributes: { value: 'g', type: 'address' }}], attributes: { member_name: 'delegatecall', type: 'function () returns (bool)' } } - - t.ok(common.isLowLevelSendInst(sendAst) && common.isLowLevelCall(sendAst), 'send is llc should work') - t.ok(common.isLowLevelCallInst(callAst) && common.isLowLevelCall(callAst), 'call is llc should work') - t.notOk(common.isLowLevelCallInst(callcodeAst), 'callcode is not call') - t.ok(common.isLowLevelCallcodeInst(callcodeAst) && common.isLowLevelCall(callcodeAst), 'callcode is llc should work') - t.notOk(common.isLowLevelCallcodeInst(callAst), 'call is not callcode') - t.ok(common.isLowLevelDelegatecallInst(delegatecallAst) && common.isLowLevelCall(delegatecallAst), 'delegatecall is llc should work') -}) diff --git a/test/staticanalysis/staticAnalysisIntegration-test.js b/test/staticanalysis/staticAnalysisIntegration-test.js deleted file mode 100644 index b4df7b82e7..0000000000 --- a/test/staticanalysis/staticAnalysisIntegration-test.js +++ /dev/null @@ -1,454 +0,0 @@ -var test = require('tape') - -var StatRunner = require('../../src/app/staticanalysis/staticAnalysisRunner') -// const util = require('util') - -var solc = require('solc/wrapper') -var compiler = solc(require('../../soljson')) - -var fs = require('fs') -var path = require('path') - -var compilerInput = require('../../src/app/compiler/compiler-input.js') - -var testFiles = [ - 'KingOfTheEtherThrone.sol', - 'assembly.sol', - 'ballot.sol', - 'ballot_reentrant.sol', - 'ballot_withoutWarnings.sol', - 'cross_contract.sol', - 'inheritance.sol', - 'modifier1.sol', - 'modifier2.sol', - 'notReentrant.sol', - 'structReentrant.sol', - 'thisLocal.sol', - 'globals.sol', - 'library.sol', - 'transfer.sol', - 'ctor.sol', - 'forgottenReturn.sol', - 'selfdestruct.sol' -] - -var testFileAsts = {} - -testFiles.forEach((fileName) => { - var content = fs.readFileSync(path.join(__dirname, 'test-contracts', fileName), 'utf8') - testFileAsts[fileName] = JSON.parse(compiler.compileStandardWrapper(compilerInput({'test.sol': { content: content }}, { optimize: false }))) -}) - -test('Integration test thisLocal.js', function (t) { - t.plan(testFiles.length) - - var module = require('../../src/app/staticanalysis/modules/thisLocal') - - var lengthCheck = { - 'KingOfTheEtherThrone.sol': 0, - 'assembly.sol': 0, - 'ballot.sol': 0, - 'ballot_reentrant.sol': 1, - 'ballot_withoutWarnings.sol': 0, - 'cross_contract.sol': 0, - 'inheritance.sol': 0, - 'modifier1.sol': 0, - 'modifier2.sol': 0, - 'notReentrant.sol': 0, - 'structReentrant.sol': 0, - 'thisLocal.sol': 1, - 'globals.sol': 0, - 'library.sol': 0, - 'transfer.sol': 0, - 'ctor.sol': 0, - 'forgottenReturn.sol': 0, - 'selfdestruct.sol': 0 - } - - runModuleOnFiles(module, t, (file, report) => { - t.equal(report.length, lengthCheck[file], `${file} has right amount of this local warnings`) - }) -}) - -test('Integration test checksEffectsInteraction.js', function (t) { - t.plan(testFiles.length) - - var module = require('../../src/app/staticanalysis/modules/checksEffectsInteraction') - - var lengthCheck = { - 'KingOfTheEtherThrone.sol': 1, - 'assembly.sol': 1, - 'ballot.sol': 0, - 'ballot_reentrant.sol': 1, - 'ballot_withoutWarnings.sol': 0, - 'cross_contract.sol': 0, - 'inheritance.sol': 1, - 'modifier1.sol': 0, - 'modifier2.sol': 0, - 'notReentrant.sol': 0, - 'structReentrant.sol': 1, - 'thisLocal.sol': 0, - 'globals.sol': 1, - 'library.sol': 1, - 'transfer.sol': 1, - 'ctor.sol': 0, - 'forgottenReturn.sol': 0, - 'selfdestruct.sol': 0 - } - - runModuleOnFiles(module, t, (file, report) => { - t.equal(report.length, lengthCheck[file], `${file} has right amount of checks-effects-interaction warnings`) - }) -}) - -test('Integration test constantFunctions.js', function (t) { - t.plan(testFiles.length) - - var module = require('../../src/app/staticanalysis/modules/constantFunctions') - - var lengthCheck = { - 'KingOfTheEtherThrone.sol': 0, - 'assembly.sol': 0, - 'ballot.sol': 0, - 'ballot_reentrant.sol': 0, - 'ballot_withoutWarnings.sol': 0, - 'cross_contract.sol': 1, - 'inheritance.sol': 0, - 'modifier1.sol': 1, - 'modifier2.sol': 0, - 'notReentrant.sol': 0, - 'structReentrant.sol': 0, - 'thisLocal.sol': 1, - 'globals.sol': 0, - 'library.sol': 1, - 'transfer.sol': 0, - 'ctor.sol': 0, - 'forgottenReturn.sol': 0, - 'selfdestruct.sol': 1 - } - - runModuleOnFiles(module, t, (file, report) => { - t.equal(report.length, lengthCheck[file], `${file} has right amount of constant warnings`) - }) -}) - -test('Integration test inlineAssembly.js', function (t) { - t.plan(testFiles.length) - - var module = require('../../src/app/staticanalysis/modules/inlineAssembly') - - var lengthCheck = { - 'KingOfTheEtherThrone.sol': 0, - 'assembly.sol': 2, - 'ballot.sol': 0, - 'ballot_reentrant.sol': 0, - 'ballot_withoutWarnings.sol': 0, - 'cross_contract.sol': 0, - 'inheritance.sol': 0, - 'modifier1.sol': 0, - 'modifier2.sol': 0, - 'notReentrant.sol': 0, - 'structReentrant.sol': 0, - 'thisLocal.sol': 0, - 'globals.sol': 0, - 'library.sol': 0, - 'transfer.sol': 0, - 'ctor.sol': 0, - 'forgottenReturn.sol': 0, - 'selfdestruct.sol': 0 - } - - runModuleOnFiles(module, t, (file, report) => { - t.equal(report.length, lengthCheck[file], `${file} has right amount of inline assembly warnings`) - }) -}) - -test('Integration test txOrigin.js', function (t) { - t.plan(testFiles.length) - - var module = require('../../src/app/staticanalysis/modules/txOrigin') - - var lengthCheck = { - 'KingOfTheEtherThrone.sol': 0, - 'assembly.sol': 1, - 'ballot.sol': 0, - 'ballot_reentrant.sol': 0, - 'ballot_withoutWarnings.sol': 0, - 'cross_contract.sol': 0, - 'inheritance.sol': 0, - 'modifier1.sol': 0, - 'modifier2.sol': 0, - 'notReentrant.sol': 0, - 'structReentrant.sol': 0, - 'thisLocal.sol': 0, - 'globals.sol': 1, - 'library.sol': 0, - 'transfer.sol': 0, - 'ctor.sol': 0, - 'forgottenReturn.sol': 0, - 'selfdestruct.sol': 0 - } - - runModuleOnFiles(module, t, (file, report) => { - t.equal(report.length, lengthCheck[file], `${file} has right amount of tx.origin warnings`) - }) -}) - -test('Integration test gasCosts.js', function (t) { - t.plan(testFiles.length) - - var module = require('../../src/app/staticanalysis/modules/gasCosts') - - var lengthCheck = { - 'KingOfTheEtherThrone.sol': 2, - 'assembly.sol': 2, - 'ballot.sol': 3, - 'ballot_reentrant.sol': 2, - 'ballot_withoutWarnings.sol': 0, - 'cross_contract.sol': 1, - 'inheritance.sol': 1, - 'modifier1.sol': 0, - 'modifier2.sol': 1, - 'notReentrant.sol': 1, - 'structReentrant.sol': 1, - 'thisLocal.sol': 2, - 'globals.sol': 1, - 'library.sol': 1, - 'transfer.sol': 1, - 'ctor.sol': 0, - 'forgottenReturn.sol': 3, - 'selfdestruct.sol': 0 - } - - runModuleOnFiles(module, t, (file, report) => { - t.equal(report.length, lengthCheck[file], `${file} has right amount of gasCost warnings`) - }) -}) - -test('Integration test similarVariableNames.js', function (t) { - t.plan(testFiles.length) - - var module = require('../../src/app/staticanalysis/modules/similarVariableNames') - - var lengthCheck = { - 'KingOfTheEtherThrone.sol': 0, - 'assembly.sol': 0, - 'ballot.sol': 2, - 'ballot_reentrant.sol': 3, - 'ballot_withoutWarnings.sol': 0, - 'cross_contract.sol': 0, - 'inheritance.sol': 0, - 'modifier1.sol': 0, - 'modifier2.sol': 0, - 'notReentrant.sol': 1, - 'structReentrant.sol': 0, - 'thisLocal.sol': 0, - 'globals.sol': 0, - 'library.sol': 0, - 'transfer.sol': 0, - 'ctor.sol': 1, - 'forgottenReturn.sol': 0, - 'selfdestruct.sol': 0 - } - - runModuleOnFiles(module, t, (file, report) => { - t.equal(report.length, lengthCheck[file], `${file} has right amount of similarVariableNames warnings`) - }) -}) - -test('Integration test inlineAssembly.js', function (t) { - t.plan(testFiles.length) - - var module = require('../../src/app/staticanalysis/modules/inlineAssembly') - - var lengthCheck = { - 'KingOfTheEtherThrone.sol': 0, - 'assembly.sol': 2, - 'ballot.sol': 0, - 'ballot_reentrant.sol': 0, - 'ballot_withoutWarnings.sol': 0, - 'cross_contract.sol': 0, - 'inheritance.sol': 0, - 'modifier1.sol': 0, - 'modifier2.sol': 0, - 'notReentrant.sol': 0, - 'structReentrant.sol': 0, - 'thisLocal.sol': 0, - 'globals.sol': 0, - 'library.sol': 0, - 'transfer.sol': 0, - 'ctor.sol': 0, - 'forgottenReturn.sol': 0, - 'selfdestruct.sol': 0 - } - - runModuleOnFiles(module, t, (file, report) => { - t.equal(report.length, lengthCheck[file], `${file} has right amount of inlineAssembly warnings`) - }) -}) - -test('Integration test blockTimestamp.js', function (t) { - t.plan(testFiles.length) - - var module = require('../../src/app/staticanalysis/modules/blockTimestamp') - - var lengthCheck = { - 'KingOfTheEtherThrone.sol': 1, - 'assembly.sol': 0, - 'ballot.sol': 0, - 'ballot_reentrant.sol': 3, - 'ballot_withoutWarnings.sol': 0, - 'cross_contract.sol': 0, - 'inheritance.sol': 0, - 'modifier1.sol': 0, - 'modifier2.sol': 0, - 'notReentrant.sol': 0, - 'structReentrant.sol': 0, - 'thisLocal.sol': 0, - 'globals.sol': 2, - 'library.sol': 0, - 'transfer.sol': 0, - 'ctor.sol': 0, - 'forgottenReturn.sol': 0, - 'selfdestruct.sol': 0 - } - - runModuleOnFiles(module, t, (file, report) => { - t.equal(report.length, lengthCheck[file], `${file} has right amount of blockTimestamp warnings`) - }) -}) - -test('Integration test lowLevelCalls.js', function (t) { - t.plan(testFiles.length) - - var module = require('../../src/app/staticanalysis/modules/lowLevelCalls') - - var lengthCheck = { - 'KingOfTheEtherThrone.sol': 1, - 'assembly.sol': 1, - 'ballot.sol': 0, - 'ballot_reentrant.sol': 7, - 'ballot_withoutWarnings.sol': 0, - 'cross_contract.sol': 1, - 'inheritance.sol': 1, - 'modifier1.sol': 0, - 'modifier2.sol': 0, - 'notReentrant.sol': 1, - 'structReentrant.sol': 1, - 'thisLocal.sol': 2, - 'globals.sol': 1, - 'library.sol': 1, - 'transfer.sol': 0, - 'ctor.sol': 0, - 'forgottenReturn.sol': 0, - 'selfdestruct.sol': 0 - } - - runModuleOnFiles(module, t, (file, report) => { - t.equal(report.length, lengthCheck[file], `${file} has right amount of lowLevelCalls warnings`) - }) -}) - -test('Integration test blockBlockhash.js', function (t) { - t.plan(testFiles.length) - - var module = require('../../src/app/staticanalysis/modules/blockBlockhash') - - var lengthCheck = { - 'KingOfTheEtherThrone.sol': 0, - 'assembly.sol': 0, - 'ballot.sol': 0, - 'ballot_reentrant.sol': 0, - 'ballot_withoutWarnings.sol': 0, - 'cross_contract.sol': 0, - 'inheritance.sol': 0, - 'modifier1.sol': 0, - 'modifier2.sol': 0, - 'notReentrant.sol': 0, - 'structReentrant.sol': 0, - 'thisLocal.sol': 0, - 'globals.sol': 0, // was 1 !! @TODO - 'library.sol': 0, - 'transfer.sol': 0, - 'ctor.sol': 0, - 'forgottenReturn.sol': 0, - 'selfdestruct.sol': 0 - } - - runModuleOnFiles(module, t, (file, report) => { - t.equal(report.length, lengthCheck[file], `${file} has right amount of blockBlockhash warnings`) - }) -}) - -test('Integration test noReturn.js', function (t) { - t.plan(testFiles.length) - - var module = require('../../src/app/staticanalysis/modules/noReturn') - - var lengthCheck = { - 'KingOfTheEtherThrone.sol': 0, - 'assembly.sol': 1, - 'ballot.sol': 0, - 'ballot_reentrant.sol': 0, - 'ballot_withoutWarnings.sol': 0, - 'cross_contract.sol': 0, - 'inheritance.sol': 0, - 'modifier1.sol': 1, - 'modifier2.sol': 0, - 'notReentrant.sol': 0, - 'structReentrant.sol': 0, - 'thisLocal.sol': 1, - 'globals.sol': 0, - 'library.sol': 0, - 'transfer.sol': 0, - 'ctor.sol': 0, - 'forgottenReturn.sol': 1, - 'selfdestruct.sol': 0 - } - - runModuleOnFiles(module, t, (file, report) => { - t.equal(report.length, lengthCheck[file], `${file} has right amount of noReturn warnings`) - }) -}) - -test('Integration test selfdestruct.js', function (t) { - t.plan(testFiles.length) - - var module = require('../../src/app/staticanalysis/modules/selfdestruct') - - var lengthCheck = { - 'KingOfTheEtherThrone.sol': 0, - 'assembly.sol': 0, - 'ballot.sol': 0, - 'ballot_reentrant.sol': 0, - 'ballot_withoutWarnings.sol': 0, - 'cross_contract.sol': 0, - 'inheritance.sol': 0, - 'modifier1.sol': 0, - 'modifier2.sol': 0, - 'notReentrant.sol': 0, - 'structReentrant.sol': 0, - 'thisLocal.sol': 0, - 'globals.sol': 1, - 'library.sol': 0, - 'transfer.sol': 0, - 'ctor.sol': 0, - 'forgottenReturn.sol': 0, - 'selfdestruct.sol': 2 - } - - runModuleOnFiles(module, t, (file, report) => { - t.equal(report.length, lengthCheck[file], `${file} has right amount of selfdestruct warnings`) - }) -}) - -// #################### Helpers -function runModuleOnFiles (module, t, cb) { - var statRunner = new StatRunner() - - testFiles.forEach((fileName) => { - statRunner.runWithModuleList(testFileAsts[fileName], [{ name: module.name, mod: new module.Module() }], (reports) => { - cb(fileName, reports[0].report) - }) - }) -} diff --git a/test/staticanalysis/test-contracts/KingOfTheEtherThrone.sol b/test/staticanalysis/test-contracts/KingOfTheEtherThrone.sol deleted file mode 100644 index 70cfb08327..0000000000 --- a/test/staticanalysis/test-contracts/KingOfTheEtherThrone.sol +++ /dev/null @@ -1,23 +0,0 @@ -// return value send -contract KingOfTheEtherThrone{ - struct Monarch { - // address of the king . - address ethAddr ; - string name ; - // how much he pays to previous king - uint claimPrice ; - uint coronationTimestamp; - } - Monarch public currentMonarch ; - - function claimThrone ( string name ) { - address wizardAddress; - uint compensation = 100; - uint valuePaid = 10; - - if ( currentMonarch.ethAddr != wizardAddress ) - if (currentMonarch.ethAddr.send( compensation )) throw; - - currentMonarch = Monarch(msg.sender,name,valuePaid,block.timestamp); - } -} diff --git a/test/staticanalysis/test-contracts/assembly.sol b/test/staticanalysis/test-contracts/assembly.sol deleted file mode 100644 index a71c7e4a26..0000000000 --- a/test/staticanalysis/test-contracts/assembly.sol +++ /dev/null @@ -1,29 +0,0 @@ - pragma solidity ^0.4.9; - contract test { - - address owner; - - function at(address _addr) returns (bytes o_code) { - assembly { - // retrieve the size of the code, this needs assembly - let size := extcodesize(_addr) - // allocate output byte array - this could also be done without assembly - // by using o_code = new bytes(size) - o_code := mload(0x40) - // new "memory end" including padding - mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) - // store length in memory - mstore(o_code, size) - // actually retrieve the code, this needs assembly - extcodecopy(_addr, add(o_code, 0x20), 0, size) - } - } - - function bla() { - if(tx.origin == owner) - msg.sender.send(19); - assembly { - - } - } - } diff --git a/test/staticanalysis/test-contracts/ballot.sol b/test/staticanalysis/test-contracts/ballot.sol deleted file mode 100644 index e1e9f676fe..0000000000 --- a/test/staticanalysis/test-contracts/ballot.sol +++ /dev/null @@ -1,145 +0,0 @@ -pragma solidity ^0.4.0; - -/// @title Voting with delegation. -contract Ballot { - // This declares a new complex type which will - // be used for variables later. - // It will represent a single voter. - struct Voter { - uint weight; // weight is accumulated by delegation - bool voted; // if true, that person already voted - address delegate; // person delegated to - uint vote; // index of the voted proposal - } - - // This is a type for a single proposal. - struct Proposal - { - bytes32 name; // short name (up to 32 bytes) - uint voteCount; // number of accumulated votes - } - - address public chairperson; - - // This declares a state variable that - // stores a `Voter` struct for each possible address. - mapping(address => Voter) public voters; - - // A dynamically-sized array of `Proposal` structs. - Proposal[] public proposals; - - /// Create a new ballot to choose one of `proposalNames`. - function Ballot(bytes32[] proposalNames) { - chairperson = msg.sender; - voters[chairperson].weight = 1; - - // For each of the provided proposal names, - // create a new proposal object and add it - // to the end of the array. - for (uint i = 0; i < proposalNames.length; i++) { - // `Proposal({...})` creates a temporary - // Proposal object and `proposals.push(...)` - // appends it to the end of `proposals`. - proposals.push(Proposal({ - name: proposalNames[i], - voteCount: 0 - })); - } - } - - // Give `voter` the right to vote on this ballot. - // May only be called by `chairperson`. - function giveRightToVote(address voter) { - if (msg.sender != chairperson || voters[voter].voted) { - // `throw` terminates and reverts all changes to - // the state and to Ether balances. It is often - // a good idea to use this if functions are - // called incorrectly. But watch out, this - // will also consume all provided gas. - throw; - } - voters[voter].weight = 1; - } - - /// Delegate your vote to the voter `to`. - function delegate(address to) { - // assigns reference - Voter sender = voters[msg.sender]; - if (sender.voted) - throw; - - // Forward the delegation as long as - // `to` also delegated. - // In general, such loops are very dangerous, - // because if they run too long, they might - // need more gas than is available in a block. - // In this case, the delegation will not be executed, - // but in other situations, such loops might - // cause a contract to get "stuck" completely. - while ( - voters[to].delegate != address(0) && - voters[to].delegate != msg.sender - ) { - to = voters[to].delegate; - } - - // We found a loop in the delegation, not allowed. - if (to == msg.sender) { - throw; - } - - // Since `sender` is a reference, this - // modifies `voters[msg.sender].voted` - sender.voted = true; - sender.delegate = to; - Voter delegate = voters[to]; - if (delegate.voted) { - // If the delegate already voted, - // directly add to the number of votes - proposals[delegate.vote].voteCount += sender.weight; - } else { - // If the delegate did not vote yet, - // add to her weight. - delegate.weight += sender.weight; - } - } - - /// Give your vote (including votes delegated to you) - /// to proposal `proposals[proposal].name`. - function vote(uint proposal) { - Voter sender = voters[msg.sender]; - if (sender.voted) - throw; - sender.voted = true; - sender.vote = proposal; - - // If `proposal` is out of the range of the array, - // this will throw automatically and revert all - // changes. - proposals[proposal].voteCount += sender.weight; - } - - /// @dev Computes the winning proposal taking all - /// previous votes into account. - function winningProposal() constant - returns (uint winningProposal) - { - uint winningVoteCount = 0; - for (uint p = 0; p < proposals.length; p++) { - if (proposals[p].voteCount > winningVoteCount) { - winningVoteCount = proposals[p].voteCount; - winningProposal = p; - } - } - } - - // Calls winningProposal() function to get the index - // of the winner contained in the proposals array and then - // returns the name of the winner - function winnerName() constant - returns (bytes32 winnerName) - { - winnerName = proposals[winningProposal()].name; - } -} - diff --git a/test/staticanalysis/test-contracts/ballot_reentrant.sol b/test/staticanalysis/test-contracts/ballot_reentrant.sol deleted file mode 100644 index a695b09e19..0000000000 --- a/test/staticanalysis/test-contracts/ballot_reentrant.sol +++ /dev/null @@ -1,101 +0,0 @@ -pragma solidity ^0.4.0; - -contract InfoFeed { - function info() payable returns (uint ret); - function call1(uint a) payable returns (bool); -} - - -contract Ballot { - - InfoFeed feed; - - struct Voter { - uint weight; - bool voted; - uint8 vote; - address delegate; - } - struct Proposal { - uint voteCount; - } - - address chairperson; - mapping(address => Voter) voters; - Proposal[] proposals; - - function send1(address a) { - giveRightToVote(a,a); - } - - /// Create a new ballot with $(_numProposals) different proposals. - function Ballot(uint8 _numProposals) { - address d; - if(!d.delegatecall.gas(800)('function_name', 'arg1', 'arg2')) throw; - if(!d.callcode.gas(800)('function_name', 'arg1', 'arg2')) throw; - if(!d.call.value(10).gas(800)('function_name', 'arg1', 'arg2')) throw; - if(!d.call.value(10).gas(800)('function_name', 'arg1', 'arg2')) throw; - - - - if(!msg.sender.send(1 wei)) throw; - if(!d.call('function_name', 'arg1', 'arg2')) throw; - - - uint a = now; - uint c = block.timestamp; - if(block.timestamp < 100){} - chairperson = msg.sender; - voters[chairperson].weight = 1; - proposals.length = _numProposals; - if(!d.send(1 wei)) throw; - feed.info.value(10).gas(800)(); - - feed.call1(1); - - this.send1(d); - } - - - /// Give $(voter) the right to vote on this ballot. - /// May only be called by $(chairperson). - function giveRightToVote(address voter, address b) payable returns (bool){ - if (msg.sender != chairperson || voters[voter].voted) return; - voters[voter].weight = 1; - return true; - } - - /// Delegate your vote to the voter $(to). - function delegate(address to) { - Voter sender = voters[msg.sender]; // assigns reference - if (sender.voted) return; - while (voters[to].delegate != address(0) && voters[to].delegate != msg.sender) - to = voters[to].delegate; - if (to == msg.sender) return; - sender.voted = true; - sender.delegate = to; - Voter delegate = voters[to]; - if (delegate.voted) - proposals[delegate.vote].voteCount += sender.weight; - else - delegate.weight += sender.weight; - } - - /// Give a single vote to proposal $(proposal). - function vote(uint8 proposal) { - Voter sender = voters[msg.sender]; - if (sender.voted || proposal >= proposals.length) return; - sender.voted = true; - sender.vote = proposal; - proposals[proposal].voteCount += sender.weight; - } - - function winningProposal() constant returns (uint8 winningProposal) { - uint256 winningVoteCount = 0; - for (uint8 proposal = 0; proposal < proposals.length; proposal++) - if (proposals[proposal].voteCount > winningVoteCount) { - winningVoteCount = proposals[proposal].voteCount; - winningProposal = proposal; - } - } -} diff --git a/test/staticanalysis/test-contracts/ballot_withoutWarnings.sol b/test/staticanalysis/test-contracts/ballot_withoutWarnings.sol deleted file mode 100644 index e273b3da4f..0000000000 --- a/test/staticanalysis/test-contracts/ballot_withoutWarnings.sol +++ /dev/null @@ -1,31 +0,0 @@ -pragma solidity ^0.4.0; - -/// @title Voting with delegation. -contract Ballot { - - struct Proposal - { - bytes32 name; // short name (up to 32 bytes) - uint voteCount; // number of accumulated votes - } - - // A dynamically-sized array of `Proposal` structs. - Proposal[] public proposals; - - /// @dev Computes the winning proposal taking all - /// previous votes into account. - function winningProposal() constant - returns (uint winningProposal) - { - winningProposal = 0; - } - - // Calls winningProposal() function to get the index - // of the winner contained in the proposals array and then - // returns the name of the winner - function winnerName() constant - returns (bytes32 winnerName) - { - winnerName = proposals[winningProposal()].name; - } -} diff --git a/test/staticanalysis/test-contracts/cross_contract.sol b/test/staticanalysis/test-contracts/cross_contract.sol deleted file mode 100644 index 426c8d444d..0000000000 --- a/test/staticanalysis/test-contracts/cross_contract.sol +++ /dev/null @@ -1,19 +0,0 @@ -pragma solidity ^0.4.0; - -contract a { - - uint x; - - function foo() { - x++; - } -} - -contract b { - a x; - function bar() constant { - address a; - a.send(100 wei); - x.foo(); - } -} diff --git a/test/staticanalysis/test-contracts/ctor.sol b/test/staticanalysis/test-contracts/ctor.sol deleted file mode 100644 index b250c02626..0000000000 --- a/test/staticanalysis/test-contracts/ctor.sol +++ /dev/null @@ -1,8 +0,0 @@ -contract c { - uint x; - uint x_abc; - function c(uint _x, uint _abc) { - x=_x; - x_abc=_abc; - } -} \ No newline at end of file diff --git a/test/staticanalysis/test-contracts/forgottenReturn.sol b/test/staticanalysis/test-contracts/forgottenReturn.sol deleted file mode 100644 index eb3df75e44..0000000000 --- a/test/staticanalysis/test-contracts/forgottenReturn.sol +++ /dev/null @@ -1,13 +0,0 @@ -contract Sheep { - string public name; - string public dna; - bool g = true; - function Sheep(string _name, string _dna) { - name = _name; - dna = _dna; - } - - function geneticallyEngineer(string _dna) returns (bool) { - dna = _dna; - } -} \ No newline at end of file diff --git a/test/staticanalysis/test-contracts/globals.sol b/test/staticanalysis/test-contracts/globals.sol deleted file mode 100644 index c945f84bba..0000000000 --- a/test/staticanalysis/test-contracts/globals.sol +++ /dev/null @@ -1,57 +0,0 @@ -pragma solidity ^0.4.9; -contract bla { - uint brr; - function duper() { - brr++; - } -} - -contract a is bla { - - function blub() { - brr++; - } - - function r () { - address a; - bytes32 hash; - uint8 v; - bytes32 r; - bytes32 s; - - block.blockhash(1); - block.coinbase; - block.difficulty; - block.gaslimit; - block.number; - block.timestamp; - msg.data; - msg.gas; - msg.sender; - msg.value; - now; - tx.gasprice; - tx.origin; - //assert(now == block.timestamp); - //require(now == block.timestamp); - keccak256(a); - sha3(a); - sha256(a); - ripemd160(a); - ecrecover(hash, v, r, s); - addmod(1, 2, 2); - mulmod(4,4,12); - - a.balance; - blub(); - a.send(a.balance); - - - - super.duper(); - //a.transfer(a.balance); - selfdestruct(a); - //revert(); - } - -} \ No newline at end of file diff --git a/test/staticanalysis/test-contracts/inheritance.sol b/test/staticanalysis/test-contracts/inheritance.sol deleted file mode 100644 index 1491e8fa9f..0000000000 --- a/test/staticanalysis/test-contracts/inheritance.sol +++ /dev/null @@ -1,40 +0,0 @@ -pragma solidity ^0.4.9; - -contract r { - function s() constant {} -} - -contract a is r { - uint x = 1; - - function getX() constant returns (uint) { - return x; - } -} - -contract b is a { - uint y = 2; - uint x = 3; - - - function getY(uint z, bool r) returns (uint) { - return y++; - } - - function getY(string storage n) internal constant returns (uint) { return 10; } - -} - -contract c is b { - string x; - - function d() returns (uint a, uint b) { - //d(); - //sha3("bla"); - msg.sender.call.gas(200000).value(this.balance)(bytes4(sha3("pay()"))); - //x++; - getY(x); - a = getX() + getY(1, false); - b = getX() + getY(x); - } -} diff --git a/test/staticanalysis/test-contracts/library.sol b/test/staticanalysis/test-contracts/library.sol deleted file mode 100644 index 3ca1454d49..0000000000 --- a/test/staticanalysis/test-contracts/library.sol +++ /dev/null @@ -1,54 +0,0 @@ -pragma solidity ^0.4.0; - -library Set { - // We define a new struct datatype that will be used to - // hold its data in the calling contract. - struct Data { mapping(uint => bool) flags; } - - // Note that the first parameter is of type "storage - // reference" and thus only its storage address and not - // its contents is passed as part of the call. This is a - // special feature of library functions. It is idiomatic - // to call the first parameter 'self', if the function can - // be seen as a method of that object. - function insert(Data storage self, uint value) - returns (bool) - { - if (self.flags[value]) - return false; // already there - self.flags[value] = true; - - return true; - } - - function remove(Data storage self, uint value) - returns (bool) - { - if (!self.flags[value]) - return false; // not there - self.flags[value] = false; - return true; - } - - function contains(Data storage self, uint value) - returns (bool) - { - return self.flags[value]; - } -} - - -contract C { - Set.Data knownValues; - - function register(uint value) { - // The library functions can be called without a - // specific instance of the library, since the - // "instance" will be the current contract. - address a; - a.send(10 wei); - if (!Set.insert(knownValues, value)) - throw; - } - // In this contract, we can also directly access knownValues.flags, if we want. -} \ No newline at end of file diff --git a/test/staticanalysis/test-contracts/modifier1.sol b/test/staticanalysis/test-contracts/modifier1.sol deleted file mode 100644 index a755d763b1..0000000000 --- a/test/staticanalysis/test-contracts/modifier1.sol +++ /dev/null @@ -1,17 +0,0 @@ -pragma solidity ^0.4.0; - -contract test { - - address owner; - - modifier onlyOwner { - var a = 0; - if (msg.sender != owner) - throw; - _; - } - - function b(address a) onlyOwner returns (bool) { - - } -} \ No newline at end of file diff --git a/test/staticanalysis/test-contracts/modifier2.sol b/test/staticanalysis/test-contracts/modifier2.sol deleted file mode 100644 index 44db1617c7..0000000000 --- a/test/staticanalysis/test-contracts/modifier2.sol +++ /dev/null @@ -1,28 +0,0 @@ -pragma solidity ^0.4.0; - -contract owned { - - uint r=0; - - modifier ntimes(uint n) { - for(uint i=0;i uint) shares; - /// Withdraw your share. - function withdraw() { - var share = shares[msg.sender]; - shares[msg.sender] = 0; - if (!msg.sender.send(share)) - throw; - } -} diff --git a/test/staticanalysis/test-contracts/reentrant.sol b/test/staticanalysis/test-contracts/reentrant.sol deleted file mode 100644 index 896395e1a6..0000000000 --- a/test/staticanalysis/test-contracts/reentrant.sol +++ /dev/null @@ -1,49 +0,0 @@ -pragma solidity ^0.4.0; - -contract InfoFeed { - uint c; - function info() constant returns (uint ret) {return c;} - function call1(uint a) constant returns (bool) {return true;} -} - -// THIS CONTRACT CONTAINS A BUG - DO NOT USE -contract Fund { - /// Mapping of ether shares of the contract. - //mapping(address => uint) shares; - /// Withdraw your share. - - uint c = 0; - function withdraw() constant { - InfoFeed f; - - - //shares[msg.sender] /= 1; - - f.info(); - - //if (msg.sender.send(shares[msg.sender])) throw; - // shares[msg.sender] = 0; - - - b(true, false); - //shares[msg.sender]++; - //c++; - - } - mapping(address => uint) shares; - - function b(bool a, bool b) returns (bool) { - mapping(address => uint) c = shares; - c[msg.sender] = 0; - //f(); - //withdraw(); - //shares[msg.sender]++; - //c++; - return true; - } - - function f() { - c++; - withdraw(); - } -} diff --git a/test/staticanalysis/test-contracts/selfdestruct.sol b/test/staticanalysis/test-contracts/selfdestruct.sol deleted file mode 100644 index d2e31dfdb0..0000000000 --- a/test/staticanalysis/test-contracts/selfdestruct.sol +++ /dev/null @@ -1,12 +0,0 @@ -contract sd { - - function() public payable { } - - function c () public constant { - selfdestruct(address(0xdeadbeef)); - } - - function b () public payable { - selfdestruct(address(0xdeadbeef)); - } -} \ No newline at end of file diff --git a/test/staticanalysis/test-contracts/structReentrant.sol b/test/staticanalysis/test-contracts/structReentrant.sol deleted file mode 100644 index 95952ff1ca..0000000000 --- a/test/staticanalysis/test-contracts/structReentrant.sol +++ /dev/null @@ -1,36 +0,0 @@ -pragma solidity ^0.4.9; - -contract Ballot { - - struct Voter { - uint weight; - bool voted; - uint8 vote; - address delegate; - baz foo; - } - - struct baz{ - uint bar; - } - - mapping(address => Voter) voters; - - /// Create a new ballot with $(_numProposals) different proposals. - function bla(address a) { - Voter x = voters[a]; - - if (!a.send(10)) - throw; - - //voters[a] = Voter(10,true,1,a); - //x.foo.bar *= 100; - bli(x); - } - - //function bla(){} - - function bli(Voter storage x) private { - x.foo.bar++; - } -} diff --git a/test/staticanalysis/test-contracts/thisLocal.sol b/test/staticanalysis/test-contracts/thisLocal.sol deleted file mode 100644 index e31cf0dbaf..0000000000 --- a/test/staticanalysis/test-contracts/thisLocal.sol +++ /dev/null @@ -1,16 +0,0 @@ -pragma solidity ^0.4.0; - -contract test { - - function (){ - address x; - this.b(x); - x.call('something'); - x.send(1 wei); - - } - - function b(address a) returns (bool) { - - } -} diff --git a/test/staticanalysis/test-contracts/transfer.sol b/test/staticanalysis/test-contracts/transfer.sol deleted file mode 100644 index 49ddd515bf..0000000000 --- a/test/staticanalysis/test-contracts/transfer.sol +++ /dev/null @@ -1,7 +0,0 @@ -contract c { - uint x; - function f(address r) { - r.transfer(1); - x = 2; - } -} \ No newline at end of file diff --git a/test/util-test.js b/test/util-test.js deleted file mode 100644 index fbe0869598..0000000000 --- a/test/util-test.js +++ /dev/null @@ -1,42 +0,0 @@ -var test = require('tape') - -var utils = require('../src/lib/utils') - -test('util.groupBy on valid input', function (t) { - t.plan(1) - - var result = utils.groupBy([ - {category: 'GAS', name: 'a'}, - {category: 'SEC', name: 'b'}, - {category: 'GAS', name: 'c'} - - ], 'category') - - var expectedResult = { - 'GAS': [ - {category: 'GAS', name: 'a'}, - {category: 'GAS', name: 'c'} - ], - 'SEC': [ - {category: 'SEC', name: 'b'} - ] - } - - t.deepEqual(result, expectedResult) -}) - -test('util.concatWithSeperator valid output', function (t) { - t.plan(4) - t.notEqual(utils.concatWithSeperator(['a', 'b', 'c'], ','), 'a, b, c', 'Concat with comma should not produce spaces') - t.equal(utils.concatWithSeperator(['a', 'b', 'c'], ','), 'a,b,c', 'Concat with comma should not produce spaces') - t.equal(utils.concatWithSeperator(['a', 'b', 'c'], ', '), 'a, b, c', 'Concat with comma space should not produce trailing comma') - t.equal(utils.concatWithSeperator(['a', 'b', 'c'], '+'), 'a+b+c', 'Concat with plus') -}) - -test('util.escapeRegExp', function (t) { - t.plan(3) - var original = 'function (uint256) returns (bool)' - t.equal(utils.escapeRegExp('abcd'), 'abcd', 'String with no regex') - t.equal(utils.escapeRegExp(original), 'function \\(uint256\\) returns \\(bool\\)', 'function string with regex') - t.ok(new RegExp(utils.escapeRegExp(original)).test(original), 'should still test for original string') -})