Turn the warning for uninitialized (result) variables into errors

This commit is contained in:
Zahary Karadjov 2020-03-29 02:06:39 +02:00 • committed by Andreas Rumpf
commit 1b570f2b18
7 changed files with 103 additions and 17 deletions

View file

@ -1396,6 +1396,9 @@ proc copyType*(t: PType, owner: PSym, keepId: bool): PType =
proc exactReplica*(t: PType): PType = copyType(t, t.owner, true) proc exactReplica*(t: PType): PType = copyType(t, t.owner, true)
template requiresInit*(t: PType): bool =
t.flags * {tfRequiresInit, tfHasRequiresInit, tfNotNil} != {}
proc copySym*(s: PSym): PSym = proc copySym*(s: PSym): PSym =
result = newSym(s.kind, s.name, s.owner, s.info, s.options) result = newSym(s.kind, s.name, s.owner, s.info, s.options)
#result.ast = nil # BUGFIX; was: s.ast which made problems #result.ast = nil # BUGFIX; was: s.ast which made problems

View file

@ -23,6 +23,7 @@ type
errGeneralParseError, errGeneralParseError,
errNewSectionExpected, errNewSectionExpected,
errInvalidDirectiveX, errInvalidDirectiveX,
errProveInit,
errGenerated, errGenerated,
errUser, errUser,
warnCannotOpenFile, warnCannotOpenFile,
@ -63,6 +64,7 @@ const
errGeneralParseError: "general parse error", errGeneralParseError: "general parse error",
errNewSectionExpected: "new section expected", errNewSectionExpected: "new section expected",
errInvalidDirectiveX: "invalid directive: '$1'", errInvalidDirectiveX: "invalid directive: '$1'",
errProveInit: "Cannot prove that '$1' is initialized.",
errGenerated: "$1", errGenerated: "$1",
errUser: "$1", errUser: "$1",
warnCannotOpenFile: "cannot open '$1'", warnCannotOpenFile: "cannot open '$1'",

View file

@ -146,8 +146,9 @@ proc fieldsPresentInInitExpr(c: PContext, fieldsRecList, initExpr: PNode): strin
proc missingMandatoryFields(c: PContext, fieldsRecList: PNode, proc missingMandatoryFields(c: PContext, fieldsRecList: PNode,
constrCtx: ObjConstrContext): string = constrCtx: ObjConstrContext): string =
for r in directFieldsInRecList(fieldsRecList): for r in directFieldsInRecList(fieldsRecList):
if constrCtx.requiresFullInit or sfRequiresInit in r.sym.flags or if constrCtx.requiresFullInit or
{tfNotNil, tfRequiresInit, tfHasRequiresInit} * r.sym.typ.flags != {}: sfRequiresInit in r.sym.flags or
r.sym.typ.requiresInit:
let assignment = locateFieldInInitExpr(c, r.sym, constrCtx.initExpr) let assignment = locateFieldInInitExpr(c, r.sym, constrCtx.initExpr)
if assignment == nil: if assignment == nil:
if result.len == 0: if result.len == 0:

View file

