Repository: VKCOM/noverify
Pull request #1215 (2024-03-13 15:53:35)

linter: fixed unboxing from null for arrays

YuriyNasretdinov: Seems quite non-obvious to me why does UnwrapArray actually panic in this case...
Hidanio: > Seems quite non-obvious to me why does UnwrapArray actually panic in this case...

@YuriyNasretdinov  The problem in unwrap1 function:
```
const stringLenBytes = 4

func unwrap1(s string) (one string) {
	return s[stringLenBytes+1:] // do not care about length, there is only 1 param
}
```
if we have "null" string, then we will have slice from 5, but we have only 3 indexes. we can modify unwrap1 function to return empty string if len(s) > stringLenBytes+1, but we are talking about "types" and "null" is a special type that`s why i think we should add condition in logic of LazyArrayElemType. What do you think?

YuriyNasretdinov: Thanks for the explanation. I think it makes sense now. BTW I am not a maintainer of the project (but I was at some point), so I'm just being curious :).

Patch:
diff --git a/src/solver/solver.go b/src/solver/solver.go
index edb275b..db7a712 100644
--- a/src/solver/solver.go
+++ b/src/solver/solver.go
@@ -20,14 +20,16 @@ func mixedType() map[string]struct{} {
 }
 
 // resolveType resolves function calls, method calls and global variables.
-//   curStaticClass is current class name (if inside the class, otherwise "")
+//
+//	curStaticClass is current class name (if inside the class, otherwise "")
 func resolveType(info *meta.Info, curStaticClass, typ string, visitedMap ResolverMap) (result map[string]struct{}) {
 	r := resolver{info: info, visited: visitedMap}
 	return r.resolveType(curStaticClass, typ)
 }
 
 // ResolveTypes resolves function calls, method calls and global variables.
-//   curStaticClass is current class name (if inside the class, otherwise "")
+//
+//	curStaticClass is current class name (if inside the class, otherwise "")
 func ResolveTypes(info *meta.Info, curStaticClass string, m types.Map, visitedMap ResolverMap) map[string]struct{} {
 	r := resolver{info: info, visited: visitedMap}
 	return r.resolveTypes(curStaticClass, m)
@@ -76,7 +78,7 @@ func (r *resolver) resolveTypeNoLateStaticBinding(class, typ string) map[string]
 		return result
 	}
 
-	if typ == "" || typ[0] >= types.WMax {
+	if types.IsAfterWMaxed(typ) {
 		return identityType(typ)
 	}
 
diff --git a/src/types/lazy.go b/src/types/lazy.go
index 9b04133..2d81df7 100644
--- a/src/types/lazy.go
+++ b/src/types/lazy.go
@@ -291,8 +291,12 @@ func FormatSimpleType(s string) (res string) {
 	return s
 }
 
+func IsAfterWMaxed(typ string) (res bool) {
+	return typ == "" || typ[0] >= WMax
+}
+
 func FormatType(s string) (res string) {
-	if s == "" || s[0] >= WMax {
+	if IsAfterWMaxed(s) {
 		return FormatSimpleType(s)
 	}
 
diff --git a/src/types/map.go b/src/types/map.go
index efe2ee1..e7ba695 100644
--- a/src/types/map.go
+++ b/src/types/map.go
@@ -410,6 +410,11 @@ func (m Map) LazyArrayElemType() Map {
 
 	mm := make(map[string]struct{}, m.Len())
 	for typ := range m.m {
+		// TODO: https://github.com/VKCOM/noverify/issues/1227
+		if IsAfterWMaxed(typ) {
+			break
+		}
+
 		if typ == "empty_array" {
 			// If the type contains only empty_array,
 			// then we resolve its element as mixed.


Test patch:
diff --git a/src/tests/exprtype/exprtype_test.go b/src/tests/exprtype/exprtype_test.go
index 6d2df4a..0206dee 100644
--- a/src/tests/exprtype/exprtype_test.go
+++ b/src/tests/exprtype/exprtype_test.go
@@ -2201,6 +2201,45 @@ function f() {
 	runExprTypeTest(t, &exprTypeTestParams{code: code})
 }
 
+func TestArrayTypesWithUnpackPrimitives(t *testing.T) {
+	code := `<?php
+function f() {
+  // with int type
+  $a = 1;
+  $b = [...$a];
+  exprtype($b, "mixed");
+
+  // with float type
+  $a1 = 1.1;
+  $b1 = [...$a1];
+  exprtype($b1, "mixed");
+
+  // with string type
+  $a2 = "a";
+  $b2 = [...$a2];
+  exprtype($b2, "mixed");
+
+  // with bool type
+  $a3 = true;
+  $b3 = [...$a3];
+  exprtype($b3, "mixed");
+
+  // null
+  $a4 = null;
+  $b4 = [...$a4];
+  exprtype($b4, "mixed");
+
+  // class
+  class Foo {}
+
+  $a5 = new Foo();
+  $b5 = [...$a5];
+  exprtype($b5, "mixed");
+}
+`
+	runExprTypeTest(t, &exprTypeTestParams{code: code})
+}
+
 func TestPropertyTypeHints(t *testing.T) {
 	code := `<?php
 class Too {}