next steps for guarded data flow analysis
This commit is contained in:
parent
8f97f3180a
commit
c156f2d493
7 changed files with 330 additions and 58 deletions
|
|
@ -27,23 +27,53 @@ const
|
||||||
|
|
||||||
proc isValue(n: PNode): bool = n.kind in {nkCharLit..nkNilLit}
|
proc isValue(n: PNode): bool = n.kind in {nkCharLit..nkNilLit}
|
||||||
proc isLocation(n: PNode): bool = not n.isValue
|
proc isLocation(n: PNode): bool = not n.isValue
|
||||||
#n.kind in {nkSym, nkBracketExpr, nkDerefExpr, nkHiddenDeref, nkDotExpr}
|
|
||||||
|
|
||||||
proc isLet(n: PNode): bool =
|
proc isLet(n: PNode): bool =
|
||||||
if n.kind == nkSym:
|
if n.kind == nkSym:
|
||||||
# XXX allow skResult, skVar here if not re-bound
|
|
||||||
if n.sym.kind in {skLet, skTemp, skForVar}:
|
if n.sym.kind in {skLet, skTemp, skForVar}:
|
||||||
result = true
|
result = true
|
||||||
elif n.sym.kind == skParam and skipTypes(n.sym.typ,
|
elif n.sym.kind == skParam and skipTypes(n.sym.typ,
|
||||||
abstractInst).kind != tyVar:
|
abstractInst).kind != tyVar:
|
||||||
result = true
|
result = true
|
||||||
|
|
||||||
proc isLetLocation(m: PNode): bool =
|
proc isVar(n: PNode): bool =
|
||||||
|
n.kind == nkSym and n.sym.kind in {skResult, skVar} and
|
||||||
|
sfGlobal notin n.sym.flags
|
||||||
|
|
||||||
|
proc isLetLocation(m: PNode, isApprox: bool): bool =
|
||||||
|
# consider: 'n[].kind' --> we really need to support 1 deref op even if this
|
||||||
|
# is technically wrong due to aliasing :-( We could introduce "soft" facts
|
||||||
|
# for this; this would still be very useful for warnings and also nicely
|
||||||
|
# solves the 'var' problems. For now we fix this by requiring much more
|
||||||
|
# restrictive expressions for the 'not nil' checking.
|
||||||
var n = m
|
var n = m
|
||||||
|
var derefs = 0
|
||||||
while true:
|
while true:
|
||||||
case n.kind
|
case n.kind
|
||||||
of nkDotExpr, nkCheckedFieldExpr, nkObjUpConv, nkObjDownConv:
|
of nkDotExpr, nkCheckedFieldExpr, nkObjUpConv, nkObjDownConv:
|
||||||
n = n.sons[0]
|
n = n.sons[0]
|
||||||
|
of nkDerefExpr, nkHiddenDeref:
|
||||||
|
n = n.sons[0]
|
||||||
|
inc derefs
|
||||||
|
of nkBracketExpr:
|
||||||
|
if isConstExpr(n.sons[1]) or isLet(n.sons[1]):
|
||||||
|
n = n.sons[0]
|
||||||
|
else: return
|
||||||
|
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
|
||||||
|
n = n.sons[1]
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
result = n.isLet and derefs <= ord(isApprox)
|
||||||
|
if not result and isApprox:
|
||||||
|
result = isVar(n)
|
||||||
|
|
||||||
|
proc interestingCaseExpr*(m: PNode): bool =
|
||||||
|
var n = m
|
||||||
|
while true:
|
||||||
|
case n.kind
|
||||||
|
of nkDotExpr, nkCheckedFieldExpr, nkObjUpConv, nkObjDownConv,
|
||||||
|
nkDerefExpr, nkHiddenDeref:
|
||||||
|
n = n.sons[0]
|
||||||
of nkBracketExpr:
|
of nkBracketExpr:
|
||||||
if isConstExpr(n.sons[1]) or isLet(n.sons[1]):
|
if isConstExpr(n.sons[1]) or isLet(n.sons[1]):
|
||||||
n = n.sons[0]
|
n = n.sons[0]
|
||||||
|
|
@ -62,14 +92,30 @@ proc neg(n: PNode): PNode =
|
||||||
result.sons[0] = newSymNode(getSysMagic("not", mNot))
|
result.sons[0] = newSymNode(getSysMagic("not", mNot))
|
||||||
result.sons[1] = n
|
result.sons[1] = n
|
||||||
|
|
||||||
|
proc buildIsNil(arg: PNode): PNode =
|
||||||
|
result = newNodeI(nkCall, arg.info, 2)
|
||||||
|
result.sons[0] = newSymNode(getSysMagic("isNil", mIsNil))
|
||||||
|
result.sons[1] = arg
|
||||||
|
|
||||||
proc usefulFact(n: PNode): PNode =
|
proc usefulFact(n: PNode): PNode =
|
||||||
case n.getMagic
|
case n.getMagic
|
||||||
of someEq+someLe+someLt:
|
of someEq:
|
||||||
if isLetLocation(n.sons[1]) or n.len == 3 and isLetLocation(n.sons[2]):
|
if skipConv(n.sons[2]).kind == nkNilLit and (
|
||||||
|
isLetLocation(n.sons[1], false) or isVar(n.sons[1])):
|
||||||
|
result = buildIsNil(n.sons[1])
|
||||||
|
else:
|
||||||
|
if isLetLocation(n.sons[1], true) or isLetLocation(n.sons[2], true):
|
||||||
|
# XXX algebraic simplifications! 'i-1 < a.len' --> 'i < a.len+1'
|
||||||
|
result = n
|
||||||
|
of someLe+someLt:
|
||||||
|
if isLetLocation(n.sons[1], true) or isLetLocation(n.sons[2], true):
|
||||||
# XXX algebraic simplifications! 'i-1 < a.len' --> 'i < a.len+1'
|
# XXX algebraic simplifications! 'i-1 < a.len' --> 'i < a.len+1'
|
||||||
result = n
|
result = n
|
||||||
of someIn, mIsNil:
|
of mIsNil:
|
||||||
if isLetLocation(n.sons[1]):
|
if isLetLocation(n.sons[1], false) or isVar(n.sons[1]):
|
||||||
|
result = n
|
||||||
|
of someIn:
|
||||||
|
if isLetLocation(n.sons[1], true):
|
||||||
result = n
|
result = n
|
||||||
of mAnd:
|
of mAnd:
|
||||||
let
|
let
|
||||||
|
|
@ -102,7 +148,9 @@ proc usefulFact(n: PNode): PNode =
|
||||||
result.sons[2] = b.neg
|
result.sons[2] = b.neg
|
||||||
else:
|
else:
|
||||||
let a = usefulFact(n.sons[1])
|
let a = usefulFact(n.sons[1])
|
||||||
if a != nil: result = n
|
if a != nil:
|
||||||
|
result = copyTree(n)
|
||||||
|
result.sons[1] = a
|
||||||
of mOr:
|
of mOr:
|
||||||
# 'or' sucks! (p.isNil or q.isNil) --> hard to do anything
|
# 'or' sucks! (p.isNil or q.isNil) --> hard to do anything
|
||||||
# with that knowledge...
|
# with that knowledge...
|
||||||
|
|
@ -157,10 +205,44 @@ proc sameTree(a, b: PNode): bool =
|
||||||
if not sameTree(a.sons[i], b.sons[i]): return
|
if not sameTree(a.sons[i], b.sons[i]): return
|
||||||
result = true
|
result = true
|
||||||
|
|
||||||
|
proc hasSubTree(n, x: PNode): bool =
|
||||||
|
if n.sameTree(x): result = true
|
||||||
|
else:
|
||||||
|
for i in 0..safeLen(n)-1:
|
||||||
|
if hasSubTree(n.sons[i], x): return true
|
||||||
|
|
||||||
|
proc invalidateFacts*(m: var TModel, n: PNode) =
|
||||||
|
# We are able to guard local vars (as opposed to 'let' variables)!
|
||||||
|
# 'while p != nil: f(p); p = p.next'
|
||||||
|
# This is actually quite easy to do:
|
||||||
|
# Re-assignments (incl. pass to a 'var' param) trigger an invalidation
|
||||||
|
# of every fact that contains 'v'.
|
||||||
|
#
|
||||||
|
# if x < 4:
|
||||||
|
# if y < 5
|
||||||
|
# x = unknown()
|
||||||
|
# # we invalidate 'x' here but it's known that x >= 4
|
||||||
|
# # for the else anyway
|
||||||
|
# else:
|
||||||
|
# echo x
|
||||||
|
#
|
||||||
|
# The same mechanism could be used for more complex data stored on the heap;
|
||||||
|
# procs that 'write: []' cannot invalidate 'n.kind' for instance. In fact, we
|
||||||
|
# could CSE these expressions then and help C's optimizer.
|
||||||
|
for i in 0..high(m):
|
||||||
|
if m[i] != nil and m[i].hasSubTree(n): m[i] = nil
|
||||||
|
|
||||||
proc valuesUnequal(a, b: PNode): bool =
|
proc valuesUnequal(a, b: PNode): bool =
|
||||||
if a.isValue and b.isValue:
|
if a.isValue and b.isValue:
|
||||||
result = not SameValue(a, b)
|
result = not SameValue(a, b)
|
||||||
|
|
||||||
|
proc pred(n: PNode): PNode =
|
||||||
|
if n.kind in {nkCharLit..nkUInt64Lit} and n.intVal != low(biggestInt):
|
||||||
|
result = copyNode(n)
|
||||||
|
dec result.intVal
|
||||||
|
else:
|
||||||
|
result = n
|
||||||
|
|
||||||
type
|
type
|
||||||
TImplication* = enum
|
TImplication* = enum
|
||||||
impUnknown, impNo, impYes
|
impUnknown, impNo, impYes
|
||||||
|
|
@ -178,35 +260,85 @@ proc impliesEq(fact, eq: PNode): TImplication =
|
||||||
if sameTree(fact.sons[1], eq.sons[val]): result = impYes
|
if sameTree(fact.sons[1], eq.sons[val]): result = impYes
|
||||||
elif valuesUnequal(fact.sons[1], eq.sons[val]): result = impNo
|
elif valuesUnequal(fact.sons[1], eq.sons[val]): result = impNo
|
||||||
of mInSet:
|
of mInSet:
|
||||||
if sameTree(fact.sons[1], eq.sons[loc]) and isValue(eq.sons[val]):
|
# remember: mInSet is 'contains' so the set comes first!
|
||||||
if inSet(fact.sons[2], eq.sons[val]): result = impYes
|
if sameTree(fact.sons[2], eq.sons[loc]) and isValue(eq.sons[val]):
|
||||||
|
if inSet(fact.sons[1], eq.sons[val]): result = impYes
|
||||||
else: result = impNo
|
else: result = impNo
|
||||||
of mIsNil:
|
|
||||||
if sameTree(fact.sons[1], eq.sons[loc]):
|
|
||||||
if eq.sons[val].kind == nkNilLit:
|
|
||||||
result = impYes
|
|
||||||
of mNot, mOr, mAnd: internalError(eq.info, "impliesEq")
|
of mNot, mOr, mAnd: internalError(eq.info, "impliesEq")
|
||||||
else: nil
|
else: discard
|
||||||
|
|
||||||
|
proc leImpliesIn(x, c, aSet: PNode): TImplication =
|
||||||
|
if c.kind in {nkCharLit..nkUInt64Lit}:
|
||||||
|
# fact: x <= 4; question x in {56}?
|
||||||
|
# --> true iff every value <= 4 is in the set {56}
|
||||||
|
#
|
||||||
|
var value = newIntNode(c.kind, firstOrd(x.typ))
|
||||||
|
# don't iterate too often:
|
||||||
|
if c.intVal - value.intVal < 1000:
|
||||||
|
var i, pos, neg: int
|
||||||
|
while value.intVal <= c.intVal:
|
||||||
|
if inSet(aSet, value): inc pos
|
||||||
|
else: inc neg
|
||||||
|
inc i; inc value.intVal
|
||||||
|
if pos == i: result = impYes
|
||||||
|
elif neg == i: result = impNo
|
||||||
|
#echo "result ", result, " ", i, " ", neg, " ", pos
|
||||||
|
# XXX wrong for the impNo case
|
||||||
|
|
||||||
|
proc geImpliesIn(x, c, aSet: PNode): TImplication =
|
||||||
|
if c.kind in {nkCharLit..nkUInt64Lit}:
|
||||||
|
# fact: x >= 4; question x in {56}?
|
||||||
|
# --> true iff every value >= 4 is in the set {56}
|
||||||
|
#
|
||||||
|
var value = newIntNode(c.kind, c.intVal)
|
||||||
|
let max = lastOrd(x.typ)
|
||||||
|
# don't iterate too often:
|
||||||
|
if max - value.intVal < 1000:
|
||||||
|
var i, pos, neg: int
|
||||||
|
while value.intVal <= max:
|
||||||
|
if inSet(aSet, value): inc pos
|
||||||
|
else: inc neg
|
||||||
|
inc i; inc value.intVal
|
||||||
|
if pos == i: result = impYes
|
||||||
|
elif neg == i: result = impNo
|
||||||
|
|
||||||
|
proc compareSets(a, b: PNode): TImplication =
|
||||||
|
if equalSets(a, b): result = impYes
|
||||||
|
elif intersectSets(a, b).len == 0: result = impNo
|
||||||
|
|
||||||
|
proc impliesIn(fact, loc, aSet: PNode): TImplication =
|
||||||
|
case fact.sons[0].sym.magic
|
||||||
|
of someEq:
|
||||||
|
if sameTree(fact.sons[1], loc):
|
||||||
|
if inSet(aSet, fact.sons[2]): result = impYes
|
||||||
|
else: result = impNo
|
||||||
|
elif sameTree(fact.sons[2], loc):
|
||||||
|
if inSet(aSet, fact.sons[1]): result = impYes
|
||||||
|
else: result = impNo
|
||||||
|
of mInSet:
|
||||||
|
if sameTree(fact.sons[2], loc):
|
||||||
|
result = compareSets(fact.sons[1], aSet)
|
||||||
|
of someLe:
|
||||||
|
if sameTree(fact.sons[1], loc):
|
||||||
|
result = leImpliesIn(fact.sons[1], fact.sons[2], aSet)
|
||||||
|
elif sameTree(fact.sons[2], loc):
|
||||||
|
result = geImpliesIn(fact.sons[2], fact.sons[1], aSet)
|
||||||
|
of someLt:
|
||||||
|
if sameTree(fact.sons[1], loc):
|
||||||
|
result = leImpliesIn(fact.sons[1], fact.sons[2].pred, aSet)
|
||||||
|
elif sameTree(fact.sons[2], loc):
|
||||||
|
# 4 < x --> 3 <= x
|
||||||
|
result = geImpliesIn(fact.sons[2], fact.sons[1].pred, aSet)
|
||||||
|
of mNot, mOr, mAnd: internalError(loc.info, "impliesIn")
|
||||||
|
else: discard
|
||||||
|
|
||||||
proc impliesIsNil(fact, eq: PNode): TImplication =
|
proc impliesIsNil(fact, eq: PNode): TImplication =
|
||||||
case fact.sons[0].sym.magic
|
case fact.sons[0].sym.magic
|
||||||
of someEq:
|
|
||||||
if sameTree(fact.sons[1], eq.sons[1]):
|
|
||||||
if fact.sons[2].kind == nkNilLit: result = impYes
|
|
||||||
elif sameTree(fact.sons[2], eq.sons[1]):
|
|
||||||
if fact.sons[1].kind == nkNilLit: result = impYes
|
|
||||||
of mIsNil:
|
of mIsNil:
|
||||||
if sameTree(fact.sons[1], eq.sons[1]):
|
if sameTree(fact.sons[1], eq.sons[1]):
|
||||||
result = impYes
|
result = impYes
|
||||||
of mNot, mOr, mAnd: internalError(eq.info, "impliesIsNil")
|
of mNot, mOr, mAnd: internalError(eq.info, "impliesIsNil")
|
||||||
else: nil
|
else: discard
|
||||||
|
|
||||||
proc pred(n: PNode): PNode =
|
|
||||||
if n.kind in {nkCharLit..nkUInt64Lit} and n.intVal != low(biggestInt):
|
|
||||||
result = copyNode(n)
|
|
||||||
dec result.intVal
|
|
||||||
else:
|
|
||||||
result = n
|
|
||||||
|
|
||||||
proc impliesGe(fact, x, c: PNode): TImplication =
|
proc impliesGe(fact, x, c: PNode): TImplication =
|
||||||
InternalAssert isLocation(x)
|
InternalAssert isLocation(x)
|
||||||
|
|
@ -242,7 +374,7 @@ proc impliesGe(fact, x, c: PNode): TImplication =
|
||||||
if isValue(fact.sons[1]) and isValue(c):
|
if isValue(fact.sons[1]) and isValue(c):
|
||||||
if leValue(c, fact.sons[1]): result = impYes
|
if leValue(c, fact.sons[1]): result = impYes
|
||||||
of mNot, mOr, mAnd: internalError(x.info, "impliesGe")
|
of mNot, mOr, mAnd: internalError(x.info, "impliesGe")
|
||||||
else: nil
|
else: discard
|
||||||
|
|
||||||
proc impliesLe(fact, x, c: PNode): TImplication =
|
proc impliesLe(fact, x, c: PNode): TImplication =
|
||||||
if not isLocation(x):
|
if not isLocation(x):
|
||||||
|
|
@ -330,17 +462,70 @@ proc factImplies(fact, prop: PNode, isNegation: bool): TImplication =
|
||||||
result = impliesLe(fact, prop.sons[1], prop.sons[2])
|
result = impliesLe(fact, prop.sons[1], prop.sons[2])
|
||||||
of someLt:
|
of someLt:
|
||||||
result = impliesLt(fact, prop.sons[1], prop.sons[2])
|
result = impliesLt(fact, prop.sons[1], prop.sons[2])
|
||||||
|
of mInSet:
|
||||||
|
result = impliesIn(fact, prop.sons[2], prop.sons[1])
|
||||||
else:
|
else:
|
||||||
internalError(prop.info, "invalid proposition")
|
internalError(prop.info, "invalid proposition")
|
||||||
|
|
||||||
proc doesImply*(facts: TModel, prop: PNode): TImplication =
|
proc doesImply*(facts: TModel, prop: PNode): TImplication =
|
||||||
assert prop.kind in nkCallKinds
|
assert prop.kind in nkCallKinds
|
||||||
for f in facts:
|
for f in facts:
|
||||||
result = f.factImplies(prop, false)
|
# facts can be invalidated, in which case they are 'nil':
|
||||||
if result != impUnknown: return
|
if not f.isNil:
|
||||||
|
result = f.factImplies(prop, false)
|
||||||
|
if result != impUnknown: return
|
||||||
|
|
||||||
proc impliesNotNil*(facts: TModel, arg: PNode): TImplication =
|
proc impliesNotNil*(facts: TModel, arg: PNode): TImplication =
|
||||||
var x = newNodeI(nkCall, arg.info, 2)
|
result = doesImply(facts, buildIsNil(arg).neg)
|
||||||
x.sons[0] = newSymNode(getSysMagic("isNil", mIsNil))
|
|
||||||
x.sons[1] = arg
|
proc settype(n: PNode): PType =
|
||||||
result = doesImply(facts, x.neg)
|
result = newType(tySet, n.typ.owner)
|
||||||
|
addSonSkipIntLit(result, n.typ)
|
||||||
|
|
||||||
|
proc buildOf(it, loc: PNode): PNode =
|
||||||
|
var s = newNodeI(nkCurly, it.info, it.len-1)
|
||||||
|
s.typ = settype(loc)
|
||||||
|
for i in 0..it.len-2: s.sons[i] = it.sons[i]
|
||||||
|
result = newNodeI(nkCall, it.info, 3)
|
||||||
|
result.sons[0] = newSymNode(getSysMagic("contains", mInSet))
|
||||||
|
result.sons[1] = s
|
||||||
|
result.sons[2] = loc
|
||||||
|
|
||||||
|
proc buildElse(n: PNode): PNode =
|
||||||
|
var s = newNodeIT(nkCurly, n.info, settype(n.sons[0]))
|
||||||
|
for i in 1..n.len-2:
|
||||||
|
let branch = n.sons[i]
|
||||||
|
assert branch.kind == nkOfBranch
|
||||||
|
for j in 0..branch.len-2:
|
||||||
|
s.add(branch.sons[j])
|
||||||
|
result = newNodeI(nkCall, n.info, 3)
|
||||||
|
result.sons[0] = newSymNode(getSysMagic("contains", mInSet))
|
||||||
|
result.sons[1] = s
|
||||||
|
result.sons[2] = n.sons[0]
|
||||||
|
|
||||||
|
proc addCaseBranchFacts*(m: var TModel, n: PNode, i: int) =
|
||||||
|
let branch = n.sons[i]
|
||||||
|
if branch.kind == nkOfBranch:
|
||||||
|
m.add buildOf(branch, n.sons[0])
|
||||||
|
else:
|
||||||
|
m.add n.buildElse.neg
|
||||||
|
|
||||||
|
proc buildProperFieldCheck(access, check: PNode): PNode =
|
||||||
|
if check.sons[1].kind == nkCurly:
|
||||||
|
result = copyTree(check)
|
||||||
|
if access.kind == nkDotExpr:
|
||||||
|
var a = copyTree(access)
|
||||||
|
a.sons[1] = check.sons[2]
|
||||||
|
result.sons[2] = a
|
||||||
|
# 'access.kind != nkDotExpr' can happen for object constructors
|
||||||
|
# which we don't check yet
|
||||||
|
else:
|
||||||
|
# it is some 'not'
|
||||||
|
assert check.getMagic == mNot
|
||||||
|
result = buildProperFieldCheck(access, check.sons[1]).neg
|
||||||
|
|
||||||
|
proc checkFieldAccess*(m: TModel, n: PNode) =
|
||||||
|
for i in 1..n.len-1:
|
||||||
|
let check = buildProperFieldCheck(n.sons[0], n.sons[i])
|
||||||
|
if m.doesImply(check) != impYes:
|
||||||
|
Message(n.info, warnProveField, renderTree(n.sons[0])); break
|
||||||
|
|
|
||||||
|
|
@ -106,11 +106,13 @@ type
|
||||||
warnUnknownSubstitutionX, warnLanguageXNotSupported, warnCommentXIgnored,
|
warnUnknownSubstitutionX, warnLanguageXNotSupported, warnCommentXIgnored,
|
||||||
warnNilStatement, warnAnalysisLoophole,
|
warnNilStatement, warnAnalysisLoophole,
|
||||||
warnDifferentHeaps, warnWriteToForeignHeap, warnImplicitClosure,
|
warnDifferentHeaps, warnWriteToForeignHeap, warnImplicitClosure,
|
||||||
warnEachIdentIsTuple, warnShadowIdent, warnProveInit, warnUninit, warnUser,
|
warnEachIdentIsTuple, warnShadowIdent,
|
||||||
hintSuccess, hintSuccessX,
|
warnProveInit, warnProveField, warnProveIndex,
|
||||||
hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded,
|
warnUninit, warnUser,
|
||||||
hintConvFromXtoItselfNotNeeded, hintExprAlwaysX, hintQuitCalled,
|
hintSuccess, hintSuccessX,
|
||||||
hintProcessing, hintCodeBegin, hintCodeEnd, hintConf, hintPath,
|
hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded,
|
||||||
|
hintConvFromXtoItselfNotNeeded, hintExprAlwaysX, hintQuitCalled,
|
||||||
|
hintProcessing, hintCodeBegin, hintCodeEnd, hintConf, hintPath,
|
||||||
hintConditionAlwaysTrue, hintPattern,
|
hintConditionAlwaysTrue, hintPattern,
|
||||||
hintUser
|
hintUser
|
||||||
|
|
||||||
|
|
@ -356,6 +358,8 @@ const
|
||||||
warnEachIdentIsTuple: "each identifier is a tuple [EachIdentIsTuple]",
|
warnEachIdentIsTuple: "each identifier is a tuple [EachIdentIsTuple]",
|
||||||
warnShadowIdent: "shadowed identifier: '$1' [ShadowIdent]",
|
warnShadowIdent: "shadowed identifier: '$1' [ShadowIdent]",
|
||||||
warnProveInit: "Cannot prove that '$1' is initialized. This will become a compile time error in the future. [ProveInit]",
|
warnProveInit: "Cannot prove that '$1' is initialized. This will become a compile time error in the future. [ProveInit]",
|
||||||
|
warnProveField: "cannot prove that field '$1' is accessible [ProveField]",
|
||||||
|
warnProveIndex: "cannot prove index '$1' is valid [ProveIndex]",
|
||||||
warnUninit: "'$1' might not have been initialized [Uninit]",
|
warnUninit: "'$1' might not have been initialized [Uninit]",
|
||||||
warnUser: "$1 [User]",
|
warnUser: "$1 [User]",
|
||||||
hintSuccess: "operation successful [Success]",
|
hintSuccess: "operation successful [Success]",
|
||||||
|
|
@ -376,15 +380,15 @@ const
|
||||||
hintUser: "$1 [User]"]
|
hintUser: "$1 [User]"]
|
||||||
|
|
||||||
const
|
const
|
||||||
WarningsToStr*: array[0..21, string] = ["CannotOpenFile", "OctalEscape",
|
WarningsToStr*: array[0..23, 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", "ProveInit", "Uninit",
|
"ImplicitClosure", "EachIdentIsTuple", "ShadowIdent",
|
||||||
"User"]
|
"ProveInit", "ProveField", "ProveIndex", "Uninit", "User"]
|
||||||
|
|
||||||
HintsToStr*: array[0..15, string] = ["Success", "SuccessX", "LineTooLong",
|
HintsToStr*: array[0..15, string] = ["Success", "SuccessX", "LineTooLong",
|
||||||
"XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded",
|
"XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded",
|
||||||
|
|
@ -516,7 +520,8 @@ proc sourceLine*(i: TLineInfo): PRope
|
||||||
|
|
||||||
var
|
var
|
||||||
gNotes*: TNoteKinds = {low(TNoteKind)..high(TNoteKind)} -
|
gNotes*: TNoteKinds = {low(TNoteKind)..high(TNoteKind)} -
|
||||||
{warnShadowIdent, warnUninit}
|
{warnShadowIdent, warnUninit,
|
||||||
|
warnProveField, warnProveIndex}
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -1657,7 +1657,9 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode =
|
||||||
e = fitNode(c, f.typ, e)
|
e = fitNode(c, f.typ, e)
|
||||||
# small hack here in a nkObjConstr the ``nkExprColonExpr`` node can have
|
# small hack here in a nkObjConstr the ``nkExprColonExpr`` node can have
|
||||||
# 3 childen the last being the field check
|
# 3 childen the last being the field check
|
||||||
if check != nil: it.add(check)
|
if check != nil:
|
||||||
|
check.sons[0] = it.sons[0]
|
||||||
|
it.add(check)
|
||||||
else:
|
else:
|
||||||
localError(it.info, errUndeclaredFieldX, id.s)
|
localError(it.info, errUndeclaredFieldX, id.s)
|
||||||
it.sons[1] = e
|
it.sons[1] = e
|
||||||
|
|
|
||||||
|
|
@ -315,15 +315,17 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) =
|
||||||
else:
|
else:
|
||||||
mergeEffects(tracked, effectList.sons[exceptionEffects], n)
|
mergeEffects(tracked, effectList.sons[exceptionEffects], n)
|
||||||
mergeTags(tracked, effectList.sons[tagEffects], n)
|
mergeTags(tracked, effectList.sons[tagEffects], n)
|
||||||
if paramType != nil and tfNotNil in paramType.flags and
|
if paramType != nil:
|
||||||
op != nil and tfNotNil notin op.flags:
|
if tfNotNil in paramType.flags and op != nil and tfNotNil notin op.flags:
|
||||||
case impliesNotNil(tracked.guards, n)
|
case impliesNotNil(tracked.guards, n)
|
||||||
of impUnknown:
|
of impUnknown:
|
||||||
Message(n.info, errGenerated,
|
Message(n.info, errGenerated,
|
||||||
"cannot prove '$1' is not nil" % n.renderTree)
|
"cannot prove '$1' is not nil" % n.renderTree)
|
||||||
of impNo:
|
of impNo:
|
||||||
Message(n.info, errGenerated, "'$1' is provably nil" % n.renderTree)
|
Message(n.info, errGenerated, "'$1' is provably nil" % n.renderTree)
|
||||||
of impYes: discard
|
of impYes: discard
|
||||||
|
if skipTypes(paramType, abstractInst).kind == tyVar:
|
||||||
|
invalidateFacts(tracked.guards, n)
|
||||||
|
|
||||||
proc breaksBlock(n: PNode): bool =
|
proc breaksBlock(n: PNode): bool =
|
||||||
case n.kind
|
case n.kind
|
||||||
|
|
@ -341,16 +343,22 @@ proc breaksBlock(n: PNode): bool =
|
||||||
proc trackCase(tracked: PEffects, n: PNode) =
|
proc trackCase(tracked: PEffects, n: PNode) =
|
||||||
track(tracked, n.sons[0])
|
track(tracked, n.sons[0])
|
||||||
let oldState = tracked.init.len
|
let oldState = tracked.init.len
|
||||||
|
let oldFacts = tracked.guards.len
|
||||||
|
let interesting = interestingCaseExpr(n.sons[0]) and warnProveField in gNotes
|
||||||
var inter: TIntersection = @[]
|
var inter: TIntersection = @[]
|
||||||
var toCover = 0
|
var toCover = 0
|
||||||
for i in 1.. <n.len:
|
for i in 1.. <n.len:
|
||||||
let branch = n.sons[i]
|
let branch = n.sons[i]
|
||||||
setLen(tracked.init, oldState)
|
setLen(tracked.init, oldState)
|
||||||
|
if interesting:
|
||||||
|
setLen(tracked.guards, oldFacts)
|
||||||
|
addCaseBranchFacts(tracked.guards, n, i)
|
||||||
for i in 0 .. <branch.len:
|
for i in 0 .. <branch.len:
|
||||||
track(tracked, branch.sons[i])
|
track(tracked, branch.sons[i])
|
||||||
if not breaksBlock(branch.lastSon): inc toCover
|
if not breaksBlock(branch.lastSon): inc toCover
|
||||||
for i in oldState.. <tracked.init.len:
|
for i in oldState.. <tracked.init.len:
|
||||||
addToIntersection(inter, tracked.init[i])
|
addToIntersection(inter, tracked.init[i])
|
||||||
|
|
||||||
let exh = case skipTypes(n.sons[0].Typ, abstractVarRange-{tyTypeDesc}).Kind
|
let exh = case skipTypes(n.sons[0].Typ, abstractVarRange-{tyTypeDesc}).Kind
|
||||||
of tyFloat..tyFloat128, tyString:
|
of tyFloat..tyFloat128, tyString:
|
||||||
lastSon(n).kind == nkElse
|
lastSon(n).kind == nkElse
|
||||||
|
|
@ -361,6 +369,7 @@ proc trackCase(tracked: PEffects, n: PNode) =
|
||||||
for id, count in items(inter):
|
for id, count in items(inter):
|
||||||
if count >= toCover: tracked.init.add id
|
if count >= toCover: tracked.init.add id
|
||||||
# else we can't merge
|
# else we can't merge
|
||||||
|
setLen(tracked.guards, oldFacts)
|
||||||
|
|
||||||
proc trackIf(tracked: PEffects, n: PNode) =
|
proc trackIf(tracked: PEffects, n: PNode) =
|
||||||
track(tracked, n.sons[0].sons[0])
|
track(tracked, n.sons[0].sons[0])
|
||||||
|
|
@ -453,12 +462,16 @@ proc track(tracked: PEffects, n: PNode) =
|
||||||
# XXX new(objWithNotNil) is not initialized properly!
|
# XXX new(objWithNotNil) is not initialized properly!
|
||||||
for i in 0 .. <safeLen(n):
|
for i in 0 .. <safeLen(n):
|
||||||
track(tracked, n.sons[i])
|
track(tracked, n.sons[i])
|
||||||
|
of nkCheckedFieldExpr:
|
||||||
|
track(tracked, n.sons[0])
|
||||||
|
if warnProveField in gNotes: checkFieldAccess(tracked.guards, n)
|
||||||
of nkTryStmt: trackTryStmt(tracked, n)
|
of nkTryStmt: trackTryStmt(tracked, n)
|
||||||
of nkPragma: trackPragmaStmt(tracked, n)
|
of nkPragma: trackPragmaStmt(tracked, n)
|
||||||
of nkMacroDef, nkTemplateDef: discard
|
of nkMacroDef, nkTemplateDef: discard
|
||||||
of nkAsgn, nkFastAsgn:
|
of nkAsgn, nkFastAsgn:
|
||||||
track(tracked, n.sons[1])
|
track(tracked, n.sons[1])
|
||||||
initVar(tracked, n.sons[0])
|
initVar(tracked, n.sons[0])
|
||||||
|
invalidateFacts(tracked.guards, n.sons[0])
|
||||||
track(tracked, n.sons[0])
|
track(tracked, n.sons[0])
|
||||||
of nkVarSection:
|
of nkVarSection:
|
||||||
for child in n:
|
for child in n:
|
||||||
|
|
@ -476,8 +489,11 @@ proc track(tracked: PEffects, n: PNode) =
|
||||||
else:
|
else:
|
||||||
# loop may never execute:
|
# loop may never execute:
|
||||||
let oldState = tracked.init.len
|
let oldState = tracked.init.len
|
||||||
|
let oldFacts = tracked.guards.len
|
||||||
|
addFact(tracked.guards, n.sons[0])
|
||||||
track(tracked, n.sons[1])
|
track(tracked, n.sons[1])
|
||||||
setLen(tracked.init, oldState)
|
setLen(tracked.init, oldState)
|
||||||
|
setLen(tracked.guards, oldFacts)
|
||||||
of nkForStmt, nkParForStmt:
|
of nkForStmt, nkParForStmt:
|
||||||
# we are very conservative here and assume the loop is never executed:
|
# we are very conservative here and assume the loop is never executed:
|
||||||
let oldState = tracked.init.len
|
let oldState = tracked.init.len
|
||||||
|
|
|
||||||
60
tests/reject/tcheckedfield1.nim
Normal file
60
tests/reject/tcheckedfield1.nim
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
discard """
|
||||||
|
errormsg: "cannot prove that field 's' is accessible"
|
||||||
|
line:54
|
||||||
|
"""
|
||||||
|
|
||||||
|
import strutils
|
||||||
|
|
||||||
|
{.warning[ProveField]: on.}
|
||||||
|
|
||||||
|
type
|
||||||
|
TNodeKind = enum
|
||||||
|
nkBinary, nkTernary, nkStr
|
||||||
|
PNode = ref TNode not nil
|
||||||
|
TNode = object
|
||||||
|
case k: TNodeKind
|
||||||
|
of nkBinary, nkTernary: a, b: PNode
|
||||||
|
of nkStr: s: string
|
||||||
|
|
||||||
|
PList = ref object
|
||||||
|
data: string
|
||||||
|
next: PList
|
||||||
|
|
||||||
|
proc getData(x: PList not nil) =
|
||||||
|
echo x.data
|
||||||
|
|
||||||
|
var head: PList
|
||||||
|
|
||||||
|
proc processList() =
|
||||||
|
var it = head
|
||||||
|
while it != nil:
|
||||||
|
getData(it)
|
||||||
|
it = it.next
|
||||||
|
|
||||||
|
proc toString2(x: PNode): string =
|
||||||
|
if x.k < nkStr:
|
||||||
|
toString2(x.a) & " " & toString2(x.b)
|
||||||
|
else:
|
||||||
|
x.s
|
||||||
|
|
||||||
|
proc toString(x: PNode): string =
|
||||||
|
case x.k
|
||||||
|
of nkTernary, nkBinary:
|
||||||
|
toString(x.a) & " " & toString(x.b)
|
||||||
|
of nkStr:
|
||||||
|
x.s
|
||||||
|
|
||||||
|
proc toString3(x: PNode): string =
|
||||||
|
if x.k <= nkBinary:
|
||||||
|
toString3(x.a) & " " & toString3(x.b)
|
||||||
|
else:
|
||||||
|
x.s # x.k in {nkStr} --> fact: not (x.k <= nkBinary)
|
||||||
|
|
||||||
|
proc p() =
|
||||||
|
var x: PNode = PNode(k: nkStr, s: "abc")
|
||||||
|
|
||||||
|
let y = x
|
||||||
|
if not y.isNil:
|
||||||
|
echo toString(y), " ", toString2(y)
|
||||||
|
|
||||||
|
p()
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
discard """
|
discard """
|
||||||
errormsg: "'y' is provably nil"
|
errormsg: "'y' is provably nil"
|
||||||
line:22
|
line:25
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import strutils
|
import strutils
|
||||||
|
|
@ -15,6 +15,9 @@ proc q(x: pointer not nil) =
|
||||||
|
|
||||||
proc p() =
|
proc p() =
|
||||||
var x: pointer
|
var x: pointer
|
||||||
|
if not x.isNil:
|
||||||
|
q(x)
|
||||||
|
|
||||||
let y = x
|
let y = x
|
||||||
if not y.isNil:
|
if not y.isNil:
|
||||||
q(y)
|
q(y)
|
||||||
|
|
|
||||||
7
todo.txt
7
todo.txt
|
|
@ -1,9 +1,10 @@
|
||||||
version 0.9.4
|
version 0.9.4
|
||||||
=============
|
=============
|
||||||
|
|
||||||
- make 'bind' default for templates and introduce 'mixin';
|
- make 'bind' default for templates and introduce 'mixin'
|
||||||
- test 'not nil' checking more
|
- fix tcheckedfield1
|
||||||
- prove field accesses; prove array accesses
|
- 'not nil' checking for globals
|
||||||
|
- prove array accesses
|
||||||
- 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'
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue