From d68814df4b1a79334863c1db4d7576061ae86616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Fr=C3=B6wis?= Date: Fri, 31 Mar 2017 10:07:11 +0200 Subject: [PATCH] Static Analysis: more unit tests --- .../modules/staticAnalysisCommon.js | 51 +- .../staticAnalysisCommon-test.js | 1563 ++++++++++++++++- 2 files changed, 1559 insertions(+), 55 deletions(-) diff --git a/src/app/staticanalysis/modules/staticAnalysisCommon.js b/src/app/staticanalysis/modules/staticAnalysisCommon.js index c0f94960e5..8c4190f840 100644 --- a/src/app/staticanalysis/modules/staticAnalysisCommon.js +++ b/src/app/staticanalysis/modules/staticAnalysisCommon.js @@ -67,12 +67,12 @@ function getType (node) { function getFunctionCallType (func) { if (!(isExternalDirectCall(func) || isThisLocalCall(func) || isLocalCall(func))) throw new Error('staticAnalysisCommon.js: not function call Node') - if (isExternalDirectCall(func)) return func.attributes.type + if (isExternalDirectCall(func) || isThisLocalCall(func)) return func.attributes.type return findFirstSubNodeLTR(func, exactMatch(nodeTypes.IDENTIFIER)).attributes.type } function getEffectedVariableName (effectNode) { - if (!isEffect(effectNode)) throw new Error('staticAnalysisCommon.js: not an effect Node') + if (!isEffect(effectNode) || isInlineAssembly(effectNode)) throw new Error('staticAnalysisCommon.js: not an effect Node or inline assembly') return findFirstSubNodeLTR(effectNode, exactMatch(nodeTypes.IDENTIFIER)).attributes.value } @@ -91,6 +91,11 @@ function getExternalDirectCallContractName (extDirectCall) { return extDirectCall.children[0].attributes.type.replace(new RegExp(basicRegex.CONTRACTTYPE), '') } +function getThisLocalCallContractName (thisLocalCall) { + if (!isThisLocalCall(thisLocalCall)) throw new Error('staticAnalysisCommon.js: not an this local call Node') + return thisLocalCall.children[0].attributes.type.replace(new RegExp(basicRegex.CONTRACTTYPE), '') +} + function getExternalDirectCallMemberName (extDirectCall) { if (!isExternalDirectCall(extDirectCall)) throw new Error('staticAnalysisCommon.js: not an external direct call Node') return extDirectCall.attributes.member_name @@ -132,7 +137,7 @@ function getFunctionCallTypeParameterType (func) { function getFullQualifiedFunctionCallIdent (contract, func) { if (isLocalCall(func)) return getContractName(contract) + '.' + getLocalCallName(func) + '(' + getFunctionCallTypeParameterType(func) + ')' - else if (isThisLocalCall(func)) return getContractName(contract) + '.' + getThisLocalCallName(func) + '(' + getFunctionCallTypeParameterType(func) + ')' + else if (isThisLocalCall(func)) return getThisLocalCallContractName(func) + '.' + getThisLocalCallName(func) + '(' + getFunctionCallTypeParameterType(func) + ')' else if (isExternalDirectCall(func)) return getExternalDirectCallContractName(func) + '.' + getExternalDirectCallMemberName(func) + '(' + getFunctionCallTypeParameterType(func) + ')' else throw new Error('staticAnalysisCommon.js: Can not get function name form non function call node') } @@ -201,6 +206,18 @@ function isConstantFunction (node) { return isFunctionDefinition(node) && node.attributes.constant === true } +function isPlusPlusUnaryOperation (node) { + return nodeType(node, exactMatch(nodeTypes.UNARYOPERATION)) && operator(node, exactMatch(utils.escapeRegExp('++'))) +} + +function isMinusMinusUnaryOperation (node) { + return nodeType(node, exactMatch(nodeTypes.UNARYOPERATION)) && operator(node, exactMatch(utils.escapeRegExp('--'))) +} + +function isFullyImplementedContract (node) { + return nodeType(node, exactMatch(nodeTypes.CONTRACTDEFINITION)) && node.attributes.fullyImplemented === true +} + function isCallToNonConstLocalFunction (node) { return isLocalCall(node) && !expressionType(node.children[0], basicRegex.CONSTANTFUNCTIONTYPE) } @@ -216,27 +233,11 @@ function isNowAccess (node) { name(node, exactMatch('now')) } -function isPlusPlusUnaryOperation (node) { - return nodeType(node, exactMatch(nodeTypes.UNARYOPERATION)) && operator(node, exactMatch(utils.escapeRegExp('++'))) -} - -function isMinusMinusUnaryOperation (node) { - return nodeType(node, exactMatch(nodeTypes.UNARYOPERATION)) && operator(node, exactMatch('--')) -} - -function isFullyImplementedContract (node) { - return nodeType(node, exactMatch(nodeTypes.CONTRACTDEFINITION)) && node.attributes.fullyImplemented === true -} - // usage of block timestamp function isBlockTimestampAccess (node) { return isSpecialVariableAccess(node, specialVariables.BLOCKTIMESTAMP) } -function isSpecialVariableAccess (node, varType) { - return isMemberAccess(node, varType.type, varType.obj, varType.obj, varType.member) -} - function isThisLocalCall (node) { return isMemberAccess(node, basicRegex.FUNCTIONTYPE, exactMatch('this'), basicRegex.CONTRACTTYPE, undefined) } @@ -281,6 +282,8 @@ function isLLDelegatecall (node) { undefined, exactMatch(basicTypes.ADDRESS), exactMatch(lowLevelCallTypes.DELEGATECALL.ident)) } +// #################### Complex Node Identification - Private + function isMemberAccess (node, retType, accessor, accessorType, memberName) { return nodeType(node, exactMatch(nodeTypes.MEMBERACCESS)) && expressionType(node, retType) && @@ -290,6 +293,10 @@ function isMemberAccess (node, retType, accessor, accessorType, memberName) { expressionType(node.children[0], accessorType) } +function isSpecialVariableAccess (node, varType) { + return isMemberAccess(node, varType.type, varType.obj, varType.obj, varType.member) +} + // #################### Node Identification Primitives function nrOfChildren (node, nr) { @@ -337,6 +344,7 @@ function buildFunctionSignature (paramTypes, returnTypes, isPayable) { } function findFirstSubNodeLTR (node, type) { + if (!node || !node.children) return null for (let i = 0; i < node.children.length; ++i) { var item = node.children[i] if (nodeType(item, type)) return item @@ -360,6 +368,7 @@ module.exports = { getLocalCallName: getLocalCallName, getInheritsFromName: getInheritsFromName, getExternalDirectCallContractName: getExternalDirectCallContractName, + getThisLocalCallContractName: getThisLocalCallContractName, getExternalDirectCallMemberName: getExternalDirectCallMemberName, getFunctionDefinitionName: getFunctionDefinitionName, getFunctionCallTypeParameterType: getFunctionCallTypeParameterType, @@ -385,6 +394,8 @@ module.exports = { isExternalDirectCall: isExternalDirectCall, isFullyImplementedContract: isFullyImplementedContract, isCallToNonConstLocalFunction: isCallToNonConstLocalFunction, + isPlusPlusUnaryOperation: isPlusPlusUnaryOperation, + isMinusMinusUnaryOperation: isMinusMinusUnaryOperation, // #################### Trivial Node Identification isFunctionDefinition: isFunctionDefinition, @@ -406,9 +417,11 @@ module.exports = { specialVariables: specialVariables, helpers: { nrOfChildren: nrOfChildren, + minNrOfChildren: minNrOfChildren, expressionType: expressionType, nodeType: nodeType, name: name, + operator: operator, buildFunctionSignature: buildFunctionSignature } } diff --git a/test/staticanalysis/staticAnalysisCommon-test.js b/test/staticanalysis/staticAnalysisCommon-test.js index e6f458141d..9d1e85ea2d 100644 --- a/test/staticanalysis/staticAnalysisCommon-test.js +++ b/test/staticanalysis/staticAnalysisCommon-test.js @@ -3,6 +3,8 @@ var test = require('tape') var common = require('../../src/app/staticanalysis/modules/staticAnalysisCommon') var utils = require('../../src/app/utils') +// #################### helpers Test + test('staticAnalysisCommon.helpers.buildFunctionSignature', function (t) { t.plan(7) @@ -35,6 +37,8 @@ test('staticAnalysisCommon.helpers.buildFunctionSignature', function (t) { 'check fixed call type') }) +// #################### Node Identification Primitives + test('staticAnalysisCommon.helpers.name', function (t) { t.plan(9) var node = { attributes: { value: 'now' } } @@ -47,6 +51,22 @@ test('staticAnalysisCommon.helpers.name', function (t) { lowlevelAccessersCommon(t, common.helpers.name, node) }) +test('staticAnalysisCommon.helpers.operator', function (t) { + t.plan(10) + var node = { attributes: { operator: '++' } } + var node2 = { attributes: { operator: '+++' } } + + var escapedPP = utils.escapeRegExp('++') + var escapedPPExact = `^${escapedPP}$` + + t.ok(common.helpers.operator(node, escapedPPExact), 'should work for ++') + t.notOk(common.helpers.operator(node2, escapedPPExact), 'should not work for +++') + t.ok(common.helpers.operator(node, escapedPP), 'should work for ++') + t.ok(common.helpers.operator(node2, escapedPP), 'should work for +++') + + lowlevelAccessersCommon(t, common.helpers.operator, node) +}) + test('staticAnalysisCommon.helpers.nodeType', function (t) { t.plan(9) var node = { name: 'Identifier', attributes: { name: 'now' } } @@ -85,6 +105,23 @@ test('staticAnalysisCommon.helpers.nrOfChildren', function (t) { lowlevelAccessersCommon(t, common.helpers.nrOfChildren, node) }) +test('staticAnalysisCommon.helpers.minNrOfChildren', function (t) { + t.plan(13) + var node = { name: 'Identifier', children: ['a', 'b'], attributes: { value: 'now', type: 'uint256' } } + var node2 = { name: 'FunctionCall', children: [], attributes: { member_name: 'call', type: 'function () payable returns (bool)' } } + var node3 = { name: 'FunctionCall', attributes: { member_name: 'call', type: 'function () payable returns (bool)' } } + + t.ok(common.helpers.minNrOfChildren(node, 2), 'should work for 2 children') + t.ok(common.helpers.minNrOfChildren(node, 1), 'should work for 1 children') + t.ok(common.helpers.minNrOfChildren(node, 0), 'should work for 0 children') + t.notOk(common.helpers.minNrOfChildren(node, 3), 'has less than 3 children') + t.notOk(common.helpers.minNrOfChildren(node, '1+2'), 'regex should not work') + t.ok(common.helpers.minNrOfChildren(node2, 0), 'should work for 0 children') + t.ok(common.helpers.minNrOfChildren(node3, 0), 'should work without children arr') + + lowlevelAccessersCommon(t, common.helpers.minNrOfChildren, node) +}) + function lowlevelAccessersCommon (t, f, someNode) { t.ok(f(someNode), 'always ok if type is undefinded') t.ok(f(someNode, undefined), 'always ok if name is undefinded 2') @@ -94,48 +131,302 @@ function lowlevelAccessersCommon (t, f, someNode) { t.notOk(f(), 'false on no params') } -test('staticAnalysisCommon.isLowLevelCall', function (t) { - t.plan(6) - var sendAst = { name: 'MemberAccess', children: [{attributes: { value: 'd', type: 'address' }}], attributes: { value: 'send', type: 'function (uint256) returns (bool)' } } - var callAst = { name: 'MemberAccess', children: [{attributes: { value: 'f', type: 'address' }}], attributes: { member_name: 'call', type: 'function () payable returns (bool)' } } - var callcodeAst = { name: 'MemberAccess', children: [{attributes: { value: 'f', type: 'address' }}], attributes: { member_name: 'callcode', type: 'function () payable returns (bool)' } } - var delegatecallAst = { name: 'MemberAccess', children: [{attributes: { value: 'g', type: 'address' }}], attributes: { member_name: 'delegatecall', type: 'function () returns (bool)' } } +// #################### Trivial Getter Test - t.ok(common.isLowLevelSendInst(sendAst) && common.isLowLevelCall(sendAst), 'send is llc should work') - t.ok(common.isLowLevelCallInst(callAst) && common.isLowLevelCall(callAst), 'call is llc should work') - t.notOk(common.isLowLevelCallInst(callcodeAst), 'callcode is not call') - t.ok(common.isLowLevelCallcodeInst(callcodeAst) && common.isLowLevelCall(callcodeAst), 'callcode is llc should work') - t.notOk(common.isLowLevelCallcodeInst(callAst), 'call is not callcode') - t.ok(common.isLowLevelDelegatecallInst(delegatecallAst) && common.isLowLevelCall(delegatecallAst), 'delegatecall is llc should work') +test('staticAnalysisCommon.getType', function (t) { + t.plan(3) + var node = { name: 'Identifier', children: ['a', 'b'], attributes: { value: 'now', type: 'uint256' } } + var node2 = { name: 'FunctionCall', children: [], attributes: { member_name: 'call', type: 'function () payable returns (bool)' } } + var node3 = { name: 'FunctionCall', attributes: { member_name: 'call', type: 'function () payable returns (bool)' } } + + t.ok(common.getType(node) === 'uint256', 'gettype should work for different nodes') + t.ok(common.getType(node2) === 'function () payable returns (bool)', 'gettype should work for different nodes') + t.ok(common.getType(node3) === 'function () payable returns (bool)', 'gettype should work for different nodes') }) -test('staticAnalysisCommon.isThisLocalCall', function (t) { +// #################### Complex Getter Test + +test('staticAnalysisCommon.getFunctionCallType', function (t) { + t.plan(4) + var localCall = { + 'attributes': { + 'type': 'tuple()', + 'type_conversion': false + }, + 'children': [ + { + 'attributes': { + 'type': 'function (struct Ballot.Voter storage pointer)', + 'value': 'bli' + }, + 'id': 37, + 'name': 'Identifier', + 'src': '540:3:0' + }, + { + 'attributes': { + 'type': 'struct Ballot.Voter storage pointer', + 'value': 'x' + }, + 'id': 38, + 'name': 'Identifier', + 'src': '544:1:0' + } + ], + 'id': 39, + 'name': 'FunctionCall', + 'src': '540:6:0' + } + var thisLocalCall = { name: 'MemberAccess', children: [ { attributes: { value: 'this', type: 'contract test' }, name: 'Identifier' } ], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } + var externalDirect = { + attributes: { + member_name: 'info', + type: 'function () payable external returns (uint256)' + }, + children: [ + { + attributes: { + type: 'contract InfoFeed', + value: 'f' + }, + id: 30, + name: 'Identifier', + src: '405:1:0' + } + ], + id: 32, + name: 'MemberAccess', + src: '405:6:0' + } + t.ok(common.getFunctionCallType(thisLocalCall) === 'function (bytes32,address) returns (bool)', 'this local call returns correct type') + t.ok(common.getFunctionCallType(externalDirect) === 'function () payable external returns (uint256)', 'external direct call returns correct type') + t.ok(common.getFunctionCallType(localCall) === 'function (struct Ballot.Voter storage pointer)', 'local call returns correct type') + t.throws(() => common.getFunctionCallType({ name: 'MemberAccess' }), undefined, 'throws on wrong type') +}) + +test('staticAnalysisCommon.getEffectedVariableName', function (t) { t.plan(3) - var node = { name: 'MemberAccess', children: [{attributes: { value: 'this', type: 'contract test' }}], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } - t.ok(common.isThisLocalCall(node), 'is this.local_method() used should work') - t.notOk(common.isBlockTimestampAccess(node), 'is block.timestamp used should not work') - t.notOk(common.isNowAccess(node), 'is now used should not work') + var assignment = { + 'attributes': { + 'operator': '=', + 'type': 'uint256' + }, + 'children': [ + { + 'attributes': { + 'type': 'uint256' + }, + 'children': [ + { + 'attributes': { + 'type': 'mapping(address => uint256)', + 'value': 'c' + }, + 'id': 61, + 'name': 'Identifier', + 'src': '873:1:0' + }, + { + 'attributes': { + 'member_name': 'sender', + 'type': 'address' + }, + 'children': [ + { + 'attributes': { + 'type': 'msg', + 'value': 'msg' + }, + 'id': 62, + 'name': 'Identifier', + 'src': '875:3:0' + } + ], + 'id': 63, + 'name': 'MemberAccess', + 'src': '875:10:0' + } + ], + 'id': 64, + 'name': 'IndexAccess', + 'src': '873:13:0' + }, + { + 'attributes': { + 'hexvalue': '30', + 'subdenomination': null, + 'token': null, + 'type': 'int_const 0', + 'value': '0' + }, + 'id': 65, + 'name': 'Literal', + 'src': '889:1:0' + } + ], + 'id': 66, + 'name': 'Assignment', + 'src': '873:17:0' + } + var inlineAssembly = { + 'children': [ + ], + 'id': 21, + 'name': 'InlineAssembly', + 'src': '809:41:0' + } + t.throws(() => common.getEffectedVariableName(inlineAssembly), 'staticAnalysisCommon.js: not an effect Node or inline assembly', 'get from inline assembly should throw') + t.ok(common.getEffectedVariableName(assignment) === 'c', 'get right name for assignment') + t.throws(() => common.getEffectedVariableName({ name: 'MemberAccess' }), undefined, 'should throw on all other nodes') }) -test('staticAnalysisCommon.isBlockTimestampAccess', function (t) { +test('staticAnalysisCommon.getLocalCallName', function (t) { t.plan(3) - var node = { name: 'MemberAccess', children: [{attributes: { value: 'block', type: 'block' }}], attributes: { value: 'timestamp', type: 'uint256' } } - t.notOk(common.isThisLocalCall(node), 'is this.local_method() used should not work') - t.ok(common.isBlockTimestampAccess(node), 'is block.timestamp used should work') - t.notOk(common.isNowAccess(node), 'is now used should not work') + var localCall = { + 'attributes': { + 'type': 'tuple()', + 'type_conversion': false + }, + 'children': [ + { + 'attributes': { + 'type': 'function (struct Ballot.Voter storage pointer)', + 'value': 'bli' + }, + 'id': 37, + 'name': 'Identifier', + 'src': '540:3:0' + }, + { + 'attributes': { + 'type': 'struct Ballot.Voter storage pointer', + 'value': 'x' + }, + 'id': 38, + 'name': 'Identifier', + 'src': '544:1:0' + } + ], + 'id': 39, + 'name': 'FunctionCall', + 'src': '540:6:0' + } + var thisLocalCall = { name: 'MemberAccess', children: [ { attributes: { value: 'this', type: 'contract test' }, name: 'Identifier' } ], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } + var externalDirect = { + attributes: { + member_name: 'info', + type: 'function () payable external returns (uint256)' + }, + children: [ + { + attributes: { + type: 'contract InfoFeed', + value: 'f' + }, + id: 30, + name: 'Identifier', + src: '405:1:0' + } + ], + id: 32, + name: 'MemberAccess', + src: '405:6:0' + } + t.ok(common.getLocalCallName(localCall) === 'bli', 'getLocal call name from node') + t.throws(() => common.getLocalCallName(externalDirect), undefined, 'throws on other nodes') + t.throws(() => common.getLocalCallName(thisLocalCall), undefined, 'throws on other nodes') }) -test('staticAnalysisCommon.isNowAccess', function (t) { +test('staticAnalysisCommon.getThisLocalCallName', function (t) { t.plan(3) - var node = { name: 'Identifier', attributes: { value: 'now', type: 'uint256' } } - t.notOk(common.isThisLocalCall(node), 'is this.local_method() used should not work') - t.notOk(common.isBlockTimestampAccess(node), 'is block.timestamp used should not work') - t.ok(common.isNowAccess(node), 'is now used should work') + var localCall = { + 'attributes': { + 'type': 'tuple()', + 'type_conversion': false + }, + 'children': [ + { + 'attributes': { + 'type': 'function (struct Ballot.Voter storage pointer)', + 'value': 'bli' + }, + 'id': 37, + 'name': 'Identifier', + 'src': '540:3:0' + }, + { + 'attributes': { + 'type': 'struct Ballot.Voter storage pointer', + 'value': 'x' + }, + 'id': 38, + 'name': 'Identifier', + 'src': '544:1:0' + } + ], + 'id': 39, + 'name': 'FunctionCall', + 'src': '540:6:0' + } + var thisLocalCall = { name: 'MemberAccess', children: [ { attributes: { value: 'this', type: 'contract test' }, name: 'Identifier' } ], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } + var externalDirect = { + attributes: { + member_name: 'info', + type: 'function () payable external returns (uint256)' + }, + children: [ + { + attributes: { + type: 'contract InfoFeed', + value: 'f' + }, + id: 30, + name: 'Identifier', + src: '405:1:0' + } + ], + id: 32, + name: 'MemberAccess', + src: '405:6:0' + } + t.ok(common.getThisLocalCallName(thisLocalCall) === 'b', 'get this Local call name from node') + t.throws(() => common.getThisLocalCallName(externalDirect), undefined, 'throws on other nodes') + t.throws(() => common.getThisLocalCallName(localCall), undefined, 'throws on other nodes') }) -test('staticAnalysisCommon.isExternalDirectCall', function (t) { - t.plan(5) - var node = { +test('staticAnalysisCommon.getExternalDirectCallContractName', function (t) { + t.plan(3) + var localCall = { + 'attributes': { + 'type': 'tuple()', + 'type_conversion': false + }, + 'children': [ + { + 'attributes': { + 'type': 'function (struct Ballot.Voter storage pointer)', + 'value': 'bli' + }, + 'id': 37, + 'name': 'Identifier', + 'src': '540:3:0' + }, + { + 'attributes': { + 'type': 'struct Ballot.Voter storage pointer', + 'value': 'x' + }, + 'id': 38, + 'name': 'Identifier', + 'src': '544:1:0' + } + ], + 'id': 39, + 'name': 'FunctionCall', + 'src': '540:6:0' + } + var thisLocalCall = { name: 'MemberAccess', children: [ { attributes: { value: 'this', type: 'contract test' }, name: 'Identifier' } ], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } + var externalDirect = { attributes: { member_name: 'info', type: 'function () payable external returns (uint256)' @@ -155,11 +446,1211 @@ test('staticAnalysisCommon.isExternalDirectCall', function (t) { name: 'MemberAccess', src: '405:6:0' } + t.ok(common.getExternalDirectCallContractName(externalDirect) === 'InfoFeed', 'external direct call contract name from node') + t.throws(() => common.getExternalDirectCallContractName(thisLocalCall), undefined, 'throws on other nodes') + t.throws(() => common.getExternalDirectCallContractName(localCall), undefined, 'throws on other nodes') +}) - var node2 = { name: 'MemberAccess', children: [{attributes: { value: 'this', type: 'contract test' }}], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } - t.notOk(common.isThisLocalCall(node), 'is this.local_method() used should not work') - t.notOk(common.isBlockTimestampAccess(node), 'is block.timestamp used should not work') - t.notOk(common.isNowAccess(node), 'is now used should not work') - t.ok(common.isExternalDirectCall(node), 'f.info() should be external direct call') - t.notOk(common.isExternalDirectCall(node2), 'local call is not an exernal call') +test('staticAnalysisCommon.getThisLocalCallContractName', function (t) { + t.plan(3) + var localCall = { + 'attributes': { + 'type': 'tuple()', + 'type_conversion': false + }, + 'children': [ + { + 'attributes': { + 'type': 'function (struct Ballot.Voter storage pointer)', + 'value': 'bli' + }, + 'id': 37, + 'name': 'Identifier', + 'src': '540:3:0' + }, + { + 'attributes': { + 'type': 'struct Ballot.Voter storage pointer', + 'value': 'x' + }, + 'id': 38, + 'name': 'Identifier', + 'src': '544:1:0' + } + ], + 'id': 39, + 'name': 'FunctionCall', + 'src': '540:6:0' + } + var thisLocalCall = { name: 'MemberAccess', children: [ { attributes: { value: 'this', type: 'contract test' }, name: 'Identifier' } ], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } + var externalDirect = { + attributes: { + member_name: 'info', + type: 'function () payable external returns (uint256)' + }, + children: [ + { + attributes: { + type: 'contract InfoFeed', + value: 'f' + }, + id: 30, + name: 'Identifier', + src: '405:1:0' + } + ], + id: 32, + name: 'MemberAccess', + src: '405:6:0' + } + t.ok(common.getThisLocalCallContractName(thisLocalCall) === 'test', 'this local call contract name from node') + t.throws(() => common.getThisLocalCallContractName(localCall), undefined, 'throws on other nodes') + t.throws(() => common.getThisLocalCallContractName(externalDirect), undefined, 'throws on other nodes') +}) + +test('staticAnalysisCommon.getExternalDirectCallMemberName', function (t) { + t.plan(3) + var localCall = { + 'attributes': { + 'type': 'tuple()', + 'type_conversion': false + }, + 'children': [ + { + 'attributes': { + 'type': 'function (struct Ballot.Voter storage pointer)', + 'value': 'bli' + }, + 'id': 37, + 'name': 'Identifier', + 'src': '540:3:0' + }, + { + 'attributes': { + 'type': 'struct Ballot.Voter storage pointer', + 'value': 'x' + }, + 'id': 38, + 'name': 'Identifier', + 'src': '544:1:0' + } + ], + 'id': 39, + 'name': 'FunctionCall', + 'src': '540:6:0' + } + var thisLocalCall = { name: 'MemberAccess', children: [ { attributes: { value: 'this', type: 'contract test' }, name: 'Identifier' } ], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } + var externalDirect = { + attributes: { + member_name: 'info', + type: 'function () payable external returns (uint256)' + }, + children: [ + { + attributes: { + type: 'contract InfoFeed', + value: 'f' + }, + id: 30, + name: 'Identifier', + src: '405:1:0' + } + ], + id: 32, + name: 'MemberAccess', + src: '405:6:0' + } + t.ok(common.getExternalDirectCallMemberName(externalDirect) === 'info', 'external direct call name from node') + t.throws(() => common.getExternalDirectCallMemberName(thisLocalCall), undefined, 'throws on other nodes') + t.throws(() => common.getExternalDirectCallMemberName(localCall), undefined, 'throws on other nodes') +}) + +test('staticAnalysisCommon.getContractName', function (t) { + t.plan(2) + var contract = { name: 'ContractDefinition', attributes: { name: 'baz' } } + t.ok(common.getContractName(contract) === 'baz', 'returns right contract name') + t.throws(() => common.getContractName({ name: 'InheritanceSpecifier' }), undefined, 'throws on other nodes') +}) + +test('staticAnalysisCommon.getFunctionDefinitionName', function (t) { + t.plan(2) + var func = { name: 'FunctionDefinition', attributes: { name: 'foo' } } + t.ok(common.getFunctionDefinitionName(func) === 'foo', 'returns right contract name') + t.throws(() => common.getFunctionDefinitionName({ name: 'InlineAssembly' }), undefined, 'throws on other nodes') +}) + +test('staticAnalysisCommon.getInheritsFromName', function (t) { + t.plan(2) + var inh = { + 'children': [ + { + 'attributes': { + 'name': 'r' + }, + 'id': 7, + 'name': 'UserDefinedTypeName', + 'src': '84:1:0' + } + ], + 'id': 8, + 'name': 'InheritanceSpecifier', + 'src': '84:1:0' + } + t.ok(common.getInheritsFromName(inh) === 'r', 'returns right contract name') + t.throws(() => common.getInheritsFromName({ name: 'ElementaryTypeName' }), undefined, 'throws on other nodes') +}) + +test('staticAnalysisCommon.getDeclaredVariableName', function (t) { + t.plan(2) + var node1 = { + 'attributes': { + 'name': 'x', + 'type': 'struct Ballot.Voter storage pointer' + }, + 'children': [ + { + 'attributes': { + 'name': 'Voter' + }, + 'id': 43, + 'name': 'UserDefinedTypeName', + 'src': '604:5:0' + } + ], + 'id': 44, + 'name': 'VariableDeclaration', + 'src': '604:15:0' + } + t.ok(common.getDeclaredVariableName(node1) === 'x', 'extract right variable name') + node1.name = 'FunctionCall' + t.throws(() => common.getDeclaredVariableName(node1) === 'x', undefined, 'throw if wrong node') +}) + +test('staticAnalysisCommon.getStateVariableDeclarationsFormContractNode', function (t) { + t.plan(4) + var contract = { + 'attributes': { + 'fullyImplemented': true, + 'isLibrary': false, + 'linearizedBaseContracts': [ + 274 + ], + 'name': 'Ballot' + }, + 'children': [ + { + 'attributes': { + 'name': 'Voter' + }, + 'children': [], + 'name': 'StructDefinition' + }, + { + 'attributes': { + 'name': 'Proposal' + }, + 'children': [], + 'name': 'StructDefinition' + }, + { + 'attributes': { + 'name': 'chairperson', + 'type': 'address' + }, + 'children': [ + { + 'attributes': { + 'name': 'address' + }, + 'name': 'ElementaryTypeName' + } + ], + 'name': 'VariableDeclaration' + }, + { + 'attributes': { + 'name': 'voters', + 'type': 'mapping(address => struct Ballot.Voter storage ref)' + }, + 'children': [ + { + 'children': [ + { + 'attributes': { + 'name': 'address' + }, + 'name': 'ElementaryTypeName' + }, + { + 'attributes': { + 'name': 'Voter' + }, + 'name': 'UserDefinedTypeName' + } + ], + 'name': 'Mapping' + } + ], + 'name': 'VariableDeclaration' + }, + { + 'attributes': { + 'name': 'proposals', + 'type': 'struct Ballot.Proposal storage ref[] storage ref' + }, + 'children': [ + { + 'children': [ + { + 'attributes': { + 'name': 'Proposal' + }, + 'name': 'UserDefinedTypeName' + } + ], + 'name': 'ArrayTypeName' + } + ], + 'name': 'VariableDeclaration' + }, + { + 'attributes': { + 'constant': false, + 'name': 'Ballot', + 'payable': false, + 'visibility': 'public' + }, + 'children': [], + 'name': 'FunctionDefinition' + }, + { + 'attributes': { + 'constant': false, + 'name': 'giveRightToVote', + 'payable': false, + 'visibility': 'public' + }, + 'children': [], + 'name': 'FunctionDefinition' + } + ], + 'name': 'ContractDefinition' + } + var res = common.getStateVariableDeclarationsFormContractNode(contract).map(common.getDeclaredVariableName) + t.comment(res) + t.ok(res[0] === 'chairperson', 'var 1 should be ') + t.ok(res[1] === 'voters', 'var 2 should be ') + t.ok(res[2] === 'proposals', 'var 3 should be ') + t.ok(res[3] === undefined, 'var 4 should be undefined') +}) + +test('staticAnalysisCommon.getFunctionOrModifierDefinitionParameterPart', function (t) { + t.plan(2) + var funDef = { + 'attributes': { + 'constant': true, + 'name': 'winnerName', + 'payable': false, + 'visibility': 'public' + }, + 'children': [ + { + 'children': [ + ], + 'name': 'ParameterList' + }, + { + 'children': [], + 'name': 'ParameterList' + }, + { + 'children': [], + 'name': 'Block' + } + ], + 'name': 'FunctionDefinition' + } + t.ok(common.helpers.nodeType(common.getFunctionOrModifierDefinitionParameterPart(funDef), 'ParameterList'), 'should return a parameterList') + t.throws(() => common.getFunctionOrModifierDefinitionParameterPart({ name: 'SourceUnit' }), undefined, 'throws on other nodes') +}) + +test('staticAnalysisCommon.getFunctionCallTypeParameterType', function (t) { + t.plan(4) + var localCall = { + 'attributes': { + 'type': 'tuple()', + 'type_conversion': false + }, + 'children': [ + { + 'attributes': { + 'type': 'function (struct Ballot.Voter storage pointer)', + 'value': 'bli' + }, + 'id': 37, + 'name': 'Identifier', + 'src': '540:3:0' + }, + { + 'attributes': { + 'type': 'struct Ballot.Voter storage pointer', + 'value': 'x' + }, + 'id': 38, + 'name': 'Identifier', + 'src': '544:1:0' + } + ], + 'id': 39, + 'name': 'FunctionCall', + 'src': '540:6:0' + } + var thisLocalCall = { name: 'MemberAccess', children: [ { attributes: { value: 'this', type: 'contract test' }, name: 'Identifier' } ], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } + var externalDirect = { + attributes: { + member_name: 'info', + type: 'function () payable external returns (uint256)' + }, + children: [ + { + attributes: { + type: 'contract InfoFeed', + value: 'f' + }, + id: 30, + name: 'Identifier', + src: '405:1:0' + } + ], + id: 32, + name: 'MemberAccess', + src: '405:6:0' + } + t.ok(common.getFunctionCallTypeParameterType(thisLocalCall) === 'bytes32,address', 'this local call returns correct type') + t.ok(common.getFunctionCallTypeParameterType(externalDirect) === '', 'external direct call returns correct type') + t.ok(common.getFunctionCallTypeParameterType(localCall) === 'struct Ballot.Voter storage pointer', 'local call returns correct type') + t.throws(() => common.getFunctionCallTypeParameterType({ name: 'MemberAccess' }), undefined, 'throws on wrong type') +}) + +test('staticAnalysisCommon.getFullQualifiedFunctionCallIdent', function (t) { + t.plan(4) + var contract = { name: 'ContractDefinition', attributes: { name: 'baz' } } + var localCall = { + 'attributes': { + 'type': 'tuple()', + 'type_conversion': false + }, + 'children': [ + { + 'attributes': { + 'type': 'function (struct Ballot.Voter storage pointer)', + 'value': 'bli' + }, + 'id': 37, + 'name': 'Identifier', + 'src': '540:3:0' + }, + { + 'attributes': { + 'type': 'struct Ballot.Voter storage pointer', + 'value': 'x' + }, + 'id': 38, + 'name': 'Identifier', + 'src': '544:1:0' + } + ], + 'id': 39, + 'name': 'FunctionCall', + 'src': '540:6:0' + } + var thisLocalCall = { name: 'MemberAccess', children: [ { attributes: { value: 'this', type: 'contract test' }, name: 'Identifier' } ], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } + var externalDirect = { + attributes: { + member_name: 'info', + type: 'function () payable external returns (uint256)' + }, + children: [ + { + attributes: { + type: 'contract InfoFeed', + value: 'f' + }, + id: 30, + name: 'Identifier', + src: '405:1:0' + } + ], + id: 32, + name: 'MemberAccess', + src: '405:6:0' + } + + t.ok(common.getFullQualifiedFunctionCallIdent(contract, thisLocalCall) === 'test.b(bytes32,address)', 'this local call returns correct type') + t.ok(common.getFullQualifiedFunctionCallIdent(contract, externalDirect) === 'InfoFeed.info()', 'external direct call returns correct type') + t.ok(common.getFullQualifiedFunctionCallIdent(contract, localCall) === 'baz.bli(struct Ballot.Voter storage pointer)', 'local call returns correct type') + t.throws(() => common.getFullQualifiedFunctionCallIdent(contract, { name: 'MemberAccess' }), undefined, 'throws on wrong type') +}) + +test('staticAnalysisCommon.getFullQuallyfiedFuncDefinitionIdent', function (t) { + t.plan(3) + var contract = { name: 'ContractDefinition', attributes: { name: 'baz' } } + var funDef = { + 'attributes': { + 'constant': false, + 'name': 'getY', + 'payable': false, + 'visibility': 'public' + }, + 'children': [ + { + 'children': [ + { + 'attributes': { + 'name': 'z', + 'type': 'uint256' + }, + 'children': [ + { + 'attributes': { + 'name': 'uint' + }, + 'name': 'ElementaryTypeName' + } + ], + 'name': 'VariableDeclaration' + }, + { + 'attributes': { + 'name': 'r', + 'type': 'bool' + }, + 'children': [ + { + 'attributes': { + 'name': 'bool' + }, + 'name': 'ElementaryTypeName' + } + ], + 'name': 'VariableDeclaration' + } + ], + 'name': 'ParameterList' + }, + { + 'children': [ + { + 'attributes': { + 'name': '', + 'type': 'uint256' + }, + 'children': [ + { + 'attributes': { + 'name': 'uint' + }, + 'id': 34, + 'name': 'ElementaryTypeName', + 'src': '285:4:0' + } + ], + 'id': 35, + 'name': 'VariableDeclaration', + 'src': '285:4:0' + } + ], + 'name': 'ParameterList' + }, + { + 'children': [], + 'name': 'Block' + } + ], + 'name': 'FunctionDefinition' + } + t.ok(common.getFullQuallyfiedFuncDefinitionIdent(contract, funDef, ['uint256', 'bool']) === 'baz.getY(uint256,bool)', 'creates right signature') + t.throws(() => common.getFullQuallyfiedFuncDefinitionIdent(contract, { name: 'MemberAccess' }, ['uint256', 'bool']), undefined, 'throws on wrong nodes') + t.throws(() => common.getFullQuallyfiedFuncDefinitionIdent({ name: 'FunctionCall' }, funDef, ['uint256', 'bool']), undefined, 'throws on wrong nodes') +}) + +// #################### Trivial Node Identification + +test('staticAnalysisCommon.isFunctionDefinition', function (t) { + t.plan(3) + var node1 = { name: 'FunctionDefinition' } + var node2 = { name: 'MemberAccess' } + var node3 = { name: 'FunctionDefinitionBLABLA' } + + t.ok(common.isFunctionDefinition(node1), 'is exact match should work') + t.notOk(common.isFunctionDefinition(node2), 'different node should not work') + t.notOk(common.isFunctionDefinition(node3), 'substring should not work') +}) + +test('staticAnalysisCommon.isModifierDefinition', function (t) { + t.plan(3) + var node1 = { name: 'ModifierDefinition' } + var node2 = { name: 'MemberAccess' } + var node3 = { name: 'ModifierDefinitionBLABLA' } + + t.ok(common.isModifierDefinition(node1), 'is exact match should work') + t.notOk(common.isModifierDefinition(node2), 'different node should not work') + t.notOk(common.isModifierDefinition(node3), 'substring should not work') +}) + +test('staticAnalysisCommon.isModifierInvocation', function (t) { + t.plan(3) + var node1 = { name: 'ModifierInvocation' } + var node2 = { name: 'MemberAccess' } + var node3 = { name: 'ModifierInvocationBLABLA' } + + t.ok(common.isModifierInvocation(node1), 'is exact match should work') + t.notOk(common.isModifierInvocation(node2), 'different node should not work') + t.notOk(common.isModifierInvocation(node3), 'substring should not work') +}) + +test('staticAnalysisCommon.isVariableDeclaration', function (t) { + t.plan(3) + var node1 = { name: 'VariableDeclaration' } + var node2 = { name: 'MemberAccess' } + var node3 = { name: 'VariableDeclarationBLABLA' } + + t.ok(common.isVariableDeclaration(node1), 'is exact match should work') + t.notOk(common.isVariableDeclaration(node2), 'different node should not work') + t.notOk(common.isVariableDeclaration(node3), 'substring should not work') +}) + +test('staticAnalysisCommon.isInheritanceSpecifier', function (t) { + t.plan(3) + var node1 = { name: 'InheritanceSpecifier' } + var node2 = { name: 'MemberAccess' } + var node3 = { name: 'InheritanceSpecifierBLABLA' } + + t.ok(common.isInheritanceSpecifier(node1), 'is exact match should work') + t.notOk(common.isInheritanceSpecifier(node2), 'different node should not work') + t.notOk(common.isInheritanceSpecifier(node3), 'substring should not work') +}) + +test('staticAnalysisCommon.isAssignment', function (t) { + t.plan(3) + var node1 = { name: 'Assignment' } + var node2 = { name: 'MemberAccess' } + var node3 = { name: 'AssignmentBLABLA' } + + t.ok(common.isAssignment(node1), 'is exact match should work') + t.notOk(common.isAssignment(node2), 'different node should not work') + t.notOk(common.isAssignment(node3), 'substring should not work') +}) + +test('staticAnalysisCommon.isContractDefinition', function (t) { + t.plan(3) + var node1 = { name: 'ContractDefinition' } + var node2 = { name: 'MemberAccess' } + var node3 = { name: 'ContractDefinitionBLABLA' } + + t.ok(common.isContractDefinition(node1), 'is exact match should work') + t.notOk(common.isContractDefinition(node2), 'different node should not work') + t.notOk(common.isContractDefinition(node3), 'substring should not work') +}) + +test('staticAnalysisCommon.isInlineAssembly', function (t) { + t.plan(3) + var node1 = { name: 'InlineAssembly' } + var node2 = { name: 'MemberAccess' } + var node3 = { name: 'InlineAssemblyBLABLA' } + + t.ok(common.isInlineAssembly(node1), 'is exact match should work') + t.notOk(common.isInlineAssembly(node2), 'different node should not work') + t.notOk(common.isInlineAssembly(node3), 'substring should not work') +}) + +// #################### Complex Node Identification + +test('staticAnalysisCommon.isStorageVariableDeclaration', function (t) { + t.plan(3) + var node1 = { + 'attributes': { + 'name': 'x', + 'type': 'struct Ballot.Voter storage pointer' + }, + 'children': [ + { + 'attributes': { + 'name': 'Voter' + }, + 'id': 43, + 'name': 'UserDefinedTypeName', + 'src': '604:5:0' + } + ], + 'id': 44, + 'name': 'VariableDeclaration', + 'src': '604:15:0' + } + var node2 = { + 'attributes': { + 'name': 'voters', + 'type': 'mapping(address => struct Ballot.Voter storage ref)' + }, + 'children': [ + { + 'children': [ + { + 'attributes': { + 'name': 'address' + }, + 'id': 16, + 'name': 'ElementaryTypeName', + 'src': '235:7:0' + }, + { + 'attributes': { + 'name': 'Voter' + }, + 'id': 17, + 'name': 'UserDefinedTypeName', + 'src': '246:5:0' + } + ], + 'id': 18, + 'name': 'Mapping', + 'src': '227:25:0' + } + ], + 'id': 19, + 'name': 'VariableDeclaration', + 'src': '227:32:0' + } + var node3 = { + 'attributes': { + 'name': 'voters', + 'type': 'bytes32' + }, + 'children': [ + { + 'attributes': { + 'name': 'bytes' + }, + 'id': 16, + 'name': 'ElementaryTypeName', + 'src': '235:7:0' + } + ], + 'id': 19, + 'name': 'VariableDeclaration', + 'src': '227:32:0' + } + + t.ok(common.isStorageVariableDeclaration(node1), 'struct storage pointer param is storage') + t.ok(common.isStorageVariableDeclaration(node2), 'struct storage pointer mapping param is storage') + t.notOk(common.isStorageVariableDeclaration(node3), 'bytes is not storage') +}) + +test('staticAnalysisCommon.isInteraction', function (t) { + t.plan(6) + var sendAst = { name: 'MemberAccess', children: [{attributes: { value: 'd', type: 'address' }}], attributes: { value: 'send', type: 'function (uint256) returns (bool)' } } + var callAst = { name: 'MemberAccess', children: [{attributes: { value: 'f', type: 'address' }}], attributes: { member_name: 'call', type: 'function () payable returns (bool)' } } + var callcodeAst = { name: 'MemberAccess', children: [{attributes: { value: 'f', type: 'address' }}], attributes: { member_name: 'callcode', type: 'function () payable returns (bool)' } } + var delegatecallAst = { name: 'MemberAccess', children: [{attributes: { value: 'g', type: 'address' }}], attributes: { member_name: 'delegatecall', type: 'function () returns (bool)' } } + var nodeExtDir = { + attributes: { + member_name: 'info', + type: 'function () payable external returns (uint256)' + }, + children: [ + { + attributes: { + type: 'contract InfoFeed', + value: 'f' + }, + id: 30, + name: 'Identifier', + src: '405:1:0' + } + ], + id: 32, + name: 'MemberAccess', + src: '405:6:0' + } + var nodeNot = { + 'attributes': { + 'type': 'tuple()', + 'type_conversion': false + }, + 'children': [ + { + 'attributes': { + 'type': 'function (struct Ballot.Voter storage pointer)', + 'value': 'bli' + }, + 'id': 37, + 'name': 'Identifier', + 'src': '540:3:0' + }, + { + 'attributes': { + 'type': 'struct Ballot.Voter storage pointer', + 'value': 'x' + }, + 'id': 38, + 'name': 'Identifier', + 'src': '544:1:0' + } + ], + 'id': 39, + 'name': 'FunctionCall', + 'src': '540:6:0' + } + + t.ok(common.isInteraction(sendAst), 'send is interaction') + t.ok(common.isInteraction(callAst), 'call is interaction') + t.ok(common.isInteraction(nodeExtDir), 'ExternalDirecCall is interaction') + t.notOk(common.isInteraction(callcodeAst), 'callcode is not interaction') + t.notOk(common.isInteraction(delegatecallAst), 'callcode is not interaction') + t.notOk(common.isInteraction(nodeNot), 'local call is not interaction') +}) + +test('staticAnalysisCommon.isEffect', function (t) { + t.plan(5) + var inlineAssembly = { + 'children': [ + ], + 'id': 21, + 'name': 'InlineAssembly', + 'src': '809:41:0' + } + var assignment = { + 'attributes': { + 'operator': '=', + 'type': 'uint256' + }, + 'children': [ + { + 'attributes': { + 'type': 'uint256' + }, + 'children': [ + { + 'attributes': { + 'type': 'mapping(address => uint256)', + 'value': 'c' + }, + 'id': 61, + 'name': 'Identifier', + 'src': '873:1:0' + }, + { + 'attributes': { + 'member_name': 'sender', + 'type': 'address' + }, + 'children': [ + { + 'attributes': { + 'type': 'msg', + 'value': 'msg' + }, + 'id': 62, + 'name': 'Identifier', + 'src': '875:3:0' + } + ], + 'id': 63, + 'name': 'MemberAccess', + 'src': '875:10:0' + } + ], + 'id': 64, + 'name': 'IndexAccess', + 'src': '873:13:0' + }, + { + 'attributes': { + 'hexvalue': '30', + 'subdenomination': null, + 'token': null, + 'type': 'int_const 0', + 'value': '0' + }, + 'id': 65, + 'name': 'Literal', + 'src': '889:1:0' + } + ], + 'id': 66, + 'name': 'Assignment', + 'src': '873:17:0' + } + var unaryOp = { name: 'UnaryOperation', attributes: { operator: '++' } } + t.ok(common.isEffect(inlineAssembly), 'inline assembly is treated as effect') + t.ok(common.isEffect(assignment), 'assignment is treated as effect') + t.ok(common.isEffect(unaryOp), '++ is treated as effect') + unaryOp.attributes.operator = '--' + t.ok(common.isEffect(unaryOp), '-- is treated as effect') + t.notOk(common.isEffect({ name: 'MemberAccess', attributes: { operator: '++' } }), 'MemberAccess not treated as effect') +}) + +test('staticAnalysisCommon.isWriteOnStateVariable', function (t) { + t.plan(3) + var inlineAssembly = { + 'children': [ + ], + 'id': 21, + 'name': 'InlineAssembly', + 'src': '809:41:0' + } + var assignment = { + 'attributes': { + 'operator': '=', + 'type': 'uint256' + }, + 'children': [ + { + 'attributes': { + 'type': 'uint256' + }, + 'children': [ + { + 'attributes': { + 'type': 'mapping(address => uint256)', + 'value': 'c' + }, + 'id': 61, + 'name': 'Identifier', + 'src': '873:1:0' + }, + { + 'attributes': { + 'member_name': 'sender', + 'type': 'address' + }, + 'children': [ + { + 'attributes': { + 'type': 'msg', + 'value': 'msg' + }, + 'id': 62, + 'name': 'Identifier', + 'src': '875:3:0' + } + ], + 'id': 63, + 'name': 'MemberAccess', + 'src': '875:10:0' + } + ], + 'id': 64, + 'name': 'IndexAccess', + 'src': '873:13:0' + }, + { + 'attributes': { + 'hexvalue': '30', + 'subdenomination': null, + 'token': null, + 'type': 'int_const 0', + 'value': '0' + }, + 'id': 65, + 'name': 'Literal', + 'src': '889:1:0' + } + ], + 'id': 66, + 'name': 'Assignment', + 'src': '873:17:0' + } + var node1 = { + 'attributes': { + 'name': 'x', + 'type': 'struct Ballot.Voter storage pointer' + }, + 'children': [ + { + 'attributes': { + 'name': 'Voter' + }, + 'name': 'UserDefinedTypeName' + } + ], + 'name': 'VariableDeclaration' + } + var node2 = { + 'attributes': { + 'name': 'y', + 'type': 'uint' + }, + 'children': [ + { + 'attributes': { + 'name': 'Voter' + }, + 'name': 'UserDefinedTypeName' + } + ], + 'name': 'VariableDeclaration' + } + var node3 = { + 'attributes': { + 'name': 'xx', + 'type': 'uint' + }, + 'children': [ + { + 'attributes': { + 'name': 'Voter' + }, + 'name': 'UserDefinedTypeName' + } + ], + 'name': 'VariableDeclaration' + } + t.notOk(common.isWriteOnStateVariable(inlineAssembly, [node1, node2, node3]), 'inline Assembly is not write on state') + t.notOk(common.isWriteOnStateVariable(assignment, [node1, node2, node3]), 'assignment on non state is not write on state') + node3.attributes.name = 'c' + t.ok(common.isWriteOnStateVariable(assignment, [node1, node2, node3]), 'assignment on state is not write on state') +}) + +test('staticAnalysisCommon.isStateVariable', function (t) { + t.plan(3) + var node1 = { + 'attributes': { + 'name': 'x', + 'type': 'struct Ballot.Voter storage pointer' + }, + 'children': [ + { + 'attributes': { + 'name': 'Voter' + }, + 'name': 'UserDefinedTypeName' + } + ], + 'name': 'VariableDeclaration' + } + var node2 = { + 'attributes': { + 'name': 'y', + 'type': 'uint' + }, + 'children': [ + { + 'attributes': { + 'name': 'Voter' + }, + 'name': 'UserDefinedTypeName' + } + ], + 'name': 'VariableDeclaration' + } + var node3 = { + 'attributes': { + 'name': 'xx', + 'type': 'uint' + }, + 'children': [ + { + 'attributes': { + 'name': 'Voter' + }, + 'name': 'UserDefinedTypeName' + } + ], + 'name': 'VariableDeclaration' + } + + t.ok(common.isStateVariable('x', [node1, node2]), 'is contained') + t.ok(common.isStateVariable('x', [node2, node1, node1]), 'is contained twice') + t.notOk(common.isStateVariable('x', [node2, node3]), 'not contained') +}) + +test('staticAnalysisCommon.isConstantFunction', function (t) { + t.plan(3) + var node1 = { name: 'FunctionDefinition', attributes: { constant: true } } + var node2 = { name: 'FunctionDefinition', attributes: { constant: false } } + var node3 = { name: 'MemberAccess', attributes: { constant: true } } + + t.ok(common.isConstantFunction(node1), 'should be const func definition') + t.notOk(common.isConstantFunction(node2), 'should not be const func definition') + t.notOk(common.isConstantFunction(node3), 'wrong node should not be const func definition') +}) + +test('staticAnalysisCommon.isPlusPlusUnaryOperation', function (t) { + t.plan(3) + var node1 = { name: 'UnaryOperation', attributes: { operator: '++' } } + var node2 = { name: 'UnaryOperation', attributes: { operator: '--' } } + var node3 = { name: 'FunctionDefinition', attributes: { operator: '++' } } + + t.ok(common.isPlusPlusUnaryOperation(node1), 'should be unary ++') + t.notOk(common.isPlusPlusUnaryOperation(node2), 'should not be unary ++') + t.notOk(common.isPlusPlusUnaryOperation(node3), 'wrong node should not be unary ++') +}) + +test('staticAnalysisCommon.isMinusMinusUnaryOperation', function (t) { + t.plan(3) + var node1 = { name: 'UnaryOperation', attributes: { operator: '--' } } + var node2 = { name: 'UnaryOperation', attributes: { operator: '++' } } + var node3 = { name: 'FunctionDefinition', attributes: { operator: '--' } } + + t.ok(common.isMinusMinusUnaryOperation(node1), 'should be unary --') + t.notOk(common.isMinusMinusUnaryOperation(node2), 'should not be unary --') + t.notOk(common.isMinusMinusUnaryOperation(node3), 'wrong node should not be unary --') +}) + +test('staticAnalysisCommon.isFullyImplementedContract', function (t) { + t.plan(3) + var node1 = { name: 'ContractDefinition', attributes: { fullyImplemented: true } } + var node2 = { name: 'ContractDefinition', attributes: { fullyImplemented: false } } + var node3 = { name: 'FunctionDefinition', attributes: { operator: '--' } } + + t.ok(common.isFullyImplementedContract(node1), 'should be fully implemented contract') + t.notOk(common.isFullyImplementedContract(node2), 'should not be fully implemented contract') + t.notOk(common.isFullyImplementedContract(node3), 'wrong node should not be fully implemented contract') +}) + +test('staticAnalysisCommon.isCallToNonConstLocalFunction', function (t) { + t.plan(2) + var node1 = { + 'attributes': { + 'type': 'tuple()', + 'type_conversion': false + }, + 'children': [ + { + 'attributes': { + 'type': 'function (struct Ballot.Voter storage pointer)', + 'value': 'bli' + }, + 'id': 37, + 'name': 'Identifier', + 'src': '540:3:0' + }, + { + 'attributes': { + 'type': 'struct Ballot.Voter storage pointer', + 'value': 'x' + }, + 'id': 38, + 'name': 'Identifier', + 'src': '544:1:0' + } + ], + 'id': 39, + 'name': 'FunctionCall', + 'src': '540:6:0' + } + + t.ok(common.isCallToNonConstLocalFunction(node1), 'should be call to non const Local func') + node1.children[0].attributes.type = 'function (struct Ballot.Voter storage pointer) constant payable (uint256)' + t.notok(common.isCallToNonConstLocalFunction(node1), 'should no longer be call to non const Local func') +}) + +test('staticAnalysisCommon.isExternalDirectCall', function (t) { + t.plan(5) + var node = { + attributes: { + member_name: 'info', + type: 'function () payable external returns (uint256)' + }, + children: [ + { + attributes: { + type: 'contract InfoFeed', + value: 'f' + }, + id: 30, + name: 'Identifier', + src: '405:1:0' + } + ], + id: 32, + name: 'MemberAccess', + src: '405:6:0' + } + + var node2 = { name: 'MemberAccess', children: [{attributes: { value: 'this', type: 'contract test' }}], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } + t.notOk(common.isThisLocalCall(node), 'is this.local_method() used should not work') + t.notOk(common.isBlockTimestampAccess(node), 'is block.timestamp used should not work') + t.notOk(common.isNowAccess(node), 'is now used should not work') + t.ok(common.isExternalDirectCall(node), 'f.info() should be external direct call') + t.notOk(common.isExternalDirectCall(node2), 'local call is not an exernal call') +}) + +test('staticAnalysisCommon.isNowAccess', function (t) { + t.plan(3) + var node = { name: 'Identifier', attributes: { value: 'now', type: 'uint256' } } + t.notOk(common.isThisLocalCall(node), 'is this.local_method() used should not work') + t.notOk(common.isBlockTimestampAccess(node), 'is block.timestamp used should not work') + t.ok(common.isNowAccess(node), 'is now used should work') +}) + +test('staticAnalysisCommon.isBlockTimestampAccess', function (t) { + t.plan(3) + var node = { name: 'MemberAccess', children: [{attributes: { value: 'block', type: 'block' }}], attributes: { value: 'timestamp', type: 'uint256' } } + t.notOk(common.isThisLocalCall(node), 'is this.local_method() used should not work') + t.ok(common.isBlockTimestampAccess(node), 'is block.timestamp used should work') + t.notOk(common.isNowAccess(node), 'is now used should not work') +}) + +test('staticAnalysisCommon.isThisLocalCall', function (t) { + t.plan(3) + var node = { name: 'MemberAccess', children: [{attributes: { value: 'this', type: 'contract test' }}], attributes: { value: 'b', type: 'function (bytes32,address) returns (bool)' } } + t.ok(common.isThisLocalCall(node), 'is this.local_method() used should work') + t.notOk(common.isBlockTimestampAccess(node), 'is block.timestamp used should not work') + t.notOk(common.isNowAccess(node), 'is now used should not work') +}) + +test('staticAnalysisCommon.isLocalCall', function (t) { + t.plan(5) + var node1 = { + 'attributes': { + 'type': 'tuple()', + 'type_conversion': false + }, + 'children': [ + { + 'attributes': { + 'type': 'function (struct Ballot.Voter storage pointer)', + 'value': 'bli' + }, + 'id': 37, + 'name': 'Identifier', + 'src': '540:3:0' + }, + { + 'attributes': { + 'type': 'struct Ballot.Voter storage pointer', + 'value': 'x' + }, + 'id': 38, + 'name': 'Identifier', + 'src': '544:1:0' + } + ], + 'id': 39, + 'name': 'FunctionCall', + 'src': '540:6:0' + } + + t.ok(common.isLocalCall(node1), 'isLocalCall') + t.notOk(common.isLowLevelCall(node1), 'is not low level call') + t.notOk(common.isExternalDirectCall(node1), 'is not external direct call') + t.notOk(common.isEffect(node1), 'is not effect') + t.notOk(common.isInteraction(node1), 'is not interaction') +}) + +test('staticAnalysisCommon.isLowLevelCall', function (t) { + t.plan(6) + var sendAst = { name: 'MemberAccess', children: [{attributes: { value: 'd', type: 'address' }}], attributes: { value: 'send', type: 'function (uint256) returns (bool)' } } + var callAst = { name: 'MemberAccess', children: [{attributes: { value: 'f', type: 'address' }}], attributes: { member_name: 'call', type: 'function () payable returns (bool)' } } + var callcodeAst = { name: 'MemberAccess', children: [{attributes: { value: 'f', type: 'address' }}], attributes: { member_name: 'callcode', type: 'function () payable returns (bool)' } } + var delegatecallAst = { name: 'MemberAccess', children: [{attributes: { value: 'g', type: 'address' }}], attributes: { member_name: 'delegatecall', type: 'function () returns (bool)' } } + + t.ok(common.isLowLevelSendInst(sendAst) && common.isLowLevelCall(sendAst), 'send is llc should work') + t.ok(common.isLowLevelCallInst(callAst) && common.isLowLevelCall(callAst), 'call is llc should work') + t.notOk(common.isLowLevelCallInst(callcodeAst), 'callcode is not call') + t.ok(common.isLowLevelCallcodeInst(callcodeAst) && common.isLowLevelCall(callcodeAst), 'callcode is llc should work') + t.notOk(common.isLowLevelCallcodeInst(callAst), 'call is not callcode') + t.ok(common.isLowLevelDelegatecallInst(delegatecallAst) && common.isLowLevelCall(delegatecallAst), 'delegatecall is llc should work') })