2018-01-14 18:09:40 +01:00
|
|
|
<?php
|
2021-12-15 04:58:32 +01:00
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
namespace Psalm\Internal\Analyzer\Statements\Expression;
|
2018-01-14 18:09:40 +01:00
|
|
|
|
|
|
|
use PhpParser;
|
2021-06-08 04:55:21 +02:00
|
|
|
use Psalm\CodeLocation;
|
|
|
|
use Psalm\Context;
|
2021-12-03 20:11:20 +01:00
|
|
|
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
|
2021-12-14 00:31:46 +01:00
|
|
|
use Psalm\Internal\Analyzer\Statements\Expression\BinaryOp\AndAnalyzer;
|
|
|
|
use Psalm\Internal\Analyzer\Statements\Expression\BinaryOp\CoalesceAnalyzer;
|
|
|
|
use Psalm\Internal\Analyzer\Statements\Expression\BinaryOp\ConcatAnalyzer;
|
|
|
|
use Psalm\Internal\Analyzer\Statements\Expression\BinaryOp\NonComparisonOpAnalyzer;
|
|
|
|
use Psalm\Internal\Analyzer\Statements\Expression\BinaryOp\OrAnalyzer;
|
2018-11-06 03:57:36 +01:00
|
|
|
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
|
|
|
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
2021-11-03 20:19:29 +01:00
|
|
|
use Psalm\Internal\Codebase\TaintFlowGraph;
|
2020-09-30 18:28:13 +02:00
|
|
|
use Psalm\Internal\Codebase\VariableUseGraph;
|
2020-10-13 22:49:03 +02:00
|
|
|
use Psalm\Internal\DataFlow\DataFlowNode;
|
2021-12-03 20:11:20 +01:00
|
|
|
use Psalm\Internal\MethodIdentifier;
|
|
|
|
use Psalm\Issue\DocblockTypeContradiction;
|
2020-02-24 00:18:25 +01:00
|
|
|
use Psalm\Issue\ImpureMethodCall;
|
2020-07-30 17:25:47 +02:00
|
|
|
use Psalm\Issue\InvalidOperand;
|
2021-12-03 20:11:20 +01:00
|
|
|
use Psalm\Issue\RedundantCondition;
|
|
|
|
use Psalm\Issue\RedundantConditionGivenDocblockType;
|
|
|
|
use Psalm\Issue\TypeDoesNotContainType;
|
2018-01-14 18:09:40 +01:00
|
|
|
use Psalm\IssueBuffer;
|
2021-03-20 03:41:41 +01:00
|
|
|
use Psalm\Plugin\EventHandler\Event\AddRemoveTaintsEvent;
|
2018-01-14 18:09:40 +01:00
|
|
|
use Psalm\Type;
|
2021-12-13 04:45:57 +01:00
|
|
|
use Psalm\Type\Atomic\TLiteralInt;
|
|
|
|
use Psalm\Type\Atomic\TLiteralString;
|
2018-05-05 18:59:30 +02:00
|
|
|
use Psalm\Type\Atomic\TNamedObject;
|
2021-12-13 16:28:14 +01:00
|
|
|
use Psalm\Type\Union;
|
2021-12-03 21:40:18 +01:00
|
|
|
use UnexpectedValueException;
|
2018-01-14 18:09:40 +01:00
|
|
|
|
2021-12-03 21:07:25 +01:00
|
|
|
use function in_array;
|
|
|
|
use function strlen;
|
|
|
|
|
2018-12-02 00:37:49 +01:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2018-11-06 03:57:36 +01:00
|
|
|
class BinaryOpAnalyzer
|
2018-01-14 18:09:40 +01:00
|
|
|
{
|
|
|
|
public static function analyze(
|
2018-11-11 18:01:14 +01:00
|
|
|
StatementsAnalyzer $statements_analyzer,
|
2018-01-14 18:09:40 +01:00
|
|
|
PhpParser\Node\Expr\BinaryOp $stmt,
|
|
|
|
Context $context,
|
2019-12-16 14:32:00 +01:00
|
|
|
int $nesting = 0,
|
|
|
|
bool $from_stmt = false
|
2021-12-05 18:51:26 +01:00
|
|
|
): bool {
|
2020-05-23 03:37:18 +02:00
|
|
|
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Concat && $nesting > 100) {
|
2020-09-28 06:45:02 +02:00
|
|
|
$statements_analyzer->node_data->setType($stmt, Type::getString());
|
2020-05-23 03:37:18 +02:00
|
|
|
|
2018-01-14 18:09:40 +01:00
|
|
|
// ignore deeply-nested string concatenation
|
2020-05-19 00:57:02 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd ||
|
2018-01-14 18:09:40 +01:00
|
|
|
$stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalAnd
|
|
|
|
) {
|
2021-06-25 15:54:39 +02:00
|
|
|
$was_inside_general_use = $context->inside_general_use;
|
|
|
|
$context->inside_general_use = true;
|
2020-09-30 18:28:13 +02:00
|
|
|
|
2021-12-14 00:31:46 +01:00
|
|
|
$expr_result = AndAnalyzer::analyze(
|
2020-01-01 18:45:17 +01:00
|
|
|
$statements_analyzer,
|
2020-05-19 00:57:02 +02:00
|
|
|
$stmt,
|
|
|
|
$context,
|
2022-12-18 17:15:15 +01:00
|
|
|
$from_stmt,
|
2018-01-14 18:09:40 +01:00
|
|
|
);
|
2020-05-19 05:00:53 +02:00
|
|
|
|
2021-06-25 15:54:39 +02:00
|
|
|
$context->inside_general_use = $was_inside_general_use;
|
2020-09-30 18:28:13 +02:00
|
|
|
|
2020-05-19 05:00:53 +02:00
|
|
|
$statements_analyzer->node_data->setType($stmt, Type::getBool());
|
|
|
|
|
|
|
|
return $expr_result;
|
2020-05-19 00:57:02 +02:00
|
|
|
}
|
2018-01-14 18:09:40 +01:00
|
|
|
|
2020-05-19 00:57:02 +02:00
|
|
|
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr ||
|
2018-01-14 18:09:40 +01:00
|
|
|
$stmt instanceof PhpParser\Node\Expr\BinaryOp\LogicalOr
|
|
|
|
) {
|
2021-06-25 15:54:39 +02:00
|
|
|
$was_inside_general_use = $context->inside_general_use;
|
|
|
|
$context->inside_general_use = true;
|
2020-09-30 18:28:13 +02:00
|
|
|
|
2021-12-14 00:31:46 +01:00
|
|
|
$expr_result = OrAnalyzer::analyze(
|
2018-11-11 18:01:14 +01:00
|
|
|
$statements_analyzer,
|
2020-05-19 00:57:02 +02:00
|
|
|
$stmt,
|
|
|
|
$context,
|
2022-12-18 17:15:15 +01:00
|
|
|
$from_stmt,
|
2018-01-14 18:09:40 +01:00
|
|
|
);
|
2020-05-19 05:00:53 +02:00
|
|
|
|
2021-06-25 15:54:39 +02:00
|
|
|
$context->inside_general_use = $was_inside_general_use;
|
2020-09-30 18:28:13 +02:00
|
|
|
|
2020-05-19 05:00:53 +02:00
|
|
|
$statements_analyzer->node_data->setType($stmt, Type::getBool());
|
|
|
|
|
|
|
|
return $expr_result;
|
2020-05-19 00:57:02 +02:00
|
|
|
}
|
2018-01-14 18:09:40 +01:00
|
|
|
|
2020-05-19 00:57:02 +02:00
|
|
|
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Coalesce) {
|
2021-12-14 00:31:46 +01:00
|
|
|
$expr_result = CoalesceAnalyzer::analyze(
|
2020-05-19 00:57:02 +02:00
|
|
|
$statements_analyzer,
|
|
|
|
$stmt,
|
2022-12-18 17:15:15 +01:00
|
|
|
$context,
|
2019-12-08 22:35:56 +01:00
|
|
|
);
|
2020-09-30 18:28:13 +02:00
|
|
|
|
2020-10-13 22:49:03 +02:00
|
|
|
self::addDataFlow(
|
2020-09-30 18:28:13 +02:00
|
|
|
$statements_analyzer,
|
|
|
|
$stmt,
|
|
|
|
$stmt->left,
|
|
|
|
$stmt->right,
|
2022-12-18 17:15:15 +01:00
|
|
|
'coalesce',
|
2020-09-30 18:28:13 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
return $expr_result;
|
2020-05-19 00:57:02 +02:00
|
|
|
}
|
2018-01-14 18:09:40 +01:00
|
|
|
|
2020-05-19 00:57:02 +02:00
|
|
|
if ($stmt->left instanceof PhpParser\Node\Expr\BinaryOp) {
|
2020-09-30 18:28:13 +02:00
|
|
|
if (self::analyze($statements_analyzer, $stmt->left, $context, $nesting + 1) === false) {
|
2018-01-14 18:09:40 +01:00
|
|
|
return false;
|
|
|
|
}
|
2020-05-19 00:57:02 +02:00
|
|
|
} else {
|
2018-11-11 18:01:14 +01:00
|
|
|
if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->left, $context) === false) {
|
2018-01-14 18:09:40 +01:00
|
|
|
return false;
|
|
|
|
}
|
2020-05-19 00:57:02 +02:00
|
|
|
}
|
2018-01-14 18:09:40 +01:00
|
|
|
|
2020-05-19 00:57:02 +02:00
|
|
|
if ($stmt->right instanceof PhpParser\Node\Expr\BinaryOp) {
|
2020-09-30 18:28:13 +02:00
|
|
|
if (self::analyze($statements_analyzer, $stmt->right, $context, $nesting + 1) === false) {
|
2020-05-19 00:57:02 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
2018-11-11 18:01:14 +01:00
|
|
|
if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->right, $context) === false) {
|
2018-01-14 18:09:40 +01:00
|
|
|
return false;
|
|
|
|
}
|
2020-05-19 00:57:02 +02:00
|
|
|
}
|
2019-08-04 16:37:36 +02:00
|
|
|
|
2020-05-19 00:57:02 +02:00
|
|
|
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Concat) {
|
2020-05-19 05:00:53 +02:00
|
|
|
$stmt_type = Type::getString();
|
|
|
|
|
2021-12-14 00:31:46 +01:00
|
|
|
ConcatAnalyzer::analyze(
|
2020-01-07 07:44:12 +01:00
|
|
|
$statements_analyzer,
|
|
|
|
$stmt->left,
|
|
|
|
$stmt->right,
|
2020-05-19 05:00:53 +02:00
|
|
|
$context,
|
2022-12-18 17:15:15 +01:00
|
|
|
$result_type,
|
2018-05-31 00:56:44 +02:00
|
|
|
);
|
|
|
|
|
2020-05-19 05:00:53 +02:00
|
|
|
if ($result_type) {
|
|
|
|
$stmt_type = $result_type;
|
|
|
|
}
|
|
|
|
|
2020-10-13 23:28:12 +02:00
|
|
|
if ($statements_analyzer->data_flow_graph
|
|
|
|
&& ($statements_analyzer->data_flow_graph instanceof VariableUseGraph
|
2021-12-03 21:07:25 +01:00
|
|
|
|| !in_array('TaintedInput', $statements_analyzer->getSuppressedIssues()))
|
2020-05-26 05:28:11 +02:00
|
|
|
) {
|
2020-05-23 07:11:16 +02:00
|
|
|
$stmt_left_type = $statements_analyzer->node_data->getType($stmt->left);
|
|
|
|
$stmt_right_type = $statements_analyzer->node_data->getType($stmt->right);
|
|
|
|
|
2020-05-24 03:38:09 +02:00
|
|
|
$var_location = new CodeLocation($statements_analyzer, $stmt);
|
2020-05-23 07:11:16 +02:00
|
|
|
|
2020-10-13 22:49:03 +02:00
|
|
|
$new_parent_node = DataFlowNode::getForAssignment('concat', $var_location);
|
2020-10-13 23:28:12 +02:00
|
|
|
$statements_analyzer->data_flow_graph->addNode($new_parent_node);
|
2020-05-24 03:38:09 +02:00
|
|
|
|
2022-11-04 19:04:23 +01:00
|
|
|
$stmt_type = $stmt_type->setParentNodes([
|
2022-12-18 17:15:15 +01:00
|
|
|
$new_parent_node->id => $new_parent_node,
|
2022-11-04 19:04:23 +01:00
|
|
|
]);
|
2020-05-23 07:11:16 +02:00
|
|
|
|
2021-03-20 03:41:41 +01:00
|
|
|
$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);
|
|
|
|
|
2020-05-24 03:38:09 +02:00
|
|
|
if ($stmt_left_type && $stmt_left_type->parent_nodes) {
|
|
|
|
foreach ($stmt_left_type->parent_nodes as $parent_node) {
|
2021-03-20 03:41:41 +01:00
|
|
|
$statements_analyzer->data_flow_graph->addPath(
|
|
|
|
$parent_node,
|
|
|
|
$new_parent_node,
|
|
|
|
'concat',
|
|
|
|
$added_taints,
|
2022-12-18 17:15:15 +01:00
|
|
|
$removed_taints,
|
2021-03-20 03:41:41 +01:00
|
|
|
);
|
2020-05-24 03:38:09 +02:00
|
|
|
}
|
2020-05-23 07:11:16 +02:00
|
|
|
}
|
|
|
|
|
2020-05-24 03:38:09 +02:00
|
|
|
if ($stmt_right_type && $stmt_right_type->parent_nodes) {
|
|
|
|
foreach ($stmt_right_type->parent_nodes as $parent_node) {
|
2021-03-20 03:41:41 +01:00
|
|
|
$statements_analyzer->data_flow_graph->addPath(
|
|
|
|
$parent_node,
|
|
|
|
$new_parent_node,
|
|
|
|
'concat',
|
|
|
|
$added_taints,
|
2022-12-18 17:15:15 +01:00
|
|
|
$removed_taints,
|
2021-03-20 03:41:41 +01:00
|
|
|
);
|
2020-05-24 03:38:09 +02:00
|
|
|
}
|
2020-05-23 07:11:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-19 05:00:53 +02:00
|
|
|
$statements_analyzer->node_data->setType($stmt, $stmt_type);
|
|
|
|
|
2020-05-19 00:57:02 +02:00
|
|
|
return true;
|
|
|
|
}
|
2018-01-14 18:09:40 +01:00
|
|
|
|
2020-05-19 00:57:02 +02:00
|
|
|
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Spaceship) {
|
2020-11-23 19:10:51 +01:00
|
|
|
$statements_analyzer->node_data->setType(
|
|
|
|
$stmt,
|
2021-12-13 16:28:14 +01:00
|
|
|
new Union(
|
2020-11-23 19:10:51 +01:00
|
|
|
[
|
2021-12-13 04:45:57 +01:00
|
|
|
new TLiteralInt(-1),
|
|
|
|
new TLiteralInt(0),
|
2022-12-18 17:15:15 +01:00
|
|
|
new TLiteralInt(1),
|
|
|
|
],
|
|
|
|
),
|
2020-11-23 19:10:51 +01:00
|
|
|
);
|
2018-01-14 18:09:40 +01:00
|
|
|
|
2020-10-13 22:49:03 +02:00
|
|
|
self::addDataFlow(
|
2020-09-28 06:45:02 +02:00
|
|
|
$statements_analyzer,
|
|
|
|
$stmt,
|
|
|
|
$stmt->left,
|
|
|
|
$stmt->right,
|
2022-12-18 17:15:15 +01:00
|
|
|
'<=>',
|
2020-09-28 06:45:02 +02:00
|
|
|
);
|
|
|
|
|
2020-05-19 00:57:02 +02:00
|
|
|
return true;
|
|
|
|
}
|
2018-01-14 18:09:40 +01:00
|
|
|
|
2020-05-19 00:57:02 +02:00
|
|
|
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());
|
2018-01-14 18:09:40 +01:00
|
|
|
|
2020-05-19 00:57:02 +02:00
|
|
|
$stmt_left_type = $statements_analyzer->node_data->getType($stmt->left);
|
|
|
|
$stmt_right_type = $statements_analyzer->node_data->getType($stmt->right);
|
2018-01-14 18:09:40 +01:00
|
|
|
|
2020-07-30 17:25:47 +02:00
|
|
|
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()))
|
|
|
|
) {
|
2021-11-29 20:54:17 +01:00
|
|
|
IssueBuffer::maybeAdd(
|
2020-07-30 17:25:47 +02:00
|
|
|
new InvalidOperand(
|
|
|
|
'Cannot compare ' . $stmt_left_type->getId() . ' to ' . $stmt_right_type->getId(),
|
2022-12-18 17:15:15 +01:00
|
|
|
new CodeLocation($statements_analyzer, $stmt),
|
2020-07-30 17:25:47 +02:00
|
|
|
),
|
2022-12-18 17:15:15 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues(),
|
2021-11-29 20:54:17 +01:00
|
|
|
);
|
2020-07-30 17:25:47 +02:00
|
|
|
}
|
|
|
|
|
2020-07-24 16:08:57 +02:00
|
|
|
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']
|
2021-10-09 23:37:04 +02:00
|
|
|
&& isset($stmt->left->getArgs()[1])
|
2020-07-24 16:08:57 +02:00
|
|
|
&& $stmt_right_type
|
|
|
|
&& $stmt_right_type->hasLiteralString()
|
|
|
|
) {
|
2021-10-09 23:37:04 +02:00
|
|
|
$from_type = $statements_analyzer->node_data->getType($stmt->left->getArgs()[1]->value);
|
2020-07-24 16:08:57 +02:00
|
|
|
|
2021-10-09 23:37:04 +02:00
|
|
|
$length_type = isset($stmt->left->getArgs()[2])
|
2021-10-13 18:35:16 +02:00
|
|
|
? ($statements_analyzer->node_data->getType($stmt->left->getArgs()[2]->value) ?? Type::getMixed())
|
2020-07-24 16:08:57 +02:00
|
|
|
: 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) {
|
2021-12-13 04:45:57 +01:00
|
|
|
if ($atomic_right_type instanceof TLiteralString) {
|
2021-12-03 21:07:25 +01:00
|
|
|
if (strlen($atomic_right_type->value) !== $string_length) {
|
2020-07-24 16:08:57 +02:00
|
|
|
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal
|
|
|
|
|| $stmt instanceof PhpParser\Node\Expr\BinaryOp\Identical
|
|
|
|
) {
|
|
|
|
if ($atomic_right_type->from_docblock) {
|
2021-11-29 20:54:17 +01:00
|
|
|
IssueBuffer::maybeAdd(
|
2021-12-03 20:11:20 +01:00
|
|
|
new DocblockTypeContradiction(
|
2020-07-24 16:08:57 +02:00
|
|
|
$atomic_right_type . ' string length is not ' . $string_length,
|
2020-09-11 04:44:35 +02:00
|
|
|
new CodeLocation($statements_analyzer, $stmt),
|
2022-12-18 17:15:15 +01:00
|
|
|
"strlen($atomic_right_type) !== $string_length",
|
2020-07-24 16:08:57 +02:00
|
|
|
),
|
2022-12-18 17:15:15 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues(),
|
2021-11-29 20:54:17 +01:00
|
|
|
);
|
2020-07-24 16:08:57 +02:00
|
|
|
} else {
|
2021-11-29 20:54:17 +01:00
|
|
|
IssueBuffer::maybeAdd(
|
2021-12-03 20:11:20 +01:00
|
|
|
new TypeDoesNotContainType(
|
2020-07-24 16:08:57 +02:00
|
|
|
$atomic_right_type . ' string length is not ' . $string_length,
|
2020-09-11 04:44:35 +02:00
|
|
|
new CodeLocation($statements_analyzer, $stmt),
|
2022-12-18 17:15:15 +01:00
|
|
|
"strlen($atomic_right_type) !== $string_length",
|
2020-07-24 16:08:57 +02:00
|
|
|
),
|
2022-12-18 17:15:15 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues(),
|
2021-11-29 20:54:17 +01:00
|
|
|
);
|
2020-07-24 16:08:57 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if ($atomic_right_type->from_docblock) {
|
2021-11-29 20:54:17 +01:00
|
|
|
IssueBuffer::maybeAdd(
|
2021-12-03 20:11:20 +01:00
|
|
|
new RedundantConditionGivenDocblockType(
|
2020-07-24 16:08:57 +02:00
|
|
|
$atomic_right_type . ' string length is never ' . $string_length,
|
2020-09-11 04:44:35 +02:00
|
|
|
new CodeLocation($statements_analyzer, $stmt),
|
2022-12-18 17:15:15 +01:00
|
|
|
"strlen($atomic_right_type) !== $string_length",
|
2020-07-24 16:08:57 +02:00
|
|
|
),
|
2022-12-18 17:15:15 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues(),
|
2021-11-29 20:54:17 +01:00
|
|
|
);
|
2020-07-24 16:08:57 +02:00
|
|
|
} else {
|
2021-11-29 20:54:17 +01:00
|
|
|
IssueBuffer::maybeAdd(
|
2021-12-03 20:11:20 +01:00
|
|
|
new RedundantCondition(
|
2020-07-24 16:08:57 +02:00
|
|
|
$atomic_right_type . ' string length is never ' . $string_length,
|
2020-09-11 04:44:35 +02:00
|
|
|
new CodeLocation($statements_analyzer, $stmt),
|
2022-12-18 17:15:15 +01:00
|
|
|
"strlen($atomic_right_type) !== $string_length",
|
2020-07-24 16:08:57 +02:00
|
|
|
),
|
2022-12-18 17:15:15 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues(),
|
2021-11-29 20:54:17 +01:00
|
|
|
);
|
2020-07-24 16:08:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-23 16:28:26 +02:00
|
|
|
$codebase = $statements_analyzer->getCodebase();
|
|
|
|
|
2020-05-19 00:57:02 +02:00
|
|
|
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal
|
|
|
|
&& $stmt_left_type
|
|
|
|
&& $stmt_right_type
|
2020-08-23 16:28:26 +02:00
|
|
|
&& ($context->mutation_free || $codebase->alter_code)
|
2020-05-19 00:57:02 +02:00
|
|
|
) {
|
|
|
|
self::checkForImpureEqualityComparison(
|
2018-12-17 21:23:56 +01:00
|
|
|
$statements_analyzer,
|
2020-05-19 00:57:02 +02:00
|
|
|
$stmt,
|
|
|
|
$stmt_left_type,
|
2022-12-18 17:15:15 +01:00
|
|
|
$stmt_right_type,
|
2018-12-17 21:23:56 +01:00
|
|
|
);
|
|
|
|
}
|
2018-01-14 18:09:40 +01:00
|
|
|
|
2020-10-13 22:49:03 +02:00
|
|
|
self::addDataFlow(
|
2020-09-28 06:45:02 +02:00
|
|
|
$statements_analyzer,
|
|
|
|
$stmt,
|
|
|
|
$stmt->left,
|
|
|
|
$stmt->right,
|
2022-12-18 17:15:15 +01:00
|
|
|
'comparison',
|
2020-09-28 06:45:02 +02:00
|
|
|
);
|
|
|
|
|
2020-05-19 00:57:02 +02:00
|
|
|
return true;
|
|
|
|
}
|
2019-08-20 04:45:24 +02:00
|
|
|
|
2021-12-14 00:31:46 +01:00
|
|
|
NonComparisonOpAnalyzer::analyze(
|
2020-05-19 00:57:02 +02:00
|
|
|
$statements_analyzer,
|
|
|
|
$stmt,
|
2022-12-18 17:15:15 +01:00
|
|
|
$context,
|
2020-05-19 00:57:02 +02:00
|
|
|
);
|
2019-08-20 04:45:24 +02:00
|
|
|
|
2020-05-19 00:57:02 +02:00
|
|
|
return true;
|
|
|
|
}
|
2019-08-20 04:45:24 +02:00
|
|
|
|
2020-10-13 22:49:03 +02:00
|
|
|
public static function addDataFlow(
|
2020-09-28 06:45:02 +02:00
|
|
|
StatementsAnalyzer $statements_analyzer,
|
|
|
|
PhpParser\Node\Expr $stmt,
|
|
|
|
PhpParser\Node\Expr $left,
|
|
|
|
PhpParser\Node\Expr $right,
|
|
|
|
string $type = 'binaryop'
|
2021-12-05 18:51:26 +01:00
|
|
|
): void {
|
2020-09-28 06:45:02 +02:00
|
|
|
if ($stmt->getLine() === -1) {
|
2021-12-03 21:40:18 +01:00
|
|
|
throw new UnexpectedValueException('bad');
|
2020-09-28 06:45:02 +02:00
|
|
|
}
|
|
|
|
$result_type = $statements_analyzer->node_data->getType($stmt);
|
2021-11-03 20:19:29 +01:00
|
|
|
if (!$result_type) {
|
|
|
|
return;
|
|
|
|
}
|
2020-09-28 06:45:02 +02:00
|
|
|
|
2021-11-03 20:19:29 +01:00
|
|
|
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
|
2021-11-04 00:27:00 +01:00
|
|
|
&& (!$stmt instanceof PhpParser\Node\Expr\BinaryOp\Plus || !$result_type->hasArray())
|
2020-09-28 06:45:02 +02:00
|
|
|
) {
|
2021-11-04 00:31:22 +01:00
|
|
|
//among BinaryOp, only Concat and Coalesce can pass tainted value to the result. Also Plus on arrays only
|
2021-11-03 20:19:29 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($statements_analyzer->data_flow_graph) {
|
2020-09-28 06:45:02 +02:00
|
|
|
$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);
|
|
|
|
|
2020-10-13 22:49:03 +02:00
|
|
|
$new_parent_node = DataFlowNode::getForAssignment($type, $var_location);
|
2020-10-13 23:28:12 +02:00
|
|
|
$statements_analyzer->data_flow_graph->addNode($new_parent_node);
|
2020-09-28 06:45:02 +02:00
|
|
|
|
2022-11-04 19:04:23 +01:00
|
|
|
$result_type = $result_type->setParentNodes([
|
2022-12-18 17:15:15 +01:00
|
|
|
$new_parent_node->id => $new_parent_node,
|
2022-11-04 19:04:23 +01:00
|
|
|
]);
|
|
|
|
$statements_analyzer->node_data->setType($stmt, $result_type);
|
2020-09-28 06:45:02 +02:00
|
|
|
|
|
|
|
if ($stmt_left_type && $stmt_left_type->parent_nodes) {
|
|
|
|
foreach ($stmt_left_type->parent_nodes as $parent_node) {
|
2020-10-13 23:28:12 +02:00
|
|
|
$statements_analyzer->data_flow_graph->addPath($parent_node, $new_parent_node, $type);
|
2020-09-28 06:45:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($stmt_right_type && $stmt_right_type->parent_nodes) {
|
|
|
|
foreach ($stmt_right_type->parent_nodes as $parent_node) {
|
2020-10-13 23:28:12 +02:00
|
|
|
$statements_analyzer->data_flow_graph->addPath($parent_node, $new_parent_node, $type);
|
2020-09-28 06:45:02 +02:00
|
|
|
}
|
|
|
|
}
|
2020-09-30 18:28:13 +02:00
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Expr\AssignOp
|
2020-10-13 23:28:12 +02:00
|
|
|
&& $statements_analyzer->data_flow_graph instanceof VariableUseGraph
|
2020-09-30 18:28:13 +02:00
|
|
|
) {
|
|
|
|
$root_expr = $left;
|
|
|
|
|
|
|
|
while ($root_expr instanceof PhpParser\Node\Expr\ArrayDimFetch) {
|
|
|
|
$root_expr = $root_expr->var;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($left instanceof PhpParser\Node\Expr\PropertyFetch) {
|
2020-10-13 23:28:12 +02:00
|
|
|
$statements_analyzer->data_flow_graph->addPath(
|
2020-09-30 18:28:13 +02:00
|
|
|
$new_parent_node,
|
2020-10-13 22:49:03 +02:00
|
|
|
new DataFlowNode('variable-use', 'variable use', null),
|
2022-12-18 17:15:15 +01:00
|
|
|
'used-by-instance-property',
|
2020-09-30 18:28:13 +02:00
|
|
|
);
|
|
|
|
} if ($left instanceof PhpParser\Node\Expr\StaticPropertyFetch) {
|
2020-10-13 23:28:12 +02:00
|
|
|
$statements_analyzer->data_flow_graph->addPath(
|
2020-09-30 18:28:13 +02:00
|
|
|
$new_parent_node,
|
2020-10-13 22:49:03 +02:00
|
|
|
new DataFlowNode('variable-use', 'variable use', null),
|
2022-12-18 17:15:15 +01:00
|
|
|
'use-in-static-property',
|
2020-09-30 18:28:13 +02:00
|
|
|
);
|
|
|
|
} elseif (!$left instanceof PhpParser\Node\Expr\Variable) {
|
2020-10-13 23:28:12 +02:00
|
|
|
$statements_analyzer->data_flow_graph->addPath(
|
2020-09-30 18:28:13 +02:00
|
|
|
$new_parent_node,
|
2020-10-13 22:49:03 +02:00
|
|
|
new DataFlowNode('variable-use', 'variable use', null),
|
2022-12-18 17:15:15 +01:00
|
|
|
'variable-use',
|
2020-09-30 18:28:13 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2020-09-28 06:45:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-19 00:57:02 +02:00
|
|
|
private static function checkForImpureEqualityComparison(
|
|
|
|
StatementsAnalyzer $statements_analyzer,
|
|
|
|
PhpParser\Node\Expr\BinaryOp\Equal $stmt,
|
2021-12-13 16:28:14 +01:00
|
|
|
Union $stmt_left_type,
|
|
|
|
Union $stmt_right_type
|
2021-12-05 18:51:26 +01:00
|
|
|
): void {
|
2020-05-19 00:57:02 +02:00
|
|
|
$codebase = $statements_analyzer->getCodebase();
|
2019-08-20 04:45:24 +02:00
|
|
|
|
2020-05-19 00:57:02 +02:00
|
|
|
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(
|
2021-12-03 20:11:20 +01:00
|
|
|
new MethodIdentifier(
|
2020-05-19 00:57:02 +02:00
|
|
|
$atomic_type->value,
|
2022-12-18 17:15:15 +01:00
|
|
|
'__tostring',
|
|
|
|
),
|
2020-05-19 00:57:02 +02:00
|
|
|
);
|
2021-12-03 21:40:18 +01:00
|
|
|
} catch (UnexpectedValueException $e) {
|
2020-05-19 00:57:02 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$storage->mutation_free) {
|
2020-08-28 18:42:55 +02:00
|
|
|
if ($statements_analyzer->getSource()
|
2021-12-03 20:11:20 +01:00
|
|
|
instanceof FunctionLikeAnalyzer
|
2020-08-28 18:42:55 +02:00
|
|
|
&& $statements_analyzer->getSource()->track_mutations
|
2020-08-23 16:28:26 +02:00
|
|
|
) {
|
2020-08-25 01:24:27 +02:00
|
|
|
$statements_analyzer->getSource()->inferred_has_mutation = true;
|
2020-08-23 16:28:26 +02:00
|
|
|
$statements_analyzer->getSource()->inferred_impure = true;
|
|
|
|
} else {
|
2021-11-29 20:54:17 +01:00
|
|
|
IssueBuffer::maybeAdd(
|
2020-08-23 16:28:26 +02:00
|
|
|
new ImpureMethodCall(
|
|
|
|
'Cannot call a possibly-mutating method '
|
|
|
|
. $atomic_type->value . '::__toString from a pure context',
|
2022-12-18 17:15:15 +01:00
|
|
|
new CodeLocation($statements_analyzer, $stmt),
|
2020-08-23 16:28:26 +02:00
|
|
|
),
|
2022-12-18 17:15:15 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues(),
|
2021-11-29 20:54:17 +01:00
|
|
|
);
|
2019-08-20 04:45:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-05-19 00:57:02 +02:00
|
|
|
} 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(
|
2021-12-03 20:11:20 +01:00
|
|
|
new MethodIdentifier(
|
2020-05-19 00:57:02 +02:00
|
|
|
$atomic_type->value,
|
2022-12-18 17:15:15 +01:00
|
|
|
'__tostring',
|
|
|
|
),
|
2020-05-19 00:57:02 +02:00
|
|
|
);
|
2021-12-03 21:40:18 +01:00
|
|
|
} catch (UnexpectedValueException $e) {
|
2020-05-19 00:57:02 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$storage->mutation_free) {
|
2021-12-03 20:11:20 +01:00
|
|
|
if ($statements_analyzer->getSource() instanceof FunctionLikeAnalyzer
|
2020-08-28 18:42:55 +02:00
|
|
|
&& $statements_analyzer->getSource()->track_mutations
|
2020-08-23 16:28:26 +02:00
|
|
|
) {
|
2020-08-25 01:24:27 +02:00
|
|
|
$statements_analyzer->getSource()->inferred_has_mutation = true;
|
2020-08-23 16:28:26 +02:00
|
|
|
$statements_analyzer->getSource()->inferred_impure = true;
|
|
|
|
} else {
|
2021-11-29 20:54:17 +01:00
|
|
|
IssueBuffer::maybeAdd(
|
2020-08-23 16:28:26 +02:00
|
|
|
new ImpureMethodCall(
|
|
|
|
'Cannot call a possibly-mutating method '
|
|
|
|
. $atomic_type->value . '::__toString from a pure context',
|
2022-12-18 17:15:15 +01:00
|
|
|
new CodeLocation($statements_analyzer, $stmt),
|
2020-08-23 16:28:26 +02:00
|
|
|
),
|
2022-12-18 17:15:15 +01:00
|
|
|
$statements_analyzer->getSuppressedIssues(),
|
2021-11-29 20:54:17 +01:00
|
|
|
);
|
2020-05-19 00:57:02 +02:00
|
|
|
}
|
|
|
|
}
|
2018-01-14 18:09:40 +01:00
|
|
|
}
|
|
|
|
}
|
2020-01-07 14:50:31 +01:00
|
|
|
}
|
2018-01-14 18:09:40 +01:00
|
|
|
}
|
|
|
|
}
|