@ -184,7 +184,7 @@ proc initVar(a: PEffects, n: PNode; volatileCheck: bool) =
proc initVarViaNew(a: PEffects, n: PNode) = proc initVarViaNew(a: PEffects, n: PNode) =
if n.kind != nkSym: return if n.kind != nkSym: return
let s = n.sym let s = n.sym
if {tfRequiresInit, tfNotNil} * s.typ.flags <= {tfNotNil}: if {tfRequiresInit, tfHasRequiresInit, tfNotNil} * s.typ.flags <= {tfNotNil}:
# 'x' is not nil, but that doesn't mean its "not nil" children # 'x' is not nil, but that doesn't mean its "not nil" children
# are initialized: # are initialized:
initVar(a, n, volatileCheck=true) initVar(a, n, volatileCheck=true)
@ -253,8 +253,8 @@ proc useVar(a: PEffects, n: PNode) =
# If the variable is explicitly marked as .noinit. do not emit any error # If the variable is explicitly marked as .noinit. do not emit any error
a.init.add s.id a.init.add s.id
elif s.id notin a.init: elif s.id notin a.init:
if {tfRequiresInit, tfNotNil} * s.typ.flags != {}: if s.typ.requiresInit:
message(a.config, n.info, warnProveInit, s.name.s) localError(a.config, n.info, errProveInit, s.name.s)
else: else:
message(a.config, n.info, warnUninit, s.name.s) message(a.config, n.info, warnUninit, s.name.s)
# prevent superfluous warnings about the same variable: # prevent superfluous warnings about the same variable:
@ -838,13 +838,13 @@ proc track(tracked: PEffects, n: PNode) =
# may not look like an assignment, but it is: # may not look like an assignment, but it is:
let arg = n[1] let arg = n[1]
initVarViaNew(tracked, arg) initVarViaNew(tracked, arg)
if arg.typ.len != 0 and {tfRequiresInit} * arg.typ.lastSon.flags != {}: if arg.typ.len != 0 and {tfRequiresInit, tfHasRequiresInit} * arg.typ.lastSon.flags != {}:
if a.sym.magic == mNewSeq and n[2].kind in {nkCharLit..nkUInt64Lit} and if a.sym.magic == mNewSeq and n[2].kind in {nkCharLit..nkUInt64Lit} and
n[2].intVal == 0: n[2].intVal == 0:
# var s: seq[notnil]; newSeq(s, 0) is a special case! # var s: seq[notnil]; newSeq(s, 0) is a special case!
discard discard
else: else:
message(tracked.config, arg.info, warnProveInit, $arg) localError(tracked.config, arg.info, errProveInit, $arg)
# check required for 'nim check': # check required for 'nim check':
if n[1].typ.len > 0: if n[1].typ.len > 0:
@ -1203,12 +1203,11 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
createTypeBoundOps(t, typ, param.info) createTypeBoundOps(t, typ, param.info)
if not isEmptyType(s.typ[0]) and if not isEmptyType(s.typ[0]) and
({tfRequiresInit, tfNotNil} * s.typ[0].flags != {} or (s.typ[0].requiresInit or s.typ[0].skipTypes(abstractInst).kind == tyVar) and
s.typ[0].skipTypes(abstractInst).kind == tyVar) and
s.kind in {skProc, skFunc, skConverter, skMethod}: s.kind in {skProc, skFunc, skConverter, skMethod}:
var res = s.ast[resultPos].sym # get result symbol var res = s.ast[resultPos].sym # get result symbol
if res.id notin t.init: if res.id notin t.init:
message(g.config, body.info, warnProveInit, "result") localError(g.config, body.info, errProveInit, "result")
let p = s.ast[pragmasPos] let p = s.ast[pragmasPos]
let raisesSpec = effectSpec(p, wRaises) let raisesSpec = effectSpec(p, wRaises)
if not isNil(raisesSpec): if not isNil(raisesSpec):

View file

@ -335,8 +335,7 @@ proc semIdentDef(c: PContext, n: PNode, kind: TSymKind): PSym =
suggestSym(c.config, info, result, c.graph.usageSym) suggestSym(c.config, info, result, c.graph.usageSym)
proc checkNilable(c: PContext; v: PSym) = proc checkNilable(c: PContext; v: PSym) =
if {sfGlobal, sfImportc} * v.flags == {sfGlobal} and if {sfGlobal, sfImportc} * v.flags == {sfGlobal} and v.typ.requiresInit:
{tfNotNil, tfRequiresInit} * v.typ.flags != {}:
if v.astdef.isNil: if v.astdef.isNil:
message(c.config, v.info, warnProveInit, v.name.s) message(c.config, v.info, warnProveInit, v.name.s)
elif tfNotNil in v.typ.flags and not v.astdef.typ.isNil and tfNotNil notin v.astdef.typ.flags: elif tfNotNil in v.typ.flags and not v.astdef.typ.isNil and tfNotNil notin v.astdef.typ.flags:
@ -485,7 +484,7 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
var a = n[i] var a = n[i]
if c.config.cmd == cmdIdeTools: suggestStmt(c, a) if c.config.cmd == cmdIdeTools: suggestStmt(c, a)
if a.kind == nkCommentStmt: continue if a.kind == nkCommentStmt: continue
if a.kind notin {nkIdentDefs, nkVarTuple, nkConstDef}: illFormedAst(a, c.config) if a.kind notin {nkIdentDefs, nkVarTuple}: illFormedAst(a, c.config)
checkMinSonsLen(a, 3, c.config) checkMinSonsLen(a, 3, c.config)
var typ: PType = nil var typ: PType = nil

