TimeToogo /
Pinq
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | |||
| 3 | namespace Pinq\Parsing\PhpParser; |
||
| 4 | |||
| 5 | use PhpParser\Node; |
||
| 6 | use Pinq\Expressions as O; |
||
| 7 | use Pinq\Expressions\Expression; |
||
| 8 | use Pinq\Expressions\Operators; |
||
| 9 | use Pinq\Parsing\ASTException; |
||
| 10 | |||
| 11 | /** |
||
| 12 | * Converts the PHP-Parser nodes into the equivalent expression tree. |
||
| 13 | * |
||
| 14 | * @author Elliot Levin <[email protected]> |
||
| 15 | */ |
||
| 16 | class AST |
||
| 17 | { |
||
| 18 | /** |
||
| 19 | * @var Node[] |
||
| 20 | */ |
||
| 21 | private $nodes = []; |
||
| 22 | |||
| 23 | public function __construct(array $nodes) |
||
| 24 | { |
||
| 25 | $this->nodes = $nodes; |
||
| 26 | } |
||
| 27 | |||
| 28 | /** |
||
| 29 | * Converts the supplied php parser nodes to an equivalent |
||
| 30 | * expression tree. |
||
| 31 | * |
||
| 32 | * @param Node[] $nodes |
||
| 33 | * |
||
| 34 | * @return Expression[] |
||
| 35 | */ |
||
| 36 | public static function convert(array $nodes) |
||
| 37 | { |
||
| 38 | return (new self($nodes))->getExpressions(); |
||
| 39 | } |
||
| 40 | |||
| 41 | /** |
||
| 42 | * Parses the nodes into the equivalent expression tree |
||
| 43 | * |
||
| 44 | * @return Expression[] |
||
| 45 | */ |
||
| 46 | public function getExpressions() |
||
| 47 | { |
||
| 48 | return $this->parseNodes($this->nodes); |
||
| 49 | } |
||
| 50 | |||
| 51 | /** |
||
| 52 | * @param Node[] $nodes |
||
| 53 | * |
||
| 54 | * @return Expression[] |
||
| 55 | */ |
||
| 56 | private function parseNodes(array $nodes) |
||
| 57 | { |
||
| 58 | return array_map( |
||
| 59 | function ($node) { |
||
| 60 | return $this->parseNode($node); |
||
| 61 | }, |
||
| 62 | $nodes |
||
| 63 | ); |
||
| 64 | } |
||
| 65 | |||
| 66 | /** |
||
| 67 | * @param Node $node |
||
| 68 | * |
||
| 69 | * @throws \Pinq\Parsing\ASTException |
||
| 70 | * @return Expression |
||
| 71 | */ |
||
| 72 | protected function parseNode(Node $node) |
||
| 73 | { |
||
| 74 | switch (true) { |
||
| 75 | case $node instanceof Node\Stmt: |
||
| 76 | return $this->parseStatementNode($node); |
||
| 77 | |||
| 78 | case $node instanceof Node\Expr: |
||
| 79 | return $this->parseExpressionNode($node); |
||
| 80 | |||
| 81 | case $node instanceof Node\Param: |
||
| 82 | return $this->parseParameterNode($node); |
||
| 83 | |||
| 84 | case $node instanceof Node\Arg: |
||
| 85 | return $this->parseArgumentNode($node); |
||
| 86 | |||
| 87 | default: |
||
| 88 | throw new ASTException('Unsupported node type: %s', get_class($node)); |
||
| 89 | } |
||
| 90 | } |
||
| 91 | |||
| 92 | /** |
||
| 93 | * @param $node |
||
| 94 | * |
||
| 95 | * @return Expression |
||
| 96 | */ |
||
| 97 | final public function parseNameNode($node) |
||
| 98 | { |
||
| 99 | if ($node instanceof Node\Name) { |
||
| 100 | return Expression::value($this->parseAbsoluteName($node)); |
||
| 101 | } elseif (is_string($node)) { |
||
| 102 | return Expression::value($node); |
||
| 103 | } |
||
| 104 | |||
| 105 | return $this->parseNode($node); |
||
| 106 | } |
||
| 107 | |||
| 108 | protected function parseAbsoluteName(Node\Name $node) |
||
| 109 | { |
||
| 110 | return ($node->isFullyQualified() ? '\\' : '') . (string) $node; |
||
| 111 | } |
||
| 112 | |||
| 113 | private function parseParameterNode(Node\Param $node) |
||
| 114 | { |
||
| 115 | $type = $node->type; |
||
| 116 | if ($type !== null) { |
||
| 117 | $type = (string) $type; |
||
| 118 | $lowerType = strtolower($type); |
||
| 119 | if ($type[0] !== '\\' && $lowerType !== 'array' && $lowerType !== 'callable') { |
||
| 120 | $type = '\\' . $type; |
||
| 121 | } |
||
| 122 | } |
||
| 123 | |||
| 124 | return Expression::parameter( |
||
| 125 | $node->name, |
||
| 126 | $type, |
||
| 127 | $node->default === null ? null : $this->parseNode($node->default), |
||
| 128 | $node->byRef, |
||
| 129 | $node->variadic |
||
| 130 | ); |
||
| 131 | } |
||
| 132 | |||
| 133 | private function parseArgumentNode(Node\Arg $node) |
||
| 134 | { |
||
| 135 | return Expression::argument( |
||
| 136 | $this->parseNode($node->value), |
||
| 137 | $node->unpack |
||
| 138 | ); |
||
| 139 | } |
||
| 140 | |||
| 141 | // <editor-fold defaultstate="collapsed" desc="Expression node parsers"> |
||
| 142 | |||
| 143 | public function parseExpressionNode(Node\Expr $node) |
||
| 144 | { |
||
| 145 | switch (true) { |
||
| 146 | case $mappedNode = $this->parseOperatorNode($node): |
||
| 147 | return $mappedNode; |
||
| 148 | |||
| 149 | case $node instanceof Node\Scalar |
||
| 150 | && $mappedNode = $this->parseScalarNode($node): |
||
| 151 | return $mappedNode; |
||
| 152 | |||
| 153 | case $node instanceof Node\Expr\Variable: |
||
| 154 | return Expression::variable($this->parseNameNode($node->name)); |
||
| 155 | |||
| 156 | case $node instanceof Node\Expr\Array_: |
||
| 157 | return $this->parseArrayNode($node); |
||
| 158 | |||
| 159 | case $node instanceof Node\Expr\FuncCall: |
||
| 160 | return $this->parseFunctionCallNode($node); |
||
| 161 | |||
| 162 | case $node instanceof Node\Expr\New_: |
||
| 163 | return Expression::newExpression( |
||
| 164 | $this->parseNameNode($node->class), |
||
| 165 | $this->parseNodes($node->args) |
||
| 166 | ); |
||
| 167 | |||
| 168 | case $node instanceof Node\Expr\MethodCall: |
||
| 169 | return Expression::methodCall( |
||
| 170 | $this->parseNode($node->var), |
||
| 171 | $this->parseNameNode($node->name), |
||
| 172 | $this->parseNodes($node->args) |
||
| 173 | ); |
||
| 174 | |||
| 175 | case $node instanceof Node\Expr\PropertyFetch: |
||
| 176 | return Expression::field( |
||
| 177 | $this->parseNode($node->var), |
||
| 178 | $this->parseNameNode($node->name) |
||
| 179 | ); |
||
| 180 | |||
| 181 | case $node instanceof Node\Expr\ArrayDimFetch: |
||
| 182 | return Expression::index( |
||
| 183 | $this->parseNode($node->var), |
||
| 184 | $node->dim === null ? null : $this->parseNode($node->dim) |
||
| 185 | ); |
||
| 186 | |||
| 187 | case $node instanceof Node\Expr\ConstFetch: |
||
| 188 | return Expression::constant($this->parseAbsoluteName($node->name)); |
||
| 189 | |||
| 190 | case $node instanceof Node\Expr\ClassConstFetch: |
||
| 191 | return Expression::classConstant( |
||
| 192 | $this->parseNameNode($node->class), |
||
| 193 | $node->name |
||
|
0 ignored issues
–
show
|
|||
| 194 | ); |
||
| 195 | |||
| 196 | case $node instanceof Node\Expr\StaticCall: |
||
| 197 | return Expression::staticMethodCall( |
||
| 198 | $this->parseNameNode($node->class), |
||
| 199 | $this->parseNameNode($node->name), |
||
| 200 | $this->parseNodes($node->args) |
||
| 201 | ); |
||
| 202 | |||
| 203 | case $node instanceof Node\Expr\StaticPropertyFetch: |
||
| 204 | return Expression::staticField( |
||
| 205 | $this->parseNameNode($node->class), |
||
| 206 | $this->parseNameNode($node->name) |
||
| 207 | ); |
||
| 208 | |||
| 209 | case $node instanceof Node\Expr\Ternary: |
||
| 210 | return $this->parseTernaryNode($node); |
||
| 211 | |||
| 212 | case $node instanceof Node\Expr\Closure: |
||
| 213 | return $this->parseClosureNode($node); |
||
| 214 | |||
| 215 | case $node instanceof Node\Expr\Empty_: |
||
| 216 | return Expression::emptyExpression($this->parseNode($node->expr)); |
||
| 217 | |||
| 218 | case $node instanceof Node\Expr\Isset_: |
||
| 219 | return Expression::issetExpression($this->parseNodes($node->vars)); |
||
| 220 | |||
| 221 | default: |
||
| 222 | throw new ASTException( |
||
| 223 | 'Cannot parse AST with unknown expression node: %s', |
||
| 224 | get_class($node)); |
||
| 225 | } |
||
| 226 | } |
||
| 227 | |||
| 228 | private function parseArrayNode(Node\Expr\Array_ $node) |
||
| 229 | { |
||
| 230 | $itemExpressions = []; |
||
| 231 | |||
| 232 | foreach ($node->items as $item) { |
||
| 233 | //Keys must match |
||
| 234 | $itemExpressions[] = Expression::arrayItem( |
||
| 235 | $item->key === null ? null : $this->parseNode($item->key), |
||
| 236 | $this->parseNode($item->value), |
||
| 237 | $item->byRef |
||
| 238 | ); |
||
| 239 | } |
||
| 240 | |||
| 241 | return Expression::arrayExpression($itemExpressions); |
||
| 242 | } |
||
| 243 | |||
| 244 | private function parseFunctionCallNode(Node\Expr\FuncCall $node) |
||
| 245 | { |
||
| 246 | $nameExpression = $this->parseNameNode($node->name); |
||
| 247 | |||
| 248 | if ($nameExpression instanceof O\TraversalExpression || $nameExpression instanceof O\VariableExpression) { |
||
| 249 | return Expression::invocation( |
||
| 250 | $nameExpression, |
||
| 251 | $this->parseNodes($node->args) |
||
| 252 | ); |
||
| 253 | } else { |
||
| 254 | return Expression::functionCall( |
||
| 255 | $nameExpression, |
||
| 256 | $this->parseNodes($node->args) |
||
| 257 | ); |
||
| 258 | } |
||
| 259 | } |
||
| 260 | |||
| 261 | private function parseTernaryNode(Node\Expr\Ternary $node) |
||
| 262 | { |
||
| 263 | return Expression::ternary( |
||
| 264 | $this->parseNode($node->cond), |
||
| 265 | $node->if === null ? null : $this->parseNode($node->if), |
||
| 266 | $this->parseNode($node->else) |
||
| 267 | ); |
||
| 268 | } |
||
| 269 | |||
| 270 | private function parseClosureNode(Node\Expr\Closure $node) |
||
| 271 | { |
||
| 272 | $parameterExpressions = []; |
||
| 273 | |||
| 274 | foreach ($node->params as $parameterNode) { |
||
| 275 | $parameterExpressions[] = $this->parseParameterNode($parameterNode); |
||
| 276 | } |
||
| 277 | |||
| 278 | $usedVariables = []; |
||
| 279 | foreach ($node->uses as $usedVariable) { |
||
| 280 | $usedVariables[] = Expression::closureUsedVariable($usedVariable->var, $usedVariable->byRef); |
||
| 281 | } |
||
| 282 | $bodyExpressions = $this->parseNodes($node->stmts); |
||
| 283 | |||
| 284 | return Expression::closure( |
||
| 285 | $node->byRef, |
||
| 286 | $node->static, |
||
| 287 | $parameterExpressions, |
||
| 288 | $usedVariables, |
||
| 289 | $bodyExpressions |
||
| 290 | ); |
||
| 291 | } |
||
| 292 | |||
| 293 | private function parseScalarNode(Node\Scalar $node) |
||
| 294 | { |
||
| 295 | switch (true) { |
||
| 296 | case $node instanceof Node\Scalar\DNumber: |
||
| 297 | case $node instanceof Node\Scalar\LNumber: |
||
| 298 | case $node instanceof Node\Scalar\String_: |
||
| 299 | return Expression::value($node->value); |
||
| 300 | |||
| 301 | case $node instanceof Node\Scalar\MagicConst\Line: |
||
| 302 | return Expression::value($node->getAttribute('startLine')); |
||
| 303 | |||
| 304 | case $node instanceof Node\Scalar\MagicConst: |
||
| 305 | return Expression::constant($node->getName()); |
||
| 306 | |||
| 307 | default: |
||
| 308 | return; |
||
| 309 | } |
||
| 310 | } |
||
| 311 | |||
| 312 | // </editor-fold> |
||
| 313 | |||
| 314 | // <editor-fold defaultstate="collapsed" desc="Statement node parsers"> |
||
| 315 | |||
| 316 | private function parseStatementNode(Node\Stmt $node) |
||
| 317 | { |
||
| 318 | switch (true) { |
||
| 319 | |||
| 320 | case $node instanceof Node\Stmt\Return_: |
||
| 321 | return Expression::returnExpression($node->expr !== null ? $this->parseNode($node->expr) : null); |
||
| 322 | |||
| 323 | case $node instanceof Node\Stmt\Throw_: |
||
| 324 | return Expression::throwExpression($this->parseNode($node->expr)); |
||
| 325 | |||
| 326 | case $node instanceof Node\Stmt\Unset_: |
||
| 327 | return Expression::unsetExpression($this->parseNodes($node->vars)); |
||
| 328 | |||
| 329 | default: |
||
| 330 | $this->verifyNotControlStructure($node); |
||
| 331 | throw new ASTException( |
||
| 332 | 'Cannot parse AST with unknown statement node: %s', |
||
| 333 | get_class($node)); |
||
| 334 | } |
||
| 335 | } |
||
| 336 | |||
| 337 | private static $constructStructureMap = [ |
||
| 338 | 'Do' => ASTException::DO_WHILE_LOOP, |
||
| 339 | 'For' => ASTException::FOR_LOOP, |
||
| 340 | 'Foreach' => ASTException::FOREACH_LOOP, |
||
| 341 | 'Goto' => ASTException::GOTO_STATEMENT, |
||
| 342 | 'If' => ASTException::IF_STATEMENT, |
||
| 343 | 'Switch' => ASTException::SWITCH_STATEMENT, |
||
| 344 | 'TryCatch' => ASTException::TRY_CATCH_STATEMENT, |
||
| 345 | 'While' => ASTException::WHILE_LOOP |
||
| 346 | ]; |
||
| 347 | |||
| 348 | private function verifyNotControlStructure(Node\Stmt $node) |
||
| 349 | { |
||
| 350 | $nodeType = str_replace('Stmt_', '', $node->getType()); |
||
| 351 | |||
| 352 | if (isset(self::$constructStructureMap[$nodeType])) { |
||
| 353 | throw ASTException::containsControlStructure( |
||
| 354 | self::$constructStructureMap[$nodeType], |
||
| 355 | $node->getAttribute('startLine') |
||
| 356 | ); |
||
| 357 | } |
||
| 358 | } |
||
| 359 | |||
| 360 | // </editor-fold> |
||
| 361 | |||
| 362 | // <editor-fold defaultstate="collapsed" desc="Operator node maps"> |
||
| 363 | |||
| 364 | private function parseOperatorNode(Node\Expr $node) |
||
| 365 | { |
||
| 366 | $nodeType = str_replace('Expr_', '', $node->getType()); |
||
| 367 | switch (true) { |
||
| 368 | |||
| 369 | View Code Duplication | case isset(self::$assignOperatorsMap[$nodeType]): |
|
| 370 | return Expression::assign( |
||
| 371 | $this->parseNode($node->var), |
||
| 372 | self::$assignOperatorsMap[$nodeType], |
||
| 373 | $this->parseNode($node->expr) |
||
| 374 | ); |
||
| 375 | |||
| 376 | case $node instanceof Node\Expr\Instanceof_: |
||
| 377 | return Expression::binaryOperation( |
||
| 378 | $this->parseNode($node->expr), |
||
| 379 | Operators\Binary::IS_INSTANCE_OF, |
||
| 380 | $this->parseNameNode($node->class) |
||
| 381 | ); |
||
| 382 | |||
| 383 | case isset(self::$binaryOperatorsMap[$nodeType]): |
||
| 384 | return Expression::binaryOperation( |
||
| 385 | $this->parseNode($node->left), |
||
| 386 | self::$binaryOperatorsMap[$nodeType], |
||
| 387 | $this->parseNode($node->right) |
||
| 388 | ); |
||
| 389 | |||
| 390 | View Code Duplication | case isset(self::$unaryOperatorsMap[$nodeType]): |
|
| 391 | return Expression::unaryOperation( |
||
| 392 | self::$unaryOperatorsMap[$nodeType], |
||
| 393 | $this->parseNode(isset($node->expr) ? $node->expr : $node->var) |
||
| 394 | ); |
||
| 395 | |||
| 396 | case isset(self::$castOperatorMap[$nodeType]): |
||
| 397 | return Expression::cast( |
||
| 398 | self::$castOperatorMap[$nodeType], |
||
| 399 | $this->parseNode($node->expr) |
||
| 400 | ); |
||
| 401 | |||
| 402 | default: |
||
| 403 | return null; |
||
| 404 | } |
||
| 405 | } |
||
| 406 | |||
| 407 | private static $unaryOperatorsMap = [ |
||
| 408 | 'BitwiseNot' => Operators\Unary::BITWISE_NOT, |
||
| 409 | 'BooleanNot' => Operators\Unary::NOT, |
||
| 410 | 'PostInc' => Operators\Unary::INCREMENT, |
||
| 411 | 'PostDec' => Operators\Unary::DECREMENT, |
||
| 412 | 'PreInc' => Operators\Unary::PRE_INCREMENT, |
||
| 413 | 'PreDec' => Operators\Unary::PRE_DECREMENT, |
||
| 414 | 'UnaryMinus' => Operators\Unary::NEGATION, |
||
| 415 | 'UnaryPlus' => Operators\Unary::PLUS |
||
| 416 | ]; |
||
| 417 | |||
| 418 | private static $castOperatorMap = [ |
||
| 419 | 'Cast_Array' => Operators\Cast::ARRAY_CAST, |
||
| 420 | 'Cast_Bool' => Operators\Cast::BOOLEAN, |
||
| 421 | 'Cast_Double' => Operators\Cast::DOUBLE, |
||
| 422 | 'Cast_Int' => Operators\Cast::INTEGER, |
||
| 423 | 'Cast_Object' => Operators\Cast::OBJECT, |
||
| 424 | 'Cast_String' => Operators\Cast::STRING |
||
| 425 | ]; |
||
| 426 | |||
| 427 | private static $binaryOperatorsMap = [ |
||
| 428 | 'BinaryOp_BitwiseAnd' => Operators\Binary::BITWISE_AND, |
||
| 429 | 'BinaryOp_BitwiseOr' => Operators\Binary::BITWISE_OR, |
||
| 430 | 'BinaryOp_BitwiseXor' => Operators\Binary::BITWISE_XOR, |
||
| 431 | 'BinaryOp_ShiftLeft' => Operators\Binary::SHIFT_LEFT, |
||
| 432 | 'BinaryOp_ShiftRight' => Operators\Binary::SHIFT_RIGHT, |
||
| 433 | 'BinaryOp_BooleanAnd' => Operators\Binary::LOGICAL_AND, |
||
| 434 | 'BinaryOp_BooleanOr' => Operators\Binary::LOGICAL_OR, |
||
| 435 | 'BinaryOp_LogicalAnd' => Operators\Binary::LOGICAL_AND, |
||
| 436 | 'BinaryOp_LogicalOr' => Operators\Binary::LOGICAL_OR, |
||
| 437 | 'BinaryOp_Plus' => Operators\Binary::ADDITION, |
||
| 438 | 'BinaryOp_Minus' => Operators\Binary::SUBTRACTION, |
||
| 439 | 'BinaryOp_Mul' => Operators\Binary::MULTIPLICATION, |
||
| 440 | 'BinaryOp_Div' => Operators\Binary::DIVISION, |
||
| 441 | 'BinaryOp_Mod' => Operators\Binary::MODULUS, |
||
| 442 | 'BinaryOp_Pow' => Operators\Binary::POWER, |
||
| 443 | 'BinaryOp_Concat' => Operators\Binary::CONCATENATION, |
||
| 444 | 'BinaryOp_Equal' => Operators\Binary::EQUALITY, |
||
| 445 | 'BinaryOp_Identical' => Operators\Binary::IDENTITY, |
||
| 446 | 'BinaryOp_NotEqual' => Operators\Binary::INEQUALITY, |
||
| 447 | 'BinaryOp_NotIdentical' => Operators\Binary::NOT_IDENTICAL, |
||
| 448 | 'BinaryOp_Smaller' => Operators\Binary::LESS_THAN, |
||
| 449 | 'BinaryOp_SmallerOrEqual' => Operators\Binary::LESS_THAN_OR_EQUAL_TO, |
||
| 450 | 'BinaryOp_Greater' => Operators\Binary::GREATER_THAN, |
||
| 451 | 'BinaryOp_GreaterOrEqual' => Operators\Binary::GREATER_THAN_OR_EQUAL_TO |
||
| 452 | ]; |
||
| 453 | |||
| 454 | private static $assignOperatorsMap = [ |
||
| 455 | 'Assign' => Operators\Assignment::EQUAL, |
||
| 456 | 'AssignRef' => Operators\Assignment::EQUAL_REFERENCE, |
||
| 457 | 'AssignOp_BitwiseAnd' => Operators\Assignment::BITWISE_AND, |
||
| 458 | 'AssignOp_BitwiseOr' => Operators\Assignment::BITWISE_OR, |
||
| 459 | 'AssignOp_BitwiseXor' => Operators\Assignment::BITWISE_XOR, |
||
| 460 | 'AssignOp_Concat' => Operators\Assignment::CONCATENATE, |
||
| 461 | 'AssignOp_Div' => Operators\Assignment::DIVISION, |
||
| 462 | 'AssignOp_Minus' => Operators\Assignment::SUBTRACTION, |
||
| 463 | 'AssignOp_Mod' => Operators\Assignment::MODULUS, |
||
| 464 | 'AssignOp_Mul' => Operators\Assignment::MULTIPLICATION, |
||
| 465 | 'AssignOp_Pow' => Operators\Assignment::POWER, |
||
| 466 | 'AssignOp_Plus' => Operators\Assignment::ADDITION, |
||
| 467 | 'AssignOp_ShiftLeft' => Operators\Assignment::SHIFT_LEFT, |
||
| 468 | 'AssignOp_ShiftRight' => Operators\Assignment::SHIFT_RIGHT |
||
| 469 | ]; |
||
| 470 | |||
| 471 | // </editor-fold> |
||
| 472 | } |
||
| 473 |
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.