Skip to content

Commit 8ec244c

Browse files
committed
Add coverage for ternary conditionals
1 parent 13cb0d3 commit 8ec244c

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)
@@ -70,7 +77,19 @@ class Injector {
7077
_getTrueMethodDefinition(id){
7178
const hash = web3Utils.keccak256(id).slice(0,10);
7279
const method = this._getTrueMethodIdentifier(id);
73-
return `\nfunction ${method}(bytes32 c__${hash}) public pure returns (bool){ return true; }\n`;
80+
return `function ${method}(bytes32 c__${hash}) public pure returns (bool){ return true; }\n`;
81+
}
82+
83+
/**
84+
* Generates a solidity statement injection defining a method
85+
* *which returns boolean false* to pass instrumentation hash to.
86+
* @param {String} fileName
87+
* @return {String} ex: bytes32[1] memory _sc_82e0891
88+
*/
89+
_getFalseMethodDefinition(id){
90+
const hash = web3Utils.keccak256(id).slice(0,10);
91+
const method = this._getFalseMethodIdentifier(id);
92+
return `function ${method}(bytes32 c__${hash}) public pure returns (bool){ return false; }\n`;
7493
}
7594

7695
injectLine(contract, fileName, injectionPoint, injection, instrumentation){
@@ -230,6 +249,7 @@ class Injector {
230249
contract.instrumented = `${start}` +
231250
`${this._getDefaultMethodDefinition(id)}` +
232251
`${this._getTrueMethodDefinition(id)}` +
252+
`${this._getFalseMethodDefinition(id)}` +
233253
`${end}`;
234254
}
235255

@@ -239,8 +259,30 @@ class Injector {
239259
contract.instrumented = `${start}(${end}`;
240260
}
241261

242-
injectLogicalOR(contract, fileName, injectionPoint, injection, instrumentation){
243-
const type = 'logicalOR';
262+
injectAndTrue(contract, fileName, injectionPoint, injection, instrumentation){
263+
const type = 'and-true';
264+
const id = `${fileName}:${injection.contractName}`;
265+
266+
const {
267+
start,
268+
end,
269+
hash,
270+
injectable
271+
} = this._getInjectionComponents(contract, injectionPoint, id, type);
272+
273+
instrumentation[hash] = {
274+
id: injection.branchId,
275+
locationIdx: injection.locationIdx,
276+
type: type,
277+
contractPath: fileName,
278+
hits: 0
279+
}
280+
281+
contract.instrumented = `${start}${injectable}${end}`;
282+
}
283+
284+
injectOrFalse(contract, fileName, injectionPoint, injection, instrumentation){
285+
const type = 'or-false';
244286
const id = `${fileName}:${injection.contractName}`;
245287

246288
const {

lib/parse.js

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

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

38-
// LogicalOR conditional search...
48+
// LogicalOR condition search...
3949
} else {
4050
parse[expression.left.type] &&
4151
parse[expression.left.type](contract, expression.left, true);
@@ -81,12 +91,10 @@ parse.FunctionCall = function(contract, expression, skipStatementRegistry) {
8191
};
8292

8393
parse.Conditional = function(contract, expression, skipStatementRegistry) {
84-
if (!skipStatementRegistry){
85-
register.statement(contract, expression);
86-
}
87-
8894
parse[expression.condition.type] &&
8995
parse[expression.condition.type](contract, expression.condition, skipStatementRegistry);
96+
97+
register.conditional(contract, expression);
9098
};
9199

92100
parse.ContractDefinition = function(contract, expression) {
@@ -239,6 +247,9 @@ parse.UsingStatement = function (contract, expression) {
239247
};
240248

241249
parse.VariableDeclarationStatement = function (contract, expression) {
250+
if (expression.initialValue && expression.initialValue.type === 'Conditional'){
251+
parse[expression.initialValue.type](contract, expression.initialValue, true)
252+
}
242253
register.statement(contract, expression);
243254
};
244255

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)