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