View file

@ -139,7 +139,8 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType =
if isPure and (let conflict = strTableInclReportConflict(symbols, e); conflict != nil): if isPure and (let conflict = strTableInclReportConflict(symbols, e); conflict != nil):
wrongRedefinition(c, e.info, e.name.s, conflict.info) wrongRedefinition(c, e.info, e.name.s, conflict.info)
inc(counter) inc(counter)
if tfNotNil in e.typ.flags and not hasNull: incl(result.flags, tfRequiresInit) if tfNotNil in e.typ.flags and not hasNull:
result.flags.incl tfRequiresInit
proc semSet(c: PContext, n: PNode, prev: PType): PType = proc semSet(c: PContext, n: PNode, prev: PType): PType =
result = newOrPrevType(tySet, prev, c) result = newOrPrevType(tySet, prev, c)
@ -769,6 +770,8 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int,
f.typ = typ f.typ = typ
f.position = pos f.position = pos
f.options = c.config.options f.options = c.config.options
if sfRequiresInit in f.flags:
rectype.flags.incl tfHasRequiresInit
if fieldOwner != nil and if fieldOwner != nil and
{sfImportc, sfExportc} * fieldOwner.flags != {} and {sfImportc, sfExportc} * fieldOwner.flags != {} and
not hasCaseFields and f.loc.r == nil: not hasCaseFields and f.loc.r == nil:

View file

