remix-analyzer: js to ts

pull/7/head
aniket-engg 5 years ago committed by Aniket
parent 727cf75d8c
commit 6a55230cae
  1. 0
      remix-analyzer/index.ts
  2. 4093
      remix-analyzer/package-lock.json
  3. 9
      remix-analyzer/package.json
  4. 56
      remix-analyzer/src/solidity-analyzer/index.js
  5. 54
      remix-analyzer/src/solidity-analyzer/index.ts
  6. 180
      remix-analyzer/src/solidity-analyzer/modules/abstractAstView.js
  7. 179
      remix-analyzer/src/solidity-analyzer/modules/abstractAstView.ts
  8. 2
      remix-analyzer/src/solidity-analyzer/modules/algorithmCategories.ts
  9. 30
      remix-analyzer/src/solidity-analyzer/modules/assignAndCompare.js
  10. 25
      remix-analyzer/src/solidity-analyzer/modules/assignAndCompare.ts
  11. 25
      remix-analyzer/tsconfig.json

File diff suppressed because it is too large Load Diff

@ -2,7 +2,8 @@
"name": "remix-analyzer",
"version": "0.3.23",
"description": "Remix Analyzer",
"main": "./index.js",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"contributors": [
{
"name": "Alex Beregszaszi",
@ -18,9 +19,11 @@
}
],
"dependencies": {
"remix-astwalker": "0.0.19",
"remix-lib": "0.4.22"
},
"scripts": {
"build": "tsc",
"lint": "standard",
"test": "npm run lint && tape ./test/tests.js"
},
@ -34,11 +37,13 @@
"license": "MIT",
"homepage": "https://github.com/ethereum/remix#readme",
"devDependencies": {
"@types/node": "^13.7.0",
"babel-eslint": "^7.1.1",
"babel-plugin-transform-object-assign": "^6.22.0",
"babel-preset-es2015": "^6.24.0",
"npm-install-version": "^6.0.2",
"standard": "^7.0.1",
"tape": "^4.6.0"
"tape": "^4.6.0",
"typescript": "^3.7.5"
}
}

@ -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
}
}

@ -3,7 +3,7 @@
* Produce exact results or have false positives and negatives in them
* A further category could be approximate if some form of approximation is used
*/
module.exports = {
export default {
EXACT: { hasFalsePositives: false, hasFalseNegatives: false, id: 'EXACT' },
HEURISTIC: { hasFalsePositives: true, hasFalseNegatives: true, id: 'HEURI' }
}

@ -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…
Cancel
Save