bugfixes for the guarded data flow analysis

This commit is contained in:
Araq 2013-06-12 00:41:02 +02:00
commit 0097305953
5 changed files with 103 additions and 71 deletions

View file

@ -38,7 +38,7 @@ proc isLet(n: PNode): bool =
proc isVar(n: PNode): bool = proc isVar(n: PNode): bool =
n.kind == nkSym and n.sym.kind in {skResult, skVar} and n.kind == nkSym and n.sym.kind in {skResult, skVar} and
sfGlobal notin n.sym.flags {sfGlobal, sfAddrTaken} * n.sym.flags == {}
proc isLetLocation(m: PNode, isApprox: bool): bool = proc isLetLocation(m: PNode, isApprox: bool): bool =
# consider: 'n[].kind' --> we really need to support 1 deref op even if this # consider: 'n[].kind' --> we really need to support 1 deref op even if this
@ -67,27 +67,58 @@ proc isLetLocation(m: PNode, isApprox: bool): bool =
if not result and isApprox: if not result and isApprox:
result = isVar(n) result = isVar(n)
proc interestingCaseExpr*(m: PNode): bool = proc interestingCaseExpr*(m: PNode): bool = isLetLocation(m, true)
var n = m
while true: proc swapArgs(fact: PNode, newOp: string, m: TMagic): PNode =
case n.kind result = newNodeI(nkCall, fact.info, 3)
of nkDotExpr, nkCheckedFieldExpr, nkObjUpConv, nkObjDownConv, result.sons[0] = newSymNode(getSysMagic(newOp, m))
nkDerefExpr, nkHiddenDeref: result.sons[1] = fact.sons[2]
n = n.sons[0] result.sons[2] = fact.sons[1]
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
proc neg(n: PNode): PNode = proc neg(n: PNode): PNode =
if n.getMagic == mNot: if n == nil: return nil
case n.getMagic
of mNot:
result = n.sons[1] result = n.sons[1]
of someLt:
# not (a < b) == a >= b == b <= a
result = swapArgs(n, "<=", mLeI)
of someLe:
result = swapArgs(n, "<", mLtI)
of mInSet:
if n.sons[1].kind != nkCurly: return nil
let t = n.sons[2].typ.skipTypes(abstractInst)
result = newNodeI(nkCall, n.info, 3)
result.sons[0] = n.sons[0]
result.sons[2] = n.sons[2]
if t.kind == tyEnum:
var s = newNodeIT(nkCurly, n.info, n.sons[1].typ)
for e in t.n:
let eAsNode = newIntNode(nkIntLit, e.sym.position)
if not inSet(n.sons[1], eAsNode): s.add eAsNode
result.sons[1] = s
elif lengthOrd(t) < 1000:
result.sons[1] = complement(n.sons[1])
else: else:
# not ({2, 3, 4}.contains(x)) x != 2 and x != 3 and x != 4
# XXX todo
result = nil
of mOr:
# not (a or b) --> not a and not b
let
a = n.sons[1].neg
b = n.sons[2].neg
if a != nil and b != nil:
result = newNodeI(nkCall, n.info, 3)
result.sons[0] = newSymNode(getSysMagic("and", mAnd))
result.sons[1] = a
result.sons[2] = b
elif a != nil:
result = a
elif b != nil:
result = b
else:
# leave not (a == 4) as it is
result = newNodeI(nkCall, n.info, 2) result = newNodeI(nkCall, n.info, 2)
result.sons[0] = newSymNode(getSysMagic("not", mNot)) result.sons[0] = newSymNode(getSysMagic("not", mNot))
result.sons[1] = n result.sons[1] = n
@ -131,26 +162,9 @@ proc usefulFact(n: PNode): PNode =
elif b != nil: elif b != nil:
result = b result = b
of mNot: of mNot:
case n.sons[1].getMagic
of mNot:
# normalize 'not (not a)' into 'a':
result = usefulFact(n.sons[1].sons[1])
of mOr:
# not (a or b) --> not a and not b
let n = n.sons[1]
let
a = usefulFact(n.sons[1])
b = usefulFact(n.sons[2])
if a != nil and b != nil:
result = newNodeI(nkCall, n.info, 3)
result.sons[0] = newSymNode(getSysMagic("and", mAnd))
result.sons[1] = a.neg
result.sons[2] = b.neg
else:
let a = usefulFact(n.sons[1]) let a = usefulFact(n.sons[1])
if a != nil: if a != nil:
result = copyTree(n) result = a.neg
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...
@ -159,13 +173,13 @@ proc usefulFact(n: PNode): PNode =
# (x == 3) or (y == 2) ---> not ( not (x==3) and not (y == 2)) # (x == 3) or (y == 2) ---> not ( not (x==3) and not (y == 2))
# not (x != 3 and y != 2) # not (x != 3 and y != 2)
let let
a = usefulFact(n.sons[1]) a = usefulFact(n.sons[1]).neg
b = usefulFact(n.sons[2]) b = usefulFact(n.sons[2]).neg
if a != nil and b != nil: if a != nil and b != nil:
result = newNodeI(nkCall, n.info, 3) result = newNodeI(nkCall, n.info, 3)
result.sons[0] = newSymNode(getSysMagic("and", mAnd)) result.sons[0] = newSymNode(getSysMagic("and", mAnd))
result.sons[1] = a.neg result.sons[1] = a
result.sons[2] = b.neg result.sons[2] = b
result = result.neg result = result.neg
elif n.kind == nkSym and n.sym.kind == skLet: elif n.kind == nkSym and n.sym.kind == skLet:
# consider: # consider:
@ -184,7 +198,9 @@ proc addFact*(m: var TModel, n: PNode) =
let n = usefulFact(n) let n = usefulFact(n)
if n != nil: m.add n if n != nil: m.add n
proc addFactNeg*(m: var TModel, n: PNode) = addFact(m, n.neg) proc addFactNeg*(m: var TModel, n: PNode) =
let n = n.neg
if n != nil: addFact(m, n)
proc sameTree(a, b: PNode): bool = proc sameTree(a, b: PNode): bool =
result = false result = false
@ -270,7 +286,7 @@ proc impliesEq(fact, eq: PNode): TImplication =
proc leImpliesIn(x, c, aSet: PNode): TImplication = proc leImpliesIn(x, c, aSet: PNode): TImplication =
if c.kind in {nkCharLit..nkUInt64Lit}: if c.kind in {nkCharLit..nkUInt64Lit}:
# fact: x <= 4; question x in {56}? # fact: x <= 4; question x in {56}?
# --> true iff every value <= 4 is in the set {56} # --> true if every value <= 4 is in the set {56}
# #
var value = newIntNode(c.kind, firstOrd(x.typ)) var value = newIntNode(c.kind, firstOrd(x.typ))
# don't iterate too often: # don't iterate too often:
@ -282,8 +298,6 @@ proc leImpliesIn(x, c, aSet: PNode): TImplication =
inc i; inc value.intVal inc i; inc value.intVal
if pos == i: result = impYes if pos == i: result = impYes
elif neg == i: result = impNo elif neg == i: result = impNo
#echo "result ", result, " ", i, " ", neg, " ", pos
# XXX wrong for the impNo case
proc geImpliesIn(x, c, aSet: PNode): TImplication = proc geImpliesIn(x, c, aSet: PNode): TImplication =
if c.kind in {nkCharLit..nkUInt64Lit}: if c.kind in {nkCharLit..nkUInt64Lit}:
@ -427,33 +441,48 @@ proc impliesLt(fact, x, c: PNode): TImplication =
if q != x: if q != x:
result = impliesLe(fact, q, c) result = impliesLe(fact, q, c)
proc factImplies(fact, prop: PNode, isNegation: bool): TImplication = proc `~`(x: TImplication): TImplication =
case x
of impUnknown: impUnknown
of impNo: impYes
of impYes: impNo
proc factImplies(fact, prop: PNode): TImplication =
case fact.getMagic case fact.getMagic
of mNot: of mNot:
case factImplies(fact.sons[1], prop, not isNegation) # Consider:
of impUnknown: return impUnknown # enum nkBinary, nkTernary, nkStr
of impNo: return impYes # fact: not (k <= nkBinary)
of impYes: return impNo # question: k in {nkStr}
# --> 'not' for facts is entirely different than 'not' for questions!
# it's provably wrong if every value > 4 is in the set {56}
# That's because we compute the implication and 'a -> not b' cannot
# be treated the same as 'not a -> b'
# (not a) -> b compute as not (a -> b) ???
# == not a or not b == not (a and b)
let arg = fact.sons[1]
case arg.getMagic
of mIsNil:
return ~factImplies(arg, prop)
of mAnd: of mAnd:
if not isNegation: # not (a and b) means not a or not b:
result = factImplies(fact.sons[1], prop, isNegation)
if result != impUnknown: return result
return factImplies(fact.sons[2], prop, isNegation)
else:
# careful! not (a and b) means not a or not b:
# a or b --> both need to imply 'prop' # a or b --> both need to imply 'prop'
let a = factImplies(fact.sons[1], prop, isNegation) let a = factImplies(arg.sons[1], prop)
let b = factImplies(fact.sons[2], prop, isNegation) let b = factImplies(arg.sons[2], prop)
if a == b: return a if a == b: return ~a
return impUnknown return impUnknown
else:
InternalError(fact.info, "invalid fact")
of mAnd:
result = factImplies(fact.sons[1], prop)
if result != impUnknown: return result
return factImplies(fact.sons[2], prop)
else: discard else: discard
case prop.sons[0].sym.magic case prop.sons[0].sym.magic
of mNot: of mNot:
case fact.factImplies(prop.sons[1], isNegation) result = ~fact.factImplies(prop.sons[1])
of impUnknown: result = impUnknown
of impNo: result = impYes
of impYes: result = impNo
of mIsNil: of mIsNil:
result = impliesIsNil(fact, prop) result = impliesIsNil(fact, prop)
of someEq: of someEq:
@ -472,7 +501,7 @@ proc doesImply*(facts: TModel, prop: PNode): TImplication =
for f in facts: for f in facts:
# facts can be invalidated, in which case they are 'nil': # facts can be invalidated, in which case they are 'nil':
if not f.isNil: if not f.isNil:
result = f.factImplies(prop, false) result = f.factImplies(prop)
if result != impUnknown: return if result != impUnknown: return
proc impliesNotNil*(facts: TModel, arg: PNode): TImplication = proc impliesNotNil*(facts: TModel, arg: PNode): TImplication =

