Skip to content

Commit 8f055f5

Browse files
committed
Dataflow: Adaptive field flow precision.
1 parent b0f0f89 commit 8f055f5

2 files changed

Lines changed: 305 additions & 6 deletions

File tree

java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll

Lines changed: 281 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1524,11 +1524,50 @@ private predicate flowCandIsReturned(
15241524
)
15251525
}
15261526

1527+
/**
1528+
* Holds if `argApf` is recorded as the summary context for flow reaching `node`
1529+
* and remains relevant for the following pruning stage.
1530+
*/
1531+
private predicate flowCandSummaryCtx(Node node, AccessPathFront argApf, Configuration config) {
1532+
exists(AccessPathFront apf |
1533+
flowCand(node, true, _, apf, config) and
1534+
flowCandFwd(node, true, TAccessPathFrontSome(argApf), apf, config)
1535+
)
1536+
}
1537+
1538+
/**
1539+
* Holds if a length 2 access path approximation with the head `tc` is expected
1540+
* to be expensive.
1541+
*/
1542+
private predicate expensiveLen2unfolding(TypedContent tc, Configuration config) {
1543+
exists(int tails, int nodes, int apLimit, int tupleLimit |
1544+
tails = strictcount(AccessPathFront apf | flowCandConsCand(tc, apf, config)) and
1545+
nodes =
1546+
strictcount(Node n |
1547+
flowCand(n, _, _, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), config)
1548+
or
1549+
flowCandSummaryCtx(n, any(AccessPathFrontHead apf | apf.headUsesContent(tc)), _)
1550+
) and
1551+
accessPathApproxCostLimits(apLimit, tupleLimit) and
1552+
apLimit < tails and
1553+
tupleLimit < (tails - 1) * nodes
1554+
)
1555+
}
1556+
15271557
private newtype TAccessPathApprox =
15281558
TNil(DataFlowType t) or
1529-
TConsNil(TypedContent tc, DataFlowType t) { flowCandConsCand(tc, TFrontNil(t), _) } or
1559+
TConsNil(TypedContent tc, DataFlowType t) {
1560+
flowCandConsCand(tc, TFrontNil(t), _) and
1561+
not expensiveLen2unfolding(tc, _)
1562+
} or
15301563
TConsCons(TypedContent tc1, TypedContent tc2, int len) {
1531-
flowCandConsCand(tc1, TFrontHead(tc2), _) and len in [2 .. accessPathLimit()]
1564+
flowCandConsCand(tc1, TFrontHead(tc2), _) and
1565+
len in [2 .. accessPathLimit()] and
1566+
not expensiveLen2unfolding(tc1, _)
1567+
} or
1568+
TCons1(TypedContent tc, int len) {
1569+
len in [1 .. accessPathLimit()] and
1570+
expensiveLen2unfolding(tc, _)
15321571
}
15331572

15341573
/**
@@ -1623,6 +1662,49 @@ private class AccessPathApproxConsCons extends AccessPathApproxCons, TConsCons {
16231662
or
16241663
len = 2 and
16251664
result = TConsNil(tc2, _)
1665+
or
1666+
result = TCons1(tc2, len - 1)
1667+
)
1668+
}
1669+
}
1670+
1671+
private class AccessPathApproxCons1 extends AccessPathApproxCons, TCons1 {
1672+
private TypedContent tc;
1673+
private int len;
1674+
1675+
AccessPathApproxCons1() { this = TCons1(tc, len) }
1676+
1677+
override string toString() {
1678+
if len = 1
1679+
then result = "[" + tc.toString() + "]"
1680+
else result = "[" + tc.toString() + ", ... (" + len.toString() + ")]"
1681+
}
1682+
1683+
override TypedContent getHead() { result = tc }
1684+
1685+
override int len() { result = len }
1686+
1687+
override DataFlowType getType() { result = tc.getContainerType() }
1688+
1689+
override AccessPathFront getFront() { result = TFrontHead(tc) }
1690+
1691+
override AccessPathApprox pop(TypedContent head) {
1692+
head = tc and
1693+
(
1694+
exists(TypedContent tc2 | flowCandConsCand(tc, TFrontHead(tc2), _) |
1695+
result = TConsCons(tc2, _, len - 1)
1696+
or
1697+
len = 2 and
1698+
result = TConsNil(tc2, _)
1699+
or
1700+
result = TCons1(tc2, len - 1)
1701+
)
1702+
or
1703+
exists(DataFlowType t |
1704+
len = 1 and
1705+
flowCandConsCand(tc, TFrontNil(t), _) and
1706+
result = TNil(t)
1707+
)
16261708
)
16271709
}
16281710
}
@@ -2062,6 +2144,14 @@ private predicate parameterMayFlowThrough(ParameterNode p, DataFlowCallable c, A
20622144
)
20632145
}
20642146

2147+
private predicate nodeMayUseSummary(Node n, AccessPathApprox apa) {
2148+
exists(DataFlowCallable c, AccessPathApprox apa0 |
2149+
parameterMayFlowThrough(_, c, apa) and
2150+
flow(n, true, _, apa0, _) and
2151+
flowFwd(n, any(CallContextCall ccc), TAccessPathApproxSome(apa), _, apa0, _)
2152+
)
2153+
}
2154+
20652155
private newtype TSummaryCtx =
20662156
TSummaryCtxNone() or
20672157
TSummaryCtxSome(ParameterNode p, AccessPath ap) { parameterMayFlowThrough(p, _, ap.getApprox()) }
@@ -2099,9 +2189,118 @@ private class SummaryCtxSome extends SummaryCtx, TSummaryCtxSome {
20992189
}
21002190
}
21012191

2192+
/**
2193+
* Gets the number of length 2 access path approximations that correspond to `apa`.
2194+
*/
2195+
private int count1to2unfold(AccessPathApproxCons1 apa, Configuration config) {
2196+
exists(TypedContent tc, int len |
2197+
tc = apa.getHead() and
2198+
len = apa.len() and
2199+
result =
2200+
strictcount(AccessPathFront apf |
2201+
flowConsCand(tc, any(AccessPathApprox ap | ap.getFront() = apf and ap.len() = len - 1),
2202+
config)
2203+
)
2204+
)
2205+
}
2206+
2207+
/**
2208+
* Holds if a length 2 access path approximation matching `apa` is expected
2209+
* to be expensive.
2210+
*/
2211+
private predicate expensiveLen1to2unfolding(AccessPathApproxCons1 apa, Configuration config) {
2212+
exists(int aps, int nodes, int apLimit, int tupleLimit |
2213+
aps = count1to2unfold(apa, config) and
2214+
nodes =
2215+
strictcount(Node n |
2216+
flow(n, _, _, apa, config)
2217+
or
2218+
nodeMayUseSummary(n, apa)
2219+
) and
2220+
accessPathCostLimits(apLimit, tupleLimit) and
2221+
apLimit < aps and
2222+
tupleLimit < (aps - 1) * nodes
2223+
)
2224+
}
2225+
2226+
private AccessPathApprox getATail(AccessPathApprox apa, Configuration config) {
2227+
exists(TypedContent head |
2228+
apa.pop(head) = result and
2229+
flowConsCand(head, result, config)
2230+
)
2231+
}
2232+
2233+
/**
2234+
* Holds with `unfold = false` if a precise head-tail representation of `apa` is
2235+
* expected to be expensive. Holds with `unfold = true` otherwise.
2236+
*/
2237+
private predicate evalUnfold(AccessPathApprox apa, boolean unfold, Configuration config) {
2238+
exists(int aps, int nodes, int apLimit, int tupleLimit |
2239+
aps = countPotentialAps(apa, config) and
2240+
nodes =
2241+
strictcount(Node n |
2242+
flow(n, _, _, apa, _)
2243+
or
2244+
nodeMayUseSummary(n, apa)
2245+
) and
2246+
accessPathCostLimits(apLimit, tupleLimit) and
2247+
if apLimit < aps and tupleLimit < (aps - 1) * nodes then unfold = false else unfold = true
2248+
)
2249+
}
2250+
2251+
/**
2252+
* Gets the number of `AccessPath`s that correspond to `apa`.
2253+
*/
2254+
private int countAps(AccessPathApprox apa, Configuration config) {
2255+
evalUnfold(apa, false, config) and
2256+
result = 1 and
2257+
(not apa instanceof AccessPathApproxCons1 or expensiveLen1to2unfolding(apa, config))
2258+
or
2259+
evalUnfold(apa, false, config) and
2260+
result = count1to2unfold(apa, config) and
2261+
not expensiveLen1to2unfolding(apa, config)
2262+
or
2263+
evalUnfold(apa, true, config) and
2264+
result = countPotentialAps(apa, config)
2265+
}
2266+
2267+
/**
2268+
* Gets the number of `AccessPath`s that would correspond to `apa` assuming
2269+
* that it is expanded to a precise head-tail representation.
2270+
*/
2271+
language[monotonicAggregates]
2272+
private int countPotentialAps(AccessPathApprox apa, Configuration config) {
2273+
apa instanceof AccessPathApproxNil and result = 1
2274+
or
2275+
result = strictsum(AccessPathApprox tail | tail = getATail(apa, config) | countAps(tail, config))
2276+
}
2277+
21022278
private newtype TAccessPath =
21032279
TAccessPathNil(DataFlowType t) or
2104-
TAccessPathCons(TypedContent head, AccessPath tail) { flowConsCand(head, tail.getApprox(), _) }
2280+
TAccessPathCons(TypedContent head, AccessPath tail) {
2281+
exists(AccessPathApproxCons apa |
2282+
not evalUnfold(apa, false, _) and
2283+
head = apa.getHead() and
2284+
tail.getApprox() = getATail(apa, _)
2285+
)
2286+
} or
2287+
TAccessPathCons2(TypedContent head1, TypedContent head2, int len) {
2288+
exists(AccessPathApproxCons apa |
2289+
evalUnfold(apa, false, _) and
2290+
not expensiveLen1to2unfolding(apa, _) and
2291+
apa.len() = len and
2292+
head1 = apa.getHead() and
2293+
head2 = getATail(apa, _).getHead()
2294+
)
2295+
} or
2296+
TAccessPathCons1(TypedContent head, int len) {
2297+
exists(AccessPathApproxCons apa |
2298+
evalUnfold(apa, false, _) and
2299+
expensiveLen1to2unfolding(apa, _) and
2300+
apa.len() = len and
2301+
head = apa.getHead()
2302+
)
2303+
}
21052304

21062305
private newtype TPathNode =
21072306
TPathNodeMid(Node node, CallContext cc, SummaryCtx sc, AccessPath ap, Configuration config) {
@@ -2205,20 +2404,96 @@ private class AccessPathCons extends AccessPath, TAccessPathCons {
22052404
result = TConsNil(head, tail.(AccessPathNil).getType())
22062405
or
22072406
result = TConsCons(head, tail.getHead(), this.length())
2407+
or
2408+
result = TCons1(head, this.length())
22082409
}
22092410

22102411
override int length() { result = 1 + tail.length() }
22112412

2212-
private string toStringImpl() {
2413+
private string toStringImpl(boolean needsSuffix) {
22132414
exists(DataFlowType t |
22142415
tail = TAccessPathNil(t) and
2416+
needsSuffix = false and
22152417
result = head.toString() + "]" + concat(" : " + ppReprType(t))
22162418
)
22172419
or
2218-
result = head + ", " + tail.(AccessPathCons).toStringImpl()
2420+
result = head + ", " + tail.(AccessPathCons).toStringImpl(needsSuffix)
2421+
or
2422+
exists(TypedContent tc2, TypedContent tc3, int len | tail = TAccessPathCons2(tc2, tc3, len) |
2423+
result = head + ", " + tc2 + ", " + tc3 + ", ... (" and len > 2 and needsSuffix = true
2424+
or
2425+
result = head + ", " + tc2 + ", " + tc3 + "]" and len = 2 and needsSuffix = false
2426+
)
2427+
or
2428+
exists(TypedContent tc2, int len | tail = TAccessPathCons1(tc2, len) |
2429+
result = head + ", " + tc2 + ", ... (" and len > 1 and needsSuffix = true
2430+
or
2431+
result = head + ", " + tc2 + "]" and len = 1 and needsSuffix = false
2432+
)
22192433
}
22202434

2221-
override string toString() { result = "[" + this.toStringImpl() }
2435+
override string toString() {
2436+
result = "[" + this.toStringImpl(true) + length().toString() + ")]"
2437+
or
2438+
result = "[" + this.toStringImpl(false)
2439+
}
2440+
}
2441+
2442+
private class AccessPathCons2 extends AccessPath, TAccessPathCons2 {
2443+
private TypedContent head1;
2444+
private TypedContent head2;
2445+
private int len;
2446+
2447+
AccessPathCons2() { this = TAccessPathCons2(head1, head2, len) }
2448+
2449+
override TypedContent getHead() { result = head1 }
2450+
2451+
override AccessPath getTail() {
2452+
flowConsCand(head1, result.getApprox(), _) and
2453+
result.getHead() = head2 and
2454+
result.length() = len - 1
2455+
}
2456+
2457+
override AccessPathFrontHead getFront() { result = TFrontHead(head1) }
2458+
2459+
override AccessPathApproxCons getApprox() {
2460+
result = TConsCons(head1, head2, len) or
2461+
result = TCons1(head1, len)
2462+
}
2463+
2464+
override int length() { result = len }
2465+
2466+
override string toString() {
2467+
if len = 2
2468+
then result = "[" + head1.toString() + ", " + head2.toString() + "]"
2469+
else
2470+
result = "[" + head1.toString() + ", " + head2.toString() + ", ... (" + len.toString() + ")]"
2471+
}
2472+
}
2473+
2474+
private class AccessPathCons1 extends AccessPath, TAccessPathCons1 {
2475+
private TypedContent head;
2476+
private int len;
2477+
2478+
AccessPathCons1() { this = TAccessPathCons1(head, len) }
2479+
2480+
override TypedContent getHead() { result = head }
2481+
2482+
override AccessPath getTail() {
2483+
flowConsCand(head, result.getApprox(), _) and result.length() = len - 1
2484+
}
2485+
2486+
override AccessPathFrontHead getFront() { result = TFrontHead(head) }
2487+
2488+
override AccessPathApproxCons getApprox() { result = TCons1(head, len) }
2489+
2490+
override int length() { result = len }
2491+
2492+
override string toString() {
2493+
if len = 1
2494+
then result = "[" + head.toString() + "]"
2495+
else result = "[" + head.toString() + ", ... (" + len.toString() + ")]"
2496+
}
22222497
}
22232498

22242499
/**

java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,30 @@ private import DataFlowImplSpecific::Private
22
private import DataFlowImplSpecific::Public
33
import Cached
44

5+
/**
6+
* The cost limits for the `AccessPathFront` to `AccessPathApprox` expansion.
7+
*
8+
* `apLimit` bounds the acceptable fan-out, and `tupleLimit` bounds the
9+
* estimated per-`AccessPathFront` tuple cost. Access paths exceeding both of
10+
* these limits are represented with lower precision during pruning.
11+
*/
12+
predicate accessPathApproxCostLimits(int apLimit, int tupleLimit) {
13+
apLimit = 10 and
14+
tupleLimit = 10000
15+
}
16+
17+
/**
18+
* The cost limits for the `AccessPathApprox` to `AccessPath` expansion.
19+
*
20+
* `apLimit` bounds the acceptable fan-out, and `tupleLimit` bounds the
21+
* estimated per-`AccessPathApprox` tuple cost. Access paths exceeding both of
22+
* these limits are represented with lower precision.
23+
*/
24+
predicate accessPathCostLimits(int apLimit, int tupleLimit) {
25+
apLimit = 5 and
26+
tupleLimit = 1000
27+
}
28+
529
cached
630
private module Cached {
731
/**

0 commit comments

Comments
 (0)