diff options
author | Araq <rumpf_a@web.de> | 2013-06-04 21:58:39 +0200 |
---|---|---|
committer | Araq <rumpf_a@web.de> | 2013-06-04 21:58:39 +0200 |
commit | 2aaa8f7909e51eb3d971e197f152e247c64362e9 (patch) | |
tree | def61be7039f3154d61efca75f996c7e1ccdf1cf /compiler | |
parent | f7c0cc976ddb4e86e2341352b0674b9787005a4a (diff) | |
download | Nim-2aaa8f7909e51eb3d971e197f152e247c64362e9.tar.gz |
implemented dataflow analysis; activate via --warning[Uninit]:on
Diffstat (limited to 'compiler')
-rw-r--r-- | compiler/msgs.nim | 11 | ||||
-rw-r--r-- | compiler/sempass2.nim | 188 | ||||
-rw-r--r-- | compiler/semstmts.nim | 2 |
3 files changed, 176 insertions, 25 deletions
diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 16ef06b61..f75aec0d5 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -106,7 +106,7 @@ type warnUnknownSubstitutionX, warnLanguageXNotSupported, warnCommentXIgnored, warnNilStatement, warnAnalysisLoophole, warnDifferentHeaps, warnWriteToForeignHeap, warnImplicitClosure, - warnEachIdentIsTuple, warnShadowIdent, warnUser, + warnEachIdentIsTuple, warnShadowIdent, warnUninit, warnUser, hintSuccess, hintSuccessX, hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded, hintConvFromXtoItselfNotNeeded, hintExprAlwaysX, hintQuitCalled, @@ -355,6 +355,7 @@ const warnImplicitClosure: "implicit closure convention: '$1' [ImplicitClosure]", warnEachIdentIsTuple: "each identifier is a tuple [EachIdentIsTuple]", warnShadowIdent: "shadowed identifier: '$1' [ShadowIdent]", + warnUninit: "read from potentially uninitialized variable: '$1' [Uninit]", warnUser: "$1 [User]", hintSuccess: "operation successful [Success]", hintSuccessX: "operation successful ($# lines compiled; $# sec total; $#) [SuccessX]", @@ -374,14 +375,15 @@ const hintUser: "$1 [User]"] const - WarningsToStr*: array[0..19, string] = ["CannotOpenFile", "OctalEscape", + WarningsToStr*: array[0..20, string] = ["CannotOpenFile", "OctalEscape", "XIsNeverRead", "XmightNotBeenInit", "Deprecated", "ConfigDeprecated", "SmallLshouldNotBeUsed", "UnknownMagic", "RedefinitionOfLabel", "UnknownSubstitutionX", "LanguageXNotSupported", "CommentXIgnored", "NilStmt", "AnalysisLoophole", "DifferentHeaps", "WriteToForeignHeap", - "ImplicitClosure", "EachIdentIsTuple", "ShadowIdent", "User"] + "ImplicitClosure", "EachIdentIsTuple", "ShadowIdent", "Uninit", + "User"] HintsToStr*: array[0..15, string] = ["Success", "SuccessX", "LineTooLong", "XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded", @@ -512,7 +514,8 @@ proc raiseRecoverableError*(msg: string) {.noinline, noreturn.} = proc sourceLine*(i: TLineInfo): PRope var - gNotes*: TNoteKinds = {low(TNoteKind)..high(TNoteKind)} - {warnShadowIdent} + gNotes*: TNoteKinds = {low(TNoteKind)..high(TNoteKind)} - + {warnShadowIdent, warnUninit} gErrorCounter*: int = 0 # counts the number of errors gHintCounter*: int = 0 gWarnCounter*: int = 0 diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index b78e36531..64aae09c1 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -15,6 +15,7 @@ import # way had some inherent problems. Performs: # # * effect+exception tracking +# * "usage before definition" checking # * checks for invalid usages of compiletime magics (not implemented) # * checks for invalid usages of PNimNode (not implemented) # * later: will do an escape analysis for closures at least @@ -42,14 +43,6 @@ import # an array and filling it in parallel should be supported but is not easily # done: It essentially requires a built-in 'indexSplit' operation and dependent # typing. - -when false: - proc sem2call(c: PContext, n: PNode): PNode = - assert n.kind in nkCallKinds - - proc sem2sym(c: PContext, n: PNode): PNode = - assert n.kind == nkSym - # ------------------------ exception and tag tracking ------------------------- @@ -80,9 +73,44 @@ type tags: PNode # list of tags bottom: int owner: PSym + init: seq[int] # list of initialized variables + # coming soon: "guard" tracking for 'let' variables PEffects = var TEffects +proc isLocalVar(a: PEffects, s: PSym): bool = + s.kind in {skVar, skResult} and sfGlobal notin s.flags and s.owner == a.owner + +proc initVar(a: PEffects, n: PNode) = + if n.kind != nkSym: return + let s = n.sym + if isLocalVar(a, s): + for x in a.init: + if x == s.id: return + a.init.add s.id + +proc useVar(a: PEffects, n: PNode) = + let s = n.sym + if isLocalVar(a, s): + if s.id notin a.init: + if true: + Message(n.info, warnUninit, s.name.s) + else: + Message(n.info, errGenerated, + "read from potentially uninitialized variable: '$1'" % s.name.s) + # prevent superfluous warnings about the same variable: + a.init.add s.id + +type + TIntersection = seq[tuple[id, count: int]] # a simple count table + +proc addToIntersection(inter: var TIntersection, s: int) = + for j in 0.. <inter.len: + if s == inter[j].id: + inc inter[j].count + return + inter.add((id: s, count: 1)) + proc throws(tracked, n: PNode) = if n.typ == nil or n.typ.kind != tyError: tracked.add n @@ -158,21 +186,43 @@ proc track(tracked: PEffects, n: PNode) proc trackTryStmt(tracked: PEffects, n: PNode) = let oldBottom = tracked.bottom tracked.bottom = tracked.exc.len - track(tracked, n.sons[0]) + + let oldState = tracked.init.len + var inter: TIntersection = @[] + + track(tracked, n.sons[0]) + for i in oldState.. <tracked.init.len: + addToIntersection(inter, tracked.init[i]) + + var branches = 1 + var hasFinally = false for i in 1 .. < n.len: let b = n.sons[i] let blen = sonsLen(b) if b.kind == nkExceptBranch: + inc branches if blen == 1: catchesAll(tracked) else: for j in countup(0, blen - 2): assert(b.sons[j].kind == nkType) catches(tracked, b.sons[j].typ) + + setLen(tracked.init, oldState) + track(tracked, b.sons[blen-1]) + for i in oldState.. <tracked.init.len: + addToIntersection(inter, tracked.init[i]) else: assert b.kind == nkFinally - track(tracked, b.sons[blen-1]) + setLen(tracked.init, oldState) + track(tracked, b.sons[blen-1]) + hasFinally = true + tracked.bottom = oldBottom + if not hasFinally: + setLen(tracked.init, oldState) + for id, count in items(inter): + if count == branches: tracked.init.add id proc isIndirectCall(n: PNode, owner: PSym): bool = # we don't count f(...) as an indirect call if 'f' is an parameter. @@ -263,11 +313,79 @@ proc trackOperand(tracked: PEffects, n: PNode) = mergeEffects(tracked, effectList.sons[exceptionEffects], n) mergeTags(tracked, effectList.sons[tagEffects], n) +proc trackCase(tracked: PEffects, n: PNode) = + track(tracked, n.sons[0]) + let oldState = tracked.init.len + var inter: TIntersection = @[] + for i in 1.. <n.len: + let branch = n.sons[i] + setLen(tracked.init, oldState) + for i in 0 .. <branch.len: + track(tracked, branch.sons[i]) + for i in oldState.. <tracked.init.len: + addToIntersection(inter, tracked.init[i]) + let exh = case skipTypes(n.sons[0].Typ, abstractVarRange-{tyTypeDesc}).Kind + of tyFloat..tyFloat128, tyString: + lastSon(n).kind == nkElse + else: + true + setLen(tracked.init, oldState) + if exh: + for id, count in items(inter): + if count == n.len-1: tracked.init.add id + # else we can't merge + +proc trackIf(tracked: PEffects, n: PNode) = + track(tracked, n.sons[0].sons[0]) + let oldState = tracked.init.len + + var inter: TIntersection = @[] + track(tracked, n.sons[0].sons[1]) + for i in oldState.. <tracked.init.len: + addToIntersection(inter, tracked.init[i]) + + for i in 1.. <n.len: + let branch = n.sons[i] + setLen(tracked.init, oldState) + for i in 0 .. <branch.len: + track(tracked, branch.sons[i]) + for i in oldState.. <tracked.init.len: + addToIntersection(inter, tracked.init[i]) + setLen(tracked.init, oldState) + if lastSon(n).len == 1: + for id, count in items(inter): + if count == n.len: tracked.init.add id + # else we can't merge as it is not exhaustive + +proc trackBlock(tracked: PEffects, n: PNode) = + if n.kind in {nkStmtList, nkStmtListExpr}: + var oldState = -1 + for i in 0.. <n.len: + if hasSubnodeWith(n.sons[i], nkBreakStmt): + # block: + # x = def + # if ...: ... break # some nested break + # y = def + # --> 'y' not defined after block! + if oldState < 0: oldState = tracked.init.len + track(tracked, n.sons[i]) + if oldState > 0: setLen(tracked.init, oldState) + else: + track(tracked, n) + +proc isTrue(n: PNode): bool = + n.kind == nkSym and n.sym.kind == skEnumField and n.sym.position != 0 or + n.kind == nkIntLit and n.intVal != 0 + proc track(tracked: PEffects, n: PNode) = case n.kind + of nkSym: + useVar(tracked, n) of nkRaiseStmt: n.sons[0].info = n.info throws(tracked.exc, n.sons[0]) + for i in 0 .. <safeLen(n): + track(tracked, n.sons[i]) of nkCallKinds: # p's effects are ours too: let a = n.sons[0] @@ -287,16 +405,45 @@ proc track(tracked: PEffects, n: PNode) = mergeEffects(tracked, effectList.sons[exceptionEffects], n) mergeTags(tracked, effectList.sons[tagEffects], n) for i in 1 .. <len(n): trackOperand(tracked, n.sons[i]) - of nkTryStmt: - trackTryStmt(tracked, n) - return - of nkPragma: - trackPragmaStmt(tracked, n) - return - of nkMacroDef, nkTemplateDef: return - else: nil - for i in 0 .. <safeLen(n): - track(tracked, n.sons[i]) + if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize, + mNewSeq, mShallowCopy}: + # may not look like an assignment, but it is: + initVar(tracked, n.sons[1]) + for i in 0 .. <safeLen(n): + track(tracked, n.sons[i]) + of nkTryStmt: trackTryStmt(tracked, n) + of nkPragma: trackPragmaStmt(tracked, n) + of nkMacroDef, nkTemplateDef: discard + of nkAsgn, nkFastAsgn: + track(tracked, n.sons[1]) + initVar(tracked, n.sons[0]) + track(tracked, n.sons[0]) + of nkVarSection: + for child in n: + if child.kind == nkIdentDefs and lastSon(child).kind != nkEmpty: + track(tracked, lastSon(child)) + for i in 0 .. child.len-3: initVar(tracked, child.sons[i]) + of nkCaseStmt: trackCase(tracked, n) + of nkIfStmt, nkIfExpr: trackIf(tracked, n) + of nkBlockStmt, nkBlockExpr: trackBlock(tracked, n.sons[1]) + of nkWhileStmt: + track(tracked, n.sons[0]) + # 'while true' loop? + if isTrue(n.sons[0]): + trackBlock(tracked, n.sons[1]) + else: + # loop may never execute: + let oldState = tracked.init.len + track(tracked, n.sons[1]) + setLen(tracked.init, oldState) + of nkForStmt, nkParForStmt: + # we are very conservative here and assume the loop is never executed: + let oldState = tracked.init.len + for i in 0 .. <len(n): + track(tracked, n.sons[i]) + setLen(tracked.init, oldState) + else: + for i in 0 .. <safeLen(n): track(tracked, n.sons[i]) proc checkRaisesSpec(spec, real: PNode, msg: string, hints: bool) = # check that any real exception is listed in 'spec'; mark those as used; @@ -362,6 +509,7 @@ proc trackProc*(s: PSym, body: PNode) = t.exc = effects.sons[exceptionEffects] t.tags = effects.sons[tagEffects] t.owner = s + t.init = @[] track(t, body) let p = s.ast.sons[pragmasPos] diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 6f372472a..6123957cd 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -190,7 +190,7 @@ proc semCase(c: PContext, n: PNode): PNode = var typ = CommonTypeBegin var hasElse = false case skipTypes(n.sons[0].Typ, abstractVarRange-{tyTypeDesc}).Kind - of tyInt..tyInt64, tyChar, tyEnum: + of tyInt..tyInt64, tyChar, tyEnum, tyUInt..tyUInt32: chckCovered = true of tyFloat..tyFloat128, tyString, tyError: nil |