123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029 |
- #
- #
- # The Nim Compiler
- # (c) Copyright 2015 Andreas Rumpf
- #
- # See the file "copying.txt", included in this
- # distribution, for details about the copyright.
- #
- import
- intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees,
- wordrecg, strutils, options, guards, lineinfos, semfold,
- modulegraphs
- when not defined(leanCompiler):
- import writetracking
- when defined(useDfa):
- import dfa
- # Second semantic checking pass over the AST. Necessary because the old
- # way had some inherent problems. Performs:
- #
- # * effect+exception tracking
- # * "usage before definition" checking
- # ------------------------ exception and tag tracking -------------------------
- discard """
- exception tracking:
- a() # raises 'x', 'e'
- try:
- b() # raises 'e'
- except e:
- # must not undo 'e' here; hrm
- c()
- --> we need a stack of scopes for this analysis
- # XXX enhance the algorithm to care about 'dirty' expressions:
- lock a[i].L:
- inc i # mark 'i' dirty
- lock a[j].L:
- access a[i], a[j] # --> reject a[i]
- """
- type
- TEffects = object
- exc: PNode # stack of exceptions
- tags: PNode # list of tags
- bottom, inTryStmt: int
- owner: PSym
- owner_module: PSym
- init: seq[int] # list of initialized variables
- guards: TModel # nested guards
- locked: seq[PNode] # locked locations
- gcUnsafe, isRecursive, isToplevel, hasSideEffect, inEnforcedGcSafe: bool
- inEnforcedNoSideEffects: bool
- maxLockLevel, currLockLevel: TLockLevel
- config: ConfigRef
- graph: ModuleGraph
- PEffects = var TEffects
- proc `<`(a, b: TLockLevel): bool {.borrow.}
- proc `<=`(a, b: TLockLevel): bool {.borrow.}
- proc `==`(a, b: TLockLevel): bool {.borrow.}
- proc max(a, b: TLockLevel): TLockLevel {.borrow.}
- proc isLocalVar(a: PEffects, s: PSym): bool =
- s.kind in {skVar, skResult} and sfGlobal notin s.flags and
- s.owner == a.owner and s.typ != nil
- proc getLockLevel(t: PType): TLockLevel =
- var t = t
- # tyGenericInst(TLock {tyGenericBody}, tyStatic, tyObject):
- if t.kind == tyGenericInst and t.len == 3: t = t.sons[1]
- if t.kind == tyStatic and t.n != nil and t.n.kind in {nkCharLit..nkInt64Lit}:
- result = t.n.intVal.TLockLevel
- proc lockLocations(a: PEffects; pragma: PNode) =
- if pragma.kind != nkExprColonExpr:
- localError(a.config, pragma.info, "locks pragma without argument")
- return
- var firstLL = TLockLevel(-1'i16)
- for x in pragma[1]:
- let thisLL = getLockLevel(x.typ)
- if thisLL != 0.TLockLevel:
- if thisLL < 0.TLockLevel or thisLL > MaxLockLevel.TLockLevel:
- localError(a.config, x.info, "invalid lock level: " & $thisLL)
- elif firstLL < 0.TLockLevel: firstLL = thisLL
- elif firstLL != thisLL:
- localError(a.config, x.info,
- "multi-lock requires the same static lock level for every operand")
- a.maxLockLevel = max(a.maxLockLevel, firstLL)
- a.locked.add x
- if firstLL >= 0.TLockLevel and firstLL != a.currLockLevel:
- if a.currLockLevel > 0.TLockLevel and a.currLockLevel <= firstLL:
- localError(a.config, pragma.info, "invalid nested locking")
- a.currLockLevel = firstLL
- proc guardGlobal(a: PEffects; n: PNode; guard: PSym) =
- # check whether the corresponding lock is held:
- for L in a.locked:
- if L.kind == nkSym and L.sym == guard: return
- # we allow accesses nevertheless in top level statements for
- # easier initialization:
- #if a.isTopLevel:
- # message(n.info, warnUnguardedAccess, renderTree(n))
- #else:
- if not a.isTopLevel:
- localError(a.config, n.info, "unguarded access: " & renderTree(n))
- # 'guard*' are checks which are concerned with 'guard' annotations
- # (var x{.guard: y.}: int)
- proc guardDotAccess(a: PEffects; n: PNode) =
- let ri = n.sons[1]
- if ri.kind != nkSym or ri.sym.kind != skField: return
- var g = ri.sym.guard
- if g.isNil or a.isTopLevel: return
- # fixup guard:
- if g.kind == skUnknown:
- var field: PSym = nil
- var ty = n.sons[0].typ.skipTypes(abstractPtrs)
- if ty.kind == tyTuple and not ty.n.isNil:
- field = lookupInRecord(ty.n, g.name)
- else:
- while ty != nil and ty.kind == tyObject:
- field = lookupInRecord(ty.n, g.name)
- if field != nil: break
- ty = ty.sons[0]
- if ty == nil: break
- ty = ty.skipTypes(skipPtrs)
- if field == nil:
- localError(a.config, n.info, "invalid guard field: " & g.name.s)
- return
- g = field
- #ri.sym.guard = field
- # XXX unfortunately this is not correct for generic instantiations!
- if g.kind == skField:
- let dot = newNodeI(nkDotExpr, n.info, 2)
- dot.sons[0] = n.sons[0]
- dot.sons[1] = newSymNode(g)
- dot.typ = g.typ
- for L in a.locked:
- #if a.guards.sameSubexprs(dot, L): return
- if guards.sameTree(dot, L): return
- localError(a.config, n.info, "unguarded access: " & renderTree(n))
- else:
- guardGlobal(a, n, g)
- proc makeVolatile(a: PEffects; s: PSym) {.inline.} =
- template compileToCpp(a): untyped =
- a.config.cmd == cmdCompileToCpp or sfCompileToCpp in getModule(a.owner).flags
- if a.inTryStmt > 0 and not compileToCpp(a):
- incl(s.flags, sfVolatile)
- proc initVar(a: PEffects, n: PNode; volatileCheck: bool) =
- if n.kind != nkSym: return
- let s = n.sym
- if isLocalVar(a, s):
- if volatileCheck: makeVolatile(a, s)
- for x in a.init:
- if x == s.id: return
- a.init.add s.id
- proc initVarViaNew(a: PEffects, n: PNode) =
- if n.kind != nkSym: return
- let s = n.sym
- if {tfNeedsInit, tfNotNil} * s.typ.flags <= {tfNotNil}:
- # 'x' is not nil, but that doesn't mean its "not nil" children
- # are initialized:
- initVar(a, n, volatileCheck=true)
- elif isLocalVar(a, s):
- makeVolatile(a, s)
- proc warnAboutGcUnsafe(n: PNode; conf: ConfigRef) =
- #assert false
- message(conf, n.info, warnGcUnsafe, renderTree(n))
- proc markGcUnsafe(a: PEffects; reason: PSym) =
- if not a.inEnforcedGcSafe:
- a.gcUnsafe = true
- if a.owner.kind in routineKinds: a.owner.gcUnsafetyReason = reason
- proc markGcUnsafe(a: PEffects; reason: PNode) =
- if not a.inEnforcedGcSafe:
- a.gcUnsafe = true
- if a.owner.kind in routineKinds:
- if reason.kind == nkSym:
- a.owner.gcUnsafetyReason = reason.sym
- else:
- a.owner.gcUnsafetyReason = newSym(skUnknown, a.owner.name,
- a.owner, reason.info, {})
- when true:
- template markSideEffect(a: PEffects; reason: typed) =
- if not a.inEnforcedNoSideEffects: a.hasSideEffect = true
- else:
- template markSideEffect(a: PEffects; reason: typed) =
- if not a.inEnforcedNoSideEffects: a.hasSideEffect = true
- markGcUnsafe(a, reason)
- proc listGcUnsafety(s: PSym; onlyWarning: bool; cycleCheck: var IntSet; conf: ConfigRef) =
- let u = s.gcUnsafetyReason
- if u != nil and not cycleCheck.containsOrIncl(u.id):
- let msgKind = if onlyWarning: warnGcUnsafe2 else: errGenerated
- case u.kind
- of skLet, skVar:
- message(conf, s.info, msgKind,
- ("'$#' is not GC-safe as it accesses '$#'" &
- " which is a global using GC'ed memory") % [s.name.s, u.name.s])
- of routineKinds:
- # recursive call *always* produces only a warning so the full error
- # message is printed:
- listGcUnsafety(u, true, cycleCheck, conf)
- message(conf, s.info, msgKind,
- "'$#' is not GC-safe as it calls '$#'" %
- [s.name.s, u.name.s])
- of skParam, skForVar:
- message(conf, s.info, msgKind,
- "'$#' is not GC-safe as it performs an indirect call via '$#'" %
- [s.name.s, u.name.s])
- else:
- message(conf, u.info, msgKind,
- "'$#' is not GC-safe as it performs an indirect call here" % s.name.s)
- proc listGcUnsafety(s: PSym; onlyWarning: bool; conf: ConfigRef) =
- var cycleCheck = initIntSet()
- listGcUnsafety(s, onlyWarning, cycleCheck, conf)
- proc useVar(a: PEffects, n: PNode) =
- let s = n.sym
- if isLocalVar(a, s):
- if s.id notin a.init:
- if {tfNeedsInit, tfNotNil} * s.typ.flags != {}:
- message(a.config, n.info, warnProveInit, s.name.s)
- else:
- message(a.config, n.info, warnUninit, s.name.s)
- # prevent superfluous warnings about the same variable:
- a.init.add s.id
- if {sfGlobal, sfThread} * s.flags != {} and s.kind in {skVar, skLet} and
- s.magic != mNimVm:
- if s.guard != nil: guardGlobal(a, n, s.guard)
- if {sfGlobal, sfThread} * s.flags == {sfGlobal} and
- (tfHasGCedMem in s.typ.flags or s.typ.isGCedMem):
- #if warnGcUnsafe in gNotes: warnAboutGcUnsafe(n)
- markGcUnsafe(a, s)
- markSideEffect(a, s)
- else:
- markSideEffect(a, s)
- type
- TIntersection = seq[tuple[id, count: int]] # a simple count table
- proc addToIntersection(inter: var TIntersection, s: int) =
- for j in 0..<inter.len:
- if s == inter[j].id:
- inc inter[j].count
- return
- inter.add((id: s, count: 1))
- proc throws(tracked, n: PNode) =
- if n.typ == nil or n.typ.kind != tyError: tracked.add n
- proc getEbase(g: ModuleGraph; info: TLineInfo): PType =
- result = g.sysTypeFromName(info, "Exception")
- proc excType(g: ModuleGraph; n: PNode): PType =
- # reraise is like raising E_Base:
- let t = if n.kind == nkEmpty or n.typ.isNil: getEbase(g, n.info) else: n.typ
- result = skipTypes(t, skipPtrs)
- proc createRaise(g: ModuleGraph; n: PNode): PNode =
- result = newNode(nkType)
- result.typ = getEbase(g, n.info)
- if not n.isNil: result.info = n.info
- proc createTag(g: ModuleGraph; n: PNode): PNode =
- result = newNode(nkType)
- result.typ = g.sysTypeFromName(n.info, "RootEffect")
- if not n.isNil: result.info = n.info
- proc addEffect(a: PEffects, e: PNode, useLineInfo=true) =
- assert e.kind != nkRaiseStmt
- var aa = a.exc
- for i in a.bottom ..< aa.len:
- if sameType(a.graph.excType(aa[i]), a.graph.excType(e)):
- if not useLineInfo or a.config.cmd == cmdDoc: return
- elif aa[i].info == e.info: return
- throws(a.exc, e)
- proc addTag(a: PEffects, e: PNode, useLineInfo=true) =
- var aa = a.tags
- for i in 0 ..< aa.len:
- if sameType(aa[i].typ.skipTypes(skipPtrs), e.typ.skipTypes(skipPtrs)):
- if not useLineInfo or a.config.cmd == cmdDoc: return
- elif aa[i].info == e.info: return
- throws(a.tags, e)
- proc mergeEffects(a: PEffects, b, comesFrom: PNode) =
- if b.isNil:
- addEffect(a, createRaise(a.graph, comesFrom))
- else:
- for effect in items(b): addEffect(a, effect, useLineInfo=comesFrom != nil)
- proc mergeTags(a: PEffects, b, comesFrom: PNode) =
- if b.isNil:
- addTag(a, createTag(a.graph, comesFrom))
- else:
- for effect in items(b): addTag(a, effect, useLineInfo=comesFrom != nil)
- proc listEffects(a: PEffects) =
- for e in items(a.exc): message(a.config, e.info, hintUser, typeToString(e.typ))
- for e in items(a.tags): message(a.config, e.info, hintUser, typeToString(e.typ))
- #if a.maxLockLevel != 0:
- # message(e.info, hintUser, "lockLevel: " & a.maxLockLevel)
- proc catches(tracked: PEffects, e: PType) =
- let e = skipTypes(e, skipPtrs)
- var L = tracked.exc.len
- var i = tracked.bottom
- while i < L:
- # r supertype of e?
- if safeInheritanceDiff(tracked.graph.excType(tracked.exc[i]), e) <= 0:
- tracked.exc.sons[i] = tracked.exc.sons[L-1]
- dec L
- else:
- inc i
- if tracked.exc.len > 0:
- setLen(tracked.exc.sons, L)
- else:
- assert L == 0
- proc catchesAll(tracked: PEffects) =
- if tracked.exc.len > 0:
- setLen(tracked.exc.sons, tracked.bottom)
- proc track(tracked: PEffects, n: PNode)
- proc trackTryStmt(tracked: PEffects, n: PNode) =
- let oldBottom = tracked.bottom
- tracked.bottom = tracked.exc.len
- let oldState = tracked.init.len
- var inter: TIntersection = @[]
- inc tracked.inTryStmt
- track(tracked, n.sons[0])
- dec tracked.inTryStmt
- for i in oldState..<tracked.init.len:
- addToIntersection(inter, tracked.init[i])
- var branches = 1
- var hasFinally = false
- for i in 1 ..< n.len:
- let b = n.sons[i]
- let blen = sonsLen(b)
- if b.kind == nkExceptBranch:
- inc branches
- if blen == 1:
- catchesAll(tracked)
- else:
- for j in countup(0, blen - 2):
- if b.sons[j].isInfixAs():
- assert(b.sons[j][1].kind == nkType)
- catches(tracked, b.sons[j][1].typ)
- else:
- assert(b.sons[j].kind == nkType)
- catches(tracked, b.sons[j].typ)
- setLen(tracked.init, oldState)
- track(tracked, b.sons[blen-1])
- for i in oldState..<tracked.init.len:
- addToIntersection(inter, tracked.init[i])
- else:
- assert b.kind == nkFinally
- setLen(tracked.init, oldState)
- track(tracked, b.sons[blen-1])
- hasFinally = true
- tracked.bottom = oldBottom
- if not hasFinally:
- setLen(tracked.init, oldState)
- for id, count in items(inter):
- if count == branches: tracked.init.add id
- proc isIndirectCall(n: PNode, owner: PSym): bool =
- # we don't count f(...) as an indirect call if 'f' is an parameter.
- # Instead we track expressions of type tyProc too. See the manual for
- # details:
- if n.kind != nkSym:
- result = true
- elif n.sym.kind == skParam:
- result = owner != n.sym.owner or owner == nil
- elif n.sym.kind notin routineKinds:
- result = true
- proc isForwardedProc(n: PNode): bool =
- result = n.kind == nkSym and sfForward in n.sym.flags
- proc trackPragmaStmt(tracked: PEffects, n: PNode) =
- for i in countup(0, sonsLen(n) - 1):
- var it = n.sons[i]
- if whichPragma(it) == wEffects:
- # list the computed effects up to here:
- listEffects(tracked)
- proc effectSpec(n: PNode, effectType: TSpecialWord): PNode =
- for i in countup(0, sonsLen(n) - 1):
- var it = n.sons[i]
- if it.kind == nkExprColonExpr and whichPragma(it) == effectType:
- result = it.sons[1]
- if result.kind notin {nkCurly, nkBracket}:
- result = newNodeI(nkCurly, result.info)
- result.add(it.sons[1])
- return
- proc documentEffect(cache: IdentCache; n, x: PNode, effectType: TSpecialWord, idx: int): PNode =
- let spec = effectSpec(x, effectType)
- if isNil(spec):
- let s = n.sons[namePos].sym
- let actual = s.typ.n.sons[0]
- if actual.len != effectListLen: return
- let real = actual.sons[idx]
- # warning: hack ahead:
- var effects = newNodeI(nkBracket, n.info, real.len)
- for i in 0 ..< real.len:
- var t = typeToString(real[i].typ)
- if t.startsWith("ref "): t = substr(t, 4)
- effects.sons[i] = newIdentNode(getIdent(cache, t), n.info)
- # set the type so that the following analysis doesn't screw up:
- effects.sons[i].typ = real[i].typ
- result = newNode(nkExprColonExpr, n.info, @[
- newIdentNode(getIdent(cache, specialWords[effectType]), n.info), effects])
- proc documentWriteEffect(cache: IdentCache; n: PNode; flag: TSymFlag; pragmaName: string): PNode =
- let s = n.sons[namePos].sym
- let params = s.typ.n
- var effects = newNodeI(nkBracket, n.info)
- for i in 1 ..< params.len:
- if params[i].kind == nkSym and flag in params[i].sym.flags:
- effects.add params[i]
- if effects.len > 0:
- result = newNode(nkExprColonExpr, n.info, @[
- newIdentNode(getIdent(cache, pragmaName), n.info), effects])
- proc documentNewEffect(cache: IdentCache; n: PNode): PNode =
- let s = n.sons[namePos].sym
- if tfReturnsNew in s.typ.flags:
- result = newIdentNode(getIdent(cache, "new"), n.info)
- proc documentRaises*(cache: IdentCache; n: PNode) =
- if n.sons[namePos].kind != nkSym: return
- let pragmas = n.sons[pragmasPos]
- let p1 = documentEffect(cache, n, pragmas, wRaises, exceptionEffects)
- let p2 = documentEffect(cache, n, pragmas, wTags, tagEffects)
- let p3 = documentWriteEffect(cache, n, sfWrittenTo, "writes")
- let p4 = documentNewEffect(cache, n)
- let p5 = documentWriteEffect(cache, n, sfEscapes, "escapes")
- if p1 != nil or p2 != nil or p3 != nil or p4 != nil or p5 != nil:
- if pragmas.kind == nkEmpty:
- n.sons[pragmasPos] = newNodeI(nkPragma, n.info)
- if p1 != nil: n.sons[pragmasPos].add p1
- if p2 != nil: n.sons[pragmasPos].add p2
- if p3 != nil: n.sons[pragmasPos].add p3
- if p4 != nil: n.sons[pragmasPos].add p4
- if p5 != nil: n.sons[pragmasPos].add p5
- template notGcSafe(t): untyped = {tfGcSafe, tfNoSideEffect} * t.flags == {}
- proc importedFromC(n: PNode): bool =
- # when imported from C, we assume GC-safety.
- result = n.kind == nkSym and sfImportc in n.sym.flags
- proc getLockLevel(s: PSym): TLockLevel =
- result = s.typ.lockLevel
- if result == UnspecifiedLockLevel:
- if {sfImportc, sfNoSideEffect} * s.flags != {} or
- tfNoSideEffect in s.typ.flags:
- result = 0.TLockLevel
- else:
- result = UnknownLockLevel
- #message(s.info, warnUser, "FOR THIS " & s.name.s)
- proc mergeLockLevels(tracked: PEffects, n: PNode, lockLevel: TLockLevel) =
- if lockLevel >= tracked.currLockLevel:
- # if in lock section:
- if tracked.currLockLevel > 0.TLockLevel:
- localError tracked.config, n.info, errGenerated,
- "expected lock level < " & $tracked.currLockLevel &
- " but got lock level " & $lockLevel
- tracked.maxLockLevel = max(tracked.maxLockLevel, lockLevel)
- proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) =
- let pragma = s.ast.sons[pragmasPos]
- let spec = effectSpec(pragma, wRaises)
- mergeEffects(tracked, spec, n)
- let tagSpec = effectSpec(pragma, wTags)
- mergeTags(tracked, tagSpec, n)
- if notGcSafe(s.typ) and sfImportc notin s.flags:
- if warnGcUnsafe in tracked.config.notes: warnAboutGcUnsafe(n, tracked.config)
- markGcUnsafe(tracked, s)
- if tfNoSideEffect notin s.typ.flags:
- markSideEffect(tracked, s)
- mergeLockLevels(tracked, n, s.getLockLevel)
- proc procVarcheck(n: PNode; conf: ConfigRef) =
- if n.kind in nkSymChoices:
- for x in n: procVarCheck(x, conf)
- elif n.kind == nkSym and n.sym.magic != mNone and n.sym.kind in routineKinds:
- localError(conf, n.info, "'$1' cannot be passed to a procvar" % n.sym.name.s)
- proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) =
- let n = n.skipConv
- if paramType.isNil or paramType.kind != tyTypeDesc:
- procVarcheck skipConvAndClosure(n), tracked.config
- #elif n.kind in nkSymChoices:
- # echo "came here"
- let paramType = paramType.skipTypesOrNil(abstractInst)
- if paramType != nil and tfNotNil in paramType.flags and
- n.typ != nil and tfNotNil notin n.typ.flags:
- if n.kind == nkAddr:
- # addr(x[]) can't be proven, but addr(x) can:
- if not containsNode(n, {nkDerefExpr, nkHiddenDeref}): return
- elif (n.kind == nkSym and n.sym.kind in routineKinds) or
- (n.kind in procDefs+{nkObjConstr, nkBracket, nkClosure, nkStrLit..nkTripleStrLit}) or
- (n.kind in nkCallKinds and n[0].kind == nkSym and n[0].sym.magic == mArrToSeq):
- # 'p' is not nil obviously:
- return
- case impliesNotNil(tracked.guards, n)
- of impUnknown:
- message(tracked.config, n.info, errGenerated,
- "cannot prove '$1' is not nil" % n.renderTree)
- of impNo:
- message(tracked.config, n.info, errGenerated,
- "'$1' is provably nil" % n.renderTree)
- of impYes: discard
- proc assumeTheWorst(tracked: PEffects; n: PNode; op: PType) =
- addEffect(tracked, createRaise(tracked.graph, n))
- addTag(tracked, createTag(tracked.graph, n))
- let lockLevel = if op.lockLevel == UnspecifiedLockLevel: UnknownLockLevel
- else: op.lockLevel
- #if lockLevel == UnknownLockLevel:
- # message(n.info, warnUser, "had to assume the worst here")
- mergeLockLevels(tracked, n, lockLevel)
- proc isOwnedProcVar(n: PNode; owner: PSym): bool =
- # XXX prove the soundness of this effect system rule
- result = n.kind == nkSym and n.sym.kind == skParam and owner == n.sym.owner
- proc isNoEffectList(n: PNode): bool {.inline.} =
- assert n.kind == nkEffectList
- n.len == 0 or (n[tagEffects] == nil and n[exceptionEffects] == nil)
- proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) =
- let a = skipConvAndClosure(n)
- let op = a.typ
- if op != nil and op.kind == tyProc and n.skipConv.kind != nkNilLit:
- internalAssert tracked.config, op.n.sons[0].kind == nkEffectList
- var effectList = op.n.sons[0]
- var s = n.skipConv
- if s.kind == nkCast and s[1].typ.kind == tyProc:
- s = s[1]
- if s.kind == nkSym and s.sym.kind in routineKinds and isNoEffectList(effectList):
- propagateEffects(tracked, n, s.sym)
- elif isNoEffectList(effectList):
- if isForwardedProc(n):
- # we have no explicit effects but it's a forward declaration and so it's
- # stated there are no additional effects, so simply propagate them:
- propagateEffects(tracked, n, n.sym)
- elif not isOwnedProcVar(a, tracked.owner):
- # we have no explicit effects so assume the worst:
- assumeTheWorst(tracked, n, op)
- # assume GcUnsafe unless in its type; 'forward' does not matter:
- if notGcSafe(op) and not isOwnedProcVar(a, tracked.owner):
- if warnGcUnsafe in tracked.config.notes: warnAboutGcUnsafe(n, tracked.config)
- markGcUnsafe(tracked, a)
- elif tfNoSideEffect notin op.flags and not isOwnedProcVar(a, tracked.owner):
- markSideEffect(tracked, a)
- else:
- mergeEffects(tracked, effectList.sons[exceptionEffects], n)
- mergeTags(tracked, effectList.sons[tagEffects], n)
- if notGcSafe(op):
- if warnGcUnsafe in tracked.config.notes: warnAboutGcUnsafe(n, tracked.config)
- markGcUnsafe(tracked, a)
- elif tfNoSideEffect notin op.flags:
- markSideEffect(tracked, a)
- if paramType != nil and paramType.kind == tyVar:
- 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:
- let argtype = skipTypes(a.typ, abstractInst)
- # XXX figure out why this can be a non tyProc here. See httpclient.nim for an
- # example that triggers it.
- if argtype.kind == tyProc and notGcSafe(argtype) and not tracked.inEnforcedGcSafe:
- localError(tracked.config, n.info, $n & " is not GC safe")
- notNilCheck(tracked, n, paramType)
- proc breaksBlock(n: PNode): bool =
- # sematic check doesn't allow statements after raise, break, return or
- # call to noreturn proc, so it is safe to check just the last statements
- var it = n
- while it.kind in {nkStmtList, nkStmtListExpr} and it.len > 0:
- it = it.lastSon
- result = it.kind in {nkBreakStmt, nkReturnStmt, nkRaiseStmt} or
- it.kind in nkCallKinds and it[0].kind == nkSym and sfNoReturn in it[0].sym.flags
- proc trackCase(tracked: PEffects, n: PNode) =
- track(tracked, n.sons[0])
- let oldState = tracked.init.len
- let oldFacts = tracked.guards.s.len
- let stringCase = skipTypes(n.sons[0].typ,
- abstractVarRange-{tyTypeDesc}).kind in {tyFloat..tyFloat128, tyString}
- let interesting = not stringCase and interestingCaseExpr(n.sons[0]) and
- warnProveField in tracked.config.notes
- var inter: TIntersection = @[]
- var toCover = 0
- for i in 1..<n.len:
- let branch = n.sons[i]
- setLen(tracked.init, oldState)
- if interesting:
- setLen(tracked.guards.s, oldFacts)
- addCaseBranchFacts(tracked.guards, n, i)
- for i in 0 ..< branch.len:
- track(tracked, branch.sons[i])
- if not breaksBlock(branch.lastSon): inc toCover
- for i in oldState..<tracked.init.len:
- addToIntersection(inter, tracked.init[i])
- setLen(tracked.init, oldState)
- if not stringCase or lastSon(n).kind == nkElse:
- for id, count in items(inter):
- if count >= toCover: tracked.init.add id
- # else we can't merge
- setLen(tracked.guards.s, oldFacts)
- proc trackIf(tracked: PEffects, n: PNode) =
- track(tracked, n.sons[0].sons[0])
- let oldFacts = tracked.guards.s.len
- addFact(tracked.guards, n.sons[0].sons[0])
- let oldState = tracked.init.len
- var inter: TIntersection = @[]
- var toCover = 0
- track(tracked, n.sons[0].sons[1])
- if not breaksBlock(n.sons[0].sons[1]): inc toCover
- for i in oldState..<tracked.init.len:
- addToIntersection(inter, tracked.init[i])
- for i in 1..<n.len:
- let branch = n.sons[i]
- setLen(tracked.guards.s, oldFacts)
- for j in 0..i-1:
- addFactNeg(tracked.guards, n.sons[j].sons[0])
- if branch.len > 1:
- addFact(tracked.guards, branch.sons[0])
- setLen(tracked.init, oldState)
- for i in 0 ..< branch.len:
- track(tracked, branch.sons[i])
- if not breaksBlock(branch.lastSon): inc toCover
- for i in oldState..<tracked.init.len:
- addToIntersection(inter, tracked.init[i])
- setLen(tracked.init, oldState)
- if lastSon(n).len == 1:
- for id, count in items(inter):
- if count >= toCover: tracked.init.add id
- # else we can't merge as it is not exhaustive
- setLen(tracked.guards.s, oldFacts)
- proc trackBlock(tracked: PEffects, n: PNode) =
- if n.kind in {nkStmtList, nkStmtListExpr}:
- var oldState = -1
- for i in 0..<n.len:
- if hasSubnodeWith(n.sons[i], nkBreakStmt):
- # block:
- # x = def
- # if ...: ... break # some nested break
- # y = def
- # --> 'y' not defined after block!
- if oldState < 0: oldState = tracked.init.len
- track(tracked, n.sons[i])
- if oldState > 0: setLen(tracked.init, oldState)
- else:
- track(tracked, n)
- proc isTrue*(n: PNode): bool =
- n.kind == nkSym and n.sym.kind == skEnumField and n.sym.position != 0 or
- n.kind == nkIntLit and n.intVal != 0
- proc paramType(op: PType, i: int): PType =
- if op != nil and i < op.len: result = op.sons[i]
- proc cstringCheck(tracked: PEffects; n: PNode) =
- if n.sons[0].typ.kind == tyCString and (let a = skipConv(n[1]);
- a.typ.kind == tyString and a.kind notin {nkStrLit..nkTripleStrLit}):
- message(tracked.config, n.info, warnUnsafeCode, renderTree(n))
- proc track(tracked: PEffects, n: PNode) =
- case n.kind
- of nkSym:
- useVar(tracked, n)
- of nkRaiseStmt:
- n.sons[0].info = n.info
- #throws(tracked.exc, n.sons[0])
- addEffect(tracked, n.sons[0], useLineInfo=false)
- for i in 0 ..< safeLen(n):
- track(tracked, n.sons[i])
- of nkCallKinds:
- if getConstExpr(tracked.owner_module, n, tracked.graph) != nil:
- return
- # p's effects are ours too:
- var a = n.sons[0]
- let op = a.typ
- if a.kind == nkCast and a[1].typ.kind == tyProc:
- a = a[1]
- # XXX: in rare situations, templates and macros will reach here after
- # calling getAst(templateOrMacro()). Currently, templates and macros
- # are indistinguishable from normal procs (both have tyProc type) and
- # we can detect them only by checking for attached nkEffectList.
- if op != nil and op.kind == tyProc and op.n.sons[0].kind == nkEffectList:
- if a.kind == nkSym:
- if a.sym == tracked.owner: tracked.isRecursive = true
- # even for recursive calls we need to check the lock levels (!):
- mergeLockLevels(tracked, n, a.sym.getLockLevel)
- if sfSideEffect in a.sym.flags: markSideEffect(tracked, a)
- else:
- mergeLockLevels(tracked, n, op.lockLevel)
- var effectList = op.n.sons[0]
- if a.kind == nkSym and a.sym.kind == skMethod:
- propagateEffects(tracked, n, a.sym)
- elif isNoEffectList(effectList):
- if isForwardedProc(a):
- propagateEffects(tracked, n, a.sym)
- elif isIndirectCall(a, tracked.owner):
- assumeTheWorst(tracked, n, op)
- else:
- mergeEffects(tracked, effectList.sons[exceptionEffects], n)
- mergeTags(tracked, effectList.sons[tagEffects], n)
- if notGcSafe(op) and not importedFromC(a):
- # and it's not a recursive call:
- if not (a.kind == nkSym and a.sym == tracked.owner):
- if warnGcUnsafe in tracked.config.notes: warnAboutGcUnsafe(n, tracked.config)
- markGcUnsafe(tracked, a)
- if tfNoSideEffect notin op.flags and not importedFromC(a):
- # and it's not a recursive call:
- if not (a.kind == nkSym and a.sym == tracked.owner):
- markSideEffect(tracked, a)
- if a.kind != nkSym or a.sym.magic != mNBindSym:
- for i in 1 ..< len(n): trackOperand(tracked, n.sons[i], paramType(op, i))
- if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize, mNewSeq}:
- # may not look like an assignment, but it is:
- let arg = n.sons[1]
- initVarViaNew(tracked, arg)
- if arg.typ.len != 0 and {tfNeedsInit} * arg.typ.lastSon.flags != {}:
- if a.sym.magic == mNewSeq and n[2].kind in {nkCharLit..nkUInt64Lit} and
- n[2].intVal == 0:
- # var s: seq[notnil]; newSeq(s, 0) is a special case!
- discard
- else:
- message(tracked.config, arg.info, warnProveInit, $arg)
- for i in 0 ..< safeLen(n):
- track(tracked, n.sons[i])
- of nkDotExpr:
- guardDotAccess(tracked, n)
- for i in 0 ..< len(n): track(tracked, n.sons[i])
- of nkCheckedFieldExpr:
- track(tracked, n.sons[0])
- if warnProveField in tracked.config.notes:
- checkFieldAccess(tracked.guards, n, tracked.config)
- of nkTryStmt: trackTryStmt(tracked, n)
- of nkPragma: trackPragmaStmt(tracked, n)
- of nkAsgn, nkFastAsgn:
- track(tracked, n.sons[1])
- initVar(tracked, n.sons[0], volatileCheck=true)
- invalidateFacts(tracked.guards, n.sons[0])
- track(tracked, n.sons[0])
- addAsgnFact(tracked.guards, n.sons[0], n.sons[1])
- notNilCheck(tracked, n.sons[1], n.sons[0].typ)
- when false: cstringCheck(tracked, n)
- of nkVarSection, nkLetSection:
- for child in n:
- let last = lastSon(child)
- if last.kind != nkEmpty: track(tracked, last)
- if child.kind == nkIdentDefs and last.kind != nkEmpty:
- for i in 0 .. child.len-3:
- initVar(tracked, child.sons[i], volatileCheck=false)
- addAsgnFact(tracked.guards, child.sons[i], last)
- notNilCheck(tracked, last, child.sons[i].typ)
- elif child.kind == nkVarTuple and last.kind != nkEmpty:
- for i in 0 .. child.len-2:
- if child[i].kind == nkEmpty or
- child[i].kind == nkSym and child[i].sym.name.s == "_":
- continue
- initVar(tracked, child[i], volatileCheck=false)
- if last.kind in {nkPar, nkTupleConstr}:
- addAsgnFact(tracked.guards, child[i], last[i])
- notNilCheck(tracked, last[i], child[i].typ)
- # since 'var (a, b): T = ()' is not even allowed, there is always type
- # inference for (a, b) and thus no nil checking is necessary.
- of nkConstSection:
- for child in n:
- let last = lastSon(child)
- track(tracked, last)
- of nkCaseStmt: trackCase(tracked, n)
- of nkWhen, nkIfStmt, nkIfExpr: trackIf(tracked, n)
- of nkBlockStmt, nkBlockExpr: trackBlock(tracked, n.sons[1])
- of nkWhileStmt:
- track(tracked, n.sons[0])
- # 'while true' loop?
- if isTrue(n.sons[0]):
- trackBlock(tracked, n.sons[1])
- else:
- # loop may never execute:
- let oldState = tracked.init.len
- let oldFacts = tracked.guards.s.len
- addFact(tracked.guards, n.sons[0])
- track(tracked, n.sons[1])
- setLen(tracked.init, oldState)
- setLen(tracked.guards.s, oldFacts)
- of nkForStmt, nkParForStmt:
- # we are very conservative here and assume the loop is never executed:
- let oldState = tracked.init.len
- for i in 0 ..< len(n):
- track(tracked, n.sons[i])
- setLen(tracked.init, oldState)
- of nkObjConstr:
- when false: track(tracked, n.sons[0])
- let oldFacts = tracked.guards.s.len
- for i in 1 ..< len(n):
- let x = n.sons[i]
- track(tracked, x)
- if x.sons[0].kind == nkSym and sfDiscriminant in x.sons[0].sym.flags:
- addDiscriminantFact(tracked.guards, x)
- setLen(tracked.guards.s, oldFacts)
- of nkPragmaBlock:
- let pragmaList = n.sons[0]
- let oldLocked = tracked.locked.len
- let oldLockLevel = tracked.currLockLevel
- var enforcedGcSafety = false
- var enforceNoSideEffects = false
- for i in 0 ..< pragmaList.len:
- let pragma = whichPragma(pragmaList.sons[i])
- if pragma == wLocks:
- lockLocations(tracked, pragmaList.sons[i])
- elif pragma == wGcSafe:
- enforcedGcSafety = true
- elif pragma == wNosideeffect:
- enforceNoSideEffects = true
- if enforcedGcSafety: tracked.inEnforcedGcSafe = true
- if enforceNoSideEffects: tracked.inEnforcedNoSideEffects = true
- track(tracked, n.lastSon)
- if enforcedGcSafety: tracked.inEnforcedGcSafe = false
- if enforceNoSideEffects: tracked.inEnforcedNoSideEffects = false
- setLen(tracked.locked, oldLocked)
- tracked.currLockLevel = oldLockLevel
- of nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef,
- nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef:
- discard
- of nkCast, nkHiddenStdConv, nkHiddenSubConv, nkConv:
- if n.len == 2: track(tracked, n.sons[1])
- of nkObjUpConv, nkObjDownConv, nkChckRange, nkChckRangeF, nkChckRange64:
- if n.len == 1: track(tracked, n.sons[0])
- else:
- for i in 0 ..< safeLen(n): track(tracked, n.sons[i])
- proc subtypeRelation(g: ModuleGraph; spec, real: PNode): bool =
- result = safeInheritanceDiff(g.excType(real), spec.typ) <= 0
- proc checkRaisesSpec(g: ModuleGraph; spec, real: PNode, msg: string, hints: bool;
- effectPredicate: proc (g: ModuleGraph; a, b: PNode): bool {.nimcall.}) =
- # check that any real exception is listed in 'spec'; mark those as used;
- # report any unused exception
- var used = initIntSet()
- for r in items(real):
- block search:
- for s in 0 ..< spec.len:
- if effectPredicate(g, spec[s], r):
- used.incl(s)
- break search
- # XXX call graph analysis would be nice here!
- pushInfoContext(g.config, spec.info)
- localError(g.config, r.info, errGenerated, msg & typeToString(r.typ))
- popInfoContext(g.config)
- # hint about unnecessarily listed exception types:
- if hints:
- for s in 0 ..< spec.len:
- if not used.contains(s):
- message(g.config, spec[s].info, hintXDeclaredButNotUsed, renderTree(spec[s]))
- proc checkMethodEffects*(g: ModuleGraph; disp, branch: PSym) =
- ## checks for consistent effects for multi methods.
- let actual = branch.typ.n.sons[0]
- if actual.len != effectListLen: return
- let p = disp.ast.sons[pragmasPos]
- let raisesSpec = effectSpec(p, wRaises)
- if not isNil(raisesSpec):
- checkRaisesSpec(g, raisesSpec, actual.sons[exceptionEffects],
- "can raise an unlisted exception: ", hints=off, subtypeRelation)
- let tagsSpec = effectSpec(p, wTags)
- if not isNil(tagsSpec):
- checkRaisesSpec(g, tagsSpec, actual.sons[tagEffects],
- "can have an unlisted effect: ", hints=off, subtypeRelation)
- 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)
- if branch.typ.lockLevel > disp.typ.lockLevel:
- when true:
- message(g.config, branch.info, warnLockLevel,
- "base method has lock level $1, but dispatcher has $2" %
- [$branch.typ.lockLevel, $disp.typ.lockLevel])
- else:
- # XXX make this an error after bigbreak has been released:
- localError(g.config, branch.info,
- "base method has lock level $1, but dispatcher has $2" %
- [$branch.typ.lockLevel, $disp.typ.lockLevel])
- proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode) =
- var effects = t.n[0]
- if t.kind != tyProc or effects.kind != nkEffectList: return
- if n.kind != nkEmpty:
- internalAssert g.config, effects.len == 0
- newSeq(effects.sons, effectListLen)
- let raisesSpec = effectSpec(n, wRaises)
- if not isNil(raisesSpec):
- effects[exceptionEffects] = raisesSpec
- let tagsSpec = effectSpec(n, wTags)
- if not isNil(tagsSpec):
- effects[tagEffects] = tagsSpec
- effects[pragmasEffects] = n
- proc initEffects(g: ModuleGraph; effects: PNode; s: PSym; t: var TEffects) =
- newSeq(effects.sons, effectListLen)
- effects.sons[exceptionEffects] = newNodeI(nkArgList, s.info)
- effects.sons[tagEffects] = newNodeI(nkArgList, s.info)
- effects.sons[usesEffects] = g.emptyNode
- effects.sons[writeEffects] = g.emptyNode
- effects.sons[pragmasEffects] = g.emptyNode
- t.exc = effects.sons[exceptionEffects]
- t.tags = effects.sons[tagEffects]
- t.owner = s
- t.owner_module = s.getModule
- t.init = @[]
- t.guards.s = @[]
- t.guards.o = initOperators(g)
- t.locked = @[]
- t.graph = g
- t.config = g.config
- proc trackProc*(g: ModuleGraph; s: PSym, body: PNode) =
- var effects = s.typ.n.sons[0]
- if effects.kind != nkEffectList: return
- # effects already computed?
- if sfForward in s.flags: return
- if effects.len == effectListLen: return
- var t: TEffects
- initEffects(g, effects, s, t)
- track(t, body)
- if not isEmptyType(s.typ.sons[0]) and
- {tfNeedsInit, tfNotNil} * s.typ.sons[0].flags != {} and
- s.kind in {skProc, skFunc, skConverter, skMethod}:
- var res = s.ast.sons[resultPos].sym # get result symbol
- if res.id notin t.init:
- message(g.config, body.info, warnProveInit, "result")
- let p = s.ast.sons[pragmasPos]
- let raisesSpec = effectSpec(p, wRaises)
- if not isNil(raisesSpec):
- checkRaisesSpec(g, raisesSpec, t.exc, "can raise an unlisted exception: ",
- hints=on, subtypeRelation)
- # after the check, use the formal spec:
- effects.sons[exceptionEffects] = raisesSpec
- let tagsSpec = effectSpec(p, wTags)
- if not isNil(tagsSpec):
- checkRaisesSpec(g, tagsSpec, t.tags, "can have an unlisted effect: ",
- hints=off, subtypeRelation)
- # after the check, use the formal spec:
- effects.sons[tagEffects] = tagsSpec
- 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)
- listGcUnsafety(s, onlyWarning=false, g.config)
- else:
- listGcUnsafety(s, onlyWarning=true, g.config)
- #localError(s.info, warnGcUnsafe2, s.name.s)
- if sfNoSideEffect in s.flags and t.hasSideEffect:
- when false:
- listGcUnsafety(s, onlyWarning=false, g.config)
- else:
- localError(g.config, s.info, "'$1' can have side effects" % s.name.s)
- if not t.gcUnsafe:
- s.typ.flags.incl tfGcSafe
- if not t.hasSideEffect and sfSideEffect notin s.flags:
- s.typ.flags.incl tfNoSideEffect
- if s.typ.lockLevel == UnspecifiedLockLevel:
- s.typ.lockLevel = t.maxLockLevel
- elif t.maxLockLevel > s.typ.lockLevel:
- #localError(s.info,
- message(g.config, s.info, warnLockLevel,
- "declared lock level is $1, but real lock level is $2" %
- [$s.typ.lockLevel, $t.maxLockLevel])
- when defined(useDfa):
- if s.kind == skFunc:
- dataflowAnalysis(s, body)
- when false: trackWrites(s, body)
- proc trackTopLevelStmt*(g: ModuleGraph; module: PSym; n: PNode) =
- if n.kind in {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef, nkFuncDef,
- nkTypeSection, nkConverterDef, nkMethodDef, nkIteratorDef}:
- return
- var effects = newNode(nkEffectList, n.info)
- var t: TEffects
- initEffects(g, effects, module, t)
- t.isToplevel = true
- track(t, n)
|