2020-05-18 21:13:27 +02:00
|
|
|
<?php
|
2021-12-15 04:58:32 +01:00
|
|
|
|
2020-05-18 21:13:27 +02:00
|
|
|
namespace Psalm\Internal\Analyzer\Statements\Expression;
|
|
|
|
|
|
|
|
use PhpParser;
|
|
|
|
use Psalm\CodeLocation;
|
|
|
|
use Psalm\Context;
|
2021-12-03 20:11:20 +01:00
|
|
|
use Psalm\FileManipulation;
|
2021-06-08 04:55:21 +02:00
|
|
|
use Psalm\Internal\Analyzer\Statements\Expression\Call\Method\MethodCallReturnTypeFetcher;
|
|
|
|
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
|
|
|
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
2021-12-03 20:11:20 +01:00
|
|
|
use Psalm\Internal\Codebase\VariableUseGraph;
|
2021-06-18 00:15:45 +02:00
|
|
|
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
|
2021-12-03 20:11:20 +01:00
|
|
|
use Psalm\Internal\MethodIdentifier;
|
2021-06-08 04:55:21 +02:00
|
|
|
use Psalm\Internal\Type\TypeCombiner;
|
2020-05-18 21:13:27 +02:00
|
|
|
use Psalm\Issue\InvalidCast;
|
|
|
|
use Psalm\Issue\PossiblyInvalidCast;
|
2020-12-01 23:25:45 +01:00
|
|
|
use Psalm\Issue\RedundantCast;
|
|
|
|
use Psalm\Issue\RedundantCastGivenDocblockType;
|
2022-09-09 03:04:14 +02:00
|
|
|
use Psalm\Issue\RiskyCast;
|
2020-05-18 21:13:27 +02:00
|
|
|
use Psalm\Issue\UnrecognizedExpression;
|
|
|
|
use Psalm\IssueBuffer;
|
|
|
|
use Psalm\Type;
|
|
|
|
use Psalm\Type\Atomic\Scalar;
|
|
|
|
use Psalm\Type\Atomic\TArray;
|
2021-12-13 04:45:57 +01:00
|
|
|
use Psalm\Type\Atomic\TBool;
|
2022-08-03 21:43:37 +02:00
|
|
|
use Psalm\Type\Atomic\TClosedResource;
|
2021-12-13 04:45:57 +01:00
|
|
|
use Psalm\Type\Atomic\TFalse;
|
2020-05-18 22:02:10 +02:00
|
|
|
use Psalm\Type\Atomic\TFloat;
|
|
|
|
use Psalm\Type\Atomic\TInt;
|
2021-06-08 04:55:21 +02:00
|
|
|
use Psalm\Type\Atomic\TKeyedArray;
|
2020-05-18 21:13:27 +02:00
|
|
|
use Psalm\Type\Atomic\TList;
|
2022-06-25 02:22:59 +02:00
|
|
|
use Psalm\Type\Atomic\TLiteralFloat;
|
2021-12-13 04:45:57 +01:00
|
|
|
use Psalm\Type\Atomic\TLiteralInt;
|
|
|
|
use Psalm\Type\Atomic\TLiteralString;
|
2020-05-18 21:13:27 +02:00
|
|
|
use Psalm\Type\Atomic\TMixed;
|
|
|
|
use Psalm\Type\Atomic\TNamedObject;
|
2022-08-03 21:43:37 +02:00
|
|
|
use Psalm\Type\Atomic\TNonEmptyArray;
|
|
|
|
use Psalm\Type\Atomic\TNonEmptyList;
|
|
|
|
use Psalm\Type\Atomic\TNonEmptyString;
|
2021-12-13 04:45:57 +01:00
|
|
|
use Psalm\Type\Atomic\TNonspecificLiteralInt;
|
|
|
|
use Psalm\Type\Atomic\TNonspecificLiteralString;
|
2020-05-18 21:13:27 +02:00
|
|
|
use Psalm\Type\Atomic\TNull;
|
2021-12-13 04:45:57 +01:00
|
|
|
use Psalm\Type\Atomic\TNumeric;
|
|
|
|
use Psalm\Type\Atomic\TNumericString;
|
|
|
|
use Psalm\Type\Atomic\TObjectWithProperties;
|
|
|
|
use Psalm\Type\Atomic\TResource;
|
2020-05-18 21:13:27 +02:00
|
|
|
use Psalm\Type\Atomic\TString;
|
2021-12-13 04:45:57 +01:00
|
|
|
use Psalm\Type\Atomic\TTemplateParam;
|
2022-08-03 21:43:37 +02:00
|
|
|
use Psalm\Type\Atomic\TTrue;
|
2021-12-13 16:28:14 +01:00
|
|
|
use Psalm\Type\Union;
|
2021-06-08 04:55:21 +02:00
|
|
|
|
2020-05-18 21:13:27 +02:00
|
|
|
use function array_merge;
|
2021-12-03 21:07:25 +01:00
|
|
|
use function array_pop;
|
2020-05-18 21:13:27 +02:00
|
|
|
use function array_values;
|
2021-06-08 04:55:21 +02:00
|
|
|
use function get_class;
|
2020-05-18 21:13:27 +02:00
|
|
|
|
|
|
|
class CastAnalyzer
|
|
|
|
{
|
|
|
|
public static function analyze(
|
|
|
|
StatementsAnalyzer $statements_analyzer,
|
|
|
|
PhpParser\Node\Expr\Cast $stmt,
|
|
|
|
Context $context
|
2021-12-05 18:51:26 +01:00
|
|
|
): bool {
|
2020-05-18 21:13:27 +02:00
|
|
|
if ($stmt instanceof PhpParser\Node\Expr\Cast\Int_) {
|
|
|
|
if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$maybe_type = $statements_analyzer->node_data->getType($stmt->expr);
|
|
|
|
|
2020-09-30 18:28:13 +02:00
|
|
|
if ($maybe_type) {
|
2020-11-25 18:04:48 +01:00
|
|
|
if ($maybe_type->isInt()) {
|
2020-12-04 05:15:07 +01:00
|
|
|
if (!$maybe_type->from_calculation) {
|
2021-06-18 00:15:45 +02:00
|
|
|
self::handleRedundantCast($maybe_type, $statements_analyzer, $stmt);
|
2020-11-25 18:04:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-03 22:15:41 +02:00
|
|
|
$type = self::castIntAttempt(
|
|
|
|
$statements_analyzer,
|
|
|
|
$context,
|
|
|
|
$maybe_type,
|
|
|
|
$stmt->expr,
|
|
|
|
true
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
$type = Type::getInt();
|
2020-05-18 21:13:27 +02:00
|
|
|
}
|
|
|
|
|
2022-08-03 22:15:41 +02:00
|
|
|
$statements_analyzer->node_data->setType($stmt, $type);
|
2020-05-18 21:13:27 +02:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Expr\Cast\Double) {
|
|
|
|
if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-10-28 19:06:05 +01:00
|
|
|
$maybe_type = $statements_analyzer->node_data->getType($stmt->expr);
|
|
|
|
|
2020-11-25 18:04:48 +01:00
|
|
|
if ($maybe_type) {
|
|
|
|
if ($maybe_type->isFloat()) {
|
2021-06-18 00:15:45 +02:00
|
|
|
self::handleRedundantCast($maybe_type, $statements_analyzer, $stmt);
|
2020-11-25 18:04:48 +01:00
|
|
|
}
|
2020-10-28 19:06:05 +01:00
|
|
|
|
2022-08-03 22:30:45 +02:00
|
|
|
$type = self::castFloatAttempt(
|
|
|
|
$statements_analyzer,
|
|
|
|
$context,
|
|
|
|
$maybe_type,
|
|
|
|
$stmt->expr,
|
|
|
|
true
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
$type = Type::getFloat();
|
2020-10-28 19:06:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$statements_analyzer->node_data->setType($stmt, $type);
|
2020-05-18 21:13:27 +02:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Expr\Cast\Bool_) {
|
|
|
|
if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-10-28 19:06:05 +01:00
|
|
|
$maybe_type = $statements_analyzer->node_data->getType($stmt->expr);
|
|
|
|
|
2020-11-25 18:04:48 +01:00
|
|
|
if ($maybe_type) {
|
|
|
|
if ($maybe_type->isBool()) {
|
2021-06-18 00:15:45 +02:00
|
|
|
self::handleRedundantCast($maybe_type, $statements_analyzer, $stmt);
|
2020-11-25 18:04:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-28 19:06:05 +01:00
|
|
|
$type = Type::getBool();
|
|
|
|
|
2021-12-03 20:11:20 +01:00
|
|
|
if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph
|
2020-10-28 19:06:05 +01:00
|
|
|
) {
|
2021-09-26 22:57:04 +02:00
|
|
|
$type->parent_nodes = $maybe_type->parent_nodes ?? [];
|
2020-10-28 19:06:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$statements_analyzer->node_data->setType($stmt, $type);
|
2020-05-18 21:13:27 +02:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Expr\Cast\String_) {
|
|
|
|
if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-06-29 15:13:19 +02:00
|
|
|
$stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr);
|
|
|
|
|
|
|
|
if ($stmt_expr_type) {
|
2020-11-25 18:04:48 +01:00
|
|
|
if ($stmt_expr_type->isString()) {
|
2021-06-18 00:15:45 +02:00
|
|
|
self::handleRedundantCast($stmt_expr_type, $statements_analyzer, $stmt);
|
2020-11-25 18:04:48 +01:00
|
|
|
}
|
|
|
|
|
2020-06-29 19:21:33 +02:00
|
|
|
$stmt_type = self::castStringAttempt(
|
|
|
|
$statements_analyzer,
|
|
|
|
$context,
|
|
|
|
$stmt_expr_type,
|
|
|
|
$stmt->expr,
|
|
|
|
true
|
|
|
|
);
|
2020-05-18 21:13:27 +02:00
|
|
|
} else {
|
|
|
|
$stmt_type = Type::getString();
|
|
|
|
}
|
|
|
|
|
|
|
|
$statements_analyzer->node_data->setType($stmt, $stmt_type);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Expr\Cast\Object_) {
|
2022-05-09 06:40:04 +02:00
|
|
|
if (!self::checkExprGeneralUse($statements_analyzer, $stmt, $context)) {
|
2020-05-18 21:13:27 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-05-09 06:40:04 +02:00
|
|
|
$permissible_atomic_types = [];
|
|
|
|
$all_permissible = false;
|
2020-10-28 19:06:05 +01:00
|
|
|
|
2022-05-09 06:40:04 +02:00
|
|
|
if ($stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr)) {
|
|
|
|
if ($stmt_expr_type->isObjectType()) {
|
|
|
|
self::handleRedundantCast($stmt_expr_type, $statements_analyzer, $stmt);
|
|
|
|
}
|
|
|
|
|
|
|
|
$all_permissible = true;
|
|
|
|
|
|
|
|
foreach ($stmt_expr_type->getAtomicTypes() as $type) {
|
|
|
|
if ($type instanceof Scalar) {
|
|
|
|
$objWithProps = new TObjectWithProperties(['scalar' => new Union([$type])]);
|
|
|
|
$permissible_atomic_types[] = $objWithProps;
|
|
|
|
} elseif ($type instanceof TKeyedArray) {
|
|
|
|
$permissible_atomic_types[] = new TObjectWithProperties($type->properties);
|
|
|
|
} else {
|
|
|
|
$all_permissible = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($permissible_atomic_types && $all_permissible) {
|
|
|
|
$type = TypeCombiner::combine($permissible_atomic_types);
|
|
|
|
} else {
|
|
|
|
$type = Type::getObject();
|
|
|
|
}
|
2020-10-28 19:06:05 +01:00
|
|
|
|
2021-12-03 20:11:20 +01:00
|
|
|
if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph
|
2020-10-28 19:06:05 +01:00
|
|
|
) {
|
2022-05-09 06:40:04 +02:00
|
|
|
$type->parent_nodes = $stmt_expr_type->parent_nodes ?? [];
|
2020-10-28 19:06:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$statements_analyzer->node_data->setType($stmt, $type);
|
2020-05-18 21:13:27 +02:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Expr\Cast\Array_) {
|
2022-05-09 06:40:04 +02:00
|
|
|
if (!self::checkExprGeneralUse($statements_analyzer, $stmt, $context)) {
|
2020-05-18 21:13:27 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$permissible_atomic_types = [];
|
|
|
|
$all_permissible = false;
|
|
|
|
|
|
|
|
if ($stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr)) {
|
2020-11-25 18:04:48 +01:00
|
|
|
if ($stmt_expr_type->isArray()) {
|
2021-06-18 00:15:45 +02:00
|
|
|
self::handleRedundantCast($stmt_expr_type, $statements_analyzer, $stmt);
|
2020-11-25 18:04:48 +01:00
|
|
|
}
|
|
|
|
|
2020-05-18 21:13:27 +02:00
|
|
|
$all_permissible = true;
|
|
|
|
|
|
|
|
foreach ($stmt_expr_type->getAtomicTypes() as $type) {
|
|
|
|
if ($type instanceof Scalar) {
|
2021-12-13 16:28:14 +01:00
|
|
|
$keyed_array = new TKeyedArray([new Union([$type])]);
|
2021-05-19 21:17:50 +02:00
|
|
|
$keyed_array->is_list = true;
|
2022-02-01 21:46:05 +01:00
|
|
|
$keyed_array->sealed = true;
|
2021-05-19 21:17:50 +02:00
|
|
|
$permissible_atomic_types[] = $keyed_array;
|
2020-05-18 21:13:27 +02:00
|
|
|
} elseif ($type instanceof TNull) {
|
|
|
|
$permissible_atomic_types[] = new TArray([Type::getEmpty(), Type::getEmpty()]);
|
|
|
|
} elseif ($type instanceof TArray
|
|
|
|
|| $type instanceof TList
|
2020-08-30 17:44:14 +02:00
|
|
|
|| $type instanceof TKeyedArray
|
2020-05-18 21:13:27 +02:00
|
|
|
) {
|
|
|
|
$permissible_atomic_types[] = clone $type;
|
|
|
|
} else {
|
|
|
|
$all_permissible = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($permissible_atomic_types && $all_permissible) {
|
2020-11-22 00:11:29 +01:00
|
|
|
$type = TypeCombiner::combine($permissible_atomic_types);
|
2020-05-18 21:13:27 +02:00
|
|
|
} else {
|
2020-10-28 19:06:05 +01:00
|
|
|
$type = Type::getArray();
|
|
|
|
}
|
|
|
|
|
2021-11-03 21:11:06 +01:00
|
|
|
if ($statements_analyzer->data_flow_graph) {
|
2021-09-26 22:57:04 +02:00
|
|
|
$type->parent_nodes = $stmt_expr_type->parent_nodes ?? [];
|
2020-05-18 21:13:27 +02:00
|
|
|
}
|
|
|
|
|
2020-10-28 19:06:05 +01:00
|
|
|
$statements_analyzer->node_data->setType($stmt, $type);
|
|
|
|
|
2020-05-18 21:13:27 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-10-04 04:59:43 +02:00
|
|
|
if ($stmt instanceof PhpParser\Node\Expr\Cast\Unset_
|
|
|
|
&& $statements_analyzer->getCodebase()->php_major_version < 8
|
|
|
|
) {
|
2020-05-18 21:13:27 +02:00
|
|
|
if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$statements_analyzer->node_data->setType($stmt, Type::getNull());
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-11-29 20:54:17 +01:00
|
|
|
IssueBuffer::maybeAdd(
|
2020-05-18 21:13:27 +02:00
|
|
|
new UnrecognizedExpression(
|
|
|
|
'Psalm does not understand the cast ' . get_class($stmt),
|
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
|
|
|
),
|
|
|
|
$statements_analyzer->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
);
|
2020-05-18 21:13:27 +02:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2022-08-03 22:15:41 +02:00
|
|
|
|
|
|
|
public static function castIntAttempt(
|
|
|
|
StatementsAnalyzer $statements_analyzer,
|
|
|
|
Context $context,
|
|
|
|
Union $stmt_type,
|
|
|
|
PhpParser\Node\Expr $stmt,
|
|
|
|
bool $explicit_cast = false
|
|
|
|
): Union {
|
|
|
|
$codebase = $statements_analyzer->getCodebase();
|
|
|
|
|
2022-09-09 03:04:14 +02:00
|
|
|
$risky_cast = [];
|
2022-08-03 22:15:41 +02:00
|
|
|
$invalid_casts = [];
|
|
|
|
$valid_ints = [];
|
|
|
|
$castable_types = [];
|
|
|
|
|
|
|
|
$atomic_types = $stmt_type->getAtomicTypes();
|
|
|
|
|
|
|
|
$parent_nodes = [];
|
|
|
|
|
|
|
|
if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph) {
|
|
|
|
$parent_nodes = $stmt_type->parent_nodes;
|
|
|
|
}
|
|
|
|
|
|
|
|
while ($atomic_types) {
|
|
|
|
$atomic_type = array_pop($atomic_types);
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TInt) {
|
|
|
|
$valid_ints[] = $atomic_type;
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TFloat) {
|
|
|
|
if ($atomic_type instanceof TLiteralFloat) {
|
|
|
|
$valid_ints[] = new TLiteralInt((int) $atomic_type->value);
|
|
|
|
} else {
|
|
|
|
$castable_types[] = new TInt();
|
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TString) {
|
2022-08-04 00:14:06 +02:00
|
|
|
if ($atomic_type instanceof TLiteralString) {
|
2022-08-03 22:15:41 +02:00
|
|
|
$valid_ints[] = new TLiteralInt((int) $atomic_type->value);
|
|
|
|
} elseif ($atomic_type instanceof TNumericString) {
|
|
|
|
$castable_types[] = new TInt();
|
|
|
|
} else {
|
2022-08-04 00:14:06 +02:00
|
|
|
// any normal string is technically $valid_int[] = new TLiteralInt(0);
|
|
|
|
// however we cannot be certain that it's not inferred, therefore less strict
|
|
|
|
$castable_types[] = new TInt();
|
2022-08-03 22:15:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TNull || $atomic_type instanceof TFalse) {
|
|
|
|
$valid_ints[] = new TLiteralInt(0);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TTrue) {
|
|
|
|
$valid_ints[] = new TLiteralInt(1);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TBool) {
|
|
|
|
// do NOT use TIntRange here, as it will cause invalid behavior, e.g. bitwiseAssignment
|
|
|
|
$valid_ints[] = new TLiteralInt(0);
|
|
|
|
$valid_ints[] = new TLiteralInt(1);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// could be invalid, but allow it, as it is allowed for TString below too
|
|
|
|
if ($atomic_type instanceof TMixed
|
|
|
|
|| $atomic_type instanceof TClosedResource
|
|
|
|
|| $atomic_type instanceof TResource
|
|
|
|
|| $atomic_type instanceof Scalar
|
|
|
|
) {
|
|
|
|
$castable_types[] = new TInt();
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TNamedObject
|
|
|
|
|| $atomic_type instanceof TObjectWithProperties
|
|
|
|
) {
|
|
|
|
$intersection_types = [$atomic_type];
|
|
|
|
|
|
|
|
if ($atomic_type->extra_types) {
|
|
|
|
$intersection_types = array_merge($intersection_types, $atomic_type->extra_types);
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($intersection_types as $intersection_type) {
|
|
|
|
if ($intersection_type instanceof TNamedObject) {
|
|
|
|
$intersection_method_id = new MethodIdentifier(
|
|
|
|
$intersection_type->value,
|
|
|
|
'__tostring'
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($codebase->methods->methodExists(
|
|
|
|
$intersection_method_id,
|
|
|
|
$context->calling_method_id,
|
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
|
|
|
)) {
|
|
|
|
$return_type = $codebase->methods->getMethodReturnType(
|
|
|
|
$intersection_method_id,
|
|
|
|
$self_class
|
|
|
|
) ?? Type::getString();
|
|
|
|
|
|
|
|
$declaring_method_id = $codebase->methods->getDeclaringMethodId($intersection_method_id);
|
|
|
|
|
|
|
|
MethodCallReturnTypeFetcher::taintMethodCallResult(
|
|
|
|
$statements_analyzer,
|
|
|
|
$return_type,
|
|
|
|
$stmt,
|
|
|
|
$stmt,
|
|
|
|
[],
|
|
|
|
$intersection_method_id,
|
|
|
|
$declaring_method_id,
|
|
|
|
$intersection_type->value . '::__toString',
|
|
|
|
$context
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($statements_analyzer->data_flow_graph) {
|
|
|
|
$parent_nodes = array_merge($return_type->parent_nodes, $parent_nodes);
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($return_type->getAtomicTypes() as $sub_atomic_type) {
|
2022-08-04 00:14:06 +02:00
|
|
|
if ($sub_atomic_type instanceof TLiteralString) {
|
2022-08-03 22:15:41 +02:00
|
|
|
$valid_ints[] = new TLiteralInt((int) $sub_atomic_type->value);
|
|
|
|
} elseif ($sub_atomic_type instanceof TNumericString) {
|
|
|
|
$castable_types[] = new TInt();
|
|
|
|
} else {
|
2022-08-04 00:14:06 +02:00
|
|
|
// any normal string is technically $valid_int[] = new TLiteralInt(0);
|
|
|
|
// however we cannot be certain that it's not inferred, therefore less strict
|
|
|
|
$castable_types[] = new TInt();
|
2022-08-03 22:15:41 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
continue 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($intersection_type instanceof TObjectWithProperties
|
|
|
|
&& isset($intersection_type->methods['__toString'])
|
|
|
|
) {
|
|
|
|
$castable_types[] = new TInt();
|
|
|
|
|
|
|
|
continue 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TNonEmptyArray
|
|
|
|
|| $atomic_type instanceof TNonEmptyList
|
|
|
|
) {
|
2022-09-09 03:04:14 +02:00
|
|
|
$risky_cast[] = $atomic_type->getId();
|
2022-08-03 22:15:41 +02:00
|
|
|
|
|
|
|
$valid_ints[] = new TLiteralInt(1);
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TArray
|
|
|
|
|| $atomic_type instanceof TList
|
|
|
|
|| $atomic_type instanceof TKeyedArray
|
|
|
|
) {
|
|
|
|
// if type is not specific, it can be both 0 or 1, depending on whether the array has data or not
|
|
|
|
// welcome to off-by-one hell if that happens :-)
|
2022-09-09 03:04:14 +02:00
|
|
|
$risky_cast[] = $atomic_type->getId();
|
2022-08-03 22:15:41 +02:00
|
|
|
|
|
|
|
$valid_ints[] = new TLiteralInt(0);
|
|
|
|
$valid_ints[] = new TLiteralInt(1);
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TTemplateParam) {
|
|
|
|
$atomic_types = array_merge($atomic_types, $atomic_type->as->getAtomicTypes());
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-08-03 23:48:49 +02:00
|
|
|
// always 1 for "error" cases
|
|
|
|
$valid_ints[] = new TLiteralInt(1);
|
|
|
|
|
2022-08-03 22:15:41 +02:00
|
|
|
$invalid_casts[] = $atomic_type->getId();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($invalid_casts) {
|
2022-08-03 23:48:49 +02:00
|
|
|
IssueBuffer::maybeAdd(
|
|
|
|
new InvalidCast(
|
|
|
|
$invalid_casts[0] . ' cannot be cast to int',
|
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
|
|
|
),
|
|
|
|
$statements_analyzer->getSuppressedIssues()
|
|
|
|
);
|
2022-09-09 03:04:14 +02:00
|
|
|
} elseif ($risky_cast) {
|
2022-08-03 22:15:41 +02:00
|
|
|
IssueBuffer::maybeAdd(
|
2022-09-09 03:04:14 +02:00
|
|
|
new RiskyCast(
|
|
|
|
'Casting ' . $risky_cast[0] . ' to int has possibly unintended value of 0/1',
|
2022-08-03 23:48:49 +02:00
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
2022-08-03 22:15:41 +02:00
|
|
|
),
|
|
|
|
$statements_analyzer->getSuppressedIssues()
|
|
|
|
);
|
|
|
|
} elseif ($explicit_cast && !$castable_types) {
|
|
|
|
// todo: emit error here
|
|
|
|
}
|
|
|
|
|
|
|
|
$valid_types = array_merge($valid_ints, $castable_types);
|
|
|
|
|
|
|
|
if (!$valid_types) {
|
|
|
|
$int_type = Type::getInt();
|
|
|
|
} else {
|
|
|
|
$int_type = TypeCombiner::combine(
|
|
|
|
$valid_types,
|
|
|
|
$codebase
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($statements_analyzer->data_flow_graph) {
|
|
|
|
$int_type->parent_nodes = $parent_nodes;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $int_type;
|
|
|
|
}
|
2022-08-03 22:30:45 +02:00
|
|
|
|
|
|
|
public static function castFloatAttempt(
|
|
|
|
StatementsAnalyzer $statements_analyzer,
|
|
|
|
Context $context,
|
|
|
|
Union $stmt_type,
|
|
|
|
PhpParser\Node\Expr $stmt,
|
|
|
|
bool $explicit_cast = false
|
|
|
|
): Union {
|
|
|
|
$codebase = $statements_analyzer->getCodebase();
|
|
|
|
|
2022-09-09 03:04:14 +02:00
|
|
|
$risky_cast = [];
|
2022-08-03 22:30:45 +02:00
|
|
|
$invalid_casts = [];
|
|
|
|
$valid_floats = [];
|
|
|
|
$castable_types = [];
|
|
|
|
|
|
|
|
$atomic_types = $stmt_type->getAtomicTypes();
|
|
|
|
|
|
|
|
$parent_nodes = [];
|
|
|
|
|
|
|
|
if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph) {
|
|
|
|
$parent_nodes = $stmt_type->parent_nodes;
|
|
|
|
}
|
|
|
|
|
|
|
|
while ($atomic_types) {
|
|
|
|
$atomic_type = array_pop($atomic_types);
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TFloat) {
|
|
|
|
$valid_floats[] = $atomic_type;
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TInt) {
|
|
|
|
if ($atomic_type instanceof TLiteralInt) {
|
|
|
|
$valid_floats[] = new TLiteralFloat((float) $atomic_type->value);
|
|
|
|
} else {
|
|
|
|
$castable_types[] = new TFloat();
|
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TString) {
|
2022-08-04 00:14:06 +02:00
|
|
|
if ($atomic_type instanceof TLiteralString) {
|
2022-08-03 22:30:45 +02:00
|
|
|
$valid_floats[] = new TLiteralFloat((float) $atomic_type->value);
|
|
|
|
} elseif ($atomic_type instanceof TNumericString) {
|
|
|
|
$castable_types[] = new TFloat();
|
|
|
|
} else {
|
2022-08-04 00:14:06 +02:00
|
|
|
// any normal string is technically $valid_floats[] = new TLiteralFloat(0.0);
|
|
|
|
// however we cannot be certain that it's not inferred, therefore less strict
|
|
|
|
$castable_types[] = new TFloat();
|
2022-08-03 22:30:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TNull || $atomic_type instanceof TFalse) {
|
|
|
|
$valid_floats[] = new TLiteralFloat(0.0);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TTrue) {
|
|
|
|
$valid_floats[] = new TLiteralFloat(1.0);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TBool) {
|
|
|
|
$valid_floats[] = new TLiteralFloat(0.0);
|
|
|
|
$valid_floats[] = new TLiteralFloat(1.0);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// could be invalid, but allow it, as it is allowed for TString below too
|
|
|
|
if ($atomic_type instanceof TMixed
|
|
|
|
|| $atomic_type instanceof TClosedResource
|
|
|
|
|| $atomic_type instanceof TResource
|
|
|
|
|| $atomic_type instanceof Scalar
|
|
|
|
) {
|
|
|
|
$castable_types[] = new TFloat();
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TNamedObject
|
|
|
|
|| $atomic_type instanceof TObjectWithProperties
|
|
|
|
) {
|
|
|
|
$intersection_types = [$atomic_type];
|
|
|
|
|
|
|
|
if ($atomic_type->extra_types) {
|
|
|
|
$intersection_types = array_merge($intersection_types, $atomic_type->extra_types);
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($intersection_types as $intersection_type) {
|
|
|
|
if ($intersection_type instanceof TNamedObject) {
|
|
|
|
$intersection_method_id = new MethodIdentifier(
|
|
|
|
$intersection_type->value,
|
|
|
|
'__tostring'
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($codebase->methods->methodExists(
|
|
|
|
$intersection_method_id,
|
|
|
|
$context->calling_method_id,
|
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
|
|
|
)) {
|
|
|
|
$return_type = $codebase->methods->getMethodReturnType(
|
|
|
|
$intersection_method_id,
|
|
|
|
$self_class
|
|
|
|
) ?? Type::getString();
|
|
|
|
|
|
|
|
$declaring_method_id = $codebase->methods->getDeclaringMethodId($intersection_method_id);
|
|
|
|
|
|
|
|
MethodCallReturnTypeFetcher::taintMethodCallResult(
|
|
|
|
$statements_analyzer,
|
|
|
|
$return_type,
|
|
|
|
$stmt,
|
|
|
|
$stmt,
|
|
|
|
[],
|
|
|
|
$intersection_method_id,
|
|
|
|
$declaring_method_id,
|
|
|
|
$intersection_type->value . '::__toString',
|
|
|
|
$context
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($statements_analyzer->data_flow_graph) {
|
|
|
|
$parent_nodes = array_merge($return_type->parent_nodes, $parent_nodes);
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($return_type->getAtomicTypes() as $sub_atomic_type) {
|
2022-08-04 00:14:06 +02:00
|
|
|
if ($sub_atomic_type instanceof TLiteralString) {
|
2022-08-03 22:30:45 +02:00
|
|
|
$valid_floats[] = new TLiteralFloat((float) $sub_atomic_type->value);
|
|
|
|
} elseif ($sub_atomic_type instanceof TNumericString) {
|
|
|
|
$castable_types[] = new TFloat();
|
|
|
|
} else {
|
2022-08-04 00:14:06 +02:00
|
|
|
// any normal string is technically $valid_int[] = new TLiteralInt(0);
|
|
|
|
// however we cannot be certain that it's not inferred, therefore less strict
|
|
|
|
$castable_types[] = new TFloat();
|
2022-08-03 22:30:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
continue 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($intersection_type instanceof TObjectWithProperties
|
|
|
|
&& isset($intersection_type->methods['__toString'])
|
|
|
|
) {
|
|
|
|
$castable_types[] = new TFloat();
|
|
|
|
|
|
|
|
continue 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TNonEmptyArray
|
|
|
|
|| $atomic_type instanceof TNonEmptyList
|
|
|
|
) {
|
2022-09-09 03:04:14 +02:00
|
|
|
$risky_cast[] = $atomic_type->getId();
|
2022-08-03 22:30:45 +02:00
|
|
|
|
|
|
|
$valid_floats[] = new TLiteralFloat(1.0);
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TArray
|
|
|
|
|| $atomic_type instanceof TList
|
|
|
|
|| $atomic_type instanceof TKeyedArray
|
|
|
|
) {
|
|
|
|
// if type is not specific, it can be both 0 or 1, depending on whether the array has data or not
|
|
|
|
// welcome to off-by-one hell if that happens :-)
|
2022-09-09 03:04:14 +02:00
|
|
|
$risky_cast[] = $atomic_type->getId();
|
2022-08-03 22:30:45 +02:00
|
|
|
|
|
|
|
$valid_floats[] = new TLiteralFloat(0.0);
|
|
|
|
$valid_floats[] = new TLiteralFloat(1.0);
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TTemplateParam) {
|
|
|
|
$atomic_types = array_merge($atomic_types, $atomic_type->as->getAtomicTypes());
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-08-03 23:48:49 +02:00
|
|
|
// always 1.0 for "error" cases
|
|
|
|
$valid_floats[] = new TLiteralFloat(1.0);
|
|
|
|
|
2022-08-03 22:30:45 +02:00
|
|
|
$invalid_casts[] = $atomic_type->getId();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($invalid_casts) {
|
2022-08-03 23:48:49 +02:00
|
|
|
IssueBuffer::maybeAdd(
|
|
|
|
new InvalidCast(
|
|
|
|
$invalid_casts[0] . ' cannot be cast to float',
|
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
|
|
|
),
|
|
|
|
$statements_analyzer->getSuppressedIssues()
|
|
|
|
);
|
2022-09-09 03:04:14 +02:00
|
|
|
} elseif ($risky_cast) {
|
2022-08-03 22:30:45 +02:00
|
|
|
IssueBuffer::maybeAdd(
|
2022-09-09 03:04:14 +02:00
|
|
|
new RiskyCast(
|
|
|
|
'Casting ' . $risky_cast[0] . ' to float has possibly unintended value of 0.0/1.0',
|
2022-08-03 23:48:49 +02:00
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
2022-08-03 22:30:45 +02:00
|
|
|
),
|
|
|
|
$statements_analyzer->getSuppressedIssues()
|
|
|
|
);
|
|
|
|
} elseif ($explicit_cast && !$castable_types) {
|
|
|
|
// todo: emit error here
|
|
|
|
}
|
|
|
|
|
|
|
|
$valid_types = array_merge($valid_floats, $castable_types);
|
|
|
|
|
|
|
|
if (!$valid_types) {
|
|
|
|
$float_type = Type::getFloat();
|
|
|
|
} else {
|
|
|
|
$float_type = TypeCombiner::combine(
|
|
|
|
$valid_types,
|
|
|
|
$codebase
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($statements_analyzer->data_flow_graph) {
|
|
|
|
$float_type->parent_nodes = $parent_nodes;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $float_type;
|
|
|
|
}
|
2020-05-18 21:13:27 +02:00
|
|
|
|
|
|
|
public static function castStringAttempt(
|
|
|
|
StatementsAnalyzer $statements_analyzer,
|
|
|
|
Context $context,
|
2021-12-13 16:28:14 +01:00
|
|
|
Union $stmt_type,
|
2020-05-18 21:13:27 +02:00
|
|
|
PhpParser\Node\Expr $stmt,
|
|
|
|
bool $explicit_cast = false
|
2021-12-13 16:28:14 +01:00
|
|
|
): Union {
|
2020-05-18 21:13:27 +02:00
|
|
|
$codebase = $statements_analyzer->getCodebase();
|
|
|
|
|
|
|
|
$invalid_casts = [];
|
|
|
|
$valid_strings = [];
|
|
|
|
$castable_types = [];
|
|
|
|
|
|
|
|
$atomic_types = $stmt_type->getAtomicTypes();
|
|
|
|
|
2020-06-29 15:13:19 +02:00
|
|
|
$parent_nodes = [];
|
|
|
|
|
2020-10-13 23:28:12 +02:00
|
|
|
if ($statements_analyzer->data_flow_graph) {
|
2020-09-30 18:28:13 +02:00
|
|
|
$parent_nodes = $stmt_type->parent_nodes;
|
|
|
|
}
|
|
|
|
|
2020-05-18 21:13:27 +02:00
|
|
|
while ($atomic_types) {
|
2021-12-03 21:07:25 +01:00
|
|
|
$atomic_type = array_pop($atomic_types);
|
2020-05-18 21:13:27 +02:00
|
|
|
|
|
|
|
if ($atomic_type instanceof TFloat
|
|
|
|
|| $atomic_type instanceof TInt
|
2021-12-13 04:45:57 +01:00
|
|
|
|| $atomic_type instanceof TNumeric
|
2020-05-18 21:13:27 +02:00
|
|
|
) {
|
2022-06-25 02:22:59 +02:00
|
|
|
if ($atomic_type instanceof TLiteralInt || $atomic_type instanceof TLiteralFloat) {
|
2021-12-13 04:45:57 +01:00
|
|
|
$castable_types[] = new TLiteralString((string) $atomic_type->value);
|
|
|
|
} elseif ($atomic_type instanceof TNonspecificLiteralInt) {
|
|
|
|
$castable_types[] = new TNonspecificLiteralString();
|
2021-06-14 22:30:45 +02:00
|
|
|
} else {
|
2021-12-13 04:45:57 +01:00
|
|
|
$castable_types[] = new TNumericString();
|
2021-06-14 22:30:45 +02:00
|
|
|
}
|
|
|
|
|
2020-05-18 21:13:27 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TString) {
|
|
|
|
$valid_strings[] = $atomic_type;
|
2020-06-29 15:29:19 +02:00
|
|
|
|
2020-05-18 21:13:27 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-05-19 20:53:06 +02:00
|
|
|
if ($atomic_type instanceof TNull
|
2021-12-13 04:45:57 +01:00
|
|
|
|| $atomic_type instanceof TFalse
|
2020-05-19 20:53:06 +02:00
|
|
|
) {
|
2021-12-13 04:45:57 +01:00
|
|
|
$valid_strings[] = new TLiteralString('');
|
2020-05-19 20:53:06 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-08-03 21:43:37 +02:00
|
|
|
if ($atomic_type instanceof TTrue
|
|
|
|
) {
|
|
|
|
$valid_strings[] = new TLiteralString('1');
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TBool
|
|
|
|
) {
|
|
|
|
$valid_strings[] = new TLiteralString('1');
|
|
|
|
$valid_strings[] = new TLiteralString('');
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TClosedResource
|
|
|
|
|| $atomic_type instanceof TResource
|
|
|
|
) {
|
|
|
|
$castable_types[] = new TNonEmptyString();
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-05-18 21:13:27 +02:00
|
|
|
if ($atomic_type instanceof TMixed
|
2021-12-13 16:28:14 +01:00
|
|
|
|| $atomic_type instanceof Scalar
|
2020-05-18 21:13:27 +02:00
|
|
|
) {
|
|
|
|
$castable_types[] = new TString();
|
2020-06-29 15:13:19 +02:00
|
|
|
|
2020-05-18 21:13:27 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($atomic_type instanceof TNamedObject
|
2021-12-13 04:45:57 +01:00
|
|
|
|| $atomic_type instanceof TObjectWithProperties
|
2020-05-18 21:13:27 +02:00
|
|
|
) {
|
|
|
|
$intersection_types = [$atomic_type];
|
|
|
|
|
|
|
|
if ($atomic_type->extra_types) {
|
|
|
|
$intersection_types = array_merge($intersection_types, $atomic_type->extra_types);
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($intersection_types as $intersection_type) {
|
|
|
|
if ($intersection_type instanceof TNamedObject) {
|
2021-12-03 20:11:20 +01:00
|
|
|
$intersection_method_id = new MethodIdentifier(
|
2020-05-18 21:13:27 +02:00
|
|
|
$intersection_type->value,
|
|
|
|
'__tostring'
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($codebase->methods->methodExists(
|
|
|
|
$intersection_method_id,
|
|
|
|
$context->calling_method_id,
|
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
|
|
|
)) {
|
|
|
|
$return_type = $codebase->methods->getMethodReturnType(
|
|
|
|
$intersection_method_id,
|
|
|
|
$self_class
|
2021-10-13 18:35:16 +02:00
|
|
|
) ?? Type::getString();
|
2020-06-29 15:13:19 +02:00
|
|
|
|
2020-06-29 18:11:11 +02:00
|
|
|
$declaring_method_id = $codebase->methods->getDeclaringMethodId($intersection_method_id);
|
|
|
|
|
2020-06-29 15:13:19 +02:00
|
|
|
MethodCallReturnTypeFetcher::taintMethodCallResult(
|
|
|
|
$statements_analyzer,
|
|
|
|
$return_type,
|
|
|
|
$stmt,
|
|
|
|
$stmt,
|
2021-02-01 04:40:48 +01:00
|
|
|
[],
|
2020-06-29 15:13:19 +02:00
|
|
|
$intersection_method_id,
|
2020-06-29 18:11:11 +02:00
|
|
|
$declaring_method_id,
|
2020-06-29 15:13:19 +02:00
|
|
|
$intersection_type->value . '::__toString',
|
|
|
|
$context
|
2020-05-18 21:13:27 +02:00
|
|
|
);
|
|
|
|
|
2020-10-13 23:28:12 +02:00
|
|
|
if ($statements_analyzer->data_flow_graph) {
|
2020-09-28 06:45:02 +02:00
|
|
|
$parent_nodes = array_merge($return_type->parent_nodes, $parent_nodes);
|
2020-06-29 15:29:19 +02:00
|
|
|
}
|
2020-06-29 15:13:19 +02:00
|
|
|
|
|
|
|
$castable_types = array_merge(
|
|
|
|
$castable_types,
|
|
|
|
array_values($return_type->getAtomicTypes())
|
|
|
|
);
|
2020-05-18 21:13:27 +02:00
|
|
|
|
|
|
|
continue 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-13 04:45:57 +01:00
|
|
|
if ($intersection_type instanceof TObjectWithProperties
|
2020-05-18 21:13:27 +02:00
|
|
|
&& isset($intersection_type->methods['__toString'])
|
|
|
|
) {
|
|
|
|
$castable_types[] = new TString();
|
|
|
|
|
|
|
|
continue 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-13 04:45:57 +01:00
|
|
|
if ($atomic_type instanceof TTemplateParam) {
|
2020-05-18 21:13:27 +02:00
|
|
|
$atomic_types = array_merge($atomic_types, $atomic_type->as->getAtomicTypes());
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$invalid_casts[] = $atomic_type->getId();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($invalid_casts) {
|
|
|
|
if ($valid_strings || $castable_types) {
|
2021-11-29 20:54:17 +01:00
|
|
|
IssueBuffer::maybeAdd(
|
2020-05-18 21:13:27 +02:00
|
|
|
new PossiblyInvalidCast(
|
|
|
|
$invalid_casts[0] . ' cannot be cast to string',
|
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
|
|
|
),
|
|
|
|
$statements_analyzer->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
);
|
2020-05-18 21:13:27 +02:00
|
|
|
} else {
|
2021-11-29 20:54:17 +01:00
|
|
|
IssueBuffer::maybeAdd(
|
2020-05-18 21:13:27 +02:00
|
|
|
new InvalidCast(
|
|
|
|
$invalid_casts[0] . ' cannot be cast to string',
|
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
|
|
|
),
|
|
|
|
$statements_analyzer->getSuppressedIssues()
|
2021-11-29 20:54:17 +01:00
|
|
|
);
|
2020-05-18 21:13:27 +02:00
|
|
|
}
|
|
|
|
} elseif ($explicit_cast && !$castable_types) {
|
|
|
|
// todo: emit error here
|
|
|
|
}
|
|
|
|
|
|
|
|
$valid_types = array_merge($valid_strings, $castable_types);
|
|
|
|
|
|
|
|
if (!$valid_types) {
|
2020-06-29 15:13:19 +02:00
|
|
|
$str_type = Type::getString();
|
|
|
|
} else {
|
2021-12-03 20:11:20 +01:00
|
|
|
$str_type = TypeCombiner::combine(
|
2020-06-29 15:13:19 +02:00
|
|
|
$valid_types,
|
|
|
|
$codebase
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-10-13 23:28:12 +02:00
|
|
|
if ($statements_analyzer->data_flow_graph) {
|
2020-06-29 15:13:19 +02:00
|
|
|
$str_type->parent_nodes = $parent_nodes;
|
2020-05-18 21:13:27 +02:00
|
|
|
}
|
|
|
|
|
2020-06-29 15:13:19 +02:00
|
|
|
return $str_type;
|
2020-05-18 21:13:27 +02:00
|
|
|
}
|
2021-06-18 00:15:45 +02:00
|
|
|
|
2022-05-09 06:40:04 +02:00
|
|
|
private static function checkExprGeneralUse(
|
|
|
|
StatementsAnalyzer $statements_analyzer,
|
|
|
|
PhpParser\Node\Expr\Cast $stmt,
|
|
|
|
Context $context
|
|
|
|
): bool {
|
|
|
|
$was_inside_general_use = $context->inside_general_use;
|
|
|
|
$context->inside_general_use = true;
|
|
|
|
$retVal = ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context);
|
|
|
|
$context->inside_general_use = $was_inside_general_use;
|
|
|
|
return $retVal;
|
|
|
|
}
|
|
|
|
|
2021-06-18 00:15:45 +02:00
|
|
|
private static function handleRedundantCast(
|
2021-12-13 16:28:14 +01:00
|
|
|
Union $maybe_type,
|
2021-06-18 00:15:45 +02:00
|
|
|
StatementsAnalyzer $statements_analyzer,
|
|
|
|
PhpParser\Node\Expr\Cast $stmt
|
|
|
|
): void {
|
|
|
|
$codebase = $statements_analyzer->getCodebase();
|
|
|
|
$project_analyzer = $statements_analyzer->getProjectAnalyzer();
|
|
|
|
|
|
|
|
$file_manipulation = null;
|
|
|
|
if ($maybe_type->from_docblock) {
|
|
|
|
$issue = new RedundantCastGivenDocblockType(
|
|
|
|
'Redundant cast to ' . $maybe_type->getKey() . ' given docblock-provided type',
|
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($codebase->alter_code
|
|
|
|
&& isset($project_analyzer->getIssuesToFix()['RedundantCastGivenDocblockType'])
|
|
|
|
) {
|
2021-12-03 20:11:20 +01:00
|
|
|
$file_manipulation = new FileManipulation(
|
2021-06-18 00:15:45 +02:00
|
|
|
(int) $stmt->getAttribute('startFilePos'),
|
|
|
|
(int) $stmt->expr->getAttribute('startFilePos'),
|
|
|
|
''
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$issue = new RedundantCast(
|
|
|
|
'Redundant cast to ' . $maybe_type->getKey(),
|
|
|
|
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($codebase->alter_code
|
|
|
|
&& isset($project_analyzer->getIssuesToFix()['RedundantCast'])
|
|
|
|
) {
|
2021-12-03 20:11:20 +01:00
|
|
|
$file_manipulation = new FileManipulation(
|
2021-06-18 00:15:45 +02:00
|
|
|
(int) $stmt->getAttribute('startFilePos'),
|
|
|
|
(int) $stmt->expr->getAttribute('startFilePos'),
|
|
|
|
''
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($file_manipulation) {
|
|
|
|
FileManipulationBuffer::add($statements_analyzer->getFilePath(), [$file_manipulation]);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (IssueBuffer::accepts($issue, $statements_analyzer->getSuppressedIssues())) {
|
|
|
|
// fall through
|
|
|
|
}
|
|
|
|
}
|
2020-05-18 21:13:27 +02:00
|
|
|
}
|