View file

@ -156,6 +156,12 @@ proc equalSets(a, b: PNode): bool =
toBitSet(b, y) toBitSet(b, y)
result = bitSetEquals(x, y) result = bitSetEquals(x, y)
proc complement*(a: PNode): PNode =
var x: TBitSet
toBitSet(a, x)
for i in countup(0, high(x)): x[i] = not x[i]
result = toTreeSet(x, a.typ, a.info)
proc cardSet(s: PNode): BiggestInt = proc cardSet(s: PNode): BiggestInt =
# here we can do better than converting it into a compact set # here we can do better than converting it into a compact set
# we just count the elements directly # we just count the elements directly

View file

@ -324,8 +324,6 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) =
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

View file

@ -1,6 +1,6 @@
discard """ discard """
errormsg: "cannot prove that field 's' is accessible" errormsg: "cannot prove that field 'x.s' is accessible"
line:54 line:51
""" """
import strutils import strutils

View file

@ -2,7 +2,6 @@ version 0.9.4
============= =============
- make 'bind' default for templates and introduce 'mixin' - make 'bind' default for templates and introduce 'mixin'
- fix tcheckedfield1
- 'not nil' checking for globals - 'not nil' checking for globals
- prove array accesses - prove array accesses
- special rule for ``[]=`` - special rule for ``[]=``