summary refs log tree commit diff stats
path: root/compiler
diff options
context:
space:
mode:
authorAraq <rumpf_a@web.de>2013-06-04 21:58:39 +0200
committerAraq <rumpf_a@web.de>2013-06-04 21:58:39 +0200
commit2aaa8f7909e51eb3d971e197f152e247c64362e9 (patch)
treedef61be7039f3154d61efca75f996c7e1ccdf1cf /compiler
parentf7c0cc976ddb4e86e2341352b0674b9787005a4a (diff)
downloadNim-2aaa8f7909e51eb3d971e197f152e247c64362e9.tar.gz
implemented dataflow analysis; activate via --warning[Uninit]:on
Diffstat (limited to 'compiler')
-rw-r--r--compiler/msgs.nim11
-rw-r--r--compiler/sempass2.nim188
-rw-r--r--compiler/semstmts.nim2
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