From 2aaa8f7909e51eb3d971e197f152e247c64362e9 Mon Sep 17 00:00:00 2001 From: Araq Date: Tue, 4 Jun 2013 21:58:39 +0200 Subject: [PATCH] implemented dataflow analysis; activate via --warning[Uninit]:on --- compiler/msgs.nim | 11 ++- compiler/sempass2.nim | 188 +++++++++++++++++++++++++++++++++---- compiler/semstmts.nim | 2 +- tests/compile/toverprc.nim | 4 + todo.txt | 2 +- web/news.txt | 4 + 6 files changed, 185 insertions(+), 26 deletions(-) diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 16ef06b615..f75aec0d5e 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 b78e365311..64aae09c1e 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.. '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 ..