truffle-analyze
Version:
Add vulnerability and weakness analysis via the MythX
322 lines (297 loc) • 12.4 kB
JavaScript
const assert = require('assert');
const sinon = require('sinon');
const rewire = require('rewire');
const fs = require('fs');
const srcmap = require('../lib/srcmap');
const mythx = require('../lib/mythx');
const rewired = rewire('../lib/issues2eslint');
describe('issues2Eslint', function() {
describe('MythXIssues class', () => {
let truffleJSON;
const MythXIssues = rewired.__get__('MythXIssues');
const contractJSON = `${__dirname}/sample-truffle/simple_dao/build/contracts/SimpleDAO.json`;
const sourceName = 'simple_dao.sol';
beforeEach(done => {
fs.readFile(contractJSON, 'utf8', (err, data) => {
if (err) return done(err);
truffleJSON = JSON.parse(data);
done();
});
});
it('should decode a source code location correctly', (done) => {
const issuesObject = new MythXIssues(truffleJSON);
assert.deepEqual(issuesObject.textSrcEntry2lineColumn('30:2:0', issuesObject.lineBreakPositions[sourceName]),
[ { 'line': 2, 'column': 27 }, { 'line': 2, 'column': 29 } ]);
done();
});
it('should decode a bytecode offset correctly', (done) => {
const issuesObject = new MythXIssues(truffleJSON);
assert.deepEqual(issuesObject.byteOffset2lineColumn('100', issuesObject.lineBreakPositions[sourceName]),
[ { 'line': 8, 'column': 0 }, { 'line': 25, 'column': 1 } ]);
done();
});
it('should decode a bytecode offset to empty result', (done) => {
const issuesObject = new MythXIssues(truffleJSON);
assert.deepEqual(issuesObject.byteOffset2lineColumn('50', issuesObject.lineBreakPositions[sourceName]),
[ { 'line': -1, 'column': 0 }, { } ]);
done();
});
it('should convert MythX issue to Eslint style with sourceFormat: evm-byzantium-bytecode', () => {
const mythXOutput = {
'sourceFormat': 'evm-byzantium-bytecode',
'sourceList': [
`/tmp/contracts/${sourceName}`
],
'sourceType': 'raw-bytecode',
'issues': [{
'description': {
'head': 'Head message',
'tail': 'Tail message'
},
'locations': [{
'sourceMap': '444:1:0'
}],
'severity': 'High',
'swcID': 'SWC-000',
'swcTitle': 'Test Title'
}],
'meta': {
'selected_compiler': '0.5.0',
'error': [],
'warning': []
}
};
const remappedMythXOutput = mythx.remapMythXOutput(mythXOutput);
const issuesObject = new MythXIssues(truffleJSON);
const res = issuesObject.issue2EsLint(remappedMythXOutput[0].issues[0], false, 'evm-byzantium-bytecode', sourceName);
assert.deepEqual({
ruleId: 'SWC-000',
column: 4,
line: 12,
endCol: 27,
endLine: 12,
fatal: false,
message: 'Head message Tail message',
mythXseverity: 'High',
severity: 3,
},
res);
});
it('should convert MythX issue to Eslint style with sourceFormat: text', () => {
const mythXOutput = {
'sourceType': 'solidity-file',
'sourceFormat': 'text',
'sourceList': [
`/tmp/contracts/${sourceName}`,
],
'issues': [{
'description': {
'head': 'Head message',
'tail': 'Tail message'
},
'locations': [{
'sourceMap': '310:23:0'
}],
'severity': 'High',
'swcID': 'SWC-000',
'swcTitle': 'Test Title'
}],
'meta': {
'selected_compiler': '0.5.0',
'error': [],
'warning': []
}
};
const remappedMythXOutput = mythx.remapMythXOutput(mythXOutput);
const issuesObject = new MythXIssues(truffleJSON);
const res = issuesObject.issue2EsLint(remappedMythXOutput[0].issues[0], false, 'text', sourceName);
assert.deepEqual({
ruleId: 'SWC-000',
column: 4,
line: 12,
endCol: 27,
endLine: 12,
fatal: false,
message: 'Head message Tail message',
mythXseverity: 'High',
severity: 3,
}, res);
});
it('should call isIgnorable correctly', () => {
const spyIsVariableDeclaration = sinon.spy(srcmap, 'isVariableDeclaration');
const spyIsDynamicArray = sinon.spy(srcmap, 'isDynamicArray');
const issuesObject = new MythXIssues(truffleJSON);
const res = issuesObject.isIgnorable('444:5:0', {}, sourceName);
assert.ok(spyIsVariableDeclaration.called);
assert.ok(spyIsDynamicArray.called);
assert.ok(spyIsDynamicArray.returned(false));
assert.equal(res, false);
spyIsVariableDeclaration.restore();
spyIsDynamicArray.restore();
});
it('should call isIgnorable correctly when issue is ignored', () => {
const spyIsVariableDeclaration = sinon.spy(srcmap, 'isVariableDeclaration');
const spyIsDynamicArray = sinon.stub(srcmap, 'isDynamicArray');
spyIsDynamicArray.returns(true);
const issuesObject = new MythXIssues(truffleJSON);
const res = issuesObject.isIgnorable('444:5:0', {}, sourceName);
assert.ok(spyIsVariableDeclaration.called);
assert.ok(spyIsDynamicArray.called);
assert.ok(res);
spyIsVariableDeclaration.restore();
spyIsDynamicArray.restore();
});
it('should call isIgnorable correctly when issue is ignored in debug mode', () => {
const spyIsVariableDeclaration = sinon.spy(srcmap, 'isVariableDeclaration');
const spyIsDynamicArray = sinon.stub(srcmap, 'isDynamicArray');
const loggerStub = sinon.stub();
spyIsDynamicArray.returns(true);
const issuesObject = new MythXIssues(truffleJSON);
const res = issuesObject.isIgnorable('444:5:0', { debug: true, logger: { log: loggerStub } }, sourceName);
assert.ok(spyIsVariableDeclaration.called);
assert.ok(spyIsDynamicArray.called);
assert.ok(loggerStub.called);
assert.ok(res);
spyIsVariableDeclaration.restore();
spyIsDynamicArray.restore();
});
it('should convert mythX report to Eslint issues', () => {
const mythXOutput = {
'sourceType': 'solidity-file',
'sourceFormat': 'text',
'sourceList': [
`/tmp/contracts/${sourceName}`,
],
'issues': [{
'description': {
'head': 'Head message',
'tail': 'Tail message'
},
'locations': [{
'sourceMap': '310:23:0'
}],
'severity': 'High',
'swcID': 'SWC-000',
'swcTitle': 'Test Title'
}],
'meta': {
'selected_compiler': '0.5.0',
'error': [],
'warning': []
}
};
const issuesObject = new MythXIssues(truffleJSON);
const remappedMythXOutput = mythx.remapMythXOutput(mythXOutput);
const result = remappedMythXOutput.map(output => issuesObject.convertMythXReport2EsIssue(output, true));
assert.deepEqual(result, [{
errorCount: 0,
warningCount: 1,
fixableErrorCount: 0,
fixableWarningCount: 0,
filePath: '/tmp/contracts/simple_dao.sol',
messages: [{
column: 4,
endCol: 27,
endLine: 12,
fatal: false,
line: 12,
message: 'Head message',
ruleId: 'SWC-000',
mythXseverity: 'High',
severity: 3,
}],
}]);
});
it('It normalize and store mythX API output', () => {
const issuesObject = new MythXIssues(truffleJSON);
const mythXOutput = [{
'sourceType': 'solidity-file',
'sourceFormat': 'text',
'sourceList': [
`/tmp/contracts/${sourceName}`,
],
'issues': [{
'description': {
'head': 'Head message',
'tail': 'Tail message'
},
'locations': [{
'sourceMap': '310:23:0'
}],
'severity': 'High',
'swcID': 'SWC-000',
'swcTitle': 'Test Title'
}],
'meta': {
'selected_compiler': '0.5.0',
'error': [],
'warning': []
}
}];
issuesObject.setIssues(mythXOutput);
assert.deepEqual(issuesObject.issues, [{
'sourceType': 'solidity-file',
'sourceFormat': 'text',
'source': '/tmp/contracts/simple_dao.sol',
'issues': [{
'description': {
'head': 'Head message',
'tail': 'Tail message'
},
'sourceMap': '310:23:0',
'severity': 'High',
'swcID': 'SWC-000',
'swcTitle': 'Test Title',
'extra': undefined,
}],
}]);
});
it('It converts mythX issues to ESLint issues output format', () => {
const issuesObject = new MythXIssues(truffleJSON);
const mythXOutput = [{
'sourceType': 'solidity-file',
'sourceFormat': 'text',
'sourceList': [
`/tmp/contracts/${sourceName}`,
],
'issues': [{
'description': {
'head': 'Head message',
'tail': 'Tail message'
},
'locations': [{
'sourceMap': '310:23:0'
}],
'severity': 'High',
'swcID': 'SWC-000',
'swcTitle': 'Test Title'
}],
'meta': {
'selected_compiler': '0.5.0',
'error': [],
'warning': []
}
}];
issuesObject.setIssues(mythXOutput);
const result = issuesObject.getEslintIssues(true);
assert.deepEqual(result, [{
errorCount: 0,
warningCount: 1,
fixableErrorCount: 0,
fixableWarningCount: 0,
filePath: '/tmp/contracts/simple_dao.sol',
messages: [{
ruleId: 'SWC-000',
line: 12,
column: 4,
endCol: 27,
endLine: 12,
message: 'Head message',
mythXseverity: 'High',
severity: 3,
fatal: false,
}],
}])
});
});
});