by making client code use the "new" class.
Really, this part of the split class should have the old name,
to minimise disruptions to clients.
Same goes for the other split classes.
Two classes have been inserted into the hierarchies:
- `NonLibraryDataFlowCallable` with a method `getACall2`.
This method implements "get a call, not considering flow summaries".
For `NonLibraryDataFlowCallable`s, `getACall` will defer to `getACall2`.
While you could have a synthesised call to such a callable,
it would not correspond to a `CallNode`.
- `NonLibraryDataFlowSourceCall` with methods
`getArg2` and `getCallable2`. These also refer to a call graph that
does not consider flow summaries.
`getArg2` is used to synthesise pre-update nodes for arguments.
`getCallable2` is used in `connects` to compute argument passing.
This is used to define data flow nodes for overflow arguments.
`getACall2` ensures that `LibraryCallableValue::getACall` is not called
when the charpred of `FunctionCall` is evaluated.
- Branch predicates are made simple. In particular, they do not try to detect library calls.
- All branches based on `CallNode`s are gathered into one.
- That branch has been given a class `NonSpecialCall`, which is the new parent of call classes based on `CallNode`s. (Those classes now have more involved charpreds.)
- A new such class, 'LambdaCall` has been split out from `FunctionCall` to allow the latter to replace its
general `CallNode` field with a specific `FunctionValue` one.
- `NonSpecialCall` is not an abstract class, but it has some abstract overrides. Therefor, it is not
considered a resolved call in the test `UnresolvedCalls.qll`.
Before:
```
Tuple counts for Exprs::Call::getAKeyword_dispred#ff#antijoin_rhs/3@7bc202ij after 9s:
1 ~0% {1} r1 = CONSTANT(unique int)[2]
4244385 ~2% {1} r2 = JOIN r1 WITH py_dict_items_10#join_rhs ON FIRST 1 OUTPUT Rhs.1 'arg0'
4244352 ~3% {3} r3 = JOIN r2 WITH AstGenerated::Call_::getNamedArg_dispred#ffb_201#join_rhs ON FIRST 1 OUTPUT Rhs.1 'arg1', Lhs.0 'arg0', Rhs.2 'arg2'
66618690 ~3% {5} r4 = JOIN r3 WITH AstGenerated::Call_::getNamedArg_dispred#ffb ON FIRST 1 OUTPUT Lhs.1 'arg0', Lhs.0 'arg1', Lhs.2 'arg2', Rhs.1, Rhs.2
31187133 ~0% {5} r5 = SELECT r4 ON In.3 < In.2 'arg2'
31187133 ~1% {5} r6 = SCAN r5 OUTPUT In.4, 0, In.0 'arg0', In.1 'arg1', In.2 'arg2'
0 ~0% {3} r7 = JOIN r6 WITH py_dict_items ON FIRST 2 OUTPUT Lhs.2 'arg0', Lhs.3 'arg1', Lhs.4 'arg2'
return r7
Tuple counts for Exprs::Call::getAKeyword_dispred#ff/2@1dc9468b after 421ms:
1 ~0% {1} r1 = CONSTANT(unique int)[2]
4244385 ~2% {1} r2 = JOIN r1 WITH py_dict_items_10#join_rhs ON FIRST 1 OUTPUT Rhs.1 'result'
4244352 ~0% {3} r3 = JOIN r2 WITH AstGenerated::Call_::getNamedArg_dispred#ffb_201#join_rhs ON FIRST 1 OUTPUT Lhs.0 'result', Rhs.1 'this', Rhs.2
4244352 ~0% {3} r4 = r3 AND NOT Exprs::Call::getAKeyword_dispred#ff#antijoin_rhs(Lhs.0 'result', Lhs.1 'this', Lhs.2)
4244352 ~6% {2} r5 = SCAN r4 OUTPUT In.1 'this', In.0 'result'
return r5
```
Oof. All that work to produce zero tuples. Luckily we can improve
matters somewhat.
Basically, there's no reason to test _all_ dictionary unpackings, since
we're only interested in a lower bound. Thus, we can use `min` instead
which is much more efficient. For convenience I factored this into its
own (private) helper predicate.
Now the tuple counts look as follows:
```
Tuple counts for Exprs::Call::getMinimumUnpackingIndex_dispred#ff#min_range/2@39b0e9sm after 1ms:
246 ~0% {2} r1 = JOIN Keywords::DictUnpackingOrKeyword#class#f#shared WITH AstGenerated::Call_::getNamedArg_dispred#ffb_201#join_rhs ON FIRST 1 OUTPUT Rhs.1 'arg0', Rhs.2 'arg1'
return r1
Registering Exprs::Call::getMinimumUnpackingIndex_dispred#ff#min_range/2@39b0e9sm + with content 9ea2f123k8necpu015v6tpsc2t1
>>> Created relation Exprs::Call::getMinimumUnpackingIndex_dispred#ff#min_range/2@39b0e9sm with 246 rows.
Starting to evaluate predicate Exprs::Call::getMinimumUnpackingIndex_dispred#ff#min_term/3@9f4ca5g8
Tuple counts for Exprs::Call::getMinimumUnpackingIndex_dispred#ff#min_term/3@9f4ca5g8 after 0ms:
246 ~2% {3} r1 = JOIN Keywords::DictUnpackingOrKeyword#class#f#shared WITH AstGenerated::Call_::getNamedArg_dispred#ffb_201#join_rhs ON FIRST 1 OUTPUT Rhs.1 'arg0', Rhs.2 'arg2', Rhs.2 'arg2'
return r1
Tuple counts for Exprs::Call::getAKeyword_dispred#ff/2@000a0alb after 906ms:
1 ~0% {1} r1 = CONSTANT(unique int)[2]
4244385 ~2% {1} r2 = JOIN r1 WITH py_dict_items_10#join_rhs ON FIRST 1 OUTPUT Rhs.1 'result'
4244352 ~0% {3} r3 = JOIN r2 WITH AstGenerated::Call_::getNamedArg_dispred#ffb_201#join_rhs ON FIRST 1 OUTPUT Lhs.0 'result', Rhs.1 'this', Rhs.2
4244280 ~0% {3} r4 = r3 AND NOT Exprs::Call::getMinimumUnpackingIndex_dispred#ff_0#antijoin_rhs(Lhs.1 'this')
4244280 ~6% {2} r5 = SCAN r4 OUTPUT In.1 'this', In.0 'result'
4244352 ~3% {3} r6 = JOIN r2 WITH AstGenerated::Call_::getNamedArg_dispred#ffb_201#join_rhs ON FIRST 1 OUTPUT Rhs.1 'this', Lhs.0 'result', Rhs.2
72 ~4% {4} r7 = JOIN r6 WITH Exprs::Call::getMinimumUnpackingIndex_dispred#ff ON FIRST 1 OUTPUT Lhs.1 'result', Lhs.0 'this', Lhs.2, Rhs.1
72 ~4% {4} r8 = SELECT r7 ON In.2 <= In.3
72 ~0% {2} r9 = SCAN r8 OUTPUT In.1 'this', In.0 'result'
4244352 ~6% {2} r10 = r5 UNION r9
return r10
```
This is not the perfect join order (note the similarity between `r3`
and `r6`) but overall it's a win.
Observed on `FreeCAD/FreeCAD`:
```
Tuple counts for Essa::MethodCallsiteRefinement#24e22a14#f/1@274967ic after 34.5s:
638284 ~0% {2} r1 = SCAN Essa::TEssaNodeRefinement#24e22a14#ffff OUTPUT In.0, In.3 'this'
636521 ~0% {2} r2 = r1 AND NOT Essa::SingleSuccessorGuard#class#24e22a14#f(Lhs.1 'this')
1579493668 ~0% {2} r3 = JOIN r2 WITH SsaDefinitions::SsaSource::method_call_refinement#9197156e#fff ON FIRST 1 OUTPUT Lhs.1 'this', Rhs.2
266673 ~3% {1} r4 = JOIN r3 WITH Essa::EssaNodeRefinement::getDefiningNode#dispred#f0820431#ff ON FIRST 2 OUTPUT Lhs.0 'this'
return r4
```
After a bit of unbinding, we have:
```
Tuple counts for Essa::MethodCallsiteRefinement#24e22a14#f/1@d73d8e27 after 66ms:
215168 ~1% {2} r1 = SCAN Definitions::SsaSourceVariable#class#486534ab#f OUTPUT In.0, In.0
283965 ~2% {2} r2 = JOIN r1 WITH SsaDefinitions::SsaSource::method_call_refinement#9197156e#fff ON FIRST 1 OUTPUT Rhs.2, Lhs.1
401274 ~0% {2} r3 = JOIN r2 WITH Essa::EssaNodeRefinement::getDefiningNode#dispred#f0820431#ff_10#join_rhs ON FIRST 1 OUTPUT Lhs.1, Rhs.1 'this'
266671 ~2% {1} r4 = JOIN r3 WITH Essa::TEssaNodeRefinement#24e22a14#ffff_03#join_rhs ON FIRST 2 OUTPUT Lhs.1 'this'
266671 ~2% {1} r5 = r4 AND NOT Essa::SingleSuccessorGuard#class#24e22a14#f(Lhs.0 'this')
return r5
```
(I'm somewhat confused about the slight difference in tuples, but it's
probably just because the compiler moved some stuff around.)
This was what it looked like (at the point when I killed the evaluation):
```
Tuple counts for SsaCompute::SsaComputeImpl::AdjacentUsesImpl::firstUse#c5fa2be7#ff/2@i1#be98bwif after 1m50s:
274000 ~7% {4} r1 = SCAN SsaCompute::SsaComputeImpl::AdjacentUsesImpl::definesAt#c5fa2be7#ffff OUTPUT In.1, In.0 'def', In.2, In.3
2731768000 ~1% {7} r2 = JOIN r1 WITH SsaCompute::SsaComputeImpl::AdjacentUsesImpl::variableSourceUse#c5fa2be7#ffff ON FIRST 1 OUTPUT Rhs.0, Lhs.2, Lhs.3, Rhs.2, Rhs.3, Rhs.1 'use', Lhs.1 'def'
178000 ~4% {2} r3 = JOIN r2 WITH SsaCompute::SsaComputeImpl::AdjacentUsesImpl::adjacentVarRefs#c5fa2be7#fffff ON FIRST 5 OUTPUT Lhs.6 'def', Lhs.5 'use'
return r3
```
And this is what it looks like now:
```
Tuple counts for SsaCompute::SsaComputeImpl::AdjacentUsesImpl::firstUse#c5fa2be7#ff/2@i1#f9d6ewsi after 207ms:
931353 ~2% {4} r1 = SCAN SsaCompute::SsaComputeImpl::AdjacentUsesImpl::variableSourceUse#c5fa2be7#ffff OUTPUT In.0, In.2, In.3, In.1 'use'
1050477 ~0% {4} r2 = JOIN r1 WITH SsaCompute::SsaComputeImpl::AdjacentUsesImpl::adjacentVarRefs#c5fa2be7#fffff_03412#join_rhs ON FIRST 3 OUTPUT Lhs.0, Rhs.3, Rhs.4, Lhs.3 'use'
506626 ~0% {2} r3 = JOIN r2 WITH SsaCompute::SsaComputeImpl::AdjacentUsesImpl::definesAt#c5fa2be7#ffff_1230#join_rhs ON FIRST 3 OUTPUT Rhs.3 'def', Lhs.3 'use'
return r3
```