From 483e5a43dfb184ce2a2ecd95edecb9f7bbb51b6a Mon Sep 17 00:00:00 2001 From: Richardas Kuchinskas Date: Thu, 13 Feb 2025 17:16:30 +0300 Subject: [PATCH] added deep chain catching for checkPropertyFetchNullSafety --- src/linter/block.go | 85 +++++++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 34 deletions(-) diff --git a/src/linter/block.go b/src/linter/block.go index 9f53f431..c5f50edd 100644 --- a/src/linter/block.go +++ b/src/linter/block.go @@ -1138,51 +1138,68 @@ func (b *blockWalker) checkListExprNullSafety(arg ir.Node, fn meta.FuncInfo, par } } +func (b *blockWalker) getPropertyComputedType(expr *ir.PropertyFetchExpr) (meta.ClassInfo, types.Map) { + baseCall, ok := expr.Variable.(*ir.SimpleVar) + if !ok { + return meta.ClassInfo{}, types.Map{} + } + + varInfo, ok := b.ctx.sc.GetVar(baseCall) + if !ok { + return meta.ClassInfo{}, types.Map{} + } + + classInfo, ok := b.r.ctx.st.Info.GetClass(varInfo.Type.String()) + if !ok { + return meta.ClassInfo{}, types.Map{} + } + + property, ok := expr.Property.(*ir.Identifier) + if !ok { + return meta.ClassInfo{}, types.Map{} + } + + propertyInfoFromClass := classInfo.Properties[property.Value] + return classInfo, propertyInfoFromClass.Typ +} + func (b *blockWalker) checkPropertyFetchNullSafety(expr *ir.PropertyFetchExpr, fn meta.FuncInfo, paramIndex int, haveVariadic bool) { - objVar, ok := expr.Variable.(*ir.SimpleVar) + // If the left part of the chain is also a property call, we check it recursively + if nested, ok := expr.Variable.(*ir.PropertyFetchExpr); ok { + b.checkPropertyFetchNullSafety(nested, fn, paramIndex, haveVariadic) + } - if ok { - varInfo, _ := b.ctx.sc.GetVar(objVar) - classInfo, _ := b.r.ctx.st.Info.GetClass(varInfo.Type.String()) - - prp, okPrp := expr.Property.(*ir.Identifier) - - if okPrp { - property := classInfo.Properties[prp.Value] - - isPrpNullable := types.IsTypeNullable(property.Typ) - if haveVariadic { - // If the parameter is outside the declared parameters, we check the latter as a variable - if paramIndex >= len(fn.Params)-1 { - lastParam := fn.Params[len(fn.Params)-1] // last param (variadic ...args) - if types.IsTypeMixed(lastParam.Typ) { - return - } - - paramAllowsNull := types.IsTypeNullable(lastParam.Typ) - if isPrpNullable && !paramAllowsNull { - b.report(expr, LevelError, "notNullSafety", - "potential null dereference when accessing property '%s'", prp.Value) - } - return + classInfo, propType := b.getPropertyComputedType(expr) + if classInfo.Name == "" || propType.Empty() { + return + } - } - } + prp, ok := expr.Property.(*ir.Identifier) + if !ok { + return + } - paramAllowsNull := types.IsTypeNullable(fn.Params[paramIndex].Typ) + isPrpNullable := types.IsTypeNullable(propType) + if haveVariadic { + if paramIndex >= len(fn.Params)-1 { + lastParam := fn.Params[len(fn.Params)-1] + if types.IsTypeMixed(lastParam.Typ) { + return + } + paramAllowsNull := types.IsTypeNullable(lastParam.Typ) if isPrpNullable && !paramAllowsNull { b.report(expr, LevelError, "notNullSafety", "potential null dereference when accessing property '%s'", prp.Value) } - - println(classInfo.Name) + return } + } - // TODO: check difficult chains like $a->b->c->d - /* if nestedProp, ok := expr.Variable.(*ir.PropertyFetchExpr); ok { - b.checkPropertyFetchNullSafety(nestedProp) - }*/ + paramAllowsNull := types.IsTypeNullable(fn.Params[paramIndex].Typ) + if isPrpNullable && !paramAllowsNull { + b.report(expr, LevelError, "notNullSafety", + "potential null dereference when accessing property '%s'", prp.Value) } }