embark-mythx/mythx.js

190 lines
6.2 KiB
JavaScript
Raw Normal View History

2019-04-15 08:11:43 +00:00
require('dotenv').config()
const armlet = require('armlet')
const fs = require('fs')
const yaml = require('js-yaml');
2019-04-15 08:11:43 +00:00
const mythXUtil = require('./lib/mythXUtil');
const asyncPool = require('tiny-async-pool');
2019-04-22 15:47:01 +00:00
const { MythXIssues, doReport } = require('./lib/issues2eslint');
2019-04-15 08:11:43 +00:00
2019-04-26 15:28:30 +00:00
const defaultConcurrentAnalyses = 4
2019-04-15 08:11:43 +00:00
async function analyse(contracts, cfg, embark) {
2019-04-15 08:11:43 +00:00
cfg.logger = embark.logger
// Set analysis parameters
2019-04-26 15:28:30 +00:00
const limit = cfg.limit || defaultConcurrentAnalyses
2019-04-15 08:11:43 +00:00
if (isNaN(limit)) {
2019-04-22 15:47:01 +00:00
embark.logger.info(`limit parameter should be a number; got ${limit}.`)
2019-04-15 08:11:43 +00:00
return 1
}
2019-04-26 15:28:30 +00:00
if (limit < 0 || limit > defaultConcurrentAnalyses) {
embark.logger.info(`limit should be between 0 and ${defaultConcurrentAnalyses}.`)
2019-04-15 08:11:43 +00:00
return 1
}
// Connect to MythX via armlet
2019-04-26 15:28:30 +00:00
if(!process.env.MYTHX_ETH_ADDRESS || !process.env.MYTHX_PASSWORD) {
embark.logger.error("Environment variables 'MYTHX_ETH_ADDRESS' and 'MYTHX_PASSWORD' not found. Continuing in evaluation mode.")
process.env.MYTHX_ETH_ADDRESS = "0x0000000000000000000000000000000000000000"
process.env.MYTHX_PASSWORD = "trial"
}
2019-04-15 08:11:43 +00:00
const armletClient = new armlet.Client(
{
clientToolName: "embark-mythx",
password: process.env.MYTHX_PASSWORD,
ethAddress: process.env.MYTHX_ETH_ADDRESS,
})
// Filter contracts based on parameter choice
2019-04-23 18:43:13 +00:00
let toSubmit = { "contracts": {}, "sources": contracts.sources };
if(!("ignore" in embark.pluginConfig)) {
embark.pluginConfig.ignore = []
}
for (let [filename, contractObjects] of Object.entries(contracts.contracts)) {
for (let [contractName, contract] of Object.entries(contractObjects)) {
if(!("contracts" in cfg)) {
if (embark.pluginConfig.ignore.indexOf(contractName) == -1) {
if(!toSubmit.contracts[filename]) {
toSubmit.contracts[filename] = {}
}
toSubmit.contracts[filename][contractName] = contract;
}
} else {
if (cfg.contracts.indexOf(contractName) >= 0 && embark.pluginConfig.ignore.indexOf(contractName) == -1) {
if(!toSubmit.contracts[filename]) {
toSubmit.contracts[filename] = {}
}
toSubmit.contracts[filename][contractName] = contract;
}
}
}
}
2019-04-23 18:43:13 +00:00
// Stop here if no contracts are left
if(Object.keys(toSubmit.contracts).length === 0) {
2019-04-23 18:43:13 +00:00
embark.logger.info("No contracts to submit.");
return 0;
}
const submitObjects = mythXUtil.buildRequestData(toSubmit)
2019-04-22 15:47:01 +00:00
const { objects, errors } = await doAnalysis(armletClient, cfg, submitObjects, null, limit)
2019-04-15 08:11:43 +00:00
2019-04-22 15:47:01 +00:00
const result = doReport(cfg, objects, errors)
2019-04-15 08:11:43 +00:00
return result
}
async function getStatus(uuid, embark) {
// Connect to MythX via armlet
const armletClient = new armlet.Client(
{
clientToolName: "embark-mythx",
password: process.env.MYTHX_PASSWORD,
ethAddress: process.env.MYTHX_ETH_ADDRESS,
})
try {
const results = await armletClient.getIssues(uuid);
return ghettoReport(embark.logger.info, results);
} catch (err) {
embark.logger.warn(err);
return 1;
}
}
2019-04-15 08:11:43 +00:00
const doAnalysis = async (armletClient, config, contracts, contractNames = null, limit) => {
const timeout = (config.timeout || 300) * 1000;
const initialDelay = ('initial-delay' in config) ? config['initial-delay'] * 1000 : undefined;
const noCacheLookup = ('no-cache-lookup' in config) ? config['no-cache-lookup'] : true;
2019-04-15 08:11:43 +00:00
const results = await asyncPool(limit, contracts, async buildObj => {
const obj = new MythXIssues(buildObj, config);
let analyzeOpts = {
clientToolName: 'embark-mythx',
noCacheLookup,
2019-04-15 08:11:43 +00:00
timeout,
initialDelay
};
analyzeOpts.data = mythXUtil.cleanAnalyzeDataEmptyProps(obj.buildObj, config.debug, config.logger.debug);
analyzeOpts.data.analysisMode = config.full ? "full" : "quick";
2019-04-15 08:11:43 +00:00
if (config.debug > 1) {
config.logger.debug("analyzeOpts: " + `${util.inspect(analyzeOpts, {depth: null})}`);
}
// request analysis to armlet.
try {
2019-04-23 18:43:13 +00:00
//TODO: Call analyze/analyzeWithStatus asynchronously
config.logger.info("Submitting '" + obj.contractName + "' for " + analyzeOpts.data.analysisMode + " analysis...")
const {issues, status} = await armletClient.analyzeWithStatus(analyzeOpts);
2019-04-15 08:11:43 +00:00
obj.uuid = status.uuid;
2019-04-23 18:43:13 +00:00
2019-04-15 08:11:43 +00:00
if (status.status === 'Error') {
return [status, null];
} else {
obj.setIssues(issues);
}
2019-04-23 18:43:13 +00:00
2019-04-15 08:11:43 +00:00
return [null, obj];
} catch (err) {
let errStr;
if (typeof err === 'string') {
errStr = `${err}`;
} else if (typeof err.message === 'string') {
errStr = err.message;
} else {
errStr = `${util.inspect(err)}`;
}
if (errStr.includes('User or default timeout reached after')
|| errStr.includes('Timeout reached after')) {
return [(buildObj.contractName + ": ").yellow + errStr, null];
} else {
return [(buildObj.contractName + ": ").red + errStr, null];
2019-04-22 15:47:01 +00:00
2019-04-15 08:11:43 +00:00
}
}
});
return results.reduce((accum, curr) => {
const [ err, obj ] = curr;
if (err) {
accum.errors.push(err);
} else if (obj) {
accum.objects.push(obj);
}
return accum;
}, { errors: [], objects: [] });
};
function ghettoReport(logger, results) {
let issuesCount = 0;
results.forEach(ele => {
issuesCount += ele.issues.length;
});
if (issuesCount === 0) {
logger('No issues found');
return 0;
}
for (const group of results) {
logger(group.sourceList.join(', ').underline);
for (const issue of group.issues) {
logger(yaml.safeDump(issue, {'skipInvalid': true}));
}
}
return 1;
}
module.exports = {
analyse,
getStatus
}