bugfixes for the guarded data flow analysis
This commit is contained in:
parent
38faa64b12
commit
0097305953
5 changed files with 103 additions and 71 deletions
|
|
@ -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 =
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
1
todo.txt
1
todo.txt
|
|
@ -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 ``[]=``
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue