Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
96.94% |
507 / 523 |
|
88.71% |
55 / 62 |
CRAP | |
0.00% |
0 / 1 |
TaintednessVisitor | |
96.94% |
507 / 523 |
|
88.71% |
55 / 62 |
154 | |
0.00% |
0 / 1 |
analyzeNodeAndGetTaintedness | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
setCachedData | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setCurTaintUnknown | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setCurTaintSafe | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
visit | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
visitClosure | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
visitFuncDecl | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
visitMethod | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
visitArrowFunc | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
analyzeFunctionLike | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
4.25 | |||
visitClassName | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
visitThrow | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
visitUnset | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
handleUnsetDim | |
72.22% |
13 / 18 |
|
0.00% |
0 / 1 |
9.37 | |||
visitClone | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
visitAssignOp | |
94.12% |
32 / 34 |
|
0.00% |
0 / 1 |
3.00 | |||
visitStatic | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
visitAssignRef | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
visitAssign | |
100.00% |
21 / 21 |
|
100.00% |
1 / 1 |
2 | |||
doVisitAssign | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
1 | |||
visitBinaryOp | |
100.00% |
21 / 21 |
|
100.00% |
1 / 1 |
2 | |||
getBinOpTaint | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
visitDim | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
3 | |||
visitPrint | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
visitExit | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
visitShellExec | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
1 | |||
visitIncludeOrEval | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
visitEcho | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
visitSimpleSinkAndPropagate | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
4 | |||
visitStaticCall | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
visitNew | |
100.00% |
40 / 40 |
|
100.00% |
1 / 1 |
8 | |||
visitMethodCall | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
2 | |||
analyzeCallNode | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
4 | |||
visitNullsafeMethodCall | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
visitCall | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
visitVar | |
91.67% |
22 / 24 |
|
0.00% |
0 / 1 |
4.01 | |||
getHardcodedTaintednessForVar | |
100.00% |
36 / 36 |
|
100.00% |
1 / 1 |
14 | |||
visitGlobal | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
6 | |||
visitReturn | |
66.67% |
6 / 9 |
|
0.00% |
0 / 1 |
3.33 | |||
setFuncTaintFromReturn | |
97.22% |
35 / 36 |
|
0.00% |
0 / 1 |
11 | |||
visitArray | |
100.00% |
33 / 33 |
|
100.00% |
1 / 1 |
9 | |||
visitClassConst | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
visitConst | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
visitStaticProp | |
80.00% |
8 / 10 |
|
0.00% |
0 / 1 |
2.03 | |||
visitProp | |
100.00% |
34 / 34 |
|
100.00% |
1 / 1 |
7 | |||
visitNullsafeProp | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
visitConditional | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
visitName | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
visitNameList | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
visitUnaryOp | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
visitPostInc | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
visitPreInc | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
visitPostDec | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
visitPreDec | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
analyzeIncOrDec | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
visitCast | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
2 | |||
visitEncapsList | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
visitIsset | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
visitEmpty | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
visitMagicConst | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
visitInstanceOf | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
visitMatch | |
100.00% |
6 / 6 |
|
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 | |
20 | namespace SecurityCheckPlugin; |
21 | |
22 | use ast\Node; |
23 | use Phan\Analysis\BlockExitStatusChecker; |
24 | use Phan\AST\ContextNode; |
25 | use Phan\Debug; |
26 | use Phan\Exception\CodeBaseException; |
27 | use Phan\Exception\IssueException; |
28 | use Phan\Exception\NodeException; |
29 | use Phan\Language\Element\FunctionInterface; |
30 | use Phan\Language\Element\GlobalVariable; |
31 | use Phan\Language\FQSEN\FullyQualifiedClassName; |
32 | use Phan\Language\FQSEN\FullyQualifiedFunctionName; |
33 | use Phan\Language\Type\FunctionLikeDeclarationType; |
34 | use 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 | */ |
53 | class 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. |