DrNim (Nim compiler with Z3 integration) (#13743)
* code cleanups and feature additions * added basic test and koch/CI integration * make it build on Unix * DrNim: now buildable on Unix, only takes 10 minutes, enjoy * added basic documentation for DrNim which can also be seen as the RFC we're following * drnim: change the build setup so that drnim.exe ends up in bin/ * makes simple floating point ranges work * added basic float range check * drnim: teach Z3 about Nim's range types plus code refactoring * drnim: make unsigned numbers work * added and fixed index checking under setLen * first implementation of .ensures, .invariant and .assume (.requires still missing and so is proc type compatibility checking * drnim: .requires checking implemented * drnim: implemented .ensures properly * more impressive test involving min() * drnim: check for proc type compatibility and base method compatibility wrt .requires and .ensures * testament: support for 'pattern <directory> * koch: uses new <directory> feature of testament * drnim: added tiny musings about 'old' * Make testament work with old SSL versions * koch: add support for 'koch drnim -d:release' * drnim: preparations for the param.old notation
This commit is contained in:
parent
dd44701728
commit
9ffec79300
25 changed files with 1242 additions and 35 deletions
|
|
@ -565,6 +565,7 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType; caller: PNode)
|
|||
elif tfNoSideEffect notin op.flags:
|
||||
markSideEffect(tracked, a)
|
||||
if paramType != nil and paramType.kind == tyVar:
|
||||
invalidateFacts(tracked.guards, n)
|
||||
if n.kind == nkSym and isLocalVar(tracked, n.sym):
|
||||
makeVolatile(tracked, n.sym)
|
||||
if paramType != nil and paramType.kind == tyProc and tfGcSafe in paramType.flags:
|
||||
|
|
@ -675,28 +676,75 @@ proc cstringCheck(tracked: PEffects; n: PNode) =
|
|||
a.typ.kind == tyString and a.kind notin {nkStrLit..nkTripleStrLit}):
|
||||
message(tracked.config, n.info, warnUnsafeCode, renderTree(n))
|
||||
|
||||
proc prove(c: PEffects; prop: PNode): bool =
|
||||
if c.graph.proofEngine != nil:
|
||||
let (success, m) = c.graph.proofEngine(c.graph, c.guards.s,
|
||||
canon(prop, c.guards.o))
|
||||
if not success:
|
||||
message(c.config, prop.info, warnStaticIndexCheck, "cannot prove: " & $prop & m)
|
||||
result = success
|
||||
|
||||
proc patchResult(c: PEffects; n: PNode) =
|
||||
if n.kind == nkSym and n.sym.kind == skResult:
|
||||
let fn = c.owner
|
||||
if fn != nil and fn.kind in routineKinds and fn.ast != nil and resultPos < fn.ast.len:
|
||||
n.sym = fn.ast[resultPos].sym
|
||||
else:
|
||||
localError(c.config, n.info, "routine has no return type, but .requires contains 'result'")
|
||||
else:
|
||||
for i in 0..<safeLen(n):
|
||||
patchResult(c, n[i])
|
||||
|
||||
when defined(drnim):
|
||||
proc requiresCheck(c: PEffects; call: PNode; op: PType) =
|
||||
assert op.n[0].kind == nkEffectList
|
||||
if requiresEffects < op.n[0].len:
|
||||
let requires = op.n[0][requiresEffects]
|
||||
if requires != nil and requires.kind != nkEmpty:
|
||||
# we need to map the call arguments to the formal parameters used inside
|
||||
# 'requires':
|
||||
let (success, m) = c.graph.requirementsCheck(c.graph, c.guards.s, call, canon(requires, c.guards.o))
|
||||
if not success:
|
||||
message(c.config, call.info, warnStaticIndexCheck, "cannot prove: " & $requires & m)
|
||||
|
||||
else:
|
||||
template requiresCheck(c, n, op) = discard
|
||||
|
||||
proc checkLe(c: PEffects; a, b: PNode) =
|
||||
case proveLe(c.guards, a, b)
|
||||
of impUnknown:
|
||||
#for g in c.guards.s:
|
||||
# if g != nil: echo "I Know ", g
|
||||
message(c.config, a.info, warnStaticIndexCheck,
|
||||
"cannot prove: " & $a & " <= " & $b)
|
||||
of impYes:
|
||||
discard
|
||||
of impNo:
|
||||
message(c.config, a.info, warnStaticIndexCheck,
|
||||
"can prove: " & $a & " > " & $b)
|
||||
if c.graph.proofEngine != nil:
|
||||
var cmpOp = mLeI
|
||||
if a.typ != nil:
|
||||
case a.typ.skipTypes(abstractInst).kind
|
||||
of tyFloat..tyFloat128: cmpOp = mLeF64
|
||||
of tyChar, tyUInt..tyUInt64: cmpOp = mLeU
|
||||
else: discard
|
||||
|
||||
let cmp = newTree(nkInfix, newSymNode createMagic(c.graph, "<=", cmpOp), a, b)
|
||||
cmp.info = a.info
|
||||
discard prove(c, cmp)
|
||||
else:
|
||||
case proveLe(c.guards, a, b)
|
||||
of impUnknown:
|
||||
#for g in c.guards.s:
|
||||
# if g != nil: echo "I Know ", g
|
||||
message(c.config, a.info, warnStaticIndexCheck,
|
||||
"cannot prove: " & $a & " <= " & $b)
|
||||
of impYes:
|
||||
discard
|
||||
of impNo:
|
||||
message(c.config, a.info, warnStaticIndexCheck,
|
||||
"can prove: " & $a & " > " & $b)
|
||||
|
||||
proc checkBounds(c: PEffects; arr, idx: PNode) =
|
||||
checkLe(c, lowBound(c.config, arr), idx)
|
||||
checkLe(c, idx, highBound(c.config, arr, c.guards.o))
|
||||
|
||||
proc checkRange(c: PEffects; value: PNode; typ: PType) =
|
||||
if typ.skipTypes(abstractInst - {tyRange}).kind == tyRange:
|
||||
let lowBound = nkIntLit.newIntNode(firstOrd(c.config, typ))
|
||||
let t = typ.skipTypes(abstractInst - {tyRange})
|
||||
if t.kind == tyRange:
|
||||
let lowBound = copyTree(t.n[0])
|
||||
lowBound.info = value.info
|
||||
let highBound = nkIntLit.newIntNode(lastOrd(c.config, typ))
|
||||
let highBound = copyTree(t.n[1])
|
||||
highBound.info = value.info
|
||||
checkLe(c, lowBound, value)
|
||||
checkLe(c, value, highBound)
|
||||
|
|
@ -783,6 +831,7 @@ proc track(tracked: PEffects, n: PNode) =
|
|||
mergeEffects(tracked, effectList[exceptionEffects], n)
|
||||
mergeTags(tracked, effectList[tagEffects], n)
|
||||
gcsafeAndSideeffectCheck()
|
||||
requiresCheck(tracked, n, op)
|
||||
if a.kind != nkSym or a.sym.magic != mNBindSym:
|
||||
for i in 1..<n.len: trackOperand(tracked, n[i], paramType(op, i), a)
|
||||
if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize, mNewSeq}:
|
||||
|
|
@ -976,6 +1025,13 @@ proc track(tracked: PEffects, n: PNode) =
|
|||
enforcedGcSafety = true
|
||||
elif pragma == wNoSideEffect:
|
||||
enforceNoSideEffects = true
|
||||
when defined(drnim):
|
||||
if pragma == wAssume:
|
||||
addFact(tracked.guards, pragmaList[i][1])
|
||||
elif pragma == wInvariant:
|
||||
if prove(tracked, pragmaList[i][1]):
|
||||
addFact(tracked.guards, pragmaList[i][1])
|
||||
|
||||
if enforcedGcSafety: tracked.inEnforcedGcSafe = true
|
||||
if enforceNoSideEffects: tracked.inEnforcedNoSideEffects = true
|
||||
track(tracked, n.lastSon)
|
||||
|
|
@ -1065,6 +1121,11 @@ proc checkMethodEffects*(g: ModuleGraph; disp, branch: PSym) =
|
|||
if sfThread in disp.flags and notGcSafe(branch.typ):
|
||||
localError(g.config, branch.info, "base method is GC-safe, but '$1' is not" %
|
||||
branch.name.s)
|
||||
when defined(drnim):
|
||||
if not g.compatibleProps(g, disp.typ, branch.typ):
|
||||
localError(g.config, branch.info, "for method '" & branch.name.s &
|
||||
"' the `.requires` or `.ensures` properties are incompatible.")
|
||||
|
||||
if branch.typ.lockLevel > disp.typ.lockLevel:
|
||||
when true:
|
||||
message(g.config, branch.info, warnLockLevel,
|
||||
|
|
@ -1088,14 +1149,22 @@ proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode) =
|
|||
let tagsSpec = effectSpec(n, wTags)
|
||||
if not isNil(tagsSpec):
|
||||
effects[tagEffects] = tagsSpec
|
||||
|
||||
let requiresSpec = propSpec(n, wRequires)
|
||||
if not isNil(requiresSpec):
|
||||
effects[requiresEffects] = requiresSpec
|
||||
let ensuresSpec = propSpec(n, wEnsures)
|
||||
if not isNil(ensuresSpec):
|
||||
effects[ensuresEffects] = ensuresSpec
|
||||
|
||||
effects[pragmasEffects] = n
|
||||
|
||||
proc initEffects(g: ModuleGraph; effects: PNode; s: PSym; t: var TEffects; c: PContext) =
|
||||
newSeq(effects.sons, effectListLen)
|
||||
effects[exceptionEffects] = newNodeI(nkArgList, s.info)
|
||||
effects[tagEffects] = newNodeI(nkArgList, s.info)
|
||||
effects[usesEffects] = g.emptyNode
|
||||
effects[writeEffects] = g.emptyNode
|
||||
effects[requiresEffects] = g.emptyNode
|
||||
effects[ensuresEffects] = g.emptyNode
|
||||
effects[pragmasEffects] = g.emptyNode
|
||||
|
||||
t.exc = effects[exceptionEffects]
|
||||
|
|
@ -1155,6 +1224,15 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
|
|||
# after the check, use the formal spec:
|
||||
effects[tagEffects] = tagsSpec
|
||||
|
||||
let requiresSpec = propSpec(p, wRequires)
|
||||
if not isNil(requiresSpec):
|
||||
effects[requiresEffects] = requiresSpec
|
||||
let ensuresSpec = propSpec(p, wEnsures)
|
||||
if not isNil(ensuresSpec):
|
||||
patchResult(t, ensuresSpec)
|
||||
effects[ensuresEffects] = ensuresSpec
|
||||
discard prove(t, ensuresSpec)
|
||||
|
||||
if sfThread in s.flags and t.gcUnsafe:
|
||||
if optThreads in g.config.globalOptions and optThreadAnalysis in g.config.globalOptions:
|
||||
#localError(s.info, "'$1' is not GC-safe" % s.name.s)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue