Skip to content

Commit 4e11296

Browse files
authored
Support Branch Coverage for if (#540)
2 parents ae99249 + 53641ef commit 4e11296

File tree

10 files changed

+149
-37
lines changed

10 files changed

+149
-37
lines changed

src/dataflow/common/environments/environment.ts

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -60,23 +60,26 @@ export function diffIdentifierReferences(a: IdentifierReference, b: IdentifierRe
6060
}
6161
}
6262

63+
64+
function makeReferenceMaybe(ref: IdentifierReference, graph: DataflowGraph, environments: REnvironmentInformation): IdentifierReference {
65+
const node = graph.get(ref.nodeId, true)
66+
const definitions = resolveByName(ref.name, LocalScope, environments)
67+
for(const definition of definitions ?? []) {
68+
if(definition.kind !== 'built-in-function') {
69+
definition.used = 'maybe'
70+
}
71+
}
72+
if(node) {
73+
node[0].when = 'maybe'
74+
}
75+
return { ...ref, used: 'maybe'}
76+
}
77+
6378
export function makeAllMaybe(references: IdentifierReference[] | undefined, graph: DataflowGraph, environments: REnvironmentInformation): IdentifierReference[] {
6479
if(references === undefined) {
6580
return []
6681
}
67-
return references.map(ref => {
68-
const node = graph.get(ref.nodeId, true)
69-
const definitions = resolveByName(ref.name, LocalScope, environments)
70-
for(const definition of definitions ?? []) {
71-
if(definition.kind !== 'built-in-function') {
72-
definition.used = 'maybe'
73-
}
74-
}
75-
if(node) {
76-
node[0].when = 'maybe'
77-
}
78-
return { ...ref, used: 'maybe'}
79-
})
82+
return references.map(ref => makeReferenceMaybe(ref, graph, environments))
8083
}
8184

8285

@@ -180,26 +183,30 @@ export function diffEnvironment(a: IEnvironment | undefined, b: IEnvironment | u
180183
continue
181184
}
182185

186+
// we sort both value arrays by their id so that we have no problems with differently ordered arrays (which have no impact)
187+
const sorted = [...value].sort((a, b) => a.nodeId.localeCompare(b.nodeId))
188+
const sorted2 = [...value2].sort((a, b) => a.nodeId.localeCompare(b.nodeId))
189+
183190
for(let i = 0; i < value.length; ++i) {
184-
const aVal = value[i]
185-
const bVal = value2[i]
191+
const aVal = sorted[i]
192+
const bVal = sorted2[i]
186193
if(aVal.name !== bVal.name) {
187194
info.report.addComment(`${info.position}Different names for ${key}. ${info.leftname}: ${aVal.name} vs. ${info.rightname}: ${bVal.name}`)
188195
}
189196
if(aVal.nodeId !== bVal.nodeId) {
190197
info.report.addComment(`${info.position}Different ids for ${key}. ${info.leftname}: ${aVal.nodeId} vs. ${info.rightname}: ${bVal.nodeId}`)
191198
}
192199
if(aVal.scope !== bVal.scope) {
193-
info.report.addComment(`${info.position}Different scopes for ${key}. ${info.leftname}: ${aVal.scope} vs. ${info.rightname}: ${bVal.scope}`)
200+
info.report.addComment(`${info.position}Different scopes for ${key} (${aVal.nodeId}). ${info.leftname}: ${aVal.scope} vs. ${info.rightname}: ${bVal.scope}`)
194201
}
195202
if(aVal.used !== bVal.used) {
196-
info.report.addComment(`${info.position}Different used for ${key}. ${info.leftname}: ${aVal.used} vs. ${info.rightname}: ${bVal.used}`)
203+
info.report.addComment(`${info.position}Different used for ${key} (${aVal.nodeId}). ${info.leftname}: ${aVal.used} vs. ${info.rightname}: ${bVal.used}`)
197204
}
198205
if(aVal.definedAt !== bVal.definedAt) {
199-
info.report.addComment(`${info.position}Different definition ids (definedAt) for ${key}. ${info.leftname}: ${aVal.definedAt} vs. ${info.rightname}: ${bVal.definedAt}`)
206+
info.report.addComment(`${info.position}Different definition ids (definedAt) for ${key} (${aVal.nodeId}). ${info.leftname}: ${aVal.definedAt} vs. ${info.rightname}: ${bVal.definedAt}`)
200207
}
201208
if(aVal.kind !== bVal.kind) {
202-
info.report.addComment(`${info.position}Different kinds for ${key}. ${info.leftname}: ${aVal.kind} vs. ${info.rightname}: ${bVal.kind}`)
209+
info.report.addComment(`${info.position}Different kinds for ${key} (${aVal.nodeId}). ${info.leftname}: ${aVal.kind} vs. ${info.rightname}: ${bVal.kind}`)
203210
}
204211
}
205212
}

