Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.94% covered (success)
96.94%
507 / 523
88.71% covered (warning)
88.71%
55 / 62
CRAP
0.00% covered (danger)
0.00%
0 / 1
TaintednessVisitor
96.94% covered (success)
96.94%
507 / 523
88.71% covered (warning)
88.71%
55 / 62
154
0.00% covered (danger)
0.00%
0 / 1
 analyzeNodeAndGetTaintedness
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 setCachedData
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setCurTaintUnknown
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setCurTaintSafe
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visit
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 visitClosure
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 visitFuncDecl
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 visitMethod
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 visitArrowFunc
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 analyzeFunctionLike
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
4.25
 visitClassName
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 visitThrow
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 visitUnset
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 handleUnsetDim
72.22% covered (warning)
72.22%
13 / 18
0.00% covered (danger)
0.00%
0 / 1
9.37
 visitClone
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 visitAssignOp
94.12% covered (success)
94.12%
32 / 34
0.00% covered (danger)
0.00%
0 / 1
3.00
 visitStatic
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 visitAssignRef
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visitAssign
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
2
 doVisitAssign
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
1
 visitBinaryOp
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
2
 getBinOpTaint
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 visitDim
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
3
 visitPrint
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visitExit
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visitShellExec
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
 visitIncludeOrEval
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 visitEcho
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 visitSimpleSinkAndPropagate
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
4
 visitStaticCall
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visitNew
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
1 / 1
8
 visitMethodCall
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 analyzeCallNode
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 visitNullsafeMethodCall
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visitCall
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 visitVar
91.67% covered (success)
91.67%
22 / 24
0.00% covered (danger)
0.00%
0 / 1
4.01
 getHardcodedTaintednessForVar
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
1 / 1
14
 visitGlobal
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
 visitReturn
66.67% covered (warning)
66.67%
6 / 9
0.00% covered (danger)
0.00%
0 / 1
3.33
 setFuncTaintFromReturn
97.22% covered (success)
97.22%
35 / 36
0.00% covered (danger)
0.00%
0 / 1
11
 visitArray
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
1 / 1
9
 visitClassConst
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 visitConst
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 visitStaticProp
80.00% covered (warning)
80.00%
8 / 10
0.00% covered (danger)
0.00%
0 / 1
2.03
 visitProp
