1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273 |
- #
- #
- # Doctor Nim
- # (c) Copyright 2020 Andreas Rumpf
- #
- # See the file "copying.txt", included in this
- # distribution, for details about the copyright.
- #
- #[
- - introduce Phi nodes to complete the SSA representation
- - the analysis has to take 'break', 'continue' and 'raises' into account
- - We need to map arrays to Z3 and test for something like 'forall(i, (i in 3..4) -> (a[i] > 3))'
- - We need teach DrNim what 'inc', 'dec' and 'swap' mean, for example
- 'x in n..m; inc x' implies 'x in n+1..m+1'
- ]#
- import std / [
- parseopt, strutils, os, tables, times, intsets, hashes
- ]
- import ".." / compiler / [
- ast, astalgo, types, renderer,
- commands, options, msgs,
- platform, trees, wordrecg, guards,
- idents, lineinfos, cmdlinehelper, modulegraphs, condsyms,
- pathutils, passes, passaux, sem, modules
- ]
- import z3 / z3_api
- when not defined(windows):
- # on UNIX we use static linking because UNIX's lib*.so system is broken
- # beyond repair and the neckbeards don't understand software development.
- {.passL: "dist/z3/build/libz3.a".}
- const
- HelpMessage = "DrNim Version $1 [$2: $3]\n" &
- "Compiled at $4\n" &
- "Copyright (c) 2006-" & copyrightYear & " by Andreas Rumpf\n"
- const
- Usage = """
- drnim [options] [projectfile]
- Options: Same options that the Nim compiler supports. Plus:
- --assumeUnique Assume unique `ref` pointers. This makes the analysis unsound
- but more useful for wild Nim code such as the Nim compiler
- itself.
- """
- proc getCommandLineDesc(conf: ConfigRef): string =
- result = (HelpMessage % [system.NimVersion, platform.OS[conf.target.hostOS].name,
- CPU[conf.target.hostCPU].name, CompileDate]) &
- Usage
- proc helpOnError(conf: ConfigRef) =
- msgWriteln(conf, getCommandLineDesc(conf), {msgStdout})
- msgQuit(0)
- type
- CannotMapToZ3Error = object of ValueError
- Z3Exception = object of ValueError
- VersionScope = distinct int
- DrnimContext = ref object
- z3: Z3_context
- graph: ModuleGraph
- idgen: IdGenerator
- facts: seq[(PNode, VersionScope)]
- varVersions: seq[int] # this maps variable IDs to their current version.
- varSyms: seq[PSym] # mirrors 'varVersions'
- o: Operators
- hasUnstructedCf: int
- currOptions: TOptions
- owner: PSym
- mangler: seq[PSym]
- opImplies: PSym
- DrCon = object
- graph: ModuleGraph
- idgen: IdGenerator
- mapping: Table[string, Z3_ast]
- canonParameterNames: bool
- assumeUniqueness: bool
- up: DrnimContext
- var
- assumeUniqueness: bool
- proc echoFacts(c: DrnimContext) =
- echo "FACTS:"
- for i in 0 ..< c.facts.len:
- let f = c.facts[i]
- echo f[0], " version ", int(f[1])
- proc isLoc(m: PNode; assumeUniqueness: bool): bool =
- # We can reason about "locations" and map them to Z3 constants.
- # For code that is full of "ref" (e.g. the Nim compiler itself) that
- # is too limiting
- proc isLet(n: PNode): bool =
- if n.kind == nkSym:
- if n.sym.kind in {skLet, skTemp, skForVar}:
- result = true
- elif n.sym.kind == skParam and skipTypes(n.sym.typ,
- abstractInst).kind != tyVar:
- result = true
- var n = m
- while true:
- case n.kind
- of nkDotExpr, nkCheckedFieldExpr, nkObjUpConv, nkObjDownConv, nkHiddenDeref:
- n = n[0]
- of nkDerefExpr:
- n = n[0]
- if not assumeUniqueness: return false
- of nkBracketExpr:
- if isConstExpr(n[1]) or isLet(n[1]) or isConstExpr(n[1].skipConv):
- n = n[0]
- else: return
- of nkHiddenStdConv, nkHiddenSubConv, nkConv:
- n = n[1]
- else:
- break
- if n.kind == nkSym:
- case n.sym.kind
- of skLet, skTemp, skForVar, skParam:
- result = true
- #of skParam:
- # result = skipTypes(n.sym.typ, abstractInst).kind != tyVar
- of skResult, skVar:
- result = {sfAddrTaken} * n.sym.flags == {}
- else:
- discard
- proc currentVarVersion(c: DrnimContext; s: PSym; begin: VersionScope): int =
- # we need to take into account both en- and disabled var bindings here,
- # hence the 'abs' call:
- result = 0
- for i in countdown(int(begin)-1, 0):
- if abs(c.varVersions[i]) == s.id: inc result
- proc previousVarVersion(c: DrnimContext; s: PSym; begin: VersionScope): int =
- # we need to ignore currently disabled var bindings here,
- # hence no 'abs' call here.
- result = -1
- for i in countdown(int(begin)-1, 0):
- if c.varVersions[i] == s.id: inc result
- proc disamb(c: DrnimContext; s: PSym): int =
- # we group by 's.name.s' to compute the stable name ID.
- result = 0
- for i in 0 ..< c.mangler.len:
- if s == c.mangler[i]: return result
- if s.name.s == c.mangler[i].name.s: inc result
- c.mangler.add s
- proc stableName(result: var string; c: DrnimContext; n: PNode; version: VersionScope;
- isOld: bool) =
- # we can map full Nim expressions like 'f(a, b, c)' to Z3 variables.
- # We must be careful to select a unique, stable name for these expressions
- # based on structural equality. 'stableName' helps us with this problem.
- # In the future we will also use this string for the caching mechanism.
- case n.kind
- of nkEmpty, nkNilLit, nkType: discard
- of nkIdent:
- result.add n.ident.s
- of nkSym:
- result.add n.sym.name.s
- if n.sym.magic == mNone:
- let d = disamb(c, n.sym)
- if d != 0:
- result.add "`scope="
- result.addInt d
- let v = if isOld: c.previousVarVersion(n.sym, version)
- else: c.currentVarVersion(n.sym, version)
- if v > 0:
- result.add '`'
- result.addInt v
- else:
- result.add "`magic="
- result.addInt ord(n.sym.magic)
- of nkBindStmt:
- # we use 'bind x 3' to use the 3rd version of variable 'x'. This
- # is easier than using 'old' which is position relative.
- assert n.len == 2
- assert n[0].kind == nkSym
- assert n[1].kind == nkIntLit
- let s = n[0].sym
- let v = int(n[1].intVal)
- result.add s.name.s
- let d = disamb(c, s)
- if d != 0:
- result.add "`scope="
- result.addInt d
- if v > 0:
- result.add '`'
- result.addInt v
- of nkCharLit..nkUInt64Lit:
- result.addInt n.intVal
- of nkFloatLit..nkFloat64Lit:
- result.addFloat n.floatVal
- of nkStrLit..nkTripleStrLit:
- result.add strutils.escape n.strVal
- of nkDotExpr:
- stableName(result, c, n[0], version, isOld)
- result.add '.'
- stableName(result, c, n[1], version, isOld)
- of nkBracketExpr:
- stableName(result, c, n[0], version, isOld)
- result.add '['
- stableName(result, c, n[1], version, isOld)
- result.add ']'
- of nkCallKinds:
- if n.len == 2:
- stableName(result, c, n[1], version, isOld)
- result.add '.'
- case getMagic(n)
- of mLengthArray, mLengthOpenArray, mLengthSeq, mLengthStr:
- result.add "len"
- of mHigh:
- result.add "high"
- of mLow:
- result.add "low"
- else:
- stableName(result, c, n[0], version, isOld)
- elif n.kind == nkInfix and n.len == 3:
- result.add '('
- stableName(result, c, n[1], version, isOld)
- result.add ' '
- stableName(result, c, n[0], version, isOld)
- result.add ' '
- stableName(result, c, n[2], version, isOld)
- result.add ')'
- else:
- stableName(result, c, n[0], version, isOld)
- result.add '('
- for i in 1..<n.len:
- if i > 1: result.add ", "
- stableName(result, c, n[i], version, isOld)
- result.add ')'
- else:
- result.add $n.kind
- result.add '('
- for i in 0..<n.len:
- if i > 0: result.add ", "
- stableName(result, c, n[i], version, isOld)
- result.add ')'
- proc stableName(c: DrnimContext; n: PNode; version: VersionScope;
- isOld = false): string =
- stableName(result, c, n, version, isOld)
- template allScopes(c): untyped = VersionScope(c.varVersions.len)
- template currentScope(c): untyped = VersionScope(c.varVersions.len)
- proc notImplemented(msg: string) {.noinline.} =
- when defined(debug):
- writeStackTrace()
- echo msg
- raise newException(CannotMapToZ3Error, "; cannot map to Z3: " & msg)
- proc notImplemented(n: PNode) {.noinline.} =
- when defined(debug):
- writeStackTrace()
- raise newException(CannotMapToZ3Error, "; cannot map to Z3: " & $n)
- proc notImplemented(t: PType) {.noinline.} =
- when defined(debug):
- writeStackTrace()
- raise newException(CannotMapToZ3Error, "; cannot map to Z3: " & typeToString t)
- proc translateEnsures(e, x: PNode): PNode =
- if e.kind == nkSym and e.sym.kind == skResult:
- result = x
- else:
- result = shallowCopy(e)
- for i in 0 ..< safeLen(e):
- result[i] = translateEnsures(e[i], x)
- proc typeToZ3(c: DrCon; t: PType): Z3_sort =
- template ctx: untyped = c.up.z3
- case t.skipTypes(abstractInst+{tyVar}).kind
- of tyEnum, tyInt..tyInt64:
- result = Z3_mk_int_sort(ctx)
- of tyBool:
- result = Z3_mk_bool_sort(ctx)
- of tyFloat..tyFloat128:
- result = Z3_mk_fpa_sort_double(ctx)
- of tyChar, tyUInt..tyUInt64:
- result = Z3_mk_bv_sort(ctx, 64)
- #cuint(getSize(c.graph.config, t) * 8))
- else:
- notImplemented(t)
- template binary(op, a, b): untyped =
- var arr = [a, b]
- op(ctx, cuint(2), addr(arr[0]))
- proc nodeToZ3(c: var DrCon; n: PNode; scope: VersionScope; vars: var seq[PNode]): Z3_ast
- proc nodeToDomain(c: var DrCon; n, q: PNode; opAnd: PSym): PNode =
- assert n.kind == nkInfix
- let opLe = createMagic(c.graph, c.idgen, "<=", mLeI)
- case $n[0]
- of "..":
- result = buildCall(opAnd, buildCall(opLe, n[1], q), buildCall(opLe, q, n[2]))
- of "..<":
- let opLt = createMagic(c.graph, c.idgen, "<", mLtI)
- result = buildCall(opAnd, buildCall(opLe, n[1], q), buildCall(opLt, q, n[2]))
- else:
- notImplemented(n)
- template quantorToZ3(fn) {.dirty.} =
- template ctx: untyped = c.up.z3
- var bound = newSeq[Z3_app](n.len-2)
- let opAnd = createMagic(c.graph, c.idgen, "and", mAnd)
- var known: PNode
- for i in 1..n.len-2:
- let it = n[i]
- doAssert it.kind == nkInfix
- let v = it[1].sym
- let name = Z3_mk_string_symbol(ctx, v.name.s)
- let vz3 = Z3_mk_const(ctx, name, typeToZ3(c, v.typ))
- c.mapping[stableName(c.up, it[1], allScopes(c.up))] = vz3
- bound[i-1] = Z3_to_app(ctx, vz3)
- let domain = nodeToDomain(c, it[2], it[1], opAnd)
- if known == nil:
- known = domain
- else:
- known = buildCall(opAnd, known, domain)
- var dummy: seq[PNode]
- assert known != nil
- let x = nodeToZ3(c, buildCall(createMagic(c.graph, c.idgen, "->", mImplies),
- known, n[^1]), scope, dummy)
- result = fn(ctx, 0, bound.len.cuint, addr(bound[0]), 0, nil, x)
- proc forallToZ3(c: var DrCon; n: PNode; scope: VersionScope): Z3_ast = quantorToZ3(Z3_mk_forall_const)
- proc existsToZ3(c: var DrCon; n: PNode; scope: VersionScope): Z3_ast = quantorToZ3(Z3_mk_exists_const)
- proc paramName(c: DrnimContext; n: PNode): string =
- case n.sym.kind
- of skParam: result = "arg" & $n.sym.position
- of skResult: result = "result"
- else: result = stableName(c, n, allScopes(c))
- proc nodeToZ3(c: var DrCon; n: PNode; scope: VersionScope; vars: var seq[PNode]): Z3_ast =
- template ctx: untyped = c.up.z3
- template rec(n): untyped = nodeToZ3(c, n, scope, vars)
- case n.kind
- of nkSym:
- let key = if c.canonParameterNames: paramName(c.up, n) else: stableName(c.up, n, scope)
- result = c.mapping.getOrDefault(key)
- if pointer(result) == nil:
- let name = Z3_mk_string_symbol(ctx, key)
- result = Z3_mk_const(ctx, name, typeToZ3(c, n.sym.typ))
- c.mapping[key] = result
- vars.add n
- of nkCharLit..nkUInt64Lit:
- if n.typ != nil and n.typ.skipTypes(abstractInst).kind in {tyInt..tyInt64}:
- # optimized for the common case
- result = Z3_mk_int64(ctx, clonglong(n.intval), Z3_mk_int_sort(ctx))
- elif n.typ != nil and n.typ.kind == tyBool:
- result = if n.intval != 0: Z3_mk_true(ctx) else: Z3_mk_false(ctx)
- elif n.typ != nil and isUnsigned(n.typ):
- result = Z3_mk_unsigned_int64(ctx, cast[uint64](n.intVal), typeToZ3(c, n.typ))
- else:
- let zt = if n.typ == nil: Z3_mk_int_sort(ctx) else: typeToZ3(c, n.typ)
- result = Z3_mk_numeral(ctx, $getOrdValue(n), zt)
- of nkFloatLit..nkFloat64Lit:
- result = Z3_mk_fpa_numeral_double(ctx, n.floatVal, Z3_mk_fpa_sort_double(ctx))
- of nkCallKinds:
- assert n.len > 0
- let operator = getMagic(n)
- case operator
- of mEqI, mEqF64, mEqEnum, mEqCh, mEqB, mEqRef, mEqProc,
- mEqStr, mEqSet, mEqCString:
- result = Z3_mk_eq(ctx, rec n[1], rec n[2])
- of mLeI, mLeEnum, mLeCh, mLeB, mLePtr, mLeStr:
- result = Z3_mk_le(ctx, rec n[1], rec n[2])
- of mLtI, mLtEnum, mLtCh, mLtB, mLtPtr, mLtStr:
- result = Z3_mk_lt(ctx, rec n[1], rec n[2])
- of mLengthOpenArray, mLengthStr, mLengthArray, mLengthSeq:
- # len(x) needs the same logic as 'x' itself
- if isLoc(n[1], c.assumeUniqueness):
- let key = stableName(c.up, n, scope)
- result = c.mapping.getOrDefault(key)
- if pointer(result) == nil:
- let name = Z3_mk_string_symbol(ctx, key)
- result = Z3_mk_const(ctx, name, Z3_mk_int_sort(ctx))
- c.mapping[key] = result
- vars.add n
- else:
- notImplemented(n)
- of mHigh:
- let addOpr = createMagic(c.graph, c.idgen, "+", mAddI)
- let lenOpr = createMagic(c.graph, c.idgen, "len", mLengthOpenArray)
- let asLenExpr = addOpr.buildCall(lenOpr.buildCall(n[1]), nkIntLit.newIntNode(-1))
- result = rec asLenExpr
- of mLow:
- result = rec lowBound(c.graph.config, n[1])
- of mAddI, mSucc:
- result = binary(Z3_mk_add, rec n[1], rec n[2])
- of mSubI, mPred:
- result = binary(Z3_mk_sub, rec n[1], rec n[2])
- of mMulI:
- result = binary(Z3_mk_mul, rec n[1], rec n[2])
- of mDivI:
- result = Z3_mk_div(ctx, rec n[1], rec n[2])
- of mModI:
- result = Z3_mk_mod(ctx, rec n[1], rec n[2])
- of mMaxI:
- # max(a, b) <=> ite(a < b, b, a)
- result = Z3_mk_ite(ctx, Z3_mk_lt(ctx, rec n[1], rec n[2]),
- rec n[2], rec n[1])
- of mMinI:
- # min(a, b) <=> ite(a < b, a, b)
- result = Z3_mk_ite(ctx, Z3_mk_lt(ctx, rec n[1], rec n[2]),
- rec n[1], rec n[2])
- of mLeU:
- result = Z3_mk_bvule(ctx, rec n[1], rec n[2])
- of mLtU:
- result = Z3_mk_bvult(ctx, rec n[1], rec n[2])
- of mAnd:
- # 'a and b' <=> ite(a, b, false)
- result = Z3_mk_ite(ctx, rec n[1], rec n[2], Z3_mk_false(ctx))
- #result = binary(Z3_mk_and, rec n[1], rec n[2])
- of mOr:
- result = Z3_mk_ite(ctx, rec n[1], Z3_mk_true(ctx), rec n[2])
- #result = binary(Z3_mk_or, rec n[1], rec n[2])
- of mXor:
- result = Z3_mk_xor(ctx, rec n[1], rec n[2])
- of mNot:
- result = Z3_mk_not(ctx, rec n[1])
- of mImplies:
- result = Z3_mk_implies(ctx, rec n[1], rec n[2])
- of mIff:
- result = Z3_mk_iff(ctx, rec n[1], rec n[2])
- of mForall:
- result = forallToZ3(c, n, scope)
- of mExists:
- result = existsToZ3(c, n, scope)
- of mLeF64:
- result = Z3_mk_fpa_leq(ctx, rec n[1], rec n[2])
- of mLtF64:
- result = Z3_mk_fpa_lt(ctx, rec n[1], rec n[2])
- of mAddF64:
- result = Z3_mk_fpa_add(ctx, Z3_mk_fpa_round_nearest_ties_to_even(ctx), rec n[1], rec n[2])
- of mSubF64:
- result = Z3_mk_fpa_sub(ctx, Z3_mk_fpa_round_nearest_ties_to_even(ctx), rec n[1], rec n[2])
- of mMulF64:
- result = Z3_mk_fpa_mul(ctx, Z3_mk_fpa_round_nearest_ties_to_even(ctx), rec n[1], rec n[2])
- of mDivF64:
- result = Z3_mk_fpa_div(ctx, Z3_mk_fpa_round_nearest_ties_to_even(ctx), rec n[1], rec n[2])
- of mShrI:
- # XXX handle conversions from int to uint here somehow
- result = Z3_mk_bvlshr(ctx, rec n[1], rec n[2])
- of mAshrI:
- result = Z3_mk_bvashr(ctx, rec n[1], rec n[2])
- of mShlI:
- result = Z3_mk_bvshl(ctx, rec n[1], rec n[2])
- of mBitandI:
- result = Z3_mk_bvand(ctx, rec n[1], rec n[2])
- of mBitorI:
- result = Z3_mk_bvor(ctx, rec n[1], rec n[2])
- of mBitxorI:
- result = Z3_mk_bvxor(ctx, rec n[1], rec n[2])
- of mOrd, mChr:
- result = rec n[1]
- of mOld:
- let key = if c.canonParameterNames: (paramName(c.up, n[1]) & ".old")
- else: stableName(c.up, n[1], scope, isOld = true)
- result = c.mapping.getOrDefault(key)
- if pointer(result) == nil:
- let name = Z3_mk_string_symbol(ctx, key)
- result = Z3_mk_const(ctx, name, typeToZ3(c, n[1].typ))
- c.mapping[key] = result
- # XXX change the logic in `addRangeInfo` for this
- #vars.add n
- else:
- # sempass2 adds some 'fact' like 'x = f(a, b)' (see addAsgnFact)
- # 'f(a, b)' can have an .ensures annotation and we need to make use
- # of this information.
- # we need to map 'f(a, b)' to a Z3 variable of this name
- let op = n[0].typ
- if op != nil and op.n != nil and op.n.len > 0 and op.n[0].kind == nkEffectList and
- ensuresEffects < op.n[0].len:
- let ensures = op.n[0][ensuresEffects]
- if ensures != nil and ensures.kind != nkEmpty:
- let key = stableName(c.up, n, scope)
- result = c.mapping.getOrDefault(key)
- if pointer(result) == nil:
- let name = Z3_mk_string_symbol(ctx, key)
- result = Z3_mk_const(ctx, name, typeToZ3(c, n.typ))
- c.mapping[key] = result
- vars.add n
- if pointer(result) == nil:
- notImplemented(n)
- of nkStmtListExpr, nkPar:
- var isTrivial = true
- for i in 0..n.len-2:
- isTrivial = isTrivial and n[i].kind in {nkEmpty, nkCommentStmt}
- if isTrivial:
- result = rec n[^1]
- else:
- notImplemented(n)
- of nkHiddenDeref:
- result = rec n[0]
- else:
- if isLoc(n, c.assumeUniqueness):
- let key = stableName(c.up, n, scope)
- result = c.mapping.getOrDefault(key)
- if pointer(result) == nil:
- let name = Z3_mk_string_symbol(ctx, key)
- result = Z3_mk_const(ctx, name, typeToZ3(c, n.typ))
- c.mapping[key] = result
- vars.add n
- else:
- notImplemented(n)
- proc addRangeInfo(c: var DrCon, n: PNode; scope: VersionScope, res: var seq[Z3_ast]) =
- var cmpOp = mLeI
- if n.typ != nil:
- cmpOp =
- case n.typ.skipTypes(abstractInst).kind
- of tyFloat..tyFloat128: mLeF64
- of tyChar, tyUInt..tyUInt64: mLeU
- else: mLeI
- var lowBound, highBound: PNode
- if n.kind == nkSym:
- let v = n.sym
- let t = v.typ.skipTypes(abstractInst - {tyRange})
- case t.kind
- of tyRange:
- lowBound = t.n[0]
- highBound = t.n[1]
- of tyFloat..tyFloat128:
- # no range information for non-range'd floats
- return
- of tyUInt..tyUInt64, tyChar:
- lowBound = newIntNode(nkUInt64Lit, firstOrd(nil, v.typ))
- lowBound.typ = v.typ
- highBound = newIntNode(nkUInt64Lit, lastOrd(nil, v.typ))
- highBound.typ = v.typ
- of tyInt..tyInt64, tyEnum:
- lowBound = newIntNode(nkInt64Lit, firstOrd(nil, v.typ))
- highBound = newIntNode(nkInt64Lit, lastOrd(nil, v.typ))
- else:
- # no range information available:
- return
- elif n.kind in nkCallKinds and n.len == 2 and n[0].kind == nkSym and
- n[0].sym.magic in {mLengthOpenArray, mLengthStr, mLengthArray, mLengthSeq}:
- # we know it's a 'len(x)' expression and we seek to teach
- # Z3 that the result is >= 0 and <= high(int).
- doAssert n.kind in nkCallKinds
- doAssert n[0].kind == nkSym
- doAssert n.len == 2
- lowBound = newIntNode(nkInt64Lit, 0)
- if n.typ != nil:
- highBound = newIntNode(nkInt64Lit, lastOrd(nil, n.typ))
- else:
- highBound = newIntNode(nkInt64Lit, high(int64))
- else:
- let op = n[0].typ
- if op != nil and op.n != nil and op.n.len > 0 and op.n[0].kind == nkEffectList and
- ensuresEffects < op.n[0].len:
- let ensures = op.n[0][ensuresEffects]
- if ensures != nil and ensures.kind != nkEmpty:
- var dummy: seq[PNode]
- res.add nodeToZ3(c, translateEnsures(ensures, n), scope, dummy)
- return
- let x = newTree(nkInfix, newSymNode createMagic(c.graph, c.idgen, "<=", cmpOp), lowBound, n)
- let y = newTree(nkInfix, newSymNode createMagic(c.graph, c.idgen, "<=", cmpOp), n, highBound)
- var dummy: seq[PNode]
- res.add nodeToZ3(c, x, scope, dummy)
- res.add nodeToZ3(c, y, scope, dummy)
- proc on_err(ctx: Z3_context, e: Z3_error_code) {.nimcall.} =
- #writeStackTrace()
- let msg = $Z3_get_error_msg(ctx, e)
- raise newException(Z3Exception, msg)
- proc forall(ctx: Z3_context; vars: seq[Z3_ast]; assumption, body: Z3_ast): Z3_ast =
- let x = Z3_mk_implies(ctx, assumption, body)
- if vars.len > 0:
- var bound: seq[Z3_app]
- for v in vars: bound.add Z3_to_app(ctx, v)
- result = Z3_mk_forall_const(ctx, 0, bound.len.cuint, addr(bound[0]), 0, nil, x)
- else:
- result = x
- proc conj(ctx: Z3_context; conds: seq[Z3_ast]): Z3_ast =
- if conds.len > 0:
- result = Z3_mk_and(ctx, cuint(conds.len), unsafeAddr conds[0])
- else:
- result = Z3_mk_true(ctx)
- proc setupZ3(): Z3_context =
- let cfg = Z3_mk_config()
- when false:
- Z3_set_param_value(cfg, "timeout", "1000")
- Z3_set_param_value(cfg, "model", "true")
- result = Z3_mk_context(cfg)
- Z3_del_config(cfg)
- Z3_set_error_handler(result, on_err)
- proc proofEngineAux(c: var DrCon; assumptions: seq[(PNode, VersionScope)];
- toProve: (PNode, VersionScope)): (bool, string) =
- c.mapping = initTable[string, Z3_ast]()
- try:
- #[
- For example, let's have these facts:
- i < 10
- i > 0
- Question:
- i + 3 < 13
- What we need to produce:
- forall(i, (i < 10) & (i > 0) -> (i + 3 < 13))
- ]#
- var collectedVars: seq[PNode]
- template ctx(): untyped = c.up.z3
- let solver = Z3_mk_solver(ctx)
- var lhs: seq[Z3_ast]
- for assumption in items(assumptions):
- try:
- let za = nodeToZ3(c, assumption[0], assumption[1], collectedVars)
- #Z3_solver_assert ctx, solver, za
- lhs.add za
- except CannotMapToZ3Error:
- discard "ignore a fact we cannot map to Z3"
- let z3toProve = nodeToZ3(c, toProve[0], toProve[1], collectedVars)
- for v in collectedVars:
- addRangeInfo(c, v, toProve[1], lhs)
- # to make Z3 produce nice counterexamples, we try to prove the
- # negation of our conjecture and see if it's Z3_L_FALSE
- let fa = Z3_mk_not(ctx, Z3_mk_implies(ctx, conj(ctx, lhs), z3toProve))
- #Z3_mk_not(ctx, forall(ctx, collectedVars, conj(ctx, lhs), z3toProve))
- when defined(dz3):
- echo "toProve: ", Z3_ast_to_string(ctx, fa), " ", c.graph.config $ toProve[0].info, " ", int(toProve[1])
- Z3_solver_assert ctx, solver, fa
- let z3res = Z3_solver_check(ctx, solver)
- result[0] = z3res == Z3_L_FALSE
- result[1] = ""
- if not result[0]:
- let counterex = strip($Z3_model_to_string(ctx, Z3_solver_get_model(ctx, solver)))
- if counterex.len > 0:
- result[1].add "; counter example: " & counterex
- except ValueError:
- result[0] = false
- result[1] = getCurrentExceptionMsg()
- proc proofEngine(ctx: DrnimContext; assumptions: seq[(PNode, VersionScope)];
- toProve: (PNode, VersionScope)): (bool, string) =
- var c: DrCon
- c.graph = ctx.graph
- c.idgen = ctx.idgen
- c.assumeUniqueness = assumeUniqueness
- c.up = ctx
- result = proofEngineAux(c, assumptions, toProve)
- proc skipAddr(n: PNode): PNode {.inline.} =
- (if n.kind == nkHiddenAddr: n[0] else: n)
- proc translateReq(r, call: PNode): PNode =
- if r.kind == nkSym and r.sym.kind == skParam:
- if r.sym.position+1 < call.len:
- result = call[r.sym.position+1].skipAddr
- else:
- notImplemented("no argument given for formal parameter: " & r.sym.name.s)
- else:
- result = shallowCopy(r)
- for i in 0 ..< safeLen(r):
- result[i] = translateReq(r[i], call)
- proc requirementsCheck(ctx: DrnimContext; assumptions: seq[(PNode, VersionScope)];
- call, requirement: PNode): (bool, string) =
- try:
- let r = translateReq(requirement, call)
- result = proofEngine(ctx, assumptions, (r, ctx.currentScope))
- except ValueError:
- result[0] = false
- result[1] = getCurrentExceptionMsg()
- proc compatibleProps(graph: ModuleGraph; formal, actual: PType): bool {.nimcall.} =
- #[
- Thoughts on subtyping rules for 'proc' types:
- proc a(y: int) {.requires: y > 0.} # a is 'weaker' than F
- # 'requires' must be weaker (or equal)
- # 'ensures' must be stronger (or equal)
- # a 'is weaker than' b iff b -> a
- # a 'is stronger than' b iff a -> b
- # --> We can use Z3 to compute whether 'var x: T = q' is valid
- type
- F = proc (y: int) {.requires: y > 5.}
- var
- x: F = a # valid?
- ]#
- proc isEmpty(n: PNode): bool {.inline.} = n == nil or n.safeLen == 0
- result = true
- if formal.n != nil and formal.n.len > 0 and formal.n[0].kind == nkEffectList and
- ensuresEffects < formal.n[0].len:
- let frequires = formal.n[0][requiresEffects]
- let fensures = formal.n[0][ensuresEffects]
- if actual.n != nil and actual.n.len > 0 and actual.n[0].kind == nkEffectList and
- ensuresEffects < actual.n[0].len:
- let arequires = actual.n[0][requiresEffects]
- let aensures = actual.n[0][ensuresEffects]
- var c: DrCon
- c.graph = graph
- c.idgen = graph.idgen
- c.canonParameterNames = true
- try:
- c.up = DrnimContext(z3: setupZ3(), o: initOperators(graph), graph: graph, owner: nil,
- opImplies: createMagic(graph, c.idgen, "->", mImplies))
- template zero: untyped = VersionScope(0)
- if not frequires.isEmpty:
- result = not arequires.isEmpty and proofEngineAux(c, @[(frequires, zero)], (arequires, zero))[0]
- if result:
- if not fensures.isEmpty:
- result = not aensures.isEmpty and proofEngineAux(c, @[(aensures, zero)], (fensures, zero))[0]
- finally:
- Z3_del_context(c.up.z3)
- else:
- # formal has requirements but 'actual' has none, so make it
- # incompatible. XXX What if the requirement only mentions that
- # we already know from the type system?
- result = frequires.isEmpty and fensures.isEmpty
- template config(c: typed): untyped = c.graph.config
- proc addFact(c: DrnimContext; n: PNode) =
- let v = c.currentScope
- if n.kind in nkCallKinds and n[0].kind == nkSym and n[0].sym.magic in {mOr, mAnd}:
- c.addFact(n[1])
- c.facts.add((n, v))
- proc neg(c: DrnimContext; n: PNode): PNode =
- result = newNodeI(nkCall, n.info, 2)
- result[0] = newSymNode(c.o.opNot)
- result[1] = n
- proc addFactNeg(c: DrnimContext; n: PNode) =
- addFact(c, neg(c, n))
- proc combineFacts(c: DrnimContext; a, b: PNode): PNode =
- if a == nil:
- result = b
- else:
- result = buildCall(c.o.opAnd, a, b)
- proc prove(c: DrnimContext; prop: PNode): bool =
- let (success, m) = proofEngine(c, c.facts, (prop, c.currentScope))
- if not success:
- message(c.config, prop.info, warnStaticIndexCheck, "cannot prove: " & $prop & m)
- result = success
- proc traversePragmaStmt(c: DrnimContext, n: PNode) =
- for it in n:
- if it.kind == nkExprColonExpr:
- let pragma = whichPragma(it)
- if pragma == wAssume:
- addFact(c, it[1])
- elif pragma == wInvariant or pragma == wAssert:
- if prove(c, it[1]):
- addFact(c, it[1])
- else:
- echoFacts(c)
- proc requiresCheck(c: DrnimContext, 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) = requirementsCheck(c, c.facts, call, requires)
- if not success:
- message(c.config, call.info, warnStaticIndexCheck, "cannot prove: " & $requires & m)
- proc freshVersion(c: DrnimContext; arg: PNode) =
- let v = getRoot(arg)
- if v != nil:
- c.varVersions.add v.id
- c.varSyms.add v
- proc translateEnsuresFromCall(c: DrnimContext, e, call: PNode): PNode =
- if e.kind in nkCallKinds and e[0].kind == nkSym and e[0].sym.magic == mOld:
- assert e[1].kind == nkSym and e[1].sym.kind == skParam
- let param = e[1].sym
- let arg = call[param.position+1].skipAddr
- result = buildCall(e[0].sym, arg)
- elif e.kind == nkSym and e.sym.kind == skParam:
- let param = e.sym
- let arg = call[param.position+1].skipAddr
- result = arg
- else:
- result = shallowCopy(e)
- for i in 0 ..< safeLen(e): result[i] = translateEnsuresFromCall(c, e[i], call)
- proc collectEnsuredFacts(c: DrnimContext, call: PNode; op: PType) =
- assert op.n[0].kind == nkEffectList
- for i in 1 ..< min(call.len, op.len):
- if op[i].kind == tyVar:
- freshVersion(c, call[i].skipAddr)
- if ensuresEffects < op.n[0].len:
- let ensures = op.n[0][ensuresEffects]
- if ensures != nil and ensures.kind != nkEmpty:
- addFact(c, translateEnsuresFromCall(c, ensures, call))
- proc checkLe(c: DrnimContext, a, b: PNode) =
- 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, c.idgen, "<=", cmpOp), a, b)
- cmp.info = a.info
- discard prove(c, cmp)
- proc checkBounds(c: DrnimContext; arr, idx: PNode) =
- checkLe(c, lowBound(c.config, arr), idx)
- checkLe(c, idx, highBound(c.config, arr, c.o))
- proc checkRange(c: DrnimContext; value: PNode; typ: PType) =
- let t = typ.skipTypes(abstractInst - {tyRange})
- if t.kind == tyRange:
- let lowBound = copyTree(t.n[0])
- lowBound.info = value.info
- let highBound = copyTree(t.n[1])
- highBound.info = value.info
- checkLe(c, lowBound, value)
- checkLe(c, value, highBound)
- proc addAsgnFact*(c: DrnimContext, key, value: PNode) =
- var fact = newNodeI(nkCall, key.info, 3)
- fact[0] = newSymNode(c.o.opEq)
- fact[1] = key
- fact[2] = value
- c.facts.add((fact, c.currentScope))
- proc traverse(c: DrnimContext; n: PNode)
- proc traverseTryStmt(c: DrnimContext; n: PNode) =
- traverse(c, n[0])
- let oldFacts = c.facts.len
- for i in 1 ..< n.len:
- traverse(c, n[i].lastSon)
- setLen(c.facts, oldFacts)
- proc traverseCase(c: DrnimContext; n: PNode) =
- traverse(c, n[0])
- let oldFacts = c.facts.len
- for i in 1 ..< n.len:
- traverse(c, n[i].lastSon)
- # XXX make this as smart as 'if elif'
- setLen(c.facts, oldFacts)
- proc disableVarVersions(c: DrnimContext; until: int) =
- for i in until..<c.varVersions.len:
- c.varVersions[i] = - abs(c.varVersions[i])
- proc varOfVersion(c: DrnimContext; x: PSym; scope: int): PNode =
- let version = currentVarVersion(c, x, VersionScope(scope))
- result = newTree(nkBindStmt, newSymNode(x), newIntNode(nkIntLit, version))
- proc traverseIf(c: DrnimContext; n: PNode) =
- #[ Consider this example::
- var x = y # x'0
- if a:
- inc x # x'1 == x'0 + 1
- elif b:
- inc x, 2 # x'2 == x'0 + 2
- afterwards we know this is fact::
- x'3 = Phi(x'0, x'1, x'2)
- So a Phi node from SSA representation is an 'or' formula like::
- x'3 == x'1 or x'3 == x'2 or x'3 == x'0
- However, this loses some information. The formula that doesn't
- lose information is::
- (a -> (x'3 == x'1)) and
- ((not a and b) -> (x'3 == x'2)) and
- ((not a and not b) -> (x'3 == x'0))
- (Where ``->`` is the logical implication.)
- In addition to the Phi information we also know the 'facts'
- computed by the branches, for example::
- if a:
- factA
- elif b:
- factB
- else:
- factC
- (a -> factA) and
- ((not a and b) -> factB) and
- ((not a and not b) -> factC)
- We can combine these two aspects by producing the following facts
- after each branch::
- var x = y # x'0
- if a:
- inc x # x'1 == x'0 + 1
- # also: x'1 == x'final
- elif b:
- inc x, 2 # x'2 == x'0 + 2
- # also: x'2 == x'final
- else:
- # also: x'0 == x'final
- ]#
- let oldFacts = c.facts.len
- let oldVars = c.varVersions.len
- var newFacts: seq[PNode]
- var branches = newSeq[(PNode, int)](n.len) # (cond, newVars) pairs
- template condVersion(): untyped = VersionScope(oldVars)
- for i in 0..<n.len:
- let branch = n[i]
- setLen(c.facts, oldFacts)
- var cond = PNode(nil)
- for j in 0..i-1:
- addFactNeg(c, n[j][0])
- cond = combineFacts(c, cond, neg(c, n[j][0]))
- if branch.len > 1:
- addFact(c, branch[0])
- cond = combineFacts(c, cond, branch[0])
- for i in 0..<branch.len:
- traverse(c, branch[i])
- assert cond != nil
- branches[i] = (cond, c.varVersions.len)
- var newInfo = PNode(nil)
- for f in oldFacts..<c.facts.len:
- newInfo = combineFacts(c, newInfo, c.facts[f][0])
- if newInfo != nil:
- newFacts.add buildCall(c.opImplies, cond, newInfo)
- disableVarVersions(c, oldVars)
- setLen(c.facts, oldFacts)
- for f in newFacts: c.facts.add((f, condVersion))
- # build the 'Phi' information:
- let varsWithoutFinals = c.varVersions.len
- var mutatedVars = initIntSet()
- for i in oldVars ..< varsWithoutFinals:
- let vv = c.varVersions[i]
- if not mutatedVars.containsOrIncl(vv):
- c.varVersions.add vv
- c.varSyms.add c.varSyms[i]
- var prevIdx = oldVars
- for i in 0 ..< branches.len:
- for v in prevIdx .. branches[i][1] - 1:
- c.facts.add((buildCall(c.opImplies, branches[i][0],
- buildCall(c.o.opEq, varOfVersion(c, c.varSyms[v], branches[i][1]), newSymNode(c.varSyms[v]))),
- condVersion))
- prevIdx = branches[i][1]
- proc traverseBlock(c: DrnimContext; n: PNode) =
- traverse(c, n)
- proc addFactLe(c: DrnimContext; a, b: PNode) =
- c.addFact c.o.opLe.buildCall(a, b)
- proc addFactLt(c: DrnimContext; a, b: PNode) =
- c.addFact c.o.opLt.buildCall(a, b)
- proc ensuresCheck(c: DrnimContext; owner: PSym) =
- if owner.typ != nil and owner.typ.kind == tyProc and owner.typ.n != nil:
- let n = owner.typ.n
- if n.len > 0 and n[0].kind == nkEffectList and ensuresEffects < n[0].len:
- let ensures = n[0][ensuresEffects]
- if ensures != nil and ensures.kind != nkEmpty:
- discard prove(c, ensures)
- proc traverseAsgn(c: DrnimContext; n: PNode) =
- traverse(c, n[0])
- traverse(c, n[1])
- proc replaceByOldParams(fact, le: PNode): PNode =
- if guards.sameTree(fact, le):
- result = newNodeIT(nkCall, fact.info, fact.typ)
- result.add newSymNode createMagic(c.graph, c.idgen, "old", mOld)
- result.add fact
- else:
- result = shallowCopy(fact)
- for i in 0 ..< safeLen(fact):
- result[i] = replaceByOldParams(fact[i], le)
- freshVersion(c, n[0])
- addAsgnFact(c, n[0], replaceByOldParams(n[1], n[0]))
- when defined(debug):
- echoFacts(c)
- proc traverse(c: DrnimContext; n: PNode) =
- case n.kind
- of nkEmpty..nkNilLit:
- discard "nothing to do"
- of nkRaiseStmt, nkBreakStmt, nkContinueStmt:
- inc c.hasUnstructedCf
- for i in 0..<n.safeLen:
- traverse(c, n[i])
- of nkReturnStmt:
- for i in 0 ..< n.safeLen:
- traverse(c, n[i])
- ensuresCheck(c, c.owner)
- of nkCallKinds:
- # p's effects are ours too:
- var a = n[0]
- let op = a.typ
- if op != nil and op.kind == tyProc and op.n[0].kind == nkEffectList:
- requiresCheck(c, n, op)
- collectEnsuredFacts(c, n, op)
- if a.kind == nkSym:
- case a.sym.magic
- of mNew, mNewFinalize, mNewSeq:
- # may not look like an assignment, but it is:
- let arg = n[1]
- freshVersion(c, arg)
- traverse(c, arg)
- let x = newNodeIT(nkObjConstr, arg.info, arg.typ)
- x.add arg
- addAsgnFact(c, arg, x)
- of mArrGet, mArrPut:
- #if optStaticBoundsCheck in c.currOptions: checkBounds(c, n[1], n[2])
- discard
- else:
- discard
- for i in 0..<n.safeLen:
- traverse(c, n[i])
- of nkDotExpr:
- #guardDotAccess(c, n)
- for i in 0..<n.len: traverse(c, n[i])
- of nkCheckedFieldExpr:
- traverse(c, n[0])
- #checkFieldAccess(c.facts, n, c.config)
- of nkTryStmt: traverseTryStmt(c, n)
- of nkPragma: traversePragmaStmt(c, n)
- of nkAsgn, nkFastAsgn: traverseAsgn(c, n)
- of nkVarSection, nkLetSection:
- for child in n:
- let last = lastSon(child)
- if last.kind != nkEmpty: traverse(c, last)
- if child.kind == nkIdentDefs and last.kind != nkEmpty:
- for i in 0..<child.len-2:
- addAsgnFact(c, child[i], last)
- elif child.kind == nkVarTuple and last.kind != nkEmpty:
- for i in 0..<child.len-1:
- if child[i].kind == nkEmpty or
- child[i].kind == nkSym and child[i].sym.name.s == "_":
- discard "anon variable"
- elif last.kind in {nkPar, nkTupleConstr}:
- addAsgnFact(c, child[i], last[i])
- of nkConstSection:
- for child in n:
- let last = lastSon(child)
- traverse(c, last)
- of nkCaseStmt: traverseCase(c, n)
- of nkWhen, nkIfStmt, nkIfExpr: traverseIf(c, n)
- of nkBlockStmt, nkBlockExpr: traverseBlock(c, n[1])
- of nkWhileStmt:
- # 'while true' loop?
- if isTrue(n[0]):
- traverseBlock(c, n[1])
- else:
- let oldFacts = c.facts.len
- addFact(c, n[0])
- traverse(c, n[0])
- traverse(c, n[1])
- setLen(c.facts, oldFacts)
- of nkForStmt, nkParForStmt:
- # we are very conservative here and assume the loop is never executed:
- let oldFacts = c.facts.len
- let iterCall = n[n.len-2]
- if optStaticBoundsCheck in c.currOptions and iterCall.kind in nkCallKinds:
- let op = iterCall[0]
- if op.kind == nkSym and fromSystem(op.sym):
- let iterVar = n[0]
- case op.sym.name.s
- of "..", "countup", "countdown":
- let lower = iterCall[1]
- let upper = iterCall[2]
- # for i in 0..n means 0 <= i and i <= n. Countdown is
- # the same since only the iteration direction changes.
- addFactLe(c, lower, iterVar)
- addFactLe(c, iterVar, upper)
- of "..<":
- let lower = iterCall[1]
- let upper = iterCall[2]
- addFactLe(c, lower, iterVar)
- addFactLt(c, iterVar, upper)
- else: discard
- for i in 0..<n.len-2:
- let it = n[i]
- traverse(c, it)
- let loopBody = n[^1]
- traverse(c, iterCall)
- traverse(c, loopBody)
- setLen(c.facts, oldFacts)
- of nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef,
- nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef:
- discard
- of nkCast:
- if n.len == 2:
- traverse(c, n[1])
- of nkHiddenStdConv, nkHiddenSubConv, nkConv:
- if n.len == 2:
- traverse(c, n[1])
- if optStaticBoundsCheck in c.currOptions:
- checkRange(c, n[1], n.typ)
- of nkObjUpConv, nkObjDownConv, nkChckRange, nkChckRangeF, nkChckRange64:
- if n.len == 1:
- traverse(c, n[0])
- if optStaticBoundsCheck in c.currOptions:
- checkRange(c, n[0], n.typ)
- of nkBracketExpr:
- if optStaticBoundsCheck in c.currOptions and n.len == 2:
- if n[0].typ != nil and skipTypes(n[0].typ, abstractVar).kind != tyTuple:
- checkBounds(c, n[0], n[1])
- for i in 0 ..< n.len: traverse(c, n[i])
- else:
- for i in 0 ..< n.len: traverse(c, n[i])
- proc strongSemCheck(graph: ModuleGraph; owner: PSym; n: PNode) =
- var c = DrnimContext()
- c.currOptions = graph.config.options + owner.options
- if optStaticBoundsCheck in c.currOptions:
- c.z3 = setupZ3()
- c.o = initOperators(graph)
- c.graph = graph
- c.idgen = graph.idgen
- c.owner = owner
- c.opImplies = createMagic(c.graph, c.idgen, "->", mImplies)
- try:
- traverse(c, n)
- ensuresCheck(c, owner)
- finally:
- Z3_del_context(c.z3)
- proc mainCommand(graph: ModuleGraph) =
- let conf = graph.config
- conf.lastCmdTime = epochTime()
- graph.strongSemCheck = strongSemCheck
- graph.compatibleProps = compatibleProps
- graph.config.setErrorMaxHighMaybe
- defineSymbol(graph.config.symbols, "nimcheck")
- defineSymbol(graph.config.symbols, "nimDrNim")
- registerPass graph, verbosePass
- registerPass graph, semPass
- compileProject(graph)
- if conf.errorCounter == 0:
- genSuccessX(graph.config)
- proc processCmdLine(pass: TCmdLinePass, cmd: string; config: ConfigRef) =
- var p = parseopt.initOptParser(cmd)
- var argsCount = 1
- config.commandLine.setLen 0
- config.setCmd cmdCheck
- while true:
- parseopt.next(p)
- case p.kind
- of cmdEnd: break
- of cmdLongOption, cmdShortOption:
- config.commandLine.add " "
- config.commandLine.addCmdPrefix p.kind
- config.commandLine.add p.key.quoteShell # quoteShell to be future proof
- if p.val.len > 0:
- config.commandLine.add ':'
- config.commandLine.add p.val.quoteShell
- if p.key == " ":
- p.key = "-"
- if processArgument(pass, p, argsCount, config): break
- else:
- case p.key.normalize
- of "assumeunique":
- assumeUniqueness = true
- else:
- processSwitch(pass, p, config)
- of cmdArgument:
- config.commandLine.add " "
- config.commandLine.add p.key.quoteShell
- if processArgument(pass, p, argsCount, config): break
- if pass == passCmd2:
- if {optRun, optWasNimscript} * config.globalOptions == {} and
- config.arguments.len > 0 and config.cmd notin {cmdTcc, cmdNimscript}:
- rawMessage(config, errGenerated, errArgsNeedRunOption)
- proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
- incl conf.options, optStaticBoundsCheck
- let self = NimProg(
- supportsStdinFile: true,
- processCmdLine: processCmdLine
- )
- self.initDefinesProg(conf, "drnim")
- if paramCount() == 0:
- helpOnError(conf)
- return
- self.processCmdLineAndProjectPath(conf)
- var graph = newModuleGraph(cache, conf)
- if not self.loadConfigsAndProcessCmdLine(cache, conf, graph): return
- mainCommand(graph)
- if conf.hasHint(hintGCStats): echo(GC_getStatistics())
- when compileOption("gc", "refc"):
- # the new correct mark&sweep collector is too slow :-/
- GC_disableMarkAndSweep()
- when not defined(selftest):
- let conf = newConfigRef()
- handleCmdLine(newIdentCache(), conf)
- when declared(GC_setMaxPause):
- echo GC_getStatistics()
- msgQuit(int8(conf.errorCounter > 0))
|