12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388 |
- #
- #
- # The Nim Compiler
- # (c) Copyright 2017 Andreas Rumpf
- #
- # See the file "copying.txt", included in this
- # distribution, for details about the copyright.
- #
- import ast, renderer, intsets, tables, msgs, options, lineinfos, strformat, idents, treetab, hashes
- import sequtils, strutils, sets
- when defined(nimPreviewSlimSystem):
- import std/assertions
- # IMPORTANT: notes not up to date, i'll update this comment again
- #
- # notes:
- #
- # Env: int => nilability
- # a = b
- # nilability a <- nilability b
- # deref a
- # if Nil error is nil
- # if MaybeNil error might be nil, hint add if isNil
- # if Safe fine
- # fun(arg: A)
- # nilability arg <- for ref MaybeNil, for not nil or others Safe
- # map is env?
- # a or b
- # each one forks a different env
- # result = union(envL, envR)
- # a and b
- # b forks a's env
- # if a: code
- # result = union(previousEnv after not a, env after code)
- # if a: b else: c
- # result = union(env after b, env after c)
- # result = b
- # nilability result <- nilability b, if return type is not nil and result not safe, error
- # return b
- # as result = b
- # try: a except: b finally: c
- # in b and c env is union of all possible try first n lines, after union of a and b and c
- # keep in mind canRaise and finally
- # case a: of b: c
- # similar to if
- # call(arg)
- # if it returns ref, assume it's MaybeNil: hint that one can add not nil to the return type
- # call(var arg) # zahary comment
- # if arg is ref, assume it's MaybeNil after call
- # loop
- # union of env for 0, 1, 2 iterations as Herb Sutter's paper
- # why 2?
- # return
- # if something: stop (break return etc)
- # is equivalent to if something: .. else: remain
- # new(ref)
- # ref becomes Safe
- # objConstr(a: b)
- # returns safe
- # each check returns its nilability and map
- type
- SeqOfDistinct[T, U] = distinct seq[U]
- # TODO use distinct base type instead of int?
- func `[]`[T, U](a: SeqOfDistinct[T, U], index: T): U =
- (seq[U])(a)[index.int]
- proc `[]=`[T, U](a: var SeqOfDistinct[T, U], index: T, value: U) =
- ((seq[U])(a))[index.int] = value
- func `[]`[T, U](a: var SeqOfDistinct[T, U], index: T): var U =
- (seq[U])(a)[index.int]
- func len[T, U](a: SeqOfDistinct[T, U]): T =
- (seq[U])(a).len.T
- func low[T, U](a: SeqOfDistinct[T, U]): T =
- (seq[U])(a).low.T
- func high[T, U](a: SeqOfDistinct[T, U]): T =
- (seq[U])(a).high.T
- proc setLen[T, U](a: var SeqOfDistinct[T, U], length: T) =
- ((seq[U])(a)).setLen(length.Natural)
- proc newSeqOfDistinct[T, U](length: T = 0.T): SeqOfDistinct[T, U] =
- (SeqOfDistinct[T, U])(newSeq[U](length.int))
- func newSeqOfDistinct[T, U](length: int = 0): SeqOfDistinct[T, U] =
- # newSeqOfDistinct(length.T)
- # ? newSeqOfDistinct[T, U](length.T)
- (SeqOfDistinct[T, U])(newSeq[U](length))
- iterator items[T, U](a: SeqOfDistinct[T, U]): U =
- for element in (seq[U])(a):
- yield element
- iterator pairs[T, U](a: SeqOfDistinct[T, U]): (T, U) =
- for i, element in (seq[U])(a):
- yield (i.T, element)
- func `$`[T, U](a: SeqOfDistinct[T, U]): string =
- $((seq[U])(a))
- proc add*[T, U](a: var SeqOfDistinct[T, U], value: U) =
- ((seq[U])(a)).add(value)
- type
- ## a hashed representation of a node: should be equal for structurally equal nodes
- Symbol = distinct int
- ## the index of an expression in the pre-indexed sequence of those
- ExprIndex = distinct int16
- ## the set index
- SetIndex = distinct int
- ## transition kind:
- ## what was the reason for changing the nilability of an expression
- ## useful for error messages and showing why an expression is being detected as nil / maybe nil
- TransitionKind = enum TArg, TAssign, TType, TNil, TVarArg, TResult, TSafe, TPotentialAlias, TDependant
- ## keep history for each transition
- History = object
- info: TLineInfo ## the location
- nilability: Nilability ## the nilability
- kind: TransitionKind ## what kind of transition was that
- node: PNode ## the node of the expression
- ## the context for the checker: an instance for each procedure
- NilCheckerContext = ref object
- # abstractTime: AbstractTime
- # partitions: Partitions
- # symbolGraphs: Table[Symbol, ]
- symbolIndices: Table[Symbol, ExprIndex] ## index for each symbol
- expressions: SeqOfDistinct[ExprIndex, PNode] ## a sequence of pre-indexed expressions
- dependants: SeqOfDistinct[ExprIndex, IntSet] ## expr indices for expressions which are compound and based on others
- warningLocations: HashSet[TLineInfo] ## warning locations to check we don't warn twice for stuff like warnings in for loops
- idgen: IdGenerator ## id generator
- config: ConfigRef ## the config of the compiler
- ## a map that is containing the current nilability for usually a branch
- ## and is pointing optionally to a parent map: they make a stack of maps
- NilMap = ref object
- expressions: SeqOfDistinct[ExprIndex, Nilability] ## the expressions with the same order as in NilCheckerContext
- history: SeqOfDistinct[ExprIndex, seq[History]] ## history for each of them
- # what about gc and refs?
- setIndices: SeqOfDistinct[ExprIndex, SetIndex] ## set indices for each expression
- sets: SeqOfDistinct[SetIndex, IntSet] ## disjoint sets with the aliased expressions
- parent: NilMap ## the parent map
- ## Nilability : if a value is nilable.
- ## we have maybe nil and nil, so we can differentiate between
- ## cases where we know for sure a value is nil and not
- ## otherwise we can have Safe, MaybeNil
- ## Parent: is because we just use a sequence with the same length
- ## instead of a table, and we need to check if something was initialized
- ## at all: if Parent is set, then you need to check the parent nilability
- ## if the parent is nil, then for now we return MaybeNil
- ## unreachable is the result of add(Safe, Nil) and others
- ## it is a result of no states left, so it's usually e.g. in unreachable else branches?
- Nilability* = enum Parent, Safe, MaybeNil, Nil, Unreachable
- ## check
- Check = object
- nilability: Nilability
- map: NilMap
- elements: seq[(PNode, Nilability)]
- # useful to have known resultId so we can set it in the beginning and on return
- const resultId: Symbol = (-1).Symbol
- const resultExprIndex: ExprIndex = 0.ExprIndex
- const noSymbol = (-2).Symbol
- func `<`*(a: ExprIndex, b: ExprIndex): bool =
- a.int16 < b.int16
- func `<=`*(a: ExprIndex, b: ExprIndex): bool =
- a.int16 <= b.int16
- func `>`*(a: ExprIndex, b: ExprIndex): bool =
- a.int16 > b.int16
- func `>=`*(a: ExprIndex, b: ExprIndex): bool =
- a.int16 >= b.int16
- func `==`*(a: ExprIndex, b: ExprIndex): bool =
- a.int16 == b.int16
- func `$`*(a: ExprIndex): string =
- $(a.int16)
- func `+`*(a: ExprIndex, b: ExprIndex): ExprIndex =
- (a.int16 + b.int16).ExprIndex
- # TODO overflowing / < 0?
- func `-`*(a: ExprIndex, b: ExprIndex): ExprIndex =
- (a.int16 - b.int16).ExprIndex
- func `$`*(a: SetIndex): string =
- $(a.int)
- func `==`*(a: SetIndex, b: SetIndex): bool =
- a.int == b.int
- func `+`*(a: SetIndex, b: SetIndex): SetIndex =
- (a.int + b.int).SetIndex
- # TODO over / under limit?
- func `-`*(a: SetIndex, b: SetIndex): SetIndex =
- (a.int - b.int).SetIndex
- proc check(n: PNode, ctx: NilCheckerContext, map: NilMap): Check
- proc checkCondition(n: PNode, ctx: NilCheckerContext, map: NilMap, reverse: bool, base: bool): NilMap
- # the NilMap structure
- proc newNilMap(parent: NilMap = nil, count: int = -1): NilMap =
- var expressionsCount = 0
- if count != -1:
- expressionsCount = count
- elif not parent.isNil:
- expressionsCount = parent.expressions.len.int
- result = NilMap(
- expressions: newSeqOfDistinct[ExprIndex, Nilability](expressionsCount),
- history: newSeqOfDistinct[ExprIndex, seq[History]](expressionsCount),
- setIndices: newSeqOfDistinct[ExprIndex, SetIndex](expressionsCount),
- parent: parent)
- if parent.isNil:
- for i, expr in result.expressions:
- result.setIndices[i] = i.SetIndex
- var newSet = initIntSet()
- newSet.incl(i.int)
- result.sets.add(newSet)
- else:
- for i, exprs in parent.sets:
- result.sets.add(exprs)
- for i, index in parent.setIndices:
- result.setIndices[i] = index
- # result.sets = parent.sets
- # if not parent.isNil:
- # # optimize []?
- # result.expressions = parent.expressions
- # result.history = parent.history
- # result.sets = parent.sets
- # result.base = if parent.isNil: result else: parent.base
- proc `[]`(map: NilMap, index: ExprIndex): Nilability =
- if index < 0.ExprIndex or index >= map.expressions.len:
- return MaybeNil
- var now = map
- while not now.isNil:
- if now.expressions[index] != Parent:
- return now.expressions[index]
- now = now.parent
- return MaybeNil
- proc history(map: NilMap, index: ExprIndex): seq[History] =
- if index < map.expressions.len:
- map.history[index]
- else:
- @[]
- # helpers for debugging
- # import macros
- # echo-s only when nilDebugInfo is defined
- # macro aecho*(a: varargs[untyped]): untyped =
- # var e = nnkCall.newTree(ident"echo")
- # for b in a:
- # e.add(b)
- # result = quote:
- # when defined(nilDebugInfo):
- # `e`
- # end of helpers for debugging
- proc symbol(n: PNode): Symbol
- func `$`(map: NilMap): string
- proc reverseDirect(map: NilMap): NilMap
- proc checkBranch(n: PNode, ctx: NilCheckerContext, map: NilMap): Check
- proc hasUnstructuredControlFlowJump(n: PNode): bool
- proc symbol(n: PNode): Symbol =
- ## returns a Symbol for each expression
- ## the goal is to get an unique Symbol
- ## but we have to ensure hashTree does it as we expect
- case n.kind:
- of nkIdent:
- # TODO ensure no idents get passed to symbol
- result = noSymbol
- of nkSym:
- if n.sym.kind == skResult: # credit to disruptek for showing me that
- result = resultId
- else:
- result = n.sym.id.Symbol
- of nkHiddenAddr, nkAddr:
- result = symbol(n[0])
- else:
- result = hashTree(n).Symbol
- # echo "symbol ", n, " ", n.kind, " ", result.int
- func `$`(map: NilMap): string =
- result = ""
- var now = map
- var stack: seq[NilMap] = @[]
- while not now.isNil:
- stack.add(now)
- now = now.parent
- result.add("### start\n")
- for i in 0 .. stack.len - 1:
- now = stack[i]
- result.add(" ###\n")
- for index, value in now.expressions:
- result.add(&" {index} {value}\n")
- result.add "### end\n"
- proc namedMapDebugInfo(ctx: NilCheckerContext, map: NilMap): string =
- result = ""
- var now = map
- var stack: seq[NilMap] = @[]
- while not now.isNil:
- stack.add(now)
- now = now.parent
- result.add("### start\n")
- for i in 0 .. stack.len - 1:
- now = stack[i]
- result.add(" ###\n")
- for index, value in now.expressions:
- let name = ctx.expressions[index]
- result.add(&" {name} {index} {value}\n")
- result.add("### end\n")
- proc namedSetsDebugInfo(ctx: NilCheckerContext, map: NilMap): string =
- result = "### sets "
- for index, setIndex in map.setIndices:
- var aliasSet = map.sets[setIndex]
- result.add("{")
- let expressions = aliasSet.mapIt($ctx.expressions[it.ExprIndex])
- result.add(join(expressions, ", "))
- result.add("} ")
- result.add("\n")
- proc namedMapAndSetsDebugInfo(ctx: NilCheckerContext, map: NilMap): string =
- result = namedMapDebugInfo(ctx, map) & namedSetsDebugInfo(ctx, map)
- const noExprIndex = (-1).ExprIndex
- const noSetIndex = (-1).SetIndex
- proc `==`(a: Symbol, b: Symbol): bool =
- a.int == b.int
- func `$`(a: Symbol): string =
- $(a.int)
- template isConstBracket(n: PNode): bool =
- n.kind == nkBracketExpr and n[1].kind in nkLiterals
- proc index(ctx: NilCheckerContext, n: PNode): ExprIndex =
- # echo "n ", n, " ", n.kind
- let a = symbol(n)
- if ctx.symbolIndices.hasKey(a):
- return ctx.symbolIndices[a]
- else:
- #for a, e in ctx.expressions:
- # echo a, " ", e
- #echo n.kind
- # internalError(ctx.config, n.info, "expected " & $a & " " & $n & " to have a index")
- return noExprIndex
- #
- #ctx.symbolIndices[symbol(n)]
- proc aliasSet(ctx: NilCheckerContext, map: NilMap, n: PNode): IntSet =
- result = map.sets[map.setIndices[ctx.index(n)]]
- proc aliasSet(ctx: NilCheckerContext, map: NilMap, index: ExprIndex): IntSet =
- result = map.sets[map.setIndices[index]]
- proc store(map: NilMap, ctx: NilCheckerContext, index: ExprIndex, value: Nilability, kind: TransitionKind, info: TLineInfo, node: PNode = nil) =
- if index == noExprIndex:
- return
- map.expressions[index] = value
- map.history[index].add(History(info: info, kind: kind, node: node, nilability: value))
- #echo node, " ", index, " ", value
- #echo ctx.namedMapAndSetsDebugInfo(map)
- #for a, b in map.sets:
- # echo a, " ", b
- # echo map
- var exprAliases = aliasSet(ctx, map, index)
- for a in exprAliases:
- if a.ExprIndex != index:
- #echo "alias ", a, " ", index
- map.expressions[a.ExprIndex] = value
- if value == Safe:
- map.history[a.ExprIndex] = @[]
- else:
- map.history[a.ExprIndex].add(History(info: info, kind: TPotentialAlias, node: node, nilability: value))
- proc moveOut(ctx: NilCheckerContext, map: NilMap, target: PNode) =
- #echo "move out ", target
- var targetIndex = ctx.index(target)
- var targetSetIndex = map.setIndices[targetIndex]
- if targetSetIndex != noSetIndex:
- var targetSet = map.sets[targetSetIndex]
- if targetSet.len > 1:
- var other: ExprIndex = default(ExprIndex)
- for element in targetSet:
- if element.ExprIndex != targetIndex:
- other = element.ExprIndex
- break
- # map.sets[element].excl(targetIndex)
- map.sets[map.setIndices[other]].excl(targetIndex.int)
- var newSet = initIntSet()
- newSet.incl(targetIndex.int)
- map.sets.add(newSet)
- map.setIndices[targetIndex] = map.sets.len - 1.SetIndex
- proc moveOutDependants(ctx: NilCheckerContext, map: NilMap, node: PNode) =
- let index = ctx.index(node)
- for dependant in ctx.dependants[index]:
- moveOut(ctx, map, ctx.expressions[dependant.ExprIndex])
- proc storeDependants(ctx: NilCheckerContext, map: NilMap, node: PNode, value: Nilability) =
- let index = ctx.index(node)
- for dependant in ctx.dependants[index]:
- map.store(ctx, dependant.ExprIndex, value, TDependant, node.info, node)
- proc move(ctx: NilCheckerContext, map: NilMap, target: PNode, assigned: PNode) =
- #echo "move ", target, " ", assigned
- var targetIndex = ctx.index(target)
- var assignedIndex: ExprIndex
- var targetSetIndex = map.setIndices[targetIndex]
- var assignedSetIndex: SetIndex
- if assigned.kind == nkSym:
- assignedIndex = ctx.index(assigned)
- assignedSetIndex = map.setIndices[assignedIndex]
- else:
- assignedIndex = noExprIndex
- assignedSetIndex = noSetIndex
- if assignedIndex == noExprIndex:
- moveOut(ctx, map, target)
- elif targetSetIndex != assignedSetIndex:
- map.sets[targetSetIndex].excl(targetIndex.int)
- map.sets[assignedSetIndex].incl(targetIndex.int)
- map.setIndices[targetIndex] = assignedSetIndex
- # proc hasKey(map: NilMap, ): bool =
- # var now = map
- # result = false
- # while not now.isNil:
- # if now.locals.hasKey(graphIndex):
- # return true
- # now = now.previous
- iterator pairs(map: NilMap): (ExprIndex, Nilability) =
- for index, value in map.expressions:
- yield (index, map[index])
- proc copyMap(map: NilMap): NilMap =
- if map.isNil:
- return nil
- result = newNilMap(map.parent) # no need for copy? if we change only this
- result.expressions = map.expressions
- result.history = map.history
- result.sets = map.sets
- result.setIndices = map.setIndices
- using
- n: PNode
- conf: ConfigRef
- ctx: NilCheckerContext
- map: NilMap
- proc typeNilability(typ: PType): Nilability
- # maybe: if canRaise, return MaybeNil ?
- # no, because the target might be safe already
- # with or without an exception
- proc checkCall(n, ctx, map): Check =
- # checks each call
- # special case for new(T) -> result is always Safe
- # for the others it depends on the return type of the call
- # check args and handle possible mutations
- var isNew = false
- result.map = map
- for i, child in n:
- discard check(child, ctx, map)
- if i > 0:
- # var args make a new map with MaybeNil for our node
- # as it might have been mutated
- # TODO similar for normal refs and fields: find dependent exprs: brackets
- if child.kind == nkHiddenAddr and not child.typ.isNil and child.typ.kind == tyVar and child.typ[0].kind == tyRef:
- if not isNew:
- result.map = newNilMap(map)
- isNew = true
- # result.map[$child] = MaybeNil
- var arg = child
- while arg.kind == nkHiddenAddr:
- arg = arg[0]
- let a = ctx.index(arg)
- if a != noExprIndex:
- moveOut(ctx, result.map, arg)
- moveOutDependants(ctx, result.map, arg)
- result.map.store(ctx, a, MaybeNil, TVarArg, n.info, arg)
- storeDependants(ctx, result.map, arg, MaybeNil)
- elif not child.typ.isNil and child.typ.kind == tyRef:
- if child.kind in {nkSym, nkDotExpr} or isConstBracket(child):
- let a = ctx.index(child)
- if ctx.dependants[a].len > 0:
- if not isNew:
- result.map = newNilMap(map)
- isNew = true
- moveOutDependants(ctx, result.map, child)
- storeDependants(ctx, result.map, child, MaybeNil)
- if n[0].kind == nkSym and n[0].sym.magic == mNew:
- # new hidden deref?
- var value = if n[1].kind == nkHiddenDeref: n[1][0] else: n[1]
- let b = ctx.index(value)
- result.map.store(ctx, b, Safe, TAssign, value.info, value)
- result.nilability = Safe
- else:
- # echo "n ", n, " ", n.typ.isNil
- if not n.typ.isNil:
- result.nilability = typeNilability(n.typ)
- else:
- result.nilability = Safe
- # echo result.map
- template event(b: History): string =
- case b.kind:
- of TArg: "param with nilable type"
- of TNil: "it returns true for isNil"
- of TAssign: "assigns a value which might be nil"
- of TVarArg: "passes it as a var arg which might change to nil"
- of TResult: "it is nil by default"
- of TType: "it has ref type"
- of TSafe: "it is safe here as it returns false for isNil"
- of TPotentialAlias: "it might be changed directly or through an alias"
- of TDependant: "it might be changed because its base might be changed"
- proc derefWarning(n, ctx, map; kind: Nilability) =
- ## a warning for potentially unsafe dereference
- if n.info in ctx.warningLocations:
- return
- ctx.warningLocations.incl(n.info)
- var a: seq[History] = @[]
- if n.kind == nkSym:
- a = history(map, ctx.index(n))
- var res = ""
- var issue = case kind:
- of Nil: "it is nil"
- of MaybeNil: "it might be nil"
- of Unreachable: "it is unreachable"
- else: ""
- res.add("can't deref " & $n & ", " & issue)
- if a.len > 0:
- res.add("\n")
- for b in a:
- res.add(" " & event(b) & " on line " & $b.info.line & ":" & $b.info.col)
- message(ctx.config, n.info, warnStrictNotNil, res)
- proc handleNilability(check: Check; n, ctx, map) =
- ## handle the check:
- ## register a warning(error?) for Nil/MaybeNil
- case check.nilability:
- of Nil:
- derefWarning(n, ctx, map, Nil)
- of MaybeNil:
- derefWarning(n, ctx, map, MaybeNil)
- of Unreachable:
- derefWarning(n, ctx, map, Unreachable)
- else:
- when defined(nilDebugInfo):
- message(ctx.config, n.info, hintUser, "can deref " & $n)
- proc checkDeref(n, ctx, map): Check =
- ## check dereference: deref n should be ok only if n is Safe
- result = check(n[0], ctx, map)
- handleNilability(result, n[0], ctx, map)
- proc checkRefExpr(n, ctx; check: Check): Check =
- ## check ref expressions: TODO not sure when this happens
- result = check
- if n.typ.kind != tyRef:
- result.nilability = typeNilability(n.typ)
- elif tfNotNil notin n.typ.flags:
- # echo "ref key ", n, " ", n.kind
- if n.kind in {nkSym, nkDotExpr} or isConstBracket(n):
- let key = ctx.index(n)
- result.nilability = result.map[key]
- elif n.kind == nkBracketExpr:
- # sometimes false positive
- result.nilability = MaybeNil
- else:
- # sometimes maybe false positive
- result.nilability = MaybeNil
- proc checkDotExpr(n, ctx, map): Check =
- ## check dot expressions: make sure we can dereference the base
- result = check(n[0], ctx, map)
- result = checkRefExpr(n, ctx, result)
- proc checkBracketExpr(n, ctx, map): Check =
- ## check bracket expressions: make sure we can dereference the base
- result = check(n[0], ctx, map)
- # if might be deref: [] == *(a + index) for cstring
- handleNilability(result, n[0], ctx, map)
- result = check(n[1], ctx, result.map)
- result = checkRefExpr(n, ctx, result)
- # echo n, " ", result.nilability
- template union(l: Nilability, r: Nilability): Nilability =
- ## unify two states
- if l == r:
- l
- else:
- MaybeNil
- template add(l: Nilability, r: Nilability): Nilability =
- if l == r: # Safe Safe -> Safe etc
- l
- elif l == Parent: # Parent Safe -> Safe etc
- r
- elif r == Parent: # Safe Parent -> Safe etc
- l
- elif l == Unreachable or r == Unreachable: # Safe Unreachable -> Unreachable etc
- Unreachable
- elif l == MaybeNil: # Safe MaybeNil -> Safe etc
- r
- elif r == MaybeNil: # MaybeNil Nil -> Nil etc
- l
- else: # Safe Nil -> Unreachable etc
- Unreachable
- proc findCommonParent(l: NilMap, r: NilMap): NilMap =
- result = l.parent
- while not result.isNil:
- var rparent = r.parent
- while not rparent.isNil:
- if result == rparent:
- return result
- rparent = rparent.parent
- result = result.parent
- proc union(ctx: NilCheckerContext, l: NilMap, r: NilMap): NilMap =
- ## unify two maps from different branches
- ## combine their locals
- ## what if they are from different parts of the same tree
- ## e.g.
- ## a -> b -> c
- ## -> b1
- ## common then?
- ##
- if l.isNil:
- return r
- elif r.isNil:
- return l
- let common = findCommonParent(l, r)
- result = newNilMap(common, ctx.expressions.len.int)
- for index, value in l:
- let h = history(r, index)
- let info = if h.len > 0: h[^1].info else: TLineInfo(line: 0) # assert h.len > 0
- # echo "history", name, value, r[name], h[^1].info.line
- result.store(ctx, index, union(value, r[index]), TAssign, info)
- proc add(ctx: NilCheckerContext, l: NilMap, r: NilMap): NilMap =
- #echo "add "
- #echo namedMapDebugInfo(ctx, l)
- #echo " : "
- #echo namedMapDebugInfo(ctx, r)
- if l.isNil:
- return r
- elif r.isNil:
- return l
- let common = findCommonParent(l, r)
- result = newNilMap(common, ctx.expressions.len.int)
- for index, value in l:
- let h = history(r, index)
- let info = if h.len > 0: h[^1].info else: TLineInfo(line: 0)
- # TODO: refactor and also think: is TAssign a good one
- result.store(ctx, index, add(value, r[index]), TAssign, info)
- #echo "result"
- #echo namedMapDebugInfo(ctx, result)
- #echo ""
- #echo ""
- proc checkAsgn(target: PNode, assigned: PNode; ctx, map): Check =
- ## check assignment
- ## update map based on `assigned`
- if assigned.kind != nkEmpty:
- result = check(assigned, ctx, map)
- else:
- result = Check(nilability: typeNilability(target.typ), map: map)
- # we need to visit and check those, but we don't use the result for now
- # is it possible to somehow have another event happen here?
- discard check(target, ctx, map)
- if result.map.isNil:
- result.map = map
- if target.kind in {nkSym, nkDotExpr} or isConstBracket(target):
- let t = ctx.index(target)
- move(ctx, map, target, assigned)
- case assigned.kind:
- of nkNilLit:
- result.map.store(ctx, t, Nil, TAssign, target.info, target)
- else:
- result.map.store(ctx, t, result.nilability, TAssign, target.info, target)
- moveOutDependants(ctx, map, target)
- storeDependants(ctx, map, target, MaybeNil)
- if assigned.kind in {nkObjConstr, nkTupleConstr}:
- for (element, value) in result.elements:
- var elementNode = nkDotExpr.newTree(nkHiddenDeref.newTree(target), element)
- if symbol(elementNode) in ctx.symbolIndices:
- var elementIndex = ctx.index(elementNode)
- result.map.store(ctx, elementIndex, value, TAssign, target.info, elementNode)
- proc checkReturn(n, ctx, map): Check =
- ## check return
- # return n same as result = n; return ?
- result = check(n[0], ctx, map)
- result.map.store(ctx, resultExprIndex, result.nilability, TAssign, n.info)
- proc checkIf(n, ctx, map): Check =
- ## check branches based on condition
- var mapIf: NilMap = map
- # first visit the condition
- # the structure is not If(Elif(Elif, Else), Else)
- # it is
- # If(Elif, Elif, Else)
- var mapCondition = checkCondition(n.sons[0].sons[0], ctx, mapIf, false, true)
- # the state of the conditions: negating conditions before the current one
- var layerHistory = newNilMap(mapIf)
- # the state after branch effects
- var afterLayer: NilMap = nil
- # the result nilability for expressions
- var nilability = Safe
- for branch in n.sons:
- var branchConditionLayer = newNilMap(layerHistory)
- var branchLayer: NilMap
- var code: PNode
- if branch.kind in {nkIfStmt, nkElifBranch}:
- var mapCondition = checkCondition(branch[0], ctx, branchConditionLayer, false, true)
- let reverseMapCondition = reverseDirect(mapCondition)
- layerHistory = ctx.add(layerHistory, reverseMapCondition)
- branchLayer = mapCondition
- code = branch[1]
- else:
- branchLayer = layerHistory
- code = branch
- let branchCheck = checkBranch(code, ctx, branchLayer)
- # handles nil afterLayer -> returns branchCheck.map
- afterLayer = ctx.union(afterLayer, branchCheck.map)
- nilability = if n.kind == nkIfStmt: Safe else: union(nilability, branchCheck.nilability)
- if n.sons.len > 1:
- result.map = afterLayer
- result.nilability = nilability
- else:
- if not hasUnstructuredControlFlowJump(n[0][1]):
- # here it matters what happend inside, because
- # we might continue in the parent branch after entering this one
- # either we enter the branch, so we get mapIf and effect of branch -> afterLayer
- # or we dont , so we get mapIf and (not condition) effect -> layerHistory
- result.map = ctx.union(layerHistory, afterLayer)
- result.nilability = Safe # no expr?
- else:
- # similar to else: because otherwise we are jumping out of
- # the branch, so no union with the mapIf (we dont continue if the condition was true)
- # here it also doesn't matter for the parent branch what happened in the branch, e.g. assigning to nil
- # as if we continue there, we haven't entered the branch probably
- # so we don't do an union with afterLayer
- # layerHistory has the effect of mapIf and (not condition)
- result.map = layerHistory
- result.nilability = Safe
- proc checkFor(n, ctx, map): Check =
- ## check for loops
- ## try to repeat the unification of the code twice
- ## to detect what can change after a several iterations
- ## approach based on discussions with Zahary/Araq
- ## similar approach used for other loops
- var m = map.copyMap()
- var map0 = map.copyMap()
- #echo namedMapDebugInfo(ctx, map)
- m = check(n.sons[2], ctx, map).map.copyMap()
- if n[0].kind == nkSym:
- m.store(ctx, ctx.index(n[0]), typeNilability(n[0].typ), TAssign, n[0].info)
- # echo namedMapDebugInfo(ctx, map)
- var check2 = check(n.sons[2], ctx, m)
- var map2 = check2.map
- result.map = ctx.union(map0, m)
- result.map = ctx.union(result.map, map2)
- result.nilability = Safe
- # check:
- # while code:
- # code2
- # if code:
- # code2
- # if code:
- # code2
- # if code:
- # code2
- # check(code), check(code2 in code's map)
- proc checkWhile(n, ctx, map): Check =
- ## check while loops
- ## try to repeat the unification of the code twice
- var m = checkCondition(n[0], ctx, map, false, false)
- var map0 = map.copyMap()
- m = check(n.sons[1], ctx, m).map
- var map1 = m.copyMap()
- var check2 = check(n.sons[1], ctx, m)
- var map2 = check2.map
- result.map = ctx.union(map0, map1)
- result.map = ctx.union(result.map, map2)
- result.nilability = Safe
- proc checkInfix(n, ctx, map): Check =
- ## check infix operators in condition
- ## a and b : map is based on a; next b
- ## a or b : map is an union of a and b's
- ## a == b : use checkCondition
- ## else: no change, just check args
- result = default(Check)
- if n[0].kind == nkSym:
- var mapL: NilMap = nil
- var mapR: NilMap = nil
- if n[0].sym.magic notin {mAnd, mEqRef}:
- mapL = checkCondition(n[1], ctx, map, false, false)
- mapR = checkCondition(n[2], ctx, map, false, false)
- case n[0].sym.magic:
- of mOr:
- result.map = ctx.union(mapL, mapR)
- of mAnd:
- result.map = checkCondition(n[1], ctx, map, false, false)
- result.map = checkCondition(n[2], ctx, result.map, false, false)
- of mEqRef:
- if n[2].kind == nkIntLit:
- if $n[2] == "true":
- result.map = checkCondition(n[1], ctx, map, false, false)
- elif $n[2] == "false":
- result.map = checkCondition(n[1], ctx, map, true, false)
- elif n[1].kind == nkIntLit:
- if $n[1] == "true":
- result.map = checkCondition(n[2], ctx, map, false, false)
- elif $n[1] == "false":
- result.map = checkCondition(n[2], ctx, map, true, false)
- if result.map.isNil:
- result.map = map
- else:
- result.map = map
- else:
- result.map = map
- result.nilability = Safe
- proc checkIsNil(n, ctx, map; isElse: bool = false): Check =
- ## check isNil calls
- ## update the map depending on if it is not isNil or isNil
- result.map = newNilMap(map)
- let value = n[1]
- result.map.store(ctx, ctx.index(n[1]), if not isElse: Nil else: Safe, TArg, n.info, n)
- proc infix(ctx: NilCheckerContext, l: PNode, r: PNode, magic: TMagic): PNode =
- var name = case magic:
- of mEqRef: "=="
- of mAnd: "and"
- of mOr: "or"
- else: ""
- var cache = newIdentCache()
- var op = newSym(skVar, cache.getIdent(name), ctx.idgen, nil, r.info)
- op.magic = magic
- result = nkInfix.newTree(
- newSymNode(op, r.info),
- l,
- r)
- result.typ = newType(tyBool, nextTypeId ctx.idgen, nil)
- proc prefixNot(ctx: NilCheckerContext, node: PNode): PNode =
- var cache = newIdentCache()
- var op = newSym(skVar, cache.getIdent("not"), ctx.idgen, nil, node.info)
- op.magic = mNot
- result = nkPrefix.newTree(
- newSymNode(op, node.info),
- node)
- result.typ = newType(tyBool, nextTypeId ctx.idgen, nil)
- proc infixEq(ctx: NilCheckerContext, l: PNode, r: PNode): PNode =
- infix(ctx, l, r, mEqRef)
- proc infixOr(ctx: NilCheckerContext, l: PNode, r: PNode): PNode =
- infix(ctx, l, r, mOr)
- proc checkCase(n, ctx, map): Check =
- # case a:
- # of b: c
- # of b2: c2
- # is like
- # if a == b:
- # c
- # elif a == b2:
- # c2
- # also a == true is a , a == false is not a
- let base = n[0]
- result.map = map.copyMap()
- result.nilability = Safe
- var a: PNode = nil
- for child in n:
- case child.kind:
- of nkOfBranch:
- if child.len < 2:
- # echo "case with of with < 2 ", n
- continue # TODO why does this happen
- let branchBase = child[0] # TODO a, b or a, b..c etc
- let code = child[^1]
- let test = infixEq(ctx, base, branchBase)
- if a.isNil:
- a = test
- else:
- a = infixOr(ctx, a, test)
- let conditionMap = checkCondition(test, ctx, map.copyMap(), false, false)
- let newCheck = checkBranch(code, ctx, conditionMap)
- result.map = ctx.union(result.map, newCheck.map)
- result.nilability = union(result.nilability, newCheck.nilability)
- of nkElifBranch:
- discard "TODO: maybe adapt to be similar to checkIf"
- of nkElse:
- let mapElse = checkCondition(prefixNot(ctx, a), ctx, map.copyMap(), false, false)
- let newCheck = checkBranch(child[0], ctx, mapElse)
- result.map = ctx.union(result.map, newCheck.map)
- result.nilability = union(result.nilability, newCheck.nilability)
- else:
- discard
- # notes
- # try:
- # a
- # b
- # except:
- # c
- # finally:
- # d
- #
- # if a doesnt raise, this is not an exit point:
- # so find what raises and update the map with that
- # (a, b); c; d
- # if nothing raises, except shouldn't happen
- # .. might be a false positive tho, if canRaise is not conservative?
- # so don't visit it
- #
- # nested nodes can raise as well: I hope nim returns canRaise for
- # their parents
- #
- # a lot of stuff can raise
- proc checkTry(n, ctx, map): Check =
- var newMap = map.copyMap()
- var currentMap = map
- # we don't analyze except if nothing canRaise in try
- var canRaise = false
- var hasFinally = false
- # var tryNodes: seq[PNode]
- # if n[0].kind == nkStmtList:
- # tryNodes = toSeq(n[0])
- # else:
- # tryNodes = @[n[0]]
- # for i, child in tryNodes:
- # let (childNilability, childMap) = check(child, conf, currentMap)
- # echo childMap
- # currentMap = childMap
- # # TODO what about nested
- # if child.canRaise:
- # newMap = union(newMap, childMap)
- # canRaise = true
- # else:
- # newMap = childMap
- let tryCheck = check(n[0], ctx, currentMap)
- newMap = ctx.union(currentMap, tryCheck.map)
- canRaise = n[0].canRaise
- var afterTryMap = newMap
- for a, branch in n:
- if a > 0:
- case branch.kind:
- of nkFinally:
- newMap = ctx.union(afterTryMap, newMap)
- let childCheck = check(branch[0], ctx, newMap)
- newMap = ctx.union(newMap, childCheck.map)
- hasFinally = true
- of nkExceptBranch:
- if canRaise:
- let childCheck = check(branch[^1], ctx, newMap)
- newMap = ctx.union(newMap, childCheck.map)
- else:
- discard
- if not hasFinally:
- # we might have not hit the except branches
- newMap = ctx.union(afterTryMap, newMap)
- result = Check(nilability: Safe, map: newMap)
- proc hasUnstructuredControlFlowJump(n: PNode): bool =
- ## if the node contains a direct stop
- ## as a continue/break/raise/return: then it means
- ## we should reverse some of the map in the code after the condition
- ## similar to else
- # echo "n ", n, " ", n.kind
- case n.kind:
- of nkStmtList:
- for child in n:
- if hasUnstructuredControlFlowJump(child):
- return true
- of nkReturnStmt, nkBreakStmt, nkContinueStmt, nkRaiseStmt:
- return true
- of nkIfStmt, nkIfExpr, nkElifExpr, nkElse:
- return false
- else:
- discard
- return false
- proc reverse(value: Nilability): Nilability =
- case value:
- of Nil: Safe
- of MaybeNil: MaybeNil
- of Safe: Nil
- of Parent: Parent
- of Unreachable: Unreachable
- proc reverse(kind: TransitionKind): TransitionKind =
- case kind:
- of TNil: TSafe
- of TSafe: TNil
- of TPotentialAlias: TPotentialAlias
- else:
- kind
- # raise newException(ValueError, "expected TNil or TSafe")
- proc reverseDirect(map: NilMap): NilMap =
- # we create a new layer
- # reverse the values only in this layer:
- # because conditions should've stored their changes there
- # b: Safe (not b.isNil)
- # b: Parent Parent
- # b: Nil (b.isNil)
- # layer block
- # [ Parent ] [ Parent ]
- # if -> if state
- # layer -> reverse
- # older older0 new
- # older new
- # [ b Nil ] [ Parent ]
- # elif
- # [ b Nil, c Nil] [ Parent ]
- #
- # if b.isNil:
- # # [ b Safe]
- # c = A() # Safe
- # elif not b.isNil:
- # # [ b Safe ] + [b Nil] MaybeNil Unreachable
- # # Unreachable defer can't deref b, it is unreachable
- # discard
- # else:
- # b
- # if
- # if: we just pass the map with a new layer for its block
- # elif: we just pass the original map but with a new layer is the reverse of the previous popped layer (?)
- # elif:
- # else: we just pass the original map but with a new layer which is initialized as the reverse of the
- # top layer of else
- # else:
- #
- # [ b MaybeNil ] [b Parent] [b Parent] [b Safe] [b Nil] []
- # Safe
- # c == 1
- # b Parent
- # c == 2
- # b Parent
- # not b.isNil
- # b Safe
- # c == 3
- # b Nil
- # (else)
- # b Nil
- result = map.copyMap()
- for index, value in result.expressions:
- result.expressions[index] = reverse(value)
- if result.history[index].len > 0:
- result.history[index][^1].kind = reverse(result.history[index][^1].kind)
- result.history[index][^1].nilability = result.expressions[index]
- proc checkCondition(n, ctx, map; reverse: bool, base: bool): NilMap =
- ## check conditions : used for if, some infix operators
- ## isNil(a)
- ## it returns a new map: you need to reverse all the direct elements for else
- # echo "condition ", n, " ", n.kind
- if n.kind == nkCall:
- result = newNilMap(map)
- for element in n:
- if element.kind == nkHiddenDeref and n[0].kind == nkSym and n[0].sym.magic == mIsNil:
- result = check(element[0], ctx, result).map
- else:
- result = check(element, ctx, result).map
- if n[0].kind == nkSym and n[0].sym.magic == mIsNil:
- # isNil(arg)
- var arg = n[1]
- while arg.kind == nkHiddenDeref:
- arg = arg[0]
- if arg.kind in {nkSym, nkDotExpr} or isConstBracket(arg):
- let a = ctx.index(arg)
- result.store(ctx, a, if not reverse: Nil else: Safe, if not reverse: TNil else: TSafe, n.info, arg)
- else:
- discard
- else:
- discard
- elif n.kind == nkPrefix and n[0].kind == nkSym and n[0].sym.magic == mNot:
- result = checkCondition(n[1], ctx, map, not reverse, false)
- elif n.kind == nkInfix:
- result = newNilMap(map)
- result = checkInfix(n, ctx, result).map
- else:
- result = check(n, ctx, map).map
- result = newNilMap(map)
- assert not result.isNil
- assert not result.parent.isNil
- proc checkResult(n, ctx, map) =
- let resultNilability = map[resultExprIndex]
- case resultNilability:
- of Nil:
- message(ctx.config, n.info, warnStrictNotNil, "return value is nil")
- of MaybeNil:
- message(ctx.config, n.info, warnStrictNotNil, "return value might be nil")
- of Unreachable:
- message(ctx.config, n.info, warnStrictNotNil, "return value is unreachable")
- of Safe, Parent:
- discard
- proc checkBranch(n: PNode, ctx: NilCheckerContext, map: NilMap): Check =
- result = check(n, ctx, map)
- # Faith!
- proc check(n: PNode, ctx: NilCheckerContext, map: NilMap): Check =
- assert not map.isNil
- # echo "check n ", n, " ", n.kind
- # echo "map ", namedMapDebugInfo(ctx, map)
- case n.kind:
- of nkSym:
- result = Check(nilability: map[ctx.index(n)], map: map)
- of nkCallKinds:
- if n.sons[0].kind == nkSym:
- let callSym = n.sons[0].sym
- case callSym.magic:
- of mAnd, mOr:
- result = checkInfix(n, ctx, map)
- of mIsNil:
- result = checkIsNil(n, ctx, map)
- else:
- result = checkCall(n, ctx, map)
- else:
- result = checkCall(n, ctx, map)
- of nkHiddenStdConv, nkHiddenSubConv, nkConv, nkExprColonExpr, nkExprEqExpr,
- nkCast:
- result = check(n.sons[1], ctx, map)
- of nkStmtList, nkStmtListExpr, nkChckRangeF, nkChckRange64, nkChckRange,
- nkBracket, nkCurly, nkPar, nkTupleConstr, nkClosure, nkObjConstr, nkElse:
- result.map = map
- if n.kind in {nkObjConstr, nkTupleConstr}:
- # TODO deeper nested elements?
- # A(field: B()) #
- # field: Safe ->
- var elements: seq[(PNode, Nilability)] = @[]
- for i, child in n:
- result = check(child, ctx, result.map)
- if i > 0:
- if child.kind == nkExprColonExpr:
- elements.add((child[0], result.nilability))
- result.elements = elements
- result.nilability = Safe
- else:
- for child in n:
- result = check(child, ctx, result.map)
- of nkDotExpr:
- result = checkDotExpr(n, ctx, map)
- of nkDerefExpr, nkHiddenDeref:
- result = checkDeref(n, ctx, map)
- of nkAddr, nkHiddenAddr:
- result = check(n.sons[0], ctx, map)
- of nkIfStmt, nkIfExpr:
- result = checkIf(n, ctx, map)
- of nkAsgn, nkFastAsgn, nkSinkAsgn:
- result = checkAsgn(n[0], n[1], ctx, map)
- of nkVarSection:
- result.map = map
- for child in n:
- result = checkAsgn(child[0], child[2], ctx, result.map)
- of nkForStmt:
- result = checkFor(n, ctx, map)
- of nkCaseStmt:
- result = checkCase(n, ctx, map)
- of nkReturnStmt:
- result = checkReturn(n, ctx, map)
- of nkBracketExpr:
- result = checkBracketExpr(n, ctx, map)
- of nkTryStmt:
- result = checkTry(n, ctx, map)
- of nkWhileStmt:
- result = checkWhile(n, ctx, map)
- of nkNone..pred(nkSym), succ(nkSym)..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef,
- nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo,
- nkFuncDef, nkConstSection, nkConstDef, nkIncludeStmt, nkImportStmt,
- nkExportStmt, nkPragma, nkCommentStmt, nkBreakState,
- nkTypeOfExpr, nkMixinStmt, nkBindStmt:
- discard "don't follow this : same as varpartitions"
- result = Check(nilability: Nil, map: map)
- else:
- var elementMap = map.copyMap()
- var elementCheck: Check
- elementCheck.map = elementMap
- for element in n:
- elementCheck = check(element, ctx, elementCheck.map)
- result = Check(nilability: Nil, map: elementCheck.map)
- proc typeNilability(typ: PType): Nilability =
- assert not typ.isNil
- # echo "typeNilability ", $typ.flags, " ", $typ.kind
- result = if tfNotNil in typ.flags:
- Safe
- elif typ.kind in {tyRef, tyCstring, tyPtr, tyPointer}:
- #
- # tyVar ? tyVarargs ? tySink ? tyLent ?
- # TODO spec? tests?
- MaybeNil
- else:
- Safe
- # echo " result ", result
- proc preVisitNode(ctx: NilCheckerContext, node: PNode, conf: ConfigRef) =
- # echo "visit node ", node
- if node.kind in {nkSym, nkDotExpr} or isConstBracket(node):
- let nodeSymbol = symbol(node)
- if not ctx.symbolIndices.hasKey(nodeSymbol):
- ctx.symbolIndices[nodeSymbol] = ctx.expressions.len
- ctx.expressions.add(node)
- if node.kind in {nkDotExpr, nkBracketExpr}:
- if node.kind == nkDotExpr and (not node.typ.isNil and node.typ.kind == tyRef and tfNotNil notin node.typ.flags) or
- node.kind == nkBracketExpr:
- let index = ctx.symbolIndices[nodeSymbol]
- var baseIndex = noExprIndex
- # deref usually?
- # ok, we hit another case
- var base = if node[0].kind notin {nkSym, nkIdent}: node[0][0] else: node[0]
- if base.kind != nkIdent:
- let baseSymbol = symbol(base)
- if not ctx.symbolIndices.hasKey(baseSymbol):
- baseIndex = ctx.expressions.len # next visit should add it
- else:
- baseIndex = ctx.symbolIndices[baseSymbol]
- if ctx.dependants.len <= baseIndex:
- ctx.dependants.setLen(baseIndex + 1.ExprIndex)
- ctx.dependants[baseIndex].incl(index.int)
- case node.kind:
- of nkSym, nkEmpty, nkNilLit, nkType, nkIdent, nkCharLit .. nkUInt64Lit, nkFloatLit .. nkFloat64Lit, nkStrLit .. nkTripleStrLit:
- discard
- of nkDotExpr:
- # visit only the base
- ctx.preVisitNode(node[0], conf)
- else:
- for element in node:
- ctx.preVisitNode(element, conf)
- proc preVisit(ctx: NilCheckerContext, s: PSym, body: PNode, conf: ConfigRef) =
- ctx.symbolIndices = {resultId: resultExprIndex}.toTable()
- var cache = newIdentCache()
- ctx.expressions = SeqOfDistinct[ExprIndex, PNode](@[newIdentNode(cache.getIdent("result"), s.ast.info)])
- var emptySet: IntSet = initIntSet() # set[ExprIndex]
- ctx.dependants = SeqOfDistinct[ExprIndex, IntSet](@[emptySet])
- for i, arg in s.typ.n.sons:
- if i > 0:
- if arg.kind != nkSym:
- continue
- let argSymbol = symbol(arg)
- if not ctx.symbolIndices.hasKey(argSymbol):
- ctx.symbolIndices[argSymbol] = ctx.expressions.len
- ctx.expressions.add(arg)
- ctx.preVisitNode(body, conf)
- if ctx.dependants.len < ctx.expressions.len:
- ctx.dependants.setLen(ctx.expressions.len)
- # echo ctx.symbolIndices
- # echo ctx.expressions
- # echo ctx.dependants
- proc checkNil*(s: PSym; body: PNode; conf: ConfigRef, idgen: IdGenerator) =
- let line = s.ast.info.line
- let fileIndex = s.ast.info.fileIndex.int
- var filename = conf.m.fileInfos[fileIndex].fullPath.string
- var context = NilCheckerContext(config: conf, idgen: idgen)
- context.preVisit(s, body, conf)
- var map = newNilMap(nil, context.symbolIndices.len)
- for i, child in s.typ.n.sons:
- if i > 0:
- if child.kind != nkSym:
- continue
- map.store(context, context.index(child), typeNilability(child.typ), TArg, child.info, child)
- map.store(context, resultExprIndex, if not s.typ[0].isNil and s.typ[0].kind == tyRef: Nil else: Safe, TResult, s.ast.info)
- # echo "checking ", s.name.s, " ", filename
- let res = check(body, context, map)
- var canCheck = resultExprIndex in res.map.history.low .. res.map.history.high
- if res.nilability == Safe and canCheck and res.map.history[resultExprIndex].len <= 1:
- res.map.store(context, resultExprIndex, Safe, TAssign, s.ast.info)
- else:
- if res.nilability == Safe:
- res.map.store(context, resultExprIndex, Safe, TAssign, s.ast.info)
- # TODO check for nilability result
- # (ANotNil, BNotNil) :
- # do we check on asgn nilability at all?
- if not s.typ[0].isNil and s.typ[0].kind == tyRef and tfNotNil in s.typ[0].flags:
- checkResult(s.ast, context, res.map)
|