implemented dataflow analysis; activate via --warning[Uninit]:on

This commit is contained in:
Araq 2013-06-04 21:58:39 +02:00
commit 2aaa8f7909
6 changed files with 185 additions and 26 deletions

View file

@ -106,7 +106,7 @@ type
warnUnknownSubstitutionX, warnLanguageXNotSupported, warnCommentXIgnored, warnUnknownSubstitutionX, warnLanguageXNotSupported, warnCommentXIgnored,
warnNilStatement, warnAnalysisLoophole, warnNilStatement, warnAnalysisLoophole,
warnDifferentHeaps, warnWriteToForeignHeap, warnImplicitClosure, warnDifferentHeaps, warnWriteToForeignHeap, warnImplicitClosure,
warnEachIdentIsTuple, warnShadowIdent, warnUser, warnEachIdentIsTuple, warnShadowIdent, warnUninit, warnUser,
hintSuccess, hintSuccessX, hintSuccess, hintSuccessX,
hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded, hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded,
hintConvFromXtoItselfNotNeeded, hintExprAlwaysX, hintQuitCalled, hintConvFromXtoItselfNotNeeded, hintExprAlwaysX, hintQuitCalled,
@ -355,6 +355,7 @@ const
warnImplicitClosure: "implicit closure convention: '$1' [ImplicitClosure]", warnImplicitClosure: "implicit closure convention: '$1' [ImplicitClosure]",
warnEachIdentIsTuple: "each identifier is a tuple [EachIdentIsTuple]", warnEachIdentIsTuple: "each identifier is a tuple [EachIdentIsTuple]",
warnShadowIdent: "shadowed identifier: '$1' [ShadowIdent]", warnShadowIdent: "shadowed identifier: '$1' [ShadowIdent]",
warnUninit: "read from potentially uninitialized variable: '$1' [Uninit]",
warnUser: "$1 [User]", warnUser: "$1 [User]",
hintSuccess: "operation successful [Success]", hintSuccess: "operation successful [Success]",
hintSuccessX: "operation successful ($# lines compiled; $# sec total; $#) [SuccessX]", hintSuccessX: "operation successful ($# lines compiled; $# sec total; $#) [SuccessX]",
@ -374,14 +375,15 @@ const
hintUser: "$1 [User]"] hintUser: "$1 [User]"]
const const
WarningsToStr*: array[0..19, string] = ["CannotOpenFile", "OctalEscape", WarningsToStr*: array[0..20, string] = ["CannotOpenFile", "OctalEscape",
"XIsNeverRead", "XmightNotBeenInit", "XIsNeverRead", "XmightNotBeenInit",
"Deprecated", "ConfigDeprecated", "Deprecated", "ConfigDeprecated",
"SmallLshouldNotBeUsed", "UnknownMagic", "SmallLshouldNotBeUsed", "UnknownMagic",
"RedefinitionOfLabel", "UnknownSubstitutionX", "LanguageXNotSupported", "RedefinitionOfLabel", "UnknownSubstitutionX", "LanguageXNotSupported",
"CommentXIgnored", "NilStmt", "CommentXIgnored", "NilStmt",
"AnalysisLoophole", "DifferentHeaps", "WriteToForeignHeap", "AnalysisLoophole", "DifferentHeaps", "WriteToForeignHeap",
"ImplicitClosure", "EachIdentIsTuple", "ShadowIdent", "User"] "ImplicitClosure", "EachIdentIsTuple", "ShadowIdent", "Uninit",
"User"]
HintsToStr*: array[0..15, string] = ["Success", "SuccessX", "LineTooLong", HintsToStr*: array[0..15, string] = ["Success", "SuccessX", "LineTooLong",
"XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded", "XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded",
@ -512,7 +514,8 @@ proc raiseRecoverableError*(msg: string) {.noinline, noreturn.} =
proc sourceLine*(i: TLineInfo): PRope proc sourceLine*(i: TLineInfo): PRope
var var
gNotes*: TNoteKinds = {low(TNoteKind)..high(TNoteKind)} - {warnShadowIdent} gNotes*: TNoteKinds = {low(TNoteKind)..high(TNoteKind)} -
{warnShadowIdent, warnUninit}
gErrorCounter*: int = 0 # counts the number of errors gErrorCounter*: int = 0 # counts the number of errors
gHintCounter*: int = 0 gHintCounter*: int = 0
gWarnCounter*: int = 0 gWarnCounter*: int = 0

View file

@ -15,6 +15,7 @@ import
# way had some inherent problems. Performs: # way had some inherent problems. Performs:
# #
# * effect+exception tracking # * effect+exception tracking
# * "usage before definition" checking
# * checks for invalid usages of compiletime magics (not implemented) # * checks for invalid usages of compiletime magics (not implemented)
# * checks for invalid usages of PNimNode (not implemented) # * checks for invalid usages of PNimNode (not implemented)
# * later: will do an escape analysis for closures at least # * later: will do an escape analysis for closures at least
@ -43,14 +44,6 @@ import
# done: It essentially requires a built-in 'indexSplit' operation and dependent # done: It essentially requires a built-in 'indexSplit' operation and dependent
# typing. # 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 ------------------------- # ------------------------ exception and tag tracking -------------------------
discard """ discard """
@ -80,9 +73,44 @@ type
tags: PNode # list of tags tags: PNode # list of tags
bottom: int bottom: int
owner: PSym owner: PSym
init: seq[int] # list of initialized variables
# coming soon: "guard" tracking for 'let' variables
PEffects = var TEffects 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) = proc throws(tracked, n: PNode) =
if n.typ == nil or n.typ.kind != tyError: tracked.add n 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) = proc trackTryStmt(tracked: PEffects, n: PNode) =
let oldBottom = tracked.bottom let oldBottom = tracked.bottom
tracked.bottom = tracked.exc.len tracked.bottom = tracked.exc.len
let oldState = tracked.init.len
var inter: TIntersection = @[]
track(tracked, n.sons[0]) 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: for i in 1 .. < n.len:
let b = n.sons[i] let b = n.sons[i]
let blen = sonsLen(b) let blen = sonsLen(b)
if b.kind == nkExceptBranch: if b.kind == nkExceptBranch:
inc branches
if blen == 1: if blen == 1:
catchesAll(tracked) catchesAll(tracked)
else: else:
for j in countup(0, blen - 2): for j in countup(0, blen - 2):
assert(b.sons[j].kind == nkType) assert(b.sons[j].kind == nkType)
catches(tracked, b.sons[j].typ) 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: else:
assert b.kind == nkFinally assert b.kind == nkFinally
setLen(tracked.init, oldState)
track(tracked, b.sons[blen-1]) track(tracked, b.sons[blen-1])
hasFinally = true
tracked.bottom = oldBottom 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 = proc isIndirectCall(n: PNode, owner: PSym): bool =
# we don't count f(...) as an indirect call if 'f' is an parameter. # 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) mergeEffects(tracked, effectList.sons[exceptionEffects], n)
mergeTags(tracked, effectList.sons[tagEffects], 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) = proc track(tracked: PEffects, n: PNode) =
case n.kind case n.kind
of nkSym:
useVar(tracked, n)
of nkRaiseStmt: of nkRaiseStmt:
n.sons[0].info = n.info n.sons[0].info = n.info
throws(tracked.exc, n.sons[0]) throws(tracked.exc, n.sons[0])
for i in 0 .. <safeLen(n):
track(tracked, n.sons[i])
of nkCallKinds: of nkCallKinds:
# p's effects are ours too: # p's effects are ours too:
let a = n.sons[0] let a = n.sons[0]
@ -287,16 +405,45 @@ proc track(tracked: PEffects, n: PNode) =
mergeEffects(tracked, effectList.sons[exceptionEffects], n) mergeEffects(tracked, effectList.sons[exceptionEffects], n)
mergeTags(tracked, effectList.sons[tagEffects], n) mergeTags(tracked, effectList.sons[tagEffects], n)
for i in 1 .. <len(n): trackOperand(tracked, n.sons[i]) for i in 1 .. <len(n): trackOperand(tracked, n.sons[i])
of nkTryStmt: if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize,
trackTryStmt(tracked, n) mNewSeq, mShallowCopy}:
return # may not look like an assignment, but it is:
of nkPragma: initVar(tracked, n.sons[1])
trackPragmaStmt(tracked, n)
return
of nkMacroDef, nkTemplateDef: return
else: nil
for i in 0 .. <safeLen(n): for i in 0 .. <safeLen(n):
track(tracked, n.sons[i]) 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) = proc checkRaisesSpec(spec, real: PNode, msg: string, hints: bool) =
# check that any real exception is listed in 'spec'; mark those as used; # 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.exc = effects.sons[exceptionEffects]
t.tags = effects.sons[tagEffects] t.tags = effects.sons[tagEffects]
t.owner = s t.owner = s
t.init = @[]
track(t, body) track(t, body)
let p = s.ast.sons[pragmasPos] let p = s.ast.sons[pragmasPos]

