100) { $statements_analyzer->node_data->setType($stmt, Type::getString()); // ignore deeply-nested string concatenation return true; } if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd || $stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalAnd ) { $was_inside_general_use = $context->inside_general_use; $context->inside_general_use = true; $expr_result = AndAnalyzer::analyze( $statements_analyzer, $stmt, $context, $from_stmt, ); $context->inside_general_use = $was_inside_general_use; $statements_analyzer->node_data->setType($stmt, Type::getBool()); return $expr_result; } if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr || $stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalOr ) { $was_inside_general_use = $context->inside_general_use; $context->inside_general_use = true; $expr_result = OrAnalyzer::analyze( $statements_analyzer, $stmt, $context, $from_stmt, ); $context->inside_general_use = $was_inside_general_use; $statements_analyzer->node_data->setType($stmt, Type::getBool()); return $expr_result; } if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Coalesce) { $expr_result = CoalesceAnalyzer::analyze( $statements_analyzer, $stmt, $context, ); self::addDataFlow( $statements_analyzer, $stmt, $stmt->left, $stmt->right, 'coalesce', ); return $expr_result; } if ($stmt->left instanceof PhpParser\Node\Expr\BinaryOp) { if (self::analyze($statements_analyzer, $stmt->left, $context, $nesting + 1) === false) { return false; } } else { if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->left, $context) === false) { return false; } } if ($stmt->right instanceof PhpParser\Node\Expr\BinaryOp) { if (self::analyze($statements_analyzer, $stmt->right, $context, $nesting + 1) === false) { return false; } } else { if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->right, $context) === false) { return false; } } if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Concat) { $stmt_type = Type::getString(); ConcatAnalyzer::analyze( $statements_analyzer, $stmt->left, $stmt->right, $context, $result_type, ); if ($result_type) { $stmt_type = $result_type; } if ($statements_analyzer->data_flow_graph && ($statements_analyzer->data_flow_graph instanceof VariableUseGraph || !in_array('TaintedInput', $statements_analyzer->getSuppressedIssues())) ) { $stmt_left_type = $statements_analyzer->node_data->getType($stmt->left); $stmt_right_type = $statements_analyzer->node_data->getType($stmt->right); $var_location = new CodeLocation($statements_analyzer, $stmt); $new_parent_node = DataFlowNode::getForAssignment('concat', $var_location); $statements_analyzer->data_flow_graph->addNode($new_parent_node); $stmt_type = $stmt_type->setParentNodes([ $new_parent_node->id => $new_parent_node, ]); $codebase = $statements_analyzer->getCodebase(); $event = new AddRemoveTaintsEvent($stmt, $context, $statements_analyzer, $codebase); $added_taints = $codebase->config->eventDispatcher->dispatchAddTaints($event); $removed_taints = $codebase->config->eventDispatcher->dispatchRemoveTaints($event); if ($stmt_left_type && $stmt_left_type->parent_nodes) { foreach ($stmt_left_type->parent_nodes as $parent_node) { $statements_analyzer->data_flow_graph->addPath( $parent_node, $new_parent_node, 'concat', $added_taints, $removed_taints, ); } } if ($stmt_right_type && $stmt_right_type->parent_nodes) { foreach ($stmt_right_type->parent_nodes as $parent_node) { $statements_analyzer->data_flow_graph->addPath( $parent_node, $new_parent_node, 'concat', $added_taints, $removed_taints, ); } } } $statements_analyzer->node_data->setType($stmt, $stmt_type); return true; } if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Spaceship) { $statements_analyzer->node_data->setType( $stmt, new Union( [ new TLiteralInt(-1), new TLiteralInt(0), new TLiteralInt(1), ], ), ); self::addDataFlow( $statements_analyzer, $stmt, $stmt->left, $stmt->right, '<=>', ); return true; } if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal || $stmt instanceof PhpParser\Node\Expr\BinaryOp\NotEqual || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Identical || $stmt instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Greater || $stmt instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Smaller || $stmt instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual ) { $statements_analyzer->node_data->setType($stmt, Type::getBool()); $stmt_left_type = $statements_analyzer->node_data->getType($stmt->left); $stmt_right_type = $statements_analyzer->node_data->getType($stmt->right); if (($stmt instanceof PhpParser\Node\Expr\BinaryOp\Greater || $stmt instanceof PhpParser\Node\Expr\BinaryOp\GreaterOrEqual || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Smaller || $stmt instanceof PhpParser\Node\Expr\BinaryOp\SmallerOrEqual) && $statements_analyzer->getCodebase()->config->strict_binary_operands && $stmt_left_type && $stmt_right_type && (($stmt_left_type->isSingle() && $stmt_left_type->hasBool()) || ($stmt_right_type->isSingle() && $stmt_right_type->hasBool())) ) { IssueBuffer::maybeAdd( new InvalidOperand( 'Cannot compare ' . $stmt_left_type->getId() . ' to ' . $stmt_right_type->getId(), new CodeLocation($statements_analyzer, $stmt), ), $statements_analyzer->getSuppressedIssues(), ); } if (($stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal || $stmt instanceof PhpParser\Node\Expr\BinaryOp\NotEqual || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Identical || $stmt instanceof PhpParser\Node\Expr\BinaryOp\NotIdentical) && $stmt->left instanceof PhpParser\Node\Expr\FuncCall && $stmt->left->name instanceof PhpParser\Node\Name && $stmt->left->name->parts === ['substr'] && isset($stmt->left->getArgs()[1]) && $stmt_right_type && $stmt_right_type->hasLiteralString() ) { $from_type = $statements_analyzer->node_data->getType($stmt->left->getArgs()[1]->value); $length_type = isset($stmt->left->getArgs()[2]) ? ($statements_analyzer->node_data->getType($stmt->left->getArgs()[2]->value) ?? Type::getMixed()) : null; $string_length = null; if ($from_type && $from_type->isSingleIntLiteral() && $length_type === null) { $string_length = -$from_type->getSingleIntLiteral()->value; } elseif ($length_type && $length_type->isSingleIntLiteral()) { $string_length = $length_type->getSingleIntLiteral()->value; } if ($string_length > 0) { foreach ($stmt_right_type->getAtomicTypes() as $atomic_right_type) { if ($atomic_right_type instanceof TLiteralString) { if (strlen($atomic_right_type->value) !== $string_length) { if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal || $stmt instanceof PhpParser\Node\Expr\BinaryOp\Identical ) { if ($atomic_right_type->from_docblock) { IssueBuffer::maybeAdd( new DocblockTypeContradiction( $atomic_right_type . ' string length is not ' . $string_length, new CodeLocation($statements_analyzer, $stmt), "strlen($atomic_right_type) !== $string_length", ), $statements_analyzer->getSuppressedIssues(), ); } else { IssueBuffer::maybeAdd( new TypeDoesNotContainType( $atomic_right_type . ' string length is not ' . $string_length, new CodeLocation($statements_analyzer, $stmt), "strlen($atomic_right_type) !== $string_length", ), $statements_analyzer->getSuppressedIssues(), ); } } else { if ($atomic_right_type->from_docblock) { IssueBuffer::maybeAdd( new RedundantConditionGivenDocblockType( $atomic_right_type . ' string length is never ' . $string_length, new CodeLocation($statements_analyzer, $stmt), "strlen($atomic_right_type) !== $string_length", ), $statements_analyzer->getSuppressedIssues(), ); } else { IssueBuffer::maybeAdd( new RedundantCondition( $atomic_right_type . ' string length is never ' . $string_length, new CodeLocation($statements_analyzer, $stmt), "strlen($atomic_right_type) !== $string_length", ), $statements_analyzer->getSuppressedIssues(), ); } } } } } } } $codebase = $statements_analyzer->getCodebase(); if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal && $stmt_left_type && $stmt_right_type && ($context->mutation_free || $codebase->alter_code) ) { self::checkForImpureEqualityComparison( $statements_analyzer, $stmt, $stmt_left_type, $stmt_right_type, ); } self::addDataFlow( $statements_analyzer, $stmt, $stmt->left, $stmt->right, 'comparison', ); return true; } NonComparisonOpAnalyzer::analyze( $statements_analyzer, $stmt, $context, ); return true; } public static function addDataFlow( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Expr $stmt, PhpParser\Node\Expr $left, PhpParser\Node\Expr $right, string $type = 'binaryop' ): void { if ($stmt->getLine() === -1) { throw new UnexpectedValueException('bad'); } $result_type = $statements_analyzer->node_data->getType($stmt); if (!$result_type) { return; } if ($statements_analyzer->data_flow_graph instanceof TaintFlowGraph && $stmt instanceof PhpParser\Node\Expr\BinaryOp && !$stmt instanceof PhpParser\Node\Expr\BinaryOp\Concat && !$stmt instanceof PhpParser\Node\Expr\BinaryOp\Coalesce && (!$stmt instanceof PhpParser\Node\Expr\BinaryOp\Plus || !$result_type->hasArray()) ) { //among BinaryOp, only Concat and Coalesce can pass tainted value to the result. Also Plus on arrays only return; } if ($statements_analyzer->data_flow_graph) { $stmt_left_type = $statements_analyzer->node_data->getType($left); $stmt_right_type = $statements_analyzer->node_data->getType($right); $var_location = new CodeLocation($statements_analyzer, $stmt); $new_parent_node = DataFlowNode::getForAssignment($type, $var_location); $statements_analyzer->data_flow_graph->addNode($new_parent_node); $result_type = $result_type->setParentNodes([ $new_parent_node->id => $new_parent_node, ]); $statements_analyzer->node_data->setType($stmt, $result_type); if ($stmt_left_type && $stmt_left_type->parent_nodes) { foreach ($stmt_left_type->parent_nodes as $parent_node) { $statements_analyzer->data_flow_graph->addPath($parent_node, $new_parent_node, $type); } } if ($stmt_right_type && $stmt_right_type->parent_nodes) { foreach ($stmt_right_type->parent_nodes as $parent_node) { $statements_analyzer->data_flow_graph->addPath($parent_node, $new_parent_node, $type); } } if ($stmt instanceof PhpParser\Node\Expr\AssignOp && $statements_analyzer->data_flow_graph instanceof VariableUseGraph ) { $root_expr = $left; while ($root_expr instanceof PhpParser\Node\Expr\ArrayDimFetch) { $root_expr = $root_expr->var; } if ($left instanceof PhpParser\Node\Expr\PropertyFetch) { $statements_analyzer->data_flow_graph->addPath( $new_parent_node, new DataFlowNode('variable-use', 'variable use', null), 'used-by-instance-property', ); } if ($left instanceof PhpParser\Node\Expr\StaticPropertyFetch) { $statements_analyzer->data_flow_graph->addPath( $new_parent_node, new DataFlowNode('variable-use', 'variable use', null), 'use-in-static-property', ); } elseif (!$left instanceof PhpParser\Node\Expr\Variable) { $statements_analyzer->data_flow_graph->addPath( $new_parent_node, new DataFlowNode('variable-use', 'variable use', null), 'variable-use', ); } } } } private static function checkForImpureEqualityComparison( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Expr\BinaryOp\Equal $stmt, Union $stmt_left_type, Union $stmt_right_type ): void { $codebase = $statements_analyzer->getCodebase(); if ($stmt_left_type->hasString() && $stmt_right_type->hasObjectType()) { foreach ($stmt_right_type->getAtomicTypes() as $atomic_type) { if ($atomic_type instanceof TNamedObject) { try { $storage = $codebase->methods->getStorage( new MethodIdentifier( $atomic_type->value, '__tostring', ), ); } catch (UnexpectedValueException $e) { continue; } if (!$storage->mutation_free) { if ($statements_analyzer->getSource() instanceof FunctionLikeAnalyzer && $statements_analyzer->getSource()->track_mutations ) { $statements_analyzer->getSource()->inferred_has_mutation = true; $statements_analyzer->getSource()->inferred_impure = true; } else { IssueBuffer::maybeAdd( new ImpureMethodCall( 'Cannot call a possibly-mutating method ' . $atomic_type->value . '::__toString from a pure context', new CodeLocation($statements_analyzer, $stmt), ), $statements_analyzer->getSuppressedIssues(), ); } } } } } elseif ($stmt_right_type->hasString() && $stmt_left_type->hasObjectType()) { foreach ($stmt_left_type->getAtomicTypes() as $atomic_type) { if ($atomic_type instanceof TNamedObject) { try { $storage = $codebase->methods->getStorage( new MethodIdentifier( $atomic_type->value, '__tostring', ), ); } catch (UnexpectedValueException $e) { continue; } if (!$storage->mutation_free) { if ($statements_analyzer->getSource() instanceof FunctionLikeAnalyzer && $statements_analyzer->getSource()->track_mutations ) { $statements_analyzer->getSource()->inferred_has_mutation = true; $statements_analyzer->getSource()->inferred_impure = true; } else { IssueBuffer::maybeAdd( new ImpureMethodCall( 'Cannot call a possibly-mutating method ' . $atomic_type->value . '::__toString from a pure context', new CodeLocation($statements_analyzer, $stmt), ), $statements_analyzer->getSuppressedIssues(), ); } } } } } } }