parent
727cf75d8c
commit
6a55230cae
File diff suppressed because it is too large
Load Diff
@ -1,56 +0,0 @@ |
||||
'use strict' |
||||
const AstWalker = require('remix-lib').AstWalker |
||||
const list = require('./modules/list') |
||||
|
||||
function staticAnalysisRunner () { |
||||
} |
||||
|
||||
staticAnalysisRunner.prototype.run = function (compilationResult, toRun, callback) { |
||||
const modules = toRun.map((i) => { |
||||
const m = this.modules()[i] |
||||
return { 'name': m.name, 'mod': new m.Module() } |
||||
}) |
||||
|
||||
this.runWithModuleList(compilationResult, modules, callback) |
||||
} |
||||
|
||||
staticAnalysisRunner.prototype.runWithModuleList = function (compilationResult, modules, callback) { |
||||
let reports = [] |
||||
// Also provide convenience analysis via the AST walker.
|
||||
const walker = new AstWalker() |
||||
for (let k in compilationResult.sources) { |
||||
walker.walk(compilationResult.sources[k].legacyAST, {'*': (node) => { |
||||
modules.map((item, i) => { |
||||
if (item.mod.visit !== undefined) { |
||||
try { |
||||
item.mod.visit(node) |
||||
} catch (e) { |
||||
reports.push({ |
||||
name: item.name, report: [{ warning: 'INTERNAL ERROR in module ' + item.name + ' ' + e.message, error: e.stack }] |
||||
}) |
||||
} |
||||
} |
||||
}) |
||||
return true |
||||
}}) |
||||
} |
||||
|
||||
// Here, modules can just collect the results from the AST walk,
|
||||
// but also perform new analysis.
|
||||
reports = reports.concat(modules.map((item, i) => { |
||||
let report = null |
||||
try { |
||||
report = item.mod.report(compilationResult) |
||||
} catch (e) { |
||||
report = [{ warning: 'INTERNAL ERROR in module ' + item.name + ' ' + e.message, error: e.stack }] |
||||
} |
||||
return { name: item.name, report: report } |
||||
})) |
||||
callback(reports) |
||||
} |
||||
|
||||
staticAnalysisRunner.prototype.modules = function () { |
||||
return list |
||||
} |
||||
|
||||
module.exports = staticAnalysisRunner |
@ -0,0 +1,54 @@ |
||||
'use strict' |
||||
import { AstWalker } from 'remix-astwalker' |
||||
import list from './modules/list' |
||||
|
||||
export class staticAnalysisRunner { |
||||
|
||||
run (compilationResult, toRun, callback) { |
||||
const modules = toRun.map((i) => { |
||||
const m = this.modules()[i] |
||||
return { 'name': m.name, 'mod': new m.Module() } |
||||
}) |
||||
|
||||
this.runWithModuleList(compilationResult, modules, callback) |
||||
} |
||||
|
||||
runWithModuleList (compilationResult, modules, callback) { |
||||
let reports: any[] = [] |
||||
// Also provide convenience analysis via the AST walker.
|
||||
const walker = new AstWalker() |
||||
for (let k in compilationResult.sources) { |
||||
walker.walk(compilationResult.sources[k].legacyAST, {'*': (node) => { |
||||
modules.map((item, i) => { |
||||
if (item.mod.visit !== undefined) { |
||||
try { |
||||
item.mod.visit(node) |
||||
} catch (e) { |
||||
reports.push({ |
||||
name: item.name, report: [{ warning: 'INTERNAL ERROR in module ' + item.name + ' ' + e.message, error: e.stack }] |
||||
}) |
||||
} |
||||
} |
||||
}) |
||||
return true |
||||
}}) |
||||
} |
||||
|
||||
// Here, modules can just collect the results from the AST walk,
|
||||
// but also perform new analysis.
|
||||
reports = reports.concat(modules.map((item, i) => { |
||||
let report: any = null |
||||
try { |
||||
report = item.mod.report(compilationResult) |
||||
} catch (e) { |
||||
report = [{ warning: 'INTERNAL ERROR in module ' + item.name + ' ' + e.message, error: e.stack }] |
||||
} |
||||
return { name: item.name, report: report } |
||||
})) |
||||
callback(reports) |
||||
} |
||||
|
||||
modules () { |
||||
return list |
||||
} |
||||
} |
@ -1,180 +0,0 @@ |
||||
const common = require('./staticAnalysisCommon') |
||||
const 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
|
||||
* "returns": [] // list of return vars as { type: ... , name: ... }
|
||||
* } |
||||
* ], |
||||
* "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)) { |
||||
const currentContract = getCurrentContract(that) |
||||
const 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)) { |
||||
let 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
|
||||
const 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) { |
||||
const 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) { |
||||
const locals = [] |
||||
new AstWalker().walk(funcNode, {'*': function (node) { |
||||
if (common.isVariableDeclaration(node)) locals.push(node) |
||||
return true |
||||
}}) |
||||
return locals |
||||
} |
||||
|
||||
module.exports = abstractAstView |
@ -0,0 +1,179 @@ |
||||
import common from './staticAnalysisCommon' |
||||
import { AstWalker } from 'remix-astwalker' |
||||
|
||||
export class abstractAstView { |
||||
contracts = [] |
||||
currentContractIndex = null |
||||
currentFunctionIndex = null |
||||
currentModifierIndex = null |
||||
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 |
||||
*/ |
||||
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
|
||||
* "returns": [] // list of return vars as { type: ... , name: ... }
|
||||
* } |
||||
* ], |
||||
* "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. |
||||
*/ |
||||
build_visit (relevantNodeFilter) { |
||||
var that = this |
||||
return function (node) { |
||||
if (common.isContractDefinition(node)) { |
||||
that.setCurrentContract(that, { |
||||
node: node, |
||||
functions: [], |
||||
relevantNodes: [], |
||||
modifiers: [], |
||||
inheritsFrom: [], |
||||
stateVariables: common.getStateVariableDeclarationsFormContractNode(node) |
||||
}) |
||||
} else if (common.isInheritanceSpecifier(node)) { |
||||
const currentContract = that.getCurrentContract(that) |
||||
const inheritsFromName = common.getInheritsFromName(node) |
||||
currentContract.inheritsFrom.push(inheritsFromName) |
||||
} else if (common.isFunctionDefinition(node)) { |
||||
that.setCurrentFunction(that, { |
||||
node: node, |
||||
relevantNodes: [], |
||||
modifierInvocations: [], |
||||
localVariables: that.getLocalVariables(node), |
||||
parameters: that.getLocalParameters(node), |
||||
returns: that.getReturnParameters(node) |
||||
}) |
||||
// push back relevant nodes to their the current fn if any
|
||||
that.getCurrentContract(that).relevantNodes.map((item) => { |
||||
if (item.referencedDeclaration === node.id) { |
||||
that.getCurrentFunction(that).relevantNodes.push(item.node) |
||||
} |
||||
}) |
||||
} else if (common.isModifierDefinition(node)) { |
||||
that.setCurrentModifier(that, { |
||||
node: node, |
||||
relevantNodes: [], |
||||
localVariables: that.getLocalVariables(node), |
||||
parameters: that.getLocalParameters(node) |
||||
}) |
||||
} else if (common.isModifierInvocation(node)) { |
||||
if (!that.isFunctionNotModifier) throw new Error('abstractAstView.js: Found modifier invocation outside of function scope.') |
||||
that.getCurrentFunction(that).modifierInvocations.push(node) |
||||
} else if (relevantNodeFilter(node)) { |
||||
let scope = (that.isFunctionNotModifier) ? that.getCurrentFunction(that) : that.getCurrentModifier(that) |
||||
if (scope) { |
||||
scope.relevantNodes.push(node) |
||||
} else { |
||||
scope = that.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 }) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
build_report (wrap) { |
||||
var that = this |
||||
return function (compilationResult) { |
||||
that.resolveStateVariablesInHierarchy(that.contracts) |
||||
return wrap(that.contracts, that.multipleContractsWithSameName) |
||||
} |
||||
} |
||||
|
||||
private resolveStateVariablesInHierarchy (contracts) { |
||||
contracts.map((c) => { |
||||
this.resolveStateVariablesInHierarchyForContract(c, contracts) |
||||
}) |
||||
} |
||||
|
||||
private resolveStateVariablesInHierarchyForContract (currentContract, contracts) { |
||||
currentContract.inheritsFrom.map((inheritsFromName) => { |
||||
// add variables from inherited contracts
|
||||
const 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) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
private setCurrentContract (that, contract) { |
||||
const name = common.getContractName(contract.node) |
||||
if (that.contracts.map((c) => common.getContractName(c.node)).filter((n) => n === name).length > 0) { |
||||
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) |
||||
} |
||||
|
||||
private setCurrentFunction (that, func) { |
||||
that.isFunctionNotModifier = true |
||||
that.currentFunctionIndex = (that.getCurrentContract(that).functions.push(func) - 1) |
||||
} |
||||
|
||||
private setCurrentModifier (that, modi) { |
||||
that.isFunctionNotModifier = false |
||||
that.currentModifierIndex = (that.getCurrentContract(that).modifiers.push(modi) - 1) |
||||
} |
||||
|
||||
private getCurrentContract (that) { |
||||
return that.contracts[that.currentContractIndex] |
||||
} |
||||
|
||||
private getCurrentFunction (that) { |
||||
return that.getCurrentContract(that).functions[that.currentFunctionIndex] |
||||
} |
||||
|
||||
private getCurrentModifier (that) { |
||||
return that.getCurrentContract(that).modifiers[that.currentModifierIndex] |
||||
} |
||||
|
||||
private getLocalParameters (funcNode) { |
||||
return this.getLocalVariables(common.getFunctionOrModifierDefinitionParameterPart(funcNode)).map(common.getType) |
||||
} |
||||
|
||||
private getReturnParameters (funcNode) { |
||||
return this.getLocalVariables(common.getFunctionOrModifierDefinitionReturnParameterPart(funcNode)).map((n) => { |
||||
return { |
||||
type: common.getType(n), |
||||
name: common.getDeclaredVariableName(n) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
private getLocalVariables (funcNode) { |
||||
const locals: any[] = [] |
||||
new AstWalker().walk(funcNode, {'*': function (node) { |
||||
if (common.isVariableDeclaration(node)) locals.push(node) |
||||
return true |
||||
}}) |
||||
return locals |
||||
} |
||||
} |
@ -1,30 +0,0 @@ |
||||
const name = 'Result not used: ' |
||||
const desc = 'The result of an operation was not used.' |
||||
const categories = require('./categories') |
||||
const common = require('./staticAnalysisCommon') |
||||
const algo = require('./algorithmCategories') |
||||
|
||||
function assignAndCompare () { |
||||
this.warningNodes = [] |
||||
} |
||||
|
||||
assignAndCompare.prototype.visit = function (node) { |
||||
if (common.isSubScopeWithTopLevelUnAssignedBinOp(node)) common.getUnAssignedTopLevelBinOps(node).forEach((n) => this.warningNodes.push(n)) |
||||
} |
||||
|
||||
assignAndCompare.prototype.report = function (compilationResults) { |
||||
return this.warningNodes.map((item, i) => { |
||||
return { |
||||
warning: 'A binary operation yields a value that is not used in the following. This is often caused by confusing assignment (=) and comparison (==).', |
||||
location: item.src |
||||
} |
||||
}) |
||||
} |
||||
|
||||
module.exports = { |
||||
name: name, |
||||
description: desc, |
||||
category: categories.MISC, |
||||
algorithm: algo.EXACT, |
||||
Module: assignAndCompare |
||||
} |
@ -0,0 +1,25 @@ |
||||
import { MISC } from './categories' |
||||
const common = require('./staticAnalysisCommon') |
||||
import { default as algorithm } from './algorithmCategories' |
||||
|
||||
export class assignAndCompare { |
||||
warningNodes: any = [] |
||||
name = 'Result not used: ' |
||||
description = 'The result of an operation was not used.' |
||||
category = MISC |
||||
algorithm = algorithm.EXACT |
||||
Module = this |
||||
|
||||
visit (node) { |
||||
if (common.isSubScopeWithTopLevelUnAssignedBinOp(node)) common.getUnAssignedTopLevelBinOps(node).forEach((n) => this.warningNodes.push(n)) |
||||
} |
||||
|
||||
report (compilationResults) { |
||||
return this.warningNodes.map((item, i) => { |
||||
return { |
||||
warning: 'A binary operation yields a value that is not used in the following. This is often caused by confusing assignment (=) and comparison (==).', |
||||
location: item.src |
||||
} |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,25 @@ |
||||
{ |
||||
"include": ["src", "index.ts"], |
||||
"compilerOptions": { |
||||
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ |
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ |
||||
"lib": ["dom", "es2018"], /* Specify library files to be included in the compilation. */ |
||||
"declaration": true, /* Generates corresponding '.d.ts' file. */ |
||||
"sourceMap": true, /* Generates corresponding '.map' file. */ |
||||
"outDir": "./dist", /* Redirect output structure to the directory. */ |
||||
/* Strict Type-Checking Options */ |
||||
"strict": true, /* Enable all strict type-checking options. */ |
||||
"noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */ |
||||
/* Module Resolution Options */ |
||||
"baseUrl": "./src", /* Base directory to resolve non-absolute module names. */ |
||||
"paths": { "remix-solidity": ["./"] }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ |
||||
"typeRoots": [ |
||||
"./@types", |
||||
"./node_modules/@types" |
||||
], |
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ |
||||
/* Experimental Options */ |
||||
"experimentalDecorators": false, /* Enables experimental support for ES7 decorators. */ |
||||
} |
||||
} |
||||
|
Loading…
Reference in new issue