View file

@ -190,7 +190,7 @@ proc semCase(c: PContext, n: PNode): PNode =
var typ = CommonTypeBegin var typ = CommonTypeBegin
var hasElse = false var hasElse = false
case skipTypes(n.sons[0].Typ, abstractVarRange-{tyTypeDesc}).Kind case skipTypes(n.sons[0].Typ, abstractVarRange-{tyTypeDesc}).Kind
of tyInt..tyInt64, tyChar, tyEnum: of tyInt..tyInt64, tyChar, tyEnum, tyUInt..tyUInt32:
chckCovered = true chckCovered = true
of tyFloat..tyFloat128, tyString, tyError: of tyFloat..tyFloat128, tyString, tyError:
nil nil

View file

@ -10,6 +10,8 @@ proc parseInt(x: TFile): int {.noSideEffect.} = nil
proc parseInt(x: char): int {.noSideEffect.} = nil proc parseInt(x: char): int {.noSideEffect.} = nil
proc parseInt(x: int16): int {.noSideEffect.} = nil proc parseInt(x: int16): int {.noSideEffect.} = nil
proc parseInt[T](x: T): int = echo x; 34
type type
TParseInt = proc (x: string): int {.noSideEffect.} TParseInt = proc (x: string): int {.noSideEffect.}
@ -33,3 +35,5 @@ type
proc bar[a,b](f: TFoo[a,b], x: a) = echo(x, " ", f.lorem, f.ipsum) proc bar[a,b](f: TFoo[a,b], x: a) = echo(x, " ", f.lorem, f.ipsum)
proc bar[a,b](f: TFoo[a,b], x: b) = echo(x, " ", f.lorem, f.ipsum) proc bar[a,b](f: TFoo[a,b], x: b) = echo(x, " ", f.lorem, f.ipsum)
discard parseInt[string]("yay")

