fixes #2257
This commit is contained in:
parent
f9a3de984d
commit
bc264618f5
4 changed files with 93 additions and 32 deletions
|
|
@ -1335,8 +1335,12 @@ proc propagateToOwner*(owner, elem: PType) =
|
||||||
if elem.isMetaType:
|
if elem.isMetaType:
|
||||||
owner.flags.incl tfHasMeta
|
owner.flags.incl tfHasMeta
|
||||||
|
|
||||||
if owner.kind != tyProc:
|
if owner.kind notin {tyProc, tyGenericInst, tyGenericBody,
|
||||||
if elem.isGCedMem or tfHasGCedMem in elem.flags:
|
tyGenericInvocation}:
|
||||||
|
let elemB = elem.skipTypes({tyGenericInst})
|
||||||
|
if elemB.isGCedMem or tfHasGCedMem in elemB.flags:
|
||||||
|
# for simplicity, we propagate this flag even to generics. We then
|
||||||
|
# ensure this doesn't bite us in sempass2.
|
||||||
owner.flags.incl tfHasGCedMem
|
owner.flags.incl tfHasGCedMem
|
||||||
|
|
||||||
proc rawAddSon*(father, son: PType) =
|
proc rawAddSon*(father, son: PType) =
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,12 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
import
|
import
|
||||||
intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees,
|
intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees,
|
||||||
wordrecg, strutils, options, guards
|
wordrecg, strutils, options, guards
|
||||||
|
|
||||||
# Second semantic checking pass over the AST. Necessary because the old
|
# Second semantic checking pass over the AST. Necessary because the old
|
||||||
# way had some inherent problems. Performs:
|
# way had some inherent problems. Performs:
|
||||||
#
|
#
|
||||||
# * effect+exception tracking
|
# * effect+exception tracking
|
||||||
# * "usage before definition" checking
|
# * "usage before definition" checking
|
||||||
# * checks for invalid usages of compiletime magics (not implemented)
|
# * checks for invalid usages of compiletime magics (not implemented)
|
||||||
|
|
@ -23,7 +23,7 @@ import
|
||||||
# Predefined effects:
|
# Predefined effects:
|
||||||
# io, time (time dependent), gc (performs GC'ed allocation), exceptions,
|
# io, time (time dependent), gc (performs GC'ed allocation), exceptions,
|
||||||
# side effect (accesses global), store (stores into *type*),
|
# side effect (accesses global), store (stores into *type*),
|
||||||
# store_unknown (performs some store) --> store(any)|store(x)
|
# store_unknown (performs some store) --> store(any)|store(x)
|
||||||
# load (loads from *type*), recursive (recursive call), unsafe,
|
# load (loads from *type*), recursive (recursive call), unsafe,
|
||||||
# endless (has endless loops), --> user effects are defined over *patterns*
|
# endless (has endless loops), --> user effects are defined over *patterns*
|
||||||
# --> a TR macro can annotate the proc with user defined annotations
|
# --> a TR macro can annotate the proc with user defined annotations
|
||||||
|
|
@ -31,31 +31,31 @@ import
|
||||||
|
|
||||||
# Load&Store analysis is performed on *paths*. A path is an access like
|
# Load&Store analysis is performed on *paths*. A path is an access like
|
||||||
# obj.x.y[i].z; splitting paths up causes some problems:
|
# obj.x.y[i].z; splitting paths up causes some problems:
|
||||||
#
|
#
|
||||||
# var x = obj.x
|
# var x = obj.x
|
||||||
# var z = x.y[i].z
|
# var z = x.y[i].z
|
||||||
#
|
#
|
||||||
# Alias analysis is affected by this too! A good solution is *type splitting*:
|
# Alias analysis is affected by this too! A good solution is *type splitting*:
|
||||||
# T becomes T1 and T2 if it's known that T1 and T2 can't alias.
|
# T becomes T1 and T2 if it's known that T1 and T2 can't alias.
|
||||||
#
|
#
|
||||||
# An aliasing problem and a race condition are effectively the same problem.
|
# An aliasing problem and a race condition are effectively the same problem.
|
||||||
# Type based alias analysis is nice but not sufficient; especially splitting
|
# Type based alias analysis is nice but not sufficient; especially splitting
|
||||||
# an array and filling it in parallel should be supported but is not easily
|
# an array and filling it in parallel should be supported but is not easily
|
||||||
# done: It essentially requires a built-in 'indexSplit' operation and dependent
|
# done: It essentially requires a built-in 'indexSplit' operation and dependent
|
||||||
# typing.
|
# typing.
|
||||||
|
|
||||||
# ------------------------ exception and tag tracking -------------------------
|
# ------------------------ exception and tag tracking -------------------------
|
||||||
|
|
||||||
discard """
|
discard """
|
||||||
exception tracking:
|
exception tracking:
|
||||||
|
|
||||||
a() # raises 'x', 'e'
|
a() # raises 'x', 'e'
|
||||||
try:
|
try:
|
||||||
b() # raises 'e'
|
b() # raises 'e'
|
||||||
except e:
|
except e:
|
||||||
# must not undo 'e' here; hrm
|
# must not undo 'e' here; hrm
|
||||||
c()
|
c()
|
||||||
|
|
||||||
--> we need a stack of scopes for this analysis
|
--> we need a stack of scopes for this analysis
|
||||||
|
|
||||||
# XXX enhance the algorithm to care about 'dirty' expressions:
|
# XXX enhance the algorithm to care about 'dirty' expressions:
|
||||||
|
|
@ -209,8 +209,7 @@ proc useVar(a: PEffects, n: PNode) =
|
||||||
a.init.add s.id
|
a.init.add s.id
|
||||||
if {sfGlobal, sfThread} * s.flags == {sfGlobal} and s.kind in {skVar, skLet}:
|
if {sfGlobal, sfThread} * s.flags == {sfGlobal} and s.kind in {skVar, skLet}:
|
||||||
if s.guard != nil: guardGlobal(a, n, s.guard)
|
if s.guard != nil: guardGlobal(a, n, s.guard)
|
||||||
if (tfHasGCedMem in s.typ.flags or s.typ.isGCedMem) and
|
if (tfHasGCedMem in s.typ.flags or s.typ.isGCedMem):
|
||||||
tfGcSafe notin s.typ.flags:
|
|
||||||
if warnGcUnsafe in gNotes: warnAboutGcUnsafe(n)
|
if warnGcUnsafe in gNotes: warnAboutGcUnsafe(n)
|
||||||
markGcUnsafe(a)
|
markGcUnsafe(a)
|
||||||
|
|
||||||
|
|
@ -321,7 +320,7 @@ proc trackTryStmt(tracked: PEffects, n: PNode) =
|
||||||
dec tracked.inTryStmt
|
dec tracked.inTryStmt
|
||||||
for i in oldState.. <tracked.init.len:
|
for i in oldState.. <tracked.init.len:
|
||||||
addToIntersection(inter, tracked.init[i])
|
addToIntersection(inter, tracked.init[i])
|
||||||
|
|
||||||
var branches = 1
|
var branches = 1
|
||||||
var hasFinally = false
|
var hasFinally = false
|
||||||
for i in 1 .. < n.len:
|
for i in 1 .. < n.len:
|
||||||
|
|
@ -345,7 +344,7 @@ proc trackTryStmt(tracked: PEffects, n: PNode) =
|
||||||
setLen(tracked.init, oldState)
|
setLen(tracked.init, oldState)
|
||||||
track(tracked, b.sons[blen-1])
|
track(tracked, b.sons[blen-1])
|
||||||
hasFinally = true
|
hasFinally = true
|
||||||
|
|
||||||
tracked.bottom = oldBottom
|
tracked.bottom = oldBottom
|
||||||
if not hasFinally:
|
if not hasFinally:
|
||||||
setLen(tracked.init, oldState)
|
setLen(tracked.init, oldState)
|
||||||
|
|
@ -356,7 +355,7 @@ proc isIndirectCall(n: PNode, owner: PSym): bool =
|
||||||
# we don't count f(...) as an indirect call if 'f' is an parameter.
|
# 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
|
# Instead we track expressions of type tyProc too. See the manual for
|
||||||
# details:
|
# details:
|
||||||
if n.kind != nkSym:
|
if n.kind != nkSym:
|
||||||
result = true
|
result = true
|
||||||
elif n.sym.kind == skParam:
|
elif n.sym.kind == skParam:
|
||||||
result = owner != n.sym.owner or owner == nil
|
result = owner != n.sym.owner or owner == nil
|
||||||
|
|
@ -366,13 +365,13 @@ proc isIndirectCall(n: PNode, owner: PSym): bool =
|
||||||
proc isForwardedProc(n: PNode): bool =
|
proc isForwardedProc(n: PNode): bool =
|
||||||
result = n.kind == nkSym and sfForward in n.sym.flags
|
result = n.kind == nkSym and sfForward in n.sym.flags
|
||||||
|
|
||||||
proc trackPragmaStmt(tracked: PEffects, n: PNode) =
|
proc trackPragmaStmt(tracked: PEffects, n: PNode) =
|
||||||
for i in countup(0, sonsLen(n) - 1):
|
for i in countup(0, sonsLen(n) - 1):
|
||||||
var it = n.sons[i]
|
var it = n.sons[i]
|
||||||
if whichPragma(it) == wEffects:
|
if whichPragma(it) == wEffects:
|
||||||
# list the computed effects up to here:
|
# list the computed effects up to here:
|
||||||
listEffects(tracked)
|
listEffects(tracked)
|
||||||
|
|
||||||
proc effectSpec(n: PNode, effectType: TSpecialWord): PNode =
|
proc effectSpec(n: PNode, effectType: TSpecialWord): PNode =
|
||||||
for i in countup(0, sonsLen(n) - 1):
|
for i in countup(0, sonsLen(n) - 1):
|
||||||
var it = n.sons[i]
|
var it = n.sons[i]
|
||||||
|
|
@ -387,12 +386,12 @@ proc documentEffect(n, x: PNode, effectType: TSpecialWord, idx: int): PNode =
|
||||||
let spec = effectSpec(x, effectType)
|
let spec = effectSpec(x, effectType)
|
||||||
if isNil(spec):
|
if isNil(spec):
|
||||||
let s = n.sons[namePos].sym
|
let s = n.sons[namePos].sym
|
||||||
|
|
||||||
let actual = s.typ.n.sons[0]
|
let actual = s.typ.n.sons[0]
|
||||||
if actual.len != effectListLen: return
|
if actual.len != effectListLen: return
|
||||||
let real = actual.sons[idx]
|
let real = actual.sons[idx]
|
||||||
|
|
||||||
# warning: hack ahead:
|
# warning: hack ahead:
|
||||||
var effects = newNodeI(nkBracket, n.info, real.len)
|
var effects = newNodeI(nkBracket, n.info, real.len)
|
||||||
for i in 0 .. <real.len:
|
for i in 0 .. <real.len:
|
||||||
var t = typeToString(real[i].typ)
|
var t = typeToString(real[i].typ)
|
||||||
|
|
@ -409,7 +408,7 @@ proc documentRaises*(n: PNode) =
|
||||||
let pragmas = n.sons[pragmasPos]
|
let pragmas = n.sons[pragmasPos]
|
||||||
let p1 = documentEffect(n, pragmas, wRaises, exceptionEffects)
|
let p1 = documentEffect(n, pragmas, wRaises, exceptionEffects)
|
||||||
let p2 = documentEffect(n, pragmas, wTags, tagEffects)
|
let p2 = documentEffect(n, pragmas, wTags, tagEffects)
|
||||||
|
|
||||||
if p1 != nil or p2 != nil:
|
if p1 != nil or p2 != nil:
|
||||||
if pragmas.kind == nkEmpty:
|
if pragmas.kind == nkEmpty:
|
||||||
n.sons[pragmasPos] = newNodeI(nkPragma, n.info)
|
n.sons[pragmasPos] = newNodeI(nkPragma, n.info)
|
||||||
|
|
@ -445,7 +444,7 @@ proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) =
|
||||||
let pragma = s.ast.sons[pragmasPos]
|
let pragma = s.ast.sons[pragmasPos]
|
||||||
let spec = effectSpec(pragma, wRaises)
|
let spec = effectSpec(pragma, wRaises)
|
||||||
mergeEffects(tracked, spec, n)
|
mergeEffects(tracked, spec, n)
|
||||||
|
|
||||||
let tagSpec = effectSpec(pragma, wTags)
|
let tagSpec = effectSpec(pragma, wTags)
|
||||||
mergeTags(tracked, tagSpec, n)
|
mergeTags(tracked, tagSpec, n)
|
||||||
|
|
||||||
|
|
@ -456,7 +455,7 @@ proc propagateEffects(tracked: PEffects, n: PNode, s: PSym) =
|
||||||
|
|
||||||
proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) =
|
proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) =
|
||||||
let n = n.skipConv
|
let n = n.skipConv
|
||||||
if paramType != nil and tfNotNil in paramType.flags and
|
if paramType != nil and tfNotNil in paramType.flags and
|
||||||
n.typ != nil and tfNotNil notin n.typ.flags:
|
n.typ != nil and tfNotNil notin n.typ.flags:
|
||||||
if n.kind == nkAddr:
|
if n.kind == nkAddr:
|
||||||
# addr(x[]) can't be proven, but addr(x) can:
|
# addr(x[]) can't be proven, but addr(x) can:
|
||||||
|
|
@ -466,7 +465,7 @@ proc notNilCheck(tracked: PEffects, n: PNode, paramType: PType) =
|
||||||
return
|
return
|
||||||
case impliesNotNil(tracked.guards, n)
|
case impliesNotNil(tracked.guards, n)
|
||||||
of impUnknown:
|
of impUnknown:
|
||||||
message(n.info, errGenerated,
|
message(n.info, errGenerated,
|
||||||
"cannot prove '$1' is not nil" % n.renderTree)
|
"cannot prove '$1' is not nil" % n.renderTree)
|
||||||
of impNo:
|
of impNo:
|
||||||
message(n.info, errGenerated, "'$1' is provably nil" % n.renderTree)
|
message(n.info, errGenerated, "'$1' is provably nil" % n.renderTree)
|
||||||
|
|
@ -517,7 +516,7 @@ proc trackOperand(tracked: PEffects, n: PNode, paramType: PType) =
|
||||||
proc breaksBlock(n: PNode): bool =
|
proc breaksBlock(n: PNode): bool =
|
||||||
case n.kind
|
case n.kind
|
||||||
of nkStmtList, nkStmtListExpr:
|
of nkStmtList, nkStmtListExpr:
|
||||||
for c in n:
|
for c in n:
|
||||||
if breaksBlock(c): return true
|
if breaksBlock(c): return true
|
||||||
of nkBreakStmt, nkReturnStmt, nkRaiseStmt:
|
of nkBreakStmt, nkReturnStmt, nkRaiseStmt:
|
||||||
return true
|
return true
|
||||||
|
|
@ -545,7 +544,7 @@ proc trackCase(tracked: PEffects, n: PNode) =
|
||||||
if not breaksBlock(branch.lastSon): inc toCover
|
if not breaksBlock(branch.lastSon): inc toCover
|
||||||
for i in oldState.. <tracked.init.len:
|
for i in oldState.. <tracked.init.len:
|
||||||
addToIntersection(inter, tracked.init[i])
|
addToIntersection(inter, tracked.init[i])
|
||||||
|
|
||||||
let exh = case skipTypes(n.sons[0].typ, abstractVarRange-{tyTypeDesc}).kind
|
let exh = case skipTypes(n.sons[0].typ, abstractVarRange-{tyTypeDesc}).kind
|
||||||
of tyFloat..tyFloat128, tyString:
|
of tyFloat..tyFloat128, tyString:
|
||||||
lastSon(n).kind == nkElse
|
lastSon(n).kind == nkElse
|
||||||
|
|
@ -590,7 +589,7 @@ proc trackIf(tracked: PEffects, n: PNode) =
|
||||||
if count >= toCover: tracked.init.add id
|
if count >= toCover: tracked.init.add id
|
||||||
# else we can't merge as it is not exhaustive
|
# else we can't merge as it is not exhaustive
|
||||||
setLen(tracked.guards, oldFacts)
|
setLen(tracked.guards, oldFacts)
|
||||||
|
|
||||||
proc trackBlock(tracked: PEffects, n: PNode) =
|
proc trackBlock(tracked: PEffects, n: PNode) =
|
||||||
if n.kind in {nkStmtList, nkStmtListExpr}:
|
if n.kind in {nkStmtList, nkStmtListExpr}:
|
||||||
var oldState = -1
|
var oldState = -1
|
||||||
|
|
@ -782,7 +781,7 @@ proc checkMethodEffects*(disp, branch: PSym) =
|
||||||
checkRaisesSpec(tagsSpec, actual.sons[tagEffects],
|
checkRaisesSpec(tagsSpec, actual.sons[tagEffects],
|
||||||
"can have an unlisted effect: ", hints=off, subtypeRelation)
|
"can have an unlisted effect: ", hints=off, subtypeRelation)
|
||||||
if sfThread in disp.flags and notGcSafe(branch.typ):
|
if sfThread in disp.flags and notGcSafe(branch.typ):
|
||||||
localError(branch.info, "base method is GC-safe, but '$1' is not" %
|
localError(branch.info, "base method is GC-safe, but '$1' is not" %
|
||||||
branch.name.s)
|
branch.name.s)
|
||||||
if branch.typ.lockLevel > disp.typ.lockLevel:
|
if branch.typ.lockLevel > disp.typ.lockLevel:
|
||||||
when true:
|
when true:
|
||||||
|
|
@ -814,14 +813,14 @@ proc initEffects(effects: PNode; s: PSym; t: var TEffects) =
|
||||||
newSeq(effects.sons, effectListLen)
|
newSeq(effects.sons, effectListLen)
|
||||||
effects.sons[exceptionEffects] = newNodeI(nkArgList, s.info)
|
effects.sons[exceptionEffects] = newNodeI(nkArgList, s.info)
|
||||||
effects.sons[tagEffects] = newNodeI(nkArgList, s.info)
|
effects.sons[tagEffects] = newNodeI(nkArgList, s.info)
|
||||||
|
|
||||||
t.exc = effects.sons[exceptionEffects]
|
t.exc = effects.sons[exceptionEffects]
|
||||||
t.tags = effects.sons[tagEffects]
|
t.tags = effects.sons[tagEffects]
|
||||||
t.owner = s
|
t.owner = s
|
||||||
t.init = @[]
|
t.init = @[]
|
||||||
t.guards = @[]
|
t.guards = @[]
|
||||||
t.locked = @[]
|
t.locked = @[]
|
||||||
|
|
||||||
proc trackProc*(s: PSym, body: PNode) =
|
proc trackProc*(s: PSym, body: PNode) =
|
||||||
var effects = s.typ.n.sons[0]
|
var effects = s.typ.n.sons[0]
|
||||||
internalAssert effects.kind == nkEffectList
|
internalAssert effects.kind == nkEffectList
|
||||||
|
|
|
||||||
26
tests/parallel/tarray_of_channels.nim
Normal file
26
tests/parallel/tarray_of_channels.nim
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# bug #2257
|
||||||
|
import threadpool
|
||||||
|
|
||||||
|
type StringChannel = TChannel[string]
|
||||||
|
var channels: array[1..3, StringChannel]
|
||||||
|
|
||||||
|
type
|
||||||
|
MyObject[T] = object
|
||||||
|
x: T
|
||||||
|
|
||||||
|
var global: MyObject[string]
|
||||||
|
var globalB: MyObject[float]
|
||||||
|
|
||||||
|
proc consumer(ix : int) {.thread.} =
|
||||||
|
echo channels[ix].recv() ###### not GC-safe: 'channels'
|
||||||
|
echo globalB
|
||||||
|
|
||||||
|
proc main =
|
||||||
|
for ix in 1..3: channels[ix].open()
|
||||||
|
for ix in 1..3: spawn consumer(ix)
|
||||||
|
for ix in 1..3: channels[ix].send("test")
|
||||||
|
sync()
|
||||||
|
for ix in 1..3: channels[ix].close()
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
main()
|
||||||
32
tests/parallel/tgc_unsafe.nim
Normal file
32
tests/parallel/tgc_unsafe.nim
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
discard """
|
||||||
|
errormsg: "'consumer' is not GC-safe"
|
||||||
|
line: 19
|
||||||
|
"""
|
||||||
|
|
||||||
|
# bug #2257
|
||||||
|
import threadpool
|
||||||
|
|
||||||
|
type StringChannel = TChannel[string]
|
||||||
|
var channels: array[1..3, StringChannel]
|
||||||
|
|
||||||
|
type
|
||||||
|
MyObject[T] = object
|
||||||
|
x: T
|
||||||
|
|
||||||
|
var global: MyObject[string]
|
||||||
|
var globalB: MyObject[float]
|
||||||
|
|
||||||
|
proc consumer(ix : int) {.thread.} =
|
||||||
|
echo channels[ix].recv() ###### not GC-safe: 'channels'
|
||||||
|
echo global
|
||||||
|
echo globalB
|
||||||
|
|
||||||
|
proc main =
|
||||||
|
for ix in 1..3: channels[ix].open()
|
||||||
|
for ix in 1..3: spawn consumer(ix)
|
||||||
|
for ix in 1..3: channels[ix].send("test")
|
||||||
|
sync()
|
||||||
|
for ix in 1..3: channels[ix].close()
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
main()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue