mirror of
https://github.com/github/codeql.git
synced 2026-05-27 01:21:23 +02:00
Compare commits
1006 Commits
nickrolfe/
...
henrymerce
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2189ab030d | ||
|
|
667305e27e | ||
|
|
79b9d05576 | ||
|
|
894ef629f4 | ||
|
|
16e5d70027 | ||
|
|
76128c6c98 | ||
|
|
07a1cbe499 | ||
|
|
662153464e | ||
|
|
8cd3b13d84 | ||
|
|
7d5205acee | ||
|
|
29eb66d772 | ||
|
|
2af509595b | ||
|
|
609d6011a2 | ||
|
|
d3da790191 | ||
|
|
3c8f6e3c07 | ||
|
|
4b3b1d2a8b | ||
|
|
1e752f305d | ||
|
|
08ce03cd93 | ||
|
|
3bab8c6d1d | ||
|
|
b0031a0d85 | ||
|
|
7dde52ced2 | ||
|
|
2a5e0a3b77 | ||
|
|
e2652591a5 | ||
|
|
c74eac4930 | ||
|
|
cec91c4831 | ||
|
|
5101a8e9f3 | ||
|
|
136ecaf49a | ||
|
|
120f2045cd | ||
|
|
6d9cea90cb | ||
|
|
47448d9efc | ||
|
|
6c7a01d3d5 | ||
|
|
f9729bccef | ||
|
|
a3b263ee6e | ||
|
|
2e7ddb479e | ||
|
|
4cbfc306ac | ||
|
|
133ec2e4af | ||
|
|
87a1ccd428 | ||
|
|
e56737e007 | ||
|
|
8c9e817c0d | ||
|
|
bb38c4d6fd | ||
|
|
1a90b388a9 | ||
|
|
055017de49 | ||
|
|
9538ac73e4 | ||
|
|
d626745ab1 | ||
|
|
e99a040884 | ||
|
|
8b44d5c39e | ||
|
|
30805d964c | ||
|
|
9b818a04f2 | ||
|
|
0bb11fa371 | ||
|
|
0547e4ccf2 | ||
|
|
d4e80c664e | ||
|
|
4498657384 | ||
|
|
28806fe5f4 | ||
|
|
6072ccd81d | ||
|
|
0ff9520575 | ||
|
|
b5165e3692 | ||
|
|
5202f963dd | ||
|
|
74c0197544 | ||
|
|
83d204d7a8 | ||
|
|
4d918b5e5f | ||
|
|
3e1164f82e | ||
|
|
055641e684 | ||
|
|
8cccee6eba | ||
|
|
0bd587b395 | ||
|
|
e185e9080c | ||
|
|
b2e40ac603 | ||
|
|
e5f473052d | ||
|
|
9d072a12ed | ||
|
|
672485ae38 | ||
|
|
21aff99637 | ||
|
|
dcca5d28bb | ||
|
|
50518b5622 | ||
|
|
b8e8ddf9ae | ||
|
|
a68b55b099 | ||
|
|
ed78d39d61 | ||
|
|
245edd41ff | ||
|
|
13459c8afc | ||
|
|
4b42c4447b | ||
|
|
5b11cfe006 | ||
|
|
752b126862 | ||
|
|
eaed870b31 | ||
|
|
f6baab6399 | ||
|
|
68c3c16ab3 | ||
|
|
186e3755c0 | ||
|
|
da39f15a9d | ||
|
|
522074940d | ||
|
|
b60fef88b5 | ||
|
|
21167f4b67 | ||
|
|
f308be7382 | ||
|
|
74221f4aba | ||
|
|
6f22867af9 | ||
|
|
ddeb700fd6 | ||
|
|
fc64faefcf | ||
|
|
6f2b528a32 | ||
|
|
8322a44379 | ||
|
|
f09f1c4c50 | ||
|
|
39e3254fe0 | ||
|
|
0de6511dff | ||
|
|
9f08acab7e | ||
|
|
344f7bca5b | ||
|
|
06000781e9 | ||
|
|
fc43220864 | ||
|
|
2b1f34ed9b | ||
|
|
8ba864e897 | ||
|
|
47fd64fc44 | ||
|
|
2b2ff7717e | ||
|
|
75586b0cf6 | ||
|
|
a393bff6cb | ||
|
|
90a9688310 | ||
|
|
cd39d15b40 | ||
|
|
8fc7e4be43 | ||
|
|
c297a68acf | ||
|
|
e2ef780c55 | ||
|
|
38ff584307 | ||
|
|
f933d24031 | ||
|
|
70efadac77 | ||
|
|
e87a4531d8 | ||
|
|
ac20eafecc | ||
|
|
2bba31eb02 | ||
|
|
dc464879a2 | ||
|
|
cd33e4d394 | ||
|
|
b6ce37b241 | ||
|
|
8fd8c9b04d | ||
|
|
0caea17118 | ||
|
|
34feafd4fa | ||
|
|
1f3f7e9ccc | ||
|
|
4068cc9c3a | ||
|
|
bc80c9b013 | ||
|
|
fb9b16325d | ||
|
|
fd0e318eb1 | ||
|
|
923ca134e8 | ||
|
|
b6a6ed5ba3 | ||
|
|
62730e7a4b | ||
|
|
a4538de3a3 | ||
|
|
2f7250a0b3 | ||
|
|
63ecae5426 | ||
|
|
2d78cce7a5 | ||
|
|
ee858d840e | ||
|
|
2218516685 | ||
|
|
46958e5bff | ||
|
|
af55f172ae | ||
|
|
011fc20963 | ||
|
|
6815a13a00 | ||
|
|
2af7817691 | ||
|
|
6c2713dd8b | ||
|
|
9d22ec88fd | ||
|
|
1cca377e7d | ||
|
|
ecdaeb0c10 | ||
|
|
a89be2e3f8 | ||
|
|
c998370c84 | ||
|
|
36585a7469 | ||
|
|
6dc6a78293 | ||
|
|
08b6a17097 | ||
|
|
22ebe68b1b | ||
|
|
1645fcf79c | ||
|
|
de72a765e0 | ||
|
|
9ff63b00d6 | ||
|
|
5ddfb37f29 | ||
|
|
6f24947ec6 | ||
|
|
3c3a65243f | ||
|
|
fbb3e8d780 | ||
|
|
d232283647 | ||
|
|
4eacbd1cbe | ||
|
|
6d58dd2823 | ||
|
|
3da73b9001 | ||
|
|
58dd75881c | ||
|
|
ac41451798 | ||
|
|
0c1285f5d9 | ||
|
|
7c2841f058 | ||
|
|
474c808373 | ||
|
|
08c778241d | ||
|
|
69671ce90d | ||
|
|
413375992d | ||
|
|
9125b85ff0 | ||
|
|
92453bd2c5 | ||
|
|
945bb7459a | ||
|
|
a62ad5000b | ||
|
|
135ee0d0c1 | ||
|
|
028ef6f27f | ||
|
|
48e6bdb117 | ||
|
|
db6f843641 | ||
|
|
8603609698 | ||
|
|
e468434b82 | ||
|
|
ddfcfc9b67 | ||
|
|
f6d99dc00d | ||
|
|
a743067dc8 | ||
|
|
04df56d1c0 | ||
|
|
7cfc696d62 | ||
|
|
f846915b58 | ||
|
|
c8cdbfa352 | ||
|
|
b4eadefb92 | ||
|
|
063c8286c8 | ||
|
|
55cdb7d755 | ||
|
|
286c894f34 | ||
|
|
3dbaa087d4 | ||
|
|
2ce5b85db4 | ||
|
|
188915e597 | ||
|
|
76606b5995 | ||
|
|
eed98bd76a | ||
|
|
ea9640a39d | ||
|
|
8d22db8089 | ||
|
|
9ad8a85f4d | ||
|
|
c70d384d28 | ||
|
|
3fe2a08376 | ||
|
|
a722631278 | ||
|
|
8d3cf7f5aa | ||
|
|
d408105fad | ||
|
|
5ef71e6ef3 | ||
|
|
a65f5725d3 | ||
|
|
a7cd097ca2 | ||
|
|
075c9d89b3 | ||
|
|
9e8e2e2b48 | ||
|
|
e7b091086d | ||
|
|
aafa5762ad | ||
|
|
98e6fc8a88 | ||
|
|
3a8e2db3ab | ||
|
|
ed3e5395d1 | ||
|
|
0e6bb28016 | ||
|
|
a980f26fda | ||
|
|
9f4107d211 | ||
|
|
71c279f537 | ||
|
|
b9ea4a8709 | ||
|
|
1a98079100 | ||
|
|
12c24c07df | ||
|
|
f01b9005b1 | ||
|
|
6599eca9fb | ||
|
|
708e059e7f | ||
|
|
d97b130bdd | ||
|
|
fd9199c0c0 | ||
|
|
d7ed325b3f | ||
|
|
f4310898b3 | ||
|
|
03ae58830a | ||
|
|
3ce41015bb | ||
|
|
87ebcea913 | ||
|
|
a40a393b38 | ||
|
|
1cd42ea668 | ||
|
|
6b7abacc5f | ||
|
|
8040d9cfcf | ||
|
|
95dbe1383b | ||
|
|
39927fa613 | ||
|
|
e6e52a3b32 | ||
|
|
2fe6880d70 | ||
|
|
cfdfcaa3e8 | ||
|
|
5d60975f65 | ||
|
|
3f0bfe1d75 | ||
|
|
63f50a9eb7 | ||
|
|
9a9f7943aa | ||
|
|
53884915a5 | ||
|
|
c616f5784d | ||
|
|
7c3b68b7f8 | ||
|
|
0023b885f5 | ||
|
|
2163648b39 | ||
|
|
9e097f5430 | ||
|
|
0d4cb1e6ce | ||
|
|
6eb4525ab2 | ||
|
|
e812029c03 | ||
|
|
4bbfa514c9 | ||
|
|
c8b8a2874f | ||
|
|
dc022430ee | ||
|
|
412bd32f45 | ||
|
|
b84c03672d | ||
|
|
bca6cecd1c | ||
|
|
c17560f948 | ||
|
|
78e3906ea7 | ||
|
|
723ac818d9 | ||
|
|
c2e057def9 | ||
|
|
d323b3b17d | ||
|
|
cce3780481 | ||
|
|
f0c5a80d1a | ||
|
|
0ff36cd083 | ||
|
|
b5d37ae0fe | ||
|
|
d1a09b62d3 | ||
|
|
eef7709982 | ||
|
|
5beb681580 | ||
|
|
3471e757f2 | ||
|
|
9f614b1d98 | ||
|
|
19e6da517b | ||
|
|
9ee1c49bac | ||
|
|
491f72bb2a | ||
|
|
de69e4c645 | ||
|
|
f7b53321b9 | ||
|
|
67ebebbaeb | ||
|
|
55ea715ce9 | ||
|
|
9034d74663 | ||
|
|
860b1a5cc3 | ||
|
|
80919e39a2 | ||
|
|
99081ea7e0 | ||
|
|
5e4b866f2b | ||
|
|
62e58b534c | ||
|
|
f48ecb1dc8 | ||
|
|
a0b7f267ff | ||
|
|
0234e77d2f | ||
|
|
b8809a20d8 | ||
|
|
b5cf4c2f82 | ||
|
|
06cae3dac2 | ||
|
|
004144bbef | ||
|
|
9ea320c53c | ||
|
|
982de28b89 | ||
|
|
e09c12430d | ||
|
|
b639a8d183 | ||
|
|
34cc61e51f | ||
|
|
5a4557f588 | ||
|
|
e2a2a42d59 | ||
|
|
ea580cd9c0 | ||
|
|
b513033e0f | ||
|
|
891694b50a | ||
|
|
140a70f9df | ||
|
|
0bf055fbec | ||
|
|
9a11c13e11 | ||
|
|
dbcd4d6d5d | ||
|
|
7ffd9b4f9e | ||
|
|
7d8284a41c | ||
|
|
901919f7ff | ||
|
|
43ff3b1c80 | ||
|
|
bf9b8cfff0 | ||
|
|
9a1b98e1d9 | ||
|
|
5d901ef728 | ||
|
|
c29011a5cf | ||
|
|
2d4176bec0 | ||
|
|
dbd393b77a | ||
|
|
974c7b0898 | ||
|
|
74ac234f1c | ||
|
|
8740e879b4 | ||
|
|
a546b38ee0 | ||
|
|
6960a7b97e | ||
|
|
ef972159a6 | ||
|
|
4cfd978bfe | ||
|
|
b92758883b | ||
|
|
e2bd792fc2 | ||
|
|
739fe75194 | ||
|
|
58de6d143f | ||
|
|
747ab122c3 | ||
|
|
8564c9001a | ||
|
|
9500c9c8bc | ||
|
|
35baa1c3df | ||
|
|
83b4070f31 | ||
|
|
281f25403d | ||
|
|
bc10fd94cb | ||
|
|
0e9fcc6c39 | ||
|
|
157f56f48a | ||
|
|
f36bb8baaf | ||
|
|
842f617bc1 | ||
|
|
be150f269b | ||
|
|
2654e27123 | ||
|
|
6b2460d4a1 | ||
|
|
060862ab3b | ||
|
|
e607953b9c | ||
|
|
9a859334d4 | ||
|
|
6c59333716 | ||
|
|
7dae6122d9 | ||
|
|
ca9d5439f0 | ||
|
|
7a7ec06819 | ||
|
|
1a4fd7bc7d | ||
|
|
2b2ac82fb7 | ||
|
|
c616eb1473 | ||
|
|
38579ef25b | ||
|
|
a80d50cbc0 | ||
|
|
a1d8dfb524 | ||
|
|
c844f5382f | ||
|
|
f9fea15a52 | ||
|
|
364de55b8d | ||
|
|
cd11ef3bf6 | ||
|
|
c3462be2c9 | ||
|
|
4ca006ba3d | ||
|
|
88032afdc3 | ||
|
|
ec772fb6b2 | ||
|
|
32ef40c77b | ||
|
|
2d907f825e | ||
|
|
ae622bd482 | ||
|
|
198b321158 | ||
|
|
34fdf11b4b | ||
|
|
82abab1510 | ||
|
|
6d9fb3ca43 | ||
|
|
678a21e532 | ||
|
|
e0b876d2f6 | ||
|
|
d23a920ed4 | ||
|
|
57e7bfbdba | ||
|
|
047cff0749 | ||
|
|
ccdaf49464 | ||
|
|
55434653f5 | ||
|
|
98da532c46 | ||
|
|
86d78b34aa | ||
|
|
de926dc2a1 | ||
|
|
1f90dcadf1 | ||
|
|
2f39c64cc2 | ||
|
|
0f086056a1 | ||
|
|
a8c4455b20 | ||
|
|
92a7114b72 | ||
|
|
c6d285dd2a | ||
|
|
a856395d56 | ||
|
|
f9fa22c14d | ||
|
|
1a751608de | ||
|
|
e2ab1c8c5e | ||
|
|
4da1dce811 | ||
|
|
ab5d9459c7 | ||
|
|
a0448240aa | ||
|
|
7514fe2b45 | ||
|
|
289d58745a | ||
|
|
f5426336c3 | ||
|
|
33135e909a | ||
|
|
e2f79d8516 | ||
|
|
24000a50e6 | ||
|
|
26e9adcc34 | ||
|
|
ef21d1b512 | ||
|
|
6388ac5f1d | ||
|
|
d2b18d952d | ||
|
|
bd1e708c5d | ||
|
|
985cd1ebdb | ||
|
|
59581690fd | ||
|
|
1efe1e0d10 | ||
|
|
7178a98e45 | ||
|
|
30251740e3 | ||
|
|
8195ebf4b3 | ||
|
|
556cdbaa21 | ||
|
|
37775407a9 | ||
|
|
340897f262 | ||
|
|
a23750a9c7 | ||
|
|
40e47c0ea3 | ||
|
|
c65d1d9a50 | ||
|
|
f70e4fea55 | ||
|
|
10bca3544c | ||
|
|
bfe2e2e0b9 | ||
|
|
1e31416049 | ||
|
|
c708b6b76f | ||
|
|
ac5a46f24f | ||
|
|
5f4aad40c1 | ||
|
|
aa1541a5c3 | ||
|
|
a58c47b07b | ||
|
|
f53314019a | ||
|
|
d9d304fc13 | ||
|
|
cd332a75fc | ||
|
|
56a7c8b163 | ||
|
|
4b82840e9d | ||
|
|
cb8f1b4593 | ||
|
|
d9e02e83fe | ||
|
|
1bacce487e | ||
|
|
954fd8d6f7 | ||
|
|
8727060ca7 | ||
|
|
87aa39cef2 | ||
|
|
0c6680b2c0 | ||
|
|
55fe01018f | ||
|
|
f14f9449ee | ||
|
|
b3e64f1669 | ||
|
|
330c2c42b5 | ||
|
|
5cafb86c88 | ||
|
|
3f4c2ba24e | ||
|
|
1e0eb2f6e4 | ||
|
|
2581efc18a | ||
|
|
a2175a3207 | ||
|
|
507c8addb2 | ||
|
|
76d2665132 | ||
|
|
96ff2f5125 | ||
|
|
dfe77f844f | ||
|
|
0ab510f543 | ||
|
|
c94bfc306a | ||
|
|
3d6a5263e0 | ||
|
|
8e496f7121 | ||
|
|
fff5d293ff | ||
|
|
92fb7f555c | ||
|
|
03ff2c622a | ||
|
|
613e971987 | ||
|
|
6f80387ac1 | ||
|
|
618d135b0a | ||
|
|
77aca0a365 | ||
|
|
85fdbda16f | ||
|
|
021d9415b8 | ||
|
|
e0b121cd90 | ||
|
|
4af3775b72 | ||
|
|
b639e82d79 | ||
|
|
cedc5fd743 | ||
|
|
d7bfaec0f5 | ||
|
|
34aa4981be | ||
|
|
a9b7fed537 | ||
|
|
ac90259906 | ||
|
|
693baae1ba | ||
|
|
5f78bbbf52 | ||
|
|
f1ac23eff5 | ||
|
|
fab3479f68 | ||
|
|
457ece152a | ||
|
|
5bfe0fff89 | ||
|
|
10e5a8b3e5 | ||
|
|
543bd28b03 | ||
|
|
ad2b068429 | ||
|
|
d324f9397c | ||
|
|
25da904314 | ||
|
|
4666024419 | ||
|
|
91f99ed2a1 | ||
|
|
fad7e9489b | ||
|
|
f3fda42b83 | ||
|
|
f41c4702c3 | ||
|
|
8a412dc5fd | ||
|
|
624b794980 | ||
|
|
5ea93d6447 | ||
|
|
9e2bc41648 | ||
|
|
9e91f3a341 | ||
|
|
95f21b5308 | ||
|
|
3544c85445 | ||
|
|
1101b1054d | ||
|
|
a56a5e4e7d | ||
|
|
7236f3b4b6 | ||
|
|
af0f32fdb6 | ||
|
|
b8d7f52d3e | ||
|
|
a19627c72f | ||
|
|
02f500b9c2 | ||
|
|
99f5f70345 | ||
|
|
bf5e36e9d4 | ||
|
|
58f6058a63 | ||
|
|
4ba5ae09b0 | ||
|
|
061fc16730 | ||
|
|
0d1ff4d2ee | ||
|
|
27bbddf035 | ||
|
|
2895428d5b | ||
|
|
3bfa868105 | ||
|
|
5515256e53 | ||
|
|
f4704f1325 | ||
|
|
fd92c4e435 | ||
|
|
ae4b6c54bc | ||
|
|
e9b114630a | ||
|
|
aef0275b3c | ||
|
|
7b4460edb7 | ||
|
|
d00196f6be | ||
|
|
0894e81ce4 | ||
|
|
a9dd868348 | ||
|
|
c94b64cbca | ||
|
|
16d96d2ad3 | ||
|
|
b9bf597044 | ||
|
|
523c15cd72 | ||
|
|
e50938588e | ||
|
|
4095c2012e | ||
|
|
df6962143d | ||
|
|
5539b7ffed | ||
|
|
3638892d35 | ||
|
|
43a4795272 | ||
|
|
f01ee5914b | ||
|
|
7b0ebd3f1a | ||
|
|
ddc9ad3187 | ||
|
|
1327d7c8d5 | ||
|
|
712614a03c | ||
|
|
737c747dbb | ||
|
|
1ba6f448cd | ||
|
|
be46c1f679 | ||
|
|
08bc80ffdb | ||
|
|
1f89b4987b | ||
|
|
76e841830f | ||
|
|
9cf34f19bb | ||
|
|
264f4ab5ab | ||
|
|
dd17271ec8 | ||
|
|
aab8c64973 | ||
|
|
2c5d5ecdd8 | ||
|
|
32765e9bc1 | ||
|
|
dfbfbe4953 | ||
|
|
ad5619ff07 | ||
|
|
ab37ae6613 | ||
|
|
05aa314ac9 | ||
|
|
c175f0aa9d | ||
|
|
51f4f57617 | ||
|
|
e6145f04d2 | ||
|
|
ab4780c505 | ||
|
|
b9eb278380 | ||
|
|
98eb848e22 | ||
|
|
06cacfdd83 | ||
|
|
cf5b317eb1 | ||
|
|
4a2894a707 | ||
|
|
eb645ba963 | ||
|
|
8b287a7846 | ||
|
|
9d99ce12c4 | ||
|
|
8cd9fdebf9 | ||
|
|
ab88d945e2 | ||
|
|
c2632cff3d | ||
|
|
768932d7b3 | ||
|
|
07d5086b07 | ||
|
|
d828ab7fd2 | ||
|
|
5975e19f53 | ||
|
|
2a8807efe4 | ||
|
|
076a3dca1f | ||
|
|
d9a214767b | ||
|
|
49ea53f32b | ||
|
|
2c013214f7 | ||
|
|
1df8ec2cae | ||
|
|
62039b866c | ||
|
|
028799deb6 | ||
|
|
7a9315f146 | ||
|
|
97625d7c2c | ||
|
|
cb6bcada4c | ||
|
|
62b3c3c9a0 | ||
|
|
0240631510 | ||
|
|
738354b8e7 | ||
|
|
971f032b5f | ||
|
|
46bd3e58a3 | ||
|
|
5f4c1dd19b | ||
|
|
83edcf515b | ||
|
|
3e6ac74d73 | ||
|
|
56cabb8f46 | ||
|
|
c52e453342 | ||
|
|
54fba2d6a1 | ||
|
|
7d0152f3c0 | ||
|
|
6fa9413f8b | ||
|
|
6dd5dad4a9 | ||
|
|
18a47227b3 | ||
|
|
f7f315adbb | ||
|
|
7a96b8e9e1 | ||
|
|
898f5ec596 | ||
|
|
6f4107ff23 | ||
|
|
53b03152f3 | ||
|
|
501ff12abb | ||
|
|
d1852af7b6 | ||
|
|
092beb8b73 | ||
|
|
8ee804a8c2 | ||
|
|
14bc297946 | ||
|
|
c1de4165a9 | ||
|
|
302373d154 | ||
|
|
d5e2026a26 | ||
|
|
5d62aa5b29 | ||
|
|
fe80c4a17b | ||
|
|
1e64893742 | ||
|
|
660398aa78 | ||
|
|
73fd66cfed | ||
|
|
41e7dea943 | ||
|
|
83389be8e2 | ||
|
|
5c2734c643 | ||
|
|
fd12b144bc | ||
|
|
a7e4e5ef83 | ||
|
|
13815fe728 | ||
|
|
62d30630aa | ||
|
|
5d77e62f3a | ||
|
|
57e13c6066 | ||
|
|
222db37c0d | ||
|
|
a64e939d71 | ||
|
|
75e2555a8a | ||
|
|
095f896f95 | ||
|
|
9bbf08ddcf | ||
|
|
9d843153d4 | ||
|
|
f1307b772a | ||
|
|
b7b9120724 | ||
|
|
c207580ed9 | ||
|
|
e2cb53c65f | ||
|
|
17da28118a | ||
|
|
42a046edc6 | ||
|
|
18b08060ae | ||
|
|
9b8b916199 | ||
|
|
5d7b09ac67 | ||
|
|
093be44258 | ||
|
|
dec7f93097 | ||
|
|
d6a714cf69 | ||
|
|
668928045e | ||
|
|
e88bbfdd67 | ||
|
|
64acd0288e | ||
|
|
da5d10fd6b | ||
|
|
b59f6665a2 | ||
|
|
9ff426cf23 | ||
|
|
dde493259a | ||
|
|
db40ccae81 | ||
|
|
301a907596 | ||
|
|
93dfee866a | ||
|
|
e44e982065 | ||
|
|
3161d112d1 | ||
|
|
d34e731f1d | ||
|
|
d624259eab | ||
|
|
a75f195df3 | ||
|
|
f334201fce | ||
|
|
cb4f10c609 | ||
|
|
c2a2a3a676 | ||
|
|
9604cd5595 | ||
|
|
a0903c377d | ||
|
|
b284e727a9 | ||
|
|
8a569da370 | ||
|
|
fe5115169f | ||
|
|
e1516b4e9d | ||
|
|
bfb9577d15 | ||
|
|
f676fc00d3 | ||
|
|
0897b004eb | ||
|
|
d36c66cfca | ||
|
|
4f59886a65 | ||
|
|
9730021641 | ||
|
|
35b6cbe549 | ||
|
|
e51a10a816 | ||
|
|
d52b2bd863 | ||
|
|
afa6424d67 | ||
|
|
1c78c792ff | ||
|
|
7e7c363e43 | ||
|
|
85f00fda19 | ||
|
|
abf508eeeb | ||
|
|
fed0a06353 | ||
|
|
3a1836c9f6 | ||
|
|
bc91f664ac | ||
|
|
513e0bbea9 | ||
|
|
bfacd23573 | ||
|
|
5951ae79b9 | ||
|
|
00df6798b1 | ||
|
|
2b4e3a7d9b | ||
|
|
6fffdf6101 | ||
|
|
e94b2b6113 | ||
|
|
635a668670 | ||
|
|
c8a4a8b965 | ||
|
|
5f73fb21b8 | ||
|
|
0f2f68bcbb | ||
|
|
8f9741ae72 | ||
|
|
490156d7db | ||
|
|
cfc5629435 | ||
|
|
05900cda87 | ||
|
|
15c90adec5 | ||
|
|
2096c0aab1 | ||
|
|
47b14f1adc | ||
|
|
b3ec82cd36 | ||
|
|
a6c285ad32 | ||
|
|
f4d63cc5e7 | ||
|
|
ef4a27ff8c | ||
|
|
e75448ebb0 | ||
|
|
d425b3782e | ||
|
|
c34b089bc5 | ||
|
|
675e284c0e | ||
|
|
246a515175 | ||
|
|
ee2541c3bc | ||
|
|
cde80ccf83 | ||
|
|
387c96d1e2 | ||
|
|
8c72cc0cdd | ||
|
|
7201b3e116 | ||
|
|
8536f5f5a2 | ||
|
|
4f6e5c903b | ||
|
|
beb0902db5 | ||
|
|
c92249525b | ||
|
|
a33a8fd518 | ||
|
|
f3977ea3d7 | ||
|
|
3abe3e43d0 | ||
|
|
0acf6aaec8 | ||
|
|
8c3349f40f | ||
|
|
12e0185b0d | ||
|
|
7197216185 | ||
|
|
fc3ff41d65 | ||
|
|
2cd23e5ee0 | ||
|
|
8135dcefdd | ||
|
|
228e9e973a | ||
|
|
521d863429 | ||
|
|
2547a8d746 | ||
|
|
21a1ee7758 | ||
|
|
3efe60fdd2 | ||
|
|
5dbaea8b52 | ||
|
|
b1ea00fa85 | ||
|
|
710d0cfc3d | ||
|
|
8caff41138 | ||
|
|
3a488574e5 | ||
|
|
8bef79502f | ||
|
|
5ebefe2d30 | ||
|
|
6648a695eb | ||
|
|
b3ba75a00f | ||
|
|
3dec222922 | ||
|
|
7069f45864 | ||
|
|
2059896882 | ||
|
|
436152a46d | ||
|
|
e8895686f8 | ||
|
|
1842fed7a2 | ||
|
|
13ce2569d7 | ||
|
|
bccd4e9e93 | ||
|
|
bd92403b42 | ||
|
|
6d09334cba | ||
|
|
3fa66519f5 | ||
|
|
d9e5d179d2 | ||
|
|
358663ffbb | ||
|
|
12305aae42 | ||
|
|
9478faf040 | ||
|
|
3fb0139430 | ||
|
|
2574aa8980 | ||
|
|
56dab252c9 | ||
|
|
cca675a161 | ||
|
|
432fc74455 | ||
|
|
235a3ec232 | ||
|
|
0addb2d1ea | ||
|
|
c3b1d7e5c8 | ||
|
|
cee80f766f | ||
|
|
adfc725225 | ||
|
|
4fd3f212f8 | ||
|
|
0d161bec7a | ||
|
|
96b6f670d9 | ||
|
|
78371894f4 | ||
|
|
a9a9e34265 | ||
|
|
1243c736dd | ||
|
|
2dedfb302a | ||
|
|
0c9c9bbde7 | ||
|
|
fa9e9dd847 | ||
|
|
3d124cf95e | ||
|
|
d1238dfd8b | ||
|
|
6e183af383 | ||
|
|
e94b0f5913 | ||
|
|
2a808b2cd6 | ||
|
|
2d65aa17db | ||
|
|
78774233c7 | ||
|
|
0372ccce02 | ||
|
|
af64b319ee | ||
|
|
f557df6c4e | ||
|
|
f1229ff071 | ||
|
|
5e2cab4fb1 | ||
|
|
71cca6d644 | ||
|
|
2e912ee28e | ||
|
|
c1ab49fe8a | ||
|
|
350cbb4c5d | ||
|
|
e0e18c6587 | ||
|
|
11154a9409 | ||
|
|
2182bb5c91 | ||
|
|
d990e790e7 | ||
|
|
aeedfd9987 | ||
|
|
b128c7ca00 | ||
|
|
19e010e6fe | ||
|
|
6eabb610b4 | ||
|
|
699630af54 | ||
|
|
92e4a1ed17 | ||
|
|
034c7f3538 | ||
|
|
51cebdce83 | ||
|
|
ce3a19458d | ||
|
|
54e946918a | ||
|
|
8077a49109 | ||
|
|
06586a13a3 | ||
|
|
826f44d98e | ||
|
|
01ad19b82b | ||
|
|
c850554467 | ||
|
|
fed6a97eb8 | ||
|
|
89e713a25c | ||
|
|
cd6d73d553 | ||
|
|
6c0083e584 | ||
|
|
1ce09afa08 | ||
|
|
6f7d0b62d7 | ||
|
|
8a4b043cb1 | ||
|
|
29e3abc977 | ||
|
|
62e729501c | ||
|
|
97264b5dda | ||
|
|
fe39823942 | ||
|
|
c15ddf6e92 | ||
|
|
2ddf445caf | ||
|
|
44afa34e37 | ||
|
|
8ba545999e | ||
|
|
9c8a51bca6 | ||
|
|
038438edca | ||
|
|
5228196f79 | ||
|
|
311df4d2b7 | ||
|
|
92d59aa11c | ||
|
|
834d5ec6ad | ||
|
|
1e1e549847 | ||
|
|
283b8231cb | ||
|
|
2cb3d2c53f | ||
|
|
ab23ffff3d | ||
|
|
f36accf3e6 | ||
|
|
53b4337795 | ||
|
|
9193984f1b | ||
|
|
6858acc6a9 | ||
|
|
26a24a3895 | ||
|
|
44db920f10 | ||
|
|
8a81d42e6f | ||
|
|
f91e43c068 | ||
|
|
a8a181a32f | ||
|
|
e117659dce | ||
|
|
f4a054ea01 | ||
|
|
d2d6b2ca7c | ||
|
|
dbd1148bd6 | ||
|
|
b79f8f1890 | ||
|
|
8cd86ae8f5 | ||
|
|
b23b3c33f6 | ||
|
|
de38570424 | ||
|
|
1bf4542c89 | ||
|
|
ddbba403f8 | ||
|
|
aeb9ace694 | ||
|
|
7741a72cc5 | ||
|
|
8ce7b287d1 | ||
|
|
3554e8d105 | ||
|
|
2de757335f | ||
|
|
068beeff56 | ||
|
|
d2ea732539 | ||
|
|
ba32c54038 | ||
|
|
7619d0fc33 | ||
|
|
b69977b37a | ||
|
|
bd8eec8475 | ||
|
|
54ab5d4bc8 | ||
|
|
7e7a6464ec | ||
|
|
149b235c7a | ||
|
|
cb61f87aa3 | ||
|
|
5d5d6bcc69 | ||
|
|
baec186359 | ||
|
|
5a02b3880e | ||
|
|
3f3988ce1c | ||
|
|
8e8a324fa6 | ||
|
|
e5b68d68cb | ||
|
|
03ada6e97a | ||
|
|
ed5a386618 | ||
|
|
f5464b79e4 | ||
|
|
f70d808e2f | ||
|
|
9fe822f41c | ||
|
|
9009dac9ea | ||
|
|
0e5cfd3469 | ||
|
|
5379b25146 | ||
|
|
f17c06a37f | ||
|
|
f7bd74ea59 | ||
|
|
57fe4b9a31 | ||
|
|
b4b8392748 | ||
|
|
e487832823 | ||
|
|
a5749a5eb1 | ||
|
|
392e2eebeb | ||
|
|
d1d2d61d7e | ||
|
|
28ae4c211f | ||
|
|
e7983fb269 | ||
|
|
bc6c13be69 | ||
|
|
4dd9e7d6a0 | ||
|
|
14963103aa | ||
|
|
445da1e71e | ||
|
|
8263524d70 | ||
|
|
2ab7a55545 | ||
|
|
28369d1822 | ||
|
|
aa2cdb7a53 | ||
|
|
f90220436f | ||
|
|
9a537f9c23 | ||
|
|
21b70a009e | ||
|
|
9604f88ae0 | ||
|
|
d7973592da | ||
|
|
5f0ce4d232 | ||
|
|
a6f2ebe820 | ||
|
|
9eb4cda1af | ||
|
|
031fa2199c | ||
|
|
529a3d9d61 | ||
|
|
5dfb0d4d64 | ||
|
|
ef30ca211a | ||
|
|
7b949e8db2 | ||
|
|
fd8a128693 | ||
|
|
09d96e65b8 | ||
|
|
8c400d9b1b | ||
|
|
d006db9d20 | ||
|
|
6b0360acca | ||
|
|
5cbf632573 | ||
|
|
047aee313c | ||
|
|
e904e7410b | ||
|
|
c40b3a9533 | ||
|
|
516674697b | ||
|
|
8d6cac76cc | ||
|
|
cf31b6e7f6 | ||
|
|
5a1eb1995c | ||
|
|
694016dcbe | ||
|
|
6a9277b5ce | ||
|
|
51b56a9e28 | ||
|
|
2062afc868 | ||
|
|
d4de5e3248 | ||
|
|
bcf4626fd0 | ||
|
|
c55b7bcd85 | ||
|
|
9b5ff66b68 | ||
|
|
2b286a856c | ||
|
|
94e2676c0f | ||
|
|
2d5c6e2723 | ||
|
|
c839f35485 | ||
|
|
50147708bf | ||
|
|
eef946a0c8 | ||
|
|
c9895b54fe | ||
|
|
c50c805f5f | ||
|
|
d34c5fd72f | ||
|
|
285de2b4c8 | ||
|
|
b1f8b5352b | ||
|
|
3661ff3bd8 | ||
|
|
fc9fb59082 | ||
|
|
115113888f | ||
|
|
cc1c32cf0e | ||
|
|
b9c08167f3 | ||
|
|
aafae24ef2 | ||
|
|
8d556ed1e1 | ||
|
|
a5912ff76d | ||
|
|
1fc58e51a3 | ||
|
|
1f2618b893 | ||
|
|
378db7de87 | ||
|
|
3c1206f873 | ||
|
|
f6311bf051 | ||
|
|
15b07bfcc0 | ||
|
|
5264936fc3 | ||
|
|
520a2da8ab | ||
|
|
272e4f6cf9 | ||
|
|
2dc38aee54 | ||
|
|
99ed4a1a89 | ||
|
|
fd64ff9ef1 | ||
|
|
c40ffab093 | ||
|
|
672e4a3d72 | ||
|
|
60993214d5 | ||
|
|
01e345c2cc | ||
|
|
8535e6f281 | ||
|
|
8170f01b66 | ||
|
|
f348a5ce47 | ||
|
|
25065bc986 | ||
|
|
0b0ac8317c | ||
|
|
054218a381 | ||
|
|
629efb85fb | ||
|
|
0b5c8909dd | ||
|
|
595ea6c383 | ||
|
|
57ac944319 | ||
|
|
92c874c2e2 | ||
|
|
0e4865c40c | ||
|
|
f3c0bf7826 | ||
|
|
f9b244ecad | ||
|
|
68f79f054b | ||
|
|
8d84d63b94 | ||
|
|
ce507beed4 | ||
|
|
e14b10370e | ||
|
|
f1b3c70909 | ||
|
|
3d2b6f7a2d | ||
|
|
7fb44470ee | ||
|
|
a1f48db60b | ||
|
|
4079e5352e | ||
|
|
07422a1dce | ||
|
|
058ade4d8e | ||
|
|
6565680dd6 | ||
|
|
d22da880e7 | ||
|
|
198f8dcc1f | ||
|
|
7ed7809a60 | ||
|
|
513055cae5 | ||
|
|
ee70eb709c | ||
|
|
5edb3b1153 |
@@ -1,11 +1,16 @@
|
||||
{ "provide": [ "ruby/.codeqlmanifest.json",
|
||||
"*/ql/src/qlpack.yml",
|
||||
"*/ql/lib/qlpack.yml",
|
||||
"*/ql/test/qlpack.yml",
|
||||
"cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/tainted/qlpack.yml",
|
||||
"*/ql/examples/qlpack.yml",
|
||||
"*/upgrades/qlpack.yml",
|
||||
"javascript/ql/experimental/adaptivethreatmodeling/lib/qlpack.yml",
|
||||
"javascript/ql/experimental/adaptivethreatmodeling/src/qlpack.yml",
|
||||
"misc/legacy-support/*/qlpack.yml",
|
||||
"misc/suite-helpers/qlpack.yml" ] }
|
||||
{
|
||||
"provide": [
|
||||
"*/ql/src/qlpack.yml",
|
||||
"*/ql/lib/qlpack.yml",
|
||||
"*/ql/test/qlpack.yml",
|
||||
"*/ql/examples/qlpack.yml",
|
||||
"*/upgrades/qlpack.yml",
|
||||
"cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/tainted/qlpack.yml",
|
||||
"javascript/ql/experimental/adaptivethreatmodeling/lib/qlpack.yml",
|
||||
"javascript/ql/experimental/adaptivethreatmodeling/src/qlpack.yml",
|
||||
"misc/legacy-support/*/qlpack.yml",
|
||||
"misc/suite-helpers/qlpack.yml",
|
||||
"ruby/ql/consistency-queries/qlpack.yml",
|
||||
"ruby/extractor-pack/codeql-extractor.yml"
|
||||
]
|
||||
}
|
||||
4
.github/actions/fetch-codeql/action.yml
vendored
4
.github/actions/fetch-codeql/action.yml
vendored
@@ -8,7 +8,7 @@ runs:
|
||||
run: |
|
||||
LATEST=$(gh release list --repo https://github.com/github/codeql-cli-binaries | cut -f 1 | grep -v beta | sort --version-sort | tail -1)
|
||||
gh release download --repo https://github.com/github/codeql-cli-binaries --pattern codeql-linux64.zip "$LATEST"
|
||||
unzip -q codeql-linux64.zip
|
||||
echo "${{ github.workspace }}/codeql" >> $GITHUB_PATH
|
||||
unzip -q -d "${RUNNER_TEMP}" codeql-linux64.zip
|
||||
echo "${RUNNER_TEMP}/codeql" >> "${GITHUB_PATH}"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
31
.github/workflows/post-pr-comment.yml
vendored
Normal file
31
.github/workflows/post-pr-comment.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: Post pull-request comment
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Query help preview"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
post_comment:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download artifact
|
||||
run: gh run download "${WORKFLOW_RUN_ID}" --repo "${GITHUB_REPOSITORY}" --name "comment"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }}
|
||||
- run: |
|
||||
PR="$(grep -o '^[0-9]\+$' pr.txt)"
|
||||
PR_HEAD_SHA="$(gh api "/repos/${GITHUB_REPOSITORY}/pulls/${PR}" --jq .head.sha)"
|
||||
# Check that the pull-request head SHA matches the head SHA of the workflow run
|
||||
if [ "${WORKFLOW_RUN_HEAD_SHA}" != "${PR_HEAD_SHA}" ]; then
|
||||
echo "PR head SHA ${PR_HEAD_SHA} does not match workflow_run event SHA ${WORKFLOW_RUN_HEAD_SHA}. Stopping." 1>&2
|
||||
exit 1
|
||||
fi
|
||||
gh pr comment "${PR}" --repo "${GITHUB_REPOSITORY}" -F comment.txt
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
WORKFLOW_RUN_HEAD_SHA: ${{ github.event.workflow_run.head_commit.id }}
|
||||
52
.github/workflows/qhelp-pr-preview.yml
vendored
52
.github/workflows/qhelp-pr-preview.yml
vendored
@@ -1,10 +1,13 @@
|
||||
name: Query help preview
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- 'rc/*'
|
||||
- "rc/*"
|
||||
paths:
|
||||
- "ruby/**/*.qhelp"
|
||||
|
||||
@@ -12,28 +15,49 @@ jobs:
|
||||
qhelp:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo "${{ github.event.number }}" > pr.txt
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: comment
|
||||
path: pr.txt
|
||||
retention-days: 1
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 2
|
||||
persist-credentials: false
|
||||
- uses: ./.github/actions/fetch-codeql
|
||||
- name: Determine changed files
|
||||
id: changes
|
||||
run: |
|
||||
echo -n "::set-output name=qhelp_files::"
|
||||
(git diff --name-only --diff-filter=ACMRT HEAD~1 HEAD | grep .qhelp$ | grep -v .inc.qhelp;
|
||||
git diff --name-only --diff-filter=ACMRT HEAD~1 HEAD | grep .inc.qhelp$ | xargs -d '\n' -rn1 basename | xargs -d '\n' -rn1 git grep -l) |
|
||||
sort -u | xargs -d '\n' -n1 printf "'%s' "
|
||||
|
||||
- uses: ./.github/actions/fetch-codeql
|
||||
(git diff -z --name-only --diff-filter=ACMRT HEAD~1 HEAD | grep -z '.qhelp$' | grep -z -v '.inc.qhelp';
|
||||
git diff -z --name-only --diff-filter=ACMRT HEAD~1 HEAD | grep -z '.inc.qhelp$' | xargs --null -rn1 basename | xargs --null -rn1 git grep -z -l) |
|
||||
grep -z '.qhelp$' | grep -z -v '^-' | sort -z -u > "${RUNNER_TEMP}/paths.txt"
|
||||
|
||||
- name: QHelp preview
|
||||
if: ${{ steps.changes.outputs.qhelp_files }}
|
||||
run: |
|
||||
( echo "QHelp previews:";
|
||||
for path in ${{ steps.changes.outputs.qhelp_files }} ; do
|
||||
EXIT_CODE=0
|
||||
echo "QHelp previews:" > comment.txt
|
||||
while read -r -d $'\0' path; do
|
||||
if [ ! -f "${path}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "<details> <summary>${path}</summary>"
|
||||
echo
|
||||
codeql generate query-help --format=markdown ${path}
|
||||
codeql generate query-help --format=markdown -- "./${path}" 2> errors.txt || EXIT_CODE="$?"
|
||||
if [ -s errors.txt ]; then
|
||||
echo "# errors/warnings:"
|
||||
echo '```'
|
||||
cat errors.txt
|
||||
cat errors.txt 1>&2
|
||||
echo '```'
|
||||
fi
|
||||
echo "</details>"
|
||||
done) | gh pr comment "${{ github.event.pull_request.number }}" -F -
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
done < "${RUNNER_TEMP}/paths.txt" >> comment.txt
|
||||
exit "${EXIT_CODE}"
|
||||
|
||||
- if: always()
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: comment
|
||||
path: comment.txt
|
||||
retention-days: 1
|
||||
|
||||
20
.github/workflows/ruby-build.yml
vendored
20
.github/workflows/ruby-build.yml
vendored
@@ -3,16 +3,18 @@ name: "Ruby: Build"
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'ruby/**'
|
||||
- "ruby/**"
|
||||
- .github/workflows/ruby-build.yml
|
||||
branches:
|
||||
- main
|
||||
- 'rc/*'
|
||||
- "rc/*"
|
||||
pull_request:
|
||||
paths:
|
||||
- 'ruby/**'
|
||||
- "ruby/**"
|
||||
- .github/workflows/ruby-build.yml
|
||||
branches:
|
||||
- main
|
||||
- 'rc/*'
|
||||
- "rc/*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
@@ -100,16 +102,6 @@ jobs:
|
||||
PACK_FOLDER=$(readlink -f target/packs/codeql/ruby-queries/*)
|
||||
codeql/codeql generate query-help --format=sarifv2.1.0 --output="${PACK_FOLDER}/rules.sarif" ql/src
|
||||
(cd ql/src; find queries \( -name '*.qhelp' -o -name '*.rb' -o -name '*.erb' \) -exec bash -c 'mkdir -p "'"${PACK_FOLDER}"'/$(dirname "{}")"' \; -exec cp "{}" "${PACK_FOLDER}/{}" \;)
|
||||
- name: Compile with previous CodeQL versions
|
||||
run: |
|
||||
for version in $(gh release list --repo https://github.com/github/codeql-cli-binaries | cut -f 1 | sort --version-sort | tail -3 | head -2); do
|
||||
rm -f codeql-linux64.zip
|
||||
gh release download --repo https://github.com/github/codeql-cli-binaries --pattern codeql-linux64.zip "$version"
|
||||
rm -rf codeql; unzip -q codeql-linux64.zip
|
||||
codeql/codeql query compile target/packs/*
|
||||
done
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: codeql-ruby-queries
|
||||
|
||||
8
.github/workflows/ruby-dataset-measure.yml
vendored
8
.github/workflows/ruby-dataset-measure.yml
vendored
@@ -4,15 +4,17 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- 'rc/*'
|
||||
- "rc/*"
|
||||
paths:
|
||||
- ruby/ql/lib/ruby.dbscheme
|
||||
- .github/workflows/ruby-dataset-measure.yml
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- 'rc/*'
|
||||
- "rc/*"
|
||||
paths:
|
||||
- ruby/ql/lib/ruby.dbscheme
|
||||
- .github/workflows/ruby-dataset-measure.yml
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -39,7 +41,7 @@ jobs:
|
||||
- name: Create database
|
||||
run: |
|
||||
codeql database create \
|
||||
--search-path "${{ github.workspace }}/ruby" \
|
||||
--search-path "${{ github.workspace }}/ruby/extractor-pack" \
|
||||
--threads 4 \
|
||||
--language ruby --source-root "${{ github.workspace }}/repo" \
|
||||
"${{ runner.temp }}/database"
|
||||
|
||||
16
.github/workflows/ruby-qltest.yml
vendored
16
.github/workflows/ruby-qltest.yml
vendored
@@ -3,16 +3,18 @@ name: "Ruby: Run QL Tests"
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'ruby/**'
|
||||
- "ruby/**"
|
||||
- .github/workflows/ruby-qltest.yml
|
||||
branches:
|
||||
- main
|
||||
- 'rc/*'
|
||||
- "rc/*"
|
||||
pull_request:
|
||||
paths:
|
||||
- 'ruby/**'
|
||||
- "ruby/**"
|
||||
- .github/workflows/ruby-qltest.yml
|
||||
branches:
|
||||
- main
|
||||
- 'rc/*'
|
||||
- "rc/*"
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -30,19 +32,19 @@ jobs:
|
||||
- uses: ./ruby/actions/create-extractor-pack
|
||||
- name: Run QL tests
|
||||
run: |
|
||||
codeql test run --check-databases --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition --search-path "${{ github.workspace }}/ruby" --additional-packs "${{ github.workspace }}" --consistency-queries ql/consistency-queries ql/test
|
||||
codeql test run --search-path "${{ github.workspace }}/ruby/extractor-pack" --check-databases --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition --consistency-queries ql/consistency-queries ql/test
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
- name: Check QL formatting
|
||||
run: find ql "(" -name "*.ql" -or -name "*.qll" ")" -print0 | xargs -0 codeql query format --check-only
|
||||
- name: Check QL compilation
|
||||
run: |
|
||||
codeql query compile --check-only --threads=4 --warnings=error --search-path "${{ github.workspace }}/ruby" --additional-packs "${{ github.workspace }}" "ql/src" "ql/examples"
|
||||
codeql query compile --check-only --threads=4 --warnings=error "ql/src" "ql/examples"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
- name: Check DB upgrade scripts
|
||||
run: |
|
||||
echo >empty.trap
|
||||
codeql dataset import -S ql/lib/upgrades/initial/ruby.dbscheme testdb empty.trap
|
||||
codeql dataset upgrade testdb --additional-packs ql/lib/upgrades
|
||||
codeql dataset upgrade testdb --additional-packs ql/lib
|
||||
diff -q testdb/ruby.dbscheme ql/lib/ruby.dbscheme
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -27,3 +27,6 @@ csharp/extractor/Semmle.Extraction.CSharp.Driver/Properties/launchSettings.json
|
||||
|
||||
# Avoid committing cached package components
|
||||
.codeql
|
||||
|
||||
# Compiled class file
|
||||
*.class
|
||||
@@ -11,13 +11,14 @@ If you have an idea for a query that you would like to share with other CodeQL u
|
||||
|
||||
1. **Directory structure**
|
||||
|
||||
There are five language-specific query directories in this repository:
|
||||
There are six language-specific query directories in this repository:
|
||||
|
||||
* C/C++: `cpp/ql/src`
|
||||
* C#: `csharp/ql/src`
|
||||
* Java: `java/ql/src`
|
||||
* JavaScript: `javascript/ql/src`
|
||||
* Python: `python/ql/src`
|
||||
* Ruby: `ruby/ql/src`
|
||||
|
||||
Each language-specific directory contains further subdirectories that group queries based on their `@tags` or purpose.
|
||||
- Experimental queries and libraries are stored in the `experimental` subdirectory within each language-specific directory in the [CodeQL repository](https://github.com/github/codeql). For example, experimental Java queries and libraries are stored in `java/ql/src/experimental` and any corresponding tests in `java/ql/test/experimental`.
|
||||
|
||||
51
benjamin-button.md
Normal file
51
benjamin-button.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# benjamin-buttons.md
|
||||
|
||||
This file describes the changes that have been applied to
|
||||
the library to make it behave as if it was younger.
|
||||
|
||||
## TaintedPath.ql
|
||||
|
||||
Sinks added between 2020-01-01 and 2020-10-06 have been removed. Found by looking at:
|
||||
|
||||
- the commit titles of https://github.com/github/codeql/commits/main/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected
|
||||
- the PR titles of https://github.com/github/codeql/pulls?page=2&q=is%3Apr+label%3AJS+is%3Aclosed+sink
|
||||
|
||||
Sinks added between 2018-08-02 and 2020-01-01 have been removed. Found by looking at:
|
||||
|
||||
- the commit titles of https://github.com/github/codeql/commits/main/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected
|
||||
- the PR titles of https://github.com/github/codeql/pulls?page=2&q=is%3Apr+label%3AJS+is%3Aclosed+sink
|
||||
- the PR titles of https://github.com/github/codeql/pulls?page=2&q=is%3Apr+label%3AJS+is%3Aclosed+pathinjection
|
||||
- the PR titles of https://github.com/github/codeql/pulls?page=2&q=is%3Apr+label%3AJS+is%3Aclosed+tainted-path
|
||||
|
||||
Sinks from the "graceful-fs" and "fs-extra" (added before the open-sourcing squash).
|
||||
|
||||
## Xss.ql
|
||||
|
||||
Sinks added between 2020-01-01 and 2020-10-06 have been removed. Found by looking at:
|
||||
|
||||
- the commit titles of https://github.com/github/codeql/commits/main/javascript/ql/test/query-tests/Security/CWE-079/Xss.expected
|
||||
- the PR titles of https://github.com/github/codeql/pulls?page=2&q=is%3Apr+label%3AJS+is%3Aclosed+sink
|
||||
|
||||
- recursive type tracking for `jQuery::dollar`, `DOM::domValueRef`.
|
||||
|
||||
## SqlInjection.ql
|
||||
|
||||
Sinks added between 2020-01-01 and 2020-10-06 have been removed. Found by looking at:
|
||||
|
||||
- the commit titles of https://github.com/github/codeql/commits/main/javascript/ql/test/query-tests/Security/CWE-089
|
||||
- the PR titles of https://github.com/github/codeql/pulls?page=2&q=is%3Apr+label%3AJS+is%3Aclosed+sink
|
||||
|
||||
Sinks added between 2018-08-02 and 2020-01-01 have been removed. Found by looking at:
|
||||
|
||||
- the commit titles of https://github.com/github/codeql/commits/main/javascript/ql/test/query-tests/Security/CWE-089
|
||||
- the PR titles of https://github.com/github/codeql/pulls?page=2&q=is%3Apr+label%3AJS+is%3Aclosed+sink
|
||||
- the PR titles of https://github.com/github/codeql/pulls?page=2&q=is%3Apr+label%3AJS+is%3Aclosed+sql
|
||||
|
||||
TypeTracking in SQL.qll (added before the open-sourcing squash)
|
||||
|
||||
The model of `mssql` and `sequelize` (added before the open-sourcing squash)
|
||||
|
||||
## PseudoProperties
|
||||
|
||||
Pseudo-properties (`$name$`) used in type-tracking and global dataflow configurations have been disabled.
|
||||
Found by searching for `"\$.*\$"`.
|
||||
@@ -449,7 +449,8 @@
|
||||
"csharp/ql/lib/semmle/code/csharp/controlflow/internal/pressa/SsaImplCommon.qll",
|
||||
"csharp/ql/lib/semmle/code/csharp/dataflow/internal/basessa/SsaImplCommon.qll",
|
||||
"csharp/ql/lib/semmle/code/cil/internal/SsaImplCommon.qll",
|
||||
"ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplCommon.qll"
|
||||
"ruby/ql/lib/codeql/ruby/dataflow/internal/SsaImplCommon.qll",
|
||||
"cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaImplCommon.qll"
|
||||
],
|
||||
"CryptoAlgorithms Python/JS": [
|
||||
"javascript/ql/lib/semmle/javascript/security/CryptoAlgorithms.qll",
|
||||
@@ -459,9 +460,10 @@
|
||||
"javascript/ql/lib/semmle/javascript/security/internal/SensitiveDataHeuristics.qll",
|
||||
"python/ql/lib/semmle/python/security/internal/SensitiveDataHeuristics.qll"
|
||||
],
|
||||
"ReDoS Util Python/JS": [
|
||||
"ReDoS Util Python/JS/Ruby": [
|
||||
"javascript/ql/lib/semmle/javascript/security/performance/ReDoSUtil.qll",
|
||||
"python/ql/lib/semmle/python/security/performance/ReDoSUtil.qll"
|
||||
"python/ql/lib/semmle/python/security/performance/ReDoSUtil.qll",
|
||||
"ruby/ql/lib/codeql/ruby/security/performance/ReDoSUtil.qll"
|
||||
],
|
||||
"ReDoS Exponential Python/JS": [
|
||||
"javascript/ql/lib/semmle/javascript/security/performance/ExponentialBackTracking.qll",
|
||||
@@ -470,7 +472,12 @@
|
||||
"ReDoS Polynomial Python/JS": [
|
||||
"javascript/ql/lib/semmle/javascript/security/performance/SuperlinearBackTracking.qll",
|
||||
"python/ql/lib/semmle/python/security/performance/SuperlinearBackTracking.qll",
|
||||
"ruby/ql/lib/codeql/ruby/regexp/SuperlinearBackTracking.qll"
|
||||
"ruby/ql/lib/codeql/ruby/security/performance/SuperlinearBackTracking.qll"
|
||||
],
|
||||
"BadTagFilterQuery Python/JS/Ruby": [
|
||||
"javascript/ql/lib/semmle/javascript/security/BadTagFilterQuery.qll",
|
||||
"python/ql/lib/semmle/python/security/BadTagFilterQuery.qll",
|
||||
"ruby/ql/lib/codeql/ruby/security/BadTagFilterQuery.qll"
|
||||
],
|
||||
"CFG": [
|
||||
"csharp/ql/lib/semmle/code/csharp/controlflow/internal/ControlFlowGraphImplShared.qll",
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
lgtm,codescanning
|
||||
* The QL library `semmle.code.cpp.commons.Exclusions` now contains a predicate
|
||||
`isFromSystemMacroDefinition` for identifying code that originates from a
|
||||
macro outside the project being analyzed.
|
||||
2
cpp/change-notes/2021-11-09-use-of-http.md
Normal file
2
cpp/change-notes/2021-11-09-use-of-http.md
Normal file
@@ -0,0 +1,2 @@
|
||||
lgtm,codescanning
|
||||
* A new query `cpp/non-https-url` has been added for C/C++. The query flags uses of `http` URLs that might be better replaced with `https`.
|
||||
@@ -237,7 +237,7 @@ class Class extends UserType {
|
||||
exists(ClassDerivation cd | cd.getBaseClass() = base |
|
||||
result =
|
||||
this.accessOfBaseMemberMulti(cd.getDerivedClass(),
|
||||
fieldInBase.accessInDirectDerived(cd.getASpecifier().(AccessSpecifier)))
|
||||
fieldInBase.accessInDirectDerived(cd.getASpecifier()))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -261,8 +261,7 @@ class Class extends UserType {
|
||||
* includes the case of `base` = `this`.
|
||||
*/
|
||||
AccessSpecifier accessOfBaseMember(Declaration member) {
|
||||
result =
|
||||
this.accessOfBaseMember(member.getDeclaringType(), member.getASpecifier().(AccessSpecifier))
|
||||
result = this.accessOfBaseMember(member.getDeclaringType(), member.getASpecifier())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -319,7 +318,7 @@ class Class extends UserType {
|
||||
exists(Type t | t = this.getAFieldSubobjectType().getUnspecifiedType() |
|
||||
// Note: Overload resolution is not implemented -- all copy
|
||||
// constructors are considered equal.
|
||||
this.cannotAccessCopyConstructorOnAny(t.(Class))
|
||||
this.cannotAccessCopyConstructorOnAny(t)
|
||||
)
|
||||
or
|
||||
// - T has direct or virtual base class that cannot be copied (has deleted,
|
||||
@@ -392,7 +391,7 @@ class Class extends UserType {
|
||||
exists(Type t | t = this.getAFieldSubobjectType().getUnspecifiedType() |
|
||||
// Note: Overload resolution is not implemented -- all copy assignment
|
||||
// operators are considered equal.
|
||||
this.cannotAccessCopyAssignmentOperatorOnAny(t.(Class))
|
||||
this.cannotAccessCopyAssignmentOperatorOnAny(t)
|
||||
)
|
||||
or
|
||||
exists(Class c | c = this.getADirectOrVirtualBase() |
|
||||
|
||||
@@ -490,8 +490,7 @@ class AccessHolder extends Declaration, TAccessHolder {
|
||||
*/
|
||||
pragma[inline]
|
||||
predicate canAccessMember(Declaration member, Class derived) {
|
||||
this.couldAccessMember(member.getDeclaringType(), member.getASpecifier().(AccessSpecifier),
|
||||
derived)
|
||||
this.couldAccessMember(member.getDeclaringType(), member.getASpecifier(), derived)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -275,7 +275,7 @@ private predicate dependsOnDeclarationEntry(Element src, DeclarationEntry dest)
|
||||
dependsOnTransitive(src, mid) and
|
||||
not mid instanceof Type and
|
||||
not mid instanceof EnumConstant and
|
||||
getDeclarationEntries(mid, dest.(DeclarationEntry)) and
|
||||
getDeclarationEntries(mid, dest) and
|
||||
not dest instanceof TypeDeclarationEntry
|
||||
)
|
||||
or
|
||||
@@ -283,9 +283,9 @@ private predicate dependsOnDeclarationEntry(Element src, DeclarationEntry dest)
|
||||
// dependency from a Type / Variable / Function use -> any (visible) definition
|
||||
dependsOnTransitive(src, mid) and
|
||||
not mid instanceof EnumConstant and
|
||||
getDeclarationEntries(mid, dest.(DeclarationEntry)) and
|
||||
getDeclarationEntries(mid, dest) and
|
||||
// must be definition
|
||||
dest.(DeclarationEntry).isDefinition()
|
||||
dest.isDefinition()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -307,7 +307,7 @@ private predicate dependsOnFull(DependsSource src, Symbol dest, int category) {
|
||||
// dependency from a Variable / Function use -> non-visible definition (link time)
|
||||
dependsOnTransitive(src, mid) and
|
||||
not mid instanceof EnumConstant and
|
||||
getDeclarationEntries(mid, dest.(DeclarationEntry)) and
|
||||
getDeclarationEntries(mid, dest) and
|
||||
not dest instanceof TypeDeclarationEntry and
|
||||
// must be definition
|
||||
dest.(DeclarationEntry).isDefinition() and
|
||||
|
||||
@@ -81,8 +81,8 @@ predicate functionContainsPreprocCode(Function f) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e` is completely or partially from a macro definition, as opposed
|
||||
* to being passed in as an argument.
|
||||
* Holds if `e` is completely or partially from a macro invocation `mi`, as
|
||||
* opposed to being passed in as an argument.
|
||||
*
|
||||
* In the following example, the call to `f` is from a macro definition,
|
||||
* while `y`, `+`, `1`, and `;` are not. This assumes that no identifier apart
|
||||
@@ -93,8 +93,8 @@ predicate functionContainsPreprocCode(Function f) {
|
||||
* M(y + 1);
|
||||
* ```
|
||||
*/
|
||||
predicate isFromMacroDefinition(Element e) {
|
||||
exists(MacroInvocation mi, Location eLocation, Location miLocation |
|
||||
private predicate isFromMacroInvocation(Element e, MacroInvocation mi) {
|
||||
exists(Location eLocation, Location miLocation |
|
||||
mi.getAnExpandedElement() = e and
|
||||
eLocation = e.getLocation() and
|
||||
miLocation = mi.getLocation() and
|
||||
@@ -109,3 +109,36 @@ predicate isFromMacroDefinition(Element e) {
|
||||
eLocation.getEndColumn() >= miLocation.getEndColumn()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `e` is completely or partially from a macro definition, as opposed
|
||||
* to being passed in as an argument.
|
||||
*
|
||||
* In the following example, the call to `f` is from a macro definition,
|
||||
* while `y`, `+`, `1`, and `;` are not. This assumes that no identifier apart
|
||||
* from `M` refers to a macro.
|
||||
* ```
|
||||
* #define M(x) f(x)
|
||||
* ...
|
||||
* M(y + 1);
|
||||
* ```
|
||||
*/
|
||||
predicate isFromMacroDefinition(Element e) { isFromMacroInvocation(e, _) }
|
||||
|
||||
/**
|
||||
* Holds if `e` is completely or partially from a _system macro_ definition, as
|
||||
* opposed to being passed in as an argument. A system macro is a macro whose
|
||||
* definition is outside the source directory of the database.
|
||||
*
|
||||
* If the system macro is invoked through a non-system macro, then this
|
||||
* predicate does not hold.
|
||||
*
|
||||
* See also `isFromMacroDefinition`.
|
||||
*/
|
||||
predicate isFromSystemMacroDefinition(Element e) {
|
||||
exists(MacroInvocation mi |
|
||||
isFromMacroInvocation(e, mi) and
|
||||
// Has no relative path in the database, meaning it's a system file.
|
||||
not exists(mi.getMacro().getFile().getRelativePath())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,17 +3,33 @@ private import semmle.code.cpp.models.interfaces.ArrayFunction
|
||||
private import semmle.code.cpp.models.implementations.Strcat
|
||||
import semmle.code.cpp.dataflow.DataFlow
|
||||
|
||||
private predicate mayAddNullTerminatorHelper(Expr e, VariableAccess va, Expr e0) {
|
||||
exists(StackVariable v0, Expr val |
|
||||
exprDefinition(v0, e, val) and
|
||||
val.getAChild*() = va and
|
||||
mayAddNullTerminator(e0, v0.getAnAccess())
|
||||
/**
|
||||
* Holds if the expression `e` assigns something including `va` to a
|
||||
* stack variable `v0`.
|
||||
*/
|
||||
private predicate mayAddNullTerminatorHelper(Expr e, VariableAccess va, StackVariable v0) {
|
||||
exists(Expr val |
|
||||
exprDefinition(v0, e, val) and // `e` is `v0 := val`
|
||||
val.getAChild*() = va
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[n1, n2]
|
||||
private predicate controlFlowNodeSuccessorTransitive(ControlFlowNode n1, ControlFlowNode n2) {
|
||||
exists(BasicBlock bb1, int pos1, BasicBlock bb2, int pos2 |
|
||||
pragma[only_bind_into](bb1).getNode(pos1) = n1 and
|
||||
pragma[only_bind_into](bb2).getNode(pos2) = n2 and
|
||||
(
|
||||
bb1 = bb2 and pos1 < pos2
|
||||
or
|
||||
bb1.getASuccessor+() = bb2
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the expression `e` may add a null terminator to the string in
|
||||
* variable `v`.
|
||||
* Holds if the expression `e` may add a null terminator to the string
|
||||
* accessed by `va`.
|
||||
*/
|
||||
predicate mayAddNullTerminator(Expr e, VariableAccess va) {
|
||||
// Assignment: dereferencing or array access
|
||||
@@ -30,14 +46,10 @@ predicate mayAddNullTerminator(Expr e, VariableAccess va) {
|
||||
)
|
||||
or
|
||||
// Assignment to another stack variable
|
||||
exists(Expr e0, BasicBlock bb, int pos, BasicBlock bb0, int pos0 |
|
||||
mayAddNullTerminatorHelper(e, va, e0) and
|
||||
bb.getNode(pos) = e and
|
||||
bb0.getNode(pos0) = e0
|
||||
|
|
||||
bb = bb0 and pos < pos0
|
||||
or
|
||||
bb.getASuccessor+() = bb0
|
||||
exists(StackVariable v0, Expr e0 |
|
||||
mayAddNullTerminatorHelper(e, va, v0) and
|
||||
mayAddNullTerminator(pragma[only_bind_into](e0), pragma[only_bind_into](v0.getAnAccess())) and
|
||||
controlFlowNodeSuccessorTransitive(e, e0)
|
||||
)
|
||||
or
|
||||
// Assignment to non-stack variable
|
||||
@@ -119,14 +131,9 @@ predicate variableMustBeNullTerminated(VariableAccess va) {
|
||||
variableMustBeNullTerminated(use) and
|
||||
// Simplified: check that `p` may not be null terminated on *any*
|
||||
// path to `use` (including the one found via `parameterUsePair`)
|
||||
not exists(Expr e, BasicBlock bb1, int pos1, BasicBlock bb2, int pos2 |
|
||||
mayAddNullTerminator(e, p.getAnAccess()) and
|
||||
bb1.getNode(pos1) = e and
|
||||
bb2.getNode(pos2) = use
|
||||
|
|
||||
bb1 = bb2 and pos1 < pos2
|
||||
or
|
||||
bb1.getASuccessor+() = bb2
|
||||
not exists(Expr e |
|
||||
mayAddNullTerminator(pragma[only_bind_into](e), p.getAnAccess()) and
|
||||
controlFlowNodeSuccessorTransitive(e, use)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -6,6 +6,8 @@ import semmle.code.cpp.Type
|
||||
import semmle.code.cpp.commons.CommonType
|
||||
import semmle.code.cpp.commons.StringAnalysis
|
||||
import semmle.code.cpp.models.interfaces.FormattingFunction
|
||||
private import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
|
||||
private import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils
|
||||
|
||||
class PrintfFormatAttribute extends FormatAttribute {
|
||||
PrintfFormatAttribute() { this.getArchetype() = ["printf", "__printf__"] }
|
||||
@@ -175,9 +177,7 @@ class FormattingFunctionCall extends Expr {
|
||||
/**
|
||||
* Gets the index at which the format string occurs in the argument list.
|
||||
*/
|
||||
int getFormatParameterIndex() {
|
||||
result = this.getTarget().(FormattingFunction).getFormatParameterIndex()
|
||||
}
|
||||
int getFormatParameterIndex() { result = this.getTarget().getFormatParameterIndex() }
|
||||
|
||||
/**
|
||||
* Gets the format expression used in this call.
|
||||
@@ -191,7 +191,7 @@ class FormattingFunctionCall extends Expr {
|
||||
exists(int i |
|
||||
result = this.getArgument(i) and
|
||||
n >= 0 and
|
||||
n = i - this.getTarget().(FormattingFunction).getFirstFormatArgumentIndex()
|
||||
n = i - this.getTarget().getFirstFormatArgumentIndex()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -251,7 +251,7 @@ class FormattingFunctionCall extends Expr {
|
||||
int getNumFormatArgument() {
|
||||
result = count(this.getFormatArgument(_)) and
|
||||
// format arguments must be known
|
||||
exists(this.getTarget().(FormattingFunction).getFirstFormatArgumentIndex())
|
||||
exists(this.getTarget().getFirstFormatArgumentIndex())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,6 +270,18 @@ class FormattingFunctionCall extends Expr {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of digits required to represent the integer represented by `f`.
|
||||
*
|
||||
* `f` is assumed to be nonnegative.
|
||||
*/
|
||||
bindingset[f]
|
||||
private int lengthInBase10(float f) {
|
||||
f = 0 and result = 1
|
||||
or
|
||||
result = f.log10().floor() + 1
|
||||
}
|
||||
|
||||
/**
|
||||
* A class to represent format strings that occur as arguments to invocations of formatting functions.
|
||||
*/
|
||||
@@ -289,35 +301,27 @@ class FormatLiteral extends Literal {
|
||||
* a `char *` (either way, `%S` will have the opposite meaning).
|
||||
* DEPRECATED: Use getDefaultCharType() instead.
|
||||
*/
|
||||
deprecated predicate isWideCharDefault() {
|
||||
this.getUse().getTarget().(FormattingFunction).isWideCharDefault()
|
||||
}
|
||||
deprecated predicate isWideCharDefault() { this.getUse().getTarget().isWideCharDefault() }
|
||||
|
||||
/**
|
||||
* Gets the default character type expected for `%s` by this format literal. Typically
|
||||
* `char` or `wchar_t`.
|
||||
*/
|
||||
Type getDefaultCharType() {
|
||||
result = this.getUse().getTarget().(FormattingFunction).getDefaultCharType()
|
||||
}
|
||||
Type getDefaultCharType() { result = this.getUse().getTarget().getDefaultCharType() }
|
||||
|
||||
/**
|
||||
* Gets the non-default character type expected for `%S` by this format literal. Typically
|
||||
* `wchar_t` or `char`. On some snapshots there may be multiple results where we can't tell
|
||||
* which is correct for a particular function.
|
||||
*/
|
||||
Type getNonDefaultCharType() {
|
||||
result = this.getUse().getTarget().(FormattingFunction).getNonDefaultCharType()
|
||||
}
|
||||
Type getNonDefaultCharType() { result = this.getUse().getTarget().getNonDefaultCharType() }
|
||||
|
||||
/**
|
||||
* Gets the wide character type for this format literal. This is usually `wchar_t`. On some
|
||||
* snapshots there may be multiple results where we can't tell which is correct for a
|
||||
* particular function.
|
||||
*/
|
||||
Type getWideCharType() {
|
||||
result = this.getUse().getTarget().(FormattingFunction).getWideCharType()
|
||||
}
|
||||
Type getWideCharType() { result = this.getUse().getTarget().getWideCharType() }
|
||||
|
||||
/**
|
||||
* Holds if this `FormatLiteral` is in a context that supports
|
||||
@@ -896,7 +900,7 @@ class FormatLiteral extends Literal {
|
||||
exists(string len, string conv |
|
||||
this.parseConvSpec(n, _, _, _, _, _, len, conv) and
|
||||
(len != "l" and len != "w" and len != "h") and
|
||||
this.getUse().getTarget().(FormattingFunction).getFormatCharType().getSize() > 1 and // wide function
|
||||
this.getUse().getTarget().getFormatCharType().getSize() > 1 and // wide function
|
||||
(
|
||||
conv = "c" and
|
||||
result = this.getNonDefaultCharType()
|
||||
@@ -1056,39 +1060,63 @@ class FormatLiteral extends Literal {
|
||||
or
|
||||
this.getConversionChar(n).toLowerCase() = ["d", "i"] and
|
||||
// e.g. -2^31 = "-2147483648"
|
||||
exists(int sizeBits |
|
||||
sizeBits =
|
||||
min(int bits |
|
||||
bits = this.getIntegralDisplayType(n).getSize() * 8
|
||||
or
|
||||
exists(IntegralType t |
|
||||
t = this.getUse().getConversionArgument(n).getType().getUnderlyingType()
|
||||
|
|
||||
t.isSigned() and bits = t.getSize() * 8
|
||||
)
|
||||
) and
|
||||
len = 1 + ((sizeBits - 1) / 10.0.log2()).ceil()
|
||||
// this calculation is as %u (below) only we take out the sign bit (- 1) and allow a whole
|
||||
// character for it to be expressed as '-'.
|
||||
)
|
||||
len =
|
||||
min(float cand |
|
||||
// The first case handles length sub-specifiers
|
||||
// Subtract one in the exponent because one bit is for the sign.
|
||||
// Add 1 to account for the possible sign in the output.
|
||||
cand = 1 + lengthInBase10(2.pow(this.getIntegralDisplayType(n).getSize() * 8 - 1))
|
||||
or
|
||||
// The second case uses range analysis to deduce a length that's shorter than the length
|
||||
// of the number -2^31.
|
||||
exists(Expr arg, float lower, float upper |
|
||||
arg = this.getUse().getConversionArgument(n) and
|
||||
lower = lowerBound(arg.getFullyConverted()) and
|
||||
upper = upperBound(arg.getFullyConverted())
|
||||
|
|
||||
cand =
|
||||
max(int cand0 |
|
||||
// Include the sign bit in the length if it can be negative
|
||||
(
|
||||
if lower < 0
|
||||
then cand0 = 1 + lengthInBase10(lower.abs())
|
||||
else cand0 = lengthInBase10(lower)
|
||||
)
|
||||
or
|
||||
(
|
||||
if upper < 0
|
||||
then cand0 = 1 + lengthInBase10(upper.abs())
|
||||
else cand0 = lengthInBase10(upper)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
or
|
||||
this.getConversionChar(n).toLowerCase() = "u" and
|
||||
// e.g. 2^32 - 1 = "4294967295"
|
||||
exists(int sizeBits |
|
||||
sizeBits =
|
||||
min(int bits |
|
||||
bits = this.getIntegralDisplayType(n).getSize() * 8
|
||||
or
|
||||
exists(IntegralType t |
|
||||
t = this.getUse().getConversionArgument(n).getType().getUnderlyingType()
|
||||
|
|
||||
t.isUnsigned() and bits = t.getSize() * 8
|
||||
)
|
||||
) and
|
||||
len = (sizeBits / 10.0.log2()).ceil()
|
||||
// convert the size from bits to decimal characters, and round up as you can't have
|
||||
// fractional characters (10.0.log2() is the number of bits expressed per decimal character)
|
||||
)
|
||||
len =
|
||||
min(float cand |
|
||||
// The first case handles length sub-specifiers
|
||||
cand = 2.pow(this.getIntegralDisplayType(n).getSize() * 8)
|
||||
or
|
||||
// The second case uses range analysis to deduce a length that's shorter than
|
||||
// the length of the number 2^31 - 1.
|
||||
exists(Expr arg, float lower |
|
||||
arg = this.getUse().getConversionArgument(n) and
|
||||
lower = lowerBound(arg.getFullyConverted())
|
||||
|
|
||||
cand =
|
||||
max(float cand0 |
|
||||
// If lower can be negative we use `(unsigned)-1` as the candidate value.
|
||||
lower < 0 and
|
||||
cand0 = 2.pow(any(IntType t | t.isUnsigned()).getSize() * 8)
|
||||
or
|
||||
cand0 = upperBound(arg.getFullyConverted())
|
||||
)
|
||||
)
|
||||
|
|
||||
lengthInBase10(cand)
|
||||
)
|
||||
or
|
||||
this.getConversionChar(n).toLowerCase() = "x" and
|
||||
// e.g. "12345678"
|
||||
|
||||
@@ -25,7 +25,7 @@ predicate definitionUsePair(SemanticStackVariable var, Expr def, Expr use) {
|
||||
* Holds if the definition `def` of some stack variable can reach `node`, which
|
||||
* is a definition or use, without crossing definitions of the same variable.
|
||||
*/
|
||||
predicate definitionReaches(Expr def, Expr node) { def.(Def).reaches(true, _, node.(DefOrUse)) }
|
||||
predicate definitionReaches(Expr def, Expr node) { def.(Def).reaches(true, _, node) }
|
||||
|
||||
private predicate hasAddressOfAccess(SemanticStackVariable var) {
|
||||
var.getAnAccess().isAddressOfAccessNonConst()
|
||||
|
||||
@@ -62,7 +62,7 @@ class SsaDefinition extends ControlFlowNodeBase {
|
||||
BasicBlock getBasicBlock() { result.contains(this.getDefinition()) }
|
||||
|
||||
/** Holds if this definition is a phi node for variable `v`. */
|
||||
predicate isPhiNode(StackVariable v) { exists(StandardSSA x | x.phi_node(v, this.(BasicBlock))) }
|
||||
predicate isPhiNode(StackVariable v) { exists(StandardSSA x | x.phi_node(v, this)) }
|
||||
|
||||
/** Gets the location of this definition. */
|
||||
Location getLocation() { result = this.(ControlFlowNode).getLocation() }
|
||||
|
||||
@@ -292,7 +292,7 @@ library class SSAHelper extends int {
|
||||
*/
|
||||
cached
|
||||
string toString(ControlFlowNode node, StackVariable v) {
|
||||
if phi_node(v, node.(BasicBlock))
|
||||
if phi_node(v, node)
|
||||
then result = "SSA phi(" + v.getName() + ")"
|
||||
else (
|
||||
ssa_defn(v, node, _, _) and result = "SSA def(" + v.getName() + ")"
|
||||
|
||||
@@ -231,7 +231,7 @@ private class PostOrderInitializer extends Initializer {
|
||||
or
|
||||
this.getDeclaration() = for.getRangeVariable()
|
||||
or
|
||||
this.getDeclaration() = for.getBeginEndDeclaration().(DeclStmt).getADeclaration()
|
||||
this.getDeclaration() = for.getBeginEndDeclaration().getADeclaration()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1143,7 +1143,7 @@ private class ExceptionSource extends Node {
|
||||
this.reachesParent(mid) and
|
||||
not mid = any(TryStmt try).getStmt() and
|
||||
not mid = any(MicrosoftTryStmt try).getStmt() and
|
||||
parent = mid.(Node).getParentNode()
|
||||
parent = mid.getParentNode()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -484,7 +484,7 @@ library class ExprEvaluator extends int {
|
||||
this.interestingInternal(e, req, true) and
|
||||
(
|
||||
result = req.(CompileTimeConstantInt).getIntValue() or
|
||||
result = this.getCompoundValue(e, req.(CompileTimeVariableExpr))
|
||||
result = this.getCompoundValue(e, req)
|
||||
) and
|
||||
(
|
||||
req.getUnderlyingType().(IntegralType).isSigned() or
|
||||
@@ -611,7 +611,7 @@ library class ExprEvaluator extends int {
|
||||
or
|
||||
exists(AssignExpr req | req = val | result = this.getValueInternal(e, req.getRValue()))
|
||||
or
|
||||
result = this.getVariableValue(e, val.(VariableAccess))
|
||||
result = this.getVariableValue(e, val)
|
||||
or
|
||||
exists(FunctionCall call | call = val and not callWithMultipleTargets(call) |
|
||||
result = this.getFunctionValue(call.getTarget())
|
||||
@@ -663,7 +663,7 @@ library class ExprEvaluator extends int {
|
||||
this.interestingInternal(_, req, false) and
|
||||
(
|
||||
result = req.(CompileTimeConstantInt).getIntValue() or
|
||||
result = this.getCompoundValueNonSubExpr(req.(CompileTimeVariableExpr))
|
||||
result = this.getCompoundValueNonSubExpr(req)
|
||||
) and
|
||||
(
|
||||
req.getUnderlyingType().(IntegralType).isSigned() or
|
||||
@@ -787,7 +787,7 @@ library class ExprEvaluator extends int {
|
||||
or
|
||||
exists(AssignExpr req | req = val | result = this.getValueInternalNonSubExpr(req.getRValue()))
|
||||
or
|
||||
result = this.getVariableValueNonSubExpr(val.(VariableAccess))
|
||||
result = this.getVariableValueNonSubExpr(val)
|
||||
or
|
||||
exists(FunctionCall call | call = val and not callWithMultipleTargets(call) |
|
||||
result = this.getFunctionValue(call.getTarget())
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
import DataFlowImplCommonPublic
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural data flow analysis. This defines
|
||||
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*/
|
||||
FlowFeature getAFeature() { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
@@ -349,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -365,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -401,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
|
||||
*/
|
||||
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
|
||||
|
||||
private predicate hasSourceCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSourceCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private predicate hasSinkCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSinkCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private module Stage1 {
|
||||
class ApApprox = Unit;
|
||||
|
||||
@@ -421,7 +454,7 @@ private module Stage1 {
|
||||
not fullBarrier(node, config) and
|
||||
(
|
||||
sourceNode(node, config) and
|
||||
cc = false
|
||||
if hasSourceCallCtx(config) then cc = true else cc = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
fwdFlow(mid, cc, config) and
|
||||
@@ -551,7 +584,7 @@ private module Stage1 {
|
||||
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
|
||||
fwdFlow(node, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false
|
||||
if hasSinkCallCtx(config) then toReturn = true else toReturn = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
localFlowStep(node, mid, config) and
|
||||
@@ -937,6 +970,8 @@ private module Stage2 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1004,7 +1039,7 @@ private module Stage2 {
|
||||
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1215,7 +1250,7 @@ private module Stage2 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -1616,6 +1651,8 @@ private module Stage3 {
|
||||
|
||||
Cc ccNone() { result = false }
|
||||
|
||||
CcCall ccSomeCall() { result = true }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1697,7 +1734,7 @@ private module Stage3 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1908,7 +1945,7 @@ private module Stage3 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -2366,6 +2403,8 @@ private module Stage4 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = LocalCallContext;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -2461,7 +2500,7 @@ private module Stage4 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -2672,7 +2711,7 @@ private module Stage4 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -3064,7 +3103,11 @@ private newtype TPathNode =
|
||||
// A PathNode is introduced by a source ...
|
||||
Stage4::revFlow(node, config) and
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap = TAccessPathNil(node.getDataFlowType())
|
||||
or
|
||||
@@ -3076,17 +3119,10 @@ private newtype TPathNode =
|
||||
)
|
||||
} or
|
||||
TPathNodeSink(NodeEx node, Configuration config) {
|
||||
sinkNode(node, pragma[only_bind_into](config)) and
|
||||
Stage4::revFlow(node, pragma[only_bind_into](config)) and
|
||||
(
|
||||
// A sink that is also a source ...
|
||||
sourceNode(node, config)
|
||||
or
|
||||
// ... or a sink that can be reached from a source
|
||||
exists(PathNodeMid mid |
|
||||
pathStep(mid, node, _, _, TAccessPathNil(_)) and
|
||||
pragma[only_bind_into](config) = mid.getConfiguration()
|
||||
)
|
||||
exists(PathNodeMid sink |
|
||||
sink.isAtSink() and
|
||||
node = sink.getNodeEx() and
|
||||
config = sink.getConfiguration()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3403,22 +3439,46 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
|
||||
// an intermediate step to another intermediate node
|
||||
result = this.getSuccMid()
|
||||
or
|
||||
// a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
|
||||
exists(PathNodeMid mid, PathNodeSink sink |
|
||||
mid = this.getSuccMid() and
|
||||
mid.getNodeEx() = sink.getNodeEx() and
|
||||
mid.getAp() instanceof AccessPathNil and
|
||||
sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
|
||||
result = sink
|
||||
)
|
||||
// a final step to a sink
|
||||
result = this.getSuccMid().projectToSink()
|
||||
}
|
||||
|
||||
override predicate isSource() {
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap instanceof AccessPathNil
|
||||
}
|
||||
|
||||
predicate isAtSink() {
|
||||
sinkNode(node, config) and
|
||||
ap instanceof AccessPathNil and
|
||||
if hasSinkCallCtx(config)
|
||||
then
|
||||
// For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
|
||||
// is exactly what we need to check. This also implies
|
||||
// `sc instanceof SummaryCtxNone`.
|
||||
// For `FeatureEqualSourceSinkCallContext` the initial call context was
|
||||
// set to `CallContextSomeCall` and jumps are disallowed, so
|
||||
// `cc instanceof CallContextNoCall` never holds. On the other hand,
|
||||
// in this case there's never any need to enter a call except to identify
|
||||
// a summary, so the condition in `pathIntoCallable` enforces this, which
|
||||
// means that `sc instanceof SummaryCtxNone` holds if and only if we are
|
||||
// in the call context of the source.
|
||||
sc instanceof SummaryCtxNone or
|
||||
cc instanceof CallContextNoCall
|
||||
else any()
|
||||
}
|
||||
|
||||
PathNodeSink projectToSink() {
|
||||
this.isAtSink() and
|
||||
result.getNodeEx() = node and
|
||||
result.getConfiguration() = unbindConf(config)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3572,7 +3632,7 @@ private predicate pathIntoArg(
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate parameterCand(
|
||||
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
|
||||
) {
|
||||
@@ -3613,7 +3673,11 @@ private predicate pathIntoCallable(
|
||||
sc = TSummaryCtxSome(p, ap)
|
||||
or
|
||||
not exists(TSummaryCtxSome(p, ap)) and
|
||||
sc = TSummaryCtxNone()
|
||||
sc = TSummaryCtxNone() and
|
||||
// When the call contexts of source and sink needs to match then there's
|
||||
// never any reason to enter a callable except to find a summary. See also
|
||||
// the comment in `PathNodeMid::isAtSink`.
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
|
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
@@ -3676,13 +3740,14 @@ private module Subpaths {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate subpaths01(
|
||||
PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
||||
PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
||||
NodeEx out, AccessPath apout
|
||||
) {
|
||||
exists(Configuration config |
|
||||
pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
|
||||
pathIntoCallable(arg, par, _, innercc, sc, _, config) and
|
||||
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config))
|
||||
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
|
||||
not arg.isHidden()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3716,8 +3781,17 @@ private module Subpaths {
|
||||
innercc = ret.getCallContext() and
|
||||
sc = ret.getSummaryCtx() and
|
||||
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
|
||||
apout = ret.getAp() and
|
||||
not ret.isHidden()
|
||||
apout = ret.getAp()
|
||||
)
|
||||
}
|
||||
|
||||
private PathNodeImpl localStepToHidden(PathNodeImpl n) {
|
||||
n.getASuccessorImpl() = result and
|
||||
result.isHidden() and
|
||||
exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
|
||||
localFlowBigStep(n1, n2, _, _, _, _) or
|
||||
store(n1, _, n2, _, _) or
|
||||
read(n1, _, n2, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3726,11 +3800,12 @@ private module Subpaths {
|
||||
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
|
||||
* `ret -> out` is summarized as the edge `arg -> out`.
|
||||
*/
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
|
||||
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
|
||||
pragma[only_bind_into](arg).getASuccessor() = par and
|
||||
pragma[only_bind_into](arg).getASuccessor() = out and
|
||||
subpaths03(arg, p, ret, o, apout) and
|
||||
subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
|
||||
not ret.isHidden() and
|
||||
par.getNodeEx() = p and
|
||||
out.getNodeEx() = o and
|
||||
out.getAp() = apout
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
import DataFlowImplCommonPublic
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural data flow analysis. This defines
|
||||
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*/
|
||||
FlowFeature getAFeature() { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
@@ -349,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -365,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -401,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
|
||||
*/
|
||||
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
|
||||
|
||||
private predicate hasSourceCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSourceCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private predicate hasSinkCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSinkCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private module Stage1 {
|
||||
class ApApprox = Unit;
|
||||
|
||||
@@ -421,7 +454,7 @@ private module Stage1 {
|
||||
not fullBarrier(node, config) and
|
||||
(
|
||||
sourceNode(node, config) and
|
||||
cc = false
|
||||
if hasSourceCallCtx(config) then cc = true else cc = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
fwdFlow(mid, cc, config) and
|
||||
@@ -551,7 +584,7 @@ private module Stage1 {
|
||||
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
|
||||
fwdFlow(node, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false
|
||||
if hasSinkCallCtx(config) then toReturn = true else toReturn = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
localFlowStep(node, mid, config) and
|
||||
@@ -937,6 +970,8 @@ private module Stage2 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1004,7 +1039,7 @@ private module Stage2 {
|
||||
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1215,7 +1250,7 @@ private module Stage2 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -1616,6 +1651,8 @@ private module Stage3 {
|
||||
|
||||
Cc ccNone() { result = false }
|
||||
|
||||
CcCall ccSomeCall() { result = true }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1697,7 +1734,7 @@ private module Stage3 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1908,7 +1945,7 @@ private module Stage3 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -2366,6 +2403,8 @@ private module Stage4 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = LocalCallContext;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -2461,7 +2500,7 @@ private module Stage4 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -2672,7 +2711,7 @@ private module Stage4 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -3064,7 +3103,11 @@ private newtype TPathNode =
|
||||
// A PathNode is introduced by a source ...
|
||||
Stage4::revFlow(node, config) and
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap = TAccessPathNil(node.getDataFlowType())
|
||||
or
|
||||
@@ -3076,17 +3119,10 @@ private newtype TPathNode =
|
||||
)
|
||||
} or
|
||||
TPathNodeSink(NodeEx node, Configuration config) {
|
||||
sinkNode(node, pragma[only_bind_into](config)) and
|
||||
Stage4::revFlow(node, pragma[only_bind_into](config)) and
|
||||
(
|
||||
// A sink that is also a source ...
|
||||
sourceNode(node, config)
|
||||
or
|
||||
// ... or a sink that can be reached from a source
|
||||
exists(PathNodeMid mid |
|
||||
pathStep(mid, node, _, _, TAccessPathNil(_)) and
|
||||
pragma[only_bind_into](config) = mid.getConfiguration()
|
||||
)
|
||||
exists(PathNodeMid sink |
|
||||
sink.isAtSink() and
|
||||
node = sink.getNodeEx() and
|
||||
config = sink.getConfiguration()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3403,22 +3439,46 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
|
||||
// an intermediate step to another intermediate node
|
||||
result = this.getSuccMid()
|
||||
or
|
||||
// a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
|
||||
exists(PathNodeMid mid, PathNodeSink sink |
|
||||
mid = this.getSuccMid() and
|
||||
mid.getNodeEx() = sink.getNodeEx() and
|
||||
mid.getAp() instanceof AccessPathNil and
|
||||
sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
|
||||
result = sink
|
||||
)
|
||||
// a final step to a sink
|
||||
result = this.getSuccMid().projectToSink()
|
||||
}
|
||||
|
||||
override predicate isSource() {
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap instanceof AccessPathNil
|
||||
}
|
||||
|
||||
predicate isAtSink() {
|
||||
sinkNode(node, config) and
|
||||
ap instanceof AccessPathNil and
|
||||
if hasSinkCallCtx(config)
|
||||
then
|
||||
// For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
|
||||
// is exactly what we need to check. This also implies
|
||||
// `sc instanceof SummaryCtxNone`.
|
||||
// For `FeatureEqualSourceSinkCallContext` the initial call context was
|
||||
// set to `CallContextSomeCall` and jumps are disallowed, so
|
||||
// `cc instanceof CallContextNoCall` never holds. On the other hand,
|
||||
// in this case there's never any need to enter a call except to identify
|
||||
// a summary, so the condition in `pathIntoCallable` enforces this, which
|
||||
// means that `sc instanceof SummaryCtxNone` holds if and only if we are
|
||||
// in the call context of the source.
|
||||
sc instanceof SummaryCtxNone or
|
||||
cc instanceof CallContextNoCall
|
||||
else any()
|
||||
}
|
||||
|
||||
PathNodeSink projectToSink() {
|
||||
this.isAtSink() and
|
||||
result.getNodeEx() = node and
|
||||
result.getConfiguration() = unbindConf(config)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3572,7 +3632,7 @@ private predicate pathIntoArg(
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate parameterCand(
|
||||
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
|
||||
) {
|
||||
@@ -3613,7 +3673,11 @@ private predicate pathIntoCallable(
|
||||
sc = TSummaryCtxSome(p, ap)
|
||||
or
|
||||
not exists(TSummaryCtxSome(p, ap)) and
|
||||
sc = TSummaryCtxNone()
|
||||
sc = TSummaryCtxNone() and
|
||||
// When the call contexts of source and sink needs to match then there's
|
||||
// never any reason to enter a callable except to find a summary. See also
|
||||
// the comment in `PathNodeMid::isAtSink`.
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
|
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
@@ -3676,13 +3740,14 @@ private module Subpaths {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate subpaths01(
|
||||
PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
||||
PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
||||
NodeEx out, AccessPath apout
|
||||
) {
|
||||
exists(Configuration config |
|
||||
pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
|
||||
pathIntoCallable(arg, par, _, innercc, sc, _, config) and
|
||||
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config))
|
||||
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
|
||||
not arg.isHidden()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3716,8 +3781,17 @@ private module Subpaths {
|
||||
innercc = ret.getCallContext() and
|
||||
sc = ret.getSummaryCtx() and
|
||||
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
|
||||
apout = ret.getAp() and
|
||||
not ret.isHidden()
|
||||
apout = ret.getAp()
|
||||
)
|
||||
}
|
||||
|
||||
private PathNodeImpl localStepToHidden(PathNodeImpl n) {
|
||||
n.getASuccessorImpl() = result and
|
||||
result.isHidden() and
|
||||
exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
|
||||
localFlowBigStep(n1, n2, _, _, _, _) or
|
||||
store(n1, _, n2, _, _) or
|
||||
read(n1, _, n2, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3726,11 +3800,12 @@ private module Subpaths {
|
||||
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
|
||||
* `ret -> out` is summarized as the edge `arg -> out`.
|
||||
*/
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
|
||||
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
|
||||
pragma[only_bind_into](arg).getASuccessor() = par and
|
||||
pragma[only_bind_into](arg).getASuccessor() = out and
|
||||
subpaths03(arg, p, ret, o, apout) and
|
||||
subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
|
||||
not ret.isHidden() and
|
||||
par.getNodeEx() = p and
|
||||
out.getNodeEx() = o and
|
||||
out.getAp() = apout
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
import DataFlowImplCommonPublic
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural data flow analysis. This defines
|
||||
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*/
|
||||
FlowFeature getAFeature() { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
@@ -349,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -365,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -401,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
|
||||
*/
|
||||
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
|
||||
|
||||
private predicate hasSourceCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSourceCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private predicate hasSinkCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSinkCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private module Stage1 {
|
||||
class ApApprox = Unit;
|
||||
|
||||
@@ -421,7 +454,7 @@ private module Stage1 {
|
||||
not fullBarrier(node, config) and
|
||||
(
|
||||
sourceNode(node, config) and
|
||||
cc = false
|
||||
if hasSourceCallCtx(config) then cc = true else cc = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
fwdFlow(mid, cc, config) and
|
||||
@@ -551,7 +584,7 @@ private module Stage1 {
|
||||
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
|
||||
fwdFlow(node, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false
|
||||
if hasSinkCallCtx(config) then toReturn = true else toReturn = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
localFlowStep(node, mid, config) and
|
||||
@@ -937,6 +970,8 @@ private module Stage2 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1004,7 +1039,7 @@ private module Stage2 {
|
||||
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1215,7 +1250,7 @@ private module Stage2 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -1616,6 +1651,8 @@ private module Stage3 {
|
||||
|
||||
Cc ccNone() { result = false }
|
||||
|
||||
CcCall ccSomeCall() { result = true }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1697,7 +1734,7 @@ private module Stage3 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1908,7 +1945,7 @@ private module Stage3 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -2366,6 +2403,8 @@ private module Stage4 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = LocalCallContext;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -2461,7 +2500,7 @@ private module Stage4 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -2672,7 +2711,7 @@ private module Stage4 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -3064,7 +3103,11 @@ private newtype TPathNode =
|
||||
// A PathNode is introduced by a source ...
|
||||
Stage4::revFlow(node, config) and
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap = TAccessPathNil(node.getDataFlowType())
|
||||
or
|
||||
@@ -3076,17 +3119,10 @@ private newtype TPathNode =
|
||||
)
|
||||
} or
|
||||
TPathNodeSink(NodeEx node, Configuration config) {
|
||||
sinkNode(node, pragma[only_bind_into](config)) and
|
||||
Stage4::revFlow(node, pragma[only_bind_into](config)) and
|
||||
(
|
||||
// A sink that is also a source ...
|
||||
sourceNode(node, config)
|
||||
or
|
||||
// ... or a sink that can be reached from a source
|
||||
exists(PathNodeMid mid |
|
||||
pathStep(mid, node, _, _, TAccessPathNil(_)) and
|
||||
pragma[only_bind_into](config) = mid.getConfiguration()
|
||||
)
|
||||
exists(PathNodeMid sink |
|
||||
sink.isAtSink() and
|
||||
node = sink.getNodeEx() and
|
||||
config = sink.getConfiguration()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3403,22 +3439,46 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
|
||||
// an intermediate step to another intermediate node
|
||||
result = this.getSuccMid()
|
||||
or
|
||||
// a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
|
||||
exists(PathNodeMid mid, PathNodeSink sink |
|
||||
mid = this.getSuccMid() and
|
||||
mid.getNodeEx() = sink.getNodeEx() and
|
||||
mid.getAp() instanceof AccessPathNil and
|
||||
sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
|
||||
result = sink
|
||||
)
|
||||
// a final step to a sink
|
||||
result = this.getSuccMid().projectToSink()
|
||||
}
|
||||
|
||||
override predicate isSource() {
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap instanceof AccessPathNil
|
||||
}
|
||||
|
||||
predicate isAtSink() {
|
||||
sinkNode(node, config) and
|
||||
ap instanceof AccessPathNil and
|
||||
if hasSinkCallCtx(config)
|
||||
then
|
||||
// For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
|
||||
// is exactly what we need to check. This also implies
|
||||
// `sc instanceof SummaryCtxNone`.
|
||||
// For `FeatureEqualSourceSinkCallContext` the initial call context was
|
||||
// set to `CallContextSomeCall` and jumps are disallowed, so
|
||||
// `cc instanceof CallContextNoCall` never holds. On the other hand,
|
||||
// in this case there's never any need to enter a call except to identify
|
||||
// a summary, so the condition in `pathIntoCallable` enforces this, which
|
||||
// means that `sc instanceof SummaryCtxNone` holds if and only if we are
|
||||
// in the call context of the source.
|
||||
sc instanceof SummaryCtxNone or
|
||||
cc instanceof CallContextNoCall
|
||||
else any()
|
||||
}
|
||||
|
||||
PathNodeSink projectToSink() {
|
||||
this.isAtSink() and
|
||||
result.getNodeEx() = node and
|
||||
result.getConfiguration() = unbindConf(config)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3572,7 +3632,7 @@ private predicate pathIntoArg(
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate parameterCand(
|
||||
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
|
||||
) {
|
||||
@@ -3613,7 +3673,11 @@ private predicate pathIntoCallable(
|
||||
sc = TSummaryCtxSome(p, ap)
|
||||
or
|
||||
not exists(TSummaryCtxSome(p, ap)) and
|
||||
sc = TSummaryCtxNone()
|
||||
sc = TSummaryCtxNone() and
|
||||
// When the call contexts of source and sink needs to match then there's
|
||||
// never any reason to enter a callable except to find a summary. See also
|
||||
// the comment in `PathNodeMid::isAtSink`.
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
|
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
@@ -3676,13 +3740,14 @@ private module Subpaths {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate subpaths01(
|
||||
PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
||||
PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
||||
NodeEx out, AccessPath apout
|
||||
) {
|
||||
exists(Configuration config |
|
||||
pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
|
||||
pathIntoCallable(arg, par, _, innercc, sc, _, config) and
|
||||
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config))
|
||||
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
|
||||
not arg.isHidden()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3716,8 +3781,17 @@ private module Subpaths {
|
||||
innercc = ret.getCallContext() and
|
||||
sc = ret.getSummaryCtx() and
|
||||
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
|
||||
apout = ret.getAp() and
|
||||
not ret.isHidden()
|
||||
apout = ret.getAp()
|
||||
)
|
||||
}
|
||||
|
||||
private PathNodeImpl localStepToHidden(PathNodeImpl n) {
|
||||
n.getASuccessorImpl() = result and
|
||||
result.isHidden() and
|
||||
exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
|
||||
localFlowBigStep(n1, n2, _, _, _, _) or
|
||||
store(n1, _, n2, _, _) or
|
||||
read(n1, _, n2, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3726,11 +3800,12 @@ private module Subpaths {
|
||||
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
|
||||
* `ret -> out` is summarized as the edge `arg -> out`.
|
||||
*/
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
|
||||
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
|
||||
pragma[only_bind_into](arg).getASuccessor() = par and
|
||||
pragma[only_bind_into](arg).getASuccessor() = out and
|
||||
subpaths03(arg, p, ret, o, apout) and
|
||||
subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
|
||||
not ret.isHidden() and
|
||||
par.getNodeEx() = p and
|
||||
out.getNodeEx() = o and
|
||||
out.getAp() = apout
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
import DataFlowImplCommonPublic
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural data flow analysis. This defines
|
||||
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*/
|
||||
FlowFeature getAFeature() { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
@@ -349,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -365,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -401,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
|
||||
*/
|
||||
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
|
||||
|
||||
private predicate hasSourceCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSourceCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private predicate hasSinkCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSinkCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private module Stage1 {
|
||||
class ApApprox = Unit;
|
||||
|
||||
@@ -421,7 +454,7 @@ private module Stage1 {
|
||||
not fullBarrier(node, config) and
|
||||
(
|
||||
sourceNode(node, config) and
|
||||
cc = false
|
||||
if hasSourceCallCtx(config) then cc = true else cc = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
fwdFlow(mid, cc, config) and
|
||||
@@ -551,7 +584,7 @@ private module Stage1 {
|
||||
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
|
||||
fwdFlow(node, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false
|
||||
if hasSinkCallCtx(config) then toReturn = true else toReturn = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
localFlowStep(node, mid, config) and
|
||||
@@ -937,6 +970,8 @@ private module Stage2 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1004,7 +1039,7 @@ private module Stage2 {
|
||||
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1215,7 +1250,7 @@ private module Stage2 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -1616,6 +1651,8 @@ private module Stage3 {
|
||||
|
||||
Cc ccNone() { result = false }
|
||||
|
||||
CcCall ccSomeCall() { result = true }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1697,7 +1734,7 @@ private module Stage3 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1908,7 +1945,7 @@ private module Stage3 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -2366,6 +2403,8 @@ private module Stage4 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = LocalCallContext;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -2461,7 +2500,7 @@ private module Stage4 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -2672,7 +2711,7 @@ private module Stage4 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -3064,7 +3103,11 @@ private newtype TPathNode =
|
||||
// A PathNode is introduced by a source ...
|
||||
Stage4::revFlow(node, config) and
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap = TAccessPathNil(node.getDataFlowType())
|
||||
or
|
||||
@@ -3076,17 +3119,10 @@ private newtype TPathNode =
|
||||
)
|
||||
} or
|
||||
TPathNodeSink(NodeEx node, Configuration config) {
|
||||
sinkNode(node, pragma[only_bind_into](config)) and
|
||||
Stage4::revFlow(node, pragma[only_bind_into](config)) and
|
||||
(
|
||||
// A sink that is also a source ...
|
||||
sourceNode(node, config)
|
||||
or
|
||||
// ... or a sink that can be reached from a source
|
||||
exists(PathNodeMid mid |
|
||||
pathStep(mid, node, _, _, TAccessPathNil(_)) and
|
||||
pragma[only_bind_into](config) = mid.getConfiguration()
|
||||
)
|
||||
exists(PathNodeMid sink |
|
||||
sink.isAtSink() and
|
||||
node = sink.getNodeEx() and
|
||||
config = sink.getConfiguration()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3403,22 +3439,46 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
|
||||
// an intermediate step to another intermediate node
|
||||
result = this.getSuccMid()
|
||||
or
|
||||
// a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
|
||||
exists(PathNodeMid mid, PathNodeSink sink |
|
||||
mid = this.getSuccMid() and
|
||||
mid.getNodeEx() = sink.getNodeEx() and
|
||||
mid.getAp() instanceof AccessPathNil and
|
||||
sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
|
||||
result = sink
|
||||
)
|
||||
// a final step to a sink
|
||||
result = this.getSuccMid().projectToSink()
|
||||
}
|
||||
|
||||
override predicate isSource() {
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap instanceof AccessPathNil
|
||||
}
|
||||
|
||||
predicate isAtSink() {
|
||||
sinkNode(node, config) and
|
||||
ap instanceof AccessPathNil and
|
||||
if hasSinkCallCtx(config)
|
||||
then
|
||||
// For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
|
||||
// is exactly what we need to check. This also implies
|
||||
// `sc instanceof SummaryCtxNone`.
|
||||
// For `FeatureEqualSourceSinkCallContext` the initial call context was
|
||||
// set to `CallContextSomeCall` and jumps are disallowed, so
|
||||
// `cc instanceof CallContextNoCall` never holds. On the other hand,
|
||||
// in this case there's never any need to enter a call except to identify
|
||||
// a summary, so the condition in `pathIntoCallable` enforces this, which
|
||||
// means that `sc instanceof SummaryCtxNone` holds if and only if we are
|
||||
// in the call context of the source.
|
||||
sc instanceof SummaryCtxNone or
|
||||
cc instanceof CallContextNoCall
|
||||
else any()
|
||||
}
|
||||
|
||||
PathNodeSink projectToSink() {
|
||||
this.isAtSink() and
|
||||
result.getNodeEx() = node and
|
||||
result.getConfiguration() = unbindConf(config)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3572,7 +3632,7 @@ private predicate pathIntoArg(
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate parameterCand(
|
||||
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
|
||||
) {
|
||||
@@ -3613,7 +3673,11 @@ private predicate pathIntoCallable(
|
||||
sc = TSummaryCtxSome(p, ap)
|
||||
or
|
||||
not exists(TSummaryCtxSome(p, ap)) and
|
||||
sc = TSummaryCtxNone()
|
||||
sc = TSummaryCtxNone() and
|
||||
// When the call contexts of source and sink needs to match then there's
|
||||
// never any reason to enter a callable except to find a summary. See also
|
||||
// the comment in `PathNodeMid::isAtSink`.
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
|
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
@@ -3676,13 +3740,14 @@ private module Subpaths {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate subpaths01(
|
||||
PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
||||
PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
||||
NodeEx out, AccessPath apout
|
||||
) {
|
||||
exists(Configuration config |
|
||||
pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
|
||||
pathIntoCallable(arg, par, _, innercc, sc, _, config) and
|
||||
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config))
|
||||
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
|
||||
not arg.isHidden()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3716,8 +3781,17 @@ private module Subpaths {
|
||||
innercc = ret.getCallContext() and
|
||||
sc = ret.getSummaryCtx() and
|
||||
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
|
||||
apout = ret.getAp() and
|
||||
not ret.isHidden()
|
||||
apout = ret.getAp()
|
||||
)
|
||||
}
|
||||
|
||||
private PathNodeImpl localStepToHidden(PathNodeImpl n) {
|
||||
n.getASuccessorImpl() = result and
|
||||
result.isHidden() and
|
||||
exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
|
||||
localFlowBigStep(n1, n2, _, _, _, _) or
|
||||
store(n1, _, n2, _, _) or
|
||||
read(n1, _, n2, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3726,11 +3800,12 @@ private module Subpaths {
|
||||
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
|
||||
* `ret -> out` is summarized as the edge `arg -> out`.
|
||||
*/
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
|
||||
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
|
||||
pragma[only_bind_into](arg).getASuccessor() = par and
|
||||
pragma[only_bind_into](arg).getASuccessor() = out and
|
||||
subpaths03(arg, p, ret, o, apout) and
|
||||
subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
|
||||
not ret.isHidden() and
|
||||
par.getNodeEx() = p and
|
||||
out.getNodeEx() = o and
|
||||
out.getAp() = apout
|
||||
|
||||
@@ -2,6 +2,42 @@ private import DataFlowImplSpecific::Private
|
||||
private import DataFlowImplSpecific::Public
|
||||
import Cached
|
||||
|
||||
module DataFlowImplCommonPublic {
|
||||
private newtype TFlowFeature =
|
||||
TFeatureHasSourceCallContext() or
|
||||
TFeatureHasSinkCallContext() or
|
||||
TFeatureEqualSourceSinkCallContext()
|
||||
|
||||
/** A flow configuration feature for use in `Configuration::getAFeature()`. */
|
||||
class FlowFeature extends TFlowFeature {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow configuration feature that implies that sources have some existing
|
||||
* call context.
|
||||
*/
|
||||
class FeatureHasSourceCallContext extends FlowFeature, TFeatureHasSourceCallContext {
|
||||
override string toString() { result = "FeatureHasSourceCallContext" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow configuration feature that implies that sinks have some existing
|
||||
* call context.
|
||||
*/
|
||||
class FeatureHasSinkCallContext extends FlowFeature, TFeatureHasSinkCallContext {
|
||||
override string toString() { result = "FeatureHasSinkCallContext" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow configuration feature that implies that source-sink pairs have some
|
||||
* shared existing call context.
|
||||
*/
|
||||
class FeatureEqualSourceSinkCallContext extends FlowFeature, TFeatureEqualSourceSinkCallContext {
|
||||
override string toString() { result = "FeatureEqualSourceSinkCallContext" }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The cost limits for the `AccessPathFront` to `AccessPathApprox` expansion.
|
||||
*
|
||||
@@ -251,7 +287,7 @@ private module Cached {
|
||||
predicate forceCachingInSameStage() { any() }
|
||||
|
||||
cached
|
||||
predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = n.getEnclosingCallable() }
|
||||
predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = nodeGetEnclosingCallable(n) }
|
||||
|
||||
cached
|
||||
predicate callEnclosingCallable(DataFlowCall call, DataFlowCallable c) {
|
||||
@@ -316,9 +352,7 @@ private module Cached {
|
||||
}
|
||||
|
||||
cached
|
||||
predicate parameterNode(Node n, DataFlowCallable c, int i) {
|
||||
n.(ParameterNode).isParameterOf(c, i)
|
||||
}
|
||||
predicate parameterNode(Node p, DataFlowCallable c, int pos) { isParameterNode(p, c, pos) }
|
||||
|
||||
cached
|
||||
predicate argumentNode(Node n, DataFlowCall call, int pos) {
|
||||
|
||||
@@ -31,7 +31,7 @@ module Consistency {
|
||||
query predicate uniqueEnclosingCallable(Node n, string msg) {
|
||||
exists(int c |
|
||||
n instanceof RelevantNode and
|
||||
c = count(n.getEnclosingCallable()) and
|
||||
c = count(nodeGetEnclosingCallable(n)) and
|
||||
c != 1 and
|
||||
msg = "Node should have one enclosing callable but has " + c + "."
|
||||
)
|
||||
@@ -85,13 +85,13 @@ module Consistency {
|
||||
}
|
||||
|
||||
query predicate parameterCallable(ParameterNode p, string msg) {
|
||||
exists(DataFlowCallable c | p.isParameterOf(c, _) and c != p.getEnclosingCallable()) and
|
||||
exists(DataFlowCallable c | isParameterNode(p, c, _) and c != nodeGetEnclosingCallable(p)) and
|
||||
msg = "Callable mismatch for parameter."
|
||||
}
|
||||
|
||||
query predicate localFlowIsLocal(Node n1, Node n2, string msg) {
|
||||
simpleLocalFlowStep(n1, n2) and
|
||||
n1.getEnclosingCallable() != n2.getEnclosingCallable() and
|
||||
nodeGetEnclosingCallable(n1) != nodeGetEnclosingCallable(n2) and
|
||||
msg = "Local flow step does not preserve enclosing callable."
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ module Consistency {
|
||||
query predicate unreachableNodeCCtx(Node n, DataFlowCall call, string msg) {
|
||||
isUnreachableInCall(n, call) and
|
||||
exists(DataFlowCallable c |
|
||||
c = n.getEnclosingCallable() and
|
||||
c = nodeGetEnclosingCallable(n) and
|
||||
not viableCallable(call) = c
|
||||
) and
|
||||
msg = "Call context for isUnreachableInCall is inconsistent with call graph."
|
||||
@@ -120,7 +120,7 @@ module Consistency {
|
||||
n.(ArgumentNode).argumentOf(call, _) and
|
||||
msg = "ArgumentNode and call does not share enclosing callable."
|
||||
) and
|
||||
n.getEnclosingCallable() != call.getEnclosingCallable()
|
||||
nodeGetEnclosingCallable(n) != call.getEnclosingCallable()
|
||||
}
|
||||
|
||||
// This predicate helps the compiler forget that in some languages
|
||||
@@ -151,7 +151,7 @@ module Consistency {
|
||||
}
|
||||
|
||||
query predicate postIsInSameCallable(PostUpdateNode n, string msg) {
|
||||
n.getEnclosingCallable() != n.getPreUpdateNode().getEnclosingCallable() and
|
||||
nodeGetEnclosingCallable(n) != nodeGetEnclosingCallable(n.getPreUpdateNode()) and
|
||||
msg = "PostUpdateNode does not share callable with its pre-update node."
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
import DataFlowImplCommonPublic
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural data flow analysis. This defines
|
||||
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*/
|
||||
FlowFeature getAFeature() { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
@@ -349,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -365,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -401,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
|
||||
*/
|
||||
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
|
||||
|
||||
private predicate hasSourceCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSourceCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private predicate hasSinkCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSinkCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private module Stage1 {
|
||||
class ApApprox = Unit;
|
||||
|
||||
@@ -421,7 +454,7 @@ private module Stage1 {
|
||||
not fullBarrier(node, config) and
|
||||
(
|
||||
sourceNode(node, config) and
|
||||
cc = false
|
||||
if hasSourceCallCtx(config) then cc = true else cc = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
fwdFlow(mid, cc, config) and
|
||||
@@ -551,7 +584,7 @@ private module Stage1 {
|
||||
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
|
||||
fwdFlow(node, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false
|
||||
if hasSinkCallCtx(config) then toReturn = true else toReturn = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
localFlowStep(node, mid, config) and
|
||||
@@ -937,6 +970,8 @@ private module Stage2 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1004,7 +1039,7 @@ private module Stage2 {
|
||||
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1215,7 +1250,7 @@ private module Stage2 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -1616,6 +1651,8 @@ private module Stage3 {
|
||||
|
||||
Cc ccNone() { result = false }
|
||||
|
||||
CcCall ccSomeCall() { result = true }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1697,7 +1734,7 @@ private module Stage3 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1908,7 +1945,7 @@ private module Stage3 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -2366,6 +2403,8 @@ private module Stage4 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = LocalCallContext;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -2461,7 +2500,7 @@ private module Stage4 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -2672,7 +2711,7 @@ private module Stage4 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -3064,7 +3103,11 @@ private newtype TPathNode =
|
||||
// A PathNode is introduced by a source ...
|
||||
Stage4::revFlow(node, config) and
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap = TAccessPathNil(node.getDataFlowType())
|
||||
or
|
||||
@@ -3076,17 +3119,10 @@ private newtype TPathNode =
|
||||
)
|
||||
} or
|
||||
TPathNodeSink(NodeEx node, Configuration config) {
|
||||
sinkNode(node, pragma[only_bind_into](config)) and
|
||||
Stage4::revFlow(node, pragma[only_bind_into](config)) and
|
||||
(
|
||||
// A sink that is also a source ...
|
||||
sourceNode(node, config)
|
||||
or
|
||||
// ... or a sink that can be reached from a source
|
||||
exists(PathNodeMid mid |
|
||||
pathStep(mid, node, _, _, TAccessPathNil(_)) and
|
||||
pragma[only_bind_into](config) = mid.getConfiguration()
|
||||
)
|
||||
exists(PathNodeMid sink |
|
||||
sink.isAtSink() and
|
||||
node = sink.getNodeEx() and
|
||||
config = sink.getConfiguration()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3403,22 +3439,46 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
|
||||
// an intermediate step to another intermediate node
|
||||
result = this.getSuccMid()
|
||||
or
|
||||
// a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
|
||||
exists(PathNodeMid mid, PathNodeSink sink |
|
||||
mid = this.getSuccMid() and
|
||||
mid.getNodeEx() = sink.getNodeEx() and
|
||||
mid.getAp() instanceof AccessPathNil and
|
||||
sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
|
||||
result = sink
|
||||
)
|
||||
// a final step to a sink
|
||||
result = this.getSuccMid().projectToSink()
|
||||
}
|
||||
|
||||
override predicate isSource() {
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap instanceof AccessPathNil
|
||||
}
|
||||
|
||||
predicate isAtSink() {
|
||||
sinkNode(node, config) and
|
||||
ap instanceof AccessPathNil and
|
||||
if hasSinkCallCtx(config)
|
||||
then
|
||||
// For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
|
||||
// is exactly what we need to check. This also implies
|
||||
// `sc instanceof SummaryCtxNone`.
|
||||
// For `FeatureEqualSourceSinkCallContext` the initial call context was
|
||||
// set to `CallContextSomeCall` and jumps are disallowed, so
|
||||
// `cc instanceof CallContextNoCall` never holds. On the other hand,
|
||||
// in this case there's never any need to enter a call except to identify
|
||||
// a summary, so the condition in `pathIntoCallable` enforces this, which
|
||||
// means that `sc instanceof SummaryCtxNone` holds if and only if we are
|
||||
// in the call context of the source.
|
||||
sc instanceof SummaryCtxNone or
|
||||
cc instanceof CallContextNoCall
|
||||
else any()
|
||||
}
|
||||
|
||||
PathNodeSink projectToSink() {
|
||||
this.isAtSink() and
|
||||
result.getNodeEx() = node and
|
||||
result.getConfiguration() = unbindConf(config)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3572,7 +3632,7 @@ private predicate pathIntoArg(
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate parameterCand(
|
||||
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
|
||||
) {
|
||||
@@ -3613,7 +3673,11 @@ private predicate pathIntoCallable(
|
||||
sc = TSummaryCtxSome(p, ap)
|
||||
or
|
||||
not exists(TSummaryCtxSome(p, ap)) and
|
||||
sc = TSummaryCtxNone()
|
||||
sc = TSummaryCtxNone() and
|
||||
// When the call contexts of source and sink needs to match then there's
|
||||
// never any reason to enter a callable except to find a summary. See also
|
||||
// the comment in `PathNodeMid::isAtSink`.
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
|
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
@@ -3676,13 +3740,14 @@ private module Subpaths {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate subpaths01(
|
||||
PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
||||
PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
||||
NodeEx out, AccessPath apout
|
||||
) {
|
||||
exists(Configuration config |
|
||||
pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
|
||||
pathIntoCallable(arg, par, _, innercc, sc, _, config) and
|
||||
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config))
|
||||
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
|
||||
not arg.isHidden()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3716,8 +3781,17 @@ private module Subpaths {
|
||||
innercc = ret.getCallContext() and
|
||||
sc = ret.getSummaryCtx() and
|
||||
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
|
||||
apout = ret.getAp() and
|
||||
not ret.isHidden()
|
||||
apout = ret.getAp()
|
||||
)
|
||||
}
|
||||
|
||||
private PathNodeImpl localStepToHidden(PathNodeImpl n) {
|
||||
n.getASuccessorImpl() = result and
|
||||
result.isHidden() and
|
||||
exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
|
||||
localFlowBigStep(n1, n2, _, _, _, _) or
|
||||
store(n1, _, n2, _, _) or
|
||||
read(n1, _, n2, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3726,11 +3800,12 @@ private module Subpaths {
|
||||
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
|
||||
* `ret -> out` is summarized as the edge `arg -> out`.
|
||||
*/
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
|
||||
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
|
||||
pragma[only_bind_into](arg).getASuccessor() = par and
|
||||
pragma[only_bind_into](arg).getASuccessor() = out and
|
||||
subpaths03(arg, p, ret, o, apout) and
|
||||
subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
|
||||
not ret.isHidden() and
|
||||
par.getNodeEx() = p and
|
||||
out.getNodeEx() = o and
|
||||
out.getAp() = apout
|
||||
|
||||
@@ -3,6 +3,12 @@ private import DataFlowUtil
|
||||
private import DataFlowDispatch
|
||||
private import FlowVar
|
||||
|
||||
/** Gets the callable in which this node occurs. */
|
||||
DataFlowCallable nodeGetEnclosingCallable(Node n) { result = n.getEnclosingCallable() }
|
||||
|
||||
/** Holds if `p` is a `ParameterNode` of `c` with position `pos`. */
|
||||
predicate isParameterNode(ParameterNode p, DataFlowCallable c, int pos) { p.isParameterOf(c, pos) }
|
||||
|
||||
/** Gets the instance argument of a non-static call. */
|
||||
private Node getInstanceArgument(Call call) {
|
||||
result.asExpr() = call.getQualifier()
|
||||
|
||||
@@ -31,7 +31,7 @@ class Expr extends StmtParent, @expr {
|
||||
override Stmt getEnclosingStmt() {
|
||||
result = this.getParent().(Expr).getEnclosingStmt()
|
||||
or
|
||||
result = this.getParent().(Stmt)
|
||||
result = this.getParent()
|
||||
or
|
||||
exists(Expr other | result = other.getEnclosingStmt() and other.getConversion() = this)
|
||||
or
|
||||
|
||||
@@ -31,7 +31,7 @@ private predicate addressConstantVariable(Variable v) {
|
||||
private predicate constantAddressLValue(Expr lvalue) {
|
||||
lvalue.(VariableAccess).getTarget() =
|
||||
any(Variable v |
|
||||
v.(Variable).isStatic()
|
||||
v.isStatic()
|
||||
or
|
||||
v instanceof GlobalOrNamespaceVariable
|
||||
)
|
||||
|
||||
@@ -474,6 +474,25 @@ module TaintedWithPath {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*/
|
||||
module Private {
|
||||
/** Gets a predecessor `PathNode` of `pathNode`, if any. */
|
||||
PathNode getAPredecessor(PathNode pathNode) { edges(result, pathNode) }
|
||||
|
||||
/** Gets the element that `pathNode` wraps, if any. */
|
||||
Element getElementFromPathNode(PathNode pathNode) {
|
||||
exists(DataFlow::Node node | node = pathNode.(WrapPathNode).inner().getNode() |
|
||||
result = node.asInstruction().getAST()
|
||||
or
|
||||
result = node.asOperand().getDef().getAST()
|
||||
)
|
||||
or
|
||||
result = pathNode.(EndpointPathNode).inner()
|
||||
}
|
||||
}
|
||||
|
||||
private class WrapPathNode extends PathNode, TWrapPathNode {
|
||||
DataFlow3::PathNode inner() { this = TWrapPathNode(result) }
|
||||
|
||||
|
||||
@@ -63,8 +63,10 @@ private module VirtualDispatch {
|
||||
|
|
||||
// Call argument
|
||||
exists(DataFlowCall call, int i |
|
||||
other.(DataFlow::ParameterNode).isParameterOf(call.getStaticCallTarget(), i) and
|
||||
src.(ArgumentNode).argumentOf(call, i)
|
||||
other
|
||||
.(DataFlow::ParameterNode)
|
||||
.isParameterOf(pragma[only_bind_into](call).getStaticCallTarget(), i) and
|
||||
src.(ArgumentNode).argumentOf(call, pragma[only_bind_into](pragma[only_bind_out](i)))
|
||||
) and
|
||||
allowOtherFromArg = true and
|
||||
allowFromArg = true
|
||||
@@ -128,6 +130,7 @@ private module VirtualDispatch {
|
||||
*
|
||||
* Used to fix a join ordering issue in flowsFrom.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate returnNodeWithKindAndEnclosingCallable(
|
||||
ReturnNode node, ReturnKind kind, DataFlowCallable callable
|
||||
) {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
import DataFlowImplCommonPublic
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural data flow analysis. This defines
|
||||
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*/
|
||||
FlowFeature getAFeature() { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
@@ -349,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -365,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -401,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
|
||||
*/
|
||||
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
|
||||
|
||||
private predicate hasSourceCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSourceCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private predicate hasSinkCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSinkCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private module Stage1 {
|
||||
class ApApprox = Unit;
|
||||
|
||||
@@ -421,7 +454,7 @@ private module Stage1 {
|
||||
not fullBarrier(node, config) and
|
||||
(
|
||||
sourceNode(node, config) and
|
||||
cc = false
|
||||
if hasSourceCallCtx(config) then cc = true else cc = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
fwdFlow(mid, cc, config) and
|
||||
@@ -551,7 +584,7 @@ private module Stage1 {
|
||||
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
|
||||
fwdFlow(node, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false
|
||||
if hasSinkCallCtx(config) then toReturn = true else toReturn = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
localFlowStep(node, mid, config) and
|
||||
@@ -937,6 +970,8 @@ private module Stage2 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1004,7 +1039,7 @@ private module Stage2 {
|
||||
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1215,7 +1250,7 @@ private module Stage2 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -1616,6 +1651,8 @@ private module Stage3 {
|
||||
|
||||
Cc ccNone() { result = false }
|
||||
|
||||
CcCall ccSomeCall() { result = true }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1697,7 +1734,7 @@ private module Stage3 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1908,7 +1945,7 @@ private module Stage3 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -2366,6 +2403,8 @@ private module Stage4 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = LocalCallContext;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -2461,7 +2500,7 @@ private module Stage4 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -2672,7 +2711,7 @@ private module Stage4 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -3064,7 +3103,11 @@ private newtype TPathNode =
|
||||
// A PathNode is introduced by a source ...
|
||||
Stage4::revFlow(node, config) and
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap = TAccessPathNil(node.getDataFlowType())
|
||||
or
|
||||
@@ -3076,17 +3119,10 @@ private newtype TPathNode =
|
||||
)
|
||||
} or
|
||||
TPathNodeSink(NodeEx node, Configuration config) {
|
||||
sinkNode(node, pragma[only_bind_into](config)) and
|
||||
Stage4::revFlow(node, pragma[only_bind_into](config)) and
|
||||
(
|
||||
// A sink that is also a source ...
|
||||
sourceNode(node, config)
|
||||
or
|
||||
// ... or a sink that can be reached from a source
|
||||
exists(PathNodeMid mid |
|
||||
pathStep(mid, node, _, _, TAccessPathNil(_)) and
|
||||
pragma[only_bind_into](config) = mid.getConfiguration()
|
||||
)
|
||||
exists(PathNodeMid sink |
|
||||
sink.isAtSink() and
|
||||
node = sink.getNodeEx() and
|
||||
config = sink.getConfiguration()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3403,22 +3439,46 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
|
||||
// an intermediate step to another intermediate node
|
||||
result = this.getSuccMid()
|
||||
or
|
||||
// a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
|
||||
exists(PathNodeMid mid, PathNodeSink sink |
|
||||
mid = this.getSuccMid() and
|
||||
mid.getNodeEx() = sink.getNodeEx() and
|
||||
mid.getAp() instanceof AccessPathNil and
|
||||
sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
|
||||
result = sink
|
||||
)
|
||||
// a final step to a sink
|
||||
result = this.getSuccMid().projectToSink()
|
||||
}
|
||||
|
||||
override predicate isSource() {
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap instanceof AccessPathNil
|
||||
}
|
||||
|
||||
predicate isAtSink() {
|
||||
sinkNode(node, config) and
|
||||
ap instanceof AccessPathNil and
|
||||
if hasSinkCallCtx(config)
|
||||
then
|
||||
// For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
|
||||
// is exactly what we need to check. This also implies
|
||||
// `sc instanceof SummaryCtxNone`.
|
||||
// For `FeatureEqualSourceSinkCallContext` the initial call context was
|
||||
// set to `CallContextSomeCall` and jumps are disallowed, so
|
||||
// `cc instanceof CallContextNoCall` never holds. On the other hand,
|
||||
// in this case there's never any need to enter a call except to identify
|
||||
// a summary, so the condition in `pathIntoCallable` enforces this, which
|
||||
// means that `sc instanceof SummaryCtxNone` holds if and only if we are
|
||||
// in the call context of the source.
|
||||
sc instanceof SummaryCtxNone or
|
||||
cc instanceof CallContextNoCall
|
||||
else any()
|
||||
}
|
||||
|
||||
PathNodeSink projectToSink() {
|
||||
this.isAtSink() and
|
||||
result.getNodeEx() = node and
|
||||
result.getConfiguration() = unbindConf(config)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3572,7 +3632,7 @@ private predicate pathIntoArg(
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate parameterCand(
|
||||
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
|
||||
) {
|
||||
@@ -3613,7 +3673,11 @@ private predicate pathIntoCallable(
|
||||
sc = TSummaryCtxSome(p, ap)
|
||||
or
|
||||
not exists(TSummaryCtxSome(p, ap)) and
|
||||
sc = TSummaryCtxNone()
|
||||
sc = TSummaryCtxNone() and
|
||||
// When the call contexts of source and sink needs to match then there's
|
||||
// never any reason to enter a callable except to find a summary. See also
|
||||
// the comment in `PathNodeMid::isAtSink`.
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
|
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
@@ -3676,13 +3740,14 @@ private module Subpaths {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate subpaths01(
|
||||
PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
||||
PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
||||
NodeEx out, AccessPath apout
|
||||
) {
|
||||
exists(Configuration config |
|
||||
pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
|
||||
pathIntoCallable(arg, par, _, innercc, sc, _, config) and
|
||||
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config))
|
||||
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
|
||||
not arg.isHidden()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3716,8 +3781,17 @@ private module Subpaths {
|
||||
innercc = ret.getCallContext() and
|
||||
sc = ret.getSummaryCtx() and
|
||||
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
|
||||
apout = ret.getAp() and
|
||||
not ret.isHidden()
|
||||
apout = ret.getAp()
|
||||
)
|
||||
}
|
||||
|
||||
private PathNodeImpl localStepToHidden(PathNodeImpl n) {
|
||||
n.getASuccessorImpl() = result and
|
||||
result.isHidden() and
|
||||
exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
|
||||
localFlowBigStep(n1, n2, _, _, _, _) or
|
||||
store(n1, _, n2, _, _) or
|
||||
read(n1, _, n2, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3726,11 +3800,12 @@ private module Subpaths {
|
||||
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
|
||||
* `ret -> out` is summarized as the edge `arg -> out`.
|
||||
*/
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
|
||||
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
|
||||
pragma[only_bind_into](arg).getASuccessor() = par and
|
||||
pragma[only_bind_into](arg).getASuccessor() = out and
|
||||
subpaths03(arg, p, ret, o, apout) and
|
||||
subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
|
||||
not ret.isHidden() and
|
||||
par.getNodeEx() = p and
|
||||
out.getNodeEx() = o and
|
||||
out.getAp() = apout
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
import DataFlowImplCommonPublic
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural data flow analysis. This defines
|
||||
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*/
|
||||
FlowFeature getAFeature() { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
@@ -349,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -365,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -401,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
|
||||
*/
|
||||
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
|
||||
|
||||
private predicate hasSourceCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSourceCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private predicate hasSinkCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSinkCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private module Stage1 {
|
||||
class ApApprox = Unit;
|
||||
|
||||
@@ -421,7 +454,7 @@ private module Stage1 {
|
||||
not fullBarrier(node, config) and
|
||||
(
|
||||
sourceNode(node, config) and
|
||||
cc = false
|
||||
if hasSourceCallCtx(config) then cc = true else cc = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
fwdFlow(mid, cc, config) and
|
||||
@@ -551,7 +584,7 @@ private module Stage1 {
|
||||
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
|
||||
fwdFlow(node, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false
|
||||
if hasSinkCallCtx(config) then toReturn = true else toReturn = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
localFlowStep(node, mid, config) and
|
||||
@@ -937,6 +970,8 @@ private module Stage2 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1004,7 +1039,7 @@ private module Stage2 {
|
||||
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1215,7 +1250,7 @@ private module Stage2 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -1616,6 +1651,8 @@ private module Stage3 {
|
||||
|
||||
Cc ccNone() { result = false }
|
||||
|
||||
CcCall ccSomeCall() { result = true }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1697,7 +1734,7 @@ private module Stage3 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1908,7 +1945,7 @@ private module Stage3 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -2366,6 +2403,8 @@ private module Stage4 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = LocalCallContext;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -2461,7 +2500,7 @@ private module Stage4 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -2672,7 +2711,7 @@ private module Stage4 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -3064,7 +3103,11 @@ private newtype TPathNode =
|
||||
// A PathNode is introduced by a source ...
|
||||
Stage4::revFlow(node, config) and
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap = TAccessPathNil(node.getDataFlowType())
|
||||
or
|
||||
@@ -3076,17 +3119,10 @@ private newtype TPathNode =
|
||||
)
|
||||
} or
|
||||
TPathNodeSink(NodeEx node, Configuration config) {
|
||||
sinkNode(node, pragma[only_bind_into](config)) and
|
||||
Stage4::revFlow(node, pragma[only_bind_into](config)) and
|
||||
(
|
||||
// A sink that is also a source ...
|
||||
sourceNode(node, config)
|
||||
or
|
||||
// ... or a sink that can be reached from a source
|
||||
exists(PathNodeMid mid |
|
||||
pathStep(mid, node, _, _, TAccessPathNil(_)) and
|
||||
pragma[only_bind_into](config) = mid.getConfiguration()
|
||||
)
|
||||
exists(PathNodeMid sink |
|
||||
sink.isAtSink() and
|
||||
node = sink.getNodeEx() and
|
||||
config = sink.getConfiguration()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3403,22 +3439,46 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
|
||||
// an intermediate step to another intermediate node
|
||||
result = this.getSuccMid()
|
||||
or
|
||||
// a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
|
||||
exists(PathNodeMid mid, PathNodeSink sink |
|
||||
mid = this.getSuccMid() and
|
||||
mid.getNodeEx() = sink.getNodeEx() and
|
||||
mid.getAp() instanceof AccessPathNil and
|
||||
sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
|
||||
result = sink
|
||||
)
|
||||
// a final step to a sink
|
||||
result = this.getSuccMid().projectToSink()
|
||||
}
|
||||
|
||||
override predicate isSource() {
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap instanceof AccessPathNil
|
||||
}
|
||||
|
||||
predicate isAtSink() {
|
||||
sinkNode(node, config) and
|
||||
ap instanceof AccessPathNil and
|
||||
if hasSinkCallCtx(config)
|
||||
then
|
||||
// For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
|
||||
// is exactly what we need to check. This also implies
|
||||
// `sc instanceof SummaryCtxNone`.
|
||||
// For `FeatureEqualSourceSinkCallContext` the initial call context was
|
||||
// set to `CallContextSomeCall` and jumps are disallowed, so
|
||||
// `cc instanceof CallContextNoCall` never holds. On the other hand,
|
||||
// in this case there's never any need to enter a call except to identify
|
||||
// a summary, so the condition in `pathIntoCallable` enforces this, which
|
||||
// means that `sc instanceof SummaryCtxNone` holds if and only if we are
|
||||
// in the call context of the source.
|
||||
sc instanceof SummaryCtxNone or
|
||||
cc instanceof CallContextNoCall
|
||||
else any()
|
||||
}
|
||||
|
||||
PathNodeSink projectToSink() {
|
||||
this.isAtSink() and
|
||||
result.getNodeEx() = node and
|
||||
result.getConfiguration() = unbindConf(config)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3572,7 +3632,7 @@ private predicate pathIntoArg(
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate parameterCand(
|
||||
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
|
||||
) {
|
||||
@@ -3613,7 +3673,11 @@ private predicate pathIntoCallable(
|
||||
sc = TSummaryCtxSome(p, ap)
|
||||
or
|
||||
not exists(TSummaryCtxSome(p, ap)) and
|
||||
sc = TSummaryCtxNone()
|
||||
sc = TSummaryCtxNone() and
|
||||
// When the call contexts of source and sink needs to match then there's
|
||||
// never any reason to enter a callable except to find a summary. See also
|
||||
// the comment in `PathNodeMid::isAtSink`.
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
|
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
@@ -3676,13 +3740,14 @@ private module Subpaths {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate subpaths01(
|
||||
PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
||||
PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
||||
NodeEx out, AccessPath apout
|
||||
) {
|
||||
exists(Configuration config |
|
||||
pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
|
||||
pathIntoCallable(arg, par, _, innercc, sc, _, config) and
|
||||
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config))
|
||||
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
|
||||
not arg.isHidden()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3716,8 +3781,17 @@ private module Subpaths {
|
||||
innercc = ret.getCallContext() and
|
||||
sc = ret.getSummaryCtx() and
|
||||
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
|
||||
apout = ret.getAp() and
|
||||
not ret.isHidden()
|
||||
apout = ret.getAp()
|
||||
)
|
||||
}
|
||||
|
||||
private PathNodeImpl localStepToHidden(PathNodeImpl n) {
|
||||
n.getASuccessorImpl() = result and
|
||||
result.isHidden() and
|
||||
exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
|
||||
localFlowBigStep(n1, n2, _, _, _, _) or
|
||||
store(n1, _, n2, _, _) or
|
||||
read(n1, _, n2, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3726,11 +3800,12 @@ private module Subpaths {
|
||||
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
|
||||
* `ret -> out` is summarized as the edge `arg -> out`.
|
||||
*/
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
|
||||
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
|
||||
pragma[only_bind_into](arg).getASuccessor() = par and
|
||||
pragma[only_bind_into](arg).getASuccessor() = out and
|
||||
subpaths03(arg, p, ret, o, apout) and
|
||||
subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
|
||||
not ret.isHidden() and
|
||||
par.getNodeEx() = p and
|
||||
out.getNodeEx() = o and
|
||||
out.getAp() = apout
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
import DataFlowImplCommonPublic
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural data flow analysis. This defines
|
||||
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*/
|
||||
FlowFeature getAFeature() { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
@@ -349,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -365,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -401,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
|
||||
*/
|
||||
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
|
||||
|
||||
private predicate hasSourceCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSourceCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private predicate hasSinkCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSinkCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private module Stage1 {
|
||||
class ApApprox = Unit;
|
||||
|
||||
@@ -421,7 +454,7 @@ private module Stage1 {
|
||||
not fullBarrier(node, config) and
|
||||
(
|
||||
sourceNode(node, config) and
|
||||
cc = false
|
||||
if hasSourceCallCtx(config) then cc = true else cc = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
fwdFlow(mid, cc, config) and
|
||||
@@ -551,7 +584,7 @@ private module Stage1 {
|
||||
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
|
||||
fwdFlow(node, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false
|
||||
if hasSinkCallCtx(config) then toReturn = true else toReturn = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
localFlowStep(node, mid, config) and
|
||||
@@ -937,6 +970,8 @@ private module Stage2 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1004,7 +1039,7 @@ private module Stage2 {
|
||||
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1215,7 +1250,7 @@ private module Stage2 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -1616,6 +1651,8 @@ private module Stage3 {
|
||||
|
||||
Cc ccNone() { result = false }
|
||||
|
||||
CcCall ccSomeCall() { result = true }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1697,7 +1734,7 @@ private module Stage3 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1908,7 +1945,7 @@ private module Stage3 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -2366,6 +2403,8 @@ private module Stage4 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = LocalCallContext;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -2461,7 +2500,7 @@ private module Stage4 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -2672,7 +2711,7 @@ private module Stage4 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -3064,7 +3103,11 @@ private newtype TPathNode =
|
||||
// A PathNode is introduced by a source ...
|
||||
Stage4::revFlow(node, config) and
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap = TAccessPathNil(node.getDataFlowType())
|
||||
or
|
||||
@@ -3076,17 +3119,10 @@ private newtype TPathNode =
|
||||
)
|
||||
} or
|
||||
TPathNodeSink(NodeEx node, Configuration config) {
|
||||
sinkNode(node, pragma[only_bind_into](config)) and
|
||||
Stage4::revFlow(node, pragma[only_bind_into](config)) and
|
||||
(
|
||||
// A sink that is also a source ...
|
||||
sourceNode(node, config)
|
||||
or
|
||||
// ... or a sink that can be reached from a source
|
||||
exists(PathNodeMid mid |
|
||||
pathStep(mid, node, _, _, TAccessPathNil(_)) and
|
||||
pragma[only_bind_into](config) = mid.getConfiguration()
|
||||
)
|
||||
exists(PathNodeMid sink |
|
||||
sink.isAtSink() and
|
||||
node = sink.getNodeEx() and
|
||||
config = sink.getConfiguration()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3403,22 +3439,46 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
|
||||
// an intermediate step to another intermediate node
|
||||
result = this.getSuccMid()
|
||||
or
|
||||
// a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
|
||||
exists(PathNodeMid mid, PathNodeSink sink |
|
||||
mid = this.getSuccMid() and
|
||||
mid.getNodeEx() = sink.getNodeEx() and
|
||||
mid.getAp() instanceof AccessPathNil and
|
||||
sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
|
||||
result = sink
|
||||
)
|
||||
// a final step to a sink
|
||||
result = this.getSuccMid().projectToSink()
|
||||
}
|
||||
|
||||
override predicate isSource() {
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap instanceof AccessPathNil
|
||||
}
|
||||
|
||||
predicate isAtSink() {
|
||||
sinkNode(node, config) and
|
||||
ap instanceof AccessPathNil and
|
||||
if hasSinkCallCtx(config)
|
||||
then
|
||||
// For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
|
||||
// is exactly what we need to check. This also implies
|
||||
// `sc instanceof SummaryCtxNone`.
|
||||
// For `FeatureEqualSourceSinkCallContext` the initial call context was
|
||||
// set to `CallContextSomeCall` and jumps are disallowed, so
|
||||
// `cc instanceof CallContextNoCall` never holds. On the other hand,
|
||||
// in this case there's never any need to enter a call except to identify
|
||||
// a summary, so the condition in `pathIntoCallable` enforces this, which
|
||||
// means that `sc instanceof SummaryCtxNone` holds if and only if we are
|
||||
// in the call context of the source.
|
||||
sc instanceof SummaryCtxNone or
|
||||
cc instanceof CallContextNoCall
|
||||
else any()
|
||||
}
|
||||
|
||||
PathNodeSink projectToSink() {
|
||||
this.isAtSink() and
|
||||
result.getNodeEx() = node and
|
||||
result.getConfiguration() = unbindConf(config)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3572,7 +3632,7 @@ private predicate pathIntoArg(
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate parameterCand(
|
||||
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
|
||||
) {
|
||||
@@ -3613,7 +3673,11 @@ private predicate pathIntoCallable(
|
||||
sc = TSummaryCtxSome(p, ap)
|
||||
or
|
||||
not exists(TSummaryCtxSome(p, ap)) and
|
||||
sc = TSummaryCtxNone()
|
||||
sc = TSummaryCtxNone() and
|
||||
// When the call contexts of source and sink needs to match then there's
|
||||
// never any reason to enter a callable except to find a summary. See also
|
||||
// the comment in `PathNodeMid::isAtSink`.
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
|
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
@@ -3676,13 +3740,14 @@ private module Subpaths {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate subpaths01(
|
||||
PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
||||
PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
||||
NodeEx out, AccessPath apout
|
||||
) {
|
||||
exists(Configuration config |
|
||||
pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
|
||||
pathIntoCallable(arg, par, _, innercc, sc, _, config) and
|
||||
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config))
|
||||
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
|
||||
not arg.isHidden()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3716,8 +3781,17 @@ private module Subpaths {
|
||||
innercc = ret.getCallContext() and
|
||||
sc = ret.getSummaryCtx() and
|
||||
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
|
||||
apout = ret.getAp() and
|
||||
not ret.isHidden()
|
||||
apout = ret.getAp()
|
||||
)
|
||||
}
|
||||
|
||||
private PathNodeImpl localStepToHidden(PathNodeImpl n) {
|
||||
n.getASuccessorImpl() = result and
|
||||
result.isHidden() and
|
||||
exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
|
||||
localFlowBigStep(n1, n2, _, _, _, _) or
|
||||
store(n1, _, n2, _, _) or
|
||||
read(n1, _, n2, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3726,11 +3800,12 @@ private module Subpaths {
|
||||
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
|
||||
* `ret -> out` is summarized as the edge `arg -> out`.
|
||||
*/
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
|
||||
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
|
||||
pragma[only_bind_into](arg).getASuccessor() = par and
|
||||
pragma[only_bind_into](arg).getASuccessor() = out and
|
||||
subpaths03(arg, p, ret, o, apout) and
|
||||
subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
|
||||
not ret.isHidden() and
|
||||
par.getNodeEx() = p and
|
||||
out.getNodeEx() = o and
|
||||
out.getAp() = apout
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
private import DataFlowImplCommon
|
||||
private import DataFlowImplSpecific::Private
|
||||
import DataFlowImplSpecific::Public
|
||||
import DataFlowImplCommonPublic
|
||||
|
||||
/**
|
||||
* A configuration of interprocedural data flow analysis. This defines
|
||||
@@ -94,6 +95,22 @@ abstract class Configuration extends string {
|
||||
*/
|
||||
int fieldFlowBranchLimit() { result = 2 }
|
||||
|
||||
/**
|
||||
* Gets a data flow configuration feature to add restrictions to the set of
|
||||
* valid flow paths.
|
||||
*
|
||||
* - `FeatureHasSourceCallContext`:
|
||||
* Assume that sources have some existing call context to disallow
|
||||
* conflicting return-flow directly following the source.
|
||||
* - `FeatureHasSinkCallContext`:
|
||||
* Assume that sinks have some existing call context to disallow
|
||||
* conflicting argument-to-parameter flow directly preceding the sink.
|
||||
* - `FeatureEqualSourceSinkCallContext`:
|
||||
* Implies both of the above and additionally ensures that the entire flow
|
||||
* path preserves the call context.
|
||||
*/
|
||||
FlowFeature getAFeature() { none() }
|
||||
|
||||
/**
|
||||
* Holds if data may flow from `source` to `sink` for this configuration.
|
||||
*/
|
||||
@@ -349,7 +366,8 @@ private predicate jumpStep(NodeEx node1, NodeEx node2, Configuration config) {
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -365,7 +383,8 @@ private predicate additionalJumpStep(NodeEx node1, NodeEx node2, Configuration c
|
||||
not outBarrier(node1, config) and
|
||||
not inBarrier(node2, config) and
|
||||
not fullBarrier(node1, config) and
|
||||
not fullBarrier(node2, config)
|
||||
not fullBarrier(node2, config) and
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
@@ -401,6 +420,20 @@ private predicate viableParamArgEx(DataFlowCall call, ParamNodeEx p, ArgNodeEx a
|
||||
*/
|
||||
private predicate useFieldFlow(Configuration config) { config.fieldFlowBranchLimit() >= 1 }
|
||||
|
||||
private predicate hasSourceCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSourceCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private predicate hasSinkCallCtx(Configuration config) {
|
||||
exists(FlowFeature feature | feature = config.getAFeature() |
|
||||
feature instanceof FeatureHasSinkCallContext or
|
||||
feature instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
}
|
||||
|
||||
private module Stage1 {
|
||||
class ApApprox = Unit;
|
||||
|
||||
@@ -421,7 +454,7 @@ private module Stage1 {
|
||||
not fullBarrier(node, config) and
|
||||
(
|
||||
sourceNode(node, config) and
|
||||
cc = false
|
||||
if hasSourceCallCtx(config) then cc = true else cc = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
fwdFlow(mid, cc, config) and
|
||||
@@ -551,7 +584,7 @@ private module Stage1 {
|
||||
private predicate revFlow0(NodeEx node, boolean toReturn, Configuration config) {
|
||||
fwdFlow(node, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false
|
||||
if hasSinkCallCtx(config) then toReturn = true else toReturn = false
|
||||
or
|
||||
exists(NodeEx mid |
|
||||
localFlowStep(node, mid, config) and
|
||||
@@ -937,6 +970,8 @@ private module Stage2 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1004,7 +1039,7 @@ private module Stage2 {
|
||||
predicate fwdFlow(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1215,7 +1250,7 @@ private module Stage2 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -1616,6 +1651,8 @@ private module Stage3 {
|
||||
|
||||
Cc ccNone() { result = false }
|
||||
|
||||
CcCall ccSomeCall() { result = true }
|
||||
|
||||
private class LocalCc = Unit;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -1697,7 +1734,7 @@ private module Stage3 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -1908,7 +1945,7 @@ private module Stage3 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -2366,6 +2403,8 @@ private module Stage4 {
|
||||
|
||||
Cc ccNone() { result instanceof CallContextAny }
|
||||
|
||||
CcCall ccSomeCall() { result instanceof CallContextSomeCall }
|
||||
|
||||
private class LocalCc = LocalCallContext;
|
||||
|
||||
bindingset[call, c, outercc]
|
||||
@@ -2461,7 +2500,7 @@ private module Stage4 {
|
||||
private predicate fwdFlow0(NodeEx node, Cc cc, ApOption argAp, Ap ap, Configuration config) {
|
||||
flowCand(node, _, config) and
|
||||
sourceNode(node, config) and
|
||||
cc = ccNone() and
|
||||
(if hasSourceCallCtx(config) then cc = ccSomeCall() else cc = ccNone()) and
|
||||
argAp = apNone() and
|
||||
ap = getApNil(node)
|
||||
or
|
||||
@@ -2672,7 +2711,7 @@ private module Stage4 {
|
||||
) {
|
||||
fwdFlow(node, _, _, ap, config) and
|
||||
sinkNode(node, config) and
|
||||
toReturn = false and
|
||||
(if hasSinkCallCtx(config) then toReturn = true else toReturn = false) and
|
||||
returnAp = apNone() and
|
||||
ap instanceof ApNil
|
||||
or
|
||||
@@ -3064,7 +3103,11 @@ private newtype TPathNode =
|
||||
// A PathNode is introduced by a source ...
|
||||
Stage4::revFlow(node, config) and
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap = TAccessPathNil(node.getDataFlowType())
|
||||
or
|
||||
@@ -3076,17 +3119,10 @@ private newtype TPathNode =
|
||||
)
|
||||
} or
|
||||
TPathNodeSink(NodeEx node, Configuration config) {
|
||||
sinkNode(node, pragma[only_bind_into](config)) and
|
||||
Stage4::revFlow(node, pragma[only_bind_into](config)) and
|
||||
(
|
||||
// A sink that is also a source ...
|
||||
sourceNode(node, config)
|
||||
or
|
||||
// ... or a sink that can be reached from a source
|
||||
exists(PathNodeMid mid |
|
||||
pathStep(mid, node, _, _, TAccessPathNil(_)) and
|
||||
pragma[only_bind_into](config) = mid.getConfiguration()
|
||||
)
|
||||
exists(PathNodeMid sink |
|
||||
sink.isAtSink() and
|
||||
node = sink.getNodeEx() and
|
||||
config = sink.getConfiguration()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3403,22 +3439,46 @@ private class PathNodeMid extends PathNodeImpl, TPathNodeMid {
|
||||
// an intermediate step to another intermediate node
|
||||
result = this.getSuccMid()
|
||||
or
|
||||
// a final step to a sink via zero steps means we merge the last two steps to prevent trivial-looking edges
|
||||
exists(PathNodeMid mid, PathNodeSink sink |
|
||||
mid = this.getSuccMid() and
|
||||
mid.getNodeEx() = sink.getNodeEx() and
|
||||
mid.getAp() instanceof AccessPathNil and
|
||||
sink.getConfiguration() = unbindConf(mid.getConfiguration()) and
|
||||
result = sink
|
||||
)
|
||||
// a final step to a sink
|
||||
result = this.getSuccMid().projectToSink()
|
||||
}
|
||||
|
||||
override predicate isSource() {
|
||||
sourceNode(node, config) and
|
||||
cc instanceof CallContextAny and
|
||||
(
|
||||
if hasSourceCallCtx(config)
|
||||
then cc instanceof CallContextSomeCall
|
||||
else cc instanceof CallContextAny
|
||||
) and
|
||||
sc instanceof SummaryCtxNone and
|
||||
ap instanceof AccessPathNil
|
||||
}
|
||||
|
||||
predicate isAtSink() {
|
||||
sinkNode(node, config) and
|
||||
ap instanceof AccessPathNil and
|
||||
if hasSinkCallCtx(config)
|
||||
then
|
||||
// For `FeatureHasSinkCallContext` the condition `cc instanceof CallContextNoCall`
|
||||
// is exactly what we need to check. This also implies
|
||||
// `sc instanceof SummaryCtxNone`.
|
||||
// For `FeatureEqualSourceSinkCallContext` the initial call context was
|
||||
// set to `CallContextSomeCall` and jumps are disallowed, so
|
||||
// `cc instanceof CallContextNoCall` never holds. On the other hand,
|
||||
// in this case there's never any need to enter a call except to identify
|
||||
// a summary, so the condition in `pathIntoCallable` enforces this, which
|
||||
// means that `sc instanceof SummaryCtxNone` holds if and only if we are
|
||||
// in the call context of the source.
|
||||
sc instanceof SummaryCtxNone or
|
||||
cc instanceof CallContextNoCall
|
||||
else any()
|
||||
}
|
||||
|
||||
PathNodeSink projectToSink() {
|
||||
this.isAtSink() and
|
||||
result.getNodeEx() = node and
|
||||
result.getConfiguration() = unbindConf(config)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3572,7 +3632,7 @@ private predicate pathIntoArg(
|
||||
)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
pragma[nomagic]
|
||||
private predicate parameterCand(
|
||||
DataFlowCallable callable, int i, AccessPathApprox apa, Configuration config
|
||||
) {
|
||||
@@ -3613,7 +3673,11 @@ private predicate pathIntoCallable(
|
||||
sc = TSummaryCtxSome(p, ap)
|
||||
or
|
||||
not exists(TSummaryCtxSome(p, ap)) and
|
||||
sc = TSummaryCtxNone()
|
||||
sc = TSummaryCtxNone() and
|
||||
// When the call contexts of source and sink needs to match then there's
|
||||
// never any reason to enter a callable except to find a summary. See also
|
||||
// the comment in `PathNodeMid::isAtSink`.
|
||||
not config.getAFeature() instanceof FeatureEqualSourceSinkCallContext
|
||||
)
|
||||
|
|
||||
if recordDataFlowCallSite(call, callable)
|
||||
@@ -3676,13 +3740,14 @@ private module Subpaths {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate subpaths01(
|
||||
PathNode arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
||||
PathNodeImpl arg, ParamNodeEx par, SummaryCtxSome sc, CallContext innercc, ReturnKindExt kind,
|
||||
NodeEx out, AccessPath apout
|
||||
) {
|
||||
exists(Configuration config |
|
||||
pathThroughCallable(arg, out, _, pragma[only_bind_into](apout)) and
|
||||
pathIntoCallable(arg, par, _, innercc, sc, _, config) and
|
||||
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config))
|
||||
paramFlowsThrough(kind, innercc, sc, pragma[only_bind_into](apout), _, unbindConf(config)) and
|
||||
not arg.isHidden()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3716,8 +3781,17 @@ private module Subpaths {
|
||||
innercc = ret.getCallContext() and
|
||||
sc = ret.getSummaryCtx() and
|
||||
ret.getConfiguration() = unbindConf(getPathNodeConf(arg)) and
|
||||
apout = ret.getAp() and
|
||||
not ret.isHidden()
|
||||
apout = ret.getAp()
|
||||
)
|
||||
}
|
||||
|
||||
private PathNodeImpl localStepToHidden(PathNodeImpl n) {
|
||||
n.getASuccessorImpl() = result and
|
||||
result.isHidden() and
|
||||
exists(NodeEx n1, NodeEx n2 | n1 = n.getNodeEx() and n2 = result.getNodeEx() |
|
||||
localFlowBigStep(n1, n2, _, _, _, _) or
|
||||
store(n1, _, n2, _, _) or
|
||||
read(n1, _, n2, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3726,11 +3800,12 @@ private module Subpaths {
|
||||
* a subpath between `par` and `ret` with the connecting edges `arg -> par` and
|
||||
* `ret -> out` is summarized as the edge `arg -> out`.
|
||||
*/
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeMid ret, PathNodeMid out) {
|
||||
predicate subpaths(PathNode arg, PathNodeImpl par, PathNodeImpl ret, PathNodeMid out) {
|
||||
exists(ParamNodeEx p, NodeEx o, AccessPath apout |
|
||||
pragma[only_bind_into](arg).getASuccessor() = par and
|
||||
pragma[only_bind_into](arg).getASuccessor() = out and
|
||||
subpaths03(arg, p, ret, o, apout) and
|
||||
subpaths03(arg, p, localStepToHidden*(ret), o, apout) and
|
||||
not ret.isHidden() and
|
||||
par.getNodeEx() = p and
|
||||
out.getNodeEx() = o and
|
||||
out.getAp() = apout
|
||||
|
||||
@@ -2,6 +2,42 @@ private import DataFlowImplSpecific::Private
|
||||
private import DataFlowImplSpecific::Public
|
||||
import Cached
|
||||
|
||||
module DataFlowImplCommonPublic {
|
||||
private newtype TFlowFeature =
|
||||
TFeatureHasSourceCallContext() or
|
||||
TFeatureHasSinkCallContext() or
|
||||
TFeatureEqualSourceSinkCallContext()
|
||||
|
||||
/** A flow configuration feature for use in `Configuration::getAFeature()`. */
|
||||
class FlowFeature extends TFlowFeature {
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow configuration feature that implies that sources have some existing
|
||||
* call context.
|
||||
*/
|
||||
class FeatureHasSourceCallContext extends FlowFeature, TFeatureHasSourceCallContext {
|
||||
override string toString() { result = "FeatureHasSourceCallContext" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow configuration feature that implies that sinks have some existing
|
||||
* call context.
|
||||
*/
|
||||
class FeatureHasSinkCallContext extends FlowFeature, TFeatureHasSinkCallContext {
|
||||
override string toString() { result = "FeatureHasSinkCallContext" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A flow configuration feature that implies that source-sink pairs have some
|
||||
* shared existing call context.
|
||||
*/
|
||||
class FeatureEqualSourceSinkCallContext extends FlowFeature, TFeatureEqualSourceSinkCallContext {
|
||||
override string toString() { result = "FeatureEqualSourceSinkCallContext" }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The cost limits for the `AccessPathFront` to `AccessPathApprox` expansion.
|
||||
*
|
||||
@@ -251,7 +287,7 @@ private module Cached {
|
||||
predicate forceCachingInSameStage() { any() }
|
||||
|
||||
cached
|
||||
predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = n.getEnclosingCallable() }
|
||||
predicate nodeEnclosingCallable(Node n, DataFlowCallable c) { c = nodeGetEnclosingCallable(n) }
|
||||
|
||||
cached
|
||||
predicate callEnclosingCallable(DataFlowCall call, DataFlowCallable c) {
|
||||
@@ -316,9 +352,7 @@ private module Cached {
|
||||
}
|
||||
|
||||
cached
|
||||
predicate parameterNode(Node n, DataFlowCallable c, int i) {
|
||||
n.(ParameterNode).isParameterOf(c, i)
|
||||
}
|
||||
predicate parameterNode(Node p, DataFlowCallable c, int pos) { isParameterNode(p, c, pos) }
|
||||
|
||||
cached
|
||||
predicate argumentNode(Node n, DataFlowCall call, int pos) {
|
||||
|
||||
@@ -31,7 +31,7 @@ module Consistency {
|
||||
query predicate uniqueEnclosingCallable(Node n, string msg) {
|
||||
exists(int c |
|
||||
n instanceof RelevantNode and
|
||||
c = count(n.getEnclosingCallable()) and
|
||||
c = count(nodeGetEnclosingCallable(n)) and
|
||||
c != 1 and
|
||||
msg = "Node should have one enclosing callable but has " + c + "."
|
||||
)
|
||||
@@ -85,13 +85,13 @@ module Consistency {
|
||||
}
|
||||
|
||||
query predicate parameterCallable(ParameterNode p, string msg) {
|
||||
exists(DataFlowCallable c | p.isParameterOf(c, _) and c != p.getEnclosingCallable()) and
|
||||
exists(DataFlowCallable c | isParameterNode(p, c, _) and c != nodeGetEnclosingCallable(p)) and
|
||||
msg = "Callable mismatch for parameter."
|
||||
}
|
||||
|
||||
query predicate localFlowIsLocal(Node n1, Node n2, string msg) {
|
||||
simpleLocalFlowStep(n1, n2) and
|
||||
n1.getEnclosingCallable() != n2.getEnclosingCallable() and
|
||||
nodeGetEnclosingCallable(n1) != nodeGetEnclosingCallable(n2) and
|
||||
msg = "Local flow step does not preserve enclosing callable."
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ module Consistency {
|
||||
query predicate unreachableNodeCCtx(Node n, DataFlowCall call, string msg) {
|
||||
isUnreachableInCall(n, call) and
|
||||
exists(DataFlowCallable c |
|
||||
c = n.getEnclosingCallable() and
|
||||
c = nodeGetEnclosingCallable(n) and
|
||||
not viableCallable(call) = c
|
||||
) and
|
||||
msg = "Call context for isUnreachableInCall is inconsistent with call graph."
|
||||
@@ -120,7 +120,7 @@ module Consistency {
|
||||
n.(ArgumentNode).argumentOf(call, _) and
|
||||
msg = "ArgumentNode and call does not share enclosing callable."
|
||||
) and
|
||||
n.getEnclosingCallable() != call.getEnclosingCallable()
|
||||
nodeGetEnclosingCallable(n) != call.getEnclosingCallable()
|
||||
}
|
||||
|
||||
// This predicate helps the compiler forget that in some languages
|
||||
@@ -151,7 +151,7 @@ module Consistency {
|
||||
}
|
||||
|
||||
query predicate postIsInSameCallable(PostUpdateNode n, string msg) {
|
||||
n.getEnclosingCallable() != n.getPreUpdateNode().getEnclosingCallable() and
|
||||
nodeGetEnclosingCallable(n) != nodeGetEnclosingCallable(n.getPreUpdateNode()) and
|
||||
msg = "PostUpdateNode does not share callable with its pre-update node."
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,12 @@ private import DataFlowUtil
|
||||
private import semmle.code.cpp.ir.IR
|
||||
private import DataFlowDispatch
|
||||
|
||||
/** Gets the callable in which this node occurs. */
|
||||
DataFlowCallable nodeGetEnclosingCallable(Node n) { result = n.getEnclosingCallable() }
|
||||
|
||||
/** Holds if `p` is a `ParameterNode` of `c` with position `pos`. */
|
||||
predicate isParameterNode(ParameterNode p, DataFlowCallable c, int pos) { p.isParameterOf(c, pos) }
|
||||
|
||||
/**
|
||||
* A data flow node that occurs as the argument of a call and is passed as-is
|
||||
* to the callable. Instance arguments (`this` pointer) and read side effects
|
||||
@@ -106,11 +112,9 @@ class ReturnNode extends InstructionNode {
|
||||
Instruction primary;
|
||||
|
||||
ReturnNode() {
|
||||
exists(ReturnValueInstruction ret | instr = ret.getReturnValue() and primary = ret)
|
||||
exists(ReturnValueInstruction ret | instr = ret and primary = ret)
|
||||
or
|
||||
exists(ReturnIndirectionInstruction rii |
|
||||
instr = rii.getSideEffectOperand().getAnyDef() and primary = rii
|
||||
)
|
||||
exists(ReturnIndirectionInstruction rii | instr = rii and primary = rii)
|
||||
}
|
||||
|
||||
/** Gets the kind of this returned value. */
|
||||
@@ -184,108 +188,16 @@ OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) {
|
||||
*/
|
||||
predicate jumpStep(Node n1, Node n2) { none() }
|
||||
|
||||
private predicate fieldStoreStepNoChi(Node node1, FieldContent f, PostUpdateNode node2) {
|
||||
exists(StoreInstruction store, Class c |
|
||||
store = node2.asInstruction() and
|
||||
store.getSourceValueOperand() = node1.asOperand() and
|
||||
getWrittenField(store, f.(FieldContent).getAField(), c) and
|
||||
f.hasOffset(c, _, _)
|
||||
)
|
||||
}
|
||||
|
||||
private FieldAddressInstruction getFieldInstruction(Instruction instr) {
|
||||
result = instr or
|
||||
result = instr.(CopyValueInstruction).getUnary()
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate getWrittenField(Instruction instr, Field f, Class c) {
|
||||
exists(FieldAddressInstruction fa |
|
||||
fa =
|
||||
getFieldInstruction([
|
||||
instr.(StoreInstruction).getDestinationAddress(),
|
||||
instr.(WriteSideEffectInstruction).getDestinationAddress()
|
||||
]) and
|
||||
f = fa.getField() and
|
||||
c = f.getDeclaringType()
|
||||
)
|
||||
}
|
||||
|
||||
private predicate fieldStoreStepChi(Node node1, FieldContent f, PostUpdateNode node2) {
|
||||
exists(ChiPartialOperand operand, ChiInstruction chi |
|
||||
chi.getPartialOperand() = operand and
|
||||
node1.asOperand() = operand and
|
||||
node2.asInstruction() = chi and
|
||||
exists(Class c |
|
||||
c = chi.getResultType() and
|
||||
exists(int startBit, int endBit |
|
||||
chi.getUpdatedInterval(startBit, endBit) and
|
||||
f.hasOffset(c, startBit, endBit)
|
||||
)
|
||||
or
|
||||
getWrittenField(operand.getDef(), f.getAField(), c) and
|
||||
f.hasOffset(c, _, _)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate arrayStoreStepChi(Node node1, ArrayContent a, PostUpdateNode node2) {
|
||||
exists(a) and
|
||||
exists(ChiPartialOperand operand, ChiInstruction chi, StoreInstruction store |
|
||||
chi.getPartialOperand() = operand and
|
||||
store = operand.getDef() and
|
||||
node1.asOperand() = operand and
|
||||
// This `ChiInstruction` will always have a non-conflated result because both `ArrayStoreNode`
|
||||
// and `PointerStoreNode` require it in their characteristic predicates.
|
||||
node2.asInstruction() = chi and
|
||||
(
|
||||
// `x[i] = taint()`
|
||||
// This matches the characteristic predicate in `ArrayStoreNode`.
|
||||
store.getDestinationAddress() instanceof PointerAddInstruction
|
||||
or
|
||||
// `*p = taint()`
|
||||
// This matches the characteristic predicate in `PointerStoreNode`.
|
||||
store.getDestinationAddress().(CopyValueInstruction).getUnary() instanceof LoadInstruction
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `node1` to `node2` via an assignment to `f`.
|
||||
* Thus, `node2` references an object with a field `f` that contains the
|
||||
* value of `node1`.
|
||||
*/
|
||||
predicate storeStep(Node node1, Content f, PostUpdateNode node2) {
|
||||
fieldStoreStepNoChi(node1, f, node2) or
|
||||
fieldStoreStepChi(node1, f, node2) or
|
||||
arrayStoreStepChi(node1, f, node2) or
|
||||
fieldStoreStepAfterArraySuppression(node1, f, node2)
|
||||
}
|
||||
|
||||
// This predicate pushes the correct `FieldContent` onto the access path when the
|
||||
// `suppressArrayRead` predicate has popped off an `ArrayContent`.
|
||||
private predicate fieldStoreStepAfterArraySuppression(
|
||||
Node node1, FieldContent f, PostUpdateNode node2
|
||||
) {
|
||||
exists(WriteSideEffectInstruction write, ChiInstruction chi, Class c |
|
||||
not chi.isResultConflated() and
|
||||
node1.asInstruction() = chi and
|
||||
node2.asInstruction() = chi and
|
||||
chi.getPartial() = write and
|
||||
getWrittenField(write, f.getAField(), c) and
|
||||
f.hasOffset(c, _, _)
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[result, i]
|
||||
private int unbindInt(int i) { i <= result and i >= result }
|
||||
|
||||
pragma[noinline]
|
||||
private predicate getLoadedField(LoadInstruction load, Field f, Class c) {
|
||||
exists(FieldAddressInstruction fa |
|
||||
fa = load.getSourceAddress() and
|
||||
f = fa.getField() and
|
||||
c = f.getDeclaringType()
|
||||
predicate storeStep(StoreNodeInstr node1, FieldContent f, StoreNodeInstr node2) {
|
||||
exists(FieldAddressInstruction fai |
|
||||
node1.getInstruction() = fai and
|
||||
node2.getInstruction() = fai.getObjectAddress() and
|
||||
f.getField() = fai.getField()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -294,122 +206,14 @@ private predicate getLoadedField(LoadInstruction load, Field f, Class c) {
|
||||
* Thus, `node1` references an object with a field `f` whose value ends up in
|
||||
* `node2`.
|
||||
*/
|
||||
private predicate fieldReadStep(Node node1, FieldContent f, Node node2) {
|
||||
exists(LoadOperand operand |
|
||||
node2.asOperand() = operand and
|
||||
node1.asInstruction() = operand.getAnyDef() and
|
||||
exists(Class c |
|
||||
c = operand.getAnyDef().getResultType() and
|
||||
exists(int startBit, int endBit |
|
||||
operand.getUsedInterval(unbindInt(startBit), unbindInt(endBit)) and
|
||||
f.hasOffset(c, startBit, endBit)
|
||||
)
|
||||
or
|
||||
getLoadedField(operand.getUse(), f.getAField(), c) and
|
||||
f.hasOffset(c, _, _)
|
||||
)
|
||||
predicate readStep(ReadNode node1, FieldContent f, ReadNode node2) {
|
||||
exists(FieldAddressInstruction fai |
|
||||
node1.getInstruction() = fai.getObjectAddress() and
|
||||
node2.getInstruction() = fai and
|
||||
f.getField() = fai.getField()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* When a store step happens in a function that looks like an array write such as:
|
||||
* ```cpp
|
||||
* void f(int* pa) {
|
||||
* pa = source();
|
||||
* }
|
||||
* ```
|
||||
* it can be a write to an array, but it can also happen that `f` is called as `f(&a.x)`. If that is
|
||||
* the case, the `ArrayContent` that was written by the call to `f` should be popped off the access
|
||||
* path, and a `FieldContent` containing `x` should be pushed instead.
|
||||
* So this case pops `ArrayContent` off the access path, and the `fieldStoreStepAfterArraySuppression`
|
||||
* predicate in `storeStep` ensures that we push the right `FieldContent` onto the access path.
|
||||
*/
|
||||
predicate suppressArrayRead(Node node1, ArrayContent a, Node node2) {
|
||||
exists(a) and
|
||||
exists(WriteSideEffectInstruction write, ChiInstruction chi |
|
||||
node1.asInstruction() = write and
|
||||
node2.asInstruction() = chi and
|
||||
chi.getPartial() = write and
|
||||
getWrittenField(write, _, _)
|
||||
)
|
||||
}
|
||||
|
||||
private class ArrayToPointerConvertInstruction extends ConvertInstruction {
|
||||
ArrayToPointerConvertInstruction() {
|
||||
this.getUnary().getResultType() instanceof ArrayType and
|
||||
this.getResultType() instanceof PointerType
|
||||
}
|
||||
}
|
||||
|
||||
private Instruction skipOneCopyValueInstructionRec(CopyValueInstruction copy) {
|
||||
copy.getUnary() = result and not result instanceof CopyValueInstruction
|
||||
or
|
||||
result = skipOneCopyValueInstructionRec(copy.getUnary())
|
||||
}
|
||||
|
||||
private Instruction skipCopyValueInstructions(Operand op) {
|
||||
not result instanceof CopyValueInstruction and result = op.getDef()
|
||||
or
|
||||
result = skipOneCopyValueInstructionRec(op.getDef())
|
||||
}
|
||||
|
||||
private predicate arrayReadStep(Node node1, ArrayContent a, Node node2) {
|
||||
exists(a) and
|
||||
// Explicit dereferences such as `*p` or `p[i]` where `p` is a pointer or array.
|
||||
exists(LoadOperand operand, Instruction address |
|
||||
operand.isDefinitionInexact() and
|
||||
node1.asInstruction() = operand.getAnyDef() and
|
||||
operand = node2.asOperand() and
|
||||
address = skipCopyValueInstructions(operand.getAddressOperand()) and
|
||||
(
|
||||
address instanceof LoadInstruction or
|
||||
address instanceof ArrayToPointerConvertInstruction or
|
||||
address instanceof PointerOffsetInstruction
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* In cases such as:
|
||||
* ```cpp
|
||||
* void f(int* pa) {
|
||||
* *pa = source();
|
||||
* }
|
||||
* ...
|
||||
* int x;
|
||||
* f(&x);
|
||||
* use(x);
|
||||
* ```
|
||||
* the load on `x` in `use(x)` will exactly overlap with its definition (in this case the definition
|
||||
* is a `WriteSideEffect`). This predicate pops the `ArrayContent` (pushed by the store in `f`)
|
||||
* from the access path.
|
||||
*/
|
||||
private predicate exactReadStep(Node node1, ArrayContent a, Node node2) {
|
||||
exists(a) and
|
||||
exists(WriteSideEffectInstruction write, ChiInstruction chi |
|
||||
not chi.isResultConflated() and
|
||||
chi.getPartial() = write and
|
||||
node1.asInstruction() = write and
|
||||
node2.asInstruction() = chi and
|
||||
// To distinquish this case from the `arrayReadStep` case we require that the entire variable was
|
||||
// overwritten by the `WriteSideEffectInstruction` (i.e., there is a load that reads the
|
||||
// entire variable).
|
||||
exists(LoadInstruction load | load.getSourceValue() = chi)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if data can flow from `node1` to `node2` via a read of `f`.
|
||||
* Thus, `node1` references an object with a field `f` whose value ends up in
|
||||
* `node2`.
|
||||
*/
|
||||
predicate readStep(Node node1, Content f, Node node2) {
|
||||
fieldReadStep(node1, f, node2) or
|
||||
arrayReadStep(node1, f, node2) or
|
||||
exactReadStep(node1, f, node2) or
|
||||
suppressArrayRead(node1, f, node2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if values stored inside content `c` are cleared at node `n`.
|
||||
*/
|
||||
@@ -441,7 +245,7 @@ private predicate suppressUnusedNode(Node n) { any() }
|
||||
// Java QL library compatibility wrappers
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
/** A node that performs a type cast. */
|
||||
class CastNode extends InstructionNode {
|
||||
class CastNode extends Node {
|
||||
CastNode() { none() } // stub implementation
|
||||
}
|
||||
|
||||
@@ -495,7 +299,17 @@ predicate isImmutableOrUnobservable(Node n) {
|
||||
}
|
||||
|
||||
/** Holds if `n` should be hidden from path explanations. */
|
||||
predicate nodeIsHidden(Node n) { n instanceof OperandNode and not n instanceof ArgumentNode }
|
||||
predicate nodeIsHidden(Node n) {
|
||||
n instanceof OperandNode and not n instanceof ArgumentNode
|
||||
or
|
||||
StoreNodeFlow::flowThrough(n, _) and
|
||||
not StoreNodeFlow::flowOutOf(n, _) and
|
||||
not StoreNodeFlow::flowInto(_, n)
|
||||
or
|
||||
ReadNodeFlow::flowThrough(n, _) and
|
||||
not ReadNodeFlow::flowOutOf(n, _) and
|
||||
not ReadNodeFlow::flowInto(_, n)
|
||||
}
|
||||
|
||||
class LambdaCallKind = Unit;
|
||||
|
||||
|
||||
@@ -10,19 +10,78 @@ private import semmle.code.cpp.ir.ValueNumbering
|
||||
private import semmle.code.cpp.ir.IR
|
||||
private import semmle.code.cpp.controlflow.IRGuards
|
||||
private import semmle.code.cpp.models.interfaces.DataFlow
|
||||
private import DataFlowPrivate
|
||||
private import SsaInternals as Ssa
|
||||
|
||||
cached
|
||||
private module Cached {
|
||||
/**
|
||||
* The IR dataflow graph consists of the following nodes:
|
||||
* - `InstructionNode`, which represents an `Instruction` in the graph.
|
||||
* - `OperandNode`, which represents an `Operand` in the graph.
|
||||
* - `VariableNode`, which is used to model global variables.
|
||||
* - Two kinds of `StoreNode`s:
|
||||
* 1. `StoreNodeInstr`, which represents the value of an address computed by an `Instruction` that
|
||||
* has been updated by a write operation.
|
||||
* 2. `StoreNodeOperand`, which represents the value of an address in an `ArgumentOperand` after a
|
||||
* function call that may have changed the value.
|
||||
* - `ReadNode`, which represents the result of reading a field of an object.
|
||||
* - `SsaPhiNode`, which represents phi nodes as computed by the shared SSA library.
|
||||
*
|
||||
* The following section describes how flow is generally transferred between these nodes:
|
||||
* - Flow between `InstructionNode`s and `OperandNode`s follow the def-use information as computed by
|
||||
* the IR. Because the IR compute must-alias information for memory operands, we only follow def-use
|
||||
* flow for register operands.
|
||||
* - Flow can enter a `StoreNode` in two ways (both done in `StoreNode.flowInto`):
|
||||
* 1. Flow is transferred from a `StoreValueOperand` to a `StoreNodeInstr`. Flow will then proceed
|
||||
* along the chain of addresses computed by `StoreNodeInstr.getInner` to identify field writes
|
||||
* and call `storeStep` accordingly (i.e., for an expression like `a.b.c = x`, we visit `c`, then
|
||||
* `b`, then `a`).
|
||||
* 2. Flow is transfered from a `WriteSideEffectInstruction` to a `StoreNodeOperand` after flow
|
||||
* returns to a caller. Flow will then proceed to the defining instruction of the operand (because
|
||||
* the `StoreNodeInstr` computed by `StoreNodeOperand.getInner()` is the `StoreNode` containing
|
||||
* the defining instruction), and then along the chain computed by `StoreNodeInstr.getInner` like
|
||||
* above.
|
||||
* In both cases, flow leaves a `StoreNode` once the entire chain has been traversed, and the shared
|
||||
* SSA library is used to find the next use of the variable at the end of the chain.
|
||||
* - Flow can enter a `ReadNode` through an `OperandNode` that represents an address of some variable.
|
||||
* Flow will then proceed along the chain of addresses computed by `ReadNode.getOuter` (i.e., for an
|
||||
* expression like `use(a.b.c)` we visit `a`, then `b`, then `c`) and call `readStep` accordingly.
|
||||
* Once the entire chain has been traversed, flow is transferred to the load instruction that reads
|
||||
* the final address of the chain.
|
||||
* - Flow can enter a `SsaPhiNode` from an `InstructionNode`, a `StoreNode` or another `SsaPhiNode`
|
||||
* (in `toPhiNode`), depending on which node provided the previous definition of the underlying
|
||||
* variable. Flow leaves a `SsaPhiNode` (in `fromPhiNode`) by using the shared SSA library to
|
||||
* determine the next use of the variable.
|
||||
*/
|
||||
cached
|
||||
newtype TIRDataFlowNode =
|
||||
TInstructionNode(Instruction i) or
|
||||
TOperandNode(Operand op) or
|
||||
TVariableNode(Variable var)
|
||||
TVariableNode(Variable var) or
|
||||
TStoreNodeInstr(Instruction i) { Ssa::explicitWrite(_, _, i) } or
|
||||
TStoreNodeOperand(ArgumentOperand op) { Ssa::explicitWrite(_, _, op.getDef()) } or
|
||||
TReadNode(Instruction i) { needsPostReadNode(i) } or
|
||||
TSsaPhiNode(Ssa::PhiNode phi)
|
||||
|
||||
cached
|
||||
predicate localFlowStepCached(Node nodeFrom, Node nodeTo) {
|
||||
simpleLocalFlowStep(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
private predicate needsPostReadNode(Instruction iFrom) {
|
||||
// If the instruction generates an address that flows to a load.
|
||||
Ssa::addressFlowTC(iFrom, Ssa::getSourceAddress(_)) and
|
||||
(
|
||||
// And it is either a field address
|
||||
iFrom instanceof FieldAddressInstruction
|
||||
or
|
||||
// Or it is instruction that either uses or is used for an address that needs a post read node.
|
||||
exists(Instruction mid | needsPostReadNode(mid) |
|
||||
Ssa::addressFlow(mid, iFrom) or Ssa::addressFlow(iFrom, mid)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private import Cached
|
||||
@@ -180,6 +239,234 @@ class OperandNode extends Node, TOperandNode {
|
||||
override string toString() { result = this.getOperand().toString() }
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: do not use.
|
||||
*
|
||||
* A `StoreNode` is a node that has been (or is about to be) the
|
||||
* source or target of a `storeStep`.
|
||||
*/
|
||||
abstract private class StoreNode extends Node {
|
||||
/** Holds if this node should receive flow from `addr`. */
|
||||
abstract predicate flowInto(Instruction addr);
|
||||
|
||||
override Declaration getEnclosingCallable() { result = this.getFunction() }
|
||||
|
||||
/** Holds if this `StoreNode` is the root of the address computation used by a store operation. */
|
||||
predicate isTerminal() {
|
||||
not exists(this.getInner()) and
|
||||
not storeStep(this, _, _)
|
||||
}
|
||||
|
||||
/** Gets the store operation that uses the address computed by this `StoreNode`. */
|
||||
abstract Instruction getStoreInstruction();
|
||||
|
||||
/** Holds if the store operation associated with this `StoreNode` overwrites the entire variable. */
|
||||
final predicate isCertain() { Ssa::explicitWrite(true, this.getStoreInstruction(), _) }
|
||||
|
||||
/**
|
||||
* Gets the `StoreNode` that computes the address used by this `StoreNode`.
|
||||
*/
|
||||
abstract StoreNode getInner();
|
||||
|
||||
/** The inverse of `StoreNode.getInner`. */
|
||||
final StoreNode getOuter() { result.getInner() = this }
|
||||
}
|
||||
|
||||
class StoreNodeInstr extends StoreNode, TStoreNodeInstr {
|
||||
Instruction instr;
|
||||
|
||||
StoreNodeInstr() { this = TStoreNodeInstr(instr) }
|
||||
|
||||
override predicate flowInto(Instruction addr) { this.getInstruction() = addr }
|
||||
|
||||
/** Gets the underlying instruction. */
|
||||
Instruction getInstruction() { result = instr }
|
||||
|
||||
override Function getFunction() { result = this.getInstruction().getEnclosingFunction() }
|
||||
|
||||
override IRType getType() { result = this.getInstruction().getResultIRType() }
|
||||
|
||||
override Location getLocation() { result = this.getInstruction().getLocation() }
|
||||
|
||||
override string toString() {
|
||||
result = instructionNode(this.getInstruction()).toString() + " [store]"
|
||||
}
|
||||
|
||||
override Instruction getStoreInstruction() {
|
||||
Ssa::explicitWrite(_, result, this.getInstruction())
|
||||
}
|
||||
|
||||
override StoreNodeInstr getInner() {
|
||||
Ssa::addressFlow(result.getInstruction(), this.getInstruction())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To avoid having `PostUpdateNode`s with multiple pre-update nodes (which can cause performance
|
||||
* problems) we attach the `PostUpdateNode` that represent output arguments to an operand instead of
|
||||
* an instruction.
|
||||
*
|
||||
* To see why we need this, consider the expression `b->set(new C())`. The IR of this expression looks
|
||||
* like (simplified):
|
||||
* ```
|
||||
* r1(glval<unknown>) = FunctionAddress[set] :
|
||||
* r2(glval<unknown>) = FunctionAddress[operator new] :
|
||||
* r3(unsigned long) = Constant[8] :
|
||||
* r4(void *) = Call[operator new] : func:r2, 0:r3
|
||||
* r5(C *) = Convert : r4
|
||||
* r6(glval<unknown>) = FunctionAddress[C] :
|
||||
* v1(void) = Call[C] : func:r6, this:r5
|
||||
* v2(void) = Call[set] : func:r1, this:r0, 0:r5
|
||||
* ```
|
||||
*
|
||||
* Notice that both the call to `C` and the call to `set` will have an argument that is the
|
||||
* result of calling `operator new` (i.e., `r4`). If we only have `PostUpdateNode`s that are
|
||||
* instructions, both `PostUpdateNode`s would have `r4` as their pre-update node.
|
||||
*
|
||||
* We avoid this issue by having a `PostUpdateNode` for each argument, and let the pre-update node of
|
||||
* each `PostUpdateNode` be the argument _operand_, instead of the defining instruction.
|
||||
*/
|
||||
class StoreNodeOperand extends StoreNode, TStoreNodeOperand {
|
||||
ArgumentOperand operand;
|
||||
|
||||
StoreNodeOperand() { this = TStoreNodeOperand(operand) }
|
||||
|
||||
override predicate flowInto(Instruction addr) { this.getOperand().getDef() = addr }
|
||||
|
||||
/** Gets the underlying operand. */
|
||||
Operand getOperand() { result = operand }
|
||||
|
||||
override Function getFunction() { result = operand.getDef().getEnclosingFunction() }
|
||||
|
||||
override IRType getType() { result = operand.getIRType() }
|
||||
|
||||
override Location getLocation() { result = operand.getLocation() }
|
||||
|
||||
override string toString() { result = operandNode(this.getOperand()).toString() + " [store]" }
|
||||
|
||||
override WriteSideEffectInstruction getStoreInstruction() {
|
||||
Ssa::explicitWrite(_, result, operand.getDef())
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of `StoreNodeOperand.getInner` is the `StoreNodeInstr` representation the instruction
|
||||
* that defines this operand. This means the graph of `getInner` looks like this:
|
||||
* ```
|
||||
* I---I---I
|
||||
* \ \ \
|
||||
* O O O
|
||||
* ```
|
||||
* where each `StoreNodeOperand` "hooks" into the chain computed by `StoreNodeInstr.getInner`.
|
||||
* This means that the chain of `getInner` calls on the argument `&o.f` on an expression
|
||||
* like `func(&o.f)` is:
|
||||
* ```
|
||||
* r4---r3---r2
|
||||
* \
|
||||
* 0:r4
|
||||
* ```
|
||||
* where the IR for `func(&o.f)` looks like (simplified):
|
||||
* ```
|
||||
* r1(glval<unknown>) = FunctionAddress[func] :
|
||||
* r2(glval<O>) = VariableAddress[o] :
|
||||
* r3(glval<int>) = FieldAddress[f] : r2
|
||||
* r4(int *) = CopyValue : r3
|
||||
* v1(void) = Call[func] : func:r1, 0:r4
|
||||
* ```
|
||||
*/
|
||||
override StoreNodeInstr getInner() { operand.getDef() = result.getInstruction() }
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: do not use.
|
||||
*
|
||||
* A `ReadNode` is a node that has been (or is about to be) the
|
||||
* source or target of a `readStep`.
|
||||
*/
|
||||
class ReadNode extends Node, TReadNode {
|
||||
Instruction i;
|
||||
|
||||
ReadNode() { this = TReadNode(i) }
|
||||
|
||||
/** Gets the underlying instruction. */
|
||||
Instruction getInstruction() { result = i }
|
||||
|
||||
override Declaration getEnclosingCallable() { result = this.getFunction() }
|
||||
|
||||
override Function getFunction() { result = this.getInstruction().getEnclosingFunction() }
|
||||
|
||||
override IRType getType() { result = this.getInstruction().getResultIRType() }
|
||||
|
||||
override Location getLocation() { result = this.getInstruction().getLocation() }
|
||||
|
||||
override string toString() {
|
||||
result = instructionNode(this.getInstruction()).toString() + " [read]"
|
||||
}
|
||||
|
||||
/** Gets a load instruction that uses the address computed by this read node. */
|
||||
final Instruction getALoadInstruction() {
|
||||
Ssa::addressFlowTC(this.getInstruction(), Ssa::getSourceAddress(result))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a read node with an underlying instruction that is used by this
|
||||
* underlying instruction to compute an address of a load instruction.
|
||||
*/
|
||||
final ReadNode getInner() { Ssa::addressFlow(result.getInstruction(), this.getInstruction()) }
|
||||
|
||||
/** The inverse of `ReadNode.getInner`. */
|
||||
final ReadNode getOuter() { result.getInner() = this }
|
||||
|
||||
/** Holds if this read node computes a value that will not be used for any future read nodes. */
|
||||
final predicate isTerminal() {
|
||||
not exists(this.getOuter()) and
|
||||
not readStep(this, _, _)
|
||||
}
|
||||
|
||||
/** Holds if this read node computes a value that has not yet been used for any read operations. */
|
||||
final predicate isInitial() {
|
||||
not exists(this.getInner()) and
|
||||
not readStep(_, _, this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: do not use.
|
||||
*
|
||||
* A phi node produced by the shared SSA library, viewed as a node in a data flow graph.
|
||||
*/
|
||||
class SsaPhiNode extends Node, TSsaPhiNode {
|
||||
Ssa::PhiNode phi;
|
||||
|
||||
SsaPhiNode() { this = TSsaPhiNode(phi) }
|
||||
|
||||
/* Get the phi node associated with this node. */
|
||||
Ssa::PhiNode getPhiNode() { result = phi }
|
||||
|
||||
override Declaration getEnclosingCallable() { result = this.getFunction() }
|
||||
|
||||
override Function getFunction() { result = phi.getBasicBlock().getEnclosingFunction() }
|
||||
|
||||
override IRType getType() { result instanceof IRVoidType }
|
||||
|
||||
override Location getLocation() { result = phi.getBasicBlock().getLocation() }
|
||||
|
||||
/** Holds if this phi node has input from the `rnk`'th write operation in block `block`. */
|
||||
final predicate hasInputAtRankInBlock(IRBlock block, int rnk) {
|
||||
hasInputAtRankInBlock(block, rnk, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this phi node has input from the definition `input` (which is the `rnk`'th write
|
||||
* operation in block `block`).
|
||||
*/
|
||||
cached
|
||||
final predicate hasInputAtRankInBlock(IRBlock block, int rnk, Ssa::Definition input) {
|
||||
Ssa::phiHasInputFromBlock(phi, input, _) and input.definesAt(_, block, rnk)
|
||||
}
|
||||
|
||||
override string toString() { result = "Phi" }
|
||||
}
|
||||
|
||||
/**
|
||||
* An expression, viewed as a node in a data flow graph.
|
||||
*/
|
||||
@@ -313,15 +600,14 @@ deprecated class UninitializedNode extends Node {
|
||||
* Nodes corresponding to AST elements, for example `ExprNode`, usually refer
|
||||
* to the value before the update with the exception of `ClassInstanceExpr`,
|
||||
* which represents the value after the constructor has run.
|
||||
*
|
||||
* This class exists to match the interface used by Java. There are currently no non-abstract
|
||||
* classes that extend it. When we implement field flow, we can revisit this.
|
||||
*/
|
||||
abstract class PostUpdateNode extends InstructionNode {
|
||||
abstract class PostUpdateNode extends Node {
|
||||
/**
|
||||
* Gets the node before the state update.
|
||||
*/
|
||||
abstract Node getPreUpdateNode();
|
||||
|
||||
override string toString() { result = this.getPreUpdateNode() + " [post update]" }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -332,7 +618,7 @@ abstract class PostUpdateNode extends InstructionNode {
|
||||
* value, but does not necessarily replace it entirely. For example:
|
||||
* ```
|
||||
* x.y = 1; // a partial definition of the object `x`.
|
||||
* x.y.z = 1; // a partial definition of the object `x.y`.
|
||||
* x.y.z = 1; // a partial definition of the object `x.y` and `x`.
|
||||
* x.setY(1); // a partial definition of the object `x`.
|
||||
* setY(&x); // a partial definition of the object `x`.
|
||||
* ```
|
||||
@@ -341,135 +627,34 @@ abstract private class PartialDefinitionNode extends PostUpdateNode {
|
||||
abstract Expr getDefinedExpr();
|
||||
}
|
||||
|
||||
private class ExplicitFieldStoreQualifierNode extends PartialDefinitionNode {
|
||||
override ChiInstruction instr;
|
||||
StoreInstruction store;
|
||||
|
||||
ExplicitFieldStoreQualifierNode() {
|
||||
not instr.isResultConflated() and
|
||||
instr.getPartial() = store and
|
||||
(
|
||||
instr.getUpdatedInterval(_, _) or
|
||||
store.getDestinationAddress() instanceof FieldAddressInstruction
|
||||
)
|
||||
private class FieldPartialDefinitionNode extends PartialDefinitionNode, StoreNodeInstr {
|
||||
FieldPartialDefinitionNode() {
|
||||
this.getInstruction() = any(FieldAddressInstruction fai).getObjectAddress()
|
||||
}
|
||||
|
||||
// By using an operand as the result of this predicate we avoid the dataflow inconsistency errors
|
||||
// caused by having multiple nodes sharing the same pre update node. This inconsistency error can cause
|
||||
// a tuple explosion in the big step dataflow relation since it can make many nodes be the entry node
|
||||
// into a big step.
|
||||
override Node getPreUpdateNode() { result.asOperand() = instr.getTotalOperand() }
|
||||
override Node getPreUpdateNode() { result.asInstruction() = this.getInstruction() }
|
||||
|
||||
override Expr getDefinedExpr() { result = this.getInstruction().getUnconvertedResultExpression() }
|
||||
|
||||
override string toString() { result = PartialDefinitionNode.super.toString() }
|
||||
}
|
||||
|
||||
private class NonPartialDefinitionPostUpdate extends PostUpdateNode, StoreNodeInstr {
|
||||
NonPartialDefinitionPostUpdate() { not this instanceof PartialDefinitionNode }
|
||||
|
||||
override Node getPreUpdateNode() { result.asInstruction() = this.getInstruction() }
|
||||
|
||||
override string toString() { result = PostUpdateNode.super.toString() }
|
||||
}
|
||||
|
||||
private class ArgumentPostUpdateNode extends PartialDefinitionNode, StoreNodeOperand {
|
||||
override ArgumentNode getPreUpdateNode() { result.asOperand() = operand }
|
||||
|
||||
override Expr getDefinedExpr() {
|
||||
result =
|
||||
store
|
||||
.getDestinationAddress()
|
||||
.(FieldAddressInstruction)
|
||||
.getObjectAddress()
|
||||
.getUnconvertedResultExpression()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Not every store instruction generates a chi instruction that we can attach a PostUpdateNode to.
|
||||
* For instance, an update to a field of a struct containing only one field. Even if the store does
|
||||
* have a chi instruction, a subsequent use of the result of the store may be linked directly to the
|
||||
* result of the store as an inexact definition if the store totally overlaps the use. For these
|
||||
* cases we attach the PostUpdateNode to the store instruction. There's no obvious pre update node
|
||||
* for this case (as the entire memory is updated), so `getPreUpdateNode` is implemented as
|
||||
* `none()`.
|
||||
*/
|
||||
private class ExplicitSingleFieldStoreQualifierNode extends PartialDefinitionNode {
|
||||
override StoreInstruction instr;
|
||||
|
||||
ExplicitSingleFieldStoreQualifierNode() {
|
||||
(
|
||||
instr.getAUse().isDefinitionInexact()
|
||||
or
|
||||
not exists(ChiInstruction chi | chi.getPartial() = instr)
|
||||
) and
|
||||
// Without this condition any store would create a `PostUpdateNode`.
|
||||
instr.getDestinationAddress() instanceof FieldAddressInstruction
|
||||
result = this.getOperand().getDef().getUnconvertedResultExpression()
|
||||
}
|
||||
|
||||
override Node getPreUpdateNode() { none() }
|
||||
|
||||
override Expr getDefinedExpr() {
|
||||
result =
|
||||
instr
|
||||
.getDestinationAddress()
|
||||
.(FieldAddressInstruction)
|
||||
.getObjectAddress()
|
||||
.getUnconvertedResultExpression()
|
||||
}
|
||||
}
|
||||
|
||||
private FieldAddressInstruction getFieldInstruction(Instruction instr) {
|
||||
result = instr or
|
||||
result = instr.(CopyValueInstruction).getUnary()
|
||||
}
|
||||
|
||||
/**
|
||||
* The target of a `fieldStoreStepAfterArraySuppression` store step, which is used to convert
|
||||
* an `ArrayContent` to a `FieldContent` when the `WriteSideEffect` instruction stores
|
||||
* into a field. See the QLDoc for `suppressArrayRead` for an example of where such a conversion
|
||||
* is inserted.
|
||||
*/
|
||||
private class WriteSideEffectFieldStoreQualifierNode extends PartialDefinitionNode {
|
||||
override ChiInstruction instr;
|
||||
WriteSideEffectInstruction write;
|
||||
FieldAddressInstruction field;
|
||||
|
||||
WriteSideEffectFieldStoreQualifierNode() {
|
||||
not instr.isResultConflated() and
|
||||
instr.getPartial() = write and
|
||||
field = getFieldInstruction(write.getDestinationAddress())
|
||||
}
|
||||
|
||||
override Node getPreUpdateNode() { result.asOperand() = instr.getTotalOperand() }
|
||||
|
||||
override Expr getDefinedExpr() {
|
||||
result = field.getObjectAddress().getUnconvertedResultExpression()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The `PostUpdateNode` that is the target of a `arrayStoreStepChi` store step. The overriden
|
||||
* `ChiInstruction` corresponds to the instruction represented by `node2` in `arrayStoreStepChi`.
|
||||
*/
|
||||
private class ArrayStoreNode extends PartialDefinitionNode {
|
||||
override ChiInstruction instr;
|
||||
PointerAddInstruction add;
|
||||
|
||||
ArrayStoreNode() {
|
||||
not instr.isResultConflated() and
|
||||
exists(StoreInstruction store |
|
||||
instr.getPartial() = store and
|
||||
add = store.getDestinationAddress()
|
||||
)
|
||||
}
|
||||
|
||||
override Node getPreUpdateNode() { result.asOperand() = instr.getTotalOperand() }
|
||||
|
||||
override Expr getDefinedExpr() { result = add.getLeft().getUnconvertedResultExpression() }
|
||||
}
|
||||
|
||||
/**
|
||||
* The `PostUpdateNode` that is the target of a `arrayStoreStepChi` store step. The overriden
|
||||
* `ChiInstruction` corresponds to the instruction represented by `node2` in `arrayStoreStepChi`.
|
||||
*/
|
||||
private class PointerStoreNode extends PostUpdateNode {
|
||||
override ChiInstruction instr;
|
||||
|
||||
PointerStoreNode() {
|
||||
not instr.isResultConflated() and
|
||||
exists(StoreInstruction store |
|
||||
instr.getPartial() = store and
|
||||
store.getDestinationAddress().(CopyValueInstruction).getUnary() instanceof LoadInstruction
|
||||
)
|
||||
}
|
||||
|
||||
override Node getPreUpdateNode() { result.asOperand() = instr.getTotalOperand() }
|
||||
override string toString() { result = PartialDefinitionNode.super.toString() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -548,6 +733,11 @@ class VariableNode extends Node, TVariableNode {
|
||||
*/
|
||||
InstructionNode instructionNode(Instruction instr) { result.getInstruction() = instr }
|
||||
|
||||
/**
|
||||
* Gets the node corresponding to `operand`.
|
||||
*/
|
||||
OperandNode operandNode(Operand operand) { result.getOperand() = operand }
|
||||
|
||||
/**
|
||||
* DEPRECATED: use `definitionByReferenceNodeFromArgument` instead.
|
||||
*
|
||||
@@ -614,61 +804,176 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
or
|
||||
// Instruction -> Operand flow
|
||||
simpleOperandLocalFlowStep(nodeFrom.asInstruction(), nodeTo.asOperand())
|
||||
or
|
||||
// Flow into, through, and out of store nodes
|
||||
StoreNodeFlow::flowInto(nodeFrom.asInstruction(), nodeTo)
|
||||
or
|
||||
StoreNodeFlow::flowThrough(nodeFrom, nodeTo)
|
||||
or
|
||||
StoreNodeFlow::flowOutOf(nodeFrom, nodeTo)
|
||||
or
|
||||
// Flow into, through, and out of read nodes
|
||||
ReadNodeFlow::flowInto(nodeFrom, nodeTo)
|
||||
or
|
||||
ReadNodeFlow::flowThrough(nodeFrom, nodeTo)
|
||||
or
|
||||
ReadNodeFlow::flowOutOf(nodeFrom, nodeTo)
|
||||
or
|
||||
// Adjacent-def-use and adjacent-use-use flow
|
||||
adjacentDefUseFlow(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate getFieldSizeOfClass(Class c, Type type, int size) {
|
||||
exists(Field f |
|
||||
f.getDeclaringType() = c and
|
||||
f.getUnderlyingType() = type and
|
||||
type.getSize() = size
|
||||
private predicate adjacentDefUseFlow(Node nodeFrom, Node nodeTo) {
|
||||
// Flow that isn't already covered by field flow out of store/read nodes.
|
||||
not nodeFrom.asInstruction() = any(StoreNode pun).getStoreInstruction() and
|
||||
not nodeFrom.asInstruction() = any(ReadNode pun).getALoadInstruction() and
|
||||
(
|
||||
//Def-use flow
|
||||
Ssa::ssaFlow(nodeFrom, nodeTo)
|
||||
or
|
||||
// Use-use flow through stores.
|
||||
exists(Instruction loadAddress, Node store |
|
||||
loadAddress = Ssa::getSourceAddressFromNode(nodeFrom) and
|
||||
Ssa::explicitWrite(_, store.asInstruction(), loadAddress) and
|
||||
Ssa::ssaFlow(store, nodeTo)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isSingleFieldClass(Type type, Operand op) {
|
||||
exists(int size, Class c |
|
||||
c = op.getType().getUnderlyingType() and
|
||||
c.getSize() = size and
|
||||
getFieldSizeOfClass(c, type, size)
|
||||
)
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*/
|
||||
module ReadNodeFlow {
|
||||
/** Holds if the read node `nodeTo` should receive flow from `nodeFrom`. */
|
||||
predicate flowInto(Node nodeFrom, ReadNode nodeTo) {
|
||||
nodeTo.isInitial() and
|
||||
(
|
||||
// If we entered through an address operand.
|
||||
nodeFrom.asOperand().getDef() = nodeTo.getInstruction()
|
||||
or
|
||||
// If we entered flow through a memory-producing instruction.
|
||||
// This can happen if we have flow to an `InitializeParameterIndirection` through
|
||||
// a `ReadSideEffectInstruction`.
|
||||
exists(Instruction load, Instruction def |
|
||||
def = nodeFrom.asInstruction() and
|
||||
def = Ssa::getSourceValueOperand(load).getAnyDef() and
|
||||
not def = any(StoreNode store).getStoreInstruction() and
|
||||
pragma[only_bind_into](nodeTo).getALoadInstruction() = load
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the read node `nodeTo` should receive flow from the read node `nodeFrom`.
|
||||
*
|
||||
* This happens when `readFrom` is _not_ the source of a `readStep`, and `nodeTo` is
|
||||
* the `ReadNode` that represents an address that directly depends on `nodeFrom`.
|
||||
*/
|
||||
predicate flowThrough(ReadNode nodeFrom, ReadNode nodeTo) {
|
||||
not readStep(nodeFrom, _, _) and
|
||||
nodeFrom.getOuter() = nodeTo
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if flow should leave the read node `nFrom` and enter the node `nodeTo`.
|
||||
* This happens either because there is use-use flow from one of the variables used in
|
||||
* the read operation, or because we have traversed all the field dereferences in the
|
||||
* read operation.
|
||||
*/
|
||||
predicate flowOutOf(ReadNode nFrom, Node nodeTo) {
|
||||
// Use-use flow to another use of the same variable instruction
|
||||
Ssa::ssaFlow(nFrom, nodeTo)
|
||||
or
|
||||
not exists(nFrom.getInner()) and
|
||||
exists(Node store |
|
||||
Ssa::explicitWrite(_, store.asInstruction(), nFrom.getInstruction()) and
|
||||
Ssa::ssaFlow(store, nodeTo)
|
||||
)
|
||||
or
|
||||
// Flow out of read nodes and into memory instructions if we cannot move any further through
|
||||
// read nodes.
|
||||
nFrom.isTerminal() and
|
||||
(
|
||||
exists(Instruction load |
|
||||
load = nodeTo.asInstruction() and
|
||||
Ssa::getSourceAddress(load) = nFrom.getInstruction()
|
||||
)
|
||||
or
|
||||
exists(CallInstruction call, int i |
|
||||
call.getArgument(i) = nodeTo.asInstruction() and
|
||||
call.getArgument(i) = nFrom.getInstruction()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*/
|
||||
module StoreNodeFlow {
|
||||
/** Holds if the store node `nodeTo` should receive flow from `nodeFrom`. */
|
||||
predicate flowInto(Instruction instrFrom, StoreNode nodeTo) {
|
||||
nodeTo.flowInto(Ssa::getDestinationAddress(instrFrom))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the store node `nodeTo` should receive flow from `nodeFom`.
|
||||
*
|
||||
* This happens when `nodeFrom` is _not_ the source of a `storeStep`, and `nodeFrom` is
|
||||
* the `Storenode` that represents an address that directly depends on `nodeTo`.
|
||||
*/
|
||||
predicate flowThrough(StoreNode nodeFrom, StoreNode nodeTo) {
|
||||
// Flow through a post update node that doesn't need a store step.
|
||||
not storeStep(nodeFrom, _, _) and
|
||||
nodeTo.getOuter() = nodeFrom
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if flow should leave the store node `nodeFrom` and enter the node `nodeTo`.
|
||||
* This happens because we have traversed an entire chain of field dereferences
|
||||
* after a store operation.
|
||||
*/
|
||||
predicate flowOutOf(StoreNodeInstr nFrom, Node nodeTo) {
|
||||
nFrom.isTerminal() and
|
||||
Ssa::ssaFlow(nFrom, nodeTo)
|
||||
}
|
||||
}
|
||||
|
||||
private predicate simpleOperandLocalFlowStep(Instruction iFrom, Operand opTo) {
|
||||
// Propagate flow from an instruction to its exact uses.
|
||||
// We do this for all instruction/operand pairs, except when the operand is the
|
||||
// side effect operand of a ReturnIndirectionInstruction, or the load operand of a LoadInstruction.
|
||||
// This is because we get these flows through the shared SSA library already, and including this
|
||||
// flow here will create multiple dataflow paths which creates a blowup in stage 3 of dataflow.
|
||||
(
|
||||
not any(ReturnIndirectionInstruction ret).getSideEffectOperand() = opTo and
|
||||
not any(LoadInstruction load).getSourceValueOperand() = opTo and
|
||||
not any(ReturnValueInstruction ret).getReturnValueOperand() = opTo
|
||||
) and
|
||||
opTo.getDef() = iFrom
|
||||
or
|
||||
opTo = any(ReadSideEffectInstruction read).getSideEffectOperand() and
|
||||
not iFrom.isResultConflated() and
|
||||
iFrom = opTo.getAnyDef()
|
||||
or
|
||||
// Loading a single `int` from an `int *` parameter is not an exact load since
|
||||
// the parameter may point to an entire array rather than a single `int`. The
|
||||
// following rule ensures that any flow going into the
|
||||
// `InitializeIndirectionInstruction`, even if it's for a different array
|
||||
// element, will propagate to a load of the first element.
|
||||
//
|
||||
// Since we're linking `InitializeIndirectionInstruction` and
|
||||
// `LoadInstruction` together directly, this rule will break if there's any
|
||||
// reassignment of the parameter indirection, including a conditional one that
|
||||
// leads to a phi node.
|
||||
exists(InitializeIndirectionInstruction init |
|
||||
iFrom = init and
|
||||
opTo.(LoadOperand).getAnyDef() = init and
|
||||
// Check that the types match. Otherwise we can get flow from an object to
|
||||
// its fields, which leads to field conflation when there's flow from other
|
||||
// fields to the object elsewhere.
|
||||
init.getParameter().getType().getUnspecifiedType().(DerivedType).getBaseType() =
|
||||
opTo.getType().getUnspecifiedType()
|
||||
)
|
||||
or
|
||||
// Flow from stores to structs with a single field to a load of that field.
|
||||
exists(LoadInstruction load |
|
||||
load.getSourceValueOperand() = opTo and
|
||||
opTo.getAnyDef() = iFrom and
|
||||
isSingleFieldClass(pragma[only_bind_out](pragma[only_bind_out](iFrom).getResultType()), opTo)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate getAddressType(LoadInstruction load, Type t) {
|
||||
exists(Instruction address |
|
||||
address = load.getSourceAddress() and
|
||||
t = address.getResultType()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Like the AST dataflow library, we want to conflate the address and value of a reference. This class
|
||||
* represents the `LoadInstruction` that is generated from a reference dereference.
|
||||
*/
|
||||
private class ReferenceDereferenceInstruction extends LoadInstruction {
|
||||
ReferenceDereferenceInstruction() {
|
||||
exists(ReferenceType ref |
|
||||
getAddressType(this, ref) and
|
||||
this.getResultType() = ref.getBaseType()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private predicate simpleInstructionLocalFlowStep(Operand opFrom, Instruction iTo) {
|
||||
iTo.(CopyInstruction).getSourceValueOperand() = opFrom
|
||||
or
|
||||
@@ -681,40 +986,8 @@ private predicate simpleInstructionLocalFlowStep(Operand opFrom, Instruction iTo
|
||||
or
|
||||
iTo.(InheritanceConversionInstruction).getUnaryOperand() = opFrom
|
||||
or
|
||||
// A chi instruction represents a point where a new value (the _partial_
|
||||
// operand) may overwrite an old value (the _total_ operand), but the alias
|
||||
// analysis couldn't determine that it surely will overwrite every bit of it or
|
||||
// that it surely will overwrite no bit of it.
|
||||
//
|
||||
// By allowing flow through the total operand, we ensure that flow is not lost
|
||||
// due to shortcomings of the alias analysis. We may get false flow in cases
|
||||
// where the data is indeed overwritten.
|
||||
//
|
||||
// Flow through the partial operand belongs in the taint-tracking libraries
|
||||
// for now.
|
||||
iTo.getAnOperand().(ChiTotalOperand) = opFrom
|
||||
or
|
||||
// Add flow from write side-effects to non-conflated chi instructions through their
|
||||
// partial operands. From there, a `readStep` will find subsequent reads of that field.
|
||||
// Consider the following example:
|
||||
// ```
|
||||
// void setX(Point* p, int new_x) {
|
||||
// p->x = new_x;
|
||||
// }
|
||||
// ...
|
||||
// setX(&p, taint());
|
||||
// ```
|
||||
// Here, a `WriteSideEffectInstruction` will provide a new definition for `p->x` after the call to
|
||||
// `setX`, which will be melded into `p` through a chi instruction.
|
||||
exists(ChiInstruction chi | chi = iTo |
|
||||
opFrom.getAnyDef() instanceof WriteSideEffectInstruction and
|
||||
chi.getPartialOperand() = opFrom and
|
||||
not chi.isResultConflated() and
|
||||
// In a call such as `set_value(&x->val);` we don't want the memory representing `x` to receive
|
||||
// dataflow by a simple step. Instead, this is handled by field flow. If we add a simple step here
|
||||
// we can get field-to-object flow.
|
||||
not chi.isPartialUpdate()
|
||||
)
|
||||
// Conflate references and values like in AST dataflow.
|
||||
iTo.(ReferenceDereferenceInstruction).getSourceAddressOperand() = opFrom
|
||||
or
|
||||
// Flow through modeled functions
|
||||
modelFlow(opFrom, iTo)
|
||||
@@ -788,25 +1061,14 @@ predicate localInstructionFlow(Instruction e1, Instruction e2) {
|
||||
*/
|
||||
predicate localExprFlow(Expr e1, Expr e2) { localFlow(exprNode(e1), exprNode(e2)) }
|
||||
|
||||
/**
|
||||
* Gets a field corresponding to the bit range `[startBit..endBit)` of class `c`, if any.
|
||||
*/
|
||||
private Field getAField(Class c, int startBit, int endBit) {
|
||||
result.getDeclaringType() = c and
|
||||
startBit = 8 * result.getByteOffset() and
|
||||
endBit = 8 * result.getType().getSize() + startBit
|
||||
or
|
||||
exists(Field f, Class cInner |
|
||||
f = c.getAField() and
|
||||
cInner = f.getUnderlyingType() and
|
||||
result = getAField(cInner, startBit - 8 * f.getByteOffset(), endBit - 8 * f.getByteOffset())
|
||||
)
|
||||
}
|
||||
|
||||
private newtype TContent =
|
||||
TFieldContent(Class c, int startBit, int endBit) { exists(getAField(c, startBit, endBit)) } or
|
||||
TCollectionContent() or
|
||||
TArrayContent()
|
||||
TFieldContent(Field f) {
|
||||
// As reads and writes to union fields can create flow even though the reads and writes
|
||||
// target different fields, we don't want a read (write) to create a read (write) step.
|
||||
not f.getDeclaringType() instanceof Union
|
||||
} or
|
||||
TCollectionContent() or // Not used in C/C++
|
||||
TArrayContent() // Not used in C/C++.
|
||||
|
||||
/**
|
||||
* A description of the way data may be stored inside an object. Examples
|
||||
@@ -824,18 +1086,13 @@ class Content extends TContent {
|
||||
|
||||
/** A reference through an instance field. */
|
||||
class FieldContent extends Content, TFieldContent {
|
||||
Class c;
|
||||
int startBit;
|
||||
int endBit;
|
||||
Field f;
|
||||
|
||||
FieldContent() { this = TFieldContent(c, startBit, endBit) }
|
||||
FieldContent() { this = TFieldContent(f) }
|
||||
|
||||
// Ensure that there's just 1 result for `toString`.
|
||||
override string toString() { result = min(Field f | f = this.getAField() | f.toString()) }
|
||||
override string toString() { result = f.toString() }
|
||||
|
||||
predicate hasOffset(Class cl, int start, int end) { cl = c and start = startBit and end = endBit }
|
||||
|
||||
Field getAField() { result = getAField(c, startBit, endBit) }
|
||||
Field getField() { result = f }
|
||||
}
|
||||
|
||||
/** A reference through an array. */
|
||||
|
||||
@@ -0,0 +1,662 @@
|
||||
/**
|
||||
* Provides a language-independent implementation of static single assignment
|
||||
* (SSA) form.
|
||||
*/
|
||||
|
||||
private import SsaImplSpecific
|
||||
|
||||
private BasicBlock getABasicBlockPredecessor(BasicBlock bb) { getABasicBlockSuccessor(result) = bb }
|
||||
|
||||
/**
|
||||
* Liveness analysis (based on source variables) to restrict the size of the
|
||||
* SSA representation.
|
||||
*/
|
||||
private module Liveness {
|
||||
/**
|
||||
* A classification of variable references into reads (of a given kind) and
|
||||
* (certain or uncertain) writes.
|
||||
*/
|
||||
private newtype TRefKind =
|
||||
Read(boolean certain) { certain in [false, true] } or
|
||||
Write(boolean certain) { certain in [false, true] }
|
||||
|
||||
private class RefKind extends TRefKind {
|
||||
string toString() {
|
||||
exists(boolean certain | this = Read(certain) and result = "read (" + certain + ")")
|
||||
or
|
||||
exists(boolean certain | this = Write(certain) and result = "write (" + certain + ")")
|
||||
}
|
||||
|
||||
int getOrder() {
|
||||
this = Read(_) and
|
||||
result = 0
|
||||
or
|
||||
this = Write(_) and
|
||||
result = 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the `i`th node of basic block `bb` is a reference to `v` of kind `k`.
|
||||
*/
|
||||
private predicate ref(BasicBlock bb, int i, SourceVariable v, RefKind k) {
|
||||
exists(boolean certain | variableRead(bb, i, v, certain) | k = Read(certain))
|
||||
or
|
||||
exists(boolean certain | variableWrite(bb, i, v, certain) | k = Write(certain))
|
||||
}
|
||||
|
||||
private newtype OrderedRefIndex =
|
||||
MkOrderedRefIndex(int i, int tag) {
|
||||
exists(RefKind rk | ref(_, i, _, rk) | tag = rk.getOrder())
|
||||
}
|
||||
|
||||
private OrderedRefIndex refOrd(BasicBlock bb, int i, SourceVariable v, RefKind k, int ord) {
|
||||
ref(bb, i, v, k) and
|
||||
result = MkOrderedRefIndex(i, ord) and
|
||||
ord = k.getOrder()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (1-based) rank of the reference to `v` at the `i`th node of
|
||||
* basic block `bb`, which has the given reference kind `k`.
|
||||
*
|
||||
* Reads are considered before writes when they happen at the same index.
|
||||
*/
|
||||
private int refRank(BasicBlock bb, int i, SourceVariable v, RefKind k) {
|
||||
refOrd(bb, i, v, k, _) =
|
||||
rank[result](int j, int ord, OrderedRefIndex res |
|
||||
res = refOrd(bb, j, v, _, ord)
|
||||
|
|
||||
res order by j, ord
|
||||
)
|
||||
}
|
||||
|
||||
private int maxRefRank(BasicBlock bb, SourceVariable v) {
|
||||
result = refRank(bb, _, v, _) and
|
||||
not result + 1 = refRank(bb, _, v, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (1-based) rank of the first reference to `v` inside basic block `bb`
|
||||
* that is either a read or a certain write.
|
||||
*/
|
||||
private int firstReadOrCertainWrite(BasicBlock bb, SourceVariable v) {
|
||||
result =
|
||||
min(int r, RefKind k |
|
||||
r = refRank(bb, _, v, k) and
|
||||
k != Write(false)
|
||||
|
|
||||
r
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if source variable `v` is live at the beginning of basic block `bb`.
|
||||
*/
|
||||
predicate liveAtEntry(BasicBlock bb, SourceVariable v) {
|
||||
// The first read or certain write to `v` inside `bb` is a read
|
||||
refRank(bb, _, v, Read(_)) = firstReadOrCertainWrite(bb, v)
|
||||
or
|
||||
// There is no certain write to `v` inside `bb`, but `v` is live at entry
|
||||
// to a successor basic block of `bb`
|
||||
not exists(firstReadOrCertainWrite(bb, v)) and
|
||||
liveAtExit(bb, v)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if source variable `v` is live at the end of basic block `bb`.
|
||||
*/
|
||||
predicate liveAtExit(BasicBlock bb, SourceVariable v) {
|
||||
liveAtEntry(getABasicBlockSuccessor(bb), v)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if variable `v` is live in basic block `bb` at index `i`.
|
||||
* The rank of `i` is `rnk` as defined by `refRank()`.
|
||||
*/
|
||||
private predicate liveAtRank(BasicBlock bb, int i, SourceVariable v, int rnk) {
|
||||
exists(RefKind kind | rnk = refRank(bb, i, v, kind) |
|
||||
rnk = maxRefRank(bb, v) and
|
||||
liveAtExit(bb, v)
|
||||
or
|
||||
ref(bb, i, v, kind) and
|
||||
kind = Read(_)
|
||||
or
|
||||
exists(RefKind nextKind |
|
||||
liveAtRank(bb, _, v, rnk + 1) and
|
||||
rnk + 1 = refRank(bb, _, v, nextKind) and
|
||||
nextKind != Write(true)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if variable `v` is live after the (certain or uncertain) write at
|
||||
* index `i` inside basic block `bb`.
|
||||
*/
|
||||
predicate liveAfterWrite(BasicBlock bb, int i, SourceVariable v) {
|
||||
exists(int rnk | rnk = refRank(bb, i, v, Write(_)) | liveAtRank(bb, i, v, rnk))
|
||||
}
|
||||
}
|
||||
|
||||
private import Liveness
|
||||
|
||||
/**
|
||||
* Holds if `df` is in the dominance frontier of `bb`.
|
||||
*
|
||||
* This is equivalent to:
|
||||
*
|
||||
* ```ql
|
||||
* bb = getImmediateBasicBlockDominator*(getABasicBlockPredecessor(df)) and
|
||||
* not bb = getImmediateBasicBlockDominator+(df)
|
||||
* ```
|
||||
*/
|
||||
private predicate inDominanceFrontier(BasicBlock bb, BasicBlock df) {
|
||||
bb = getABasicBlockPredecessor(df) and not bb = getImmediateBasicBlockDominator(df)
|
||||
or
|
||||
exists(BasicBlock prev | inDominanceFrontier(prev, df) |
|
||||
bb = getImmediateBasicBlockDominator(prev) and
|
||||
not bb = getImmediateBasicBlockDominator(df)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `bb` is in the dominance frontier of a block containing a
|
||||
* definition of `v`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
private predicate inDefDominanceFrontier(BasicBlock bb, SourceVariable v) {
|
||||
exists(BasicBlock defbb, Definition def |
|
||||
def.definesAt(v, defbb, _) and
|
||||
inDominanceFrontier(defbb, bb)
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
newtype TDefinition =
|
||||
TWriteDef(SourceVariable v, BasicBlock bb, int i) {
|
||||
variableWrite(bb, i, v, _) and
|
||||
liveAfterWrite(bb, i, v)
|
||||
} or
|
||||
TPhiNode(SourceVariable v, BasicBlock bb) {
|
||||
inDefDominanceFrontier(bb, v) and
|
||||
liveAtEntry(bb, v)
|
||||
}
|
||||
|
||||
private module SsaDefReaches {
|
||||
newtype TSsaRefKind =
|
||||
SsaRead() or
|
||||
SsaDef()
|
||||
|
||||
/**
|
||||
* A classification of SSA variable references into reads and definitions.
|
||||
*/
|
||||
class SsaRefKind extends TSsaRefKind {
|
||||
string toString() {
|
||||
this = SsaRead() and
|
||||
result = "SsaRead"
|
||||
or
|
||||
this = SsaDef() and
|
||||
result = "SsaDef"
|
||||
}
|
||||
|
||||
int getOrder() {
|
||||
this = SsaRead() and
|
||||
result = 0
|
||||
or
|
||||
this = SsaDef() and
|
||||
result = 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the `i`th node of basic block `bb` is a reference to `v`,
|
||||
* either a read (when `k` is `SsaRead()`) or an SSA definition (when `k`
|
||||
* is `SsaDef()`).
|
||||
*
|
||||
* Unlike `Liveness::ref`, this includes `phi` nodes.
|
||||
*/
|
||||
predicate ssaRef(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) {
|
||||
variableRead(bb, i, v, _) and
|
||||
k = SsaRead()
|
||||
or
|
||||
exists(Definition def | def.definesAt(v, bb, i)) and
|
||||
k = SsaDef()
|
||||
}
|
||||
|
||||
private newtype OrderedSsaRefIndex =
|
||||
MkOrderedSsaRefIndex(int i, SsaRefKind k) { ssaRef(_, i, _, k) }
|
||||
|
||||
private OrderedSsaRefIndex ssaRefOrd(BasicBlock bb, int i, SourceVariable v, SsaRefKind k, int ord) {
|
||||
ssaRef(bb, i, v, k) and
|
||||
result = MkOrderedSsaRefIndex(i, k) and
|
||||
ord = k.getOrder()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the (1-based) rank of the reference to `v` at the `i`th node of basic
|
||||
* block `bb`, which has the given reference kind `k`.
|
||||
*
|
||||
* For example, if `bb` is a basic block with a phi node for `v` (considered
|
||||
* to be at index -1), reads `v` at node 2, and defines it at node 5, we have:
|
||||
*
|
||||
* ```ql
|
||||
* ssaRefRank(bb, -1, v, SsaDef()) = 1 // phi node
|
||||
* ssaRefRank(bb, 2, v, Read()) = 2 // read at node 2
|
||||
* ssaRefRank(bb, 5, v, SsaDef()) = 3 // definition at node 5
|
||||
* ```
|
||||
*
|
||||
* Reads are considered before writes when they happen at the same index.
|
||||
*/
|
||||
int ssaRefRank(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) {
|
||||
ssaRefOrd(bb, i, v, k, _) =
|
||||
rank[result](int j, int ord, OrderedSsaRefIndex res |
|
||||
res = ssaRefOrd(bb, j, v, _, ord)
|
||||
|
|
||||
res order by j, ord
|
||||
)
|
||||
}
|
||||
|
||||
int maxSsaRefRank(BasicBlock bb, SourceVariable v) {
|
||||
result = ssaRefRank(bb, _, v, _) and
|
||||
not result + 1 = ssaRefRank(bb, _, v, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the SSA definition `def` reaches rank index `rnk` in its own
|
||||
* basic block `bb`.
|
||||
*/
|
||||
predicate ssaDefReachesRank(BasicBlock bb, Definition def, int rnk, SourceVariable v) {
|
||||
exists(int i |
|
||||
rnk = ssaRefRank(bb, i, v, SsaDef()) and
|
||||
def.definesAt(v, bb, i)
|
||||
)
|
||||
or
|
||||
ssaDefReachesRank(bb, def, rnk - 1, v) and
|
||||
rnk = ssaRefRank(bb, _, v, SsaRead())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the SSA definition of `v` at `def` reaches index `i` in the same
|
||||
* basic block `bb`, without crossing another SSA definition of `v`.
|
||||
*/
|
||||
predicate ssaDefReachesReadWithinBlock(SourceVariable v, Definition def, BasicBlock bb, int i) {
|
||||
exists(int rnk |
|
||||
ssaDefReachesRank(bb, def, rnk, v) and
|
||||
rnk = ssaRefRank(bb, i, v, SsaRead())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the SSA definition of `v` at `def` reaches uncertain SSA definition
|
||||
* `redef` in the same basic block, without crossing another SSA definition of `v`.
|
||||
*/
|
||||
predicate ssaDefReachesUncertainDefWithinBlock(
|
||||
SourceVariable v, Definition def, UncertainWriteDefinition redef
|
||||
) {
|
||||
exists(BasicBlock bb, int rnk, int i |
|
||||
ssaDefReachesRank(bb, def, rnk, v) and
|
||||
rnk = ssaRefRank(bb, i, v, SsaDef()) - 1 and
|
||||
redef.definesAt(v, bb, i)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as `ssaRefRank()`, but restricted to a particular SSA definition `def`.
|
||||
*/
|
||||
int ssaDefRank(Definition def, SourceVariable v, BasicBlock bb, int i, SsaRefKind k) {
|
||||
v = def.getSourceVariable() and
|
||||
result = ssaRefRank(bb, i, v, k) and
|
||||
(
|
||||
ssaDefReachesRead(_, def, bb, i)
|
||||
or
|
||||
def.definesAt(_, bb, i)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the reference to `def` at index `i` in basic block `bb` is the
|
||||
* last reference to `v` inside `bb`.
|
||||
*/
|
||||
pragma[noinline]
|
||||
predicate lastSsaRef(Definition def, SourceVariable v, BasicBlock bb, int i) {
|
||||
ssaDefRank(def, v, bb, i, _) = maxSsaRefRank(bb, v)
|
||||
}
|
||||
|
||||
predicate defOccursInBlock(Definition def, BasicBlock bb, SourceVariable v) {
|
||||
exists(ssaDefRank(def, v, bb, _, _))
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate ssaDefReachesThroughBlock(Definition def, BasicBlock bb) {
|
||||
ssaDefReachesEndOfBlock(bb, def, _) and
|
||||
not defOccursInBlock(_, bb, def.getSourceVariable())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `def` is accessed in basic block `bb1` (either a read or a write),
|
||||
* `bb2` is a transitive successor of `bb1`, `def` is live at the end of `bb1`,
|
||||
* and the underlying variable for `def` is neither read nor written in any block
|
||||
* on the path between `bb1` and `bb2`.
|
||||
*/
|
||||
predicate varBlockReaches(Definition def, BasicBlock bb1, BasicBlock bb2) {
|
||||
defOccursInBlock(def, bb1, _) and
|
||||
bb2 = getABasicBlockSuccessor(bb1)
|
||||
or
|
||||
exists(BasicBlock mid |
|
||||
varBlockReaches(def, bb1, mid) and
|
||||
ssaDefReachesThroughBlock(def, mid) and
|
||||
bb2 = getABasicBlockSuccessor(mid)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `def` is accessed in basic block `bb1` (either a read or a write),
|
||||
* `def` is read at index `i2` in basic block `bb2`, `bb2` is in a transitive
|
||||
* successor block of `bb1`, and `def` is neither read nor written in any block
|
||||
* on a path between `bb1` and `bb2`.
|
||||
*/
|
||||
predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) {
|
||||
varBlockReaches(def, bb1, bb2) and
|
||||
ssaRefRank(bb2, i2, def.getSourceVariable(), SsaRead()) = 1
|
||||
}
|
||||
}
|
||||
|
||||
private import SsaDefReaches
|
||||
|
||||
pragma[nomagic]
|
||||
predicate liveThrough(BasicBlock bb, SourceVariable v) {
|
||||
liveAtExit(bb, v) and
|
||||
not ssaRef(bb, _, v, SsaDef())
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Holds if the SSA definition of `v` at `def` reaches the end of basic
|
||||
* block `bb`, at which point it is still live, without crossing another
|
||||
* SSA definition of `v`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable v) {
|
||||
exists(int last | last = maxSsaRefRank(bb, v) |
|
||||
ssaDefReachesRank(bb, def, last, v) and
|
||||
liveAtExit(bb, v)
|
||||
)
|
||||
or
|
||||
// The construction of SSA form ensures that each read of a variable is
|
||||
// dominated by its definition. An SSA definition therefore reaches a
|
||||
// control flow node if it is the _closest_ SSA definition that dominates
|
||||
// the node. If two definitions dominate a node then one must dominate the
|
||||
// other, so therefore the definition of _closest_ is given by the dominator
|
||||
// tree. Thus, reaching definitions can be calculated in terms of dominance.
|
||||
ssaDefReachesEndOfBlock(getImmediateBasicBlockDominator(bb), def, pragma[only_bind_into](v)) and
|
||||
liveThrough(bb, pragma[only_bind_into](v))
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Holds if `inp` is an input to the phi node `phi` along the edge originating in `bb`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate phiHasInputFromBlock(PhiNode phi, Definition inp, BasicBlock bb) {
|
||||
exists(SourceVariable v, BasicBlock bbDef |
|
||||
phi.definesAt(v, bbDef, _) and
|
||||
getABasicBlockPredecessor(bbDef) = bb and
|
||||
ssaDefReachesEndOfBlock(bb, inp, v)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Holds if the SSA definition of `v` at `def` reaches a read at index `i` in
|
||||
* basic block `bb`, without crossing another SSA definition of `v`. The read
|
||||
* is of kind `rk`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate ssaDefReachesRead(SourceVariable v, Definition def, BasicBlock bb, int i) {
|
||||
ssaDefReachesReadWithinBlock(v, def, bb, i)
|
||||
or
|
||||
variableRead(bb, i, v, _) and
|
||||
ssaDefReachesEndOfBlock(getABasicBlockPredecessor(bb), def, v) and
|
||||
not ssaDefReachesReadWithinBlock(v, _, bb, i)
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Holds if `def` is accessed at index `i1` in basic block `bb1` (either a read
|
||||
* or a write), `def` is read at index `i2` in basic block `bb2`, and there is a
|
||||
* path between them without any read of `def`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) {
|
||||
exists(int rnk |
|
||||
rnk = ssaDefRank(def, _, bb1, i1, _) and
|
||||
rnk + 1 = ssaDefRank(def, _, bb1, i2, SsaRead()) and
|
||||
variableRead(bb1, i2, _, _) and
|
||||
bb2 = bb1
|
||||
)
|
||||
or
|
||||
lastSsaRef(def, _, bb1, i1) and
|
||||
defAdjacentRead(def, bb1, bb2, i2)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate adjacentDefRead(
|
||||
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2, SourceVariable v
|
||||
) {
|
||||
adjacentDefRead(def, bb1, i1, bb2, i2) and
|
||||
v = def.getSourceVariable()
|
||||
}
|
||||
|
||||
private predicate adjacentDefReachesRead(
|
||||
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
|
||||
) {
|
||||
exists(SourceVariable v | adjacentDefRead(def, bb1, i1, bb2, i2, v) |
|
||||
ssaRef(bb1, i1, v, SsaDef())
|
||||
or
|
||||
variableRead(bb1, i1, v, true)
|
||||
)
|
||||
or
|
||||
exists(BasicBlock bb3, int i3 |
|
||||
adjacentDefReachesRead(def, bb1, i1, bb3, i3) and
|
||||
variableRead(bb3, i3, _, false) and
|
||||
adjacentDefRead(def, bb3, i3, bb2, i2)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Same as `adjacentDefRead`, but ignores uncertain reads.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate adjacentDefNoUncertainReads(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) {
|
||||
adjacentDefReachesRead(def, bb1, i1, bb2, i2) and
|
||||
variableRead(bb2, i2, _, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Holds if the node at index `i` in `bb` is a last reference to SSA definition
|
||||
* `def`. The reference is last because it can reach another write `next`,
|
||||
* without passing through another read or write.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) {
|
||||
exists(SourceVariable v |
|
||||
// Next reference to `v` inside `bb` is a write
|
||||
exists(int rnk, int j |
|
||||
rnk = ssaDefRank(def, v, bb, i, _) and
|
||||
next.definesAt(v, bb, j) and
|
||||
rnk + 1 = ssaRefRank(bb, j, v, SsaDef())
|
||||
)
|
||||
or
|
||||
// Can reach a write using one or more steps
|
||||
lastSsaRef(def, v, bb, i) and
|
||||
exists(BasicBlock bb2 |
|
||||
varBlockReaches(def, bb, bb2) and
|
||||
1 = ssaDefRank(next, v, bb2, _, SsaDef())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Holds if `inp` is an immediately preceding definition of uncertain definition
|
||||
* `def`. Since `def` is uncertain, the value from the preceding definition might
|
||||
* still be valid.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate uncertainWriteDefinitionInput(UncertainWriteDefinition def, Definition inp) {
|
||||
lastRefRedef(inp, _, _, def)
|
||||
}
|
||||
|
||||
private predicate adjacentDefReachesUncertainRead(
|
||||
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
|
||||
) {
|
||||
adjacentDefReachesRead(def, bb1, i1, bb2, i2) and
|
||||
variableRead(bb2, i2, _, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Same as `lastRefRedef`, but ignores uncertain reads.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate lastRefRedefNoUncertainReads(Definition def, BasicBlock bb, int i, Definition next) {
|
||||
lastRefRedef(def, bb, i, next) and
|
||||
not variableRead(bb, i, def.getSourceVariable(), false)
|
||||
or
|
||||
exists(BasicBlock bb0, int i0 |
|
||||
lastRefRedef(def, bb0, i0, next) and
|
||||
adjacentDefReachesUncertainRead(def, bb, i, bb0, i0)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Holds if the node at index `i` in `bb` is a last reference to SSA
|
||||
* definition `def`.
|
||||
*
|
||||
* That is, the node can reach the end of the enclosing callable, or another
|
||||
* SSA definition for the underlying source variable, without passing through
|
||||
* another read.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate lastRef(Definition def, BasicBlock bb, int i) {
|
||||
lastRefRedef(def, bb, i, _)
|
||||
or
|
||||
lastSsaRef(def, _, bb, i) and
|
||||
(
|
||||
// Can reach exit directly
|
||||
bb instanceof ExitBasicBlock
|
||||
or
|
||||
// Can reach a block using one or more steps, where `def` is no longer live
|
||||
exists(BasicBlock bb2 | varBlockReaches(def, bb, bb2) |
|
||||
not defOccursInBlock(def, bb2, _) and
|
||||
not ssaDefReachesEndOfBlock(bb2, def, _)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* NB: If this predicate is exposed, it should be cached.
|
||||
*
|
||||
* Same as `lastRefRedef`, but ignores uncertain reads.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
predicate lastRefNoUncertainReads(Definition def, BasicBlock bb, int i) {
|
||||
lastRef(def, bb, i) and
|
||||
not variableRead(bb, i, def.getSourceVariable(), false)
|
||||
or
|
||||
exists(BasicBlock bb0, int i0 |
|
||||
lastRef(def, bb0, i0) and
|
||||
adjacentDefReachesUncertainRead(def, bb, i, bb0, i0)
|
||||
)
|
||||
}
|
||||
|
||||
/** A static single assignment (SSA) definition. */
|
||||
class Definition extends TDefinition {
|
||||
/** Gets the source variable underlying this SSA definition. */
|
||||
SourceVariable getSourceVariable() { this.definesAt(result, _, _) }
|
||||
|
||||
/**
|
||||
* Holds if this SSA definition defines `v` at index `i` in basic block `bb`.
|
||||
* Phi nodes are considered to be at index `-1`, while normal variable writes
|
||||
* are at the index of the control flow node they wrap.
|
||||
*/
|
||||
final predicate definesAt(SourceVariable v, BasicBlock bb, int i) {
|
||||
this = TWriteDef(v, bb, i)
|
||||
or
|
||||
this = TPhiNode(v, bb) and i = -1
|
||||
}
|
||||
|
||||
/** Gets the basic block to which this SSA definition belongs. */
|
||||
final BasicBlock getBasicBlock() { this.definesAt(_, result, _) }
|
||||
|
||||
/** Gets a textual representation of this SSA definition. */
|
||||
string toString() { none() }
|
||||
}
|
||||
|
||||
/** An SSA definition that corresponds to a write. */
|
||||
class WriteDefinition extends Definition, TWriteDef {
|
||||
private SourceVariable v;
|
||||
private BasicBlock bb;
|
||||
private int i;
|
||||
|
||||
WriteDefinition() { this = TWriteDef(v, bb, i) }
|
||||
|
||||
override string toString() { result = "WriteDef" }
|
||||
}
|
||||
|
||||
/** A phi node. */
|
||||
class PhiNode extends Definition, TPhiNode {
|
||||
override string toString() { result = "Phi" }
|
||||
}
|
||||
|
||||
/**
|
||||
* An SSA definition that represents an uncertain update of the underlying
|
||||
* source variable.
|
||||
*/
|
||||
class UncertainWriteDefinition extends WriteDefinition {
|
||||
UncertainWriteDefinition() {
|
||||
exists(SourceVariable v, BasicBlock bb, int i |
|
||||
this.definesAt(v, bb, i) and
|
||||
variableWrite(bb, i, v, false)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides a set of consistency queries. */
|
||||
module Consistency {
|
||||
abstract class RelevantDefinition extends Definition {
|
||||
abstract predicate hasLocationInfo(
|
||||
string filepath, int startline, int startcolumn, int endline, int endcolumn
|
||||
);
|
||||
}
|
||||
|
||||
query predicate nonUniqueDef(RelevantDefinition def, SourceVariable v, BasicBlock bb, int i) {
|
||||
ssaDefReachesRead(v, def, bb, i) and
|
||||
not exists(unique(Definition def0 | ssaDefReachesRead(v, def0, bb, i)))
|
||||
}
|
||||
|
||||
query predicate readWithoutDef(SourceVariable v, BasicBlock bb, int i) {
|
||||
variableRead(bb, i, v, _) and
|
||||
not ssaDefReachesRead(v, _, bb, i)
|
||||
}
|
||||
|
||||
query predicate deadDef(RelevantDefinition def, SourceVariable v) {
|
||||
v = def.getSourceVariable() and
|
||||
not ssaDefReachesRead(_, def, _, _) and
|
||||
not phiHasInputFromBlock(_, def, _) and
|
||||
not uncertainWriteDefinitionInput(_, def)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
private import semmle.code.cpp.ir.IR
|
||||
private import SsaInternals as Ssa
|
||||
|
||||
class BasicBlock = IRBlock;
|
||||
|
||||
class SourceVariable = Ssa::SourceVariable;
|
||||
|
||||
BasicBlock getImmediateBasicBlockDominator(BasicBlock bb) { result.immediatelyDominates(bb) }
|
||||
|
||||
BasicBlock getABasicBlockSuccessor(BasicBlock bb) { result = bb.getASuccessor() }
|
||||
|
||||
class ExitBasicBlock extends IRBlock {
|
||||
ExitBasicBlock() { this.getLastInstruction() instanceof ExitFunctionInstruction }
|
||||
}
|
||||
|
||||
predicate variableWrite = Ssa::variableWrite/4;
|
||||
|
||||
predicate variableRead = Ssa::variableRead/4;
|
||||
639
cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternals.qll
Normal file
639
cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/SsaInternals.qll
Normal file
@@ -0,0 +1,639 @@
|
||||
import SsaImplCommon
|
||||
private import cpp as Cpp
|
||||
private import semmle.code.cpp.ir.IR
|
||||
private import DataFlowUtil
|
||||
private import DataFlowImplCommon as DataFlowImplCommon
|
||||
private import semmle.code.cpp.models.interfaces.Allocation as Alloc
|
||||
private import semmle.code.cpp.models.interfaces.DataFlow as DataFlow
|
||||
|
||||
private module SourceVariables {
|
||||
private newtype TSourceVariable =
|
||||
TSourceIRVariable(IRVariable var) or
|
||||
TSourceIRVariableIndirection(InitializeIndirectionInstruction init)
|
||||
|
||||
abstract class SourceVariable extends TSourceVariable {
|
||||
IRVariable var;
|
||||
|
||||
abstract string toString();
|
||||
}
|
||||
|
||||
class SourceIRVariable extends SourceVariable, TSourceIRVariable {
|
||||
SourceIRVariable() { this = TSourceIRVariable(var) }
|
||||
|
||||
IRVariable getIRVariable() { result = var }
|
||||
|
||||
override string toString() { result = this.getIRVariable().toString() }
|
||||
}
|
||||
|
||||
class SourceIRVariableIndirection extends SourceVariable, TSourceIRVariableIndirection {
|
||||
InitializeIndirectionInstruction init;
|
||||
|
||||
SourceIRVariableIndirection() {
|
||||
this = TSourceIRVariableIndirection(init) and var = init.getIRVariable()
|
||||
}
|
||||
|
||||
IRVariable getUnderlyingIRVariable() { result = var }
|
||||
|
||||
override string toString() { result = "*" + this.getUnderlyingIRVariable().toString() }
|
||||
}
|
||||
}
|
||||
|
||||
import SourceVariables
|
||||
|
||||
cached
|
||||
private newtype TDefOrUse =
|
||||
TExplicitDef(Instruction store) { explicitWrite(_, store, _) } or
|
||||
TInitializeParam(Instruction instr) {
|
||||
instr instanceof InitializeParameterInstruction
|
||||
or
|
||||
instr instanceof InitializeIndirectionInstruction
|
||||
} or
|
||||
TExplicitUse(Operand op) { isExplicitUse(op) } or
|
||||
TReturnParamIndirection(Operand op) { returnParameterIndirection(op, _) }
|
||||
|
||||
pragma[nomagic]
|
||||
private int getRank(DefOrUse defOrUse, IRBlock block) {
|
||||
defOrUse =
|
||||
rank[result](int i, DefOrUse cand |
|
||||
block.getInstruction(i) = toInstruction(cand)
|
||||
|
|
||||
cand order by i
|
||||
)
|
||||
}
|
||||
|
||||
private class DefOrUse extends TDefOrUse {
|
||||
/** Gets the instruction associated with this definition, if any. */
|
||||
Instruction asDef() { none() }
|
||||
|
||||
/** Gets the operand associated with this use, if any. */
|
||||
Operand asUse() { none() }
|
||||
|
||||
/** Gets a textual representation of this element. */
|
||||
abstract string toString();
|
||||
|
||||
/** Gets the block of this definition or use. */
|
||||
abstract IRBlock getBlock();
|
||||
|
||||
/** Holds if this definition or use has rank `rank` in block `block`. */
|
||||
cached
|
||||
final predicate hasRankInBlock(IRBlock block, int rnk) { rnk = getRank(this, block) }
|
||||
|
||||
/** Gets the location of this element. */
|
||||
abstract Cpp::Location getLocation();
|
||||
}
|
||||
|
||||
private Instruction toInstruction(DefOrUse defOrUse) {
|
||||
result = defOrUse.asDef()
|
||||
or
|
||||
result = defOrUse.asUse().getUse()
|
||||
}
|
||||
|
||||
abstract class Def extends DefOrUse {
|
||||
Instruction store;
|
||||
|
||||
/** Gets the instruction of this definition. */
|
||||
Instruction getInstruction() { result = store }
|
||||
|
||||
/** Gets the variable that is defined by this definition. */
|
||||
abstract SourceVariable getSourceVariable();
|
||||
|
||||
/** Holds if this definition is guaranteed to happen. */
|
||||
abstract predicate isCertain();
|
||||
|
||||
override Instruction asDef() { result = this.getInstruction() }
|
||||
|
||||
override string toString() { result = "Def" }
|
||||
|
||||
override IRBlock getBlock() { result = this.getInstruction().getBlock() }
|
||||
|
||||
override Cpp::Location getLocation() { result = store.getLocation() }
|
||||
}
|
||||
|
||||
private class ExplicitDef extends Def, TExplicitDef {
|
||||
ExplicitDef() { this = TExplicitDef(store) }
|
||||
|
||||
override SourceVariable getSourceVariable() {
|
||||
exists(VariableInstruction var |
|
||||
explicitWrite(_, this.getInstruction(), var) and
|
||||
result.(SourceIRVariable).getIRVariable() = var.getIRVariable()
|
||||
)
|
||||
}
|
||||
|
||||
override predicate isCertain() { explicitWrite(true, this.getInstruction(), _) }
|
||||
}
|
||||
|
||||
private class ParameterDef extends Def, TInitializeParam {
|
||||
ParameterDef() { this = TInitializeParam(store) }
|
||||
|
||||
override SourceVariable getSourceVariable() {
|
||||
result.(SourceIRVariable).getIRVariable() =
|
||||
store.(InitializeParameterInstruction).getIRVariable()
|
||||
or
|
||||
result.(SourceIRVariableIndirection).getUnderlyingIRVariable() =
|
||||
store.(InitializeIndirectionInstruction).getIRVariable()
|
||||
}
|
||||
|
||||
override predicate isCertain() { any() }
|
||||
}
|
||||
|
||||
abstract class Use extends DefOrUse {
|
||||
Operand use;
|
||||
|
||||
override Operand asUse() { result = use }
|
||||
|
||||
/** Gets the underlying operand of this use. */
|
||||
Operand getOperand() { result = use }
|
||||
|
||||
override string toString() { result = "Use" }
|
||||
|
||||
/** Gets the variable that is used by this use. */
|
||||
abstract SourceVariable getSourceVariable();
|
||||
|
||||
override IRBlock getBlock() { result = use.getUse().getBlock() }
|
||||
|
||||
override Cpp::Location getLocation() { result = use.getLocation() }
|
||||
}
|
||||
|
||||
private class ExplicitUse extends Use, TExplicitUse {
|
||||
ExplicitUse() { this = TExplicitUse(use) }
|
||||
|
||||
override SourceVariable getSourceVariable() {
|
||||
exists(VariableInstruction var |
|
||||
use.getDef() = var and
|
||||
if use.getUse() instanceof ReadSideEffectInstruction
|
||||
then result.(SourceIRVariableIndirection).getUnderlyingIRVariable() = var.getIRVariable()
|
||||
else result.(SourceIRVariable).getIRVariable() = var.getIRVariable()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class ReturnParameterIndirection extends Use, TReturnParamIndirection {
|
||||
ReturnParameterIndirection() { this = TReturnParamIndirection(use) }
|
||||
|
||||
override SourceVariable getSourceVariable() {
|
||||
exists(ReturnIndirectionInstruction ret |
|
||||
returnParameterIndirection(use, ret) and
|
||||
result.(SourceIRVariableIndirection).getUnderlyingIRVariable() = ret.getIRVariable()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private predicate isExplicitUse(Operand op) {
|
||||
op.getDef() instanceof VariableAddressInstruction and
|
||||
not exists(LoadInstruction load |
|
||||
load.getSourceAddressOperand() = op and
|
||||
load.getAUse().getUse() instanceof InitializeIndirectionInstruction
|
||||
)
|
||||
}
|
||||
|
||||
private predicate returnParameterIndirection(Operand op, ReturnIndirectionInstruction ret) {
|
||||
ret.getSourceAddressOperand() = op
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `iFrom` computes an address that is used by `iTo`.
|
||||
*/
|
||||
predicate addressFlow(Instruction iFrom, Instruction iTo) {
|
||||
iTo.(CopyValueInstruction).getSourceValue() = iFrom
|
||||
or
|
||||
iTo.(ConvertInstruction).getUnary() = iFrom
|
||||
or
|
||||
iTo.(CheckedConvertOrNullInstruction).getUnary() = iFrom
|
||||
or
|
||||
iTo.(InheritanceConversionInstruction).getUnary() = iFrom
|
||||
or
|
||||
iTo.(PointerArithmeticInstruction).getLeft() = iFrom
|
||||
or
|
||||
iTo.(FieldAddressInstruction).getObjectAddress() = iFrom
|
||||
or
|
||||
// We traverse `LoadInstruction`s since we want to conclude that the
|
||||
// destination of the store operation `*x = source()` is derived from `x`.
|
||||
iTo.(LoadInstruction).getSourceAddress() = iFrom
|
||||
or
|
||||
// We want to include `ReadSideEffectInstruction`s for the same reason that we include
|
||||
// `LoadInstruction`s, but only when a `WriteSideEffectInstruction` for the same index exists as well
|
||||
// (as otherwise we know that the callee won't override the data). However, given an index `i`, the
|
||||
// destination of the `WriteSideEffectInstruction` for `i` is identical to the source address of the
|
||||
// `ReadSideEffectInstruction` for `i`. So we don't have to talk about the `ReadSideEffectInstruction`
|
||||
// at all.
|
||||
exists(WriteSideEffectInstruction write |
|
||||
write.getPrimaryInstruction() = iTo and
|
||||
write.getDestinationAddress() = iFrom
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The reflexive, transitive closure of `addressFlow` that ends as the address of a
|
||||
* store or read operation.
|
||||
*/
|
||||
cached
|
||||
predicate addressFlowTC(Instruction iFrom, Instruction iTo) {
|
||||
iTo = [getDestinationAddress(_), getSourceAddress(_)] and
|
||||
addressFlow*(iFrom, iTo)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the destination address of `instr` if it is a `StoreInstruction` or
|
||||
* a `WriteSideEffectInstruction`.
|
||||
*/
|
||||
Instruction getDestinationAddress(Instruction instr) {
|
||||
result =
|
||||
[
|
||||
instr.(StoreInstruction).getDestinationAddress(),
|
||||
instr.(WriteSideEffectInstruction).getDestinationAddress()
|
||||
]
|
||||
}
|
||||
|
||||
/** Gets the source address of `instr` if it is an instruction that behaves like a `LoadInstruction`. */
|
||||
Instruction getSourceAddress(Instruction instr) { result = getSourceAddressOperand(instr).getDef() }
|
||||
|
||||
/**
|
||||
* Gets the operand that represents the source address of `instr` if it is an
|
||||
* instruction that behaves like a `LoadInstruction`.
|
||||
*/
|
||||
Operand getSourceAddressOperand(Instruction instr) {
|
||||
result =
|
||||
[
|
||||
instr.(LoadInstruction).getSourceAddressOperand(),
|
||||
instr.(ReadSideEffectInstruction).getArgumentOperand()
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the source address of `node` if it's an instruction or operand that
|
||||
* behaves like a `LoadInstruction`.
|
||||
*/
|
||||
Instruction getSourceAddressFromNode(Node node) {
|
||||
result = getSourceAddress(node.asInstruction())
|
||||
or
|
||||
result = getSourceAddress(node.asOperand().(SideEffectOperand).getUse())
|
||||
}
|
||||
|
||||
/** Gets the source value of `instr` if it's an instruction that behaves like a `LoadInstruction`. */
|
||||
Instruction getSourceValue(Instruction instr) { result = getSourceValueOperand(instr).getDef() }
|
||||
|
||||
/**
|
||||
* Gets the operand that represents the source value of `instr` if it's an instruction
|
||||
* that behaves like a `LoadInstruction`.
|
||||
*/
|
||||
Operand getSourceValueOperand(Instruction instr) {
|
||||
result = instr.(LoadInstruction).getSourceValueOperand()
|
||||
or
|
||||
result = instr.(ReadSideEffectInstruction).getSideEffectOperand()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `instr` is a `StoreInstruction` or a `WriteSideEffectInstruction` that writes to an address.
|
||||
* The addresses is computed using `address`, and `certain` is `true` if the write is guaranteed to overwrite
|
||||
* the entire variable.
|
||||
*/
|
||||
cached
|
||||
predicate explicitWrite(boolean certain, Instruction instr, Instruction address) {
|
||||
exists(StoreInstruction store |
|
||||
store = instr and addressFlowTC(address, store.getDestinationAddress())
|
||||
|
|
||||
// Set `certain = false` if the address is derived from any instructions that prevents us from
|
||||
// concluding that the entire variable is overridden.
|
||||
if
|
||||
addressFlowTC(any(Instruction i |
|
||||
i instanceof FieldAddressInstruction or
|
||||
i instanceof PointerArithmeticInstruction or
|
||||
i instanceof LoadInstruction or
|
||||
i instanceof InheritanceConversionInstruction
|
||||
), store.getDestinationAddress())
|
||||
then certain = false
|
||||
else certain = true
|
||||
)
|
||||
or
|
||||
addressFlowTC(address, instr.(WriteSideEffectInstruction).getDestinationAddress()) and
|
||||
certain = false
|
||||
}
|
||||
|
||||
cached
|
||||
private module Cached {
|
||||
private predicate defUseFlow(Node nodeFrom, Node nodeTo) {
|
||||
exists(IRBlock bb1, int i1, IRBlock bb2, int i2, DefOrUse defOrUse, Use use |
|
||||
defOrUse.hasRankInBlock(bb1, i1) and
|
||||
use.hasRankInBlock(bb2, i2) and
|
||||
adjacentDefRead(_, bb1, i1, bb2, i2) and
|
||||
nodeFrom.asInstruction() = toInstruction(defOrUse) and
|
||||
flowOutOfAddressStep(use.getOperand(), nodeTo)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate fromStoreNode(StoreNodeInstr nodeFrom, Node nodeTo) {
|
||||
// Def-use flow from a `StoreNode`.
|
||||
exists(IRBlock bb1, int i1, IRBlock bb2, int i2, Def def, Use use |
|
||||
nodeFrom.isTerminal() and
|
||||
def.getInstruction() = nodeFrom.getStoreInstruction() and
|
||||
def.hasRankInBlock(bb1, i1) and
|
||||
adjacentDefRead(_, bb1, i1, bb2, i2) and
|
||||
use.hasRankInBlock(bb2, i2) and
|
||||
flowOutOfAddressStep(use.getOperand(), nodeTo)
|
||||
)
|
||||
or
|
||||
// This final case is a bit annoying. The write side effect on an expression like `a = new A;` writes
|
||||
// to a fresh address returned by `operator new`, and there's no easy way to use the shared SSA
|
||||
// library to hook that up to the assignment to `a`. So instead we flow to the _first_ use of the
|
||||
// value computed by `operator new` that occurs after `nodeFrom` (to avoid a loop in the
|
||||
// dataflow graph).
|
||||
exists(WriteSideEffectInstruction write, IRBlock bb, int i1, int i2, Operand op |
|
||||
nodeFrom.getInstruction().(CallInstruction).getStaticCallTarget() instanceof
|
||||
Alloc::OperatorNewAllocationFunction and
|
||||
write = nodeFrom.getStoreInstruction() and
|
||||
bb.getInstruction(i1) = write and
|
||||
bb.getInstruction(i2) = op.getUse() and
|
||||
// Flow to an instruction that occurs later in the block.
|
||||
conversionFlow*(nodeFrom.getInstruction(), op.getDef()) and
|
||||
nodeTo.asOperand() = op and
|
||||
i2 > i1 and
|
||||
// There is no previous instruction that also occurs after `nodeFrom`.
|
||||
not exists(Instruction instr, int i |
|
||||
bb.getInstruction(i) = instr and
|
||||
conversionFlow(instr, op.getDef()) and
|
||||
i1 < i and
|
||||
i < i2
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate fromReadNode(ReadNode nodeFrom, Node nodeTo) {
|
||||
exists(IRBlock bb1, int i1, IRBlock bb2, int i2, Use use1, Use use2 |
|
||||
use1.hasRankInBlock(bb1, i1) and
|
||||
use2.hasRankInBlock(bb2, i2) and
|
||||
use1.getOperand().getDef() = nodeFrom.getInstruction() and
|
||||
adjacentDefRead(_, bb1, i1, bb2, i2) and
|
||||
flowOutOfAddressStep(use2.getOperand(), nodeTo)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate fromPhiNode(SsaPhiNode nodeFrom, Node nodeTo) {
|
||||
exists(PhiNode phi, Use use, IRBlock block, int rnk |
|
||||
phi = nodeFrom.getPhiNode() and
|
||||
adjacentDefRead(phi, _, _, block, rnk) and
|
||||
use.hasRankInBlock(block, rnk) and
|
||||
flowOutOfAddressStep(use.getOperand(), nodeTo)
|
||||
)
|
||||
}
|
||||
|
||||
private predicate toPhiNode(Node nodeFrom, SsaPhiNode nodeTo) {
|
||||
// Flow to phi nodes
|
||||
exists(Def def, IRBlock block, int rnk |
|
||||
def.hasRankInBlock(block, rnk) and
|
||||
nodeTo.hasInputAtRankInBlock(block, rnk)
|
||||
|
|
||||
exists(StoreNodeInstr storeNode |
|
||||
storeNode = nodeFrom and
|
||||
storeNode.isTerminal() and
|
||||
def.getInstruction() = storeNode.getStoreInstruction()
|
||||
)
|
||||
or
|
||||
def.getInstruction() = nodeFrom.asInstruction()
|
||||
)
|
||||
or
|
||||
// Phi -> phi flow
|
||||
nodeTo.hasInputAtRankInBlock(_, _, nodeFrom.(SsaPhiNode).getPhiNode())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom` is a read or write, and `nTo` is the next subsequent read of the variable
|
||||
* written (or read) by `storeOrRead`.
|
||||
*/
|
||||
cached
|
||||
predicate ssaFlow(Node nodeFrom, Node nodeTo) {
|
||||
// Def-use/use-use flow from an `InstructionNode`.
|
||||
defUseFlow(nodeFrom, nodeTo)
|
||||
or
|
||||
// Def-use flow from a `StoreNode`.
|
||||
fromStoreNode(nodeFrom, nodeTo)
|
||||
or
|
||||
// Use-use flow from a `ReadNode`.
|
||||
fromReadNode(nodeFrom, nodeTo)
|
||||
or
|
||||
fromPhiNode(nodeFrom, nodeTo)
|
||||
or
|
||||
toPhiNode(nodeFrom, nodeTo)
|
||||
or
|
||||
// When we want to transfer flow out of a `StoreNode` we perform two steps:
|
||||
// 1. Find the next use of the address being stored to
|
||||
// 2. Find the `LoadInstruction` that loads the address
|
||||
// When the address being stored into doesn't have a `LoadInstruction` associated with it because it's
|
||||
// passed into a `CallInstruction` we transfer flow to the `ReadSideEffect`, which will then flow into
|
||||
// the callee. We then pickup the flow from the `InitializeIndirectionInstruction` and use the shared
|
||||
// SSA library to determine where the next use of the address that received the flow is.
|
||||
exists(Node init, Node mid |
|
||||
nodeFrom.asInstruction().(InitializeIndirectionInstruction).getIRVariable() =
|
||||
init.asInstruction().(InitializeParameterInstruction).getIRVariable() and
|
||||
// No need for the flow if the next use is the instruction that returns the flow out of the callee.
|
||||
not mid.asInstruction() instanceof ReturnIndirectionInstruction and
|
||||
// Find the next use of the address
|
||||
ssaFlow(init, mid) and
|
||||
// And flow to the next load of that address
|
||||
flowOutOfAddressStep([mid.asInstruction().getAUse(), mid.asOperand()], nodeTo)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `iTo` is a conversion-like instruction that copies
|
||||
* the value computed by `iFrom`.
|
||||
*
|
||||
* This predicate is used by `fromStoreNode` to find the next use of a pointer that
|
||||
* points to freshly allocated memory.
|
||||
*/
|
||||
private predicate conversionFlow(Instruction iFrom, Instruction iTo) {
|
||||
iTo.(CopyValueInstruction).getSourceValue() = iFrom
|
||||
or
|
||||
iTo.(ConvertInstruction).getUnary() = iFrom
|
||||
or
|
||||
iTo.(CheckedConvertOrNullInstruction).getUnary() = iFrom
|
||||
or
|
||||
iTo.(InheritanceConversionInstruction).getUnary() = iFrom
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private predicate callTargetHasInputOutput(
|
||||
CallInstruction call, DataFlow::FunctionInput input, DataFlow::FunctionOutput output
|
||||
) {
|
||||
exists(DataFlow::DataFlowFunction func |
|
||||
call.getStaticCallTarget() = func and
|
||||
func.hasDataFlow(input, output)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The role of `flowOutOfAddressStep` is to select the node for which we want dataflow to end up in
|
||||
* after the shared SSA library's `adjacentDefRead` predicate has determined that `operand` is the
|
||||
* next use of some variable.
|
||||
*
|
||||
* More precisely, this predicate holds if `operand` is an operand that represents an address, and:
|
||||
* - `nodeTo` is the next load of that address, or
|
||||
* - `nodeTo` is a `ReadNode` that uses the definition of `operand` to start a sequence of reads, or
|
||||
* - `nodeTo` is the outer-most `StoreNode` that uses the address represented by `operand`. We obtain
|
||||
* use-use flow in this case since `StoreNodeFlow::flowOutOf` will then provide flow to the next of
|
||||
* of `operand`.
|
||||
*
|
||||
* There is one final (slightly annoying) case: When `operand` is a an argument to a modeled function
|
||||
* without any `ReadSideEffect` (such as `std::move`). Here, the address flows from the argument to
|
||||
* the return value, which might then be read later.
|
||||
*/
|
||||
private predicate flowOutOfAddressStep(Operand operand, Node nodeTo) {
|
||||
// Flow into a read node
|
||||
exists(ReadNode readNode | readNode = nodeTo |
|
||||
readNode.isInitial() and
|
||||
operand.getDef() = readNode.getInstruction()
|
||||
)
|
||||
or
|
||||
exists(StoreNodeInstr storeNode, Instruction def |
|
||||
storeNode = nodeTo and
|
||||
def = operand.getDef()
|
||||
|
|
||||
storeNode.isTerminal() and
|
||||
not addressFlow(def, _) and
|
||||
// Only transfer flow to a store node if it doesn't immediately overwrite the address
|
||||
// we've just written to.
|
||||
explicitWrite(false, storeNode.getStoreInstruction(), def)
|
||||
)
|
||||
or
|
||||
// The destination of a store operation has undergone lvalue-to-rvalue conversion and is now a
|
||||
// right-hand-side of a store operation.
|
||||
// Find the next use of the variable in that store operation, and recursively find the load of that
|
||||
// pointer. For example, consider this case:
|
||||
//
|
||||
// ```cpp
|
||||
// int x = source();
|
||||
// int* p = &x;
|
||||
// sink(*p);
|
||||
// ```
|
||||
//
|
||||
// if we want to find the load of the address of `x`, we see that the pointer is stored into `p`,
|
||||
// and we then need to recursively look for the load of `p`.
|
||||
exists(
|
||||
Def def, StoreInstruction store, IRBlock block1, int rnk1, Use use, IRBlock block2, int rnk2
|
||||
|
|
||||
store = def.getInstruction() and
|
||||
store.getSourceValueOperand() = operand and
|
||||
def.hasRankInBlock(block1, rnk1) and
|
||||
use.hasRankInBlock(block2, rnk2) and
|
||||
adjacentDefRead(_, block1, rnk1, block2, rnk2)
|
||||
|
|
||||
// The shared SSA library has determined that `use` is the next use of the operand
|
||||
// so we find the next load of that use (but only if there is no `PostUpdateNode`) we
|
||||
// need to flow into first.
|
||||
not StoreNodeFlow::flowInto(store, _) and
|
||||
flowOutOfAddressStep(use.getOperand(), nodeTo)
|
||||
or
|
||||
// It may also be the case that `store` gives rise to another store step. So let's make sure that
|
||||
// we also take those into account.
|
||||
StoreNodeFlow::flowInto(store, nodeTo)
|
||||
)
|
||||
or
|
||||
// As we find the next load of an address, we might come across another use of the same variable.
|
||||
// In that case, we recursively find the next use of _that_ operand, and continue searching for
|
||||
// the next load of that operand. For example, consider this case:
|
||||
//
|
||||
// ```cpp
|
||||
// int x = source();
|
||||
// use(&x);
|
||||
// int* p = &x;
|
||||
// sink(*p);
|
||||
// ```
|
||||
//
|
||||
// The next use of `x` after its definition is `use(&x)`, but there is a later load of the address
|
||||
// of `x` that we want to flow to. So we use the shared SSA library to find the next load.
|
||||
not operand = getSourceAddressOperand(_) and
|
||||
exists(Use use1, Use use2, IRBlock block1, int rnk1, IRBlock block2, int rnk2 |
|
||||
use1.getOperand() = operand and
|
||||
use1.hasRankInBlock(block1, rnk1) and
|
||||
// Don't flow to the next use if this use is part of a store operation that totally
|
||||
// overrides a variable.
|
||||
not explicitWrite(true, _, use1.getOperand().getDef()) and
|
||||
adjacentDefRead(_, block1, rnk1, block2, rnk2) and
|
||||
use2.hasRankInBlock(block2, rnk2) and
|
||||
flowOutOfAddressStep(use2.getOperand(), nodeTo)
|
||||
)
|
||||
or
|
||||
operand = getSourceAddressOperand(nodeTo.asInstruction())
|
||||
or
|
||||
exists(ReturnIndirectionInstruction ret |
|
||||
ret.getSourceAddressOperand() = operand and
|
||||
ret = nodeTo.asInstruction()
|
||||
)
|
||||
or
|
||||
exists(ReturnValueInstruction ret |
|
||||
ret.getReturnAddressOperand() = operand and
|
||||
nodeTo.asInstruction() = ret
|
||||
)
|
||||
or
|
||||
exists(CallInstruction call, int index, ReadSideEffectInstruction read |
|
||||
call.getArgumentOperand(index) = operand and
|
||||
read = getSideEffectFor(call, index) and
|
||||
nodeTo.asOperand() = read.getSideEffectOperand()
|
||||
)
|
||||
or
|
||||
exists(CopyInstruction copy |
|
||||
not exists(getSourceAddressOperand(copy)) and
|
||||
copy.getSourceValueOperand() = operand and
|
||||
flowOutOfAddressStep(copy.getAUse(), nodeTo)
|
||||
)
|
||||
or
|
||||
exists(ConvertInstruction convert |
|
||||
convert.getUnaryOperand() = operand and
|
||||
flowOutOfAddressStep(convert.getAUse(), nodeTo)
|
||||
)
|
||||
or
|
||||
exists(CheckedConvertOrNullInstruction convert |
|
||||
convert.getUnaryOperand() = operand and
|
||||
flowOutOfAddressStep(convert.getAUse(), nodeTo)
|
||||
)
|
||||
or
|
||||
exists(InheritanceConversionInstruction convert |
|
||||
convert.getUnaryOperand() = operand and
|
||||
flowOutOfAddressStep(convert.getAUse(), nodeTo)
|
||||
)
|
||||
or
|
||||
exists(PointerArithmeticInstruction arith |
|
||||
arith.getLeftOperand() = operand and
|
||||
flowOutOfAddressStep(arith.getAUse(), nodeTo)
|
||||
)
|
||||
or
|
||||
// Flow through a modeled function that has parameter -> return value flow.
|
||||
exists(
|
||||
CallInstruction call, int index, DataFlow::FunctionInput input,
|
||||
DataFlow::FunctionOutput output
|
||||
|
|
||||
callTargetHasInputOutput(call, input, output) and
|
||||
call.getArgumentOperand(index) = operand and
|
||||
not getSideEffectFor(call, index) instanceof ReadSideEffectInstruction and
|
||||
input.isParameter(index) and
|
||||
output.isReturnValue() and
|
||||
flowOutOfAddressStep(call.getAUse(), nodeTo)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
import Cached
|
||||
|
||||
/**
|
||||
* Holds if the `i`'th write in block `bb` writes to the variable `v`.
|
||||
* `certain` is `true` if the write is guaranteed to overwrite the entire variable.
|
||||
*/
|
||||
predicate variableWrite(IRBlock bb, int i, SourceVariable v, boolean certain) {
|
||||
DataFlowImplCommon::forceCachingInSameStage() and
|
||||
exists(Def def |
|
||||
def.hasRankInBlock(bb, i) and
|
||||
v = def.getSourceVariable() and
|
||||
(if def.isCertain() then certain = true else certain = false)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the `i`'th read in block `bb` reads to the variable `v`.
|
||||
* `certain` is `true` if the read is guaranteed. For C++, this is always the case.
|
||||
*/
|
||||
predicate variableRead(IRBlock bb, int i, SourceVariable v, boolean certain) {
|
||||
exists(Use use |
|
||||
use.hasRankInBlock(bb, i) and
|
||||
v = use.getSourceVariable() and
|
||||
certain = true
|
||||
)
|
||||
}
|
||||
@@ -44,8 +44,6 @@ private predicate instructionToOperandTaintStep(Instruction fromInstr, Operand t
|
||||
fromInstr = readInstr.getArgumentDef() and
|
||||
toOperand = readInstr.getSideEffectOperand()
|
||||
)
|
||||
or
|
||||
toOperand.(LoadOperand).getAnyDef() = fromInstr
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,8 +82,6 @@ private predicate operandToInstructionTaintStep(Operand opFrom, Instruction inst
|
||||
instrTo.(FieldAddressInstruction).getField().getDeclaringType() instanceof Union
|
||||
)
|
||||
or
|
||||
instrTo.(LoadInstruction).getSourceAddressOperand() = opFrom
|
||||
or
|
||||
// Flow from an element to an array or union that contains it.
|
||||
instrTo.(ChiInstruction).getPartialOperand() = opFrom and
|
||||
not instrTo.isResultConflated() and
|
||||
|
||||
@@ -762,11 +762,21 @@ class ReturnValueInstruction extends ReturnInstruction {
|
||||
*/
|
||||
final LoadOperand getReturnValueOperand() { result = this.getAnOperand() }
|
||||
|
||||
/**
|
||||
* Gets the operand that provides the address of the value being returned by the function.
|
||||
*/
|
||||
final AddressOperand getReturnAddressOperand() { result = this.getAnOperand() }
|
||||
|
||||
/**
|
||||
* Gets the instruction whose result provides the value being returned by the function, if an
|
||||
* exact definition is available.
|
||||
*/
|
||||
final Instruction getReturnValue() { result = this.getReturnValueOperand().getDef() }
|
||||
|
||||
/**
|
||||
* Gets the instruction whose result provides the address of the value being returned by the function.
|
||||
*/
|
||||
final Instruction getReturnAddress() { result = this.getReturnAddressOperand().getDef() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,14 @@ private import internal.OperandInternal
|
||||
private class TStageOperand =
|
||||
TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand;
|
||||
|
||||
/**
|
||||
* A known location. Testing `loc instanceof KnownLocation` will account for non existing locations, as
|
||||
* opposed to testing `not loc isntanceof UnknownLocation`
|
||||
*/
|
||||
private class KnownLocation extends Language::Location {
|
||||
KnownLocation() { not this instanceof Language::UnknownLocation }
|
||||
}
|
||||
|
||||
/**
|
||||
* An operand of an `Instruction`. The operand represents a use of the result of one instruction
|
||||
* (the defining instruction) in another instruction (the use instruction)
|
||||
@@ -45,8 +53,10 @@ class Operand extends TStageOperand {
|
||||
|
||||
/**
|
||||
* Gets the location of the source code for this operand.
|
||||
* By default this is where the operand is used, but some subclasses may override this
|
||||
* using `getAnyDef()` if it makes more sense.
|
||||
*/
|
||||
final Language::Location getLocation() { result = this.getUse().getLocation() }
|
||||
Language::Location getLocation() { result = this.getUse().getLocation() }
|
||||
|
||||
/**
|
||||
* Gets the function that contains this operand.
|
||||
@@ -269,6 +279,10 @@ class RegisterOperand extends NonPhiOperand, TRegisterOperand {
|
||||
|
||||
final override string toString() { result = tag.toString() }
|
||||
|
||||
// most `RegisterOperands` have a more meaningful location at the definition
|
||||
// the only exception are specific cases of `ThisArgumentOperand`
|
||||
override Language::Location getLocation() { result = this.getAnyDef().getLocation() }
|
||||
|
||||
final override Instruction getAnyDef() { result = defInstr }
|
||||
|
||||
final override Overlap getDefinitionOverlap() {
|
||||
@@ -401,11 +415,19 @@ class ArgumentOperand extends RegisterOperand {
|
||||
}
|
||||
|
||||
/**
|
||||
* An operand representing the implicit 'this' argument to a member function
|
||||
* An operand representing the implicit `this` argument to a member function
|
||||
* call.
|
||||
*/
|
||||
class ThisArgumentOperand extends ArgumentOperand {
|
||||
override ThisArgumentOperandTag tag;
|
||||
|
||||
// in most cases the def location makes more sense, but in some corner cases it
|
||||
// has an unknown location: in those cases we fall back to the use location
|
||||
override Language::Location getLocation() {
|
||||
if this.getAnyDef().getLocation() instanceof KnownLocation
|
||||
then result = this.getAnyDef().getLocation()
|
||||
else result = this.getUse().getLocation()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -762,11 +762,21 @@ class ReturnValueInstruction extends ReturnInstruction {
|
||||
*/
|
||||
final LoadOperand getReturnValueOperand() { result = this.getAnOperand() }
|
||||
|
||||
/**
|
||||
* Gets the operand that provides the address of the value being returned by the function.
|
||||
*/
|
||||
final AddressOperand getReturnAddressOperand() { result = this.getAnOperand() }
|
||||
|
||||
/**
|
||||
* Gets the instruction whose result provides the value being returned by the function, if an
|
||||
* exact definition is available.
|
||||
*/
|
||||
final Instruction getReturnValue() { result = this.getReturnValueOperand().getDef() }
|
||||
|
||||
/**
|
||||
* Gets the instruction whose result provides the address of the value being returned by the function.
|
||||
*/
|
||||
final Instruction getReturnAddress() { result = this.getReturnAddressOperand().getDef() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,14 @@ private import internal.OperandInternal
|
||||
private class TStageOperand =
|
||||
TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand;
|
||||
|
||||
/**
|
||||
* A known location. Testing `loc instanceof KnownLocation` will account for non existing locations, as
|
||||
* opposed to testing `not loc isntanceof UnknownLocation`
|
||||
*/
|
||||
private class KnownLocation extends Language::Location {
|
||||
KnownLocation() { not this instanceof Language::UnknownLocation }
|
||||
}
|
||||
|
||||
/**
|
||||
* An operand of an `Instruction`. The operand represents a use of the result of one instruction
|
||||
* (the defining instruction) in another instruction (the use instruction)
|
||||
@@ -45,8 +53,10 @@ class Operand extends TStageOperand {
|
||||
|
||||
/**
|
||||
* Gets the location of the source code for this operand.
|
||||
* By default this is where the operand is used, but some subclasses may override this
|
||||
* using `getAnyDef()` if it makes more sense.
|
||||
*/
|
||||
final Language::Location getLocation() { result = this.getUse().getLocation() }
|
||||
Language::Location getLocation() { result = this.getUse().getLocation() }
|
||||
|
||||
/**
|
||||
* Gets the function that contains this operand.
|
||||
@@ -269,6 +279,10 @@ class RegisterOperand extends NonPhiOperand, TRegisterOperand {
|
||||
|
||||
final override string toString() { result = tag.toString() }
|
||||
|
||||
// most `RegisterOperands` have a more meaningful location at the definition
|
||||
// the only exception are specific cases of `ThisArgumentOperand`
|
||||
override Language::Location getLocation() { result = this.getAnyDef().getLocation() }
|
||||
|
||||
final override Instruction getAnyDef() { result = defInstr }
|
||||
|
||||
final override Overlap getDefinitionOverlap() {
|
||||
@@ -401,11 +415,19 @@ class ArgumentOperand extends RegisterOperand {
|
||||
}
|
||||
|
||||
/**
|
||||
* An operand representing the implicit 'this' argument to a member function
|
||||
* An operand representing the implicit `this` argument to a member function
|
||||
* call.
|
||||
*/
|
||||
class ThisArgumentOperand extends ArgumentOperand {
|
||||
override ThisArgumentOperandTag tag;
|
||||
|
||||
// in most cases the def location makes more sense, but in some corner cases it
|
||||
// has an unknown location: in those cases we fall back to the use location
|
||||
override Language::Location getLocation() {
|
||||
if this.getAnyDef().getLocation() instanceof KnownLocation
|
||||
then result = this.getAnyDef().getLocation()
|
||||
else result = this.getUse().getLocation()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1032,7 +1032,7 @@ abstract class TranslatedConversion extends TranslatedNonConstantExpr {
|
||||
|
||||
final override TranslatedElement getChild(int id) { id = 0 and result = this.getOperand() }
|
||||
|
||||
final TranslatedExpr getOperand() { result = getTranslatedExpr(expr.(Conversion).getExpr()) }
|
||||
final TranslatedExpr getOperand() { result = getTranslatedExpr(expr.getExpr()) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1305,9 +1305,9 @@ class TranslatedBinaryOperation extends TranslatedSingleInstructionExpr {
|
||||
}
|
||||
|
||||
override Opcode getOpcode() {
|
||||
result = binaryArithmeticOpcode(expr.(BinaryArithmeticOperation)) or
|
||||
result = binaryBitwiseOpcode(expr.(BinaryBitwiseOperation)) or
|
||||
result = comparisonOpcode(expr.(ComparisonOperation))
|
||||
result = binaryArithmeticOpcode(expr) or
|
||||
result = binaryBitwiseOpcode(expr) or
|
||||
result = comparisonOpcode(expr)
|
||||
}
|
||||
|
||||
override int getInstructionElementSize(InstructionTag tag) {
|
||||
|
||||
@@ -103,9 +103,7 @@ class TranslatedDeclStmt extends TranslatedStmt {
|
||||
class TranslatedExprStmt extends TranslatedStmt {
|
||||
override ExprStmt stmt;
|
||||
|
||||
TranslatedExpr getExpr() {
|
||||
result = getTranslatedExpr(stmt.(ExprStmt).getExpr().getFullyConverted())
|
||||
}
|
||||
TranslatedExpr getExpr() { result = getTranslatedExpr(stmt.getExpr().getFullyConverted()) }
|
||||
|
||||
override TranslatedElement getChild(int id) { id = 0 and result = getExpr() }
|
||||
|
||||
|
||||
@@ -762,11 +762,21 @@ class ReturnValueInstruction extends ReturnInstruction {
|
||||
*/
|
||||
final LoadOperand getReturnValueOperand() { result = this.getAnOperand() }
|
||||
|
||||
/**
|
||||
* Gets the operand that provides the address of the value being returned by the function.
|
||||
*/
|
||||
final AddressOperand getReturnAddressOperand() { result = this.getAnOperand() }
|
||||
|
||||
/**
|
||||
* Gets the instruction whose result provides the value being returned by the function, if an
|
||||
* exact definition is available.
|
||||
*/
|
||||
final Instruction getReturnValue() { result = this.getReturnValueOperand().getDef() }
|
||||
|
||||
/**
|
||||
* Gets the instruction whose result provides the address of the value being returned by the function.
|
||||
*/
|
||||
final Instruction getReturnAddress() { result = this.getReturnAddressOperand().getDef() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,14 @@ private import internal.OperandInternal
|
||||
private class TStageOperand =
|
||||
TRegisterOperand or TNonSSAMemoryOperand or TPhiOperand or TChiOperand;
|
||||
|
||||
/**
|
||||
* A known location. Testing `loc instanceof KnownLocation` will account for non existing locations, as
|
||||
* opposed to testing `not loc isntanceof UnknownLocation`
|
||||
*/
|
||||
private class KnownLocation extends Language::Location {
|
||||
KnownLocation() { not this instanceof Language::UnknownLocation }
|
||||
}
|
||||
|
||||
/**
|
||||
* An operand of an `Instruction`. The operand represents a use of the result of one instruction
|
||||
* (the defining instruction) in another instruction (the use instruction)
|
||||
@@ -45,8 +53,10 @@ class Operand extends TStageOperand {
|
||||
|
||||
/**
|
||||
* Gets the location of the source code for this operand.
|
||||
* By default this is where the operand is used, but some subclasses may override this
|
||||
* using `getAnyDef()` if it makes more sense.
|
||||
*/
|
||||
final Language::Location getLocation() { result = this.getUse().getLocation() }
|
||||
Language::Location getLocation() { result = this.getUse().getLocation() }
|
||||
|
||||
/**
|
||||
* Gets the function that contains this operand.
|
||||
@@ -269,6 +279,10 @@ class RegisterOperand extends NonPhiOperand, TRegisterOperand {
|
||||
|
||||
final override string toString() { result = tag.toString() }
|
||||
|
||||
// most `RegisterOperands` have a more meaningful location at the definition
|
||||
// the only exception are specific cases of `ThisArgumentOperand`
|
||||
override Language::Location getLocation() { result = this.getAnyDef().getLocation() }
|
||||
|
||||
final override Instruction getAnyDef() { result = defInstr }
|
||||
|
||||
final override Overlap getDefinitionOverlap() {
|
||||
@@ -401,11 +415,19 @@ class ArgumentOperand extends RegisterOperand {
|
||||
}
|
||||
|
||||
/**
|
||||
* An operand representing the implicit 'this' argument to a member function
|
||||
* An operand representing the implicit `this` argument to a member function
|
||||
* call.
|
||||
*/
|
||||
class ThisArgumentOperand extends ArgumentOperand {
|
||||
override ThisArgumentOperandTag tag;
|
||||
|
||||
// in most cases the def location makes more sense, but in some corner cases it
|
||||
// has an unknown location: in those cases we fall back to the use location
|
||||
override Language::Location getLocation() {
|
||||
if this.getAnyDef().getLocation() instanceof KnownLocation
|
||||
then result = this.getAnyDef().getLocation()
|
||||
else result = this.getUse().getLocation()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,6 +34,16 @@ private class IteratorByTraits extends Iterator {
|
||||
IteratorByTraits() { exists(IteratorTraits it | it.getIteratorType() = this) }
|
||||
}
|
||||
|
||||
/**
|
||||
* The C++ standard includes an `std::iterator_traits` specialization for pointer types. When
|
||||
* this specialization is included in the database, a pointer type `T*` will be an instance
|
||||
* of the `IteratorByTraits` class. However, if the `T*` specialization is not in the database,
|
||||
* we need to explicitly include them with this class.
|
||||
*/
|
||||
private class IteratorByPointer extends Iterator instanceof PointerType {
|
||||
IteratorByPointer() { not this instanceof IteratorByTraits }
|
||||
}
|
||||
|
||||
/**
|
||||
* A type which has the typedefs expected for an iterator.
|
||||
*/
|
||||
|
||||
@@ -88,7 +88,7 @@ abstract class Architecture extends string {
|
||||
or
|
||||
t instanceof LongLongType and result = this.longLongSize()
|
||||
or
|
||||
result = this.enumBitSize(t.(Enum))
|
||||
result = this.enumBitSize(t)
|
||||
or
|
||||
result = this.integralBitSize(t.(SpecifiedType).getBaseType())
|
||||
or
|
||||
@@ -183,7 +183,7 @@ abstract class Architecture extends string {
|
||||
or
|
||||
t instanceof ReferenceType and result = this.pointerSize()
|
||||
or
|
||||
result = this.enumAlignment(t.(Enum))
|
||||
result = this.enumAlignment(t)
|
||||
or
|
||||
result = this.alignment(t.(SpecifiedType).getBaseType())
|
||||
or
|
||||
@@ -232,14 +232,14 @@ private Field getAnInitialField(PaddedType t) {
|
||||
result = t.getAField()
|
||||
or
|
||||
// Initial field of the type of a field of the union
|
||||
result = getAnInitialField(t.getAField().getUnspecifiedType().(PaddedType))
|
||||
result = getAnInitialField(t.getAField().getUnspecifiedType())
|
||||
else
|
||||
exists(Field firstField | t.fieldIndex(firstField) = 1 |
|
||||
// The first field of `t`
|
||||
result = firstField
|
||||
or
|
||||
// Initial field of the first field of `t`
|
||||
result = getAnInitialField(firstField.getUnspecifiedType().(PaddedType))
|
||||
result = getAnInitialField(firstField.getUnspecifiedType())
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ class RangeSsaDefinition extends ControlFlowNodeBase {
|
||||
BasicBlock getBasicBlock() { result.contains(this.getDefinition()) }
|
||||
|
||||
/** Whether this definition is a phi node for variable `v`. */
|
||||
predicate isPhiNode(StackVariable v) { exists(RangeSSA x | x.phi_node(v, this.(BasicBlock))) }
|
||||
predicate isPhiNode(StackVariable v) { exists(RangeSSA x | x.phi_node(v, this)) }
|
||||
|
||||
/**
|
||||
* DEPRECATED: Use isGuardPhi/4 instead
|
||||
|
||||
@@ -173,6 +173,6 @@ private predicate fileWriteWithConvChar(FormattingFunctionCall ffc, Expr source,
|
||||
source = ffc.getFormatArgument(n)
|
||||
|
|
||||
exists(f.getOutputParameterIndex(true)) and
|
||||
conv = ffc.(FormattingFunctionCall).getFormat().(FormatLiteral).getConversionChar(n)
|
||||
conv = ffc.getFormat().(FormatLiteral).getConversionChar(n)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -589,7 +589,7 @@ private predicate mk_HasAlloc(HashCons hc, NewOrNewArrayExpr new) {
|
||||
}
|
||||
|
||||
private predicate mk_HasExtent(HashCons hc, NewArrayExpr new) {
|
||||
hc = hashCons(new.(NewArrayExpr).getExtent().getFullyConverted())
|
||||
hc = hashCons(new.getExtent().getFullyConverted())
|
||||
}
|
||||
|
||||
private predicate analyzableNewExpr(NewExpr new) {
|
||||
@@ -619,7 +619,7 @@ private predicate analyzableNewArrayExpr(NewArrayExpr new) {
|
||||
strictcount(new.getAllocatedType().getUnspecifiedType()) = 1 and
|
||||
count(new.getAllocatorCall().getFullyConverted()) <= 1 and
|
||||
count(new.getInitializer().getFullyConverted()) <= 1 and
|
||||
count(new.(NewArrayExpr).getExtent().getFullyConverted()) <= 1
|
||||
count(new.getExtent().getFullyConverted()) <= 1
|
||||
}
|
||||
|
||||
private predicate mk_NewArrayExpr(
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
|
||||
<overview>
|
||||
<p>about General Class-Level Information</p>
|
||||
|
||||
<!--TOC-->
|
||||
|
||||
</overview>
|
||||
</qhelp>
|
||||
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
|
||||
<overview>
|
||||
<p>about Architecture</p>
|
||||
|
||||
<!--TOC-->
|
||||
|
||||
</overview>
|
||||
</qhelp>
|
||||
@@ -81,9 +81,8 @@ class BlockOrNonChild extends Element {
|
||||
predicate emptyBlockContainsNonchild(BlockStmt b) {
|
||||
emptyBlock(_, b) and
|
||||
exists(BlockOrNonChild c, AffectedFile file |
|
||||
c.(BlockOrNonChild).getStartRankIn(file) = 1 + b.(BlockOrNonChild).getStartRankIn(file) and
|
||||
c.(BlockOrNonChild).getNonContiguousEndRankIn(file) <
|
||||
b.(BlockOrNonChild).getNonContiguousEndRankIn(file)
|
||||
c.getStartRankIn(file) = 1 + b.(BlockOrNonChild).getStartRankIn(file) and
|
||||
c.getNonContiguousEndRankIn(file) < b.(BlockOrNonChild).getNonContiguousEndRankIn(file)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ class Thing extends Locatable {
|
||||
}
|
||||
|
||||
Thing callsOrAccesses() {
|
||||
this.(Function).calls(result.(Function))
|
||||
this.(Function).calls(result)
|
||||
or
|
||||
this.(Function).accesses(result.(Function))
|
||||
or
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
|
||||
<overview>
|
||||
<p>about best practices</p>
|
||||
|
||||
<!--TOC-->
|
||||
|
||||
|
||||
|
||||
|
||||
</overview>
|
||||
</qhelp>
|
||||
@@ -63,14 +63,14 @@ predicate cannotContainString(Type t) {
|
||||
|
||||
predicate isNonConst(DataFlow::Node node) {
|
||||
exists(Expr e | e = node.asExpr() |
|
||||
exists(FunctionCall fc | fc = e.(FunctionCall) |
|
||||
exists(FunctionCall fc | fc = e |
|
||||
not (
|
||||
whitelistFunction(fc.getTarget(), _) or
|
||||
fc.getTarget().hasDefinition()
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(Parameter p | p = e.(VariableAccess).getTarget().(Parameter) |
|
||||
exists(Parameter p | p = e.(VariableAccess).getTarget() |
|
||||
p.getFunction().getName() = "main" and p.getType() instanceof PointerType
|
||||
)
|
||||
or
|
||||
|
||||
@@ -10,7 +10,7 @@ import semmle.code.cpp.commons.DateTime
|
||||
* Get the top-level `BinaryOperation` enclosing the expression e.
|
||||
*/
|
||||
private BinaryOperation getATopLevelBinaryOperationExpression(Expr e) {
|
||||
result = e.getEnclosingElement().(BinaryOperation)
|
||||
result = e.getEnclosingElement()
|
||||
or
|
||||
result = getATopLevelBinaryOperationExpression(e.getEnclosingElement())
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ predicate functionDefinedInIfDefRecursive(Function f) {
|
||||
*/
|
||||
predicate baseCall(FunctionCall call) {
|
||||
call.getNameQualifier().getQualifyingElement() =
|
||||
call.getEnclosingFunction().getDeclaringType().(Class).getABaseClass+()
|
||||
call.getEnclosingFunction().getDeclaringType().getABaseClass+()
|
||||
}
|
||||
|
||||
from PureExprInVoidContext peivc, Locatable parent, Locatable info, string info_text, string tail
|
||||
|
||||
@@ -15,7 +15,7 @@ import cpp
|
||||
from File f, float complexity, float loc
|
||||
where
|
||||
f.fromSource() and
|
||||
loc = sum(FunctionDeclarationEntry fde | fde.getFile() = f | fde.getNumberOfLines()).(float) and
|
||||
loc = sum(FunctionDeclarationEntry fde | fde.getFile() = f | fde.getNumberOfLines()) and
|
||||
if loc > 0
|
||||
then
|
||||
// Weighted average of complexity by function length
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
This metric measures the number of lines of text that have been added, deleted
|
||||
or modified in files below this location in the tree.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Code churn is known to be a good (if not the best) predictor of defects in a
|
||||
code component (see e.g. [Nagappan] or [Khoshgoftaar]). The intuition is that
|
||||
files, packages or projects that have experienced a disproportionately high
|
||||
amount of churn for the amount of code involved may have been harder to write,
|
||||
and are thus likely to contain more bugs.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
It is a fact of life that some code is going to be changed more than the rest,
|
||||
and little can be done to change this. However, bearing in mind code churn's
|
||||
effectiveness as a defect predictor, code that has been repeatedly changed
|
||||
should be subjected to vigorous testing and code review.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
<references>
|
||||
|
||||
|
||||
<li>
|
||||
N. Nagappan et al. <em>Change Bursts as Defect Predictors</em>. In Proceedings of the 21st IEEE International Symposium on Software Reliability Engineering, 2010.
|
||||
</li>
|
||||
<li>
|
||||
T. M. Khoshgoftaar and R. M. Szabo. <em>Improving code churn predictions during the system test and maintenance phases</em>. In ICSM '94, 1994, pp. 58-67.
|
||||
</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,6 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<include src="HChurn.qhelp" />
|
||||
</qhelp>
|
||||
@@ -1,6 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<include src="HChurn.qhelp" />
|
||||
</qhelp>
|
||||
@@ -1,48 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
This metric measures the number of different authors (by examining the
|
||||
version control history)
|
||||
for files below this location in the tree. (This is a better version
|
||||
of the metric that counts the number of different authors using Javadoc
|
||||
tags.)
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Files that have been changed by a large number of different authors are
|
||||
by definition the product of many minds. New authors working on a file
|
||||
may be less familiar with the design and implementation of the code than
|
||||
the original authors, which can be a potential source of bugs. Furthermore,
|
||||
code that has been worked on by many people, if not carefully maintained,
|
||||
often ends up lacking conceptual integrity. For both of these reasons, any
|
||||
code that has been worked on by an unusually high number of different people
|
||||
merits careful inspection in code reviews.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
There is clearly no way to reduce the number of authors that have worked
|
||||
on a file - it is impossible to rewrite history. However, files highlighted
|
||||
by this metric should be given special attention in a code review, and may
|
||||
ultimately be good candidates for refactoring/rewriting by an individual,
|
||||
experienced developer.
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
</recommendation>
|
||||
<references>
|
||||
|
||||
|
||||
<li>
|
||||
F. P. Brooks Jr. <em>The Mythical Man-Month</em>, Chapter 4. Addison-Wesley, 1974.
|
||||
</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,30 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
This metric measures the total number of file-level changes made to files
|
||||
below this location in the tree. For an individual file, it measures the
|
||||
number of commits that have affected that file. For a directory of files, it
|
||||
measures the sum of the file-level changes for each of the files in the
|
||||
directory.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
For example, suppose we have a directory containing two files, A and B. If the
|
||||
number of file-level changes to A is <code>100</code>, and the number of
|
||||
file-level changes to B is <code>80</code>, then the total number of
|
||||
file-level changes to the directory is <code>180</code>. Note that this is
|
||||
likely to be different (in some cases very different) from the number of
|
||||
commits that affected any file in the directory, since more than one file can
|
||||
be changed by a single commit. (Note what would happen if we performed
|
||||
<code>80</code> commits on A and B, followed by another <code>20</code>
|
||||
commits on A alone - the total number of file-level changes would be
|
||||
<code>180</code>, but the number of commits involved would be
|
||||
<code>100</code>.)
|
||||
</p>
|
||||
|
||||
|
||||
</overview>
|
||||
</qhelp>
|
||||
@@ -1,51 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
This metric measures the average number of co-committed files for the files
|
||||
below this location in the tree.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
A co-committed file is one that is committed at the same time as a given file.
|
||||
For instance, if you commit files A, B and C together, then B and C would be
|
||||
the co-committed files of A for that commit. The value of the metric for an
|
||||
individual file is the average number of such co-committed files over all
|
||||
commits. The value of the metric for a directory is the aggregation of these
|
||||
averages - for instance, if we are using <code>max</code> as our aggregation
|
||||
function, the value would be the maximum of the average number of co-commits
|
||||
over all files in the directory.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
An unusually high value for this metric may indicate that the file in question
|
||||
is too tightly-coupled to other files, and it is difficult to change it in
|
||||
isolation. Alternatively, it may just be an indication that you commit lots of
|
||||
unrelated changes at the same time.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
Examine the file in question to see what the problem is.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
If the file is too tightly coupled, it will have high values for its afferent
|
||||
and/or efferent coupling metrics, and you should apply the advice given there.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
If the file is not tightly coupled, but you find that you are committing lots
|
||||
of unrelated changes at the same time, then you may want to revisit your commit
|
||||
practices.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
</recommendation>
|
||||
</qhelp>
|
||||
@@ -1,53 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
This metric measures the number of file re-commits that have occurred below
|
||||
this location in the tree. A re-commit is taken to mean a commit to a file
|
||||
that was touched less than five days ago.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In a system that is being developed using a controlled change process (where
|
||||
changes are not committed until they are in some sense 'complete'), re-commits
|
||||
can be (but are not always) an indication that an initial change was not
|
||||
successful and had to be revisited within a short time period. The intuition
|
||||
is that the original change may have been difficult to get right, and hence
|
||||
the code in the file may be more than usually defect-prone. The concept is
|
||||
somewhat similar to that of 'change bursts', as described in [Nagappan].
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
High numbers of re-commits can be addressed on two levels: preventative and
|
||||
corrective.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
On the preventative side, a high number of re-commits may be an indication
|
||||
that your code review process needs an overhaul.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
On the corrective side, code that has experienced a high number of re-commits
|
||||
should be vigorously code reviewed and tested.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
</recommendation>
|
||||
<references>
|
||||
|
||||
|
||||
<li>
|
||||
N. Nagappan et al. <em>Change Bursts as Defect Predictors</em>. In Proceedings of the 21st IEEE International Symposium on Software Reliability Engineering, 2010.
|
||||
</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,63 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>
|
||||
This metric measures the number of recent changes to files that have occurred
|
||||
below this location in the tree. A recent change is taken to mean a change
|
||||
that has occurred in the last <code>180</code> days.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
All code that has changed a great deal may be more than usually prone to
|
||||
defects, but this is particularly true of code that has been changing
|
||||
dramatically in the recent past, because it has not yet had a chance to be
|
||||
properly field-tested in order to iron out the bugs.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
There is more than one reason why a file may have been changing a lot
|
||||
recently:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
The file may be part of a new subsystem that is being written. New code is
|
||||
always going to change a lot in a short period of time, but it is important
|
||||
to ensure that it is properly code reviewed and unit tested before integrating
|
||||
it into a working product.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
The file may be being heavily refactored. Large refactorings are sometimes
|
||||
essential, but they are also quite risky. You should write proper regression
|
||||
tests before starting on a major refactoring, and check that they still pass
|
||||
once you're done.
|
||||
</li>
|
||||
|
||||
<li>
|
||||
The same bit of code may be being changed repeatedly because it is difficult
|
||||
to get right. Aside from vigorous code reviewing and testing, it may be a good
|
||||
idea to rethink the system design - if something is that hard
|
||||
to get right (and it's not an inherently difficult concept), you might be making life unnecessarily hard for yourself and
|
||||
risking introducing insidious defects.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
</recommendation>
|
||||
<references>
|
||||
|
||||
|
||||
<li>
|
||||
N. Nagappan et al. <em>Change Bursts as Defect Predictors</em>. In Proceedings of the 21st IEEE International Symposium on Software Reliability Engineering, 2010.
|
||||
</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -29,7 +29,7 @@ class SqliteFunctionCall extends FunctionCall {
|
||||
}
|
||||
|
||||
predicate sqlite_encryption_used() {
|
||||
any(StringLiteral l).getValue().toLowerCase().regexpMatch("pragma key.*") or
|
||||
any(StringLiteral l).getValue().toLowerCase().matches("pragma key%") or
|
||||
any(StringLiteral l).getValue().toLowerCase().matches("%attach%database%key%") or
|
||||
any(FunctionCall fc).getTarget().getName().matches("sqlite%\\_key\\_%")
|
||||
}
|
||||
|
||||
9
cpp/ql/src/Security/CWE/CWE-319/UseOfHttp.cpp
Normal file
9
cpp/ql/src/Security/CWE/CWE-319/UseOfHttp.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
void openUrl(char *url)
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
openUrl("http://example.com"); // BAD
|
||||
|
||||
openUrl("https://example.com"); // GOOD: Opening a connection to a URL using HTTPS enforces SSL.
|
||||
35
cpp/ql/src/Security/CWE/CWE-319/UseOfHttp.qhelp
Normal file
35
cpp/ql/src/Security/CWE/CWE-319/UseOfHttp.qhelp
Normal file
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
|
||||
<p>Constructing URLs with the HTTP protocol can lead to unsecured connections.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>When you construct a URL, ensure that you use an HTTPS URL rather than an HTTP URL. Then, any connections that are made using that URL are secure SSL connections.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>The following example shows two ways of opening a connection using a URL. When the connection is
|
||||
opened using an HTTP URL rather than an HTTPS URL, the connection is unsecured. When the connection is opened using an HTTPS URL, the connection is a secure SSL connection.</p>
|
||||
|
||||
<sample src="UseOfHttp.cpp" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html">Transport Layer Protection Cheat Sheet</a>.
|
||||
</li>
|
||||
<li>
|
||||
OWASP Top 10:
|
||||
<a href="https://owasp.org/Top10/A08_2021-Software_and_Data_Integrity_Failures/">A08:2021 - Software and Data Integrity Failures</a>.
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
90
cpp/ql/src/Security/CWE/CWE-319/UseOfHttp.ql
Normal file
90
cpp/ql/src/Security/CWE/CWE-319/UseOfHttp.ql
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* @name Failure to use HTTPS URLs
|
||||
* @description Non-HTTPS connections can be intercepted by third parties.
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
* @id cpp/non-https-url
|
||||
* @tags security
|
||||
* external/cwe/cwe-319
|
||||
* external/cwe/cwe-345
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.dataflow.TaintTracking
|
||||
import DataFlow::PathGraph
|
||||
|
||||
/**
|
||||
* A string matching private host names of IPv4 and IPv6, which only matches
|
||||
* the host portion therefore checking for port is not necessary.
|
||||
* Several examples are localhost, reserved IPv4 IP addresses including
|
||||
* 127.0.0.1, 10.x.x.x, 172.16.x,x, 192.168.x,x, and reserved IPv6 addresses
|
||||
* including [0:0:0:0:0:0:0:1] and [::1]
|
||||
*/
|
||||
class PrivateHostName extends string {
|
||||
bindingset[this]
|
||||
PrivateHostName() {
|
||||
this.regexpMatch("(?i)localhost(?:[:/?#].*)?|127\\.0\\.0\\.1(?:[:/?#].*)?|10(?:\\.[0-9]+){3}(?:[:/?#].*)?|172\\.16(?:\\.[0-9]+){2}(?:[:/?#].*)?|192.168(?:\\.[0-9]+){2}(?:[:/?#].*)?|\\[?0:0:0:0:0:0:0:1\\]?(?:[:/?#].*)?|\\[?::1\\]?(?:[:/?#].*)?")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A string containing an HTTP URL not in a private domain.
|
||||
*/
|
||||
class HttpStringLiteral extends StringLiteral {
|
||||
HttpStringLiteral() {
|
||||
exists(string s | this.getValue() = s |
|
||||
s = "http"
|
||||
or
|
||||
exists(string tail |
|
||||
tail = s.regexpCapture("http://(.*)", 1) and not tail instanceof PrivateHostName
|
||||
) and
|
||||
not TaintTracking::localExprTaint(any(StringLiteral p |
|
||||
p.getValue() instanceof PrivateHostName
|
||||
), this.getParent*())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint tracking configuration for HTTP connections.
|
||||
*/
|
||||
class HttpStringToUrlOpenConfig extends TaintTracking::Configuration {
|
||||
HttpStringToUrlOpenConfig() { this = "HttpStringToUrlOpenConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node src) {
|
||||
// Sources are strings containing an HTTP URL not in a private domain.
|
||||
src.asExpr() instanceof HttpStringLiteral
|
||||
}
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
// Sinks can be anything that demonstrates the string is likely to be
|
||||
// accessed as a URL, for example using it in a network access. Some
|
||||
// URLs are only ever displayed or used for data processing.
|
||||
exists(FunctionCall fc |
|
||||
fc.getTarget()
|
||||
.hasGlobalOrStdName([
|
||||
"system", "gethostbyname", "gethostbyname2", "gethostbyname_r", "getaddrinfo",
|
||||
"X509_load_http", "X509_CRL_load_http"
|
||||
]) and
|
||||
sink.asExpr() = fc.getArgument(0)
|
||||
or
|
||||
fc.getTarget().hasGlobalOrStdName(["send", "URLDownloadToFile", "URLDownloadToCacheFile"]) and
|
||||
sink.asExpr() = fc.getArgument(1)
|
||||
or
|
||||
fc.getTarget().hasGlobalOrStdName(["curl_easy_setopt", "getnameinfo"]) and
|
||||
sink.asExpr() = fc.getArgument(2)
|
||||
or
|
||||
fc.getTarget().hasGlobalOrStdName(["ShellExecute", "ShellExecuteA", "ShellExecuteW"]) and
|
||||
sink.asExpr() = fc.getArgument(3)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
from
|
||||
HttpStringToUrlOpenConfig config, DataFlow::PathNode source, DataFlow::PathNode sink,
|
||||
HttpStringLiteral str
|
||||
where
|
||||
config.hasFlowPath(source, sink) and
|
||||
str = source.getNode().asExpr()
|
||||
select str, source, sink, "A URL may be constructed with the HTTP protocol."
|
||||
@@ -324,10 +324,8 @@ abstract class DataOutput extends Element {
|
||||
/**
|
||||
* Data that is output via standard output or standard error.
|
||||
*/
|
||||
class StandardOutput extends DataOutput {
|
||||
StandardOutput() { this instanceof OutputWrite }
|
||||
|
||||
override Expr getASource() { result = this.(OutputWrite).getASource() }
|
||||
class StandardOutput extends DataOutput instanceof OutputWrite {
|
||||
override Expr getASource() { result = OutputWrite.super.getASource() }
|
||||
}
|
||||
|
||||
private predicate socketCallOrIndirect(FunctionCall call) {
|
||||
@@ -378,5 +376,5 @@ class SocketOutput extends DataOutput {
|
||||
from SystemData sd, DataOutput ow
|
||||
where
|
||||
sd.getAnExprIndirect() = ow.getASource() or
|
||||
sd.getAnExprIndirect() = ow.getASource().(Expr).getAChild*()
|
||||
sd.getAnExprIndirect() = ow.getASource().getAChild*()
|
||||
select ow, "This operation exposes system data from $@.", sd, sd.toString()
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
...
|
||||
chroot("/myFold/myTmp"); // BAD
|
||||
...
|
||||
chdir("/myFold/myTmp"); // BAD
|
||||
...
|
||||
int fd = open("/myFold/myTmp", O_RDONLY | O_DIRECTORY);
|
||||
fchdir(fd); // BAD
|
||||
...
|
||||
if (chdir("/myFold/myTmp") == -1) {
|
||||
exit(-1);
|
||||
}
|
||||
if (chroot("/myFold/myTmp") == -1) { // GOOD
|
||||
exit(-1);
|
||||
}
|
||||
...
|
||||
if (chdir("/myFold/myTmp") == -1) { // GOOD
|
||||
exit(-1);
|
||||
}
|
||||
...
|
||||
int fd = open("/myFold/myTmp", O_RDONLY | O_DIRECTORY);
|
||||
if(fchdir(fd) == -1) { // GOOD
|
||||
exit(-1);
|
||||
}
|
||||
...
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>Working with changing directories, without checking the return value or pinning the directory, may not be safe. Requires the attention of developers.</p>
|
||||
|
||||
</overview>
|
||||
|
||||
<example>
|
||||
<p>The following example demonstrates erroneous and corrected work with changing working directories.</p>
|
||||
<sample src="IncorrectChangingWorkingDirectory.cpp" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>
|
||||
CERT C Coding Standard:
|
||||
<a href="https://wiki.sei.cmu.edu/confluence/display/c/POS05-C.+Limit+access+to+files+by+creating+a+jail">POS05-C. Limit access to files by creating a jail.</a>
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* @name Find work with changing working directories, with security errors.
|
||||
* @description Not validating the return value or pinning the directory can be unsafe.
|
||||
* @kind problem
|
||||
* @id cpp/work-with-changing-working-directories
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
* @tags correctness
|
||||
* security
|
||||
* external/cwe/cwe-243
|
||||
* external/cwe/cwe-252
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.commons.Exclusions
|
||||
|
||||
/** Holds if a `fc` function call is available before or after a `chdir` function call. */
|
||||
predicate inExistsChdir(FunctionCall fcp) {
|
||||
exists(FunctionCall fctmp |
|
||||
(
|
||||
fctmp.getTarget().hasGlobalOrStdName("chdir") or
|
||||
fctmp.getTarget().hasGlobalOrStdName("fchdir")
|
||||
) and
|
||||
(
|
||||
fcp.getBasicBlock().getASuccessor*() = fctmp.getBasicBlock() or
|
||||
fctmp.getBasicBlock().getASuccessor*() = fcp.getBasicBlock()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if a `fc` function call is available before or after a function call containing a `chdir` call. */
|
||||
predicate outExistsChdir(FunctionCall fcp) {
|
||||
exists(FunctionCall fctmp |
|
||||
exists(FunctionCall fctmp2 |
|
||||
(
|
||||
fctmp2.getTarget().hasGlobalOrStdName("chdir") or
|
||||
fctmp2.getTarget().hasGlobalOrStdName("fchdir")
|
||||
) and
|
||||
// we are looking for a call containing calls chdir and fchdir
|
||||
fctmp2.getEnclosingStmt().getParentStmt*() = fctmp.getTarget().getEntryPoint().getChildStmt*()
|
||||
) and
|
||||
(
|
||||
fcp.getBasicBlock().getASuccessor*() = fctmp.getBasicBlock() or
|
||||
fctmp.getBasicBlock().getASuccessor*() = fcp.getBasicBlock()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
from FunctionCall fc, string msg
|
||||
where
|
||||
fc.getTarget().hasGlobalOrStdName("chroot") and
|
||||
not inExistsChdir(fc) and
|
||||
not outExistsChdir(fc) and
|
||||
// in this section I want to exclude calls to functions containing chroot that have a direct path to chdir, or to a function containing chdir
|
||||
exists(FunctionCall fctmp |
|
||||
fc.getEnclosingStmt().getParentStmt*() = fctmp.getTarget().getEntryPoint().getChildStmt*() and
|
||||
not inExistsChdir(fctmp) and
|
||||
not outExistsChdir(fctmp)
|
||||
) and
|
||||
msg = "Creation of 'chroot' jail without changing the working directory"
|
||||
or
|
||||
(
|
||||
fc.getTarget().hasGlobalOrStdName("chdir") or
|
||||
fc.getTarget().hasGlobalOrStdName("fchdir")
|
||||
) and
|
||||
fc instanceof ExprInVoidContext and
|
||||
not isFromMacroDefinition(fc) and
|
||||
msg = "Unchecked return value for call to '" + fc.getTarget().getName() + "'."
|
||||
select fc, msg
|
||||
@@ -0,0 +1,14 @@
|
||||
...
|
||||
fp = fopen("/tmp/name.tmp","w"); // BAD
|
||||
...
|
||||
char filename = tmpnam(NULL);
|
||||
fp = fopen(filename,"w"); // BAD
|
||||
...
|
||||
|
||||
strcat (filename, "/tmp/name.XXXXXX");
|
||||
fd = mkstemp(filename);
|
||||
if ( fd < 0 ) {
|
||||
return error;
|
||||
}
|
||||
fp = fdopen(fd,"w") // GOOD
|
||||
...
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>Working with a file, without checking its existence and its rights, as well as working with names that can be predicted, may not be safe. Requires the attention of developers.</p>
|
||||
|
||||
</overview>
|
||||
|
||||
<example>
|
||||
<p>The following example demonstrates erroneous and corrected work with file.</p>
|
||||
<sample src="InsecureTemporaryFile.cpp" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>
|
||||
CERT C Coding Standard:
|
||||
<a href="https://wiki.sei.cmu.edu/confluence/display/c/CON33-C.+Avoid+race+conditions+when+using+library+functions">CON33-C. Avoid race conditions when using library functions</a>.
|
||||
</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* @name Insecure generation of filenames.
|
||||
* @description Using a predictable filename when creating a temporary file can lead to an attacker-controlled input.
|
||||
* @kind problem
|
||||
* @id cpp/insecure-generation-of-filename
|
||||
* @problem.severity warning
|
||||
* @precision medium
|
||||
* @tags correctness
|
||||
* security
|
||||
* external/cwe/cwe-377
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
|
||||
|
||||
/** Holds for a function `f` that has an argument at index `apos` used to read the file. */
|
||||
predicate numberArgumentRead(Function f, int apos) {
|
||||
f.hasGlobalOrStdName("fgets") and apos = 2
|
||||
or
|
||||
f.hasGlobalOrStdName("fread") and apos = 3
|
||||
or
|
||||
f.hasGlobalOrStdName("read") and apos = 0
|
||||
or
|
||||
f.hasGlobalOrStdName("fscanf") and apos = 0
|
||||
}
|
||||
|
||||
/** Holds for a function `f` that has an argument at index `apos` used to write to file */
|
||||
predicate numberArgumentWrite(Function f, int apos) {
|
||||
f.hasGlobalOrStdName("fprintf") and apos = 0
|
||||
or
|
||||
f.hasGlobalOrStdName("fputs") and apos = 1
|
||||
or
|
||||
f.hasGlobalOrStdName("write") and apos = 0
|
||||
or
|
||||
f.hasGlobalOrStdName("fwrite") and apos = 3
|
||||
or
|
||||
f.hasGlobalOrStdName("fflush") and apos = 0
|
||||
}
|
||||
|
||||
from FunctionCall fc, string msg
|
||||
where
|
||||
// search for functions for generating a name, without a guarantee of the absence of a file during the period of work with it.
|
||||
(
|
||||
fc.getTarget().hasGlobalOrStdName("tmpnam") or
|
||||
fc.getTarget().hasGlobalOrStdName("tmpnam_s") or
|
||||
fc.getTarget().hasGlobalOrStdName("tmpnam_r")
|
||||
) and
|
||||
not exists(FunctionCall fctmp |
|
||||
(
|
||||
fctmp.getTarget().hasGlobalOrStdName("mktemp") or
|
||||
fctmp.getTarget().hasGlobalOrStdName("mkstemp") or
|
||||
fctmp.getTarget().hasGlobalOrStdName("mkstemps") or
|
||||
fctmp.getTarget().hasGlobalOrStdName("mkdtemp")
|
||||
) and
|
||||
(
|
||||
fc.getBasicBlock().getASuccessor*() = fctmp.getBasicBlock() or
|
||||
fctmp.getBasicBlock().getASuccessor*() = fc.getBasicBlock()
|
||||
)
|
||||
) and
|
||||
msg =
|
||||
"Finding the name of a file that does not exist does not mean that it will not be exist at the next operation."
|
||||
or
|
||||
// finding places to work with a file without setting permissions, but with predictable names.
|
||||
(
|
||||
fc.getTarget().hasGlobalOrStdName("fopen") or
|
||||
fc.getTarget().hasGlobalOrStdName("open")
|
||||
) and
|
||||
fc.getNumberOfArguments() = 2 and
|
||||
exists(FunctionCall fctmp, int i |
|
||||
numberArgumentWrite(fctmp.getTarget(), i) and
|
||||
globalValueNumber(fc) = globalValueNumber(fctmp.getArgument(i))
|
||||
) and
|
||||
not exists(FunctionCall fctmp, int i |
|
||||
numberArgumentRead(fctmp.getTarget(), i) and
|
||||
globalValueNumber(fc) = globalValueNumber(fctmp.getArgument(i))
|
||||
) and
|
||||
exists(FunctionCall fctmp |
|
||||
(
|
||||
fctmp.getTarget().hasGlobalOrStdName("strcat") or
|
||||
fctmp.getTarget().hasGlobalOrStdName("strcpy")
|
||||
) and
|
||||
globalValueNumber(fc.getArgument(0)) = globalValueNumber(fctmp.getAnArgument())
|
||||
or
|
||||
fctmp.getTarget().hasGlobalOrStdName("getenv") and
|
||||
globalValueNumber(fc.getArgument(0)) = globalValueNumber(fctmp)
|
||||
or
|
||||
(
|
||||
fctmp.getTarget().hasGlobalOrStdName("asprintf") or
|
||||
fctmp.getTarget().hasGlobalOrStdName("vasprintf") or
|
||||
fctmp.getTarget().hasGlobalOrStdName("xasprintf") or
|
||||
fctmp.getTarget().hasGlobalOrStdName("xvasprintf ")
|
||||
) and
|
||||
exists(Variable vrtmp |
|
||||
vrtmp = fc.getArgument(0).(VariableAccess).getTarget() and
|
||||
vrtmp = fctmp.getArgument(0).(AddressOfExpr).getAddressable().(Variable) and
|
||||
not vrtmp instanceof Field
|
||||
)
|
||||
) and
|
||||
not exists(FunctionCall fctmp |
|
||||
(
|
||||
fctmp.getTarget().hasGlobalOrStdName("umask") or
|
||||
fctmp.getTarget().hasGlobalOrStdName("fchmod") or
|
||||
fctmp.getTarget().hasGlobalOrStdName("chmod")
|
||||
) and
|
||||
(
|
||||
fc.getBasicBlock().getASuccessor*() = fctmp.getBasicBlock() or
|
||||
fctmp.getBasicBlock().getASuccessor*() = fc.getBasicBlock()
|
||||
)
|
||||
) and
|
||||
msg =
|
||||
"Creating a file for writing without evaluating its existence and setting permissions can be unsafe."
|
||||
select fc, msg
|
||||
@@ -188,8 +188,7 @@ where
|
||||
isBitwiseandBitwise(exp) and
|
||||
isDifferentResults(exp.(BinaryBitwiseOperation).getLeftOperand(),
|
||||
exp.(BinaryBitwiseOperation).getRightOperand().(BinaryBitwiseOperation).getLeftOperand(),
|
||||
exp.(BinaryBitwiseOperation).getRightOperand().(BinaryBitwiseOperation).getRightOperand(),
|
||||
exp.(BinaryBitwiseOperation),
|
||||
exp.(BinaryBitwiseOperation).getRightOperand().(BinaryBitwiseOperation)) and
|
||||
exp.(BinaryBitwiseOperation).getRightOperand().(BinaryBitwiseOperation).getRightOperand(), exp,
|
||||
exp.(BinaryBitwiseOperation).getRightOperand()) and
|
||||
msg = "specify the priority with parentheses."
|
||||
select exp, msg
|
||||
|
||||
@@ -142,7 +142,7 @@ class Resource extends MemberVariable {
|
||||
|
||||
predicate acquisitionWithRequiredKind(Assignment acquireAssign, string kind) {
|
||||
// acquireAssign is an assignment to this resource
|
||||
acquireAssign.(Assignment).getLValue() = this.getAnAccess() and
|
||||
acquireAssign.getLValue() = this.getAnAccess() and
|
||||
// Should be in this class, but *any* member method will do
|
||||
this.inSameClass(acquireAssign) and
|
||||
// Check that it is an acquisition function and return the corresponding kind
|
||||
|
||||
@@ -31,7 +31,7 @@ from Variable v, Variable shadowed
|
||||
where
|
||||
not v.getParentScope().(BlockStmt).isInMacroExpansion() and
|
||||
(
|
||||
v.(LocalVariableOrParameter).shadowsGlobal(shadowed.(GlobalVariable)) or
|
||||
v.(LocalVariableOrParameter).shadowsGlobal(shadowed) or
|
||||
localShadowsParameter(v, shadowed) or
|
||||
shadowing(v, shadowed)
|
||||
)
|
||||
|
||||
@@ -26,7 +26,7 @@ import cpp
|
||||
from Assignment a, Variable global, Variable local
|
||||
where
|
||||
a.fromSource() and
|
||||
global.getAnAccess() = a.getLValue().(VariableAccess) and
|
||||
global.getAnAccess() = a.getLValue() and
|
||||
local.getAnAccess() = a.getRValue().(AddressOfExpr).getOperand() and
|
||||
local.hasSpecifier("auto") and
|
||||
(
|
||||
|
||||
@@ -49,11 +49,11 @@ class ExposingIntegralUnion extends Union {
|
||||
exists(MemberVariable mv1, MemberVariable mv2, IntegralType mv1tp, IntegralType mv2tp |
|
||||
mv1 = this.getAMemberVariable() and
|
||||
mv2 = this.getAMemberVariable() and
|
||||
mv1tp = mv1.getUnderlyingType().(IntegralType) and
|
||||
mv1tp = mv1.getUnderlyingType() and
|
||||
(
|
||||
mv2tp = mv2.getUnderlyingType().(IntegralType)
|
||||
mv2tp = mv2.getUnderlyingType()
|
||||
or
|
||||
mv2tp = mv2.getUnderlyingType().(ArrayType).getBaseType().getUnderlyingType().(IntegralType)
|
||||
mv2tp = mv2.getUnderlyingType().(ArrayType).getBaseType().getUnderlyingType()
|
||||
) and
|
||||
mv1tp.getSize() > mv2tp.getSize()
|
||||
)
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
| test.cpp:12:7:12:12 | call to chroot | Creation of 'chroot' jail without changing the working directory |
|
||||
| test.cpp:29:3:29:7 | call to chdir | Unchecked return value for call to 'chdir'. |
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE/CWE-243/IncorrectChangingWorkingDirectory.ql
|
||||
@@ -0,0 +1,46 @@
|
||||
typedef int FILE;
|
||||
#define size_t int
|
||||
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
|
||||
FILE *fopen(const char *filename, const char *mode);
|
||||
int fread(char *buf, int size, int count, FILE *fp);
|
||||
int fclose(FILE *fp);
|
||||
int chroot(char *path);
|
||||
int chdir(char *path);
|
||||
void exit(int status);
|
||||
|
||||
int funTest1(){
|
||||
if (chroot("/myFold/myTmp") == -1) { // BAD
|
||||
exit(-1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int funTest2(){
|
||||
if (chdir("/myFold/myTmp") == -1) { // GOOD
|
||||
exit(-1);
|
||||
}
|
||||
if (chroot("/myFold/myTmp") == -1) { // GOOD
|
||||
exit(-1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int funTest3(){
|
||||
chdir("/myFold/myTmp"); // BAD
|
||||
return 0;
|
||||
}
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if(argc = 0) {
|
||||
funTest3();
|
||||
return 2;
|
||||
}
|
||||
if(argc = 1)
|
||||
funTest1();
|
||||
else
|
||||
funTest2();
|
||||
FILE *fp = fopen(argv[1], "w");
|
||||
fwrite("12345", 5, 1, fp);
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
| test.cpp:16:20:16:25 | call to tmpnam | Finding the name of a file that does not exist does not mean that it will not be exist at the next operation. |
|
||||
| test.cpp:42:8:42:12 | call to fopen | Creating a file for writing without evaluating its existence and setting permissions can be unsafe. |
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE/CWE-377/InsecureTemporaryFile.ql
|
||||
@@ -0,0 +1,68 @@
|
||||
typedef int FILE;
|
||||
#define NULL (0)
|
||||
FILE *fopen(char *filename, const char *mode);
|
||||
FILE *fdopen(int handle, char *mode);
|
||||
char * tmpnam(char * name);
|
||||
int mkstemp(char * name);
|
||||
char * strcat(char *str1, const char *str2);
|
||||
int umask(int pmode);
|
||||
int chmod(char * filename,int pmode);
|
||||
int fprintf(FILE *fp,const char *fmt, ...);
|
||||
int fclose(FILE *stream);
|
||||
|
||||
int funcTest1()
|
||||
{
|
||||
FILE *fp;
|
||||
char *filename = tmpnam(NULL); // BAD
|
||||
fp = fopen(filename,"w");
|
||||
fprintf(fp,"%s\n","data to file");
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int funcTest2()
|
||||
{
|
||||
FILE *fp;
|
||||
int fd;
|
||||
char filename[80];
|
||||
strcat (filename, "/tmp/name.XXXXXX");
|
||||
fd = mkstemp(filename);
|
||||
if ( fd < 0 ) {
|
||||
return 1;
|
||||
}
|
||||
fp = fdopen(fd,"w"); // GOOD
|
||||
return 0;
|
||||
}
|
||||
|
||||
int funcTest3()
|
||||
{
|
||||
FILE *fp;
|
||||
char filename[80];
|
||||
strcat(filename, "/tmp/tmp.name");
|
||||
fp = fopen(filename,"w"); // BAD
|
||||
fprintf(fp,"%s\n","data to file");
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int funcTest4()
|
||||
{
|
||||
FILE *fp;
|
||||
char filename[80];
|
||||
umask(0022);
|
||||
strcat(filename, "/tmp/tmp.name");
|
||||
fp = fopen(filename,"w"); // GOOD
|
||||
chmod(filename,0666);
|
||||
fprintf(fp,"%s\n","data to file");
|
||||
fclose(fp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
funcTest1();
|
||||
funcTest2();
|
||||
funcTest3();
|
||||
funcTest4();
|
||||
return 0;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user