1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 20:34:47 +01:00

Add more type-system protections for bad array args

This commit is contained in:
Matthew Brown 2020-01-29 23:41:17 -05:00
parent 2829530ea6
commit 5eb2ebc508
15 changed files with 98 additions and 54 deletions

View File

@ -198,6 +198,10 @@ class TryAnalyzer
throw new \UnexpectedValueException('Catch var name must be a string');
}
if (!$catch->types) {
throw new \UnexpectedValueException('Very bad');
}
foreach ($catch->types as $catch_type) {
$fq_catch_class = ClassLikeAnalyzer::getFQCLNFromNameObject(
$catch_type,

View File

@ -298,6 +298,7 @@ class ArrayAnalyzer
&& $item_key_type
&& ($item_key_type->hasString() || $item_key_type->hasInt())
&& $can_create_objectlike
&& $property_types
) {
$object_like = new Type\Atomic\ObjectLike($property_types, $class_strings);
$object_like->sealed = true;

View File

@ -1744,7 +1744,7 @@ class MethodCallAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\
}
$class_template_params[$type_name][$candidate_class_storage->name] = [
$output_type_extends ?: Type::getMixed()
$output_type_extends
];
}
}

View File

@ -1996,7 +1996,7 @@ class CallAnalyzer
}
}
$template_types[$template_name][$class_storage->name] = [$output_type ?: Type::getMixed()];
$template_types[$template_name][$class_storage->name] = [$output_type];
}
}
}

View File

@ -1051,6 +1051,10 @@ class ArrayFetchAnalyzer
$valid_offsets[] = new TLiteralInt($i);
}
if (!$valid_offsets) {
throw new \UnexpectedValueException('This is weird');
}
$valid_offset_type = new Type\Union($valid_offsets);
} else {
$valid_offset_type = Type::getInt();

View File

@ -299,7 +299,7 @@ class ExpressionAnalyzer
}
}
if ($unacceptable_type) {
if ($unacceptable_type || !$acceptable_types) {
$message = 'Cannot negate a non-numeric non-string type ' . $unacceptable_type;
if ($has_valid_operand) {
if (IssueBuffer::accepts(
@ -648,7 +648,7 @@ class ExpressionAnalyzer
}
}
if ($all_permissible) {
if ($permissible_atomic_types && $all_permissible) {
$statements_analyzer->node_data->setType(
$stmt,
TypeCombination::combineTypes($permissible_atomic_types)
@ -1218,7 +1218,7 @@ class ExpressionAnalyzer
* @param string|null $self_class
* @param string|Type\Atomic\TNamedObject|Type\Atomic\TTemplateParam|null $static_class_type
*
* @return Type\Atomic|array<int, Type\Atomic>
* @return Type\Atomic|non-empty-array<int, Type\Atomic>
*/
private static function fleshOutAtomicType(
Codebase $codebase,
@ -1974,8 +1974,14 @@ class ExpressionAnalyzer
// todo: emit error here
}
$valid_types = array_merge($valid_strings, $castable_types);
if (!$valid_types) {
return Type::getString();
}
return \Psalm\Internal\Type\TypeCombination::combineTypes(
array_merge($valid_strings, $castable_types),
$valid_types,
$codebase
);
}

View File

@ -1948,6 +1948,7 @@ class StatementsAnalyzer extends SourceAnalyzer implements StatementsSource
&& $item_key_type
&& ($item_key_type->hasString() || $item_key_type->hasInt())
&& $can_create_objectlike
&& $property_types
) {
$objectlike = new Type\Atomic\ObjectLike($property_types, $class_strings);
$objectlike->sealed = true;

View File

@ -762,7 +762,7 @@ class TypeAnalyzer
}
}
if ($all_string_int_literals) {
if ($all_string_int_literals && $properties) {
$input_type_part = new ObjectLike($properties);
}
}
@ -1970,14 +1970,12 @@ class TypeAnalyzer
}
}
if ($new_input_param) {
$new_input_param = clone $new_input_param;
$new_input_param->replaceTemplateTypesWithArgTypes(
$replacement_templates
);
}
$new_input_param = clone $new_input_param;
$new_input_param->replaceTemplateTypesWithArgTypes(
$replacement_templates
);
$new_input_params[] = $new_input_param ?: Type::getMixed();
$new_input_params[] = $new_input_param;
}
}
@ -2520,7 +2518,7 @@ class TypeAnalyzer
}
}
if (count($unique_types) === 0) {
if (!$unique_types) {
throw new \UnexpectedValueException('There must be more than one unique type');
}

View File

@ -61,7 +61,11 @@ class ArrayMapReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturnTyp
}
}
return new Type\Union([new Type\Atomic\ObjectLike($array_arg_types)]);
if ($array_arg_types) {
return new Type\Union([new Type\Atomic\ObjectLike($array_arg_types)]);
}
return Type::getArray();
}
$array_arg = isset($call_args[1]->value) ? $call_args[1]->value : null;

