Skip to content

Commit 64f205a

Browse files
committed
Add coverage for ternary conditionals (#587)
1 parent 02cea3d commit 64f205a

21 files changed

+530
-332
lines changed

lib/coverage.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ class Coverage {
7979
case 'function': this.data[contractPath].f[id] = hits; break;
8080
case 'statement': this.data[contractPath].s[id] = hits; break;
8181
case 'branch': this.data[contractPath].b[id][data.locationIdx] = hits; break;
82-
case 'logicalOR': this.data[contractPath].b[id][data.locationIdx] = hits / 2; break;
82+
case 'and-true': this.data[contractPath].b[id][data.locationIdx] = hits / 2; break;
83+
case 'or-false': this.data[contractPath].b[id][data.locationIdx] = hits / 2; break;
8384
case 'requirePre': this.requireData[contractPath][id].preEvents = hits; break;
8485
case 'requirePost': this.requireData[contractPath][id].postEvents = hits; break;
8586
}

lib/injector.js

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ class Injector {
1414

1515
_getInjectable(id, hash, type){
1616
switch(type){
17-
case 'logicalOR':
17+
case 'and-true':
1818
return ` && ${this._getTrueMethodIdentifier(id)}(${hash}))`;
19+
case 'or-false':
20+
return ` || ${this._getFalseMethodIdentifier(id)}(${hash}))`;
1921
default:
2022
return `${this._getDefaultMethodIdentifier(id)}(${hash}); /* ${type} */ \n`;
2123
}
@@ -31,11 +33,16 @@ class Injector {
3133
return `c_${web3Utils.keccak256(id).slice(0,10)}`
3234
}
3335

34-
// Method returns boolean true
36+
// Method returns boolean: true
3537
_getTrueMethodIdentifier(id){
3638
return `c_true${web3Utils.keccak256(id).slice(0,10)}`
3739
}
3840

41+
// Method returns boolean: false
42+
_getFalseMethodIdentifier(id){
43+
return `c_false${web3Utils.keccak256(id).slice(0,10)}`
44+
}
45+
3946
_getInjectionComponents(contract, injectionPoint, id, type){
4047
const { start, end } = this._split(contract, injectionPoint);
4148
const hash = this._getHash(id)
@@ -82,7 +89,19 @@ class Injector {
8289
_getTrueMethodDefinition(id){
8390
const hash = web3Utils.keccak256(id).slice(0,10);
8491
const method = this._getTrueMethodIdentifier(id);
85-
return `\nfunction ${method}(bytes32 c__${hash}) public pure returns (bool){ return true; }\n`;
92+
return `function ${method}(bytes32 c__${hash}) public pure returns (bool){ return true; }\n`;
93+
}
94+
95+
/**
96+
* Generates a solidity statement injection defining a method
97+
* *which returns boolean false* to pass instrumentation hash to.
98+
* @param {String} fileName
99+
* @return {String} ex: bytes32[1] memory _sc_82e0891
100+
*/
101+
_getFalseMethodDefinition(id){
102+
const hash = web3Utils.keccak256(id).slice(0,10);
103+
const method = this._getFalseMethodIdentifier(id);
104+
return `function ${method}(bytes32 c__${hash}) public pure returns (bool){ return false; }\n`;
86105
}
87106

88107
injectLine(contract, fileName, injectionPoint, injection, instrumentation){
@@ -247,6 +266,7 @@ class Injector {
247266
contract.instrumented = `${start}` +
248267
`${defaultMethodDefinition}` +
249268
`${this._getTrueMethodDefinition(id)}` +
269+
`${this._getFalseMethodDefinition(id)}` +
250270
`${end}`;
251271
}
252272

@@ -256,8 +276,30 @@ class Injector {
256276
contract.instrumented = `${start}(${end}`;
257277
}
258278

259-
injectLogicalOR(contract, fileName, injectionPoint, injection, instrumentation){
260-
const type = 'logicalOR';
279+
injectAndTrue(contract, fileName, injectionPoint, injection, instrumentation){
280+
const type = 'and-true';
281+
const id = `${fileName}:${injection.contractName}`;
282+
283+
const {
284+
start,
285+
end,
286+
hash,
287+
injectable
288+
} = this._getInjectionComponents(contract, injectionPoint, id, type);
289+
290+
instrumentation[hash] = {
291+
id: injection.branchId,
292+
locationIdx: injection.locationIdx,
293+
type: type,
294+
contractPath: fileName,
295+
hits: 0
296+
}
297+
298+
contract.instrumented = `${start}${injectable}${end}`;
299+
}
300+
301+
injectOrFalse(contract, fileName, injectionPoint, injection, instrumentation){
302+
const type = 'or-false';
261303
const id = `${fileName}:${injection.contractName}`;
262304

263305
const {

lib/parse.js

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,21 @@ parse.Block = function(contract, expression) {
3333
};
3434

3535
parse.BinaryOperation = function(contract, expression, skipStatementRegistry) {
36+
// Free-floating ternary conditional
37+
if (expression.left && expression.left.type === 'Conditional'){
38+
parse[expression.left.type](contract, expression.left, true);
39+
register.statement(contract, expression);
40+
41+
// Ternary conditional assignment
42+
} else if (expression.right && expression.right.type === 'Conditional'){
43+
parse[expression.right.type](contract, expression.right, true);
44+
register.statement(contract, expression);
45+
3646
// Regular binary operation
37-
if (!skipStatementRegistry){
47+
} else if(!skipStatementRegistry){
3848
register.statement(contract, expression);
3949

40-
// LogicalOR conditional search...
50+
// LogicalOR condition search...
4151
} else {
4252
parse[expression.left.type] &&
4353
parse[expression.left.type](contract, expression.left, true);
@@ -83,12 +93,10 @@ parse.FunctionCall = function(contract, expression, skipStatementRegistry) {
8393
};
8494

8595
parse.Conditional = function(contract, expression, skipStatementRegistry) {
86-
if (!skipStatementRegistry){
87-
register.statement(contract, expression);
88-
}
89-
9096
parse[expression.condition.type] &&
9197
parse[expression.condition.type](contract, expression.condition, skipStatementRegistry);
98+
99+
register.conditional(contract, expression);
92100
};
93101

94102
parse.ContractDefinition = function(contract, expression) {
@@ -291,6 +299,9 @@ parse.UsingStatement = function (contract, expression) {
291299
};
292300

293301
parse.VariableDeclarationStatement = function (contract, expression) {
302+
if (expression.initialValue && expression.initialValue.type === 'Conditional'){
303+
parse[expression.initialValue.type](contract, expression.initialValue, true)
304+
}
294305
register.statement(contract, expression);
295306
};
296307

lib/registrar.js

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,54 @@ class Registrar {
186186
};
187187
};
188188

189+
addNewConditionalBranch(contract, expression){
190+
let start;
191+
// Instabul HTML highlighting location data...
192+
const trueZero = expression.trueExpression.range[0];
193+
const trueOne = expression.trueExpression.range[1];
194+
const falseZero = expression.falseExpression.range[0];
195+
const falseOne = expression.falseExpression.range[1];
196+
197+
start = contract.instrumented.slice(0, trueZero);
198+
const trueStartLine = ( start.match(/\n/g) || [] ).length + 1;
199+
const trueStartCol = trueZero - start.lastIndexOf('\n') - 1;
200+
201+
start = contract.instrumented.slice(0, trueOne);
202+
const trueEndLine = ( start.match(/\n/g) || [] ).length + 1;
203+
const trueEndCol = trueOne - start.lastIndexOf('\n') - 1;
204+
205+
start = contract.instrumented.slice(0, falseZero);
206+
const falseStartLine = ( start.match(/\n/g) || [] ).length + 1;
207+
const falseStartCol = falseZero - start.lastIndexOf('\n') - 1;
208+
209+
start = contract.instrumented.slice(0, falseOne);
210+
const falseEndLine = ( start.match(/\n/g) || [] ).length + 1;
211+
const falseEndCol = falseOne - start.lastIndexOf('\n') - 1;
212+
213+
contract.branchId += 1;
214+
215+
contract.branchMap[contract.branchId] = {
216+
line: trueStartLine,
217+
type: 'if',
218+
locations: [{
219+
start: {
220+
line: trueStartLine, column: trueStartCol,
221+
},
222+
end: {
223+
line: trueEndLine, column: trueEndCol,
224+
},
225+
}, {
226+
start: {
227+
line: falseStartLine, column: falseStartCol,
228+
},
229+
end: {
230+
line: falseEndLine, column: falseEndCol,
231+
},
232+
}],
233+
};
234+
235+
}
236+
189237
/**
190238
* Registers injections for branch measurements. Used by logicalOR registration method.
191239
* @param {Object} contract instrumentation target
@@ -241,6 +289,46 @@ class Registrar {
241289
};
242290
};
243291

292+
conditional(contract, expression){
293+
this.addNewConditionalBranch(contract, expression);
294+
295+
// Double open parens
296+
this._createInjectionPoint(
297+
contract,
298+
expression.condition.range[0],
299+
{
300+
type: 'injectOpenParen',
301+
}
302+
);
303+
this._createInjectionPoint(
304+
contract,
305+
expression.condition.range[0],
306+
{
307+
type: 'injectOpenParen',
308+
}
309+
);
310+
// False condition: (these get sorted in reverse order, so create in reversed order)
311+
this._createInjectionPoint(
312+
contract,
313+
expression.condition.range[1] + 1,
314+
{
315+
type: 'injectOrFalse',
316+
branchId: contract.branchId,
317+
locationIdx: 1
318+
}
319+
);
320+
// True condition
321+
this._createInjectionPoint(
322+
contract,
323+
expression.condition.range[1] + 1,
324+
{
325+
type: 'injectAndTrue',
326+
branchId: contract.branchId,
327+
locationIdx: 0
328+
}
329+
);
330+
}
331+
244332
/**
245333
* Registers injections for logicalOR clause measurements (branches)
246334
* @param {Object} contract instrumentation target
@@ -262,7 +350,7 @@ class Registrar {
262350
contract,
263351
expression.left.range[1] + 1,
264352
{
265-
type: 'injectLogicalOR',
353+
type: 'injectAndTrue',
266354
branchId: contract.branchId,
267355
locationIdx: 0
268356
}
@@ -280,7 +368,7 @@ class Registrar {
280368
contract,
281369
expression.right.range[1] + 1,
282370
{
283-
type: 'injectLogicalOR',
371+
type: 'injectAndTrue',
284372
branchId: contract.branchId,
285373
locationIdx: 1
286374
}

0 commit comments

Comments
 (0)