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",