View file

@ -2,6 +2,7 @@ version 0.9.4
============= =============
- make 'bind' default for templates and introduce 'mixin'; - make 'bind' default for templates and introduce 'mixin';
- implement full 'not nil' checking; range[1..3] needs the same mechanism
- special rule for ``[]=`` - special rule for ``[]=``
- ``=`` should be overloadable; requires specialization for ``=``; general - ``=`` should be overloadable; requires specialization for ``=``; general
lift mechanism in the compiler is already implemented for 'fields' lift mechanism in the compiler is already implemented for 'fields'
@ -31,7 +32,6 @@ version 0.9.x
============= =============
- macros as type pragmas - macros as type pragmas
- implement full 'not nil' checking; range[1..3] needs the same mechanism
- implicit deref for parameter matching - implicit deref for parameter matching
- lazy overloading resolution: - lazy overloading resolution:
* get rid of ``expr[typ]``, use perhaps ``static[typ]`` instead * get rid of ``expr[typ]``, use perhaps ``static[typ]`` instead

View file

@ -26,6 +26,10 @@ Changes affecting backwards compatibility
Compiler Additions Compiler Additions
------------------ ------------------
- The compiler can now warn about "uninitialized" variables. (There are no
real uninitialized variables in Nimrod as they are initialized to binary
zero). Activate via ``{.warning[Uninit]:on.}``.
Language Additions Language Additions
------------------ ------------------