From 1ec36cab12491eff148834bb2c3e6101faaacdbf Mon Sep 17 00:00:00 2001 From: cgewecke Date: Tue, 9 Jul 2019 00:34:28 -0700 Subject: [PATCH] Modify preprocessor for nested conditionals, test w/ oraclize --- lib/preprocessor.js | 32 +++++++++----- test/app.js | 32 +++++++++++++- test/cli/oraclize.js | 10 +++++ test/if.js | 24 ++++++++++ .../oraclize-plus.sol => cli/Oraclize.sol} | 44 ++++++++++++------- test/sources/if/else-if-without-brackets.sol | 10 +++++ test/statements.js | 7 --- test/util/mockTruffle.js | 18 ++++++-- 8 files changed, 138 insertions(+), 39 deletions(-) create mode 100644 test/cli/oraclize.js rename test/sources/{statements/oraclize-plus.sol => cli/Oraclize.sol} (98%) create mode 100644 test/sources/if/else-if-without-brackets.sol diff --git a/lib/preprocessor.js b/lib/preprocessor.js index 9624f3c..6ab90ae 100644 --- a/lib/preprocessor.js +++ b/lib/preprocessor.js @@ -2,14 +2,17 @@ const SolExplore = require('sol-explore'); const SolidityParser = require('solidity-parser-antlr'); const crRegex = /[\r\n ]+$/g; +const OPEN = '{'; +const CLOSE = '}'; + /** * Splices enclosing brackets into `contract` around `expression`; * @param {String} contract solidity source * @param {Object} node AST node to bracket * @return {String} contract */ -function blockWrap(contract, expression) { - return contract.slice(0, expression.range[0]) + '{' + contract.slice(expression.range[0], expression.range[1] + 1) + '}' + contract.slice(expression.range[1] + 1); +function insertBrace(contract, item, offset) { + return contract.slice(0,item.pos + offset) + item.type + contract.slice(item.pos + offset) } /** Remove 'pure' and 'view' from the function declaration. @@ -47,24 +50,28 @@ module.exports.run = function r(contract) { try { const ast = SolidityParser.parse(contract, { range: true }); - blocksToWrap = []; + insertions = []; viewPureToRemove = []; SolidityParser.visit(ast, { IfStatement: function(node) { if (node.trueBody.type !== 'Block') { - blocksToWrap.push(node.trueBody); - } else if (node.falseBody && node.falseBody.type !== 'Block'){ - blocksToWrap.push(node.falseBody); + insertions.push({type: OPEN, pos: node.trueBody.range[0]}); + insertions.push({type: CLOSE, pos: node.trueBody.range[1] + 1}); + } else if ( node.falseBody && node.falseBody.type !== 'Block' ) { + insertions.push({type: OPEN, pos: node.falseBody.range[0]}); + insertions.push({type: CLOSE, pos: node.falseBody.range[1] + 1}); } }, ForStatement: function(node){ if (node.body.type !== 'Block'){ - blocksToWrap.push(node.body); + insertions.push({type: OPEN, pos: node.body.range[0]}); + insertions.push({type: CLOSE, pos: node.body.range[1] + 1}); } }, WhileStatement: function(node){ if (node.body.type !== 'Block'){ - blocksToWrap.push(node.body); + insertions.push({type: OPEN, pos: node.body.range[0]}); + insertions.push({type: CLOSE, pos: node.body.range[1] + 1}); } }, FunctionDefinition: function(node){ @@ -73,12 +80,13 @@ module.exports.run = function r(contract) { } } }) - // Firstly, remove pures and views. Note that we replace 'pure' and 'view' with spaces, so + // Firstly, remove pures and views. Note that we replace 'pure' and 'view' with spaces, so // character counts remain the same, so we can do this in any order viewPureToRemove.forEach(node => contract = removePureView(contract, node)); - // We apply the blocks we found in reverse order to avoid extra characters messing things up. - blocksToWrap.sort((a,b) => a.range[0] < b.range[0]); - blocksToWrap.forEach(block => contract = blockWrap(contract, block)) + // Sort the insertion points. + insertions.sort((a,b) => a.pos - b.pos); + insertions.forEach((item, idx) => contract = insertBrace(contract, item, idx)); + } catch (err) { contract = err; keepRunning = false; diff --git a/test/app.js b/test/app.js index 8b56a42..0dc3cf1 100644 --- a/test/app.js +++ b/test/app.js @@ -28,7 +28,7 @@ describe('app', () => { }; before(done => { - const command = `./node_modules/.bin/testrpc-sc --gasLimit 0xfffffffffff --port ${port}`; + const command = `./node_modules/.bin/testrpc-sc --allowUnlimitedContractSize --gasLimit 0xfffffffffff --port ${port}`; testrpcProcess = childprocess.exec(command); testrpcProcess.stdout.on('data', data => { @@ -194,6 +194,36 @@ describe('app', () => { } }); + it('large contract w/ many unbracketed statements (Oraclize)', () => { + const trufflejs = + `module.exports = { + networks: { + coverage: { + host: "localhost", + network_id: "*", + port: 8555, + gas: 0xfffffffffff, + gasPrice: 0x01 + }, + }, + compilers: { + solc: { + version: "0.4.24", + } + } + };`; + + // Directory should be clean + assert(pathExists('./coverage') === false, 'should start without: coverage'); + assert(pathExists('./coverage.json') === false, 'should start without: coverage.json'); + + // Run script (exits 0); + mock.install('Oraclize.sol', 'oraclize.js', config, trufflejs, null, true); + shell.exec(script); + assert(shell.error() === null, 'script should not error'); + + }); + it('simple contract: should generate coverage, cleanup & exit(0)', () => { // Directory should be clean assert(pathExists('./coverage') === false, 'should start without: coverage'); diff --git a/test/cli/oraclize.js b/test/cli/oraclize.js new file mode 100644 index 0000000..994d5e9 --- /dev/null +++ b/test/cli/oraclize.js @@ -0,0 +1,10 @@ +/* eslint-env node, mocha */ +/* global artifacts, contract, assert */ +const usingOraclize = artifacts.require('usingOraclize'); + +contract('Nothing', () => { + it('nothing', async () => { + const ora = await usingOraclize.new(); + await ora.test(); + }); +}); \ No newline at end of file diff --git a/test/if.js b/test/if.js index 60cb49b..cd989d6 100644 --- a/test/if.js +++ b/test/if.js @@ -186,6 +186,30 @@ describe('if, else, and else if statements', () => { }).catch(done); }); + it('should cover an else if statement with an unbracketed alternate', done => { + const contract = util.getCode('if/else-if-without-brackets.sol'); + const info = getInstrumentedVersion(contract, filePath); + const coverage = new CoverageMap(); + coverage.addContract(info, filePath); + + vm.execute(info.contract, 'a', [2]).then(events => { + const mapping = coverage.generate(events, pathPrefix); + assert.deepEqual(mapping[filePath].l, { + 5: 1, 6: 0, 8: 0, + }); + assert.deepEqual(mapping[filePath].b, { + 1: [0, 1], 2: [0, 1] + }); + assert.deepEqual(mapping[filePath].s, { + 1: 1, 2: 0, 3: 1, 4: 0 + }); + assert.deepEqual(mapping[filePath].f, { + 1: 1, + }); + done(); + }).catch(done); + }); + it('should cover nested if statements with missing else statements', done => { const contract = util.getCode('if/nested-if-missing-else.sol'); const info = getInstrumentedVersion(contract, filePath); diff --git a/test/sources/statements/oraclize-plus.sol b/test/sources/cli/Oraclize.sol similarity index 98% rename from test/sources/statements/oraclize-plus.sol rename to test/sources/cli/Oraclize.sol index 307f8e0..de77e3f 100644 --- a/test/sources/statements/oraclize-plus.sol +++ b/test/sources/cli/Oraclize.sol @@ -1,4 +1,3 @@ -pragma solidity ^0.4.0; // /* Copyright (c) 2015-2016 Oraclize SRL @@ -30,7 +29,7 @@ THE SOFTWARE. */ // This api is currently targeted at 0.4.18, please import oraclizeAPI_pre0.4.sol or oraclizeAPI_0.4 where necessary -pragma solidity ^0.4.18; +pragma solidity ^0.4.24; contract OraclizeI { address public cbAddress; @@ -44,7 +43,7 @@ contract OraclizeI { function getPrice(string _datasource, uint gaslimit) public returns (uint _dsprice); function setProofType(byte _proofType) external; function setCustomGasPrice(uint _gasPrice) external; - function randomDS_getSessionPubKeyHash() external constant returns(bytes32); + function randomDS_getSessionPubKeyHash() external view returns(bytes32); } contract OraclizeAddrResolverI { function getAddress() public returns (address _addr); @@ -525,7 +524,7 @@ contract usingOraclize { return oraclize.randomDS_getSessionPubKeyHash(); } - function getCodeSize(address _addr) constant internal returns(uint _size) { + function getCodeSize(address _addr) view internal returns(uint _size) { assembly { _size := extcodesize(_addr) } @@ -551,7 +550,7 @@ contract usingOraclize { return address(iaddr); } - function strCompare(string _a, string _b) internal pure returns (int) { + /*function strCompare(string _a, string _b) internal pure returns (int) { bytes memory a = bytes(_a); bytes memory b = bytes(_b); uint minLength = a.length; @@ -567,7 +566,7 @@ contract usingOraclize { return 1; else return 0; - } + }*/ function indexOf(string _haystack, string _needle) internal pure returns (int) { bytes memory h = bytes(_haystack); @@ -638,8 +637,9 @@ contract usingOraclize { for (uint i=0; i= 48)&&(bresult[i] <= 57)){ if (decimals){ - if (_b == 0) break; - else _b--; + if (_b == 0) { + break; + } else _b--; } mint *= 10; mint += uint(bresult[i]) - 48; @@ -1043,29 +1043,39 @@ contract usingOraclize { } function nestingTest(string _a, string _b, string _c, string _d, string _e) { + uint i; + uint k; + + uint[] _ba; + uint[] _bb; + uint[] _bc; + uint[] _bd; + uint[] _be; + uint[] babcde; + for (i = 0; i < _bb.length; i++) for (i = 0; i < _bb.length; i++) - if (i = 0) + if (i == 0) babcde[k++] = _bb[i]; for (i = 0; i < _bb.length; i++) for (i = 0; i < _bb.length; i++) - if (i = 0) - while (i) + if (i == 0) + while (true) babcde[k++] = _bb[i]; for (i = 0; i < _bb.length; i++) for (i = 0; i < _bb.length; i++) - if (i = 0) + if (i == 0) babcde[k++] = _bb[i]; for (i = 0; i < _bb.length; i++) for (i = 0; i < _bb.length; i++) - if (i = 0) + if (i == 0) babcde[k++] = _bb[i]; for (i = 0; i < _be.length; i++) babcde[k++] = _be[i]; - for (uint i = 0; i < _ba.length; i++) babcde[k++] = _ba[i]; + for (i = 0; i < _ba.length; i++) babcde[k++] = _ba[i]; for (i = 0; i < _bb.length; i++) babcde[k++] = _bb[i]; for (i = 0; i < _bb.length; i++) for (i = 0; i < _bb.length; i++) - if (i = 0) + if (i == 0) babcde[k++] = _bb[i]; for (i = 0; i < _bc.length; i++) babcde[k++] = _bc[i]; for (i = 0; i < _bd.length; i++) babcde[k++] = _bd[i]; @@ -1079,5 +1089,9 @@ contract usingOraclize { return; } + function test() public { + uint tls = 5; + } + } // diff --git a/test/sources/if/else-if-without-brackets.sol b/test/sources/if/else-if-without-brackets.sol new file mode 100644 index 0000000..d5f08b9 --- /dev/null +++ b/test/sources/if/else-if-without-brackets.sol @@ -0,0 +1,10 @@ +pragma solidity ^0.5.0; + +contract Test { + function a(uint x) public { + if (x == 1) { + revert(); + } else if (x == 50) + x = 5; + } +} \ No newline at end of file diff --git a/test/statements.js b/test/statements.js index fa9d9e7..876c4cc 100644 --- a/test/statements.js +++ b/test/statements.js @@ -59,13 +59,6 @@ describe('generic statements', () => { util.report(output.errors); }); - it.only('should compile after instrumenting many unbracketed statements', () => { - const contract = util.getCode('statements/oraclize-plus.sol'); - const info = getInstrumentedVersion(contract, filePath); - const output = JSON.parse(solc.compile(util.codeToCompilerInput(info.contract))); - util.report(output.errors); - }); - it('should NOT pass tests if the contract has a compilation error', () => { const contract = util.getCode('statements/compilation-error.sol'); const info = getInstrumentedVersion(contract, filePath); diff --git a/test/util/mockTruffle.js b/test/util/mockTruffle.js index dded226..f8fd55e 100644 --- a/test/util/mockTruffle.js +++ b/test/util/mockTruffle.js @@ -27,7 +27,14 @@ const defaultTruffleJs = `module.exports = { * @param {String} contract located in /test/sources/cli/ * @param {[type]} test located in /test/cli/ */ -module.exports.install = function install(contract, test, config, _trufflejs, _trufflejsName) { +module.exports.install = function install( + contract, + test, + config, + _trufflejs, + _trufflejsName, + noMigrations +) { const configjs = `module.exports = ${JSON.stringify(config)}`; const contractLocation = `./${contract}`; const trufflejsName = _trufflejsName || 'truffle.js'; @@ -68,9 +75,12 @@ module.exports.install = function install(contract, test, config, _trufflejs, _t shell.cp(`./test/sources/cli/${contract}`, `./mock/contracts/${contract}`); } - shell.cp('./test/sources/cli/Migrations.sol', './mock/contracts/Migrations.sol'); - fs.writeFileSync('./mock/migrations/1_initial_migration.js', initialMigration); - fs.writeFileSync('./mock/migrations/2_deploy_contracts.js', deployContracts); + if (!noMigrations){ + shell.cp('./test/sources/cli/Migrations.sol', './mock/contracts/Migrations.sol'); + fs.writeFileSync('./mock/migrations/1_initial_migration.js', initialMigration); + fs.writeFileSync('./mock/migrations/2_deploy_contracts.js', deployContracts); + } + fs.writeFileSync(`./mock/${trufflejsName}`, trufflejs); fs.writeFileSync('./mock/assets/asset.js', asset); fs.writeFileSync('./.solcover.js', configjs);