Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
77.92% covered (warning)
77.92%
60 / 77
64.00% covered (warning)
64.00%
16 / 25
CRAP
0.00% covered (danger)
0.00%
0 / 1
TaintednessBackpropVisitor
77.92% covered (warning)
77.92%
60 / 77
64.00% covered (warning)
64.00%
16 / 25
72.79
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 visitProp
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visitNullsafeProp
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 visitStaticProp
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 visitVar
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 visitEncapsList
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 visitArray
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 visitArrayElem
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 visitCast
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 visitDim
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 visitUnaryOp
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 visitBinaryOp
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 visitConditional
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 visitCall
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visitMethodCall
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visitStaticCall
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visitNullsafeMethodCall
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 handleCall
55.56% covered (warning)
55.56%
10 / 18
0.00% covered (danger)
0.00%
0 / 1
9.16
 visitPreDec
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 visitPreInc
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 visitPostDec
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 visitPostInc
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 handleIncOrDec
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 recurse
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 doBackpropElements
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace SecurityCheckPlugin;
4
5use ast\Node;
6use Exception;
7use Phan\CodeBase;
8use Phan\Exception\CodeBaseException;
9use Phan\Exception\FQSENException;
10use Phan\Exception\IssueException;
11use Phan\Exception\NodeException;
12use Phan\Language\Context;
13use Phan\Language\Element\TypedElementInterface;
14use Phan\Language\Element\Variable;
15use Phan\PluginV3\PluginAwareBaseAnalysisVisitor;
16
17class 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}