diff --git a/docs/checkers_doc.md b/docs/checkers_doc.md
index 5d7b1690e..1c28d347b 100644
--- a/docs/checkers_doc.md
+++ b/docs/checkers_doc.md
@@ -1252,7 +1252,7 @@ test($arr[1]);
#### Compliant code:
```php
-reported not safety call
+reported not safe call call
```
@@ -1303,7 +1303,7 @@ test(testNullable());
#### Compliant code:
```php
-reported not safety call
+reported not safe call call
```
@@ -1321,7 +1321,7 @@ test(list($a) = [null]);
#### Compliant code:
```php
-reported not safety call
+reported not safe call call
```
@@ -1346,7 +1346,7 @@ echo $user->name;
#### Compliant code:
```php
-reported not safety call
+reported not safe call call
```
@@ -1374,7 +1374,7 @@ test(A::hello());
#### Compliant code:
```php
-reported not safety call
+reported not safe call call
```
@@ -1393,7 +1393,7 @@ function f(A $klass);
#### Compliant code:
```php
-reported not safety call with null in variable.
+reported not safe call call with null in variable.
```
@@ -1413,7 +1413,7 @@ $getUserOrNull()->test();
#### Compliant code:
```php
-reported not safety function call
+reported not safe call function call
```
@@ -1438,7 +1438,7 @@ echo $user->name;
#### Compliant code:
```php
-reported not safety call
+reported not safe call call
```
@@ -1466,7 +1466,7 @@ test(A::hello());
#### Compliant code:
```php
-reported not safety static function call
+reported not safe call static function call
```
@@ -1488,7 +1488,7 @@ echo $user->name;
#### Compliant code:
```php
-reported not safety call with null in variable.
+reported not safe call call with null in variable.
```
diff --git a/src/linter/and_walker.go b/src/linter/and_walker.go
index 962c2d76a..9e0bcabac 100644
--- a/src/linter/and_walker.go
+++ b/src/linter/and_walker.go
@@ -45,6 +45,9 @@ func (a *andWalker) EnterNode(w ir.Node) (res bool) {
case *ir.ParenExpr:
return true
+ case *ir.SimpleVar:
+ a.handleVariableCondition(n)
+
case *ir.FunctionCallExpr:
// If the absence of a function or method is being
// checked, then nothing needs to be done.
@@ -72,6 +75,23 @@ func (a *andWalker) EnterNode(w ir.Node) (res bool) {
}
}
+ switch {
+ case nm.Value == `is_int`:
+ a.handleTypeCheckCondition("int", n.Args)
+ case nm.Value == `is_float`:
+ a.handleTypeCheckCondition("float", n.Args)
+ case nm.Value == `is_string`:
+ a.handleTypeCheckCondition("string", n.Args)
+ case nm.Value == `is_object`:
+ a.handleTypeCheckCondition("object", n.Args)
+ case nm.Value == `is_array`:
+ a.handleTypeCheckCondition("array", n.Args)
+ case nm.Value == `is_null`:
+ a.handleTypeCheckCondition("null", n.Args)
+ case nm.Value == `is_resource`:
+ a.handleTypeCheckCondition("resource", n.Args)
+ }
+
case *ir.BooleanAndExpr:
a.path.Push(n)
n.Left.Walk(a)
@@ -191,6 +211,14 @@ func (a *andWalker) EnterNode(w ir.Node) (res bool) {
// TODO: actually this needs to be present inside if body only
}
+ case *ir.NotIdenticalExpr:
+ a.handleConditionSafety(n.Left, n.Right, false)
+ a.handleConditionSafety(n.Right, n.Left, false)
+
+ case *ir.IdenticalExpr:
+ a.handleConditionSafety(n.Left, n.Right, true)
+ a.handleConditionSafety(n.Right, n.Left, true)
+
case *ir.BooleanNotExpr:
a.inNot = true
@@ -219,6 +247,183 @@ func (a *andWalker) EnterNode(w ir.Node) (res bool) {
return res
}
+func (a *andWalker) handleVariableCondition(variable *ir.SimpleVar) {
+ if !a.b.ctx.sc.HaveVar(variable) {
+ return
+ }
+
+ currentType := a.exprType(variable) // nolint:staticcheck
+ if a.inNot {
+ currentType = a.exprTypeInContext(a.trueContext, variable)
+ } else {
+ currentType = a.exprTypeInContext(a.falseContext, variable)
+ }
+
+ var trueType, falseType types.Map
+
+ // First, handle "null": if currentType contains "null", then in the true branch we remove it,
+ // and in the false branch we narrow to "null"
+ if currentType.Contains("null") {
+ trueType = currentType.Clone().Erase("null")
+ falseType = types.NewMap("null")
+ } else {
+ trueType = currentType.Clone()
+ falseType = currentType.Clone()
+ }
+
+ // Next, handle booleans
+ // If currentType contains any boolean-related literal ("bool", "true", "false"),
+ // then we want to narrow them:
+ // - If there are non-boolean parts (e.g. "User") in the union, they are always truthy
+ // In that case, true branch becomes nonBool ∪ {"true"} and false branch becomes {"false"}
+ // - If only the boolean part is present, then narrow to {"true"} and {"false"} respectively
+ if currentType.Contains("bool") || currentType.Contains("true") || currentType.Contains("false") {
+ nonBool := currentType.Clone().Erase("bool").Erase("true").Erase("false")
+ if nonBool.Len() > 0 {
+ if currentType.Contains("bool") || currentType.Contains("true") {
+ trueType = nonBool.Union(types.NewMap("true"))
+ } else {
+ trueType = nonBool
+ }
+ falseType = types.NewMap("false")
+ } else {
+ trueType = types.NewMap("true")
+ falseType = types.NewMap("false")
+ }
+ }
+
+ // Note: For other types (e.g. int, string, array), our type system doesn't include literal values,
+ // so we don't perform additional narrowing
+
+ // If we are in the "not" context (i.e. if(!$variable)), swap the branches
+ if a.inNot {
+ trueType, falseType = falseType, trueType
+ }
+
+ a.trueContext.sc.ReplaceVar(variable, trueType, "type narrowing for "+variable.Name, meta.VarAlwaysDefined)
+ a.falseContext.sc.ReplaceVar(variable, falseType, "type narrowing for "+variable.Name, meta.VarAlwaysDefined)
+}
+
+func (a *andWalker) handleTypeCheckCondition(expectedType string, args []ir.Node) {
+ for _, arg := range args {
+ argument, ok := arg.(*ir.Argument)
+ if !ok {
+ continue
+ }
+ variable, ok := argument.Expr.(*ir.SimpleVar)
+ if !ok {
+ continue
+ }
+
+ // Traverse the variable to ensure it exists, since this variable
+ // will be added to the context later
+ a.b.handleVariable(variable)
+
+ // Get the current type of the variable from the appropriate context
+ currentType := a.exprType(variable) // nolint:staticcheck
+ if a.inNot {
+ currentType = a.exprTypeInContext(a.trueContext, variable)
+ } else {
+ currentType = a.exprTypeInContext(a.falseContext, variable)
+ }
+
+ var trueType, falseType types.Map
+
+ switch expectedType {
+ case "bool":
+ // For bool: consider possible literal types "bool", "true" and "false"
+ boolMerge := types.MergeMaps(types.NewMap("bool"), types.NewMap("true"), types.NewMap("false"))
+ intersection := currentType.Intersect(boolMerge)
+ if intersection.Empty() {
+ // If there is no explicit bool subtype, then the positive branch becomes simply "bool"
+ trueType = types.NewMap("bool")
+ } else {
+ // Otherwise, keep exactly those literals that were present in the current type
+ trueType = intersection
+ }
+ // Negative branch: remove all bool subtypes
+ falseType = currentType.Clone().Erase("bool").Erase("true").Erase("false")
+ case "object":
+ // For is_object: keep only keys that are not considered primitive
+ keys := currentType.Keys()
+ var objectKeys []string
+ for _, k := range keys {
+ switch k {
+ case "int", "float", "string", "bool", "null", "true", "false", "mixed", "callable", "resource", "void", "iterable", "never":
+ // Skip primitive types
+ continue
+ default:
+ objectKeys = append(objectKeys, k)
+ }
+ }
+ if len(objectKeys) == 0 {
+ trueType = types.NewMap("object")
+ } else {
+ trueType = types.NewEmptyMap(1)
+ for _, k := range objectKeys {
+ trueType = trueType.Union(types.NewMap(k))
+ }
+ }
+ falseType = currentType.Clone()
+ for _, k := range objectKeys {
+ falseType = falseType.Erase(k)
+ }
+ default:
+ // Standard logic for other types
+ trueType = types.NewMap(expectedType)
+ falseType = currentType.Clone().Erase(expectedType)
+ }
+
+ if a.inNot {
+ trueType, falseType = falseType, trueType
+ }
+
+ a.trueContext.sc.ReplaceVar(variable, trueType, "type narrowing for "+expectedType, meta.VarAlwaysDefined)
+ a.falseContext.sc.ReplaceVar(variable, falseType, "type narrowing for "+expectedType, meta.VarAlwaysDefined)
+ }
+}
+
+func (a *andWalker) handleConditionSafety(left ir.Node, right ir.Node, identical bool) {
+ variable, ok := left.(*ir.SimpleVar)
+ if !ok {
+ return
+ }
+
+ constValue, ok := right.(*ir.ConstFetchExpr)
+ if !ok || (constValue.Constant.Value != "false" && constValue.Constant.Value != "null") {
+ return
+ }
+
+ // We need to traverse the variable here to check that
+ // it exists, since this variable will be added to the
+ // context later.
+ a.b.handleVariable(variable)
+
+ currentVar, isGotVar := a.b.ctx.sc.GetVar(variable)
+ if !isGotVar {
+ return
+ }
+
+ var currentType types.Map
+ if a.inNot {
+ currentType = a.exprTypeInContext(a.trueContext, variable)
+ } else {
+ currentType = a.exprTypeInContext(a.falseContext, variable)
+ }
+
+ if constValue.Constant.Value == "false" || constValue.Constant.Value == "null" {
+ clearType := currentType.Erase(constValue.Constant.Value)
+ if identical {
+ a.trueContext.sc.ReplaceVar(variable, currentType.Erase(clearType.String()), "type narrowing", currentVar.Flags)
+ a.falseContext.sc.ReplaceVar(variable, clearType, "type narrowing", currentVar.Flags)
+ } else {
+ a.trueContext.sc.ReplaceVar(variable, clearType, "type narrowing", currentVar.Flags)
+ a.falseContext.sc.ReplaceVar(variable, currentType.Erase(clearType.String()), "type narrowing", currentVar.Flags)
+ }
+ return
+ }
+}
+
func (a *andWalker) runRules(w ir.Node) {
kind := ir.GetNodeKind(w)
if a.b.r.anyRset != nil {
diff --git a/src/linter/block.go b/src/linter/block.go
index 1a623928a..ec2c899d5 100644
--- a/src/linter/block.go
+++ b/src/linter/block.go
@@ -1038,7 +1038,7 @@ func formatSlashesFuncName(fn meta.FuncInfo) string {
return strings.TrimPrefix(fn.Name, "\\")
}
-func (b *blockWalker) checkNullSafetyCallArgsF(args []ir.Node, fn meta.FuncInfo) {
+func (b *blockWalker) checknotSafeCallArgsF(args []ir.Node, fn meta.FuncInfo) {
if fn.Params == nil || fn.Name == "" {
return
}
@@ -1056,26 +1056,26 @@ func (b *blockWalker) checkNullSafetyCallArgsF(args []ir.Node, fn meta.FuncInfo)
switch a := arg.(*ir.Argument).Expr.(type) {
case *ir.SimpleVar:
- b.checkSimpleVarNullSafety(arg, fn, i, a, haveVariadic)
+ b.checkSimpleVarSafety(arg, fn, i, a, haveVariadic)
case *ir.ConstFetchExpr:
- b.checkConstFetchNullSafety(arg, fn, i, a, haveVariadic)
+ b.checkConstFetchSafety(arg, fn, i, a, haveVariadic)
case *ir.ArrayDimFetchExpr:
- b.checkArrayDimFetchNullSafety(arg, fn, i, a, haveVariadic)
+ b.checkArrayDimFetchSafety(arg, fn, i, a, haveVariadic)
case *ir.ListExpr:
- b.checkListExprNullSafety(arg, fn, i, a, haveVariadic)
+ b.checkListExprSafety(arg, fn, i, a, haveVariadic)
case *ir.PropertyFetchExpr:
- b.checkPropertyFetchNullSafety(a, fn, i, haveVariadic)
+ b.checkUnifiedPropertyFetchNotSafety(a, fn, i, haveVariadic)
case *ir.StaticCallExpr:
- b.checkStaticCallNullSafety(arg, fn, i, a, haveVariadic)
+ b.checkStaticCallSafety(arg, fn, i, a, haveVariadic)
case *ir.StaticPropertyFetchExpr:
- b.checkStaticPropertyFetchNullSafety(a, fn, i, haveVariadic)
+ b.checkUnifiedPropertyFetchNotSafety(a, fn, i, haveVariadic)
case *ir.FunctionCallExpr:
- b.checkFunctionCallNullSafety(arg, fn, i, a, haveVariadic)
+ b.checkFunctionCallSafety(arg, fn, i, a, haveVariadic)
}
}
}
-func (b *blockWalker) checkFunctionCallNullSafety(arg ir.Node, fn meta.FuncInfo, paramIndex int, funcCall *ir.FunctionCallExpr, haveVariadic bool) {
+func (b *blockWalker) checkFunctionCallSafety(arg ir.Node, fn meta.FuncInfo, paramIndex int, funcCall *ir.FunctionCallExpr, haveVariadic bool) {
var funcName string
var isClearF bool
@@ -1105,22 +1105,33 @@ func (b *blockWalker) checkFunctionCallNullSafety(arg ir.Node, fn meta.FuncInfo,
}
param := nullSafetyRealParamForCheck(fn, paramIndex, haveVariadic)
+ paramType := param.Typ
if haveVariadic && paramIndex >= len(fn.Params)-1 {
// For variadic parameter check, if type is mixed then skip.
- if types.IsTypeMixed(param.Typ) {
+ if types.IsTypeMixed(paramType) {
return
}
}
- paramAllowsNull := types.IsTypeNullable(param.Typ)
+ paramAllowsNull := types.IsTypeNullable(paramType)
varIsNullable := types.IsTypeNullable(callType)
if varIsNullable && !paramAllowsNull {
b.report(arg, LevelWarning, "notNullSafetyFunctionArgumentFunctionCall",
"not null safety call in function %s signature of param %s when calling function %s",
formatSlashesFuncName(fn), param.Name, funcInfo.Name)
}
+
+ if paramType.Empty() || varIsNullable {
+ return
+ }
+
+ if !b.isTypeCompatible(callType, param.Typ) {
+ b.report(arg, LevelWarning, "notSafeCall",
+ "potentially not safe call in function %s signature of param %s when calling function %s",
+ formatSlashesFuncName(fn), param.Name, funcInfo.Name)
+ }
}
-func (b *blockWalker) checkStaticCallNullSafety(arg ir.Node, fn meta.FuncInfo, paramIndex int, staticCallF *ir.StaticCallExpr, haveVariadic bool) {
+func (b *blockWalker) checkStaticCallSafety(arg ir.Node, fn meta.FuncInfo, paramIndex int, staticCallF *ir.StaticCallExpr, haveVariadic bool) {
funcName, ok := staticCallF.Call.(*ir.Identifier)
if !ok {
return
@@ -1149,62 +1160,173 @@ func (b *blockWalker) checkStaticCallNullSafety(arg ir.Node, fn meta.FuncInfo, p
}
param := nullSafetyRealParamForCheck(fn, paramIndex, haveVariadic)
+ paramType := param.Typ
if haveVariadic && paramIndex >= len(fn.Params)-1 {
// For variadic parameter check, if type is mixed then skip.
- if types.IsTypeMixed(param.Typ) {
+ if types.IsTypeMixed(paramType) {
return
}
}
- paramAllowsNull := types.IsTypeNullable(param.Typ)
- varIsNullable := types.IsTypeNullable(funcInfo.Typ)
+ funcType := funcInfo.Typ
+ paramAllowsNull := types.IsTypeNullable(paramType)
+ varIsNullable := types.IsTypeNullable(funcType)
if varIsNullable && !paramAllowsNull {
b.report(arg, LevelWarning, "notNullSafetyFunctionArgumentStaticFunctionCall",
"not null safety call in function %s signature of param %s when calling static function %s",
formatSlashesFuncName(fn), param.Name, funcInfo.Name)
}
+
+ if paramType.Empty() || varIsNullable {
+ return
+ }
+
+ if !b.isTypeCompatible(funcType, param.Typ) {
+ b.report(arg, LevelWarning, "notSafeCall",
+ "potentially not safe static call in function %s signature of param %s",
+ formatSlashesFuncName(fn), param.Name)
+ }
}
-func (b *blockWalker) checkSimpleVarNullSafety(arg ir.Node, fn meta.FuncInfo, paramIndex int, variable *ir.SimpleVar, haveVariadic bool) {
+func (b *blockWalker) checkSimpleVarSafety(arg ir.Node, fn meta.FuncInfo, paramIndex int, variable *ir.SimpleVar, haveVariadic bool) {
varInfo, ok := b.ctx.sc.GetVar(variable)
if !ok {
return
}
param := nullSafetyRealParamForCheck(fn, paramIndex, haveVariadic)
+ paramType := param.Typ
if haveVariadic && paramIndex >= len(fn.Params)-1 {
// For variadic parameter check, if type is mixed then skip.
- if types.IsTypeMixed(param.Typ) {
+ if types.IsTypeMixed(paramType) {
return
}
}
- paramAllowsNull := types.IsTypeNullable(param.Typ)
- varIsNullable := types.IsTypeNullable(varInfo.Type)
+ paramAllowsNull := types.IsTypeNullable(paramType)
+ varType := varInfo.Type
+ varIsNullable := types.IsTypeNullable(varType)
if varIsNullable && !paramAllowsNull {
b.report(arg, LevelWarning, "notNullSafetyFunctionArgumentVariable",
"not null safety call in function %s signature of param %s",
formatSlashesFuncName(fn), param.Name)
}
+
+ if paramType.Empty() || varIsNullable {
+ return
+ }
+
+ if !b.isTypeCompatible(varType, paramType) {
+ b.report(arg, LevelWarning, "notSafeCall",
+ "potentially not safe call in function %s signature of param %s",
+ formatSlashesFuncName(fn), param.Name)
+ }
+}
+
+func (b *blockWalker) isTypeCompatible(varType types.Map, paramType types.Map) bool {
+ if paramType.Empty() {
+ return true
+ }
+
+ var forcedVarType = types.NewMapFromMap(solver.ResolveTypes(b.r.metaInfo(), "", varType, solver.ResolverMap{}))
+
+ // Attempt to merge union types if one is a subclass/implementation of the other
+ if forcedVarType.Len() > 1 {
+ metaInfo := b.r.metaInfo()
+ forcedVarType = solver.MergeUnionTypes(metaInfo, forcedVarType)
+ }
+
+ if forcedVarType.Len() > paramType.Len() {
+ if paramType.Contains(types.WrapArrayOf("mixed")) || paramType.Contains("mixed") {
+ return true
+ }
+ return false
+ }
+
+ isVarBoolean := forcedVarType.IsBoolean()
+ isClass := forcedVarType.IsClass()
+ varClassName := forcedVarType.String()
+ isClosure := types.IsClosure(forcedVarType.String())
+
+ for _, param := range paramType.Keys() {
+ // boolean case
+ if isVarBoolean && (param == "bool" || param == "boolean") {
+ return true
+ }
+
+ if paramType.Contains(types.WrapArrayOf("mixed")) || paramType.Contains("mixed") {
+ return true
+ }
+
+ // exact match
+ if forcedVarType.Contains(param) {
+ return true
+ }
+
+ // class check
+ if isClass {
+ if param == "object" || strings.Contains(param, varClassName) {
+ return true
+ }
+ if !types.IsScalar(param) {
+ metaInfo := b.r.metaInfo()
+ if solver.Implements(metaInfo, varClassName, param) {
+ return true
+ } else if solver.ImplementsAbstract(metaInfo, varClassName, param) {
+ return true
+ }
+ }
+ }
+ }
+
+ forcedParamType := types.NewMapFromMap(solver.ResolveTypes(b.r.metaInfo(), "", paramType, solver.ResolverMap{}))
+
+ if isClosure && forcedParamType.Contains("callable") {
+ return true
+ }
+ // TODO: This is bullshit because we have no good type inferring for arrays: bool[1] will be bool[]! !not bool!
+ if strings.Contains(forcedParamType.String(), "[") {
+ idx := strings.Index(forcedParamType.String(), "[")
+ arrayType := forcedParamType.String()[:idx] //nolint:gocritic
+ return forcedParamType.Contains(arrayType)
+ } else if strings.Contains(varType.String(), "[") {
+ idx := strings.Index(varType.String(), "[")
+ arrayType := varType.String()[:idx] //nolint:gocritic
+ return forcedParamType.Contains(arrayType)
+ }
+ return !forcedParamType.Intersect(forcedVarType).Empty()
}
-func (b *blockWalker) checkConstFetchNullSafety(arg ir.Node, fn meta.FuncInfo, paramIndex int, constExpr *ir.ConstFetchExpr, haveVariadic bool) {
+func (b *blockWalker) checkConstFetchSafety(arg ir.Node, fn meta.FuncInfo, paramIndex int, constExpr *ir.ConstFetchExpr, haveVariadic bool) {
constVal := constExpr.Constant.Value
isNull := constVal == "null"
param := nullSafetyRealParamForCheck(fn, paramIndex, haveVariadic)
+ paramType := param.Typ
if haveVariadic && paramIndex >= len(fn.Params)-1 {
- if types.IsTypeMixed(param.Typ) {
+ if types.IsTypeMixed(paramType) {
return
}
}
- paramAllowsNull := types.IsTypeNullable(param.Typ)
- if isNull && !paramAllowsNull {
- b.report(arg, LevelWarning, "notNullSafetyFunctionArgumentConstFetch",
- "null passed to non-nullable parameter %s in function %s",
- param.Name, formatSlashesFuncName(fn))
+ paramAllowsNull := types.IsTypeNullable(paramType)
+ if isNull {
+ if !paramAllowsNull {
+ b.report(arg, LevelWarning, "notNullSafetyFunctionArgumentConstFetch",
+ "null passed to non-nullable parameter %s in function %s",
+ param.Name, formatSlashesFuncName(fn))
+ }
+ } else {
+ isBool := constVal == "true" || constVal == "false"
+ if isBool {
+ typ := types.NewMap(constVal)
+ if !b.isTypeCompatible(typ, paramType) {
+ b.report(arg, LevelWarning, "notSafeCall",
+ "potentially not safe access in parameter %s of function %s",
+ param.Name, formatSlashesFuncName(fn))
+ }
+ }
}
}
-func (b *blockWalker) checkArrayDimFetchNullSafety(arg ir.Node, fn meta.FuncInfo, paramIndex int, arrayExpr *ir.ArrayDimFetchExpr, haveVariadic bool) {
+func (b *blockWalker) checkArrayDimFetchSafety(arg ir.Node, fn meta.FuncInfo, paramIndex int, arrayExpr *ir.ArrayDimFetchExpr, haveVariadic bool) {
baseVar, ok := arrayExpr.Variable.(*ir.SimpleVar)
if !ok {
return
@@ -1222,107 +1344,69 @@ func (b *blockWalker) checkArrayDimFetchNullSafety(arg ir.Node, fn meta.FuncInfo
}
}
paramAllowsNull := types.IsTypeNullable(param.Typ)
- if types.IsTypeNullable(varInfo.Type) && !paramAllowsNull {
+ varType := varInfo.Type
+ varIsNullable := types.IsTypeNullable(varType)
+ if varIsNullable && !paramAllowsNull {
b.report(arg, LevelWarning, "notNullSafetyFunctionArgumentArrayDimFetch",
"potential null array access in parameter %s of function %s",
param.Name, formatSlashesFuncName(fn))
}
+
+ if param.Typ.Empty() || varIsNullable {
+ return
+ }
+
+ if !b.isTypeCompatible(varType, param.Typ) {
+ b.report(arg, LevelWarning, "notSafeCall",
+ "potentially not safe array access in parameter %s of function %s",
+ param.Name, formatSlashesFuncName(fn))
+ }
}
-func (b *blockWalker) checkListExprNullSafety(arg ir.Node, fn meta.FuncInfo, paramIndex int, listExpr *ir.ListExpr, haveVariadic bool) {
+func (b *blockWalker) checkListExprSafety(arg ir.Node, fn meta.FuncInfo, paramIndex int, listExpr *ir.ListExpr, haveVariadic bool) {
for _, item := range listExpr.Items {
if item == nil {
continue
}
if item.Key != nil {
- b.checkNullSafetyCallArgsF([]ir.Node{item.Key}, fn)
+ b.checknotSafeCallArgsF([]ir.Node{item.Key}, fn)
}
if item.Val != nil {
param := nullSafetyRealParamForCheck(fn, paramIndex, haveVariadic)
+ paramType := param.Typ
if simpleVar, ok := item.Val.(*ir.SimpleVar); ok {
varInfo, found := b.ctx.sc.GetVar(simpleVar)
- if found && types.IsTypeNullable(varInfo.Type) && !types.IsTypeNullable(param.Typ) {
- b.report(arg, LevelWarning, "notNullSafetyFunctionArgumentList",
- "potential null value in list assignment for param %s in function %s",
- param.Name, formatSlashesFuncName(fn))
+ if found {
+ varType := varInfo.Type
+ varIsNullable := types.IsTypeNullable(varType)
+ if varIsNullable && !types.IsTypeNullable(paramType) {
+ {
+ b.report(arg, LevelWarning, "notNullSafetyFunctionArgumentList",
+ "potential null value in list assignment for param %s in function %s",
+ param.Name, formatSlashesFuncName(fn))
+ }
+ }
+ if paramType.Empty() || varIsNullable {
+ return
+ }
+
+ if !b.isTypeCompatible(varType, paramType) {
+ b.report(arg, LevelWarning, "notSafeCall",
+ "potentially not safe list assignment for param %s in function %s",
+ param.Name, formatSlashesFuncName(fn))
+ }
}
}
- b.checkNullSafetyCallArgsF([]ir.Node{item.Val}, fn)
- }
- }
-}
-
-func (b *blockWalker) getPropertyComputedType(expr ir.Node) (meta.ClassInfo, types.Map) {
- var baseNode ir.Node
- var propertyNode ir.Node
-
- switch e := expr.(type) {
- case *ir.PropertyFetchExpr:
- baseNode = e.Variable
- propertyNode = e.Property
- case *ir.StaticPropertyFetchExpr:
- baseNode = e.Class
- propertyNode = e.Property
- default:
- return meta.ClassInfo{}, types.Map{}
- }
-
- var classInfo meta.ClassInfo
- var ok bool
- var varType string
-
- if baseCall, ok := baseNode.(*ir.SimpleVar); ok {
- varInfo, found := b.ctx.sc.GetVar(baseCall)
- if !found {
- return meta.ClassInfo{}, types.Map{}
+ b.checknotSafeCallArgsF([]ir.Node{item.Val}, fn)
}
- varType = varInfo.Type.String()
- } else if nameNode, ok := baseNode.(*ir.Name); ok {
- varType = "\\" + nameNode.Value
- } else {
- return meta.ClassInfo{}, types.Map{}
- }
-
- classInfo, ok = b.r.ctx.st.Info.GetClass(varType)
- if !ok {
- return meta.ClassInfo{}, types.Map{}
- }
-
- switch prop := propertyNode.(type) {
- case *ir.Identifier:
- propertyInfoFromClass := classInfo.Properties[prop.Value]
- return classInfo, propertyInfoFromClass.Typ
- case *ir.SimpleVar:
- // static: $maybeClass::$value
- propertyInfoFromClass := classInfo.Properties["$"+prop.Name]
- return classInfo, propertyInfoFromClass.Typ
- default:
- return meta.ClassInfo{}, types.Map{}
- }
-}
-
-func (b *blockWalker) checkPropertyFetchNullSafety(expr *ir.PropertyFetchExpr, fn meta.FuncInfo, paramIndex int, haveVariadic bool) {
- // Recursively check the left part of the chain if it is also a property fetch.
- if nested, ok := expr.Variable.(*ir.PropertyFetchExpr); ok {
- b.checkPropertyFetchNullSafety(nested, fn, paramIndex, haveVariadic)
}
-
- classInfo, propType := b.getPropertyComputedType(expr)
- if classInfo.Name == "" || propType.Empty() {
- return
- }
-
- prp, ok := expr.Property.(*ir.Identifier)
- if !ok {
- return
- }
-
- b.checkingPropertyFetchNullSafetyCondition(expr, propType, prp.Value, fn, paramIndex, haveVariadic)
}
-func (b *blockWalker) checkingPropertyFetchNullSafetyCondition(
+// checkingPropertyFetchSafetyCondition verifies the safety of the final property fetch
+// (the rightmost node in the chain) by checking null-safety and type compatibility
+func (b *blockWalker) checkingPropertyFetchSafetyCondition(
expr ir.Node,
propType types.Map,
prpName string,
@@ -1332,11 +1416,13 @@ func (b *blockWalker) checkingPropertyFetchNullSafetyCondition(
) {
isPrpNullable := types.IsTypeNullable(propType)
param := nullSafetyRealParamForCheck(fn, paramIndex, haveVariadic)
+ paramType := param.Typ
+
if haveVariadic && paramIndex >= len(fn.Params)-1 {
- if types.IsTypeMixed(param.Typ) {
+ if types.IsTypeMixed(paramType) {
return
}
- paramAllowsNull := types.IsTypeNullable(param.Typ)
+ paramAllowsNull := types.IsTypeNullable(paramType)
if isPrpNullable && !paramAllowsNull {
b.report(expr, LevelWarning, "notNullSafetyFunctionArgumentPropertyFetch",
"potential null dereference when accessing property '%s'", prpName)
@@ -1344,34 +1430,161 @@ func (b *blockWalker) checkingPropertyFetchNullSafetyCondition(
return
}
- paramAllowsNull := types.IsTypeNullable(param.Typ)
+ paramAllowsNull := types.IsTypeNullable(paramType)
if isPrpNullable && !paramAllowsNull {
b.report(expr, LevelWarning, "notNullSafetyFunctionArgumentPropertyFetch",
"potential null dereference when accessing property '%s'", prpName)
}
+
+ if paramType.Empty() || isPrpNullable {
+ return
+ }
+
+ if !b.isTypeCompatible(propType, paramType) {
+ b.report(expr, LevelWarning, "notSafeCall",
+ "potentially not safe accessing property '%s'", prpName)
+ }
}
-func (b *blockWalker) checkStaticPropertyFetchNullSafety(expr *ir.StaticPropertyFetchExpr, fn meta.FuncInfo, paramIndex int, haveVariadic bool) {
- // Recursively check the left part of the chain if it is also a property fetch.
- if nested, ok := expr.Class.(*ir.StaticPropertyFetchExpr); ok {
- b.checkStaticPropertyFetchNullSafety(nested, fn, paramIndex, haveVariadic)
+// collectUnifiedPropertyFetchChain collects the entire chain of property fetches (instance or static)
+// and returns a slice of nodes in the order: [rightmost property fetch, ..., base node (e.g. SimpleVar)]
+func (b *blockWalker) collectUnifiedPropertyFetchChain(expr ir.Node) []ir.Node {
+ var chain []ir.Node
+ switch e := expr.(type) {
+ case *ir.PropertyFetchExpr:
+ cur := e
+ for {
+ chain = append(chain, cur)
+ if nested, ok := cur.Variable.(*ir.PropertyFetchExpr); ok {
+ cur = nested
+ } else {
+ chain = append(chain, cur.Variable)
+ break
+ }
+ }
+ case *ir.StaticPropertyFetchExpr:
+ cur := e
+ for {
+ chain = append(chain, cur)
+ if nested, ok := cur.Class.(*ir.StaticPropertyFetchExpr); ok {
+ cur = nested
+ } else {
+ chain = append(chain, cur.Class)
+ break
+ }
+ }
}
+ return chain
+}
- classInfo, propType := b.getPropertyComputedType(expr)
- if classInfo.Name == "" || propType.Empty() {
+// checkUnifiedPropertyFetchNotSafety combines checks for instance and static property access
+// For the final (rightmost) node (the one being substituted), we check null and type safety
+// and all intermediate nodes (except the base one) must be classes
+func (b *blockWalker) checkUnifiedPropertyFetchNotSafety(expr ir.Node, fn meta.FuncInfo, paramIndex int, haveVariadic bool) {
+ chain := b.collectUnifiedPropertyFetchChain(expr)
+ if len(chain) == 0 {
return
}
- prp, ok := expr.Property.(*ir.SimpleVar)
- if !ok {
+ globalMetaInfo := b.linter.classParseState()
+
+ var finalPropType types.Map
+ var finalPropName string
+ switch node := chain[0].(type) {
+ case *ir.PropertyFetchExpr:
+ propInfo := resolvePropertyFetch(b.ctx.sc, globalMetaInfo, b.ctx.customTypes, node, b.r.strictMixed)
+ if !propInfo.isFound {
+ return
+ }
+ ip, ok := node.Property.(*ir.Identifier)
+ if !ok {
+ return
+ }
+ finalPropType = propInfo.info.Typ
+ finalPropName = ip.Value
+ case *ir.StaticPropertyFetchExpr:
+ variable, isVar := node.Property.(*ir.SimpleVar)
+ class, isClass := node.Class.(*ir.SimpleVar)
+
+ if isVar && isClass {
+ if !isClass {
+ return
+ }
+ classTyp, ok := b.ctx.sc.GetVarType(class)
+ if !ok {
+ return
+ }
+ if classTyp.Contains("null") {
+ classTyp.Erase("null")
+ }
+
+ property, found := solver.FindProperty(b.r.ctx.st.Info, classTyp.String(), "$"+variable.Name)
+ if !found {
+ return
+ }
+
+ finalPropType = property.Info.Typ
+ finalPropName = variable.Name
+ } else {
+ propInfo := resolveStaticPropertyFetch(globalMetaInfo, node)
+ if !propInfo.isFound {
+ return
+ }
+
+ finalPropType = propInfo.info.Info.Typ
+ finalPropName = propInfo.propertyName
+ }
+ default:
return
}
- b.checkingPropertyFetchNullSafetyCondition(expr, propType, prp.Name, fn, paramIndex, haveVariadic)
+ b.checkingPropertyFetchSafetyCondition(expr, finalPropType, finalPropName, fn, paramIndex, haveVariadic)
+
+ for i := 1; i < len(chain); i++ {
+ switch node := chain[i].(type) {
+ case *ir.PropertyFetchExpr:
+ propInfo := resolvePropertyFetch(b.ctx.sc, globalMetaInfo, b.ctx.customTypes, node, b.r.strictMixed)
+ if !propInfo.isFound {
+ return
+ }
+ propType := propInfo.info.Typ
+ if types.IsTypeNullable(propType) {
+ b.report(node, LevelWarning, "notSafeCall",
+ "potential null dereference when accessing property '%s'", propInfo.propertyNode.Value)
+ return
+ }
+
+ propType.Iterate(func(typ string) {
+ if types.IsTrivial(typ) {
+ b.report(node, LevelWarning, "notSafeCall",
+ "potentially not safe accessing property '%s': intermediary node is not a class", propInfo.propertyNode.Value)
+ return
+ }
+ })
+ case *ir.SimpleVar:
+ varType, ok := b.ctx.sc.GetVarType(node)
+ if !ok {
+ return
+ }
+ varType = solver.MergeUnionTypes(b.r.metaInfo(), varType)
+ if types.IsTypeNullable(varType) {
+ b.report(node, LevelWarning, "notSafeCall",
+ "potential null dereference when accessing variable '%s'", node.Name)
+ return
+ }
+ varType.Iterate(func(typ string) {
+ if types.IsTrivial(typ) {
+ b.report(node, LevelWarning, "notSafeCall",
+ "potentially not safe accessing variable '%s': intermediary node is not a class", node.Name)
+ return
+ }
+ })
+ }
+ }
}
func (b *blockWalker) handleCallArgs(args []ir.Node, fn meta.FuncInfo) {
- b.checkNullSafetyCallArgsF(args, fn)
+ b.checknotSafeCallArgsF(args, fn)
for i, arg := range args {
if i >= len(fn.Params) {
diff --git a/src/linter/block_linter.go b/src/linter/block_linter.go
index 4353bdcb8..66cd5c135 100644
--- a/src/linter/block_linter.go
+++ b/src/linter/block_linter.go
@@ -603,9 +603,8 @@ func (b *blockLinter) checkStmtExpression(s *ir.ExpressionStmt) {
parseState := b.classParseState()
left, ok := parseState.Info.GetVarType(v.Class)
- if ok && left.Contains("null") {
- b.report(s, LevelWarning, "notNullSafetyPropertyFetch",
- "potential null dereference when accessing static property")
+ if ok {
+ b.checkSafetyCall(s, left, "", "PropertyFetch")
}
}
return
@@ -1356,15 +1355,28 @@ func (b *blockLinter) checkMethodCall(e *ir.MethodCallExpr) {
switch caller := e.Variable.(type) {
case *ir.FunctionCallExpr:
+ var funcName string
+ var ok bool
+
switch fn := caller.Function.(type) {
- case *ir.SimpleVar, *ir.Name:
- checkNullSafetyWhenMethodCallChain(parseState, b, e, fn)
+ case *ir.SimpleVar:
+ funcName, ok = solver.GetFuncName(parseState, &ir.Name{Value: fn.Name})
+
+ case *ir.Name:
+ funcName, ok = solver.GetFuncName(parseState, fn)
+ }
+ if ok {
+ funInfo, found := parseState.Info.GetFunction(funcName)
+ if found {
+ funcType := funInfo.Typ
+ b.checkSafetyCall(e, funcType, funInfo.Name, "FunctionCall")
+ }
}
+
case *ir.SimpleVar:
- callerVarType, ok := parseState.Info.GetVarType(caller)
- if ok && callerVarType.Contains("null") {
- b.report(e, LevelWarning, "notNullSafetyVariable",
- "potential null dereference in $%s when accessing method", caller.Name)
+ varType, ok := b.walker.ctx.sc.GetVarType(caller)
+ if ok {
+ b.checkSafetyCall(e, varType, caller.Name, "Variable")
}
}
@@ -1385,16 +1397,37 @@ func (b *blockLinter) checkMethodCall(e *ir.MethodCallExpr) {
}
}
-func checkNullSafetyWhenMethodCallChain(parseState *meta.ClassParseState, b *blockLinter, e ir.Node, funcExpr ir.Node) {
- funcCallerName, ok := solver.GetFuncName(parseState, funcExpr)
- if !ok {
- return
+func (b *blockLinter) checkSafetyCall(e ir.Node, typ types.Map, name string, suffix string) {
+ if typ.Contains("null") {
+ reportFullName := "notNullSafety" + suffix
+ switch {
+ case reportFullName == "notNullSafetyPropertyFetch":
+ b.report(e, LevelWarning, "notNullSafety"+suffix,
+ "potential attempt to access property through null")
+ return
+ case reportFullName == "notNullSafetyVariable" || reportFullName == "notNullSafetyFunctionCall":
+ b.report(e, LevelWarning, reportFullName,
+ "potential null dereference in %s when accessing method", name)
+ return
+ }
}
- funInfo, ok := parseState.Info.GetFunction(funcCallerName)
- if ok && funInfo.Typ.Contains("null") {
- b.report(e, LevelWarning, "notNullSafetyFunctionCall",
- "potential null dereference in %s when accessing method", funInfo.Name)
+ isSafetyCall := true
+ typ.Iterate(func(typ string) {
+ // TODO: here we can problem with mixed: $xs = [0, new Foo()]; $foo = $xs[0]; <== mixed. Need fix for array elem
+ if types.IsScalar(typ) {
+ isSafetyCall = false
+ }
+ })
+
+ if !isSafetyCall {
+ if name == "" {
+ b.report(e, LevelWarning, "notSafeCall",
+ "potentially not safe call when accessing property")
+ return
+ }
+ b.report(e, LevelWarning, "notSafeCall",
+ "potentially not safe call in %s when accessing method", name)
}
}
@@ -1491,10 +1524,9 @@ func (b *blockLinter) checkPropertyFetch(e *ir.PropertyFetchExpr) {
b.report(e.Property, LevelError, "accessLevel", "Cannot access %s property %s->%s", fetch.info.AccessLevel, fetch.className, fetch.propertyNode.Value)
}
- left, ok := globalMetaInfo.Info.GetVarType(e.Variable)
- if ok && left.Contains("null") {
- b.report(e, LevelWarning, "notNullSafetyPropertyFetch",
- "attempt to access property that can be null")
+ left, ok := b.walker.ctx.sc.GetVarType(e.Variable)
+ if ok {
+ b.checkSafetyCall(e, left, "", "PropertyFetch")
}
}
diff --git a/src/linter/report.go b/src/linter/report.go
index 37bc595a1..c4c9c62fb 100644
--- a/src/linter/report.go
+++ b/src/linter/report.go
@@ -25,13 +25,29 @@ func addBuiltinCheckers(reg *CheckersRegistry) {
After: `$s = strip_tags($s, '
')`,
},
+ {
+ Name: "notSafeCall",
+ Default: true,
+ Quickfix: false,
+ Comment: "Report not safe call call",
+ Before: `/**
+ * @return User|false
+ */
+function getUser():User|false {
+ return null;
+}
+$a = getUser()->do();
+`,
+ After: `reported not safe call`,
+ },
+
{
Name: "notNullSafetyFunctionArgumentList",
Default: true,
Quickfix: false,
Comment: "Report not nullsafety call for null list",
Before: `test(list($a) = [null]);`,
- After: `reported not safety call`,
+ After: `reported not safe call call`,
},
{
@@ -49,7 +65,7 @@ function test(A $a): void {
$arr = [new A(), null];
test($arr[1]);`,
- After: `reported not safety call`,
+ After: `reported not safe call call`,
},
{
@@ -78,7 +94,7 @@ function test(string $s): void {
}
test(A::hello());`,
- After: `reported not safety call`,
+ After: `reported not safe call call`,
},
{
@@ -88,7 +104,7 @@ test(A::hello());`,
Comment: "Report not nullsafety call",
Before: `function f(A $klass);
f(null);`,
- After: `reported not safety call with null in variable.`,
+ After: `reported not safe call call with null in variable.`,
},
{
@@ -111,7 +127,7 @@ function testNullable(): ?A{
}
test(testNullable());`,
- After: `reported not safety call`,
+ After: `reported not safe call call`,
},
{
@@ -127,7 +143,7 @@ class User {
$user = new User();
$user = null;
echo $user->name;`,
- After: `reported not safety call`,
+ After: `reported not safe call call`,
},
{
@@ -143,7 +159,7 @@ class User {
$user = new User();
$user = null;
echo $user->name;`,
- After: `reported not safety call`,
+ After: `reported not safe call call`,
},
{
@@ -156,7 +172,7 @@ echo $user->name;`,
$user = null;
echo $user->name;`,
- After: `reported not safety call with null in variable.`,
+ After: `reported not safe call call with null in variable.`,
},
{
@@ -167,7 +183,7 @@ echo $user->name;`,
Before: `function getUserOrNull(): ?User { echo "test"; }
$getUserOrNull()->test();`,
- After: `reported not safety function call`,
+ After: `reported not safe call function call`,
},
{
@@ -186,7 +202,7 @@ function test(string $s): void {
}
test(A::hello());`,
- After: `reported not safety static function call`,
+ After: `reported not safe call static function call`,
},
{
diff --git a/src/linttest/linttest.go b/src/linttest/linttest.go
index fc968b52f..9dc09a3de 100644
--- a/src/linttest/linttest.go
+++ b/src/linttest/linttest.go
@@ -107,9 +107,19 @@ func newSuite(t testing.TB, ver string) *Suite {
return &Suite{
t: t,
defaultStubs: map[string]struct{}{
- `stubs/phpstorm-stubs/Core/Core.php`: {},
- `stubs/phpstorm-stubs/Core/Core_c.php`: {},
- `stubs/phpstorm-stubs/Core/Core_d.php`: {},
+ `stubs/phpstorm-stubs/Core/Core.php`: {},
+ `stubs/phpstorm-stubs/Core/Core_c.php`: {},
+ `stubs/phpstorm-stubs/Core/Core_d.php`: {},
+ `stubs/phpstorm-stubs/standard/standard_0.php`: {},
+ `stubs/phpstorm-stubs/standard/standard_1.php`: {},
+ `stubs/phpstorm-stubs/standard/standard_2.php`: {},
+ `stubs/phpstorm-stubs/standard/standard_3.php`: {},
+ `stubs/phpstorm-stubs/standard/standard_4.php`: {},
+ `stubs/phpstorm-stubs/standard/standard_5.php`: {},
+ `stubs/phpstorm-stubs/standard/standard_6.php`: {},
+ `stubs/phpstorm-stubs/standard/standard_7.php`: {},
+ `stubs/phpstorm-stubs/standard/standard_8.php`: {},
+ `stubs/phpstorm-stubs/standard/standard_9.php`: {},
},
ignoreUndeclaredChecks: false,
config: conf,
diff --git a/src/solver/oop.go b/src/solver/oop.go
index 5f6782c24..765e0a7a6 100644
--- a/src/solver/oop.go
+++ b/src/solver/oop.go
@@ -60,6 +60,24 @@ func GetClassName(cs *meta.ClassParseState, classNode ir.Node) (className string
className = nm.Value
firstPart, restParts = nm.HeadTail()
partsCount = nm.NumParts()
+
+ // TODO: here we have bug with data-race. uncomment it and run e2e phprocksyd
+ /* case *ir.SimpleVar:
+ varTyp, ok := cs.Info.GetVarType(nm)
+ varTyp = MergeUnionTypes(cs.Info, varTyp)
+ if varTyp.Contains("null") {
+ varTyp.Erase("null")
+ }
+ if !varTyp.IsClass() {
+ return "", false
+ }
+
+ if !ok || varTyp.Len() > 1 || varTyp.Empty() {
+ return "", false
+ }
+
+ return varTyp.String(), true
+ */
default:
return "", false
}
diff --git a/src/solver/solver.go b/src/solver/solver.go
index db7a712d5..c422b735e 100644
--- a/src/solver/solver.go
+++ b/src/solver/solver.go
@@ -490,6 +490,46 @@ func Implements(info *meta.Info, className, interfaceName string) bool {
return implements(info, className, interfaceName, visited)
}
+func ImplementsAbstract(info *meta.Info, className, abstractName string) bool {
+ classInfo, got := info.GetClass(className)
+ if !got {
+ return false
+ }
+ if classInfo.Parent != "" && strings.Contains(abstractName, classInfo.Parent) {
+ return true
+ }
+
+ return false
+}
+
+// IsMoreSpecific returns true if type key1 is more specific than key2.
+// That is, key1 extends/implements key2 (or in abstract sense as well)
+func IsMoreSpecific(metaInfo *meta.Info, key1, key2 string) bool {
+ return Implements(metaInfo, key1, key2) ||
+ ImplementsAbstract(metaInfo, key1, key2)
+}
+
+// MergeUnionTypes iteratively merges union types in the Map using specific-ness
+func MergeUnionTypes(metaInfo *meta.Info, m types.Map) types.Map {
+ // Iteratively try to merge the union until no more changes occur
+ // Always re-read keys after modifications
+ keys := m.Keys()
+ for i := 0; i < len(keys); i++ {
+ for j := i + 1; j < len(keys); j++ {
+ key1 := keys[i]
+ key2 := keys[j]
+ if IsMoreSpecific(metaInfo, key1, key2) {
+ m = m.Erase(key2)
+ return m
+ } else if IsMoreSpecific(metaInfo, key2, key1) {
+ m = m.Erase(key1)
+ return m
+ }
+ }
+ }
+ return m
+}
+
func implements(info *meta.Info, className, interfaceName string, visited map[string]struct{}) bool {
if className == interfaceName {
return true
diff --git a/src/tests/checkers/basic_test.go b/src/tests/checkers/basic_test.go
index c204111de..3d457bcc3 100644
--- a/src/tests/checkers/basic_test.go
+++ b/src/tests/checkers/basic_test.go
@@ -2147,6 +2147,7 @@ function f() {
`Use float cast instead of real`,
`Use is_float function instead of is_real`,
`Use is_float instead of 'is_real`,
+ `Call to deprecated function is_real (since: 7.4)`,
}
test.RunAndMatch()
}
diff --git a/src/tests/checkers/not_safety_test.go b/src/tests/checkers/not_safety_test.go
new file mode 100644
index 000000000..8135dade1
--- /dev/null
+++ b/src/tests/checkers/not_safety_test.go
@@ -0,0 +1,533 @@
+package checkers
+
+import (
+ "testing"
+
+ "github.com/VKCOM/noverify/src/linttest"
+)
+
+func TestFunctionPassingFalse_SimpleVar(t *testing.T) {
+ test := linttest.NewSuite(t)
+ test.AddFile(`do();
+}
+`)
+ test.Expect = []string{
+ "Missing PHPDoc for \\User::do public method",
+ }
+ test.RunAndMatch()
+}
+
+func TestNotEqualFalseElseCondition(t *testing.T) {
+ test := linttest.NewSuite(t)
+ test.AddFile(`do();
+} else{
+$с = $b->do();
+}
+`)
+ test.Expect = []string{
+ "Missing PHPDoc for \\User::do public method",
+ "Call to undefined method",
+ "potentially not safe call in b when accessing method",
+ }
+ test.RunAndMatch()
+}
+
+func TestAssignFalseMethodCall(t *testing.T) {
+ test := linttest.NewSuite(t)
+ test.AddFile(`do();
+
+`)
+ test.Expect = []string{
+ "Missing PHPDoc for \\User::do public method",
+ "potentially not safe call in \\getUser when accessing method",
+ }
+ test.RunAndMatch()
+}
+
+func TestFalseParamInFunc(t *testing.T) {
+ test := linttest.NewSuite(t)
+ test.AddFile(`value;
+}
+
+$a = new A();
+test($a->b);
+`)
+ test.Expect = []string{
+ "potentially not safe accessing property 'b'",
+ }
+ test.RunAndMatch()
+}
+
+func TestIsCondition(t *testing.T) {
+ test := linttest.NewSuite(t)
+ test.AddFile(`name;
+} else{
+$b = $x->name;
+}
+`)
+ test.Expect = []string{
+ "Property {int}->name does not exist",
+ "potentially not safe call when accessing property",
+ }
+ test.RunAndMatch()
+}
+
+func TestIsObjectCondition(t *testing.T) {
+ test := linttest.NewSuite(t)
+ test.AddFile(`name;
+} else{
+$b = $x->name;
+}
+`)
+ test.Expect = []string{
+ "Property {int}->name does not exist",
+ "potentially not safe call when accessing property",
+ }
+ test.RunAndMatch()
+}
+
+func TestInheritDoc(t *testing.T) {
+ test := linttest.NewSuite(t)
+ test.AddFile(`getAdapter()->has($path);
+ }
+
+ /**
+ * Assert a file is present.
+ *
+ * @param string $path path to file
+ *
+ * @throws FileNotFoundException
+ *
+ * @return void
+ */
+ public function assertPresent($path)
+ {
+ if ($this->config->get('disable_asserts', false) === false && ! $this->has($path)) {
+ }
+ }
+ }
+`)
+ test.Expect = []string{
+ "Call to undefined method {\\Filesystem}->getAdapter()",
+ "Property {\\Filesystem}->config does not exist",
+ }
+ test.RunAndMatch()
+}
+
+func TestForceInferring(t *testing.T) {
+ test := linttest.NewSuite(t)
+ test.AddFile(`strictCallables ? self::STRICT_IS_CALLABLE : self::IS_CALLABLE;
+
+ return sprintf($tpl, $variable, $variable);
+ }
+}
+`)
+ test.Expect = []string{
+ "Property {\\User}->strictCallables does not exist",
+ }
+ test.RunAndMatch()
+}
+
+func TestVariableCondition(t *testing.T) {
+ test := linttest.NewSuite(t)
+ test.AddFile(`name;
+} else {
+ echo "User not found." . $user->name;
+}
+`)
+ test.Expect = []string{
+ "potentially not safe call when accessing property",
+ "Property {false}->name does not exist",
+ "potentially not safe call when accessing property",
+ }
+ test.RunAndMatch()
+}
+
+func TestVariableNotCondition(t *testing.T) {
+ test := linttest.NewSuite(t)
+ test.AddFile(`name;
+} else {
+ echo "User not found." . $user->name;
+}
+`)
+ test.Expect = []string{
+ "potentially not safe call when accessing property",
+ "Property {false}->name does not exist",
+ "potentially not safe call when accessing property",
+ }
+ test.RunAndMatch()
+}
+
+func TestSelfNewInstanceHandlerWithAbstract(t *testing.T) {
+ test := linttest.NewSuite(t)
+ test.AddFile(`type = $type;
+ $this->handler = $handler;
+ }
+
+ // Factory method for creating a task with a handler.
+ public static function newWithHandler(string $type, TaskHandler $handler): self {
+ return new self($type, $handler);
+ }
+}
+
+// Concrete task handler extending the abstract TaskHandler.
+class ConcreteTaskHandler extends TaskHandler {
+ // Factory method for creating an instance of ConcreteTaskHandler.
+ public static function create(): self {
+ return new self();
+ }
+}
+
+$handler = ConcreteTaskHandler::create();
+$task = Task::newWithHandler("example_task", $handler);
+
+`)
+ test.Expect = []string{
+ "Missing PHPDoc for \\Task::newWithHandler public method",
+ "Missing PHPDoc for \\ConcreteTaskHandler::create public method",
+ }
+ test.RunAndMatch()
+}
diff --git a/src/tests/checkers/null_safety_test.go b/src/tests/checkers/null_safety_test.go
index 1c4a2c402..d3dce824a 100644
--- a/src/tests/checkers/null_safety_test.go
+++ b/src/tests/checkers/null_safety_test.go
@@ -156,7 +156,6 @@ testVariadic(new A(), null);
test.RunAndMatch()
}
-// TODO: After realisation Control Flow Graph (CFG) и Data Flow Graph (DFG) this test must fail
func TestIfNullCheckSafe(t *testing.T) {
test := linttest.NewSuite(t)
test.AddFile(`name;
`)
test.Expect = []string{
- "attempt to access property that can be null",
+ "potential attempt to access property through null",
}
test.RunAndMatch()
}
@@ -366,7 +368,29 @@ test(A::hello());
test.RunAndMatch()
}
-func TestStaticCallNullSafetyThrowVariable(t *testing.T) {
+func TestFuncCallNullSafety(t *testing.T) {
+ test := linttest.NewSuite(t)
+ test.AddFile(`name;
+} else {
+ echo "User not found." . $user->name;
+}
+`)
+ test.Expect = []string{
+ "potential attempt to access property through null",
+ }
+ test.RunAndMatch()
+}
+
+func TestVariableInConditionNullSafety(t *testing.T) {
+ test := linttest.NewSuite(t)
+ test.AddFile(`name;
+} else {
+ echo "User not found." . $user->name;
+}
+`)
+ test.Expect = []string{
+ "potential attempt to access property through null",
+ }
+ test.RunAndMatch()
+}
+
+func TestPropertyFetchMiddleChainNullSafety(t *testing.T) {
+ test := linttest.NewSuite(t)
+ test.AddFile(`id = $id;
+ }
+}
+
+class MyTask {
+ public ?ParentTask $parent_task;
+
+ public function __construct(ParentTask $parent_task) {
+ $this->parent_task = $parent_task;
+ }
+
+ public function execute(): void {
+ processTask($this->parent_task->id);
+ }
+}
+
+function processTask(int $taskId): void {
+ echo "Processing task with ID: " . $taskId . "\n";
+}
+`)
+ test.Expect = []string{
+ "potential null dereference when accessing property 'parent_task'",
+ `Missing PHPDoc for \MyTask::execute public method`,
+ }
+ test.RunAndMatch()
+}
diff --git a/src/tests/checkers/oop_test.go b/src/tests/checkers/oop_test.go
index 7e6a49888..0856828a8 100644
--- a/src/tests/checkers/oop_test.go
+++ b/src/tests/checkers/oop_test.go
@@ -289,6 +289,7 @@ $_ = WithProps::$int;
test.Expect = []string{
`Class constant \WithProps::int does not exist`,
`Property \WithProps::$int does not exist`,
+ `potential attempt to access property through null`,
}
test.RunAndMatch()
@@ -347,6 +348,8 @@ $_ = WithProps::$int1;
`Class constant \WithProps::int1 does not exist`,
`Property \WithProps::$int does not exist`,
`Property \WithProps::$int1 does not exist`,
+ `potential attempt to access property through null`,
+ `potential attempt to access property through null`,
}
test.RunAndMatch()
@@ -1111,6 +1114,8 @@ function test3(?A $instance) {
`)
test.Expect = []string{
`Property {\A|null}->c does not exist`,
+ `potential attempt to access property through null`,
+ `potential attempt to access property through null`,
}
test.RunAndMatch()
}
diff --git a/src/tests/checkers/php_aliases_test.go b/src/tests/checkers/php_aliases_test.go
index 6fba1a8b9..7c4124f19 100644
--- a/src/tests/checkers/php_aliases_test.go
+++ b/src/tests/checkers/php_aliases_test.go
@@ -13,7 +13,6 @@ declare(strict_types = "1")
$_ = join("", []);
`)
test.Expect = []string{
- `Call to undefined function join`,
`Use implode instead of 'join'`,
}
test.RunAndMatch()
@@ -35,9 +34,7 @@ test(join("", []));
`Use OCICollection::max instead of 'ocicollmax'`,
`Call to undefined function ocicollmax`,
`Use implode instead of 'join'`,
- `Call to undefined function join`,
`Use implode instead of 'join'`,
- `Call to undefined function join`,
}
test.RunAndMatch()
}
diff --git a/src/tests/checkers/strict_mixed_test.go b/src/tests/checkers/strict_mixed_test.go
index 7c4f094e6..d2a45015a 100644
--- a/src/tests/checkers/strict_mixed_test.go
+++ b/src/tests/checkers/strict_mixed_test.go
@@ -72,6 +72,10 @@ function f(stdClass|null $a) {
"Call to undefined method {mixed|null}->f()",
"Call to undefined method {mixed|null}->f()",
"Call to undefined method {\\stdClass|null}->f()",
+ "potential null dereference in a when accessing method",
+ "potential null dereference in a when accessing method",
+ "potential null dereference in a when accessing method",
+ "potential null dereference in a when accessing method",
}
test.RunAndMatch()
}
@@ -131,6 +135,10 @@ function f(stdClass|null $a) {
test.Expect = []string{
"Cannot find referenced variable $a",
"Call to undefined method {\\Foo}->f()",
+ "potential null dereference in a when accessing method",
+ "potential null dereference in a when accessing method",
+ "potential null dereference in a when accessing method",
+ "potential null dereference in a when accessing method",
}
test.RunAndMatch()
}
diff --git a/src/tests/golden/testdata/ctype/golden.txt b/src/tests/golden/testdata/ctype/golden.txt
index a36379e24..13274269e 100644
--- a/src/tests/golden/testdata/ctype/golden.txt
+++ b/src/tests/golden/testdata/ctype/golden.txt
@@ -1,18 +1,54 @@
+WARNING notSafeCall: potentially not safe call in function preg_match signature of param subject at testdata/ctype/ctype.php:36
+ return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text);
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function preg_match signature of param subject at testdata/ctype/ctype.php:52
+ return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text);
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function preg_match signature of param subject at testdata/ctype/ctype.php:68
+ return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text);
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function preg_match signature of param subject at testdata/ctype/ctype.php:84
+ return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text);
+ ^^^^^
MAYBE regexpSimplify: May re-write '/[^0-9]/' as '/\D/' at testdata/ctype/ctype.php:84
return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text);
^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function preg_match signature of param subject at testdata/ctype/ctype.php:100
+ return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text);
+ ^^^^^
WARNING regexpVet: suspicious char range '!-~' in [^!-~] at testdata/ctype/ctype.php:100
return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text);
^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function preg_match signature of param subject at testdata/ctype/ctype.php:116
+ return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text);
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function preg_match signature of param subject at testdata/ctype/ctype.php:132
+ return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text);
+ ^^^^^
WARNING regexpVet: suspicious char range ' -~' in [^ -~] at testdata/ctype/ctype.php:132
return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text);
^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function preg_match signature of param subject at testdata/ctype/ctype.php:148
+ return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text);
+ ^^^^^
MAYBE regexpSimplify: May re-write '/[^!-\/\:-@\[-`\{-~]/' as '/[^!-\/:-@\[-`\{-~]/' at testdata/ctype/ctype.php:148
return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text);
^^^^^^^^^^^^^^^^^^^^^^^
WARNING regexpVet: suspicious char range '!-\/' in [^!-\/\:-@\[-`\{-~] at testdata/ctype/ctype.php:148
return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text);
^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function preg_match signature of param subject at testdata/ctype/ctype.php:164
+ return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text);
+ ^^^^^
MAYBE regexpSimplify: May re-write '/[^\s]/' as '/\S/' at testdata/ctype/ctype.php:164
return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text);
^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function preg_match signature of param subject at testdata/ctype/ctype.php:180
+ return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text);
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function preg_match signature of param subject at testdata/ctype/ctype.php:196
+ return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text);
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function chr signature of param codepoint at testdata/ctype/ctype.php:225
+ return \chr($int);
+ ^^^^
diff --git a/src/tests/golden/testdata/embeddedrules/golden.txt b/src/tests/golden/testdata/embeddedrules/golden.txt
index f3e3719c8..ea5cbcd84 100644
--- a/src/tests/golden/testdata/embeddedrules/golden.txt
+++ b/src/tests/golden/testdata/embeddedrules/golden.txt
@@ -109,51 +109,99 @@ $_ = $b[0]{0};
WARNING intNeedle: Since PHP 7.3, passing the int parameter needle to string search functions has been deprecated, cast it explicitly to string or wrap it in a chr() function call at testdata/embeddedrules/intNeedle.php:21
$_ = strpos($str, 10);
^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function strpos signature of param needle when calling function \getInt at testdata/embeddedrules/intNeedle.php:22
+$_ = strpos($str, getInt());
+ ^^^^^^^^
WARNING intNeedle: Since PHP 7.3, passing the int parameter needle to string search functions has been deprecated, cast it explicitly to string or wrap it in a chr() function call at testdata/embeddedrules/intNeedle.php:22
$_ = strpos($str, getInt());
^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function strpos signature of param needle when calling function \getIntOrString at testdata/embeddedrules/intNeedle.php:23
+$_ = strpos($str, getIntOrString(true)); // ok
+ ^^^^^^^^^^^^^^^^^^^^
WARNING intNeedle: Since PHP 7.3, passing the int parameter needle to string search functions has been deprecated, cast it explicitly to string or wrap it in a chr() function call at testdata/embeddedrules/intNeedle.php:27
$_ = strrpos($str, 10);
^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function strrpos signature of param needle when calling function \getInt at testdata/embeddedrules/intNeedle.php:28
+$_ = strrpos($str, getInt());
+ ^^^^^^^^
WARNING intNeedle: Since PHP 7.3, passing the int parameter needle to string search functions has been deprecated, cast it explicitly to string or wrap it in a chr() function call at testdata/embeddedrules/intNeedle.php:28
$_ = strrpos($str, getInt());
^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function strrpos signature of param needle when calling function \getIntOrString at testdata/embeddedrules/intNeedle.php:29
+$_ = strrpos($str, getIntOrString(true)); // ok
+ ^^^^^^^^^^^^^^^^^^^^
WARNING intNeedle: Since PHP 7.3, passing the int parameter needle to string search functions has been deprecated, cast it explicitly to string or wrap it in a chr() function call at testdata/embeddedrules/intNeedle.php:33
$_ = stripos($str, 10);
^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function stripos signature of param needle when calling function \getInt at testdata/embeddedrules/intNeedle.php:34
+$_ = stripos($str, getInt());
+ ^^^^^^^^
WARNING intNeedle: Since PHP 7.3, passing the int parameter needle to string search functions has been deprecated, cast it explicitly to string or wrap it in a chr() function call at testdata/embeddedrules/intNeedle.php:34
$_ = stripos($str, getInt());
^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function stripos signature of param needle when calling function \getIntOrString at testdata/embeddedrules/intNeedle.php:35
+$_ = stripos($str, getIntOrString(true)); // ok
+ ^^^^^^^^^^^^^^^^^^^^
WARNING intNeedle: Since PHP 7.3, passing the int parameter needle to string search functions has been deprecated, cast it explicitly to string or wrap it in a chr() function call at testdata/embeddedrules/intNeedle.php:39
$_ = strripos($str, 10);
^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function strripos signature of param needle when calling function \getInt at testdata/embeddedrules/intNeedle.php:40
+$_ = strripos($str, getInt());
+ ^^^^^^^^
WARNING intNeedle: Since PHP 7.3, passing the int parameter needle to string search functions has been deprecated, cast it explicitly to string or wrap it in a chr() function call at testdata/embeddedrules/intNeedle.php:40
$_ = strripos($str, getInt());
^^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function strripos signature of param needle when calling function \getIntOrString at testdata/embeddedrules/intNeedle.php:41
+$_ = strripos($str, getIntOrString(true)); // ok
+ ^^^^^^^^^^^^^^^^^^^^
WARNING intNeedle: Since PHP 7.3, passing the int parameter needle to string search functions has been deprecated, cast it explicitly to string or wrap it in a chr() function call at testdata/embeddedrules/intNeedle.php:45
$_ = strstr($str, 10);
^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function strstr signature of param needle when calling function \getInt at testdata/embeddedrules/intNeedle.php:46
+$_ = strstr($str, getInt());
+ ^^^^^^^^
WARNING intNeedle: Since PHP 7.3, passing the int parameter needle to string search functions has been deprecated, cast it explicitly to string or wrap it in a chr() function call at testdata/embeddedrules/intNeedle.php:46
$_ = strstr($str, getInt());
^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function strstr signature of param needle when calling function \getIntOrString at testdata/embeddedrules/intNeedle.php:47
+$_ = strstr($str, getIntOrString(true)); // ok
+ ^^^^^^^^^^^^^^^^^^^^
WARNING intNeedle: Since PHP 7.3, passing the int parameter needle to string search functions has been deprecated, cast it explicitly to string or wrap it in a chr() function call at testdata/embeddedrules/intNeedle.php:51
$_ = strchr($str, 10);
^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function strchr signature of param needle when calling function \getInt at testdata/embeddedrules/intNeedle.php:52
+$_ = strchr($str, getInt());
+ ^^^^^^^^
WARNING intNeedle: Since PHP 7.3, passing the int parameter needle to string search functions has been deprecated, cast it explicitly to string or wrap it in a chr() function call at testdata/embeddedrules/intNeedle.php:52
$_ = strchr($str, getInt());
^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function strchr signature of param needle when calling function \getIntOrString at testdata/embeddedrules/intNeedle.php:53
+$_ = strchr($str, getIntOrString(true)); // ok
+ ^^^^^^^^^^^^^^^^^^^^
WARNING intNeedle: Since PHP 7.3, passing the int parameter needle to string search functions has been deprecated, cast it explicitly to string or wrap it in a chr() function call at testdata/embeddedrules/intNeedle.php:57
$_ = strrchr($str, 10);
^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function strrchr signature of param needle when calling function \getInt at testdata/embeddedrules/intNeedle.php:58
+$_ = strrchr($str, getInt());
+ ^^^^^^^^
WARNING intNeedle: Since PHP 7.3, passing the int parameter needle to string search functions has been deprecated, cast it explicitly to string or wrap it in a chr() function call at testdata/embeddedrules/intNeedle.php:58
$_ = strrchr($str, getInt());
^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function strrchr signature of param needle when calling function \getIntOrString at testdata/embeddedrules/intNeedle.php:59
+$_ = strrchr($str, getIntOrString(true)); // ok
+ ^^^^^^^^^^^^^^^^^^^^
WARNING intNeedle: Since PHP 7.3, passing the int parameter needle to string search functions has been deprecated, cast it explicitly to string or wrap it in a chr() function call at testdata/embeddedrules/intNeedle.php:63
$_ = stristr($str, 10);
^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function stristr signature of param needle when calling function \getInt at testdata/embeddedrules/intNeedle.php:64
+$_ = stristr($str, getInt());
+ ^^^^^^^^
WARNING intNeedle: Since PHP 7.3, passing the int parameter needle to string search functions has been deprecated, cast it explicitly to string or wrap it in a chr() function call at testdata/embeddedrules/intNeedle.php:64
$_ = stristr($str, getInt());
^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function stristr signature of param needle when calling function \getIntOrString at testdata/embeddedrules/intNeedle.php:65
+$_ = stristr($str, getIntOrString(true)); // ok
+ ^^^^^^^^^^^^^^^^^^^^
WARNING langDeprecated: Since PHP 7.3, the definition of case insensitive constants has been deprecated at testdata/embeddedrules/langDeprecated.php:3
define("Z_CONST", 1, true);
^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/src/tests/golden/testdata/flysystem/golden.txt b/src/tests/golden/testdata/flysystem/golden.txt
index 5baee7328..7ca6977a2 100644
--- a/src/tests/golden/testdata/flysystem/golden.txt
+++ b/src/tests/golden/testdata/flysystem/golden.txt
@@ -1,3 +1,6 @@
+WARNING notSafeCall: potentially not safe call in function ucfirst signature of param string at testdata/flysystem/src/Adapter/AbstractFtpAdapter.php:111
+ $method = 'set' . ucfirst($setting);
+ ^^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function preg_match signature of param subject at testdata/flysystem/src/Adapter/AbstractFtpAdapter.php:358
if (preg_match('#^.*:$#', $item)) {
^^^^^
@@ -31,6 +34,9 @@ MAYBE regexpSimplify: May re-write '/^[0-9]{2,4}-[0-9]{2}-[0-9]{2}/' as '/^\d{
MAYBE callSimplify: Could simplify to $permissions[0] at testdata/flysystem/src/Adapter/AbstractFtpAdapter.php:548
return substr($permissions, 0, 1) === 'd' ? 'dir' : 'file';
^^^^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function strtr signature of param str at testdata/flysystem/src/Adapter/AbstractFtpAdapter.php:569
+ $permissions = strtr($permissions, $map);
+ ^^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function str_split signature of param string at testdata/flysystem/src/Adapter/AbstractFtpAdapter.php:576
return array_sum(str_split($part));
^^^^^
@@ -61,6 +67,12 @@ WARNING notNullSafetyFunctionArgumentPropertyFetch: potential null dereference w
WARNING notNullSafetyFunctionArgumentPropertyFetch: potential null dereference when accessing property 'connection' at testdata/flysystem/src/Adapter/Ftp.php:176
if ( ! ftp_pasv($this->connection, $this->passive)) {
^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function ftp_chdir signature of param ftp at testdata/flysystem/src/Adapter/Ftp.php:191
+ if ($root && ! ftp_chdir($connection, $root)) {
+ ^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function ftp_pwd signature of param ftp at testdata/flysystem/src/Adapter/Ftp.php:199
+ $this->root = ftp_pwd($connection);
+ ^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentPropertyFetch: potential null dereference when accessing property 'connection' at testdata/flysystem/src/Adapter/Ftp.php:212
$this->connection,
^^^^^^^^^^^^^^^^^
@@ -70,9 +82,18 @@ WARNING errorSilence: Don't use @, silencing errors is bad practice at testdata/
WARNING notNullSafetyFunctionArgumentPropertyFetch: potential null dereference when accessing property 'connection' at testdata/flysystem/src/Adapter/Ftp.php:233
@ftp_close($this->connection);
^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function fwrite signature of param stream at testdata/flysystem/src/Adapter/Ftp.php:245
+ fwrite($stream, $contents);
+ ^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function fwrite signature of param data at testdata/flysystem/src/Adapter/Ftp.php:245
fwrite($stream, $contents);
^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function rewind signature of param stream at testdata/flysystem/src/Adapter/Ftp.php:246
+ rewind($stream);
+ ^^^^^^^
+WARNING notSafeCall: potentially not safe call in function fclose signature of param stream at testdata/flysystem/src/Adapter/Ftp.php:248
+ fclose($stream);
+ ^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function guessMimeType signature of param path at testdata/flysystem/src/Adapter/Ftp.php:255
$result['mimetype'] = $config->get('mimetype') ?: Util::guessMimeType($path, $contents);
^^^^^
@@ -103,15 +124,27 @@ WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function listDirectoryContents signature of param directory at testdata/flysystem/src/Adapter/Ftp.php:318
$contents = array_reverse($this->listDirectoryContents($dirname, false));
^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function ftp_delete signature of param ftp at testdata/flysystem/src/Adapter/Ftp.php:322
+ if ( ! ftp_delete($connection, $object['path'])) {
+ ^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentArrayDimFetch: potential null array access in parameter filename of function ftp_delete at testdata/flysystem/src/Adapter/Ftp.php:322
if ( ! ftp_delete($connection, $object['path'])) {
^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function ftp_rmdir signature of param ftp at testdata/flysystem/src/Adapter/Ftp.php:330
+ return ftp_rmdir($connection, $dirname);
+ ^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function ftp_rmdir signature of param directory at testdata/flysystem/src/Adapter/Ftp.php:330
return ftp_rmdir($connection, $dirname);
^^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function explode signature of param string at testdata/flysystem/src/Adapter/Ftp.php:339
$directories = explode('/', $dirname);
^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function createActualDirectory signature of param connection at testdata/flysystem/src/Adapter/Ftp.php:342
+ if (false === $this->createActualDirectory($directory, $connection)) {
+ ^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function ftp_chdir signature of param ftp at testdata/flysystem/src/Adapter/Ftp.php:348
+ ftp_chdir($connection, $directory);
+ ^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function preg_match signature of param subject at testdata/flysystem/src/Adapter/Ftp.php:370
if (preg_match('~^\./.*~', $item)) {
^^^^^
@@ -124,6 +157,12 @@ WARNING errorSilence: Don't use @, silencing errors is bad practice at testdata/
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function ftp_chdir signature of param directory at testdata/flysystem/src/Adapter/Ftp.php:391
if (@ftp_chdir($this->getConnection(), $path) === true) {
^^^^^
+WARNING notSafeCall: potentially not safe array access in parameter subject of function preg_match at testdata/flysystem/src/Adapter/Ftp.php:403
+ if (preg_match('/.* not found/', $listing[0])) {
+ ^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe array access in parameter subject of function preg_match at testdata/flysystem/src/Adapter/Ftp.php:407
+ if (preg_match('/^total [0-9]*$/', $listing[0])) {
+ ^^^^^^^^^^^
MAYBE regexpSimplify: May re-write '/^total [0-9]*$/' as '/^total \d*$/' at testdata/flysystem/src/Adapter/Ftp.php:407
if (preg_match('/^total [0-9]*$/', $listing[0])) {
^^^^^^^^^^^^^^^^^^
@@ -136,18 +175,45 @@ WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function ftp_mdtm signature of param filename at testdata/flysystem/src/Adapter/Ftp.php:433
$timestamp = ftp_mdtm($this->getConnection(), $path);
^^^^^
+WARNING notSafeCall: potentially not safe array access in parameter stream of function stream_get_contents at testdata/flysystem/src/Adapter/Ftp.php:447
+ $object['contents'] = stream_get_contents($object['stream']);
+ ^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe array access in parameter stream of function fclose at testdata/flysystem/src/Adapter/Ftp.php:448
+ fclose($object['stream']);
+ ^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function ftp_fget signature of param stream at testdata/flysystem/src/Adapter/Ftp.php:460
+ $result = ftp_fget($this->getConnection(), $stream, $path, $this->transferMode);
+ ^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function ftp_fget signature of param remote_filename at testdata/flysystem/src/Adapter/Ftp.php:460
$result = ftp_fget($this->getConnection(), $stream, $path, $this->transferMode);
^^^^^
WARNING notNullSafetyFunctionArgumentPropertyFetch: potential null dereference when accessing property 'transferMode' at testdata/flysystem/src/Adapter/Ftp.php:460
$result = ftp_fget($this->getConnection(), $stream, $path, $this->transferMode);
^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function rewind signature of param stream at testdata/flysystem/src/Adapter/Ftp.php:461
+ rewind($stream);
+ ^^^^^^^
+WARNING notSafeCall: potentially not safe call in function fclose signature of param stream at testdata/flysystem/src/Adapter/Ftp.php:464
+ fclose($stream);
+ ^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function ftp_chmod signature of param filename at testdata/flysystem/src/Adapter/Ftp.php:479
if ( ! ftp_chmod($this->getConnection(), $mode, $path)) {
^^^^^
+WARNING notSafeCall: potentially not safe call in function listDirectoryContentsRecursive signature of param directory at testdata/flysystem/src/Adapter/Ftp.php:496
+ return $this->listDirectoryContentsRecursive($directory);
+ ^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function normalizeListing signature of param prefix at testdata/flysystem/src/Adapter/Ftp.php:502
+ return $listing ? $this->normalizeListing($listing, $directory) : [];
+ ^^^^^^^^^^
+WARNING notSafeCall: potentially not safe array access in parameter directory of function listDirectoryContentsRecursive at testdata/flysystem/src/Adapter/Ftp.php:520
+ $output = array_merge($output, $this->listDirectoryContentsRecursive($item['path']));
+ ^^^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentPropertyFetch: potential null dereference when accessing property 'connection' at testdata/flysystem/src/Adapter/Ftp.php:544
$response = ftp_raw($this->connection, 'HELP');
^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function ftp_rawlist signature of param ftp at testdata/flysystem/src/Adapter/Ftp.php:565
+ return ftp_rawlist($connection, $options . ' ' . $path);
+ ^^^^^^^^^^^
WARNING errorSilence: Don't use @, silencing errors is bad practice at testdata/flysystem/src/Adapter/Ftp.php:570
$response = @ftp_raw($this->connection, trim($command));
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -166,12 +232,24 @@ WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function ftp_rawlist signature of param directory at testdata/flysystem/src/Adapter/Ftpd.php:37
$listing = ftp_rawlist($this->getConnection(), $directory, $recursive);
^^^^^^^^^^
-WARNING notNullSafetyFunctionArgumentArrayDimFetch: potential null array access in parameter string of function substr at testdata/flysystem/src/Adapter/Ftpd.php:39
+WARNING notSafeCall: potentially not safe array access in parameter string of function substr at testdata/flysystem/src/Adapter/Ftpd.php:39
if ($listing === false || ( ! empty($listing) && substr($listing[0], 0, 5) === "ftpd:")) {
^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function normalizeListing signature of param prefix at testdata/flysystem/src/Adapter/Ftpd.php:43
return $this->normalizeListing($listing, $directory);
^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function ensureDirectory signature of param root at testdata/flysystem/src/Adapter/Local.php:78
+ $this->ensureDirectory($root);
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function is_dir signature of param filename at testdata/flysystem/src/Adapter/Local.php:80
+ if ( ! is_dir($root) || ! is_readable($root)) {
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function is_readable signature of param filename at testdata/flysystem/src/Adapter/Local.php:80
+ if ( ! is_dir($root) || ! is_readable($root)) {
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function setPathPrefix signature of param prefix at testdata/flysystem/src/Adapter/Local.php:84
+ $this->setPathPrefix($root);
+ ^^^^^
WARNING errorSilence: Don't use @, silencing errors is bad practice at testdata/flysystem/src/Adapter/Local.php:103
if ( ! @mkdir($root, $this->permissionMap['dir']['public'], true)) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -199,6 +277,12 @@ WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function stream_copy_to_stream signature of param from at testdata/flysystem/src/Adapter/Local.php:159
if ( ! $stream || stream_copy_to_stream($resource, $stream) === false || ! fclose($stream)) {
^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function stream_copy_to_stream signature of param to at testdata/flysystem/src/Adapter/Local.php:159
+ if ( ! $stream || stream_copy_to_stream($resource, $stream) === false || ! fclose($stream)) {
+ ^^^^^^^
+WARNING notSafeCall: potentially not safe call in function fclose signature of param stream at testdata/flysystem/src/Adapter/Local.php:159
+ if ( ! $stream || stream_copy_to_stream($resource, $stream) === false || ! fclose($stream)) {
+ ^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function applyPathPrefix signature of param path at testdata/flysystem/src/Adapter/Local.php:179
$location = $this->applyPathPrefix($path);
^^^^^
@@ -247,6 +331,12 @@ WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function
WARNING errorSilence: Don't use @, silencing errors is bad practice at testdata/flysystem/src/Adapter/Local.php:263
return @unlink($location);
^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function getFilePath signature of param file at testdata/flysystem/src/Adapter/Local.php:281
+ $path = $this->getFilePath($file);
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function normalizeFileInfo signature of param file at testdata/flysystem/src/Adapter/Local.php:287
+ $result[] = $this->normalizeFileInfo($file);
+ ^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function applyPathPrefix signature of param path at testdata/flysystem/src/Adapter/Local.php:300
$location = $this->applyPathPrefix($path);
^^^^^
@@ -259,6 +349,9 @@ WARNING strictCmp: 3rd argument of in_array must be true when comparing strings
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function applyPathPrefix signature of param path at testdata/flysystem/src/Adapter/Local.php:344
$location = $this->applyPathPrefix($path);
^^^^^
+WARNING notSafeCall: potentially not safe call in function octdec signature of param octal_string when calling function \substr at testdata/flysystem/src/Adapter/Local.php:346
+ $permissions = octdec(substr(sprintf('%o', fileperms($location)), -4));
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function applyPathPrefix signature of param path at testdata/flysystem/src/Adapter/Local.php:365
$location = $this->applyPathPrefix($path);
^^^^^
@@ -271,12 +364,30 @@ WARNING errorSilence: Don't use @, silencing errors is bad practice at testdata/
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function applyPathPrefix signature of param path at testdata/flysystem/src/Adapter/Local.php:403
$location = $this->applyPathPrefix($dirname);
^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function guardAgainstUnreadableFileInfo signature of param file at testdata/flysystem/src/Adapter/Local.php:413
+ $this->guardAgainstUnreadableFileInfo($file);
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function deleteFileInfoObject signature of param file at testdata/flysystem/src/Adapter/Local.php:414
+ $this->deleteFileInfoObject($file);
+ ^^^^^
MAYBE invalidDocblockType: Void type can only be used as a standalone type for the return type at testdata/flysystem/src/Adapter/Local.php:444
* @return array|void
^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function trim signature of param string when calling function \str_replace at testdata/flysystem/src/Adapter/Local.php:471
+ return trim(str_replace('\\', '/', $path), '/');
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
WARNING invalidDocblockRef: @see tag refers to unknown symbol League\Flysystem\ReadInterface::readStream at testdata/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php:17
* @see League\Flysystem\ReadInterface::readStream()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function fwrite signature of param stream at testdata/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php:26
+ fwrite($stream, $data['contents']);
+ ^^^^^^^
+WARNING notSafeCall: potentially not safe array access in parameter data of function fwrite at testdata/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php:26
+ fwrite($stream, $data['contents']);
+ ^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function rewind signature of param stream at testdata/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php:27
+ rewind($stream);
+ ^^^^^^^
WARNING invalidDocblockRef: @see tag refers to unknown symbol League\Flysystem\ReadInterface::read at testdata/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php:41
* @see League\Flysystem\ReadInterface::read()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -292,6 +403,12 @@ MAYBE missingPhpdoc: Missing PHPDoc for \League\Flysystem\Adapter\Polyfill\Str
MAYBE deprecated: Has deprecated class Directory at testdata/flysystem/src/Directory.php:8
class Directory extends Handler
^
+WARNING notNullSafetyFunctionArgumentPropertyFetch: potential null dereference when accessing property 'path' at testdata/flysystem/src/Directory.php:17
+ return $this->filesystem->deleteDir($this->path);
+ ^^^^^^^^^^^
+WARNING notNullSafetyFunctionArgumentPropertyFetch: potential null dereference when accessing property 'path' at testdata/flysystem/src/Directory.php:29
+ return $this->filesystem->listContents($this->path, $recursive);
+ ^^^^^^^^^^^
MAYBE deprecated: Has deprecated class File at testdata/flysystem/src/File.php:8
class File extends Handler
^
@@ -343,6 +460,9 @@ WARNING unused: Variable $e is unused (use $_ to ignore this inspection or speci
WARNING notExplicitNullableParam: parameter with null default value should be explicitly nullable at testdata/flysystem/src/Handler.php:28
public function __construct(FilesystemInterface $filesystem = null, $path = null)
^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function mountFilesystem signature of param prefix at testdata/flysystem/src/MountManager.php:57
+ $this->mountFilesystem($prefix, $filesystem);
+ ^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function mountFilesystem signature of param filesystem at testdata/flysystem/src/MountManager.php:57
$this->mountFilesystem($prefix, $filesystem);
^^^^^^^^^^^
@@ -352,6 +472,9 @@ WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function invokePluginOnFilesystem signature of param prefix at testdata/flysystem/src/MountManager.php:166
return $this->invokePluginOnFilesystem($method, $arguments, $prefix);
^^^^^^^
+WARNING notSafeCall: potentially not safe call in function writeStream signature of param resource at testdata/flysystem/src/MountManager.php:192
+ $result = $this->getFilesystem($prefixTo)->writeStream($to, $buffer, $config);
+ ^^^^^^^
WARNING notNullSafetyFunctionArgumentArrayDimFetch: potential null array access in parameter visibility of function setVisibility at testdata/flysystem/src/MountManager.php:243
return $filesystem->setVisibility($pathTo, $config['visibility']);
^^^^^^^^^^^^^^^^^^^^^
@@ -367,6 +490,12 @@ MAYBE deprecatedUntagged: Call to deprecated method {\League\Flysystem\Filesys
WARNING notExplicitNullableParam: parameter with null default value should be explicitly nullable at testdata/flysystem/src/MountManager.php:642
public function get($path, Handler $handler = null)
^^^^^^^
+WARNING notSafeCall: potentially not safe array access in parameter dirname of function deleteDir at testdata/flysystem/src/Plugin/EmptyDir.php:28
+ $this->filesystem->deleteDir($item['path']);
+ ^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe array access in parameter path of function delete at testdata/flysystem/src/Plugin/EmptyDir.php:30
+ $this->filesystem->delete($item['path']);
+ ^^^^^^^^^^^^^
WARNING unused: Variable $e is unused (use $_ to ignore this inspection or specify --unused-var-regex flag) at testdata/flysystem/src/Plugin/ForcedCopy.php:33
} catch (FileNotFoundException $e) {
^^
@@ -382,6 +511,9 @@ WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function call_user_func_array signature of param callback at testdata/flysystem/src/Plugin/PluggableTrait.php:72
return call_user_func_array($callback, $arguments);
^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function invokePlugin signature of param filesystem at testdata/flysystem/src/Plugin/PluggableTrait.php:88
+ return $this->invokePlugin($method, $arguments, $this);
+ ^^^^^
WARNING unused: Variable $e is unused (use $_ to ignore this inspection or specify --unused-var-regex flag) at testdata/flysystem/src/Plugin/PluggableTrait.php:89
} catch (PluginNotFoundException $e) {
^^
@@ -397,6 +529,9 @@ WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function
MAYBE missingPhpdoc: Missing PHPDoc for \League\Flysystem\UnreadableFileException::forFileInfo public method at testdata/flysystem/src/UnreadableFileException.php:9
public static function forFileInfo(SplFileInfo $fileInfo)
^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe array access in parameter path of function pathinfo at testdata/flysystem/src/Util.php:27
+ $pathinfo += pathinfo($pathinfo['basename']);
+ ^^^^^^^^^^^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function pathinfo signature of param path at testdata/flysystem/src/Util.php:214
$listing[] = static::pathinfo($directory) + ['type' => 'dir'];
^^^^^^^^^^
@@ -460,3 +595,6 @@ WARNING unused: Variable $e is unused (use $_ to ignore this inspection or speci
MAYBE ternarySimplify: Could rewrite as `static::$extensionToMimeTypeMap[$extension] ?? 'text/plain'` at testdata/flysystem/src/Util/MimeType.php:222
return isset(static::$extensionToMimeTypeMap[$extension])
^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function strtolower signature of param string when calling function \pathinfo at testdata/flysystem/src/Util/MimeType.php:234
+ $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/src/tests/golden/testdata/idn/golden.txt b/src/tests/golden/testdata/idn/golden.txt
index fb12b3d57..7faf6a8c5 100644
--- a/src/tests/golden/testdata/idn/golden.txt
+++ b/src/tests/golden/testdata/idn/golden.txt
@@ -28,6 +28,9 @@ WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function
MAYBE arraySyntax: Use the short form '[]' instead of the old 'array()' at testdata/idn/idn.php:119
$idna_info = array(
+WARNING notSafeCall: potentially not safe call in function mb_chr signature of param codepoint at testdata/idn/idn.php:139
+ $output .= mb_chr($code, 'utf-8');
+ ^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function mb_strlen signature of param string at testdata/idn/idn.php:152
$length = mb_strlen($input, 'utf-8');
^^^^^^
@@ -64,6 +67,9 @@ WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function substr signature of param string at testdata/idn/idn.php:249
$output = substr($input, 0, $pos++);
^^^^^^
+WARNING notSafeCall: potentially not safe call in function strlen signature of param string at testdata/idn/idn.php:254
+ $outputLength = \strlen($output);
+ ^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function strlen signature of param string at testdata/idn/idn.php:255
$inputLength = \strlen($input);
^^^^^^
@@ -73,3 +79,15 @@ MAYBE assignOp: Could rewrite as `$n += (int) ($i / $outputLength)` at testdat
MAYBE assignOp: Could rewrite as `$i %= $outputLength` at testdata/idn/idn.php:275
$i = $i % $outputLength;
^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function mb_substr signature of param string at testdata/idn/idn.php:276
+ $output = mb_substr($output, 0, $i, 'utf-8').mb_chr($n, 'utf-8').mb_substr($output, $i, $outputLength - 1, 'utf-8');
+ ^^^^^^^
+WARNING notSafeCall: potentially not safe call in function mb_substr signature of param length at testdata/idn/idn.php:276
+ $output = mb_substr($output, 0, $i, 'utf-8').mb_chr($n, 'utf-8').mb_substr($output, $i, $outputLength - 1, 'utf-8');
+ ^^
+WARNING notSafeCall: potentially not safe call in function mb_substr signature of param string at testdata/idn/idn.php:276
+ $output = mb_substr($output, 0, $i, 'utf-8').mb_chr($n, 'utf-8').mb_substr($output, $i, $outputLength - 1, 'utf-8');
+ ^^^^^^^
+WARNING notSafeCall: potentially not safe call in function mb_substr signature of param start at testdata/idn/idn.php:276
+ $output = mb_substr($output, 0, $i, 'utf-8').mb_chr($n, 'utf-8').mb_substr($output, $i, $outputLength - 1, 'utf-8');
+ ^^
diff --git a/src/tests/golden/testdata/math/golden.txt b/src/tests/golden/testdata/math/golden.txt
index 98474b6a7..811fcbb47 100644
--- a/src/tests/golden/testdata/math/golden.txt
+++ b/src/tests/golden/testdata/math/golden.txt
@@ -1,33 +1,78 @@
WARNING unused: Variable $a is unused (use $_ to ignore this inspection or specify --unused-var-regex flag) at testdata/math/src/BigDecimal.php:292
[$a, $b] = $this->scaleValues($this, $that);
^^
+WARNING notSafeCall: potentially not safe call in function str_repeat signature of param times at testdata/math/src/BigDecimal.php:464
+ $value .= \str_repeat('0', $addDigits);
+ ^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function substr signature of param length at testdata/math/src/BigDecimal.php:472
+ $value = \substr($value, 0, $addDigits);
+ ^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function sqrt signature of param n at testdata/math/src/BigDecimal.php:475
+ $value = Calculator::get()->sqrt($value);
+ ^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function of signature of param value at testdata/math/src/BigDecimal.php:588
$that = BigNumber::of($that);
^^^^^
+WARNING notSafeCall: potentially not safe call in function str_pad signature of param string at testdata/math/src/BigDecimal.php:847
+ $value = \str_pad($value, $targetLength, '0', STR_PAD_LEFT);
+ ^^^^^^
+WARNING notSafeCall: potentially not safe call in function str_pad signature of param length at testdata/math/src/BigDecimal.php:847
+ $value = \str_pad($value, $targetLength, '0', STR_PAD_LEFT);
+ ^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function ltrim signature of param string at testdata/math/src/BigInteger.php:105
+ $number = \ltrim($number, '0');
+ ^^^^^^^
MAYBE trailingComma: Last element in a multi-line array should have a trailing comma at testdata/math/src/BigInteger.php:435
new BigInteger($remainder)
^^^^^^^^^^^^^^^^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function of signature of param value at testdata/math/src/BigInteger.php:742
$that = BigNumber::of($that);
^^^^^
+WARNING notSafeCall: potentially not safe call in function preg_match signature of param subject at testdata/math/src/BigNumber.php:74
+ if (\preg_match(self::PARSE_REGEXP, $value, $matches) !== 1) {
+ ^^^^^^
MAYBE ternarySimplify: Could rewrite as `$matches['fractional'] ?? ''` at testdata/math/src/BigNumber.php:90
$fractional = isset($matches['fractional']) ? $matches['fractional'] : '';
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function setlocale signature of param rest at testdata/math/src/BigNumber.php:131
+ \setlocale(LC_NUMERIC, $currentLocale);
+ ^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function of signature of param value at testdata/math/src/BigNumber.php:171
+ $value = static::of($value);
+ ^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function isLessThan signature of param that at testdata/math/src/BigNumber.php:173
if ($min === null || $value->isLessThan($min)) {
^^^^
+WARNING notSafeCall: potentially not safe call in function of signature of param value at testdata/math/src/BigNumber.php:203
+ $value = static::of($value);
+ ^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function isGreaterThan signature of param that at testdata/math/src/BigNumber.php:205
if ($max === null || $value->isGreaterThan($max)) {
^^^^
+WARNING notSafeCall: potentially not safe call in function of signature of param value at testdata/math/src/BigNumber.php:236
+ $value = static::of($value);
+ ^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function add signature of param a at testdata/math/src/BigNumber.php:241
$sum = self::add($sum, $value);
^^^^
+WARNING notSafeCall: potentially not safe call in function ltrim signature of param string at testdata/math/src/BigNumber.php:307
+ $number = \ltrim($number, '0');
+ ^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function minus signature of param that at testdata/math/src/BigRational.php:365
return $this->minus($that)->getSign();
^^^^^
MAYBE implicitModifiers: Specify the access modifier for \Brick\Math\Internal\Calculator::powmod method explicitly at testdata/math/src/Internal/Calculator.php:260
abstract function powmod(string $base, string $exp, string $mod) : string;
^^^^^^
+WARNING notSafeCall: potentially not safe call in function toArbitraryBase signature of param number at testdata/math/src/Internal/Calculator.php:333
+ $number = $this->toArbitraryBase($number, self::ALPHABET, $base);
+ ^^^^^^^
+WARNING notSafeCall: potentially not safe call in function twosComplement signature of param number at testdata/math/src/Internal/Calculator.php:605
+ $value = $this->twosComplement($value);
+ ^^^^^^
+WARNING notSafeCall: potentially not safe call in function toDecimal signature of param bytes at testdata/math/src/Internal/Calculator.php:608
+ $result = $this->toDecimal($value);
+ ^^^^^^
MAYBE assignOp: Could rewrite as `$number ^= $xor` at testdata/math/src/Internal/Calculator.php:622
$number = $number ^ $xor;
^^^^^^^^^^^^^^^^^^^^^^^^
@@ -40,9 +85,27 @@ WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function
MAYBE trailingComma: Last element in a multi-line array should have a trailing comma at testdata/math/src/Internal/Calculator/GmpCalculator.php:67
\gmp_strval($r)
^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function neg signature of param n at testdata/math/src/Internal/Calculator/NativeCalculator.php:79
+ $result = $this->neg($result);
+ ^^^^^^^
MAYBE trailingComma: Last element in a multi-line array should have a trailing comma at testdata/math/src/Internal/Calculator/NativeCalculator.php:187
(string) $r
^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function substr signature of param offset at testdata/math/src/Internal/Calculator/NativeCalculator.php:322
+ $blockA = \substr($a, $i, $blockLength);
+ ^^
+WARNING notSafeCall: potentially not safe call in function substr signature of param offset at testdata/math/src/Internal/Calculator/NativeCalculator.php:323
+ $blockB = \substr($b, $i, $blockLength);
+ ^^
+WARNING notSafeCall: potentially not safe call in function substr signature of param offset at testdata/math/src/Internal/Calculator/NativeCalculator.php:392
+ $blockA = \substr($a, $i, $blockLength);
+ ^^
+WARNING notSafeCall: potentially not safe call in function substr signature of param offset at testdata/math/src/Internal/Calculator/NativeCalculator.php:393
+ $blockB = \substr($b, $i, $blockLength);
+ ^^
+WARNING notSafeCall: potentially not safe call in function doCmp signature of param a at testdata/math/src/Internal/Calculator/NativeCalculator.php:532
+ $cmp = $this->doCmp($focus, $b);
+ ^^^^^^
ERROR classMembersOrder: Constant UNNECESSARY must go before methods in the class RoundingMode at testdata/math/src/RoundingMode.php:33
public const UNNECESSARY = 0;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/src/tests/golden/testdata/mustache/golden.txt b/src/tests/golden/testdata/mustache/golden.txt
index 761638cdc..38e42adcc 100644
--- a/src/tests/golden/testdata/mustache/golden.txt
+++ b/src/tests/golden/testdata/mustache/golden.txt
@@ -1,9 +1,15 @@
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function realpath signature of param path at testdata/mustache/src/Mustache/Autoloader.php:39
$realDir = realpath($baseDir);
^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function is_dir signature of param filename at testdata/mustache/src/Mustache/Autoloader.php:40
+ if (is_dir($realDir)) {
+ ^^^^^^^^
MAYBE ternarySimplify: Could rewrite as `$baseDir ?: 0` at testdata/mustache/src/Mustache/Autoloader.php:56
$key = $baseDir ? $baseDir : 0;
^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function strpos signature of param haystack at testdata/mustache/src/Mustache/Autoloader.php:79
+ if (strpos($class, 'Mustache') !== 0) {
+ ^^^^^^
ERROR undefinedClass: Class or interface named \Psr\Log\LoggerInterface does not exist at testdata/mustache/src/Mustache/Cache/AbstractCache.php:26
* @return Mustache_Logger|Psr\Log\LoggerInterface
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -16,15 +22,24 @@ WARNING errorSilence: Don't use @, silencing errors is bad practice at testdata/
WARNING errorSilence: Don't use @, silencing errors is bad practice at testdata/mustache/src/Mustache/Cache/FilesystemCache.php:140
if (false !== @file_put_contents($tempFile, $value)) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function file_put_contents signature of param filename at testdata/mustache/src/Mustache/Cache/FilesystemCache.php:140
+ if (false !== @file_put_contents($tempFile, $value)) {
+ ^^^^^^^^^
WARNING errorSilence: Don't use @, silencing errors is bad practice at testdata/mustache/src/Mustache/Cache/FilesystemCache.php:141
if (@rename($tempFile, $fileName)) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function rename signature of param from at testdata/mustache/src/Mustache/Cache/FilesystemCache.php:141
+ if (@rename($tempFile, $fileName)) {
+ ^^^^^^^^^
MAYBE ternarySimplify: Could rewrite as `$this->fileMode ?? (0666 & ~umask())` at testdata/mustache/src/Mustache/Cache/FilesystemCache.php:142
$mode = isset($this->fileMode) ? $this->fileMode : (0666 & ~umask());
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
WARNING errorSilence: Don't use @, silencing errors is bad practice at testdata/mustache/src/Mustache/Cache/FilesystemCache.php:143
@chmod($fileName, $mode);
^^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function chmod signature of param permissions at testdata/mustache/src/Mustache/Cache/FilesystemCache.php:143
+ @chmod($fileName, $mode);
+ ^^^^^
WARNING useEval: Don't use the 'eval' function at testdata/mustache/src/Mustache/Cache/NoopCache.php:45
eval('?>' . $value);
^^^^^^^^^^^^^^^^^^^
@@ -217,6 +232,9 @@ WARNING notNullSafetyFunctionArgumentArrayDimFetch: potential null array access
MAYBE misspellComment: "entitity" is a misspelling of "entity" at testdata/mustache/src/Mustache/Engine.php:254
public function getEntityFlags()
^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function addHelper signature of param name at testdata/mustache/src/Mustache/Engine.php:373
+ $this->addHelper($name, $helper);
+ ^^^^^
ERROR undefinedClass: Class or interface named \Psr\Log\LoggerInterface does not exist at testdata/mustache/src/Mustache/Engine.php:451
* @param Mustache_Logger|Psr\Log\LoggerInterface $logger
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -238,6 +256,15 @@ ERROR undefinedMethod: Call to undefined method {\Mustache_Cache}->setLogger()
MAYBE ternarySimplify: Could rewrite as `$this->delimiters ?: '{{ }}'` at testdata/mustache/src/Mustache/Engine.php:628
'delimiters' => $this->delimiters ? $this->delimiters : '{{ }}',
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function md5 signature of param string at testdata/mustache/src/Mustache/Engine.php:645
+ return $this->templateClassPrefix . md5($key);
+ ^^^^
+WARNING notSafeCall: potentially not safe call in function parse signature of param source at testdata/mustache/src/Mustache/Engine.php:808
+ $tree = $this->parse($source);
+ ^^^^^^^
+WARNING notSafeCall: potentially not safe call in function compile signature of param source at testdata/mustache/src/Mustache/Engine.php:813
+ return $compiler->compile($source, $tree, $name, isset($this->escape), $this->charset, $this->strictCallables, $this->entityFlags);
+ ^^^^^^^
WARNING notNullSafetyFunctionArgumentPropertyFetch: potential null dereference when accessing property 'charset' at testdata/mustache/src/Mustache/Engine.php:813
return $compiler->compile($source, $tree, $name, isset($this->escape), $this->charset, $this->strictCallables, $this->entityFlags);
^^^^^^^^^^^^^^
@@ -268,18 +295,42 @@ MAYBE missingPhpdoc: Missing PHPDoc for \Mustache_Exception_UnknownTemplateExc
WARNING notExplicitNullableParam: parameter with null default value should be explicitly nullable at testdata/mustache/src/Mustache/Exception/UnknownTemplateException.php:23
public function __construct($templateName, Exception $previous = null)
^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function add signature of param name at testdata/mustache/src/Mustache/HelperCollection.php:39
+ $this->add($name, $helper);
+ ^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function addLoader signature of param loader at testdata/mustache/src/Mustache/Loader/CascadingLoader.php:35
$this->addLoader($loader);
^^^^^^^
+WARNING notSafeCall: potentially not safe accessing property 'baseDir' at testdata/mustache/src/Mustache/Loader/FilesystemLoader.php:52
+ if (strpos($this->baseDir, '://') === false) {
+ ^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe accessing property 'baseDir' at testdata/mustache/src/Mustache/Loader/FilesystemLoader.php:53
+ $this->baseDir = realpath($this->baseDir);
+ ^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe accessing property 'baseDir' at testdata/mustache/src/Mustache/Loader/FilesystemLoader.php:56
+ if ($this->shouldCheckPath() && !is_dir($this->baseDir)) {
+ ^^^^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentArrayDimFetch: potential null array access in parameter string of function ltrim at testdata/mustache/src/Mustache/Loader/FilesystemLoader.php:64
$this->extension = '.' . ltrim($options['extension'], '.');
^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe accessing property 'baseDir' at testdata/mustache/src/Mustache/Loader/FilesystemLoader.php:133
+ return strpos($this->baseDir, '://') === false || strpos($this->baseDir, 'file://') === 0;
+ ^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe accessing property 'baseDir' at testdata/mustache/src/Mustache/Loader/FilesystemLoader.php:133
+ return strpos($this->baseDir, '://') === false || strpos($this->baseDir, 'file://') === 0;
+ ^^^^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentConstFetch: null passed to non-nullable parameter context in function file_get_contents at testdata/mustache/src/Mustache/Loader/InlineLoader.php:114
$data = file_get_contents($this->fileName, false, null, $this->offset);
^^^^
+WARNING notSafeCall: potentially not safe call in function preg_split signature of param subject at testdata/mustache/src/Mustache/Loader/InlineLoader.php:115
+ foreach (preg_split("/^@@(?= [\w\d\.]+$)/m", $data, -1) as $chunk) {
+ ^^^^^
WARNING regexpVet: '\w' intersects with '\d' in [\w\d\.] at testdata/mustache/src/Mustache/Loader/InlineLoader.php:115
foreach (preg_split("/^@@(?= [\w\d\.]+$)/m", $data, -1) as $chunk) {
^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function setLevel signature of param level at testdata/mustache/src/Mustache/Logger/StreamLogger.php:46
+ $this->setLevel($level);
+ ^^^^^^
WARNING notNullSafetyFunctionArgumentPropertyFetch: potential null dereference when accessing property 'stream' at testdata/mustache/src/Mustache/Logger/StreamLogger.php:61
fclose($this->stream);
^^^^^^^^^^^^^
@@ -295,6 +346,9 @@ WARNING notNullSafetyFunctionArgumentPropertyFetch: potential null dereference w
WARNING notNullSafetyFunctionArgumentPropertyFetch: potential null dereference when accessing property 'stream' at testdata/mustache/src/Mustache/Logger/StreamLogger.php:136
fwrite($this->stream, self::formatLine($level, $message, $context));
^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function strtoupper signature of param string at testdata/mustache/src/Mustache/Logger/StreamLogger.php:150
+ return strtoupper($level);
+ ^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function enablePragma signature of param name at testdata/mustache/src/Mustache/Parser.php:58
$this->enablePragma($pragma);
^^^^^^^
@@ -328,9 +382,30 @@ WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function trim signature of param string at testdata/mustache/src/Mustache/Tokenizer.php:110
if ($delimiters = trim($delimiters)) {
^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function changeDelimiters signature of param index at testdata/mustache/src/Mustache/Tokenizer.php:145
+ $i = $this->changeDelimiters($text, $i);
+ ^^
+WARNING notSafeCall: potentially not safe call in function addPragma signature of param index at testdata/mustache/src/Mustache/Tokenizer.php:148
+ $i = $this->addPragma($text, $i);
+ ^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function substr signature of param string at testdata/mustache/src/Mustache/Tokenizer.php:188
if (substr($lastName, -1) === '}') {
^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function trim signature of param string when calling function \substr at testdata/mustache/src/Mustache/Tokenizer.php:189
+ $token[self::NAME] = trim(substr($lastName, 0, -1));
+ ^^^^^^^^^^^^^^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function substr signature of param string at testdata/mustache/src/Mustache/Tokenizer.php:189
$token[self::NAME] = trim(substr($lastName, 0, -1));
^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function trim signature of param string when calling function \substr at testdata/mustache/src/Mustache/Tokenizer.php:283
+ $this->setDelimiters(trim(substr($text, $startIndex, $closeIndex - $startIndex)));
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function substr signature of param offset at testdata/mustache/src/Mustache/Tokenizer.php:283
+ $this->setDelimiters(trim(substr($text, $startIndex, $closeIndex - $startIndex)));
+ ^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe accessing property 'ctag' at testdata/mustache/src/Mustache/Tokenizer.php:330
+ $end = strpos($text, $this->ctag, $index);
+ ^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function trim signature of param string when calling function \substr at testdata/mustache/src/Mustache/Tokenizer.php:331
+ $pragma = trim(substr($text, $index + 2, $end - $index - 2));
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/src/tests/golden/testdata/options-resolver/golden.txt b/src/tests/golden/testdata/options-resolver/golden.txt
index 87db7f21c..56737f6bc 100644
--- a/src/tests/golden/testdata/options-resolver/golden.txt
+++ b/src/tests/golden/testdata/options-resolver/golden.txt
@@ -31,6 +31,9 @@ MAYBE deprecated: Call to deprecated method {\ReflectionParameter}->getClass()
MAYBE deprecated: Call to deprecated method {\ReflectionParameter}->getClass() (since: 8.0, reason: Use ReflectionParameter::getType() and the ReflectionType APIs should be used instead) at testdata/options-resolver/OptionsResolver.php:209
if (isset($params[0]) && null !== ($class = $params[0]->getClass()) && self::class === $class->name && (!isset($params[1]) || (null !== ($class = $params[1]->getClass()) && Options::class === $class->name))) {
^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function setDefault signature of param option at testdata/options-resolver/OptionsResolver.php:251
+ $this->setDefault($option, $value);
+ ^^^^^^^
WARNING invalidDocblock: @param for non-existing argument $package at testdata/options-resolver/OptionsResolver.php:424
* @param string $package The name of the composer package that is triggering the deprecation
^^^^^^^^
@@ -40,18 +43,48 @@ WARNING invalidDocblock: @param for non-existing argument $version at testdata/o
WARNING invalidDocblock: @param for non-existing argument $message at testdata/options-resolver/OptionsResolver.php:426
* @param string|\Closure $message The deprecation message to use
^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function offsetGet signature of param option at testdata/options-resolver/OptionsResolver.php:895
+ $clone->offsetGet($option);
+ ^^^^^^^
MAYBE complexity: Too big method: more than 150 lines at testdata/options-resolver/OptionsResolver.php:917
public function offsetGet($option, bool $triggerDeprecation = true)
^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function verifyTypes signature of param type at testdata/options-resolver/OptionsResolver.php:1000
+ if ($valid = $this->verifyTypes($type, $value, $invalidTypes)) {
+ ^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function verifyTypes signature of param value at testdata/options-resolver/OptionsResolver.php:1000
if ($valid = $this->verifyTypes($type, $value, $invalidTypes)) {
^^^^^^
+WARNING notSafeCall: potentially not safe call in function verifyTypes signature of param type at testdata/options-resolver/OptionsResolver.php:1000
+ if ($valid = $this->verifyTypes($type, $value, $invalidTypes)) {
+ ^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function verifyTypes signature of param value at testdata/options-resolver/OptionsResolver.php:1000
if ($valid = $this->verifyTypes($type, $value, $invalidTypes)) {
^^^^^^
+WARNING notSafeCall: potentially not safe array access in parameter package of function trigger_deprecation at testdata/options-resolver/OptionsResolver.php:1090
+ trigger_deprecation($deprecation['package'], $deprecation['version'], strtr($message, ['%name%' => $option]));
+ ^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe array access in parameter version of function trigger_deprecation at testdata/options-resolver/OptionsResolver.php:1090
+ trigger_deprecation($deprecation['package'], $deprecation['version'], strtr($message, ['%name%' => $option]));
+ ^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function strtr signature of param str at testdata/options-resolver/OptionsResolver.php:1090
+ trigger_deprecation($deprecation['package'], $deprecation['version'], strtr($message, ['%name%' => $option]));
+ ^^^^^^^^
MAYBE typeHint: Specify the type for the parameter $invalidTypes in PHPDoc, 'array' type hint too generic at testdata/options-resolver/OptionsResolver.php:1123
private function verifyTypes(string $type, $value, array &$invalidTypes, int $level = 0): bool
^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function verifyTypes signature of param type at testdata/options-resolver/OptionsResolver.php:1130
+ if (!$this->verifyTypes($type, $val, $invalidTypes, $level + 1)) {
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function verifyTypes signature of param value at testdata/options-resolver/OptionsResolver.php:1130
+ if (!$this->verifyTypes($type, $val, $invalidTypes, $level + 1)) {
+ ^^^^
+WARNING notSafeCall: potentially not safe call in function verifyTypes signature of param type at testdata/options-resolver/OptionsResolver.php:1130
+ if (!$this->verifyTypes($type, $val, $invalidTypes, $level + 1)) {
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function verifyTypes signature of param value at testdata/options-resolver/OptionsResolver.php:1130
+ if (!$this->verifyTypes($type, $val, $invalidTypes, $level + 1)) {
+ ^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function get_class signature of param object at testdata/options-resolver/OptionsResolver.php:1221
return \get_class($value);
^^^^^^
diff --git a/src/tests/golden/testdata/parsedown/golden.txt b/src/tests/golden/testdata/parsedown/golden.txt
index 0287d6303..50b833e31 100644
--- a/src/tests/golden/testdata/parsedown/golden.txt
+++ b/src/tests/golden/testdata/parsedown/golden.txt
@@ -1,6 +1,9 @@
MAYBE implicitModifiers: Specify the access modifier for \Parsedown::text method explicitly at testdata/parsedown/parsedown.php:24
function text($text)
^^^^
+WARNING notSafeCall: potentially not safe call in function trim signature of param string at testdata/parsedown/parsedown.php:46
+ $text = trim($text, "\n");
+ ^^^^^
MAYBE implicitModifiers: Specify the access modifier for \Parsedown::setBreaksEnabled method explicitly at testdata/parsedown/parsedown.php:59
function setBreaksEnabled($breaksEnabled)
^^^^^^^^^^^^^^^^
@@ -28,9 +31,18 @@ WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function strstr signature of param haystack at testdata/parsedown/parsedown.php:186
while (($beforeTab = strstr($line, "\t", true)) !== false)
^^^^^
+WARNING notSafeCall: potentially not safe call in function mb_strlen signature of param string at testdata/parsedown/parsedown.php:188
+ $shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4;
+ ^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function str_repeat signature of param times at testdata/parsedown/parsedown.php:191
+ . str_repeat(' ', $shortage)
+ ^^^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function substr signature of param string at testdata/parsedown/parsedown.php:192
. substr($line, strlen($beforeTab) + 1)
^^^^^
+WARNING notSafeCall: potentially not safe call in function strlen signature of param string at testdata/parsedown/parsedown.php:192
+ . substr($line, strlen($beforeTab) + 1)
+ ^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function strspn signature of param string at testdata/parsedown/parsedown.php:196
$indent = strspn($line, ' ');
^^^^^
@@ -67,6 +79,9 @@ WARNING notNullSafetyFunctionArgumentArrayDimFetch: potential null array access
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function strspn signature of param characters at testdata/parsedown/parsedown.php:452
$openerLength = strspn($Line['text'], $marker);
^^^^^^^
+WARNING notSafeCall: potentially not safe call in function trim signature of param string when calling function \substr at testdata/parsedown/parsedown.php:459
+ $infostring = trim(substr($Line['text'], $openerLength), "\t ");
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentArrayDimFetch: potential null array access in parameter string of function substr at testdata/parsedown/parsedown.php:459
$infostring = trim(substr($Line['text'], $openerLength), "\t ");
^^^^^^^^^^^^^
@@ -85,6 +100,9 @@ WARNING notNullSafetyFunctionArgumentArrayDimFetch: potential null array access
WARNING notNullSafetyFunctionArgumentArrayDimFetch: potential null array access in parameter characters of function strspn at testdata/parsedown/parsedown.php:516
if (($len = strspn($Line['text'], $Block['char'])) >= $Block['openerLength']
^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function chop signature of param string when calling function \substr at testdata/parsedown/parsedown.php:517
+ and chop(substr($Line['text'], $len), ' ') === ''
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentArrayDimFetch: potential null array access in parameter string of function substr at testdata/parsedown/parsedown.php:517
and chop(substr($Line['text'], $len), ' ') === ''
^^^^^^^^^^^^^
@@ -103,6 +121,18 @@ MAYBE typeHint: Specify the type for the parameter $CurrentBlock in PHPDoc, 'a
WARNING notNullSafetyFunctionArgumentArrayDimFetch: potential null array access in parameter subject of function preg_match at testdata/parsedown/parsedown.php:578
if (preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Line['text'], $matches))
^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe array access in parameter haystack of function strstr at testdata/parsedown/parsedown.php:593
+ $markerWithoutWhitespace = strstr($matches[1], ' ', true);
+ ^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function substr signature of param string at testdata/parsedown/parsedown.php:601
+ 'markerType' => ($name === 'ul' ? $markerWithoutWhitespace : substr($markerWithoutWhitespace, -1)),
+ ^^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function ltrim signature of param string when calling function \strstr at testdata/parsedown/parsedown.php:612
+ $listStart = ltrim(strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0';
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe array access in parameter haystack of function strstr at testdata/parsedown/parsedown.php:612
+ $listStart = ltrim(strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0';
+ ^^^^^^^^^^^
MAYBE trailingComma: Last element in a multi-line array should have a trailing comma at testdata/parsedown/parsedown.php:633
'destination' => 'elements'
^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -118,6 +148,9 @@ WARNING notNullSafetyFunctionArgumentArrayDimFetch: potential null array access
WARNING notNullSafetyFunctionArgumentArrayDimFetch: potential null array access in parameter subject of function preg_match at testdata/parsedown/parsedown.php:659
and preg_match('/^'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function preg_match signature of param matches at testdata/parsedown/parsedown.php:659
+ and preg_match('/^'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
+ ^^^^^^^^
MAYBE ternarySimplify: Could rewrite as `$matches[1] ?? ''` at testdata/parsedown/parsedown.php:674
$text = isset($matches[1]) ? $matches[1] : '';
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -130,6 +163,9 @@ MAYBE trailingComma: Last element in a multi-line array should have a trailing
WARNING notNullSafetyFunctionArgumentArrayDimFetch: potential null array access in parameter string of function substr at testdata/parsedown/parsedown.php:712
$text = substr($Line['body'], $requiredIndent);
^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function substr signature of param offset at testdata/parsedown/parsedown.php:712
+ $text = substr($Line['body'], $requiredIndent);
+ ^^^^^^^^^^^^^^^
MAYBE typeHint: Specify the type for the parameter $Block in PHPDoc, 'array' type hint too generic at testdata/parsedown/parsedown.php:729
protected function blockListComplete(array $Block)
^^^^^^^^^^^^^^^^^
@@ -226,6 +262,30 @@ MAYBE trailingComma: Last element in a multi-line array should have a trailing
MAYBE typeHint: Specify the type for the parameter $Block in PHPDoc, 'array' type hint too generic at testdata/parsedown/parsedown.php:1093
protected function paragraphContinue($Line, array $Block)
^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function lineElements signature of param nonNestables at testdata/parsedown/parsedown.php:1132
+ return $this->elements($this->lineElements($text, $nonNestables));
+ ^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function strpbrk signature of param string at testdata/parsedown/parsedown.php:1149
+ while ($excerpt = strpbrk($text, $this->inlineMarkerList))
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function strlen signature of param string at testdata/parsedown/parsedown.php:1153
+ $markerPosition = strlen($text) - strlen($excerpt);
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function strlen signature of param string at testdata/parsedown/parsedown.php:1153
+ $markerPosition = strlen($text) - strlen($excerpt);
+ ^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function substr signature of param string at testdata/parsedown/parsedown.php:1196
+ $unmarkedText = substr($text, 0, $Inline['position']);
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function substr signature of param string at testdata/parsedown/parsedown.php:1206
+ $text = substr($text, $Inline['position'] + $Inline['extent']);
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function substr signature of param string at testdata/parsedown/parsedown.php:1213
+ $unmarkedText = substr($text, 0, $markerPosition + 1);
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function substr signature of param string at testdata/parsedown/parsedown.php:1218
+ $text = substr($text, $markerPosition + 1);
+ ^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function strlen signature of param string at testdata/parsedown/parsedown.php:1242
'extent' => strlen($text),
^^^^^
@@ -262,12 +322,18 @@ WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function preg_match signature of param subject at testdata/parsedown/parsedown.php:1421
if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches))
^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function preg_match signature of param matches at testdata/parsedown/parsedown.php:1421
+ if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches))
+ ^^^^^^^^
MAYBE regexpSimplify: May re-write '/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/' as '/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?: +("[^"]*+"|'[^']*+'))?\s*+[)]/' at testdata/parsedown/parsedown.php:1421
if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function preg_match signature of param subject at testdata/parsedown/parsedown.php:1434
if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function preg_match signature of param matches at testdata/parsedown/parsedown.php:1434
+ if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
+ ^^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function strtolower signature of param string at testdata/parsedown/parsedown.php:1437
$definition = strtolower($definition);
^^^^^^^^^^^
@@ -283,9 +349,15 @@ MAYBE regexpSimplify: May re-write '/^<\/\w[\w-]*+[ ]*+>/s' as '/^<\/\w[\w-]*+
WARNING notNullSafetyFunctionArgumentArrayDimFetch: potential null array access in parameter subject of function preg_match at testdata/parsedown/parsedown.php:1478
if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches))
^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function preg_match signature of param matches at testdata/parsedown/parsedown.php:1478
+ if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches))
+ ^^^^^^^^
WARNING notNullSafetyFunctionArgumentArrayDimFetch: potential null array access in parameter subject of function preg_match at testdata/parsedown/parsedown.php:1486
if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*+(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+\/?>/s', $Excerpt['text'], $matches))
^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function preg_match signature of param matches at testdata/parsedown/parsedown.php:1486
+ if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*+(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+\/?>/s', $Excerpt['text'], $matches))
+ ^^^^^^^^
WARNING notNullSafetyFunctionArgumentArrayDimFetch: potential null array access in parameter string of function substr at testdata/parsedown/parsedown.php:1497
if (substr($Excerpt['text'], 1, 1) !== ' ' and strpos($Excerpt['text'], ';') !== false
^^^^^^^^^^^^^^^^
@@ -373,6 +445,9 @@ WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function substr signature of param string at testdata/parsedown/parsedown.php:1832
$before = substr($text, 0, $offset);
^^^^^
+WARNING notSafeCall: potentially not safe call in function substr signature of param length at testdata/parsedown/parsedown.php:1832
+ $before = substr($text, 0, $offset);
+ ^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function substr signature of param string at testdata/parsedown/parsedown.php:1833
$after = substr($text, $offset + strlen($matches[0][0]));
^^^^^
@@ -382,6 +457,9 @@ MAYBE implicitModifiers: Specify the access modifier for \Parsedown::parse met
MAYBE typeHint: Specify the type for the parameter $Element in PHPDoc, 'array' type hint too generic at testdata/parsedown/parsedown.php:1861
protected function sanitiseElement(array $Element)
^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function preg_match signature of param subject at testdata/parsedown/parsedown.php:1885
+ if ( ! preg_match($goodAttribute, $att))
+ ^^^^
WARNING unused: Variable $val is unused (use $_ to ignore this inspection or specify --unused-var-regex flag) at testdata/parsedown/parsedown.php:1882
foreach ($Element['attributes'] as $att => $val)
^^^^
@@ -397,6 +475,9 @@ WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function strlen signature of param string at testdata/parsedown/parsedown.php:1928
if ($len > strlen($string))
^^^^^^^
+WARNING notSafeCall: potentially not safe call in function strtolower signature of param string when calling function \substr at testdata/parsedown/parsedown.php:1934
+ return strtolower(substr($string, 0, $len)) === strtolower($needle);
+ ^^^^^^^^^^^^^^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function substr signature of param string at testdata/parsedown/parsedown.php:1934
return strtolower(substr($string, 0, $len)) === strtolower($needle);
^^^^^^^
diff --git a/src/tests/golden/testdata/phprocksyd/golden.txt b/src/tests/golden/testdata/phprocksyd/golden.txt
index 9439964cd..007c8f92c 100644
--- a/src/tests/golden/testdata/phprocksyd/golden.txt
+++ b/src/tests/golden/testdata/phprocksyd/golden.txt
@@ -31,6 +31,18 @@ WARNING useExitOrDie: Don't use the 'exit' function at testdata/phprocksyd/Phpro
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function stream_socket_accept signature of param socket at testdata/phprocksyd/Phprocksyd.php:200
$client = stream_socket_accept($server, self::ACCEPT_TIMEOUT, $peername);
^^^^^^^
+WARNING notSafeCall: potentially not safe call in function stream_set_read_buffer signature of param stream at testdata/phprocksyd/Phprocksyd.php:207
+ stream_set_read_buffer($client, 0);
+ ^^^^^^^
+WARNING notSafeCall: potentially not safe call in function stream_set_write_buffer signature of param stream at testdata/phprocksyd/Phprocksyd.php:208
+ stream_set_write_buffer($client, 0);
+ ^^^^^^^
+WARNING notSafeCall: potentially not safe call in function stream_set_blocking signature of param stream at testdata/phprocksyd/Phprocksyd.php:209
+ stream_set_blocking($client, 0);
+ ^^^^^^^
+WARNING notSafeCall: potentially not safe call in function stream_set_timeout signature of param stream at testdata/phprocksyd/Phprocksyd.php:210
+ stream_set_timeout($client, self::CONN_TIMEOUT);
+ ^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function pcntl_wait signature of param status at testdata/phprocksyd/Phprocksyd.php:231
$pid = pcntl_wait($status, WNOHANG);
^^^^^^^
@@ -40,6 +52,12 @@ WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function rtrim signature of param string at testdata/phprocksyd/Phprocksyd.php:272
$req = rtrim($req);
^^^^
+WARNING notSafeCall: potentially not safe call in function fwrite signature of param data when calling function \substr at testdata/phprocksyd/Phprocksyd.php:295
+ $wrote = fwrite($this->streams[$stream_id], substr($this->write_buf[$stream_id], 0, 65536));
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function substr signature of param offset at testdata/phprocksyd/Phprocksyd.php:309
+ $this->write_buf[$stream_id] = substr($this->write_buf[$stream_id], $wrote);
+ ^^^^^^
ERROR constCase: Constant 'NULL' should be used in lower case as 'null' at testdata/phprocksyd/Phprocksyd.php:321
$n = stream_select($read, $write, $except, NULL);
^^^^
@@ -79,6 +97,9 @@ WARNING invalidDocblock: Malformed @param $stream_id tag (maybe type is missing?
WARNING invalidDocblock: Malformed @param $req tag (maybe type is missing?) at testdata/phprocksyd/Phprocksyd.php:422
* @param $req
^^^^
+WARNING notSafeCall: potentially not safe call in function posix_kill signature of param process_id at testdata/phprocksyd/Phprocksyd.php:438
+ $result = posix_kill($pid, SIGTERM);
+ ^^^^
WARNING invalidDocblock: Malformed @param $stream_id tag (maybe type is missing?) at testdata/phprocksyd/Phprocksyd.php:443
* @param $stream_id
^^^^^^^^^^
@@ -88,6 +109,15 @@ WARNING invalidDocblock: Malformed @param $req tag (maybe type is missing?) at t
WARNING useExitOrDie: Don't use the 'exit' function at testdata/phprocksyd/Phprocksyd.php:481
exit(0);
^^^^^^^
+WARNING notSafeCall: potentially not safe call in function explode signature of param string when calling function \microtime at testdata/phprocksyd/Phprocksyd.php:473
+ $seed = floor(explode(" ", microtime())[0] * 1e6);
+ ^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function srand signature of param seed at testdata/phprocksyd/Phprocksyd.php:474
+ srand($seed);
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function mt_srand signature of param seed at testdata/phprocksyd/Phprocksyd.php:475
+ mt_srand($seed);
+ ^^^^^
ERROR undefinedMethod: Call to undefined method {mixed}->run() at testdata/phprocksyd/Phprocksyd.php:479
$instance->run($req['params']);
^^^
@@ -97,12 +127,36 @@ WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function
WARNING useExitOrDie: Don't use the 'exit' function at testdata/phprocksyd/Phprocksyd.php:557
exit(1);
^^^^^^^
+WARNING notSafeCall: potentially not safe call in function stream_set_read_buffer signature of param stream at testdata/phprocksyd/Phprocksyd.php:560
+ stream_set_read_buffer($fp, 0);
+ ^^^
+WARNING notSafeCall: potentially not safe call in function stream_set_write_buffer signature of param stream at testdata/phprocksyd/Phprocksyd.php:561
+ stream_set_write_buffer($fp, 0);
+ ^^^
+WARNING notSafeCall: potentially not safe call in function stream_set_blocking signature of param stream at testdata/phprocksyd/Phprocksyd.php:562
+ stream_set_blocking($fp, 0);
+ ^^^
+WARNING notSafeCall: potentially not safe call in function stream_set_timeout signature of param stream at testdata/phprocksyd/Phprocksyd.php:563
+ stream_set_timeout($fp, self::CONN_TIMEOUT);
+ ^^^
WARNING useExitOrDie: Don't use the 'exit' function at testdata/phprocksyd/Phprocksyd.php:590
exit(0);
^^^^^^^
+WARNING notSafeCall: potentially not safe call in function readdir signature of param dir_handle at testdata/phprocksyd/Phprocksyd.php:598
+ while (false !== ($file = readdir($dh))) {
+ ^^^
+WARNING notSafeCall: potentially not safe call in function fclose signature of param stream when calling function \fopen at testdata/phprocksyd/Phprocksyd.php:605
+ fclose(fopen("php://fd/" . $fd, 'r+'));
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
WARNING useExitOrDie: Don't use the 'exit' function at testdata/phprocksyd/Phprocksyd.php:619
exit(1);
^^^^^^^
+WARNING notSafeCall: potentially not safe call in function unserialize signature of param data at testdata/phprocksyd/Phprocksyd.php:638
+ $res = unserialize($contents);
+ ^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function explode signature of param string at testdata/phprocksyd/Phprocksyd.php:667
+ $parts = explode(' ', $contents);
+ ^^^^^^^^^
WARNING useExitOrDie: Don't use the 'exit' function at testdata/phprocksyd/Phprocksyd.php:685
exit(0);
^^^^^^^
@@ -112,9 +166,27 @@ WARNING useExitOrDie: Don't use the 'exit' function at testdata/phprocksyd/Simpl
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function stream_socket_accept signature of param socket at testdata/phprocksyd/Simple.php:65
$client = stream_socket_accept($server, 1, $peername);
^^^^^^^
+WARNING notSafeCall: potentially not safe call in function stream_set_read_buffer signature of param stream at testdata/phprocksyd/Simple.php:72
+ stream_set_read_buffer($client, 0);
+ ^^^^^^^
+WARNING notSafeCall: potentially not safe call in function stream_set_write_buffer signature of param stream at testdata/phprocksyd/Simple.php:73
+ stream_set_write_buffer($client, 0);
+ ^^^^^^^
+WARNING notSafeCall: potentially not safe call in function stream_set_blocking signature of param stream at testdata/phprocksyd/Simple.php:74
+ stream_set_blocking($client, 0);
+ ^^^^^^^
+WARNING notSafeCall: potentially not safe call in function stream_set_timeout signature of param stream at testdata/phprocksyd/Simple.php:75
+ stream_set_timeout($client, 1);
+ ^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function rtrim signature of param string at testdata/phprocksyd/Simple.php:116
$res = json_decode(rtrim($req), true);
^^^^
+WARNING notSafeCall: potentially not safe call in function fwrite signature of param data when calling function \substr at testdata/phprocksyd/Simple.php:132
+ $wrote = fwrite($this->streams[$stream_id], substr($this->write_buf[$stream_id], 0, 65536));
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function substr signature of param offset at testdata/phprocksyd/Simple.php:146
+ $this->write_buf[$stream_id] = substr($this->write_buf[$stream_id], $wrote);
+ ^^^^^^
ERROR constCase: Constant 'NULL' should be used in lower case as 'null' at testdata/phprocksyd/Simple.php:158
$n = stream_select($read, $write, $except, NULL);
^^^^
diff --git a/src/tests/golden/testdata/qrcode/golden.txt b/src/tests/golden/testdata/qrcode/golden.txt
index 3fb44dc4c..d91b856dd 100644
--- a/src/tests/golden/testdata/qrcode/golden.txt
+++ b/src/tests/golden/testdata/qrcode/golden.txt
@@ -1,6 +1,12 @@
MAYBE missingPhpdoc: Missing PHPDoc for \QRCode::output_image public method at testdata/qrcode/qrcode.php:45
public function output_image() {
^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function imagepng signature of param image at testdata/qrcode/qrcode.php:49
+ imagepng($image);
+ ^^^^^^
+WARNING notSafeCall: potentially not safe call in function imagedestroy signature of param image at testdata/qrcode/qrcode.php:50
+ imagedestroy($image);
+ ^^^^^^
MAYBE missingPhpdoc: Missing PHPDoc for \QRCode::render_image public method at testdata/qrcode/qrcode.php:53
public function render_image() {
^^^^^^^^^^^^
@@ -10,27 +16,66 @@ WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function imagecreatetruecolor signature of param height at testdata/qrcode/qrcode.php:56
$image = imagecreatetruecolor($width, $height);
^^^^^^^
+WARNING notSafeCall: potentially not safe call in function imagesavealpha signature of param image at testdata/qrcode/qrcode.php:57
+ imagesavealpha($image, true);
+ ^^^^^^
MAYBE ternarySimplify: Could rewrite as `$this->options['bc'] ?? 'FFFFFF'` at testdata/qrcode/qrcode.php:59
$bgcolor = (isset($this->options['bc']) ? $this->options['bc'] : 'FFFFFF');
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function imagefill signature of param image at testdata/qrcode/qrcode.php:61
+ imagefill($image, 0, 0, $bgcolor);
+ ^^^^^^
+WARNING notSafeCall: potentially not safe call in function imagefill signature of param color at testdata/qrcode/qrcode.php:61
+ imagefill($image, 0, 0, $bgcolor);
+ ^^^^^^^^
MAYBE ternarySimplify: Could rewrite as `$this->options['fc'] ?? '000000'` at testdata/qrcode/qrcode.php:63
$fgcolor = (isset($this->options['fc']) ? $this->options['fc'] : '000000');
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function floor signature of param num at testdata/qrcode/qrcode.php:72
$scale = (($scale > 1) ? floor($scale) : 1);
^^^^^^
+WARNING notSafeCall: potentially not safe call in function imagefilledrectangle signature of param image at testdata/qrcode/qrcode.php:93
+ imagefilledrectangle($image, $rx, $ry, $rx + $rw - 1, $ry + $rh - 1, $mc);
+ ^^^^^^
+WARNING notSafeCall: potentially not safe call in function imagefilledrectangle signature of param x1 at testdata/qrcode/qrcode.php:93
+ imagefilledrectangle($image, $rx, $ry, $rx + $rw - 1, $ry + $rh - 1, $mc);
+ ^^^
+WARNING notSafeCall: potentially not safe call in function imagefilledrectangle signature of param y1 at testdata/qrcode/qrcode.php:93
+ imagefilledrectangle($image, $rx, $ry, $rx + $rw - 1, $ry + $rh - 1, $mc);
+ ^^^
+WARNING notSafeCall: potentially not safe call in function imagefilledrectangle signature of param color at testdata/qrcode/qrcode.php:93
+ imagefilledrectangle($image, $rx, $ry, $rx + $rw - 1, $ry + $rh - 1, $mc);
+ ^^^
+WARNING notSafeCall: potentially not safe call in function hexdec signature of param hex_string when calling function \substr at testdata/qrcode/qrcode.php:134
+ $r = hexdec(substr($color, 0, 2));
+ ^^^^^^^^^^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function substr signature of param string at testdata/qrcode/qrcode.php:134
$r = hexdec(substr($color, 0, 2));
^^^^^^
+WARNING notSafeCall: potentially not safe call in function hexdec signature of param hex_string when calling function \substr at testdata/qrcode/qrcode.php:135
+ $g = hexdec(substr($color, 2, 2));
+ ^^^^^^^^^^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function substr signature of param string at testdata/qrcode/qrcode.php:135
$g = hexdec(substr($color, 2, 2));
^^^^^^
+WARNING notSafeCall: potentially not safe call in function hexdec signature of param hex_string when calling function \substr at testdata/qrcode/qrcode.php:136
+ $b = hexdec(substr($color, 4, 2));
+ ^^^^^^^^^^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function substr signature of param string at testdata/qrcode/qrcode.php:136
$b = hexdec(substr($color, 4, 2));
^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function imagecolorallocate signature of param image at testdata/qrcode/qrcode.php:137
return imagecolorallocate($image, $r, $g, $b);
^^^^^^
+WARNING notSafeCall: potentially not safe call in function imagecolorallocate signature of param red at testdata/qrcode/qrcode.php:137
+ return imagecolorallocate($image, $r, $g, $b);
+ ^^
+WARNING notSafeCall: potentially not safe call in function imagecolorallocate signature of param green at testdata/qrcode/qrcode.php:137
+ return imagecolorallocate($image, $r, $g, $b);
+ ^^
+WARNING notSafeCall: potentially not safe call in function imagecolorallocate signature of param blue at testdata/qrcode/qrcode.php:137
+ return imagecolorallocate($image, $r, $g, $b);
+ ^^
WARNING notNullSafetyFunctionArgumentFunctionCall: not null safety call in function strtolower signature of param string when calling function \preg_replace at testdata/qrcode/qrcode.php:143
switch (strtolower(preg_replace('/[^A-Za-z0-9]/', '', $options['s']))) {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -79,6 +124,9 @@ WARNING switchDefault: Add 'default' branch to avoid unexpected unhandled condit
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function substr signature of param string at testdata/qrcode/qrcode.php:316
$group = substr($data, $i, 3);
^^^^^
+WARNING notSafeCall: potentially not safe call in function strlen signature of param string at testdata/qrcode/qrcode.php:317
+ switch (strlen($group)) {
+ ^^^^^^
WARNING caseBreak: Add break or '// fallthrough' to the end of the case at testdata/qrcode/qrcode.php:318
case 3:
^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -103,12 +151,30 @@ WARNING switchDefault: Add 'default' branch to avoid unexpected unhandled condit
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function substr signature of param string at testdata/qrcode/qrcode.php:359
$group = substr($data, $i, 2);
^^^^^
+WARNING notSafeCall: potentially not safe call in function strlen signature of param string at testdata/qrcode/qrcode.php:360
+ if (strlen($group) > 1) {
+ ^^^^^^
+WARNING notSafeCall: potentially not safe call in function strpos signature of param needle when calling function \substr at testdata/qrcode/qrcode.php:361
+ $c1 = strpos($alphabet, substr($group, 0, 1));
+ ^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function substr signature of param string at testdata/qrcode/qrcode.php:361
+ $c1 = strpos($alphabet, substr($group, 0, 1));
+ ^^^^^^
MAYBE callSimplify: Could simplify to $group[0] at testdata/qrcode/qrcode.php:361
$c1 = strpos($alphabet, substr($group, 0, 1));
^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function strpos signature of param needle when calling function \substr at testdata/qrcode/qrcode.php:362
+ $c2 = strpos($alphabet, substr($group, 1, 1));
+ ^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function substr signature of param string at testdata/qrcode/qrcode.php:362
+ $c2 = strpos($alphabet, substr($group, 1, 1));
+ ^^^^^^
MAYBE callSimplify: Could simplify to $group[1] at testdata/qrcode/qrcode.php:362
$c2 = strpos($alphabet, substr($group, 1, 1));
^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function strpos signature of param needle at testdata/qrcode/qrcode.php:376
+ $ch = strpos($alphabet, $group);
+ ^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function strlen signature of param string at testdata/qrcode/qrcode.php:390
$length = strlen($data);
^^^^^
@@ -118,6 +184,9 @@ WARNING caseBreak: Add break or '// fallthrough' to the end of the case at testd
WARNING switchDefault: Add 'default' branch to avoid unexpected unhandled condition values at testdata/qrcode/qrcode.php:391
switch ($version_group) {
^
+WARNING notSafeCall: potentially not safe call in function ord signature of param character when calling function \substr at testdata/qrcode/qrcode.php:413
+ $ch = ord(substr($data, $i, 1));
+ ^^^^^^^^^^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function substr signature of param string at testdata/qrcode/qrcode.php:413
$ch = ord(substr($data, $i, 1));
^^^^^
@@ -139,12 +208,27 @@ WARNING switchDefault: Add 'default' branch to avoid unexpected unhandled condit
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function substr signature of param string at testdata/qrcode/qrcode.php:447
$group = substr($data, $i, 2);
^^^^^
+WARNING notSafeCall: potentially not safe call in function ord signature of param character when calling function \substr at testdata/qrcode/qrcode.php:448
+ $c1 = ord(substr($group, 0, 1));
+ ^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function substr signature of param string at testdata/qrcode/qrcode.php:448
+ $c1 = ord(substr($group, 0, 1));
+ ^^^^^^
MAYBE callSimplify: Could simplify to $group[0] at testdata/qrcode/qrcode.php:448
$c1 = ord(substr($group, 0, 1));
^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function ord signature of param character when calling function \substr at testdata/qrcode/qrcode.php:449
+ $c2 = ord(substr($group, 1, 1));
+ ^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function substr signature of param string at testdata/qrcode/qrcode.php:449
+ $c2 = ord(substr($group, 1, 1));
+ ^^^^^^
MAYBE callSimplify: Could simplify to $group[1] at testdata/qrcode/qrcode.php:449
$c2 = ord(substr($group, 1, 1));
^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function array_slice signature of param offset at testdata/qrcode/qrcode.php:520
+ $blocks[] = array_slice($data, $offset, $length);
+ ^^^^^^^
WARNING switchDefault: Add 'default' branch to avoid unexpected unhandled condition values at testdata/qrcode/qrcode.php:697
switch ($mask) {
^
diff --git a/src/tests/golden/testdata/twitter-api-php/golden.txt b/src/tests/golden/testdata/twitter-api-php/golden.txt
index 37b41176c..bed316d4f 100644
--- a/src/tests/golden/testdata/twitter-api-php/golden.txt
+++ b/src/tests/golden/testdata/twitter-api-php/golden.txt
@@ -19,12 +19,48 @@ WARNING strictCmp: 3rd argument of in_array must be true when comparing strings
MAYBE trailingComma: Last element in a multi-line array should have a trailing comma at testdata/twitter-api-php/TwitterAPIExchange.php:223
'oauth_version' => '1.0'
^^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe array access in parameter string of function urldecode at testdata/twitter-api-php/TwitterAPIExchange.php:239
+ $oauth[$split[0]] = urldecode($split[1]);
+ ^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function rawurlencode signature of param string at testdata/twitter-api-php/TwitterAPIExchange.php:253
+ $composite_key = rawurlencode($consumer_secret) . '&' . rawurlencode($oauth_access_token_secret);
+ ^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function rawurlencode signature of param string at testdata/twitter-api-php/TwitterAPIExchange.php:253
+ $composite_key = rawurlencode($consumer_secret) . '&' . rawurlencode($oauth_access_token_secret);
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function base64_encode signature of param string when calling function \hash_hmac at testdata/twitter-api-php/TwitterAPIExchange.php:254
+ $oauth_signature = base64_encode(hash_hmac('sha1', $base_info, $composite_key, true));
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
MAYBE invalidDocblockType: Use bool type instead of boolean at testdata/twitter-api-php/TwitterAPIExchange.php:267
* @param boolean $return If true, returns data. This is left in for backward compatibility reasons
^^^^^^^
WARNING strictCmp: 3rd argument of in_array must be true when comparing strings at testdata/twitter-api-php/TwitterAPIExchange.php:286
if (in_array(strtolower($this->requestMethod), array('put', 'delete')))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function curl_setopt_array signature of param handle at testdata/twitter-api-php/TwitterAPIExchange.php:312
+ curl_setopt_array($feed, $options);
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function curl_exec signature of param handle at testdata/twitter-api-php/TwitterAPIExchange.php:313
+ $json = curl_exec($feed);
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function curl_getinfo signature of param handle at testdata/twitter-api-php/TwitterAPIExchange.php:315
+ $this->httpStatusCode = curl_getinfo($feed, CURLINFO_HTTP_CODE);
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function curl_error signature of param handle at testdata/twitter-api-php/TwitterAPIExchange.php:317
+ if (($error = curl_error($feed)) !== '')
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function curl_error signature of param handle at testdata/twitter-api-php/TwitterAPIExchange.php:317
+ if (($error = curl_error($feed)) !== '')
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function curl_close signature of param handle at testdata/twitter-api-php/TwitterAPIExchange.php:319
+ curl_close($feed);
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function curl_close signature of param handle at testdata/twitter-api-php/TwitterAPIExchange.php:324
+ curl_close($feed);
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function rawurlencode signature of param string at testdata/twitter-api-php/TwitterAPIExchange.php:345
+ $return[] = rawurlencode($key) . '=' . rawurlencode($value);
+ ^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function rawurlencode signature of param string at testdata/twitter-api-php/TwitterAPIExchange.php:345
$return[] = rawurlencode($key) . '=' . rawurlencode($value);
^^^^^^
diff --git a/src/tests/golden/testdata/underscore/golden.txt b/src/tests/golden/testdata/underscore/golden.txt
index 9aaf1ba15..9100bc52b 100644
--- a/src/tests/golden/testdata/underscore/golden.txt
+++ b/src/tests/golden/testdata/underscore/golden.txt
@@ -19,6 +19,15 @@ WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function
MAYBE arraySyntax: Use the short form '[]' instead of the old 'array()' at testdata/underscore/underscore.php:113
$return = array();
^^^^^^^
+WARNING notSafeCall: potentially not safe call in function first signature of param collection at testdata/underscore/underscore.php:139
+ list($collection, $function_name) = $__->first($args, 2);
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function map signature of param collection at testdata/underscore/underscore.php:161
+ if(!is_null($iterator)) $collection = $__->map($collection, $iterator);
+ ^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function map signature of param collection at testdata/underscore/underscore.php:177
+ if(!is_null($iterator)) $collection = $__->map($collection, $iterator);
+ ^^^^^^^^^^^
MAYBE arraySyntax: Use the short form '[]' instead of the old 'array()' at testdata/underscore/underscore.php:193
$return = array();
^^^^^^^
@@ -40,12 +49,27 @@ MAYBE arraySyntax: Use the short form '[]' instead of the old 'array()' at tes
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function array_splice signature of param offset at testdata/underscore/underscore.php:262
return self::_wrap(array_splice($collection, $index));
^^^^^^
+WARNING notSafeCall: potentially not safe call in function first signature of param n at testdata/underscore/underscore.php:275
+ return self::_wrap($__->first($collection, $first_index));
+ ^^^^^^^^^^^^
MAYBE arraySyntax: Use the short form '[]' instead of the old 'array()' at testdata/underscore/underscore.php:284
if($n === 0) $result = array();
^^^^^^^
+WARNING notSafeCall: potentially not safe call in function rest signature of param collection at testdata/underscore/underscore.php:288
+ $result = $__->rest($collection, -$n);
+ ^^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function select signature of param collection at testdata/underscore/underscore.php:302
+ return self::_wrap($__->select($collection, function($val) {
+ ^^^^^^^^^^^
MAYBE arraySyntax: Use the short form '[]' instead of the old 'array()' at testdata/underscore/underscore.php:314
$return = array();
^^^^^^^
+WARNING notSafeCall: potentially not safe call in function flatten signature of param collection at testdata/underscore/underscore.php:319
+ $return = array_merge($return, ($shallow) ? $item : $__->flatten($item));
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function rest signature of param collection at testdata/underscore/underscore.php:339
+ $removes = $__->rest($args);
+ ^^^^^
MAYBE arraySyntax: Use the short form '[]' instead of the old 'array()' at testdata/underscore/underscore.php:360
$return = array();
^^^^^^^
@@ -55,6 +79,21 @@ MAYBE arraySyntax: Use the short form '[]' instead of the old 'array()' at tes
WARNING unused: Variable $is_sorted is unused (use $_ to ignore this inspection or specify --unused-var-regex flag) at testdata/underscore/underscore.php:356
list($collection, $is_sorted, $iterator) = self::_wrapArgs(func_get_args(), 3);
^^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function first signature of param collection at testdata/underscore/underscore.php:383
+ $return = $__->first($arrays);
+ ^^^^^^^
+WARNING notSafeCall: potentially not safe call in function rest signature of param collection at testdata/underscore/underscore.php:384
+ foreach($__->rest($arrays) as $next) {
+ ^^^^^^^
+WARNING notSafeCall: potentially not safe call in function isArray signature of param item at testdata/underscore/underscore.php:385
+ if(!$__->isArray($next)) $next = str_split((string) $next);
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function isArray signature of param item at testdata/underscore/underscore.php:385
+ if(!$__->isArray($next)) $next = str_split((string) $next);
+ ^^^^^
+WARNING notSafeCall: potentially not safe call in function reject signature of param collection at testdata/underscore/underscore.php:441
+ $args = $__->reject($args, function($val) {
+ ^^^^^
MAYBE arrayAccess: Array access to non-array type \__|mixed at testdata/underscore/underscore.php:448
list($start, $stop, $step) = array(0, $args[0], 1);
^^^^^
@@ -91,6 +130,12 @@ MAYBE arraySyntax: Use the short form '[]' instead of the old 'array()' at tes
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function range signature of param step at testdata/underscore/underscore.php:458
$results = range($start, $stop, $step);
^^^^^
+WARNING notSafeCall: potentially not safe call in function map signature of param collection at testdata/underscore/underscore.php:475
+ $num_return_arrays = $__->max($__->map($arrays, function($array) {
+ ^^^^^^^
+WARNING notSafeCall: potentially not safe call in function range signature of param stop at testdata/underscore/underscore.php:478
+ $return_arrays = $__->range($num_return_arrays);
+ ^^^^^^^^^^^^^^^^^^
MAYBE arrayAccess: Array access to non-array type \__|mixed at testdata/underscore/underscore.php:480
if(!is_array($return_arrays[$k])) $return_arrays[$k] = array();
^^^^^^^^^^^^^^
@@ -115,9 +160,15 @@ WARNING unused: Variable $v is unused (use $_ to ignore this inspection or speci
MAYBE arraySyntax: Use the short form '[]' instead of the old 'array()' at testdata/underscore/underscore.php:497
$results = array();
^^^^^^^
+WARNING notSafeCall: potentially not safe call in function first signature of param collection when calling function \array_keys at testdata/underscore/underscore.php:503
+ $first_key = $__->first(array_keys($results));
+ ^^^^^^^^^^^^^^^^^^^^
MAYBE arraySyntax: Use the short form '[]' instead of the old 'array()' at testdata/underscore/underscore.php:514
$results = array();
^^^^^^^
+WARNING notSafeCall: potentially not safe call in function first signature of param collection when calling function \array_keys at testdata/underscore/underscore.php:520
+ $first_key = $__->first(array_keys($results));
+ ^^^^^^^^^^^^^^^^^^^^
MAYBE arraySyntax: Use the short form '[]' instead of the old 'array()' at testdata/underscore/underscore.php:529
$results = array();
^^^^^^^
@@ -133,30 +184,48 @@ WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function
MAYBE arraySyntax: Use the short form '[]' instead of the old 'array()' at testdata/underscore/underscore.php:549
if(!array_key_exists($key, $result)) $result[$key] = array();
^^^^^^^
+WARNING notSafeCall: potentially not safe call in function array_slice signature of param offset at testdata/underscore/underscore.php:567
+ $midpoint_values = array_slice($collection, $midpoint, 1);
+ ^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function array_slice signature of param length at testdata/underscore/underscore.php:571
+ $collection = ($calculated_value < $midpoint_calculated_value) ? array_slice($collection, 0, $midpoint, true) : array_slice($collection, $midpoint, null, true);
+ ^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function array_slice signature of param offset at testdata/underscore/underscore.php:571
+ $collection = ($calculated_value < $midpoint_calculated_value) ? array_slice($collection, 0, $midpoint, true) : array_slice($collection, $midpoint, null, true);
+ ^^^^^^^^^
WARNING unused: Variable $__ is unused (use $_ to ignore this inspection or specify --unused-var-regex flag) at testdata/underscore/underscore.php:561
$__ = new self;
^^^
WARNING unused: Variable $args is unused (use $_ to ignore this inspection or specify --unused-var-regex flag) at testdata/underscore/underscore.php:615
$args = self::_wrapArgs(func_get_args(), 1);
^^^^^
+WARNING notSafeCall: potentially not safe call in function rest signature of param collection at testdata/underscore/underscore.php:643
+ $extensions = $__->rest($args);
+ ^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function get_class signature of param object at testdata/underscore/underscore.php:658
return self::_wrap(get_class_methods(get_class($object)));
^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function array_key_exists signature of param key at testdata/underscore/underscore.php:703
return self::_wrap(array_key_exists($key, $collection));
^^^^
-ERROR undefinedProperty: Property {\__|mixed|null}->isEqual does not exist at testdata/underscore/underscore.php:723
+ERROR undefinedProperty: Property {array}->isEqual does not exist at testdata/underscore/underscore.php:723
if(is_object($a) && isset($a->isEqual)) return self::_wrap($a->isEqual($b));
^^^^^^^
-ERROR undefinedProperty: Property {mixed|null}->isEqual does not exist at testdata/underscore/underscore.php:724
+ERROR undefinedMethod: Call to undefined method {array}->isEqual() at testdata/underscore/underscore.php:723
+ if(is_object($a) && isset($a->isEqual)) return self::_wrap($a->isEqual($b));
+ ^^^^^^^
+ERROR undefinedProperty: Property {object}->isEqual does not exist at testdata/underscore/underscore.php:724
if(is_object($b) && isset($b->isEqual)) return self::_wrap($b->isEqual($a));
^^^^^^^
-ERROR undefinedMethod: Call to undefined method {mixed|null}->isEqual() at testdata/underscore/underscore.php:724
+ERROR undefinedMethod: Call to undefined method {object}->isEqual() at testdata/underscore/underscore.php:724
if(is_object($b) && isset($b->isEqual)) return self::_wrap($b->isEqual($a));
^^^^^^^
-MAYBE arrayAccess: Array access to non-array type \__|mixed|null at testdata/underscore/underscore.php:725
- if(is_array($a) && array_key_exists('isEqual', $a)) return self::_wrap($a['isEqual']($b));
- ^^
+WARNING notSafeCall: potentially not safe call in function keys signature of param collection at testdata/underscore/underscore.php:731
+ $keys_equal = $__->isEqual($__->keys($a), $__->keys($b));
+ ^^
+WARNING notSafeCall: potentially not safe call in function values signature of param collection at testdata/underscore/underscore.php:732
+ $values_equal = $__->isEqual($__->values($a), $__->values($b));
+ ^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function is_nan signature of param num at testdata/underscore/underscore.php:772
return self::_wrap((is_int($item) || is_float($item)) && !is_nan($item) && !is_infinite($item));
^^^^^
@@ -184,9 +253,15 @@ ERROR undefinedProperty: Property {mixed}->_mixins does not exist at testdata/
ERROR undefinedProperty: Property {mixed}->_mixins does not exist at testdata/underscore/underscore.php:853
$mixins =& self::getInstance()->_mixins;
^^^^^^^
+WARNING notSafeCall: potentially not safe array access in parameter callback of function call_user_func_array at testdata/underscore/underscore.php:854
+ return call_user_func_array($mixins[$name], $arguments);
+ ^^^^^^^^^^^^^^
ERROR undefinedProperty: Property {mixed}->_mixins does not exist at testdata/underscore/underscore.php:859
$mixins =& self::getInstance()->_mixins;
^^^^^^^
+WARNING notSafeCall: potentially not safe array access in parameter callback of function call_user_func_array at testdata/underscore/underscore.php:861
+ return call_user_func_array($mixins[$name], $arguments);
+ ^^^^^^^^^^^^^^
ERROR undefinedProperty: Property {mixed}->_template_settings does not exist at testdata/underscore/underscore.php:882
$_template_settings =& self::getInstance()->_template_settings;
^^^^^^^^^^^^^^^^^^
@@ -211,12 +286,18 @@ WARNING notNullSafetyFunctionArgumentArrayDimFetch: potential null array access
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function preg_match_all signature of param subject at testdata/underscore/underscore.php:920
preg_match_all($ts['interpolate'], $code, $vars, PREG_SET_ORDER);
^^^^^
+WARNING notSafeCall: potentially not safe call in function preg_match_all signature of param matches at testdata/underscore/underscore.php:920
+ preg_match_all($ts['interpolate'], $code, $vars, PREG_SET_ORDER);
+ ^^^^^
WARNING notNullSafetyFunctionArgumentArrayDimFetch: potential null array access in parameter pattern of function preg_match_all at testdata/underscore/underscore.php:927
preg_match_all($ts['evaluate'], $code, $vars, PREG_SET_ORDER);
^^^^^^^^^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function preg_match_all signature of param subject at testdata/underscore/underscore.php:927
preg_match_all($ts['evaluate'], $code, $vars, PREG_SET_ORDER);
^^^^^
+WARNING notSafeCall: potentially not safe call in function preg_match_all signature of param matches at testdata/underscore/underscore.php:927
+ preg_match_all($ts['evaluate'], $code, $vars, PREG_SET_ORDER);
+ ^^^^^
MAYBE deprecated: Call to deprecated function create_function (since: 7.2, reason: Use anonymous functions instead, removed: 8.0) at testdata/underscore/underscore.php:940
$func = create_function('$context', $code);
^^^^^^^^^^^^^^^
@@ -253,6 +334,12 @@ MAYBE arraySyntax: Use the short form '[]' instead of the old 'array()' at tes
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function call_user_func_array signature of param callback at testdata/underscore/underscore.php:1037
return call_user_func_array($wrapper, $args);
^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function call_user_func_array signature of param callback at testdata/underscore/underscore.php:1049
+ $args[0] = call_user_func_array($function, $args);
+ ^^^^^^^^^
+WARNING notSafeCall: potentially not safe call in function md5 signature of param string when calling function \mt_rand at testdata/underscore/underscore.php:1062
+ $key = md5(mt_rand());
+ ^^^^^^^^^
WARNING notNullSafetyFunctionArgumentVariable: not null safety call in function call_user_func_array signature of param callback at testdata/underscore/underscore.php:1068
if($_instance->_aftered[$key] >= $count) return call_user_func_array($function, func_get_args());
^^^^^^^^^
diff --git a/src/types/lazy.go b/src/types/lazy.go
index 2d81df7f3..77876e85e 100644
--- a/src/types/lazy.go
+++ b/src/types/lazy.go
@@ -121,8 +121,12 @@ func wrap(typ byte, byteFields []uint8, args ...string) string {
return string(buf)
}
-func unwrap1(s string) (one string) {
- return s[stringLenBytes+1:] // do not care about length, there is only 1 param
+func unwrap1(s string) string {
+ if len(s) > stringLenBytes+1 {
+ return s[stringLenBytes+1:]
+ }
+
+ return ""
}
func unwrap2(s string) (one, two string) {
diff --git a/src/types/map.go b/src/types/map.go
index f942d81ba..dafe8f3bb 100644
--- a/src/types/map.go
+++ b/src/types/map.go
@@ -122,6 +122,14 @@ func NewMap(str string) Map {
return Map{m: m}
}
+func (m Map) Keys() []string {
+ keys := make([]string, 0, len(m.m))
+ for k := range m.m {
+ keys = append(keys, k)
+ }
+ return keys
+}
+
func NewPreciseMap(str string) Map {
m := NewMap(str)
m.flags |= mapPrecise
@@ -180,6 +188,36 @@ func (m Map) Equals(m2 Map) bool {
return true
}
+// Union return new Map that contains all types from the current and other
+func (m Map) Union(other Map) Map {
+ result := make(map[string]struct{}, len(m.m)+len(other.m))
+ for k := range m.m {
+ result[k] = struct{}{}
+ }
+ for k := range other.m {
+ result[k] = struct{}{}
+ }
+
+ return Map{
+ flags: m.flags,
+ m: result,
+ }
+}
+
+// Intersect return new Map that contains only intersect types
+func (m Map) Intersect(other Map) Map {
+ result := make(map[string]struct{})
+ for k := range m.m {
+ if _, ok := other.m[k]; ok {
+ result[k] = struct{}{}
+ }
+ }
+ return Map{
+ flags: m.flags,
+ m: result,
+ }
+}
+
// Len returns number of different types in map
func (m Map) Len() int {
return len(m.m)
@@ -224,6 +262,31 @@ func (m Map) IsArray() bool {
return false
}
+func (m Map) IsClass() bool {
+ if len(m.m) != 1 {
+ return false
+ }
+ typ := m.String()
+
+ return strings.HasPrefix(typ, `\`) && !IsShape(typ) && !IsArray(typ) && !IsClosure(typ) && !IsScalar(typ)
+}
+
+func (m Map) IsBoolean() bool {
+ if len(m.m) != 1 {
+ return false
+ }
+
+ // we can have type with flags like 0400bool
+ for typ := range m.m {
+ if UnwrapElemOf(typ) == "bool" || strings.HasSuffix(typ, "bool") ||
+ strings.HasSuffix(typ, "true") ||
+ strings.HasSuffix(typ, "false") {
+ return true
+ }
+ }
+ return false
+}
+
// Is reports whether m contains exactly one specified type.
//
// Warning: typ must be a proper *lazy* or *solved* type.
diff --git a/src/types/map_test.go b/src/types/map_test.go
index 6762a62d6..514d00e27 100644
--- a/src/types/map_test.go
+++ b/src/types/map_test.go
@@ -47,3 +47,129 @@ func TestEqualNonMatching(t *testing.T) {
}
}
}
+
+func TestMapUnion(t *testing.T) {
+ tests := []struct {
+ a, b Map
+ expected Map
+ }{
+ {
+ NewMapFromMap(map[string]struct{}{"int": {}, "string": {}}),
+ NewMapFromMap(map[string]struct{}{"float": {}}),
+ NewMapFromMap(map[string]struct{}{"int": {}, "string": {}, "float": {}}),
+ },
+ {
+ NewMapFromMap(map[string]struct{}{"int": {}}),
+ NewMapFromMap(map[string]struct{}{"int": {}}),
+ NewMapFromMap(map[string]struct{}{"int": {}}),
+ },
+ {
+ NewEmptyMap(0),
+ NewMapFromMap(map[string]struct{}{"bool": {}}),
+ NewMapFromMap(map[string]struct{}{"bool": {}}),
+ },
+ {
+ NewEmptyMap(0),
+ NewEmptyMap(0),
+ NewEmptyMap(0),
+ },
+ }
+
+ for _, tt := range tests {
+ result := tt.a.Union(tt.b)
+ if !result.Equals(tt.expected) {
+ t.Errorf("Union failed: %v ∪ %v = %v, expected %v", tt.a, tt.b, result, tt.expected)
+ }
+ }
+}
+
+func TestMapIntersect(t *testing.T) {
+ tests := []struct {
+ a, b Map
+ expected Map
+ }{
+ {
+ NewMapFromMap(map[string]struct{}{"int": {}, "string": {}}),
+ NewMapFromMap(map[string]struct{}{"int": {}, "float": {}}),
+ NewMapFromMap(map[string]struct{}{"int": {}}),
+ },
+ {
+ NewMapFromMap(map[string]struct{}{"bool": {}}),
+ NewMapFromMap(map[string]struct{}{"int": {}}),
+ NewEmptyMap(0),
+ },
+ {
+ NewEmptyMap(0),
+ NewMapFromMap(map[string]struct{}{"string": {}}),
+ NewEmptyMap(0),
+ },
+ {
+ NewMapFromMap(map[string]struct{}{"a": {}, "b": {}}),
+ NewMapFromMap(map[string]struct{}{"a": {}, "b": {}}),
+ NewMapFromMap(map[string]struct{}{"a": {}, "b": {}}),
+ },
+ }
+
+ for _, tt := range tests {
+ result := tt.a.Intersect(tt.b)
+ if !result.Equals(tt.expected) {
+ t.Errorf("Intersect failed: %v ∩ %v = %v, expected %v", tt.a, tt.b, result, tt.expected)
+ }
+ }
+}
+
+func TestMapIsClass(t *testing.T) {
+ testCases := []struct {
+ m Map
+ expected bool
+ }{
+ // Class
+ {NewMapFromMap(map[string]struct{}{`\MyClass`: {}}), true},
+
+ // Array
+ {NewMapFromMap(map[string]struct{}{`\MyClass[]`: {}}), false},
+
+ // Shape
+ {NewMapFromMap(map[string]struct{}{`\shape$foo`: {}}), false},
+
+ // Closure
+ {NewMapFromMap(map[string]struct{}{`\Closure$123`: {}}), false},
+
+ // Scalar
+ {NewMapFromMap(map[string]struct{}{"int": {}}), false},
+
+ // Multi types
+ {NewMapFromMap(map[string]struct{}{`\MyClass`: {}, `int`: {}}), false},
+ }
+
+ for _, tc := range testCases {
+ if got := tc.m.IsClass(); got != tc.expected {
+ t.Errorf("IsClass() = %v, want %v for %v", got, tc.expected, tc.m)
+ }
+ }
+}
+
+func TestMapIsBoolean(t *testing.T) {
+ testCases := []struct {
+ m Map
+ expected bool
+ }{
+ {NewMapFromMap(map[string]struct{}{"bool": {}}), true},
+
+ {NewMapFromMap(map[string]struct{}{"0400bool": {}}), true},
+
+ {NewMapFromMap(map[string]struct{}{"true": {}}), true},
+ {NewMapFromMap(map[string]struct{}{"false": {}}), true},
+
+ {NewMapFromMap(map[string]struct{}{"int": {}}), false},
+ {NewMapFromMap(map[string]struct{}{`\MyClass`: {}}), false},
+
+ {NewMapFromMap(map[string]struct{}{"bool": {}, "int": {}}), false},
+ }
+
+ for _, tc := range testCases {
+ if got := tc.m.IsBoolean(); got != tc.expected {
+ t.Errorf("IsBoolean() = %v, want %v for %v", got, tc.expected, tc.m)
+ }
+ }
+}
diff --git a/src/types/predicates.go b/src/types/predicates.go
index 3c7b28489..56d330ccd 100644
--- a/src/types/predicates.go
+++ b/src/types/predicates.go
@@ -32,6 +32,10 @@ func IsTrivial(s string) bool {
return trivial[s]
}
+func IsScalar(s string) bool {
+ return scalar[s]
+}
+
func IsAlias(s string) bool {
_, has := aliases[s]
return has
@@ -81,6 +85,16 @@ var trivial = map[string]bool{
"false": true,
}
+var scalar = map[string]bool{
+ "bool": true,
+ "float": true,
+ "int": true,
+ "string": true,
+
+ "true": true,
+ "false": true,
+}
+
var aliases = map[string]string{
"integer": "int",
"long": "int",