From da29222f86f7689227ffe12605842d18c9bf0fc1 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 23 Jun 2020 10:53:57 +0200 Subject: init checks and 'out' parameters (#14521) * I don't care about observable stores * enforce explicit initializations * cleaner code for the stdlib * stdlib: use explicit initializations * make tests green * algorithm.nim: set result explicitly * remove out parameters and bring the PR into a mergable state * updated the changelog --- compiler/ast.nim | 12 ++- compiler/ccgcalls.nim | 14 +-- compiler/ccgexprs.nim | 12 +-- compiler/ccgtypes.nim | 2 +- compiler/dfa.nim | 6 +- compiler/guards.nim | 2 +- compiler/jsgen.nim | 2 +- compiler/lineinfos.nim | 2 +- compiler/lowerings.nim | 2 +- compiler/nim.cfg | 4 + compiler/nimsets.nim | 32 +++---- compiler/parampatterns.nim | 6 +- compiler/parser.nim | 6 +- compiler/renderer.nim | 8 +- compiler/renderverbatim.nim | 1 + compiler/semcall.nim | 2 +- compiler/semexprs.nim | 6 +- compiler/semmagic.nim | 3 - compiler/semobjconstr.nim | 2 +- compiler/sempass2.nim | 219 +++++++++++++++++++++++++------------------- compiler/semstmts.nim | 18 +--- compiler/semtypes.nim | 8 +- compiler/sigmatch.nim | 12 +-- compiler/spawn.nim | 4 +- compiler/transf.nim | 10 +- compiler/types.nim | 2 +- compiler/writetracking.nim | 6 +- 27 files changed, 210 insertions(+), 193 deletions(-) (limited to 'compiler') diff --git a/compiler/ast.nim b/compiler/ast.nim index 9639f52d3..66a1a253b 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -435,7 +435,8 @@ type # be any type. tyOptDeprecated - # deadcode: was `tyOpt`, Builtin optional type + # 'out' parameter. Comparable to a 'var' parameter but every + # path must assign a value to it before it can be read from. tyVoid # now different from tyEmpty, hurray! @@ -1802,12 +1803,12 @@ proc skipStmtList*(n: PNode): PNode = else: result = n -proc toVar*(typ: PType): PType = +proc toVar*(typ: PType; kind: TTypeKind): PType = ## If ``typ`` is not a tyVar then it is converted into a `var ` and ## returned. Otherwise ``typ`` is simply returned as-is. result = typ - if typ.kind != tyVar: - result = newType(tyVar, typ.owner) + if typ.kind != kind: + result = newType(kind, typ.owner) rawAddSon(result, typ) proc toRef*(typ: PType): PType = @@ -1934,3 +1935,6 @@ proc toHumanStr*(kind: TSymKind): string = proc toHumanStr*(kind: TTypeKind): string = ## strips leading `tk` result = toHumanStrImpl(kind, 2) + +proc skipAddr*(n: PNode): PNode {.inline.} = + (if n.kind == nkHiddenAddr: n[0] else: n) diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim index 3dd2b54d4..80f02b6c3 100644 --- a/compiler/ccgcalls.nim +++ b/compiler/ccgcalls.nim @@ -178,10 +178,10 @@ proc openArrayLoc(p: BProc, formalType: PType, n: PNode): Rope = result = "($4*)($1)+($2), ($3)-($2)+1" % [rdLoc(a), rdLoc(b), rdLoc(c), dest] of tyString, tySequence: let atyp = skipTypes(a.t, abstractInst) - if formalType.skipTypes(abstractInst).kind == tyVar and atyp.kind == tyString and + if formalType.skipTypes(abstractInst).kind in {tyVar} and atyp.kind == tyString and optSeqDestructors in p.config.globalOptions: linefmt(p, cpsStmts, "#nimPrepareStrMutationV2($1);$n", [byRefLoc(p, a)]) - if atyp.kind == tyVar and not compileToCpp(p.module): + if atyp.kind in {tyVar} and not compileToCpp(p.module): result = "($5*)(*$1)$4+($2), ($3)-($2)+1" % [rdLoc(a), rdLoc(b), rdLoc(c), dataField(p), dest] else: result = "($5*)$1$4+($2), ($3)-($2)+1" % [rdLoc(a), rdLoc(b), rdLoc(c), dataField(p), dest] @@ -194,10 +194,10 @@ proc openArrayLoc(p: BProc, formalType: PType, n: PNode): Rope = result = "$1, $1Len_0" % [rdLoc(a)] of tyString, tySequence: let ntyp = skipTypes(n.typ, abstractInst) - if formalType.skipTypes(abstractInst).kind == tyVar and ntyp.kind == tyString and + if formalType.skipTypes(abstractInst).kind in {tyVar} and ntyp.kind == tyString and optSeqDestructors in p.config.globalOptions: linefmt(p, cpsStmts, "#nimPrepareStrMutationV2($1);$n", [byRefLoc(p, a)]) - if ntyp.kind == tyVar and not compileToCpp(p.module): + if ntyp.kind in {tyVar} and not compileToCpp(p.module): var t: TLoc t.r = "(*$1)" % [a.rdLoc] result = "(*$1)$3, $2" % [a.rdLoc, lenExpr(p, t), dataField(p)] @@ -232,7 +232,7 @@ proc genArg(p: BProc, n: PNode, param: PSym; call: PNode): Rope = elif ccgIntroducedPtr(p.config, param, call[0].typ[0]): initLocExpr(p, n, a) result = addrLoc(p.config, a) - elif p.module.compileToCpp and param.typ.kind == tyVar and + elif p.module.compileToCpp and param.typ.kind in {tyVar} and n.kind == nkHiddenAddr: initLocExprSingleUse(p, n[0], a) # if the proc is 'importc'ed but not 'importcpp'ed then 'var T' still @@ -372,7 +372,7 @@ proc genOtherArg(p: BProc; ri: PNode; i: int; typ: PType): Rope = assert(paramType.kind == nkSym) if paramType.typ.isCompileTimeOnly: result = nil - elif typ[i].kind == tyVar and ri[i].kind == nkHiddenAddr: + elif typ[i].kind in {tyVar} and ri[i].kind == nkHiddenAddr: result = genArgNoParam(p, ri[i][0]) else: result = genArgNoParam(p, ri[i]) #, typ.n[i].sym) @@ -449,7 +449,7 @@ proc genThisArg(p: BProc; ri: PNode; i: int; typ: PType): Rope = var ri = ri[i] while ri.kind == nkObjDownConv: ri = ri[0] let t = typ[i].skipTypes({tyGenericInst, tyAlias, tySink}) - if t.kind == tyVar: + if t.kind in {tyVar}: let x = if ri.kind == nkHiddenAddr: ri[0] else: ri if x.typ.kind == tyPtr: result = genArgNoParam(p, x) diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index 2850ab750..a59529021 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -126,9 +126,8 @@ proc genRawSetData(cs: TBitSet, size: int): Rope = result = intLiteral(cast[BiggestInt](bitSetToWord(cs, size))) proc genSetNode(p: BProc, n: PNode): Rope = - var cs: TBitSet var size = int(getSize(p.config, n.typ)) - toBitSet(p.config, n, cs) + let cs = toBitSet(p.config, n) if size > 8: let id = nodeTableTestOrSet(p.module.dataCache, n, p.module.labels) result = p.module.tmpBase & rope(id) @@ -676,7 +675,7 @@ proc unaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) = proc isCppRef(p: BProc; typ: PType): bool {.inline.} = result = p.module.compileToCpp and - skipTypes(typ, abstractInstOwned).kind == tyVar and + skipTypes(typ, abstractInstOwned).kind in {tyVar} and tfVarIsPtr notin skipTypes(typ, abstractInstOwned).flags proc genDeref(p: BProc, e: PNode, d: var TLoc) = @@ -693,7 +692,7 @@ proc genDeref(p: BProc, e: PNode, d: var TLoc) = if typ.kind in {tyUserTypeClass, tyUserTypeClassInst} and typ.isResolvedUserTypeClass: typ = typ.lastSon typ = typ.skipTypes(abstractInstOwned) - if typ.kind == tyVar and tfVarIsPtr notin typ.flags and p.module.compileToCpp and e[0].kind == nkHiddenAddr: + if typ.kind in {tyVar} and tfVarIsPtr notin typ.flags and p.module.compileToCpp and e[0].kind == nkHiddenAddr: initLocExprSingleUse(p, e[0][0], d) return else: @@ -716,7 +715,7 @@ proc genDeref(p: BProc, e: PNode, d: var TLoc) = else: internalError(p.config, e.info, "genDeref " & $typ.kind) elif p.module.compileToCpp: - if typ.kind == tyVar and tfVarIsPtr notin typ.flags and + if typ.kind in {tyVar} and tfVarIsPtr notin typ.flags and e.kind == nkHiddenDeref: putIntoDest(p, d, e, rdLoc(a), a.storage) return @@ -2960,8 +2959,7 @@ proc genBracedInit(p: BProc, n: PNode; isConst: bool): Rope = ty = skipTypes(n.typ, abstractInstOwned + {tyStatic}).kind case ty of tySet: - var cs: TBitSet - toBitSet(p.config, n, cs) + let cs = toBitSet(p.config, n) result = genRawSetData(cs, int(getSize(p.config, n.typ))) of tySequence: if optSeqDestructors in p.config.globalOptions: diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 199d5c918..11743499d 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -701,7 +701,7 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope = return case t.kind of tyRef, tyPtr, tyVar, tyLent: - var star = if t.kind == tyVar and tfVarIsPtr notin origTyp.flags and + var star = if t.kind in {tyVar} and tfVarIsPtr notin origTyp.flags and compileToCpp(m): "&" else: "*" var et = origTyp.skipTypes(abstractInst).lastSon var etB = et.skipTypes(abstractInst) diff --git a/compiler/dfa.nim b/compiler/dfa.nim index a0ec31ac6..c393b2c81 100644 --- a/compiler/dfa.nim +++ b/compiler/dfa.nim @@ -715,10 +715,8 @@ proc genCall(c: var Con; n: PNode) = for i in 1.. 0 and canRaiseConservative(n[0]): diff --git a/compiler/guards.nim b/compiler/guards.nim index e4f87bae4..a6ca44978 100644 --- a/compiler/guards.nim +++ b/compiler/guards.nim @@ -46,7 +46,7 @@ proc isLet(n: PNode): bool = if n.sym.kind in {skLet, skTemp, skForVar}: result = true elif n.sym.kind == skParam and skipTypes(n.sym.typ, - abstractInst).kind != tyVar: + abstractInst).kind notin {tyVar}: result = true proc isVar(n: PNode): bool = diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index 99d2bc4f2..57a6b8094 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -1053,7 +1053,7 @@ proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) = lineF(p, "$1 = nimCopy(null, $2, $3);$n", [a.rdLoc, b.res, genTypeInfo(p, y.typ)]) of etyObject: - if x.typ.kind == tyVar or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded: + if x.typ.kind in {tyVar} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded: lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc]) else: useMagic(p, "nimCopy") diff --git a/compiler/lineinfos.nim b/compiler/lineinfos.nim index bc3c51d53..6287e21aa 100644 --- a/compiler/lineinfos.nim +++ b/compiler/lineinfos.nim @@ -113,7 +113,7 @@ const warnStaticIndexCheck: "$1", warnGcUnsafe: "not GC-safe: '$1'", warnGcUnsafe2: "$1", - warnUninit: "'$1' might not have been initialized", + warnUninit: "use explicit initialization of '$1' for clarity", warnGcMem: "'$1' uses GC'ed memory", warnDestructor: "usage of a type with a destructor in a non destructible context. This will become a compile time error in the future.", warnLockLevel: "$1", diff --git a/compiler/lowerings.nim b/compiler/lowerings.nim index 575bfe8aa..5e75c36de 100644 --- a/compiler/lowerings.nim +++ b/compiler/lowerings.nim @@ -59,7 +59,7 @@ proc newFastAsgnStmt*(le, ri: PNode): PNode = result[0] = le result[1] = ri -proc newFastMoveStmt*(g: ModuleGraph, le, ri: PNode): PNode = +proc newFastMoveStmt*(g: ModuleGraph, le, ri: PNode): PNode = result = newNodeI(nkFastAsgn, le.info, 2) result[0] = le result[1] = newNodeIT(nkCall, ri.info, ri.typ) diff --git a/compiler/nim.cfg b/compiler/nim.cfg index a77fa84d3..8e733bdf8 100644 --- a/compiler/nim.cfg +++ b/compiler/nim.cfg @@ -21,3 +21,7 @@ define:useStdoutAsStdmsg #define:useNodeIds #gc:markAndSweep + +@if nimHasWarningObservableStores: + warning[ObservableStores]: off +@end diff --git a/compiler/nimsets.nim b/compiler/nimsets.nim index 3876a114e..8683604af 100644 --- a/compiler/nimsets.nim +++ b/compiler/nimsets.nim @@ -58,18 +58,18 @@ proc someInSet*(s: PNode, a, b: PNode): bool = return true result = false -proc toBitSet*(conf: ConfigRef; s: PNode, b: var TBitSet) = +proc toBitSet*(conf: ConfigRef; s: PNode): TBitSet = var first, j: Int128 first = firstOrd(conf, s.typ[0]) - bitSetInit(b, int(getSize(conf, s.typ))) + bitSetInit(result, int(getSize(conf, s.typ))) for i in 0.. 0: result = bracketKind(g, n[0]) - of nkSym: + of nkSym: result = case n.sym.name.s of "[]": bkBracket of "[]=": bkBracketAsgn @@ -974,7 +974,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = put(g, tkParRi, ")") put(g, tkColon, ":") gsub(g, n, n.len-1) - elif n.len >= 1: + elif n.len >= 1: case bracketKind(g, n[0]) of bkBracket: gsub(g, n, 1) diff --git a/compiler/renderverbatim.nim b/compiler/renderverbatim.nim index d20ee1549..2dce6824c 100644 --- a/compiler/renderverbatim.nim +++ b/compiler/renderverbatim.nim @@ -107,6 +107,7 @@ proc extractRunnableExamplesSource*(conf: ConfigRef; n: PNode): string = var indent = info.col let numLines = numLines(conf, info.fileIndex).uint16 var lastNonemptyPos = 0 + result = "" var ldata = LineData(lineFirst: first.line.int, conf: conf) visitMultilineStrings(ldata, n[^1]) diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 665eb4ea4..405de52ee 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -676,7 +676,7 @@ proc searchForBorrowProc(c: PContext, startScope: PScope, fn: PSym): PSym = if t.kind == tyDistinct or param.typ.kind == tyDistinct: hasDistinct = true var x: PType if param.typ.kind == tyVar: - x = newTypeS(tyVar, c) + x = newTypeS(param.typ.kind, c) x.addSonSkipIntLit t.baseOfDistinct else: x = t.baseOfDistinct diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 200d1d60a..6f267b4eb 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -712,7 +712,7 @@ proc analyseIfAddressTakenInCall(c: PContext, n: PNode) = # note sometimes this is eval'ed twice so we check for nkHiddenAddr here: for i in 1.. 0: @@ -257,20 +270,11 @@ proc useVar(a: PEffects, n: PNode) = elif s.id notin a.init: if s.typ.requiresInit: message(a.config, n.info, warnProveInit, s.name.s) - else: + elif a.leftPartOfAsgn <= 0: message(a.config, n.info, warnUninit, s.name.s) # prevent superfluous warnings about the same variable: a.init.add s.id - if {sfGlobal, sfThread} * s.flags != {} and s.kind in {skVar, skLet} and - s.magic != mNimvm: - if s.guard != nil: guardGlobal(a, n, s.guard) - if {sfGlobal, sfThread} * s.flags == {sfGlobal} and - (tfHasGCedMem in s.typ.flags or s.typ.isGCedMem): - #if a.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n) - markGcUnsafe(a, s) - markSideEffect(a, s) - else: - markSideEffect(a, s) + useVarNoInitCheck(a, n, s) type @@ -538,7 +542,7 @@ proc isNoEffectList(n: PNode): bool {.inline.} = proc isTrival(caller: PNode): bool {.inline.} = result = caller.kind == nkSym and caller.sym.magic in {mEqProc, mIsNil, mMove, mWasMoved} -proc trackOperand(tracked: PEffects, n: PNode, paramType: PType; caller: PNode) = +proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, paramType: PType; caller: PNode) = let a = skipConvAndClosure(n) let op = a.typ # assume indirect calls are taken here: @@ -572,7 +576,7 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType; caller: PNode) markGcUnsafe(tracked, a) elif tfNoSideEffect notin op.flags: markSideEffect(tracked, a) - if paramType != nil and paramType.kind == tyVar: + if paramType != nil and paramType.kind in {tyVar}: invalidateFacts(tracked.guards, n) if n.kind == nkSym and isLocalVar(tracked, n.sym): makeVolatile(tracked, n.sym) @@ -732,7 +736,7 @@ proc createTypeBoundOps(tracked: PEffects, typ: PType; info: TLineInfo) = optSeqDestructors in tracked.config.globalOptions: tracked.owner.flags.incl sfInjectDestructors -proc track(tracked: PEffects, n: PNode) = +proc trackCall(tracked: PEffects; n: PNode) = template gcsafeAndSideeffectCheck() = if notGcSafe(op) and not importedFromC(a): # and it's not a recursive call: @@ -744,11 +748,105 @@ proc track(tracked: PEffects, n: PNode) = if not (a.kind == nkSym and a.sym == tracked.owner): markSideEffect(tracked, a) + # p's effects are ours too: + var a = n[0] + #if canRaise(a): + # echo "this can raise ", tracked.config $ n.info + let op = a.typ + if n.typ != nil: + if tracked.owner.kind != skMacro and n.typ.skipTypes(abstractVar).kind != tyOpenArray: + createTypeBoundOps(tracked, n.typ, n.info) + if getConstExpr(tracked.ownerModule, n, tracked.graph) != nil: + return + if a.kind == nkCast and a[1].typ.kind == tyProc: + a = a[1] + # XXX: in rare situations, templates and macros will reach here after + # calling getAst(templateOrMacro()). Currently, templates and macros + # are indistinguishable from normal procs (both have tyProc type) and + # we can detect them only by checking for attached nkEffectList. + if op != nil and op.kind == tyProc and op.n[0].kind == nkEffectList: + if a.kind == nkSym: + if a.sym == tracked.owner: tracked.isRecursive = true + # even for recursive calls we need to check the lock levels (!): + mergeLockLevels(tracked, n, a.sym.getLockLevel) + if sfSideEffect in a.sym.flags: markSideEffect(tracked, a) + else: + mergeLockLevels(tracked, n, op.lockLevel) + var effectList = op.n[0] + if a.kind == nkSym and a.sym.kind == skMethod: + propagateEffects(tracked, n, a.sym) + elif isNoEffectList(effectList): + if isForwardedProc(a): + propagateEffects(tracked, n, a.sym) + elif isIndirectCall(a, tracked.owner): + assumeTheWorst(tracked, n, op) + gcsafeAndSideeffectCheck() + else: + mergeEffects(tracked, effectList[exceptionEffects], n) + mergeTags(tracked, effectList[tagEffects], n) + gcsafeAndSideeffectCheck() + if a.kind != nkSym or a.sym.magic != mNBindSym: + for i in 1.. 0: + createTypeBoundOps(tracked, n[1].typ.lastSon, n.info) + createTypeBoundOps(tracked, n[1].typ, n.info) + # new(x, finalizer): Problem: how to move finalizer into 'createTypeBoundOps'? + + elif a.kind == nkSym and a.sym.magic in {mArrGet, mArrPut} and + optStaticBoundsCheck in tracked.currOptions: + checkBounds(tracked, n[1], n[2]) + + if a.kind == nkSym and a.sym.name.s.len > 0 and a.sym.name.s[0] == '=' and + tracked.owner.kind != skMacro: + let opKind = find(AttachedOpToStr, a.sym.name.s.normalize) + if opKind != -1: + # rebind type bounds operations after createTypeBoundOps call + let t = n[1].typ.skipTypes({tyAlias, tyVar}) + if a.sym != t.attachedOps[TTypeAttachedOp(opKind)]: + createTypeBoundOps(tracked, t, n.info) + let op = t.attachedOps[TTypeAttachedOp(opKind)] + if op != nil: + n[0].sym = op + + if a.kind != nkSym or a.sym.magic != mRunnableExamples: + for i in 0.. 0: - createTypeBoundOps(tracked, n[1].typ.lastSon, n.info) - createTypeBoundOps(tracked, n[1].typ, n.info) - # new(x, finalizer): Problem: how to move finalizer into 'createTypeBoundOps'? - - elif a.kind == nkSym and a.sym.magic in {mArrGet, mArrPut} and - optStaticBoundsCheck in tracked.currOptions: - checkBounds(tracked, n[1], n[2]) - - if a.kind == nkSym and a.sym.name.s.len > 0 and a.sym.name.s[0] == '=' and - tracked.owner.kind != skMacro: - let opKind = find(AttachedOpToStr, a.sym.name.s.normalize) - if opKind != -1: - # rebind type bounds operations after createTypeBoundOps call - let t = n[1].typ.skipTypes({tyAlias, tyVar}) - if a.sym != t.attachedOps[TTypeAttachedOp(opKind)]: - createTypeBoundOps(tracked, t, n.info) - let op = t.attachedOps[TTypeAttachedOp(opKind)] - if op != nil: - n[0].sym = op - - if a.kind != nkSym or a.sym.magic != mRunnableExamples: - for i in 0.. x @@ -569,14 +569,14 @@ proc putArgInto(arg: PNode, formal: PType): TPutArgInto = result = putArgInto(arg[0], formal) of nkCurly, nkBracket: for i in 0..