Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
77.92% |
60 / 77 |
|
64.00% |
16 / 25 |
CRAP | |
0.00% |
0 / 1 |
TaintednessBackpropVisitor | |
77.92% |
60 / 77 |
|
64.00% |
16 / 25 |
72.79 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
visitProp | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
visitNullsafeProp | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
visitStaticProp | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
visitVar | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
visitEncapsList | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
visitArray | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
visitArrayElem | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
visitCast | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
visitDim | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
visitUnaryOp | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
visitBinaryOp | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
visitConditional | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
visitCall | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
visitMethodCall | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
visitStaticCall | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
visitNullsafeMethodCall | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
handleCall | |
55.56% |
10 / 18 |
|
0.00% |
0 / 1 |
9.16 | |||
visitPreDec | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
visitPreInc | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
visitPostDec | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
visitPostInc | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
handleIncOrDec | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
recurse | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
doBackpropElements | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace SecurityCheckPlugin; |
4 | |
5 | use ast\Node; |
6 | use Exception; |
7 | use Phan\CodeBase; |
8 | use Phan\Exception\CodeBaseException; |
9 | use Phan\Exception\FQSENException; |
10 | use Phan\Exception\IssueException; |
11 | use Phan\Exception\NodeException; |
12 | use Phan\Language\Context; |
13 | use Phan\Language\Element\TypedElementInterface; |
14 | use Phan\Language\Element\Variable; |
15 | use Phan\PluginV3\PluginAwareBaseAnalysisVisitor; |
16 | |
17 | class TaintednessBackpropVisitor extends PluginAwareBaseAnalysisVisitor { |
18 | use TaintednessBaseVisitor; |
19 | |
20 | /** @var Taintedness */ |
21 | private $taintedness; |
22 | |
23 | /** @var CausedByLines|null */ |
24 | private $additionalError; |
25 | |
26 | /** |
27 | * @inheritDoc |
28 | * @param Taintedness $taintedness |
29 | * @param CausedByLines|null $additionalError |
30 | */ |
31 | public function __construct( |
32 | CodeBase $code_base, |
33 | Context $context, |
34 | Taintedness $taintedness, |
35 | CausedByLines $additionalError = null |
36 | ) { |
37 | parent::__construct( $code_base, $context ); |
38 | $this->taintedness = $taintedness; |
39 | $this->additionalError = $additionalError; |
40 | } |
41 | |
42 | /** |
43 | * @inheritDoc |
44 | */ |
45 | public function visitProp( Node $node ): void { |
46 | $this->doBackpropElements( $this->getPropFromNode( $node ) ); |
47 | } |
48 | |
49 | /** |
50 | * @inheritDoc |
51 | */ |
52 | public function visitNullsafeProp( Node $node ): void { |
53 | $this->doBackpropElements( $this->getPropFromNode( $node ) ); |
54 | } |
55 | |
56 | /** |
57 | * @inheritDoc |
58 | */ |
59 | public function visitStaticProp( Node $node ): void { |
60 | $this->doBackpropElements( $this->getPropFromNode( $node ) ); |
61 | } |
62 | |
63 | /** |
64 | * @inheritDoc |
65 | */ |
66 | public function visitVar( Node $node ): void { |
67 | $cn = $this->getCtxN( $node ); |
68 | if ( Variable::isHardcodedGlobalVariableWithName( $cn->getVariableName() ) ) { |
69 | return; |
70 | } |
71 | try { |
72 | $this->doBackpropElements( $cn->getVariable() ); |
73 | } catch ( NodeException | IssueException $e ) { |
74 | $this->debug( __METHOD__, "variable not in scope?? " . $this->getDebugInfo( $e ) ); |
75 | } |
76 | } |
77 | |
78 | /** |
79 | * @inheritDoc |
80 | */ |
81 | public function visitEncapsList( Node $node ): void { |
82 | foreach ( $node->children as $child ) { |
83 | if ( $child instanceof Node ) { |
84 | $this->recurse( $child ); |
85 | } |
86 | } |
87 | } |
88 | |
89 | /** |
90 | * @inheritDoc |
91 | */ |
92 | public function visitArray( Node $node ): void { |
93 | foreach ( $node->children as $child ) { |
94 | if ( $child instanceof Node ) { |
95 | $this->recurse( $child ); |
96 | } |
97 | } |
98 | } |
99 | |
100 | /** |
101 | * @inheritDoc |
102 | */ |
103 | public function visitArrayElem( Node $node ): void { |
104 | $key = $node->children['key']; |
105 | if ( $key instanceof Node ) { |
106 | $this->recurse( $key, $this->taintedness->asKeyForForeach() ); |
107 | } |
108 | $value = $node->children['value']; |
109 | if ( $value instanceof Node ) { |
110 | $this->recurse( $value, $this->taintedness->getTaintednessForOffsetOrWhole( $key ) ); |
111 | } |
112 | } |
113 | |
114 | /** |
115 | * @inheritDoc |
116 | */ |
117 | public function visitCast( Node $node ): void { |
118 | // Future todo might be to ignore casts to ints, since |
119 | // such things should be safe. Unclear if that makes |
120 | // sense in all circumstances. |
121 | if ( $node->children['expr'] instanceof Node ) { |
122 | $this->recurse( $node->children['expr'] ); |
123 | } |
124 | } |
125 | |
126 | /** |
127 | * @inheritDoc |
128 | */ |
129 | public function visitDim( Node $node ): void { |
130 | if ( $node->children['expr'] instanceof Node ) { |
131 | // For now just consider the outermost array. |
132 | // FIXME. doesn't handle tainted array keys! |
133 | $offs = $node->children['dim']; |
134 | $realOffs = $offs !== null ? $this->resolveOffset( $offs ) : null; |
135 | $this->recurse( $node->children['expr'], $this->taintedness->asMaybeMovedAtOffset( $realOffs ) ); |
136 | } |
137 | } |
138 | |
139 | /** |
140 | * @inheritDoc |
141 | */ |
142 | public function visitUnaryOp( Node $node ): void { |
143 | if ( $node->children['expr'] instanceof Node ) { |
144 | $this->recurse( $node->children['expr'] ); |
145 | } |
146 | } |
147 | |
148 | /** |
149 | * @inheritDoc |
150 | */ |
151 | public function visitBinaryOp( Node $node ): void { |
152 | if ( $node->children['left'] instanceof Node ) { |
153 | $this->recurse( $node->children['left'] ); |
154 | } |
155 | if ( $node->children['right'] instanceof Node ) { |
156 | $this->recurse( $node->children['right'] ); |
157 | } |
158 | } |
159 | |
160 | /** |
161 | * @inheritDoc |
162 | */ |
163 | public function visitConditional( Node $node ): void { |
164 | if ( $node->children['true'] instanceof Node ) { |
165 | $this->recurse( $node->children['true'] ); |
166 | } |
167 | if ( $node->children['false'] instanceof Node ) { |
168 | $this->recurse( $node->children['false'] ); |
169 | } |
170 | } |
171 | |
172 | /** |
173 | * @inheritDoc |
174 | */ |
175 | public function visitCall( Node $node ): void { |
176 | $this->handleCall( $node ); |
177 | } |
178 | |
179 | /** |
180 | * @inheritDoc |
181 | */ |
182 | public function visitMethodCall( Node $node ): void { |
183 | $this->handleCall( $node ); |
184 | } |
185 | |
186 | /** |
187 | * @inheritDoc |
188 | */ |
189 | public function visitStaticCall( Node $node ): void { |
190 | $this->handleCall( $node ); |
191 | } |
192 | |
193 | /** |
194 | * @inheritDoc |
195 | */ |
196 | public function visitNullsafeMethodCall( Node $node ): void { |
197 | $this->handleCall( $node ); |
198 | } |
199 | |
200 | /** |
201 | * @param Node $node |
202 | */ |
203 | private function handleCall( Node $node ): void { |
204 | $ctxNode = $this->getCtxN( $node ); |
205 | // @todo Future todo might be to still return arguments when catching an exception. |
206 | if ( $node->kind === \ast\AST_CALL ) { |
207 | if ( $node->children['expr']->kind !== \ast\AST_NAME ) { |
208 | // TODO Handle this case! |
209 | return; |
210 | } |
211 | try { |
212 | $func = $ctxNode->getFunction( $node->children['expr']->children['name'] ); |
213 | } catch ( IssueException | FQSENException $e ) { |
214 | $this->debug( __METHOD__, "FIXME func not found: " . $this->getDebugInfo( $e ) ); |
215 | return; |
216 | } |
217 | } else { |
218 | $methodName = $node->children['method']; |
219 | try { |
220 | $func = $ctxNode->getMethod( $methodName, $node->kind === \ast\AST_STATIC_CALL, true ); |
221 | } catch ( NodeException | CodeBaseException | IssueException $e ) { |
222 | $this->debug( __METHOD__, "FIXME method not found: " . $this->getDebugInfo( $e ) ); |
223 | return; |
224 | } |
225 | } |
226 | // intentionally resetting options to [] |
227 | // here to ensure we don't recurse beyond |
228 | // a depth of 1. |
229 | try { |
230 | $retObjs = $this->getReturnObjsOfFunc( $func ); |
231 | } catch ( Exception $e ) { |
232 | $this->debug( __METHOD__, "FIXME: " . $this->getDebugInfo( $e ) ); |
233 | return; |
234 | } |
235 | $this->doBackpropElements( ...$retObjs ); |
236 | } |
237 | |
238 | /** |
239 | * @inheritDoc |
240 | */ |
241 | public function visitPreDec( Node $node ): void { |
242 | $this->handleIncOrDec( $node ); |
243 | } |
244 | |
245 | /** |
246 | * @inheritDoc |
247 | */ |
248 | public function visitPreInc( Node $node ): void { |
249 | $this->handleIncOrDec( $node ); |
250 | } |
251 | |
252 | /** |
253 | * @inheritDoc |
254 | */ |
255 | public function visitPostDec( Node $node ): void { |
256 | $this->handleIncOrDec( $node ); |
257 | } |
258 | |
259 | /** |
260 | * @inheritDoc |
261 | */ |
262 | public function visitPostInc( Node $node ): void { |
263 | $this->handleIncOrDec( $node ); |
264 | } |
265 | |
266 | /** |
267 | * @param Node $node |
268 | */ |
269 | private function handleIncOrDec( Node $node ): void { |
270 | $children = $node->children; |
271 | assert( count( $children ) === 1 ); |
272 | $this->recurse( reset( $children ) ); |
273 | } |
274 | |
275 | /** |
276 | * Wrapper for __invoke. Allows changing the taintedness before recursing, and restoring later. |
277 | * |
278 | * @param Node $node |
279 | * @param Taintedness|null $taint |
280 | */ |
281 | private function recurse( Node $node, Taintedness $taint = null ): void { |
282 | if ( !$taint ) { |
283 | $this( $node ); |
284 | return; |
285 | } |
286 | $oldTaint = $this->taintedness; |
287 | $this->taintedness = $taint; |
288 | try { |
289 | $this( $node ); |
290 | } finally { |
291 | $this->taintedness = $oldTaint; |
292 | } |
293 | } |
294 | |
295 | /** |
296 | * @param TypedElementInterface|null ...$elements |
297 | */ |
298 | private function doBackpropElements( ?TypedElementInterface ...$elements ): void { |
299 | foreach ( array_unique( array_filter( $elements ) ) as $el ) { |
300 | $this->markAllDependentMethodsExec( $el, $this->taintedness, $this->additionalError ); |
301 | } |
302 | } |
303 | } |