View File

@ -39,6 +39,7 @@ class ArrayMergeReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturnT
$generic_properties = [];
$all_int_offsets = true;
$all_nonempty_lists = true;
$any_nonempty = false;
foreach ($call_args as $call_arg) {
if (!($call_arg_type = $statements_source->node_data->getType($call_arg->value))) {
@ -90,12 +91,18 @@ class ArrayMergeReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturnT
$all_nonempty_lists = false;
}
if ($unpacked_type_part->sealed) {
$any_nonempty = true;
}
$unpacked_type_part = $unpacked_type_part->getGenericArrayType();
} elseif ($unpacked_type_part instanceof Type\Atomic\TList) {
$generic_properties = null;
if (!$unpacked_type_part instanceof Type\Atomic\TNonEmptyList) {
$all_nonempty_lists = false;
} else {
$any_nonempty = true;
}
} else {
if ($unpacked_type_part instanceof Type\Atomic\TMixed
@ -122,6 +129,10 @@ class ArrayMergeReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturnT
if (!$unpacked_type_part->type_params[0]->isInt()) {
$all_int_offsets = false;
}
if ($unpacked_type_part instanceof Type\Atomic\TNonEmptyArray) {
$any_nonempty = true;
}
}
$inner_key_types = array_merge(
@ -154,7 +165,7 @@ class ArrayMergeReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturnT
$inner_value_type = TypeCombination::combineTypes($inner_value_types, $codebase, true);
if ($all_int_offsets) {
if ($all_nonempty_lists) {
if ($any_nonempty) {
return new Type\Union([
new Type\Atomic\TNonEmptyList($inner_value_type),
]);
@ -165,7 +176,18 @@ class ArrayMergeReturnTypeProvider implements \Psalm\Plugin\Hook\FunctionReturnT
]);
}
$inner_key_type = TypeCombination::combineTypes($inner_key_types, $codebase, true);
$inner_key_type = $inner_key_types
? TypeCombination::combineTypes($inner_key_types, $codebase, true)
: Type::getArrayKey();
if ($any_nonempty) {
return new Type\Union([
new Type\Atomic\TNonEmptyArray([
$inner_key_type,
$inner_value_type,
]),
]);
}
return new Type\Union([
new Type\Atomic\TArray([

View File

@ -148,7 +148,7 @@ class TypeCombination
* - and `array<string> + array<empty> = array<string>`
* - and `array + array<string> = array<mixed>`
*
* @param list<Atomic> $types
* @param non-empty-list<Atomic> $types
* @param int $literal_limit any greater number of literal types than this
* will be merged to a scalar
*
@ -176,10 +176,6 @@ class TypeCombination
return $union_type;
}
if (!$types) {
throw new \InvalidArgumentException('You must pass at least one type to combineTypes');
}
$combination = new TypeCombination();
$from_docblock = false;

View File

@ -470,6 +470,12 @@ abstract class Type
$atomic_types[] = new TNull;
}
if (!$atomic_types) {
throw new TypeParseTreeException(
'No atomic types found'
);
}
return TypeCombination::combineTypes($atomic_types);
}

View File

@ -24,7 +24,7 @@ use Psalm\Type\Union;
class ObjectLike extends \Psalm\Type\Atomic
{
/**
* @var array<string|int, Union>
* @var non-empty-array<string|int, Union>
*/
public $properties;
@ -62,7 +62,7 @@ class ObjectLike extends \Psalm\Type\Atomic
/**
* Constructs a new instance of a generic type
*
* @param array<string|int, Union> $properties
* @param non-empty-array<string|int, Union> $properties
* @param array<string, bool> $class_strings
*/
public function __construct(array $properties, array $class_strings = null)
@ -231,10 +231,6 @@ class ObjectLike extends \Psalm\Type\Atomic
}
}
if (!$value_type) {
throw new \UnexpectedValueException('$value_type should not be null here');
}
if ($this->previous_value_type) {
$value_type = Type::combineUnionTypes($this->previous_value_type, $value_type);
}
@ -268,10 +264,6 @@ class ObjectLike extends \Psalm\Type\Atomic
}
}
if (!$value_type) {
throw new \UnexpectedValueException('$value_type should not be null here');
}
$key_type = TypeCombination::combineTypes($key_types);
if ($this->previous_value_type) {

View File

@ -52,9 +52,9 @@ class Union
| self::TAINTED_SYSTEM_SECRET;
/**
* @var array<string, Atomic>
* @var non-empty-array<string, Atomic>
*/
private $types = [];
private $types;
/**
* Whether the type originated in a docblock
@ -196,19 +196,17 @@ class Union
/**
* Constructs an Union instance
*
* @param array<int, Atomic> $types
* @param non-empty-array<int, Atomic> $types
*/
public function __construct(array $types)
{
$from_docblock = false;
if (!$types) {
throw new \UnexpectedValueException('Cannot construct a union with empty types');
}
$keyed_types = [];
foreach ($types as $type) {
$key = $type->getKey();
$this->types[$key] = $type;
$keyed_types[$key] = $type;
if ($type instanceof TLiteralInt) {
$this->literal_int_types[$key] = $type;
@ -223,6 +221,8 @@ class Union
$from_docblock = $from_docblock || $type->from_docblock;
}
$this->types = $keyed_types;
$this->from_docblock = $from_docblock;
}
@ -237,7 +237,7 @@ class Union
}
/**
* @return array<string, Atomic>
* @return non-empty-array<string, Atomic>
*/
public function getAtomicTypes()
{
@ -486,15 +486,15 @@ class Union
$types = $this->types;
if (isset($types['null'])) {
if (count($types) === 1) {
return null;
}
unset($types['null']);
$nullable = true;
}
if (!$types) {
return null;
}
$atomic_type = array_values($types)[0];
$atomic_type_string = $atomic_type->toPhpString(
@ -524,11 +524,11 @@ class Union
$types = $this->types;
if (isset($types['null'])) {
unset($types['null']);
}
if (!$types) {
return false;
if (count($types) > 1) {
unset($types['null']);
} else {
return false;
}
}
$atomic_type = array_values($types)[0];
@ -1252,6 +1252,10 @@ class Union
$this->id = null;
if ($is_mixed) {
if (!$new_types) {
throw new \UnexpectedValueException('This array should be full');
}
$this->types = $new_types;
return;
@ -1271,10 +1275,14 @@ class Union
}
}
$this->types = TypeCombination::combineTypes(
array_values(array_merge($this->types, $new_types)),
$codebase
)->getAtomicTypes();
$atomic_types = array_values(array_merge($this->types, $new_types));
if ($atomic_types) {
$this->types = TypeCombination::combineTypes(
$atomic_types,
$codebase
)->getAtomicTypes();
}
}
/**

View File

@ -27,6 +27,8 @@ class TypeCombinationTest extends TestCase
$converted_types[] = $converted_type;
}
$this->assertNotEmpty($converted_types);
$this->assertSame(
$expected,
(string) TypeCombination::combineTypes($converted_types)