@ -32,10 +32,14 @@ type
a {.requiresInit.}: int a {.requiresInit.}: int
b: string b: string
PartialRequiresInitRef = ref PartialRequiresInit
FullRequiresInit {.requiresInit.} = object FullRequiresInit {.requiresInit.} = object
a: int a: int
b: int b: int
FullRequiresInitRef = ref FullRequiresInit
FullRequiresInitWithParent {.requiresInit.} = object of THasNotNils FullRequiresInitWithParent {.requiresInit.} = object of THasNotNils
e: int e: int
d: int d: int
@ -72,8 +76,13 @@ var nilRef: TRefObj
var notNilRef = TRefObj(x: 20) var notNilRef = TRefObj(x: 20)
proc makeHasNotNils: ref THasNotNils = proc makeHasNotNils: ref THasNotNils =
result.a = TRefObj(x: 10) (ref THasNotNils)(a: TRefObj(x: 10),
result.b = TRefObj(x: 20) b: TRefObj(x: 20))
proc userDefinedDefault(T: typedesc): T =
# We'll use that to make sure the user cannot cheat
# with constructing requiresInit types
discard
accept TObj() accept TObj()
accept TObj(choice: A) accept TObj(choice: A)
@ -125,7 +134,17 @@ accept PartialRequiresInit(a: 10, b: "x")
accept PartialRequiresInit(a: 20) accept PartialRequiresInit(a: 20)
reject PartialRequiresInit(b: "x") reject PartialRequiresInit(b: "x")
reject PartialRequiresInit() reject PartialRequiresInit()
accept PartialRequiresInitRef(a: 10, b: "x")
accept PartialRequiresInitRef(a: 20)
reject PartialRequiresInitRef(b: "x")
reject PartialRequiresInitRef()
accept((ref PartialRequiresInit)(a: 10, b: "x"))
accept((ref PartialRequiresInit)(a: 20))
reject((ref PartialRequiresInit)(b: "x"))
reject((ref PartialRequiresInit)())
reject default(PartialRequiresInit) reject default(PartialRequiresInit)
reject userDefinedDefault(PartialRequiresInit)
reject: reject:
var obj: PartialRequiresInit var obj: PartialRequiresInit
@ -133,7 +152,17 @@ accept FullRequiresInit(a: 10, b: 20)
reject FullRequiresInit(a: 10) reject FullRequiresInit(a: 10)
reject FullRequiresInit(b: 20) reject FullRequiresInit(b: 20)
reject FullRequiresInit() reject FullRequiresInit()
accept FullRequiresInitRef(a: 10, b: 20)
reject FullRequiresInitRef(a: 10)
reject FullRequiresInitRef(b: 20)
reject FullRequiresInitRef()
accept((ref FullRequiresInit)(a: 10, b: 20))
reject((ref FullRequiresInit)(a: 10))
reject((ref FullRequiresInit)(b: 20))
reject((ref FullRequiresInit)())
reject default(FullRequiresInit) reject default(FullRequiresInit)
reject userDefinedDefault(FullRequiresInit)
reject: reject:
var obj: FullRequiresInit var obj: FullRequiresInit
@ -144,6 +173,7 @@ reject FullRequiresInitWithParent(a: notNilRef, b: notNilRef, e: 10, d: 20) #
reject FullRequiresInitWithParent(a: notNilRef, b: notNilRef, c: nil, e: 10) # d should not be missing reject FullRequiresInitWithParent(a: notNilRef, b: notNilRef, c: nil, e: 10) # d should not be missing
reject FullRequiresInitWithParent() reject FullRequiresInitWithParent()
reject default(FullRequiresInitWithParent) reject default(FullRequiresInitWithParent)
reject userDefinedDefault(FullRequiresInitWithParent)
reject: reject:
var obj: FullRequiresInitWithParent var obj: FullRequiresInitWithParent
@ -153,6 +183,55 @@ accept default(TNestedChoices)
accept: accept:
var obj: TNestedChoices var obj: TNestedChoices
reject:
# This proc is illegal, because it tries to produce
# a default object of a type that requires initialization:
proc defaultHasNotNils: THasNotNils =
discard
reject:
# You cannot cheat by using the result variable to specify
# only some of the fields
proc invalidPartialTHasNotNils: THasNotNils =
result.c = nilRef
reject:
# The same applies for requiresInit types
proc invalidPartialRequiersInit: PartialRequiresInit =
result.b = "x"
# All code paths must return a value when the result requires initialization:
reject:
proc ifWithoutAnElse: THasNotNils =
if stdin.readLine == "":
return THasNotNils(a: notNilRef, b: notNilRef, c: nilRef)
accept:
# All code paths must return a value when the result requires initialization:
proc wellFormedIf: THasNotNils =
if stdin.readLine == "":
return THasNotNils(a: notNilRef, b: notNilRef, c: nilRef)
else:
return THasNotNIls(a: notNilRef, b: notNilRef)
reject:
proc caseWithoutAllCasesCovered: FullRequiresInit =
# Please note that these is no else branch here:
case stdin.readLine
of "x":
return FullRequiresInit(a: 10, b: 20)
of "y":
return FullRequiresInit(a: 30, b: 40)
accept:
proc wellFormedCase: FullRequiresInit =
case stdin.readLine
of "x":
result = FullRequiresInit(a: 10, b: 20)
else:
# Mixing result and return is fine:
return FullRequiresInit(a: 30, b: 40)
# but if we supply a run-time value for the inner branch, the compiler won't be able to prove # but if we supply a run-time value for the inner branch, the compiler won't be able to prove
# that the notnil field was initialized # that the notnil field was initialized
reject TNestedChoices(outerChoice: false, innerChoice: x) # XXX: The error message is not very good here reject TNestedChoices(outerChoice: false, innerChoice: x) # XXX: The error message is not very good here