src/dataflow/common/environments/register.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@ import { guard } from '../../../util/assert'
22
import { cloneEnvironments, IdentifierDefinition, IEnvironment, REnvironmentInformation } from './environment'
33
import { DataflowScopeName, GlobalScope, LocalScope } from './scopes'
44

5+
function defInEnv(newEnvironments: IEnvironment, definition: IdentifierDefinition) {
6+
const existing = newEnvironments.memory.get(definition.name)
7+
// check if it is maybe or not
8+
if(existing === undefined || definition.used === 'always') {
9+
newEnvironments.memory.set(definition.name, [definition])
10+
} else {
11+
existing.push(definition)
12+
}
13+
}
14+
515
/**
616
* Insert the given `definition` --- defined within the given scope --- into the passed along `environments` will take care of propagation.
717
* Does not modify the passed along `environments` in-place! It returns the new reference.
@@ -10,7 +20,7 @@ export function define(definition: IdentifierDefinition, withinScope: DataflowSc
1020
let newEnvironments = environments
1121
if(withinScope === LocalScope) {
1222
newEnvironments = cloneEnvironments(environments, false)
13-
newEnvironments.current.memory.set(definition.name, [definition])
23+
defInEnv(newEnvironments.current, definition)
1424
} else if(withinScope === GlobalScope) {
1525
newEnvironments = cloneEnvironments(environments, true)
1626
let current: IEnvironment | undefined = newEnvironments.current

src/dataflow/v1/internal/process/access.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { ParentInformation, RAccess } from '../../../../r-bridge'
22
import { DataflowInformation } from '../info'
33
import { DataflowProcessorInformation, processDataflowFor } from '../../processor'
4-
import { makeAllMaybe, overwriteEnvironments } from '../../../common/environments'
4+
import { makeAllMaybe } from '../../../common/environments'
55
import { EdgeType } from '../../graph'
66

77
export function processAccess<OtherInfo>(node: RAccess<OtherInfo & ParentInformation>, data: DataflowProcessorInformation<OtherInfo & ParentInformation>): DataflowInformation {
88
const processedAccessed = processDataflowFor(node.accessed, data)
99
const nextGraph = processedAccessed.graph
1010
const outgoing = processedAccessed.out
1111
const ingoing = processedAccessed.in
12-
const environments = processedAccessed.environments
12+
let environments = processedAccessed.environments
1313

1414
const accessedNodes = processedAccessed.unknownReferences
1515

@@ -18,6 +18,7 @@ export function processAccess<OtherInfo>(node: RAccess<OtherInfo & ParentInforma
1818
if(access === null || access.value === undefined) {
1919
continue
2020
}
21+
data = { ...data, environments }
2122
const processedAccess = processDataflowFor(access, data)
2223

2324
nextGraph.mergeWith(processedAccess.graph)
@@ -29,7 +30,7 @@ export function processAccess<OtherInfo>(node: RAccess<OtherInfo & ParentInforma
2930
}
3031
}
3132
ingoing.push(...processedAccess.in, ...processedAccess.unknownReferences)
32-
overwriteEnvironments(environments, processedAccess.environments)
33+
environments = processedAccess.environments
3334
}
3435
}
3536

src/dataflow/v1/internal/process/expression-list.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ function updateSideEffectsForCalledFunctions(calledEnvs: {
9696
}
9797
current = current.parent
9898
}
99-
// we update all definitions to be linked with teh corresponding function call
99+
// we update all definitions to be linked with the corresponding function call
100100
environments = overwriteEnvironments(environments, environment)
101101
}
102102
}
@@ -129,6 +129,7 @@ export function processExpressionList<OtherInfo>(exprList: RExpressionList<Other
129129
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- seems to be a bug in eslint
130130
if(!foundNextOrBreak) {
131131
visitAst(expression, n => {
132+
// we should track returns more consistently
132133
if(n.type === RType.Next || n.type === RType.Break) {
133134
foundNextOrBreak = true
134135
}
@@ -155,7 +156,11 @@ export function processExpressionList<OtherInfo>(exprList: RExpressionList<Other
155156

156157
const calledEnvs = linkFunctionCalls(nextGraph, data.completeAst.idMap, functionCallIds, processed.graph)
157158

158-
environments = overwriteEnvironments(environments, processed.environments)
159+
if(foundNextOrBreak) {
160+
environments = overwriteEnvironments(environments, processed.environments)
161+
} else {
162+
environments = processed.environments
163+
}
159164

160165
// if the called function has global redefinitions, we have to keep them within our environment
161166
environments = updateSideEffectsForCalledFunctions(calledEnvs, environments, nextGraph)

src/dataflow/v1/internal/process/if-then-else.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ export function processIfThenElse<OtherInfo>(ifThen: RIfThenElse<OtherInfo & Par
2929

3030
const nextGraph = cond.graph.mergeWith(then?.graph).mergeWith(otherwise?.graph)
3131

32-
const thenEnvironment = appendEnvironments(cond.environments, then?.environments)
33-
const finalEnvironment = otherwise ? appendEnvironments(thenEnvironment, otherwise.environments) : thenEnvironment
32+
const thenEnvironment = then?.environments ?? cond.environments
33+
// if there is no "else" case we have to recover whatever we had before as it may be not executed
34+
const finalEnvironment = appendEnvironments(thenEnvironment, otherwise ? otherwise.environments : cond.environments)
3435

3536
// again within an if-then-else we consider all actives to be read
3637
const ingoing: IdentifierReference[] = [

src/dataflow/v1/internal/process/operators/assignment.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export function processAssignment<OtherInfo>(op: RAssignmentOp<OtherInfo & Paren
3737
}
3838
}
3939
}
40+
4041
return {
4142
unknownReferences: [],
4243
in: readTargets,
@@ -124,7 +125,6 @@ function processReadAndWriteForAssignmentBasedOnOp<OtherInfo>(
124125
for(const write of writeNodes) {
125126
environments = define(write, global ? GlobalScope: LocalScope, environments)
126127
}
127-
128128
return {
129129
readTargets: [...source.unknownReferences, ...read, ...readFromSourceWritten],
130130
writeTargets: [...writeNodes, ...target.out, ...readFromSourceWritten],

src/util/quads.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,6 @@ function serializeObject(obj: DataForQuad | undefined | null, quads: Quad[], con
277277
} else if(obj instanceof Set) {
278278
let i = 0
279279
for(const value of obj.values()) {
280-
console.log('set', value)
281280
processObjectEntry('idx-'+String(i++), value, obj, quads, config)
282281
}
283282
} else {

test/functionality/dataflow/processing-of-elements/expression-lists/if-then-tests.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,91 @@ describe('Lists with if-then constructs', withShell(shell => {
8383
})
8484
})
8585
}
86+
describe('Branch Coverage', () => {
87+
//All test related to branch coverage (testing the interaction between then end else block)
88+
const envWithX = () => define({ nodeId: '0', name: 'x', scope: LocalScope, kind: 'variable', definedAt: '2', used: 'always' }, LocalScope, initializeCleanEnvironments())
89+
const envThenBranch = () => define({nodeId: '4', scope: LocalScope, name: 'x', used: 'maybe', kind: 'variable',definedAt: '6'}, LocalScope, initializeCleanEnvironments())
90+
assertDataflow('assignment both branches in if',
91+
shell,
92+
'x <- 1\nif(r) { x <- 2 } else { x <- 3}\n y <- x',
93+
new DataflowGraph()
94+
.addVertex( { tag: 'variable-definition', id: '0', name: 'x', scope: LocalScope})
95+
.addVertex( { tag: 'use', id: '3', name: 'r', scope: LocalScope,environment: envWithX()} )
96+
.addVertex( { tag: 'variable-definition', id: '4', name: 'x', scope: LocalScope, environment: envWithX(), when: 'maybe' } )
97+
.addVertex( { tag: 'variable-definition', id: '8', name: 'x', scope: LocalScope, environment: envWithX(), when: 'maybe' } )
98+
.addVertex( { tag: 'use', id: '14', name: 'x', scope: LocalScope, environment: define({ nodeId: '8',scope: LocalScope, name: 'x', used: 'maybe',kind: 'variable',definedAt: '10'}, LocalScope, envThenBranch())})
99+
.addVertex( { tag: 'variable-definition', id: '13', name: 'y', scope: LocalScope, environment: define({ nodeId: '8',scope: LocalScope, name: 'x', used: 'maybe',kind: 'variable',definedAt: '10'}, LocalScope, envThenBranch())})
100+
.addEdge('0', '8', EdgeType.SameDefDef, 'maybe')
101+
.addEdge('0', '4', EdgeType.SameDefDef, 'maybe')
102+
.addEdge('14', '4', EdgeType.Reads, 'maybe')
103+
.addEdge('14', '8', EdgeType.Reads, 'maybe')
104+
.addEdge('13', '14', EdgeType.DefinedBy, 'always')
105+
)
106+
const envWithXMaybe = () => define({ nodeId: '0', name: 'x', scope: LocalScope, kind: 'variable', definedAt: '2', used: 'maybe' }, LocalScope, initializeCleanEnvironments())
107+
const envWithSecondXMaybe = () => define({ nodeId: '4', name: 'x', scope: LocalScope, kind: 'variable', definedAt: '6', used: 'maybe' }, LocalScope, initializeCleanEnvironments())
108+
assertDataflow('assignment if one branch',
109+
shell,
110+
'x <- 1\nif(r) { x <- 2 } \n y <- x',
111+
new DataflowGraph()
112+
.addVertex( { tag: 'variable-definition', id: '0', name: 'x', scope: LocalScope})
113+
.addVertex( { tag: 'use', id: '3', name: 'r', scope: LocalScope, environment: envWithX()} )
114+
.addVertex( { tag: 'variable-definition', id: '4', name: 'x', scope: LocalScope, environment: envWithX(), when: 'maybe' } )
115+
.addVertex( { tag: 'use', id: '10', name: 'x', scope: LocalScope, environment: appendEnvironments(envWithSecondXMaybe(), envWithXMaybe()) })
116+
.addVertex( { tag: 'variable-definition', id: '9', name: 'y', scope: LocalScope, environment: appendEnvironments(envWithSecondXMaybe(), envWithXMaybe()) })
117+
.addEdge('0', '4', EdgeType.SameDefDef, 'maybe')
118+
.addEdge('10', '0', EdgeType.Reads, 'maybe')
119+
.addEdge('10', '4', EdgeType.Reads, 'maybe')
120+
.addEdge('9', '10', EdgeType.DefinedBy, 'always')
121+
)
122+
const envWithY = () => define({ nodeId: '3', name: 'y', scope: LocalScope, kind: 'variable', definedAt: '5', used: 'always' }, LocalScope, initializeCleanEnvironments())
123+
const envWithXThen = () => define({ nodeId: '7', name: 'x', scope: LocalScope, kind: 'variable', definedAt: '9', used: 'always' }, LocalScope, initializeCleanEnvironments())
124+
const envWithXThenMaybe = () => define({ nodeId: '7', name: 'x', scope: LocalScope, kind: 'variable', definedAt: '9', used: 'maybe' }, LocalScope, initializeCleanEnvironments())
125+
const envWithXElseMaybe = () => define({ nodeId: '14', name: 'x', scope: LocalScope, kind: 'variable', definedAt: '16', used: 'maybe' }, LocalScope, initializeCleanEnvironments())
126+
const envWithYMaybeBeforeIf = () => define({ nodeId: '3', name: 'y', scope: LocalScope, kind: 'variable', definedAt: '5', used: 'maybe' }, LocalScope, initializeCleanEnvironments())
127+
const envWithYMaybeInThen = () => define({ nodeId: '10', name: 'y', scope: LocalScope, kind: 'variable', definedAt: '12', used: 'maybe'}, LocalScope, initializeCleanEnvironments())
128+
const envForYAfterIf = () => appendEnvironments(envWithYMaybeBeforeIf(), envWithYMaybeInThen())
129+
const envForXAfterIf = () => appendEnvironments(envWithXThenMaybe(), envWithXElseMaybe())
130+
const envDirectlyAfterIf = () => appendEnvironments(envForYAfterIf(), envForXAfterIf())
131+
const envWithW = () => define({ nodeId: '19', name: 'w', scope: LocalScope, kind: 'variable', definedAt: '21', used: 'always' }, LocalScope, initializeCleanEnvironments())
132+
//const envFirstYReassignment = () => appendEnvironments(envWithXThen(), envWithY())
133+
assertDataflow('assignment if multiple variables with else',
134+
shell,
135+
'x <- 1 \n y <- 2 \n if(r){ x <- 3 \n y <- 4} else {x <- 5} \n w <- x \n z <- y',
136+
new DataflowGraph()
137+
.addVertex({ tag: 'variable-definition', id: '0', name: 'x', scope: LocalScope})
138+
.addVertex({ tag: 'variable-definition', id: '3', name: 'y', scope: LocalScope, environment: envWithX()})
139+
.addVertex({ tag: 'use', id: '6', name: 'r', scope: LocalScope, environment: appendEnvironments(envWithX(), envWithY())})
140+
.addVertex({ tag: 'variable-definition', id: '7', name: 'x', scope: LocalScope, environment: appendEnvironments(envWithX(),envWithY()), when: 'maybe' })
141+
.addVertex({ tag: 'variable-definition', id: '10', name: 'y', scope: LocalScope, environment: appendEnvironments(envWithXThen(), envWithY()), when: 'maybe'})
142+
.addVertex({ tag: 'variable-definition', id: '14', name: 'x', scope: LocalScope, environment: appendEnvironments(envWithX(),envWithY()), when: 'maybe' })
143+
.addVertex({ tag: 'use', id: '20', name: 'x', scope: LocalScope, environment: envDirectlyAfterIf()})
144+
.addVertex({ tag: 'use', id: '23', name: 'y', scope: LocalScope, environment: appendEnvironments(envDirectlyAfterIf(), envWithW())})
145+
.addVertex({ tag: 'variable-definition', id: '19', name: 'w', scope: LocalScope, environment: envDirectlyAfterIf()})
146+
.addVertex({ tag: 'variable-definition', id: '22', name: 'z', scope: LocalScope, environment: appendEnvironments(envDirectlyAfterIf(), envWithW())})
147+
.addEdge('20', '7', EdgeType.Reads, 'maybe')
148+
.addEdge('20', '14', EdgeType.Reads, 'maybe')
149+
.addEdge('23', '3', EdgeType.Reads, 'maybe')
150+
.addEdge('23', '10', EdgeType.Reads, 'maybe')
151+
.addEdge('19', '20', EdgeType.DefinedBy, 'always')
152+
.addEdge('22', '23', EdgeType.DefinedBy, 'always')
153+
.addEdge('7', '0', EdgeType.SameDefDef, 'maybe')
154+
.addEdge('14', '0', EdgeType.SameDefDef, 'maybe')
155+
.addEdge('10', '3', EdgeType.SameDefDef, 'maybe')
156+
)
157+
const envWithElseXMaybe = () => define({ nodeId: '5', name: 'x', scope: LocalScope, kind: 'variable', definedAt: '7', used: 'maybe' }, LocalScope, initializeCleanEnvironments())
158+
assertDataflow('assignment in else block',
159+
shell,
160+
'x <- 1 \n if(r){} else{x <- 2} \n y <- x',
161+
new DataflowGraph()
162+
.addVertex( { tag: 'variable-definition', id: '0', name: 'x', scope: LocalScope})
163+
.addVertex( { tag: 'use', id: '3', name: 'r', scope: LocalScope, environment: envWithX()} )
164+
.addVertex( { tag: 'variable-definition', id: '5', name: 'x', scope: LocalScope, environment: envWithX(), when: 'maybe' } )
165+
.addVertex( { tag: 'use', id: '11', name: 'x', scope: LocalScope, environment: appendEnvironments(envWithElseXMaybe(), envWithXMaybe()) })
166+
.addVertex( { tag: 'variable-definition', id: '10', name: 'y', scope: LocalScope, environment: appendEnvironments(envWithElseXMaybe(), envWithXMaybe()) })
167+
.addEdge('0', '5', EdgeType.SameDefDef, 'maybe')
168+
.addEdge('11', '0', EdgeType.Reads, 'maybe')
169+
.addEdge('11', '5', EdgeType.Reads, 'maybe')
170+
.addEdge('10', '11', EdgeType.DefinedBy, 'always')
171+
)
172+
})
86173
}))

0 commit comments

Comments
 (0)