mirror of
https://github.com/nim-lang/Nim.git
synced 2026-01-08 05:53:22 +00:00
```nim
{.experimental: "strictdefs".}
type Test = object
id: int
proc test(): Test =
if true:
return Test()
else:
return
echo test()
```
I will tackle https://github.com/nim-lang/Nim/issues/16735 and #21615 in
the following PR.
The old code just premises that in branches ended with returns, raise
statements etc. , all variables including the result variable are
initialized for that branch. It's true for noreturn statements. But it
is false for the result variable in a branch tailing with a return
statement, in which the result variable is not initialized. The solution
is not perfect for usages below branch statements with the result
variable uninitialized, but it should suffice for now, which gives a
proper warning.
It also fixes
```nim
{.experimental: "strictdefs".}
type Test = object
id: int
proc foo {.noreturn.} = discard
proc test9(x: bool): Test =
if x:
foo()
else:
foo()
```
which gives a warning, but shouldn't
1648 lines
61 KiB
Nim
1648 lines
61 KiB
Nim
#
|
|
#
|
|
# 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, semdata,
|
|
modulegraphs, varpartitions, typeallowed, nilcheck, errorhandling, tables,
|
|
semstrictfuncs
|
|
|
|
when defined(nimPreviewSlimSystem):
|
|
import std/assertions
|
|
|
|
when defined(useDfa):
|
|
import dfa
|
|
|
|
import liftdestructors
|
|
include sinkparameter_inference
|
|
|
|
#[ Second semantic checking pass over the AST. Necessary because the old
|
|
way had some inherent problems. Performs:
|
|
|
|
* effect+exception tracking
|
|
* "usage before definition" checking
|
|
* also now calls the "lift destructor logic" at strategic positions, this
|
|
is about to be put into the spec:
|
|
|
|
We treat assignment and sinks and destruction as identical.
|
|
|
|
In the construct let/var x = expr() x's type is marked.
|
|
|
|
In x = y the type of x is marked.
|
|
|
|
For every sink parameter of type T T is marked.
|
|
|
|
For every call f() the return type of f() is marked.
|
|
|
|
]#
|
|
|
|
# ------------------------ 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
|
|
forbids: PNode # list of tags
|
|
bottom, inTryStmt, inExceptOrFinallyStmt, leftPartOfAsgn, inIfStmt, currentBlock: int
|
|
owner: PSym
|
|
ownerModule: PSym
|
|
init: seq[int] # list of initialized variables
|
|
scopes: Table[int, int] # maps var-id to its scope (see also `currentBlock`).
|
|
guards: TModel # nested guards
|
|
locked: seq[PNode] # locked locations
|
|
gcUnsafe, isRecursive, isTopLevel, hasSideEffect, inEnforcedGcSafe: bool
|
|
isInnerProc: bool
|
|
inEnforcedNoSideEffects: bool
|
|
currOptions: TOptions
|
|
config: ConfigRef
|
|
graph: ModuleGraph
|
|
c: PContext
|
|
escapingParams: IntSet
|
|
PEffects = var TEffects
|
|
|
|
const
|
|
errXCannotBeAssignedTo = "'$1' cannot be assigned to"
|
|
errLetNeedsInit = "'let' symbol requires an initialization"
|
|
|
|
proc createTypeBoundOps(tracked: PEffects, typ: PType; info: TLineInfo) =
|
|
if typ == nil or sfGeneratedOp in tracked.owner.flags:
|
|
# don't create type bound ops for anything in a function with a `nodestroy` pragma
|
|
# bug #21987
|
|
return
|
|
when false:
|
|
let realType = typ.skipTypes(abstractInst)
|
|
if realType.kind == tyRef and
|
|
optSeqDestructors in tracked.config.globalOptions:
|
|
createTypeBoundOps(tracked.graph, tracked.c, realType.lastSon, info)
|
|
|
|
createTypeBoundOps(tracked.graph, tracked.c, typ, info, tracked.c.idgen)
|
|
if (tfHasAsgn in typ.flags) or
|
|
optSeqDestructors in tracked.config.globalOptions:
|
|
tracked.owner.flags.incl sfInjectDestructors
|
|
|
|
proc isLocalSym(a: PEffects, s: PSym): bool =
|
|
s.typ != nil and (s.kind in {skLet, skVar, skResult} or (s.kind == skParam and isOutParam(s.typ))) and
|
|
sfGlobal notin s.flags and s.owner == a.owner
|
|
|
|
proc lockLocations(a: PEffects; pragma: PNode) =
|
|
if pragma.kind != nkExprColonExpr:
|
|
localError(a.config, pragma.info, "locks pragma without argument")
|
|
return
|
|
for x in pragma[1]:
|
|
a.locked.add x
|
|
|
|
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(a.config, 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[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[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[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[0] = n[0]
|
|
dot[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.} =
|
|
if a.inTryStmt > 0 and a.config.exc == excSetjmp:
|
|
incl(s.flags, sfVolatile)
|
|
|
|
proc varDecl(a: PEffects; n: PNode) {.inline.} =
|
|
if n.kind == nkSym:
|
|
a.scopes[n.sym.id] = a.currentBlock
|
|
|
|
proc skipHiddenDeref(n: PNode): PNode {.inline.} =
|
|
result = if n.kind == nkHiddenDeref: n[0] else: n
|
|
|
|
proc initVar(a: PEffects, n: PNode; volatileCheck: bool) =
|
|
let n = skipHiddenDeref(n)
|
|
if n.kind != nkSym: return
|
|
let s = n.sym
|
|
if isLocalSym(a, s):
|
|
if volatileCheck: makeVolatile(a, s)
|
|
for x in a.init:
|
|
if x == s.id:
|
|
if strictDefs in a.c.features and s.kind == skLet:
|
|
localError(a.config, n.info, errXCannotBeAssignedTo %
|
|
renderTree(n, {renderNoComments}
|
|
))
|
|
return
|
|
a.init.add s.id
|
|
if a.scopes.getOrDefault(s.id) == a.currentBlock:
|
|
#[ Consider this case:
|
|
|
|
var x: T
|
|
while true:
|
|
if cond:
|
|
x = T() #1
|
|
else:
|
|
x = T() #2
|
|
use x
|
|
|
|
Even though both #1 and #2 are first writes we must use the `=copy`
|
|
here so that the old value is destroyed because `x`'s destructor is
|
|
run outside of the while loop. This is why we need the check here that
|
|
the assignment is done in the same logical block as `x` was declared in.
|
|
]#
|
|
n.flags.incl nfFirstWrite
|
|
|
|
proc initVarViaNew(a: PEffects, n: PNode) =
|
|
let n = skipHiddenDeref(n)
|
|
if n.kind != nkSym: return
|
|
let s = n.sym
|
|
if {tfRequiresInit, 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 isLocalSym(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.c.idgen,
|
|
a.owner, reason.info, {})
|
|
|
|
proc markSideEffect(a: PEffects; reason: PNode | PSym; useLoc: TLineInfo) =
|
|
if not a.inEnforcedNoSideEffects:
|
|
a.hasSideEffect = true
|
|
if a.owner.kind in routineKinds:
|
|
var sym: PSym
|
|
when reason is PNode:
|
|
if reason.kind == nkSym:
|
|
sym = reason.sym
|
|
else:
|
|
let kind = if reason.kind == nkHiddenDeref: skParam else: skUnknown
|
|
sym = newSym(kind, a.owner.name, a.c.idgen, a.owner, reason.info, {})
|
|
else:
|
|
sym = reason
|
|
a.c.sideEffects.mgetOrPut(a.owner.id, @[]).add (useLoc, sym)
|
|
when false: 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:
|
|
if u.typ.skipTypes(abstractInst).kind == tyProc:
|
|
message(conf, s.info, msgKind,
|
|
"'$#' is not GC-safe as it calls '$#'" %
|
|
[s.name.s, u.name.s])
|
|
else:
|
|
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:
|
|
if u.kind == skMethod and {sfBase, sfThread} * u.flags == {sfBase}:
|
|
message(conf, u.info, msgKind,
|
|
"Base method '$#' requires explicit '{.gcsafe.}' to be GC-safe" %
|
|
[u.name.s])
|
|
else:
|
|
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 listSideEffects(result: var string; s: PSym; cycleCheck: var IntSet;
|
|
conf: ConfigRef; context: PContext; indentLevel: int) =
|
|
template addHint(msg; lineInfo; sym; level = indentLevel) =
|
|
result.addf("$# $# Hint: '$#' $#\n", repeat(">", level), conf $ lineInfo, sym, msg)
|
|
if context.sideEffects.hasKey(s.id):
|
|
for (useLineInfo, u) in context.sideEffects[s.id]:
|
|
if u != nil and not cycleCheck.containsOrIncl(u.id):
|
|
case u.kind
|
|
of skLet, skVar:
|
|
addHint("accesses global state '$#'" % u.name.s, useLineInfo, s.name.s)
|
|
addHint("accessed by '$#'" % s.name.s, u.info, u.name.s, indentLevel + 1)
|
|
of routineKinds:
|
|
addHint("calls `.sideEffect` '$#'" % u.name.s, useLineInfo, s.name.s)
|
|
addHint("called by '$#'" % s.name.s, u.info, u.name.s, indentLevel + 1)
|
|
listSideEffects(result, u, cycleCheck, conf, context, indentLevel + 2)
|
|
of skParam, skForVar:
|
|
addHint("calls routine via hidden pointer indirection", useLineInfo, s.name.s)
|
|
else:
|
|
addHint("calls routine via pointer indirection", useLineInfo, s.name.s)
|
|
|
|
proc listSideEffects(result: var string; s: PSym; conf: ConfigRef; context: PContext) =
|
|
var cycleCheck = initIntSet()
|
|
result.addf("'$#' can have side effects\n", s.name.s)
|
|
listSideEffects(result, s, cycleCheck, conf, context, 1)
|
|
|
|
proc useVarNoInitCheck(a: PEffects; n: PNode; s: PSym) =
|
|
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 a.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n)
|
|
markGcUnsafe(a, s)
|
|
markSideEffect(a, s, n.info)
|
|
if s.owner != a.owner and s.kind in {skVar, skLet, skForVar, skResult, skParam} and
|
|
{sfGlobal, sfThread} * s.flags == {}:
|
|
a.isInnerProc = true
|
|
|
|
proc useVar(a: PEffects, n: PNode) =
|
|
let s = n.sym
|
|
if a.inExceptOrFinallyStmt > 0:
|
|
incl s.flags, sfUsedInFinallyOrExcept
|
|
if isLocalSym(a, s):
|
|
if sfNoInit in s.flags:
|
|
# If the variable is explicitly marked as .noinit. do not emit any error
|
|
a.init.add s.id
|
|
elif s.id notin a.init:
|
|
if s.typ.requiresInit:
|
|
message(a.config, n.info, warnProveInit, s.name.s)
|
|
elif a.leftPartOfAsgn <= 0:
|
|
if strictDefs in a.c.features:
|
|
if s.kind == skLet:
|
|
localError(a.config, n.info, errLetNeedsInit)
|
|
else:
|
|
message(a.config, n.info, warnUninit, s.name.s)
|
|
# prevent superfluous warnings about the same variable:
|
|
a.init.add s.id
|
|
useVarNoInitCheck(a, n, s)
|
|
|
|
type
|
|
BreakState = enum
|
|
bsNone
|
|
bsBreakOrReturn
|
|
bsNoReturn
|
|
|
|
type
|
|
TIntersection = seq[tuple[id, count: int]] # a simple count table
|
|
|
|
proc addToIntersection(inter: var TIntersection, s: int, state: BreakState) =
|
|
for j in 0..<inter.len:
|
|
if s == inter[j].id:
|
|
if state == bsNone:
|
|
inc inter[j].count
|
|
return
|
|
if state == bsNone:
|
|
inter.add((id: s, count: 1))
|
|
else:
|
|
inter.add((id: s, count: 0))
|
|
|
|
proc throws(tracked, n, orig: PNode) =
|
|
if n.typ == nil or n.typ.kind != tyError:
|
|
if orig != nil:
|
|
let x = copyTree(orig)
|
|
x.typ = n.typ
|
|
tracked.add x
|
|
else:
|
|
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 addRaiseEffect(a: PEffects, e, comesFrom: PNode) =
|
|
#assert e.kind != nkRaiseStmt
|
|
var aa = a.exc
|
|
for i in a.bottom..<aa.len:
|
|
# we only track the first node that can have the effect E in order
|
|
# to safe space and time.
|
|
if sameType(a.graph.excType(aa[i]), a.graph.excType(e)): return
|
|
|
|
if e.typ != nil:
|
|
if not isDefectException(e.typ):
|
|
throws(a.exc, e, comesFrom)
|
|
|
|
proc addTag(a: PEffects, e, comesFrom: PNode) =
|
|
var aa = a.tags
|
|
for i in 0..<aa.len:
|
|
# we only track the first node that can have the effect E in order
|
|
# to safe space and time.
|
|
if sameType(aa[i].typ.skipTypes(skipPtrs), e.typ.skipTypes(skipPtrs)): return
|
|
throws(a.tags, e, comesFrom)
|
|
|
|
proc addNotTag(a: PEffects, e, comesFrom: PNode) =
|
|
var aa = a.forbids
|
|
for i in 0..<aa.len:
|
|
if sameType(aa[i].typ.skipTypes(skipPtrs), e.typ.skipTypes(skipPtrs)): return
|
|
throws(a.forbids, e, comesFrom)
|
|
|
|
proc mergeRaises(a: PEffects, b, comesFrom: PNode) =
|
|
if b.isNil:
|
|
addRaiseEffect(a, createRaise(a.graph, comesFrom), comesFrom)
|
|
else:
|
|
for effect in items(b): addRaiseEffect(a, effect, comesFrom)
|
|
|
|
proc mergeTags(a: PEffects, b, comesFrom: PNode) =
|
|
if b.isNil:
|
|
addTag(a, createTag(a.graph, comesFrom), comesFrom)
|
|
else:
|
|
for effect in items(b): addTag(a, effect, comesFrom)
|
|
|
|
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))
|
|
for e in items(a.forbids): message(a.config, e.info, hintUser, typeToString(e.typ))
|
|
|
|
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[i] = tracked.exc[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[0])
|
|
dec tracked.inTryStmt
|
|
for i in oldState..<tracked.init.len:
|
|
addToIntersection(inter, tracked.init[i], bsNone)
|
|
|
|
var branches = 1
|
|
var hasFinally = false
|
|
inc tracked.inExceptOrFinallyStmt
|
|
|
|
# Collect the exceptions caught by the except branches
|
|
for i in 1..<n.len:
|
|
let b = n[i]
|
|
if b.kind == nkExceptBranch:
|
|
inc branches
|
|
if b.len == 1:
|
|
catchesAll(tracked)
|
|
else:
|
|
for j in 0..<b.len - 1:
|
|
if b[j].isInfixAs():
|
|
assert(b[j][1].kind == nkType)
|
|
catches(tracked, b[j][1].typ)
|
|
createTypeBoundOps(tracked, b[j][2].typ, b[j][2].info)
|
|
else:
|
|
assert(b[j].kind == nkType)
|
|
catches(tracked, b[j].typ)
|
|
else:
|
|
assert b.kind == nkFinally
|
|
# Add any other exception raised in the except bodies
|
|
for i in 1..<n.len:
|
|
let b = n[i]
|
|
if b.kind == nkExceptBranch:
|
|
setLen(tracked.init, oldState)
|
|
for j in 0..<b.len - 1:
|
|
if b[j].isInfixAs(): # skips initialization checks
|
|
assert(b[j][2].kind == nkSym)
|
|
tracked.init.add b[j][2].sym.id
|
|
track(tracked, b[^1])
|
|
for i in oldState..<tracked.init.len:
|
|
addToIntersection(inter, tracked.init[i], bsNone)
|
|
else:
|
|
setLen(tracked.init, oldState)
|
|
track(tracked, b[^1])
|
|
hasFinally = true
|
|
|
|
tracked.bottom = oldBottom
|
|
dec tracked.inExceptOrFinallyStmt
|
|
if not hasFinally:
|
|
setLen(tracked.init, oldState)
|
|
for id, count in items(inter):
|
|
if count == branches: tracked.init.add id
|
|
|
|
proc isIndirectCall(tracked: PEffects; n: PNode): 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:
|
|
if laxEffects notin tracked.c.config.legacyFeatures:
|
|
if tracked.owner == n.sym.owner and sfEffectsDelayed in n.sym.flags:
|
|
result = false # it is not a harmful call
|
|
else:
|
|
result = true
|
|
else:
|
|
result = tracked.owner != n.sym.owner or tracked.owner == nil
|
|
elif n.sym.kind notin routineKinds:
|
|
result = true
|
|
else:
|
|
result = false
|
|
|
|
proc isForwardedProc(n: PNode): bool =
|
|
result = n.kind == nkSym and sfForward in n.sym.flags
|
|
|
|
proc trackPragmaStmt(tracked: PEffects, n: PNode) =
|
|
for i in 0..<n.len:
|
|
var it = n[i]
|
|
let pragma = whichPragma(it)
|
|
if pragma == wEffects:
|
|
# list the computed effects up to here:
|
|
listEffects(tracked)
|
|
|
|
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 propagateEffects(tracked: PEffects, n: PNode, s: PSym) =
|
|
let pragma = s.ast[pragmasPos]
|
|
let spec = effectSpec(pragma, wRaises)
|
|
mergeRaises(tracked, spec, n)
|
|
|
|
let tagSpec = effectSpec(pragma, wTags)
|
|
mergeTags(tracked, tagSpec, n)
|
|
|
|
if notGcSafe(s.typ) and sfImportc notin s.flags:
|
|
if tracked.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n, tracked.config)
|
|
markGcUnsafe(tracked, s)
|
|
if tfNoSideEffect notin s.typ.flags:
|
|
markSideEffect(tracked, s, n.info)
|
|
|
|
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' is a built-in and cannot be used as " &
|
|
"a first-class procedure") % n.sym.name.s)
|
|
|
|
proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) =
|
|
let n = n.skipConv
|
|
if paramType.isNil or paramType.kind != tyTypeDesc:
|
|
procVarCheck skipConvCastAndClosure(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:
|
|
let ntyp = n.typ.skipTypesOrNil({tyVar, tyLent, tySink})
|
|
if ntyp != nil and tfNotNil notin ntyp.flags:
|
|
if isAddrNode(n):
|
|
# 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) or
|
|
n.typ.kind == tyTypeDesc:
|
|
# '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) =
|
|
addRaiseEffect(tracked, createRaise(tracked.graph, n), nil)
|
|
addTag(tracked, createTag(tracked.graph, n), nil)
|
|
|
|
proc isOwnedProcVar(tracked: PEffects; n: PNode): bool =
|
|
# XXX prove the soundness of this effect system rule
|
|
result = n.kind == nkSym and n.sym.kind == skParam and
|
|
tracked.owner == n.sym.owner
|
|
#if result and sfPolymorphic notin n.sym.flags:
|
|
# echo tracked.config $ n.info, " different here!"
|
|
if laxEffects notin tracked.c.config.legacyFeatures:
|
|
result = result and sfEffectsDelayed in n.sym.flags
|
|
|
|
proc isNoEffectList(n: PNode): bool {.inline.} =
|
|
assert n.kind == nkEffectList
|
|
n.len == 0 or (n[tagEffects] == nil and n[exceptionEffects] == nil and n[forbiddenEffects] == nil)
|
|
|
|
proc isTrival(caller: PNode): bool {.inline.} =
|
|
result = caller.kind == nkSym and caller.sym.magic in {mEqProc, mIsNil, mMove, mWasMoved, mSwap}
|
|
|
|
proc trackOperandForIndirectCall(tracked: PEffects, n: PNode, formals: PType; argIndex: int; caller: PNode) =
|
|
let a = skipConvCastAndClosure(n)
|
|
let op = a.typ
|
|
let param = if formals != nil and argIndex < formals.len and formals.n != nil: formals.n[argIndex].sym else: nil
|
|
# assume indirect calls are taken here:
|
|
if op != nil and op.kind == tyProc and n.skipConv.kind != nkNilLit and
|
|
not isTrival(caller) and
|
|
((param != nil and sfEffectsDelayed in param.flags) or laxEffects in tracked.c.config.legacyFeatures):
|
|
|
|
internalAssert tracked.config, op.n[0].kind == nkEffectList
|
|
var effectList = op.n[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(tracked, a):
|
|
# 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(tracked, a):
|
|
if tracked.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n, tracked.config)
|
|
markGcUnsafe(tracked, a)
|
|
elif tfNoSideEffect notin op.flags and not isOwnedProcVar(tracked, a):
|
|
markSideEffect(tracked, a, n.info)
|
|
else:
|
|
mergeRaises(tracked, effectList[exceptionEffects], n)
|
|
mergeTags(tracked, effectList[tagEffects], n)
|
|
if notGcSafe(op):
|
|
if tracked.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n, tracked.config)
|
|
markGcUnsafe(tracked, a)
|
|
elif tfNoSideEffect notin op.flags:
|
|
markSideEffect(tracked, a, n.info)
|
|
let paramType = if formals != nil and argIndex < formals.len: formals[argIndex] else: nil
|
|
if paramType != nil and paramType.kind in {tyVar}:
|
|
invalidateFacts(tracked.guards, n)
|
|
if n.kind == nkSym and isLocalSym(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): BreakState =
|
|
# semantic 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
|
|
|
|
case it.kind
|
|
of nkBreakStmt, nkReturnStmt:
|
|
result = bsBreakOrReturn
|
|
of nkRaiseStmt:
|
|
result = bsNoReturn
|
|
of nkCallKinds:
|
|
if it[0].kind == nkSym and sfNoReturn in it[0].sym.flags:
|
|
result = bsNoReturn
|
|
else:
|
|
result = bsNone
|
|
else:
|
|
result = bsNone
|
|
|
|
proc addIdToIntersection(tracked: PEffects, inter: var TIntersection, resCounter: var int,
|
|
hasBreaksBlock: BreakState, oldState: int, resSym: PSym, hasResult: bool) =
|
|
if hasResult:
|
|
var alreadySatisfy = false
|
|
|
|
if hasBreaksBlock == bsNoReturn:
|
|
alreadySatisfy = true
|
|
inc resCounter
|
|
|
|
for i in oldState..<tracked.init.len:
|
|
if tracked.init[i] == resSym.id:
|
|
if not alreadySatisfy:
|
|
inc resCounter
|
|
alreadySatisfy = true
|
|
else:
|
|
addToIntersection(inter, tracked.init[i], hasBreaksBlock)
|
|
else:
|
|
for i in oldState..<tracked.init.len:
|
|
addToIntersection(inter, tracked.init[i], hasBreaksBlock)
|
|
|
|
template hasResultSym(s: PSym): bool =
|
|
s != nil and s.kind in {skProc, skFunc, skConverter, skMethod} and
|
|
not isEmptyType(s.typ[0])
|
|
|
|
proc trackCase(tracked: PEffects, n: PNode) =
|
|
track(tracked, n[0])
|
|
inc tracked.inIfStmt
|
|
let oldState = tracked.init.len
|
|
let oldFacts = tracked.guards.s.len
|
|
let stringCase = n[0].typ != nil and skipTypes(n[0].typ,
|
|
abstractVarRange-{tyTypeDesc}).kind in {tyFloat..tyFloat128, tyString, tyCstring}
|
|
let interesting = not stringCase and interestingCaseExpr(n[0]) and
|
|
(tracked.config.hasWarn(warnProveField) or strictCaseObjects in tracked.c.features)
|
|
var inter: TIntersection = @[]
|
|
var toCover = 0
|
|
let hasResult = hasResultSym(tracked.owner)
|
|
let resSym = if hasResult: tracked.owner.ast[resultPos].sym else: nil
|
|
var resCounter = 0
|
|
|
|
for i in 1..<n.len:
|
|
let branch = n[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[i])
|
|
let hasBreaksBlock = breaksBlock(branch.lastSon)
|
|
if hasBreaksBlock == bsNone:
|
|
inc toCover
|
|
addIdToIntersection(tracked, inter, resCounter, hasBreaksBlock, oldState, resSym, hasResult)
|
|
|
|
setLen(tracked.init, oldState)
|
|
if not stringCase or lastSon(n).kind == nkElse:
|
|
if hasResult and resCounter == n.len-1:
|
|
tracked.init.add resSym.id
|
|
for id, count in items(inter):
|
|
if count >= toCover: tracked.init.add id
|
|
# else we can't merge
|
|
setLen(tracked.guards.s, oldFacts)
|
|
dec tracked.inIfStmt
|
|
|
|
proc trackIf(tracked: PEffects, n: PNode) =
|
|
track(tracked, n[0][0])
|
|
inc tracked.inIfStmt
|
|
let oldFacts = tracked.guards.s.len
|
|
addFact(tracked.guards, n[0][0])
|
|
let oldState = tracked.init.len
|
|
|
|
let hasResult = hasResultSym(tracked.owner)
|
|
let resSym = if hasResult: tracked.owner.ast[resultPos].sym else: nil
|
|
var resCounter = 0
|
|
|
|
var inter: TIntersection = @[]
|
|
var toCover = 0
|
|
track(tracked, n[0][1])
|
|
let hasBreaksBlock = breaksBlock(n[0][1])
|
|
if hasBreaksBlock == bsNone:
|
|
inc toCover
|
|
addIdToIntersection(tracked, inter, resCounter, hasBreaksBlock, oldState, resSym, hasResult)
|
|
|
|
for i in 1..<n.len:
|
|
let branch = n[i]
|
|
setLen(tracked.guards.s, oldFacts)
|
|
for j in 0..i-1:
|
|
addFactNeg(tracked.guards, n[j][0])
|
|
if branch.len > 1:
|
|
addFact(tracked.guards, branch[0])
|
|
setLen(tracked.init, oldState)
|
|
for i in 0..<branch.len:
|
|
track(tracked, branch[i])
|
|
let hasBreaksBlock = breaksBlock(branch.lastSon)
|
|
if hasBreaksBlock == bsNone:
|
|
inc toCover
|
|
addIdToIntersection(tracked, inter, resCounter, hasBreaksBlock, oldState, resSym, hasResult)
|
|
|
|
setLen(tracked.init, oldState)
|
|
if lastSon(n).len == 1:
|
|
if hasResult and resCounter == n.len:
|
|
tracked.init.add resSym.id
|
|
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)
|
|
dec tracked.inIfStmt
|
|
|
|
proc trackBlock(tracked: PEffects, n: PNode) =
|
|
if n.kind in {nkStmtList, nkStmtListExpr}:
|
|
var oldState = -1
|
|
for i in 0..<n.len:
|
|
if hasSubnodeWith(n[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[i])
|
|
if oldState > 0: setLen(tracked.init, oldState)
|
|
else:
|
|
track(tracked, n)
|
|
|
|
proc cstringCheck(tracked: PEffects; n: PNode) =
|
|
if n[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 patchResult(c: PEffects; n: PNode) =
|
|
if n.kind == nkSym and n.sym.kind == skResult:
|
|
let fn = c.owner
|
|
if fn != nil and fn.kind in routineKinds and fn.ast != nil and resultPos < fn.ast.len:
|
|
n.sym = fn.ast[resultPos].sym
|
|
else:
|
|
localError(c.config, n.info, "routine has no return type, but .requires contains 'result'")
|
|
else:
|
|
for i in 0..<safeLen(n):
|
|
patchResult(c, n[i])
|
|
|
|
proc checkLe(c: PEffects; a, b: PNode) =
|
|
case proveLe(c.guards, a, b)
|
|
of impUnknown:
|
|
#for g in c.guards.s:
|
|
# if g != nil: echo "I Know ", g
|
|
message(c.config, a.info, warnStaticIndexCheck,
|
|
"cannot prove: " & $a & " <= " & $b)
|
|
of impYes:
|
|
discard
|
|
of impNo:
|
|
message(c.config, a.info, warnStaticIndexCheck,
|
|
"can prove: " & $a & " > " & $b)
|
|
|
|
proc checkBounds(c: PEffects; arr, idx: PNode) =
|
|
checkLe(c, lowBound(c.config, arr), idx)
|
|
checkLe(c, idx, highBound(c.config, arr, c.guards.g.operators))
|
|
|
|
proc checkRange(c: PEffects; 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 passedToEffectsDelayedParam(tracked: PEffects; n: PNode) =
|
|
let t = n.typ.skipTypes(abstractInst)
|
|
if t.kind == tyProc:
|
|
if n.kind == nkSym and tracked.owner == n.sym.owner and sfEffectsDelayed in n.sym.flags:
|
|
discard "the arg is itself a delayed parameter, so do nothing"
|
|
else:
|
|
var effectList = t.n[0]
|
|
if effectList.len == effectListLen:
|
|
mergeRaises(tracked, effectList[exceptionEffects], n)
|
|
mergeTags(tracked, effectList[tagEffects], n)
|
|
if not importedFromC(n):
|
|
if notGcSafe(t):
|
|
if tracked.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n, tracked.config)
|
|
markGcUnsafe(tracked, n)
|
|
if tfNoSideEffect notin t.flags:
|
|
markSideEffect(tracked, n, n.info)
|
|
]#
|
|
|
|
proc checkForSink(tracked: PEffects; n: PNode) =
|
|
if tracked.inIfStmt == 0 and optSinkInference in tracked.config.options:
|
|
checkForSink(tracked.config, tracked.c.idgen, tracked.owner, n)
|
|
|
|
proc trackCall(tracked: PEffects; n: PNode) =
|
|
template gcsafeAndSideeffectCheck() =
|
|
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 tracked.config.hasWarn(warnGcUnsafe): 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, n.info)
|
|
# p's effects are ours too:
|
|
var a = n[0]
|
|
#if canRaise(a):
|
|
# echo "this can raise ", tracked.config $ n.info
|
|
let op = a.typ
|
|
if n.typ != nil:
|
|
if tracked.owner.kind != skMacro and n.typ.skipTypes(abstractVar).kind != tyOpenArray:
|
|
createTypeBoundOps(tracked, n.typ, n.info)
|
|
|
|
let notConstExpr = getConstExpr(tracked.ownerModule, n, tracked.c.idgen, tracked.graph) == nil
|
|
if notConstExpr:
|
|
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[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 (!):
|
|
if sfSideEffect in a.sym.flags: markSideEffect(tracked, a, n.info)
|
|
else:
|
|
discard
|
|
var effectList = op.n[0]
|
|
if a.kind == nkSym and a.sym.kind == skMethod:
|
|
if {sfBase, sfThread} * a.sym.flags == {sfBase}:
|
|
if tracked.config.hasWarn(warnGcUnsafe): warnAboutGcUnsafe(n, tracked.config)
|
|
markGcUnsafe(tracked, a)
|
|
propagateEffects(tracked, n, a.sym)
|
|
elif isNoEffectList(effectList):
|
|
if isForwardedProc(a):
|
|
propagateEffects(tracked, n, a.sym)
|
|
elif isIndirectCall(tracked, a):
|
|
assumeTheWorst(tracked, n, op)
|
|
gcsafeAndSideeffectCheck()
|
|
else:
|
|
if laxEffects notin tracked.c.config.legacyFeatures and a.kind == nkSym and
|
|
a.sym.kind in routineKinds:
|
|
propagateEffects(tracked, n, a.sym)
|
|
else:
|
|
mergeRaises(tracked, effectList[exceptionEffects], n)
|
|
mergeTags(tracked, effectList[tagEffects], n)
|
|
gcsafeAndSideeffectCheck()
|
|
if a.kind != nkSym or a.sym.magic notin {mNBindSym, mFinished, mExpandToAst, mQuoteAst}:
|
|
for i in 1..<n.len:
|
|
trackOperandForIndirectCall(tracked, n[i], op, i, a)
|
|
if a.kind == nkSym and a.sym.magic in {mNew, mNewFinalize, mNewSeq}:
|
|
# may not look like an assignment, but it is:
|
|
let arg = n[1]
|
|
initVarViaNew(tracked, arg)
|
|
if arg.typ.len != 0 and {tfRequiresInit} * 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)
|
|
|
|
# check required for 'nim check':
|
|
if n[1].typ.len > 0:
|
|
createTypeBoundOps(tracked, n[1].typ.lastSon, n.info)
|
|
createTypeBoundOps(tracked, n[1].typ, n.info)
|
|
# new(x, finalizer): Problem: how to move finalizer into 'createTypeBoundOps'?
|
|
|
|
elif a.kind == nkSym and a.sym.magic in {mArrGet, mArrPut} and
|
|
optStaticBoundsCheck in tracked.currOptions:
|
|
checkBounds(tracked, n[1], n[2])
|
|
|
|
|
|
if a.kind == nkSym and a.sym.name.s.len > 0 and a.sym.name.s[0] == '=' and
|
|
tracked.owner.kind != skMacro:
|
|
var opKind = find(AttachedOpToStr, a.sym.name.s.normalize)
|
|
if a.sym.name.s == "=": opKind = attachedAsgn.int
|
|
if opKind != -1:
|
|
# rebind type bounds operations after createTypeBoundOps call
|
|
let t = n[1].typ.skipTypes({tyAlias, tyVar})
|
|
if a.sym != getAttachedOp(tracked.graph, t, TTypeAttachedOp(opKind)):
|
|
createTypeBoundOps(tracked, t, n.info)
|
|
let op = getAttachedOp(tracked.graph, t, TTypeAttachedOp(opKind))
|
|
if op != nil:
|
|
n[0].sym = op
|
|
|
|
if op != nil and op.kind == tyProc:
|
|
for i in 1..<min(n.safeLen, op.len):
|
|
let paramType = op[i]
|
|
case paramType.kind
|
|
of tySink:
|
|
createTypeBoundOps(tracked, paramType[0], n.info)
|
|
checkForSink(tracked, n[i])
|
|
of tyVar:
|
|
if isOutParam(paramType):
|
|
# consider this case: p(out x, x); we want to remark that 'x' is not
|
|
# initialized until after the call. Since we do this after we analysed the
|
|
# call, this is fine.
|
|
initVar(tracked, n[i].skipAddr, false)
|
|
if strictFuncs in tracked.c.features and not tracked.inEnforcedNoSideEffects and
|
|
isDangerousLocation(n[i].skipAddr, tracked.owner):
|
|
if sfNoSideEffect in tracked.owner.flags:
|
|
localError(tracked.config, n[i].info,
|
|
"cannot pass $1 to `var T` parameter within a strict func" % renderTree(n[i]))
|
|
tracked.hasSideEffect = true
|
|
else: discard
|
|
|
|
if notConstExpr and (a.kind != nkSym or
|
|
a.sym.magic notin {mRunnableExamples, mNBindSym, mExpandToAst, mQuoteAst}
|
|
):
|
|
# tracked after out analysis
|
|
for i in 0..<n.safeLen:
|
|
track(tracked, n[i])
|
|
|
|
type
|
|
PragmaBlockContext = object
|
|
oldLocked: int
|
|
enforcedGcSafety, enforceNoSideEffects: bool
|
|
oldExc, oldTags, oldForbids: int
|
|
exc, tags, forbids: PNode
|
|
|
|
proc createBlockContext(tracked: PEffects): PragmaBlockContext =
|
|
var oldForbidsLen = 0
|
|
if tracked.forbids != nil: oldForbidsLen = tracked.forbids.len
|
|
result = PragmaBlockContext(oldLocked: tracked.locked.len,
|
|
enforcedGcSafety: false, enforceNoSideEffects: false,
|
|
oldExc: tracked.exc.len, oldTags: tracked.tags.len,
|
|
oldForbids: oldForbidsLen)
|
|
|
|
proc applyBlockContext(tracked: PEffects, bc: PragmaBlockContext) =
|
|
if bc.enforcedGcSafety: tracked.inEnforcedGcSafe = true
|
|
if bc.enforceNoSideEffects: tracked.inEnforcedNoSideEffects = true
|
|
|
|
proc unapplyBlockContext(tracked: PEffects; bc: PragmaBlockContext) =
|
|
if bc.enforcedGcSafety: tracked.inEnforcedGcSafe = false
|
|
if bc.enforceNoSideEffects: tracked.inEnforcedNoSideEffects = false
|
|
setLen(tracked.locked, bc.oldLocked)
|
|
if bc.exc != nil:
|
|
# beware that 'raises: []' is very different from not saying
|
|
# anything about 'raises' in the 'cast' at all. Same applies for 'tags'.
|
|
setLen(tracked.exc.sons, bc.oldExc)
|
|
for e in bc.exc:
|
|
addRaiseEffect(tracked, e, e)
|
|
if bc.tags != nil:
|
|
setLen(tracked.tags.sons, bc.oldTags)
|
|
for t in bc.tags:
|
|
addTag(tracked, t, t)
|
|
if bc.forbids != nil:
|
|
setLen(tracked.forbids.sons, bc.oldForbids)
|
|
for t in bc.forbids:
|
|
addNotTag(tracked, t, t)
|
|
|
|
proc castBlock(tracked: PEffects, pragma: PNode, bc: var PragmaBlockContext) =
|
|
case whichPragma(pragma)
|
|
of wGcSafe:
|
|
bc.enforcedGcSafety = true
|
|
of wNoSideEffect:
|
|
bc.enforceNoSideEffects = true
|
|
of wTags:
|
|
let n = pragma[1]
|
|
if n.kind in {nkCurly, nkBracket}:
|
|
bc.tags = n
|
|
else:
|
|
bc.tags = newNodeI(nkArgList, pragma.info)
|
|
bc.tags.add n
|
|
of wForbids:
|
|
let n = pragma[1]
|
|
if n.kind in {nkCurly, nkBracket}:
|
|
bc.forbids = n
|
|
else:
|
|
bc.forbids = newNodeI(nkArgList, pragma.info)
|
|
bc.forbids.add n
|
|
of wRaises:
|
|
let n = pragma[1]
|
|
if n.kind in {nkCurly, nkBracket}:
|
|
bc.exc = n
|
|
else:
|
|
bc.exc = newNodeI(nkArgList, pragma.info)
|
|
bc.exc.add n
|
|
of wUncheckedAssign:
|
|
discard "handled in sempass1"
|
|
else:
|
|
localError(tracked.config, pragma.info,
|
|
"invalid pragma block: " & $pragma)
|
|
|
|
proc trackInnerProc(tracked: PEffects, n: PNode) =
|
|
case n.kind
|
|
of nkSym:
|
|
let s = n.sym
|
|
if s.kind == skParam and s.owner == tracked.owner:
|
|
tracked.escapingParams.incl s.id
|
|
of nkNone..pred(nkSym), succ(nkSym)..nkNilLit:
|
|
discard
|
|
of nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef, nkLambda, nkFuncDef, nkDo:
|
|
if n[0].kind == nkSym and n[0].sym.ast != nil:
|
|
trackInnerProc(tracked, getBody(tracked.graph, n[0].sym))
|
|
of nkTypeSection, nkMacroDef, nkTemplateDef, nkError,
|
|
nkConstSection, nkConstDef, nkIncludeStmt, nkImportStmt,
|
|
nkExportStmt, nkPragma, nkCommentStmt, nkBreakState,
|
|
nkTypeOfExpr, nkMixinStmt, nkBindStmt:
|
|
discard
|
|
else:
|
|
for ch in n: trackInnerProc(tracked, ch)
|
|
|
|
proc allowCStringConv(n: PNode): bool =
|
|
case n.kind
|
|
of nkStrLit..nkTripleStrLit: result = true
|
|
of nkSym: result = n.sym.kind in {skConst, skParam}
|
|
of nkAddr: result = isCharArrayPtr(n.typ, true)
|
|
of nkCallKinds:
|
|
result = isCharArrayPtr(n.typ, n[0].kind == nkSym and n[0].sym.magic == mAddr)
|
|
else: result = isCharArrayPtr(n.typ, false)
|
|
|
|
proc track(tracked: PEffects, n: PNode) =
|
|
case n.kind
|
|
of nkSym:
|
|
useVar(tracked, n)
|
|
if n.sym.typ != nil and tfHasAsgn in n.sym.typ.flags:
|
|
tracked.owner.flags.incl sfInjectDestructors
|
|
# bug #15038: ensure consistency
|
|
if not hasDestructor(n.typ) and sameType(n.typ, n.sym.typ): n.typ = n.sym.typ
|
|
of nkHiddenAddr, nkAddr:
|
|
if n[0].kind == nkSym and isLocalSym(tracked, n[0].sym):
|
|
useVarNoInitCheck(tracked, n[0], n[0].sym)
|
|
else:
|
|
track(tracked, n[0])
|
|
of nkRaiseStmt:
|
|
if n[0].kind != nkEmpty:
|
|
n[0].info = n.info
|
|
#throws(tracked.exc, n[0])
|
|
addRaiseEffect(tracked, n[0], n)
|
|
for i in 0..<n.safeLen:
|
|
track(tracked, n[i])
|
|
createTypeBoundOps(tracked, n[0].typ, n.info)
|
|
else:
|
|
# A `raise` with no arguments means we're going to re-raise the exception
|
|
# being handled or, if outside of an `except` block, a `ReraiseDefect`.
|
|
# Here we add a `Exception` tag in order to cover both the cases.
|
|
addRaiseEffect(tracked, createRaise(tracked.graph, n), nil)
|
|
of nkCallKinds:
|
|
trackCall(tracked, n)
|
|
of nkDotExpr:
|
|
guardDotAccess(tracked, n)
|
|
for i in 0..<n.len: track(tracked, n[i])
|
|
of nkCheckedFieldExpr:
|
|
track(tracked, n[0])
|
|
if tracked.config.hasWarn(warnProveField) or strictCaseObjects in tracked.c.features:
|
|
checkFieldAccess(tracked.guards, n, tracked.config, strictCaseObjects in tracked.c.features)
|
|
of nkTryStmt: trackTryStmt(tracked, n)
|
|
of nkPragma: trackPragmaStmt(tracked, n)
|
|
of nkAsgn, nkFastAsgn, nkSinkAsgn:
|
|
track(tracked, n[1])
|
|
initVar(tracked, n[0], volatileCheck=true)
|
|
invalidateFacts(tracked.guards, n[0])
|
|
inc tracked.leftPartOfAsgn
|
|
track(tracked, n[0])
|
|
dec tracked.leftPartOfAsgn
|
|
addAsgnFact(tracked.guards, n[0], n[1])
|
|
notNilCheck(tracked, n[1], n[0].typ)
|
|
when false: cstringCheck(tracked, n)
|
|
if tracked.owner.kind != skMacro and n[0].typ.kind notin {tyOpenArray, tyVarargs}:
|
|
createTypeBoundOps(tracked, n[0].typ, n.info)
|
|
if n[0].kind != nkSym or not isLocalSym(tracked, n[0].sym):
|
|
checkForSink(tracked, n[1])
|
|
if strictFuncs in tracked.c.features and not tracked.inEnforcedNoSideEffects and
|
|
isDangerousLocation(n[0], tracked.owner):
|
|
tracked.hasSideEffect = true
|
|
if sfNoSideEffect in tracked.owner.flags:
|
|
localError(tracked.config, n[0].info,
|
|
"cannot mutate location $1 within a strict func" % renderTree(n[0]))
|
|
of nkVarSection, nkLetSection:
|
|
for child in n:
|
|
let last = lastSon(child)
|
|
if last.kind != nkEmpty: track(tracked, last)
|
|
if tracked.owner.kind != skMacro:
|
|
if child.kind == nkVarTuple:
|
|
createTypeBoundOps(tracked, child[^1].typ, child.info)
|
|
for i in 0..<child.len-2:
|
|
createTypeBoundOps(tracked, child[i].typ, child.info)
|
|
else:
|
|
createTypeBoundOps(tracked, skipPragmaExpr(child[0]).typ, child.info)
|
|
if child.kind == nkIdentDefs:
|
|
for i in 0..<child.len-2:
|
|
let a = skipPragmaExpr(child[i])
|
|
varDecl(tracked, a)
|
|
if last.kind != nkEmpty:
|
|
initVar(tracked, a, volatileCheck=false)
|
|
addAsgnFact(tracked.guards, a, last)
|
|
notNilCheck(tracked, last, a.typ)
|
|
elif child.kind == nkVarTuple:
|
|
for i in 0..<child.len-1:
|
|
if child[i].kind == nkEmpty or
|
|
child[i].kind == nkSym and child[i].sym.name.id == ord(wUnderscore):
|
|
continue
|
|
varDecl(tracked, child[i])
|
|
if last.kind != nkEmpty:
|
|
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[1])
|
|
of nkWhileStmt:
|
|
# 'while true' loop?
|
|
inc tracked.currentBlock
|
|
if isTrue(n[0]):
|
|
trackBlock(tracked, n[1])
|
|
else:
|
|
# loop may never execute:
|
|
let oldState = tracked.init.len
|
|
let oldFacts = tracked.guards.s.len
|
|
addFact(tracked.guards, n[0])
|
|
track(tracked, n[0])
|
|
track(tracked, n[1])
|
|
setLen(tracked.init, oldState)
|
|
setLen(tracked.guards.s, oldFacts)
|
|
dec tracked.currentBlock
|
|
of nkForStmt, nkParForStmt:
|
|
# we are very conservative here and assume the loop is never executed:
|
|
inc tracked.currentBlock
|
|
let oldState = tracked.init.len
|
|
|
|
let oldFacts = tracked.guards.s.len
|
|
let iterCall = n[n.len-2]
|
|
if optStaticBoundsCheck in tracked.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(tracked.guards, lower, iterVar)
|
|
addFactLe(tracked.guards, iterVar, upper)
|
|
of "..<":
|
|
let lower = iterCall[1]
|
|
let upper = iterCall[2]
|
|
addFactLe(tracked.guards, lower, iterVar)
|
|
addFactLt(tracked.guards, iterVar, upper)
|
|
else: discard
|
|
|
|
for i in 0..<n.len-2:
|
|
let it = n[i]
|
|
track(tracked, it)
|
|
if tracked.owner.kind != skMacro:
|
|
if it.kind == nkVarTuple:
|
|
for x in it:
|
|
createTypeBoundOps(tracked, x.typ, x.info)
|
|
else:
|
|
createTypeBoundOps(tracked, it.typ, it.info)
|
|
let loopBody = n[^1]
|
|
if tracked.owner.kind != skMacro and iterCall.safeLen > 1:
|
|
# XXX this is a bit hacky:
|
|
if iterCall[1].typ != nil and iterCall[1].typ.skipTypes(abstractVar).kind notin {tyVarargs, tyOpenArray}:
|
|
createTypeBoundOps(tracked, iterCall[1].typ, iterCall[1].info)
|
|
track(tracked, iterCall)
|
|
track(tracked, loopBody)
|
|
setLen(tracked.init, oldState)
|
|
setLen(tracked.guards.s, oldFacts)
|
|
dec tracked.currentBlock
|
|
|
|
of nkObjConstr:
|
|
when false: track(tracked, n[0])
|
|
let oldFacts = tracked.guards.s.len
|
|
for i in 1..<n.len:
|
|
let x = n[i]
|
|
track(tracked, x)
|
|
if x[0].kind == nkSym and sfDiscriminant in x[0].sym.flags:
|
|
addDiscriminantFact(tracked.guards, x)
|
|
if tracked.owner.kind != skMacro:
|
|
createTypeBoundOps(tracked, x[1].typ, n.info)
|
|
|
|
if x.kind == nkExprColonExpr:
|
|
if x[0].kind == nkSym:
|
|
notNilCheck(tracked, x[1], x[0].sym.typ)
|
|
checkForSink(tracked, x[1])
|
|
else:
|
|
checkForSink(tracked, x)
|
|
setLen(tracked.guards.s, oldFacts)
|
|
if tracked.owner.kind != skMacro:
|
|
# XXX n.typ can be nil in runnableExamples, we need to do something about it.
|
|
if n.typ != nil and n.typ.skipTypes(abstractInst).kind == tyRef:
|
|
createTypeBoundOps(tracked, n.typ.lastSon, n.info)
|
|
createTypeBoundOps(tracked, n.typ, n.info)
|
|
of nkTupleConstr:
|
|
for i in 0..<n.len:
|
|
track(tracked, n[i])
|
|
notNilCheck(tracked, n[i].skipColon, n[i].typ)
|
|
if tracked.owner.kind != skMacro:
|
|
if n[i].kind == nkExprColonExpr:
|
|
createTypeBoundOps(tracked, n[i][0].typ, n.info)
|
|
else:
|
|
createTypeBoundOps(tracked, n[i].typ, n.info)
|
|
checkForSink(tracked, n[i])
|
|
of nkPragmaBlock:
|
|
let pragmaList = n[0]
|
|
var bc = createBlockContext(tracked)
|
|
for i in 0..<pragmaList.len:
|
|
let pragma = whichPragma(pragmaList[i])
|
|
case pragma
|
|
of wLocks:
|
|
lockLocations(tracked, pragmaList[i])
|
|
of wGcSafe:
|
|
bc.enforcedGcSafety = true
|
|
of wNoSideEffect:
|
|
bc.enforceNoSideEffects = true
|
|
of wCast:
|
|
castBlock(tracked, pragmaList[i][1], bc)
|
|
else:
|
|
discard
|
|
applyBlockContext(tracked, bc)
|
|
track(tracked, n.lastSon)
|
|
unapplyBlockContext(tracked, bc)
|
|
|
|
of nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef, nkLambda, nkFuncDef, nkDo:
|
|
if n[0].kind == nkSym and n[0].sym.ast != nil:
|
|
trackInnerProc(tracked, getBody(tracked.graph, n[0].sym))
|
|
of nkTypeSection, nkMacroDef, nkTemplateDef:
|
|
discard
|
|
of nkCast:
|
|
if n.len == 2:
|
|
track(tracked, n[1])
|
|
if tracked.owner.kind != skMacro:
|
|
createTypeBoundOps(tracked, n.typ, n.info)
|
|
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
|
|
if n.kind in {nkHiddenStdConv, nkHiddenSubConv} and
|
|
n.typ.skipTypes(abstractInst).kind == tyCstring and
|
|
not allowCStringConv(n[1]):
|
|
message(tracked.config, n.info, warnCstringConv,
|
|
"implicit conversion to 'cstring' from a non-const location: $1; this will become a compile time error in the future" %
|
|
$n[1])
|
|
if n.typ.skipTypes(abstractInst).kind == tyCstring and
|
|
isCharArrayPtr(n[1].typ, true):
|
|
message(tracked.config, n.info, warnPtrToCstringConv,
|
|
$n[1].typ)
|
|
|
|
|
|
let t = n.typ.skipTypes(abstractInst)
|
|
if t.kind == tyEnum:
|
|
if tfEnumHasHoles in t.flags:
|
|
message(tracked.config, n.info, warnHoleEnumConv, "conversion to enum with holes is unsafe: $1" % $n)
|
|
else:
|
|
message(tracked.config, n.info, warnAnyEnumConv, "enum conversion: $1" % $n)
|
|
|
|
if n.len == 2:
|
|
track(tracked, n[1])
|
|
if tracked.owner.kind != skMacro:
|
|
createTypeBoundOps(tracked, n.typ, n.info)
|
|
# This is a hacky solution in order to fix bug #13110. Hopefully
|
|
# a better solution will come up eventually.
|
|
if n[1].typ.kind != tyString:
|
|
createTypeBoundOps(tracked, n[1].typ, n[1].info)
|
|
if optStaticBoundsCheck in tracked.currOptions:
|
|
checkRange(tracked, n[1], n.typ)
|
|
of nkObjUpConv, nkObjDownConv, nkChckRange, nkChckRangeF, nkChckRange64:
|
|
if n.len == 1:
|
|
track(tracked, n[0])
|
|
if tracked.owner.kind != skMacro:
|
|
createTypeBoundOps(tracked, n.typ, n.info)
|
|
createTypeBoundOps(tracked, n[0].typ, n[0].info)
|
|
if optStaticBoundsCheck in tracked.currOptions:
|
|
checkRange(tracked, n[0], n.typ)
|
|
of nkBracket:
|
|
for i in 0..<n.safeLen:
|
|
track(tracked, n[i])
|
|
checkForSink(tracked, n[i])
|
|
if tracked.owner.kind != skMacro:
|
|
createTypeBoundOps(tracked, n.typ, n.info)
|
|
of nkBracketExpr:
|
|
if optStaticBoundsCheck in tracked.currOptions and n.len == 2:
|
|
if n[0].typ != nil and skipTypes(n[0].typ, abstractVar).kind != tyTuple:
|
|
checkBounds(tracked, n[0], n[1])
|
|
track(tracked, n[0])
|
|
dec tracked.leftPartOfAsgn
|
|
for i in 1 ..< n.len: track(tracked, n[i])
|
|
inc tracked.leftPartOfAsgn
|
|
of nkError:
|
|
localError(tracked.config, n.info, errorToString(tracked.config, n))
|
|
else:
|
|
for i in 0..<n.safeLen: track(tracked, n[i])
|
|
|
|
proc subtypeRelation(g: ModuleGraph; spec, real: PNode): bool =
|
|
if spec.typ.kind == tyOr:
|
|
result = false
|
|
for t in spec.typ:
|
|
if safeInheritanceDiff(g.excType(real), t) <= 0:
|
|
return true
|
|
else:
|
|
return safeInheritanceDiff(g.excType(real), spec.typ) <= 0
|
|
|
|
proc checkRaisesSpec(g: ModuleGraph; emitWarnings: bool; spec, real: PNode, msg: string, hints: bool;
|
|
effectPredicate: proc (g: ModuleGraph; a, b: PNode): bool {.nimcall.};
|
|
hintsArg: PNode = nil; isForbids: bool = false) =
|
|
# 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):
|
|
if isForbids: break
|
|
used.incl(s)
|
|
break search
|
|
if isForbids:
|
|
break search
|
|
# XXX call graph analysis would be nice here!
|
|
pushInfoContext(g.config, spec.info)
|
|
var rr = if r.kind == nkRaiseStmt: r[0] else: r
|
|
while rr.kind in {nkStmtList, nkStmtListExpr} and rr.len > 0: rr = rr.lastSon
|
|
message(g.config, r.info, if emitWarnings: warnEffect else: errGenerated,
|
|
renderTree(rr) & " " & 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, hintXCannotRaiseY,
|
|
"'$1' cannot raise '$2'" % [renderTree(hintsArg), renderTree(spec[s])])
|
|
|
|
proc checkMethodEffects*(g: ModuleGraph; disp, branch: PSym) =
|
|
## checks for consistent effects for multi methods.
|
|
let actual = branch.typ.n[0]
|
|
if actual.len != effectListLen: return
|
|
|
|
let p = disp.ast[pragmasPos]
|
|
let raisesSpec = effectSpec(p, wRaises)
|
|
if not isNil(raisesSpec):
|
|
checkRaisesSpec(g, false, raisesSpec, actual[exceptionEffects],
|
|
"can raise an unlisted exception: ", hints=off, subtypeRelation)
|
|
let tagsSpec = effectSpec(p, wTags)
|
|
if not isNil(tagsSpec):
|
|
checkRaisesSpec(g, false, tagsSpec, actual[tagEffects],
|
|
"can have an unlisted effect: ", hints=off, subtypeRelation)
|
|
let forbidsSpec = effectSpec(p, wForbids)
|
|
if not isNil(forbidsSpec):
|
|
checkRaisesSpec(g, false, forbidsSpec, actual[tagEffects],
|
|
"has an illegal effect: ", hints=off, subtypeRelation, isForbids=true)
|
|
if sfThread in disp.flags and notGcSafe(branch.typ):
|
|
localError(g.config, branch.info, "base method is GC-safe, but '$1' is not" %
|
|
branch.name.s)
|
|
when defined(drnim):
|
|
if not g.compatibleProps(g, disp.typ, branch.typ):
|
|
localError(g.config, branch.info, "for method '" & branch.name.s &
|
|
"' the `.requires` or `.ensures` properties are incompatible.")
|
|
|
|
proc setEffectsForProcType*(g: ModuleGraph; t: PType, n: PNode; s: PSym = nil) =
|
|
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
|
|
elif s != nil and (s.magic != mNone or {sfImportc, sfExportc} * s.flags == {sfImportc}):
|
|
effects[exceptionEffects] = newNodeI(nkArgList, effects.info)
|
|
|
|
let tagsSpec = effectSpec(n, wTags)
|
|
if not isNil(tagsSpec):
|
|
effects[tagEffects] = tagsSpec
|
|
elif s != nil and (s.magic != mNone or {sfImportc, sfExportc} * s.flags == {sfImportc}):
|
|
effects[tagEffects] = newNodeI(nkArgList, effects.info)
|
|
|
|
let forbidsSpec = effectSpec(n, wForbids)
|
|
if not isNil(forbidsSpec):
|
|
effects[forbiddenEffects] = forbidsSpec
|
|
elif s != nil and (s.magic != mNone or {sfImportc, sfExportc} * s.flags == {sfImportc}):
|
|
effects[forbiddenEffects] = newNodeI(nkArgList, effects.info)
|
|
|
|
let requiresSpec = propSpec(n, wRequires)
|
|
if not isNil(requiresSpec):
|
|
effects[requiresEffects] = requiresSpec
|
|
let ensuresSpec = propSpec(n, wEnsures)
|
|
if not isNil(ensuresSpec):
|
|
effects[ensuresEffects] = ensuresSpec
|
|
|
|
effects[pragmasEffects] = n
|
|
if s != nil and s.magic != mNone:
|
|
if s.magic != mEcho:
|
|
t.flags.incl tfNoSideEffect
|
|
|
|
proc rawInitEffects(g: ModuleGraph; effects: PNode) =
|
|
newSeq(effects.sons, effectListLen)
|
|
effects[exceptionEffects] = newNodeI(nkArgList, effects.info)
|
|
effects[tagEffects] = newNodeI(nkArgList, effects.info)
|
|
effects[forbiddenEffects] = newNodeI(nkArgList, effects.info)
|
|
effects[requiresEffects] = g.emptyNode
|
|
effects[ensuresEffects] = g.emptyNode
|
|
effects[pragmasEffects] = g.emptyNode
|
|
|
|
proc initEffects(g: ModuleGraph; effects: PNode; s: PSym; c: PContext): TEffects =
|
|
rawInitEffects(g, effects)
|
|
|
|
result = TEffects(exc: effects[exceptionEffects], tags: effects[tagEffects],
|
|
forbids: effects[forbiddenEffects], owner: s, ownerModule: s.getModule,
|
|
init: @[], locked: @[], graph: g, config: g.config, c: c,
|
|
currentBlock: 1
|
|
)
|
|
result.guards.s = @[]
|
|
result.guards.g = g
|
|
when defined(drnim):
|
|
result.currOptions = g.config.options + s.options - {optStaticBoundsCheck}
|
|
else:
|
|
result.currOptions = g.config.options + s.options
|
|
result.guards.beSmart = optStaticBoundsCheck in result.currOptions
|
|
|
|
proc hasRealBody(s: PSym): bool =
|
|
## also handles importc procs with runnableExamples, which requires `=`,
|
|
## which is not a real implementation, refs #14314
|
|
result = {sfForward, sfImportc} * s.flags == {}
|
|
|
|
proc trackProc*(c: PContext; s: PSym, body: PNode) =
|
|
let g = c.graph
|
|
when defined(nimsuggest):
|
|
if g.config.expandDone():
|
|
return
|
|
var effects = s.typ.n[0]
|
|
if effects.kind != nkEffectList: return
|
|
# effects already computed?
|
|
if not s.hasRealBody: return
|
|
let emitWarnings = tfEffectSystemWorkaround in s.typ.flags
|
|
if effects.len == effectListLen and not emitWarnings: return
|
|
|
|
var inferredEffects = newNodeI(nkEffectList, s.info)
|
|
|
|
var t: TEffects = initEffects(g, inferredEffects, s, c)
|
|
rawInitEffects g, effects
|
|
|
|
if not isEmptyType(s.typ[0]) and
|
|
s.kind in {skProc, skFunc, skConverter, skMethod}:
|
|
var res = s.ast[resultPos].sym # get result symbol
|
|
t.scopes[res.id] = t.currentBlock
|
|
|
|
track(t, body)
|
|
|
|
if s.kind != skMacro:
|
|
let params = s.typ.n
|
|
for i in 1..<params.len:
|
|
let param = params[i].sym
|
|
let typ = param.typ
|
|
if isSinkTypeForParam(typ) or
|
|
(t.config.selectedGC in {gcArc, gcOrc, gcAtomicArc} and
|
|
(isClosure(typ.skipTypes(abstractInst)) or param.id in t.escapingParams)):
|
|
createTypeBoundOps(t, typ, param.info)
|
|
if isOutParam(typ) and param.id notin t.init:
|
|
message(g.config, param.info, warnProveInit, param.name.s)
|
|
|
|
if not isEmptyType(s.typ[0]) and
|
|
(s.typ[0].requiresInit or s.typ[0].skipTypes(abstractInst).kind == tyVar or
|
|
strictDefs in c.features) and
|
|
s.kind in {skProc, skFunc, skConverter, skMethod} and s.magic == mNone:
|
|
var res = s.ast[resultPos].sym # get result symbol
|
|
if res.id notin t.init:
|
|
if tfRequiresInit in s.typ[0].flags:
|
|
localError(g.config, body.info, "'$1' requires explicit initialization" % "result")
|
|
else:
|
|
message(g.config, body.info, warnProveInit, "result")
|
|
let p = s.ast[pragmasPos]
|
|
let raisesSpec = effectSpec(p, wRaises)
|
|
if not isNil(raisesSpec):
|
|
let useWarning = s.name.s == "=destroy"
|
|
checkRaisesSpec(g, useWarning, raisesSpec, t.exc, "can raise an unlisted exception: ",
|
|
hints=on, subtypeRelation, hintsArg=s.ast[0])
|
|
# after the check, use the formal spec:
|
|
effects[exceptionEffects] = raisesSpec
|
|
else:
|
|
effects[exceptionEffects] = t.exc
|
|
|
|
let tagsSpec = effectSpec(p, wTags)
|
|
if not isNil(tagsSpec):
|
|
checkRaisesSpec(g, false, tagsSpec, t.tags, "can have an unlisted effect: ",
|
|
hints=off, subtypeRelation)
|
|
# after the check, use the formal spec:
|
|
effects[tagEffects] = tagsSpec
|
|
else:
|
|
effects[tagEffects] = t.tags
|
|
|
|
let forbidsSpec = effectSpec(p, wForbids)
|
|
if not isNil(forbidsSpec):
|
|
checkRaisesSpec(g, false, forbidsSpec, t.tags, "has an illegal effect: ",
|
|
hints=off, subtypeRelation, isForbids=true)
|
|
# after the check, use the formal spec:
|
|
effects[forbiddenEffects] = forbidsSpec
|
|
else:
|
|
effects[forbiddenEffects] = t.forbids
|
|
|
|
let requiresSpec = propSpec(p, wRequires)
|
|
if not isNil(requiresSpec):
|
|
effects[requiresEffects] = requiresSpec
|
|
let ensuresSpec = propSpec(p, wEnsures)
|
|
if not isNil(ensuresSpec):
|
|
patchResult(t, ensuresSpec)
|
|
effects[ensuresEffects] = ensuresSpec
|
|
|
|
var mutationInfo = MutationInfo()
|
|
if views in c.features:
|
|
var partitions = computeGraphPartitions(s, body, g, {borrowChecking})
|
|
checkBorrowedLocations(partitions, body, g.config)
|
|
|
|
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:
|
|
if c.compilesContextId == 0: # don't render extended diagnostic messages in `system.compiles` context
|
|
var msg = ""
|
|
listSideEffects(msg, s, g.config, t.c)
|
|
message(g.config, s.info, errGenerated, msg)
|
|
else:
|
|
localError(g.config, s.info, "") # simple error for `system.compiles` context
|
|
if not t.gcUnsafe:
|
|
s.typ.flags.incl tfGcSafe
|
|
if not t.hasSideEffect and sfSideEffect notin s.flags:
|
|
s.typ.flags.incl tfNoSideEffect
|
|
when defined(drnim):
|
|
if c.graph.strongSemCheck != nil: c.graph.strongSemCheck(c.graph, s, body)
|
|
when defined(useDfa):
|
|
if s.name.s == "testp":
|
|
dataflowAnalysis(s, body)
|
|
|
|
when false: trackWrites(s, body)
|
|
if strictNotNil in c.features and s.kind == skProc:
|
|
checkNil(s, body, g.config, c.idgen)
|
|
|
|
proc trackStmt*(c: PContext; module: PSym; n: PNode, isTopLevel: bool) =
|
|
if n.kind in {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef, nkFuncDef,
|
|
nkTypeSection, nkConverterDef, nkMethodDef, nkIteratorDef}:
|
|
return
|
|
let g = c.graph
|
|
var effects = newNodeI(nkEffectList, n.info)
|
|
var t: TEffects = initEffects(g, effects, module, c)
|
|
t.isTopLevel = isTopLevel
|
|
track(t, n)
|
|
when defined(drnim):
|
|
if c.graph.strongSemCheck != nil: c.graph.strongSemCheck(c.graph, module, n)
|