100.00% covered (success)
100.00%
34 / 34
100.00% covered (success)
100.00%
1 / 1
7
 visitNullsafeProp
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visitConditional
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 visitName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visitNameList
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visitUnaryOp
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
2
 visitPostInc
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visitPreInc
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visitPostDec
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visitPreDec
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 analyzeIncOrDec
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 visitCast
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
2
 visitEncapsList
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 visitIsset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visitEmpty
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visitMagicConst
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visitInstanceOf
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 visitMatch
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
1<?php declare( strict_types=1 );
2/**
3 * Copyright (C) 2017  Brian Wolff <bawolff@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20namespace SecurityCheckPlugin;
21
22use ast\Node;
23use Phan\Analysis\BlockExitStatusChecker;
24use Phan\AST\ContextNode;
25use Phan\Debug;
26use Phan\Exception\CodeBaseException;
27use Phan\Exception\IssueException;
28use Phan\Exception\NodeException;
29use Phan\Language\Element\FunctionInterface;
30use Phan\Language\Element\GlobalVariable;
31use Phan\Language\FQSEN\FullyQualifiedClassName;
32use Phan\Language\FQSEN\FullyQualifiedFunctionName;
33use Phan\Language\Type\FunctionLikeDeclarationType;
34use Phan\PluginV3\PluginAwarePostAnalysisVisitor;
35
36/**
37 * This class visits all the nodes in the ast. It has two jobs:
38 *
39 * 1) Return the taint value of the current node we are visiting.
40 * 2) In the event of an assignment (and similar things) propagate
41 *  the taint value from the left hand side to the right hand side.
42 *
43 * For the moment, the taint values are stored in a "taintedness"
44 * property of various phan TypedElement objects. This is probably
45 * not the best solution for where to store the data, but its what
46 * this does for now.
47 *
48 * This also maintains some other properties, such as where the error
49 * originates, and dependencies in certain cases.
50 *
51 * @phan-file-suppress PhanUnusedPublicMethodParameter Many methods don't use $node
52 */
53class TaintednessVisitor extends PluginAwarePostAnalysisVisitor {
54    use TaintednessBaseVisitor;
55
56    /**
57     * Node kinds whose taintedness is not well-defined and for which we don't need a visit* method.
58     */
59    public const INAPPLICABLE_NODES_WITHOUT_VISITOR = [
60        \ast\AST_ARG_LIST => true,
61        \ast\AST_TYPE => true,
62        \ast\AST_NULLABLE_TYPE => true,
63        \ast\AST_PARAM_LIST => true,
64        // Params are handled in PreTaintednessVisitor
65        \ast\AST_PARAM => true,
66        \ast\AST_CLASS => true,
67        \ast\AST_USE_ELEM => true,
68        \ast\AST_STMT_LIST => true,
69        \ast\AST_CLASS_CONST_DECL => true,
70        \ast\AST_CLASS_CONST_GROUP => true,
71        \ast\AST_CONST_DECL => true,
72        \ast\AST_IF => true,
73        \ast\AST_IF_ELEM => true,
74        \ast\AST_PROP_DECL => true,
75        \ast\AST_CONST_ELEM => true,
76        \ast\AST_USE => true,
77        \ast\AST_USE_TRAIT => true,
78        \ast\AST_BREAK => true,
79        \ast\AST_CONTINUE => true,
80        \ast\AST_GOTO => true,
81        \ast\AST_CATCH => true,
82        \ast\AST_NAMESPACE => true,
83        \ast\AST_SWITCH => true,
84        \ast\AST_SWITCH_CASE => true,
85        \ast\AST_SWITCH_LIST => true,
86        \ast\AST_WHILE => true,
87        \ast\AST_DO_WHILE => true,
88        \ast\AST_FOR => true,
89        // Handled in TaintednessLoopVisitor
90        \ast\AST_FOREACH => true,
91        \ast\AST_EXPR_LIST => true,
92        \ast\AST_TRY => true,
93        // Array elems are handled directly in visitArray
94        \ast\AST_ARRAY_ELEM => true,
95        // Initializing the prop is done in preorder
96        \ast\AST_PROP_ELEM => true,
97        \ast\AST_PROP_GROUP => true,
98        // Variables are already handled in visitVar
99        \ast\AST_CLOSURE_VAR => true,
100        \ast\AST_CLOSURE_USES => true,
101        \ast\AST_LABEL => true,
102        \ast\AST_ATTRIBUTE => true,
103        \ast\AST_ATTRIBUTE_GROUP => true,
104        \ast\AST_ATTRIBUTE_LIST => true,
105    ];
106
107    /**
108     * Node kinds whose taintedness is not well-defined, but for which we still need a visit* method.
109     * Trying to get the taintedness of these nodes will still result in an error.
110     */
111    public const INAPPLICABLE_NODES_WITH_VISITOR = [
112        \ast\AST_GLOBAL => true,
113        \ast\AST_RETURN => true,
114        \ast\AST_STATIC => true,
115        \ast\AST_FUNC_DECL => true,
116        \ast\AST_METHOD => true,
117    ];
118
119    /**
120     * Map of node kinds whose taintedness is not well-defined, e.g. because that node
121     * cannot be used as an expression. Note that it's safe to use array plus here.
122     */
123    private const INAPPLICABLE_NODES = self::INAPPLICABLE_NODES_WITHOUT_VISITOR + self::INAPPLICABLE_NODES_WITH_VISITOR;
124
125    /** @var TaintednessWithError|null */
126    private $curTaintWithError;
127
128    /**
129     * @param Node $node
130     * @return TaintednessWithError
131     */
132    public function analyzeNodeAndGetTaintedness( Node $node ): TaintednessWithError {
133        assert(
134            !isset( self::INAPPLICABLE_NODES[$node->kind] ),
135            'Should not try to get taintedness of inapplicable nodes (got ' . Debug::nodeName( $node ) . ')'
136        );
137        $this->__invoke( $node );
138        $this->setCachedData( $node );
139        return $this->curTaintWithError;
140    }
141
142    /**
143     * Cache taintedness data in an AST node. Ideally we'd want this to happen at the end of __invoke, but phan
144     * calls visit* methods by name, so that doesn't work.
145     * Caching a node *may* improve the speed, but *will* increase the memory usage, so only do that for nodes
146     * whose taintedness:
147     *  - Is not trivial to compute, and
148     *  - Might be needed from another node (via getTaintednessNode)
149     * @param Node $node
150     */
151    private function setCachedData( Node $node ): void {
152        // @phan-suppress-next-line PhanUndeclaredProperty
153        $node->taint = $this->curTaintWithError;
154    }
155
156    /**
157     * Sets $this->curTaint to UNKNOWN. Shorthand to filter the usages of curTaint.
158     */
159    private function setCurTaintUnknown(): void {
160        $this->curTaintWithError = TaintednessWithError::unknownSingleton();
161    }
162
163    private function setCurTaintSafe(): void {
164        $this->curTaintWithError = TaintednessWithError::emptySingleton();
165    }
166
167    /**
168     * Generic visitor when we haven't defined a more specific one.
169     *
170     * @param Node $node
171     */
172    public function visit( Node $node ): void {
173        // This method will be called on all nodes for which
174        // there is no implementation of its kind visitor.
175
176        if ( isset( self::INAPPLICABLE_NODES_WITHOUT_VISITOR[$node->kind] ) ) {
177            return;
178        }
179
180        // To see what kinds of nodes are passing through here,
181        // you can run `Debug::printNode($node)`.
182        # Debug::printNode( $node );
183        $this->debug( __METHOD__, "unhandled case " . Debug::nodeName( $node ) );
184        $this->setCurTaintUnknown();
185    }
186
187    /**
188     * @param Node $node
189     */
190    public function visitClosure( Node $node ): void {
191        // We cannot use getFunctionLikeInScope for closures
192        $closureFQSEN = FullyQualifiedFunctionName::fromClosureInContext( $this->context, $node );
193
194        if ( $this->code_base->hasFunctionWithFQSEN( $closureFQSEN ) ) {
195            $func = $this->code_base->getFunctionByFQSEN( $closureFQSEN );
196            $this->analyzeFunctionLike( $func );
197        } else {
198            // @codeCoverageIgnoreStart
199            $this->debug( __METHOD__, 'closure doesn\'t exist' );
200            // @codeCoverageIgnoreEnd
201        }
202        $this->setCurTaintSafe();
203        $this->setCachedData( $node );
204    }
205
206    /**
207     * @param Node $node
208     */
209    public function visitFuncDecl( Node $node ): void {
210        $func = $this->context->getFunctionLikeInScope( $this->code_base );
211        $this->analyzeFunctionLike( $func );
212    }
213
214    /**
215     * Visit a method declaration
216     *
217     * @param Node $node
218     */
219    public function visitMethod( Node $node ): void {
220        $method = $this->context->getFunctionLikeInScope( $this->code_base );
221        $this->analyzeFunctionLike( $method );
222    }
223
224    /**
225     * @param Node $node
226     */
227    public function visitArrowFunc( Node $node ): void {
228        $this->visitClosure( $node );
229    }
230
231    /**
232     * Handles methods, functions and closures.
233     *
234     * @param FunctionInterface $func The func to analyze
235     */
236    private function analyzeFunctionLike( FunctionInterface $func ): void {
237        if ( self::getFuncTaint( $func ) === null ) {
238            // If we still have no data, presumably the function doesn't return anything, so mark as safe.