From 91febf1f4cb5a3ba689c0bfac670d83dff0be657 Mon Sep 17 00:00:00 2001 From: Jacek Sieka Date: Mon, 1 Dec 2025 22:59:26 +0100 Subject: [PATCH] Ensure channels don't leak exception effects (#25318) The forward declarations cause `Exception` to be inferred - also, `llrecv` is an internal implementation detail and the type of the received item is controlled by generics, thus the ValueError raised there seems out of place for the generic api. --- lib/core/typeinfo.nim | 20 ++++++++++---------- lib/system/channels_builtin.nim | 6 +++++- lib/system/gc.nim | 14 +++++++------- lib/system/gc_hooks.nim | 4 ++-- lib/system/gc_ms.nim | 14 +++++++------- lib/system/gc_regions.nim | 22 +++++++++++++--------- lib/system/mm/boehm.nim | 6 ++++-- lib/system/mm/go.nim | 3 +++ lib/system/mm/none.nim | 2 +- lib/system/mmdisp.nim | 4 ++-- lib/system/osalloc.nim | 4 ++++ lib/system/sysstr.nim | 6 ++++-- tests/threads/tmembug.nim | 4 ++-- 13 files changed, 64 insertions(+), 45 deletions(-) diff --git a/lib/core/typeinfo.nim b/lib/core/typeinfo.nim index 5ea776b727..c02d8d731c 100644 --- a/lib/core/typeinfo.nim +++ b/lib/core/typeinfo.nim @@ -118,21 +118,21 @@ when not defined(js): template `rawType=`(x: var Any, p: PNimType) = x.rawTypePtr = cast[pointer](p) -proc genericAssign(dest, src: pointer, mt: PNimType) {.importCompilerProc.} +proc genericAssign(dest, src: pointer, mt: PNimType) {.importCompilerProc, raises: [].} when not defined(gcDestructors): - proc genericShallowAssign(dest, src: pointer, mt: PNimType) {.importCompilerProc.} - proc incrSeq(seq: PGenSeq, elemSize, elemAlign: int): PGenSeq {.importCompilerProc.} - proc newObj(typ: PNimType, size: int): pointer {.importCompilerProc.} - proc newSeq(typ: PNimType, len: int): pointer {.importCompilerProc.} - proc objectInit(dest: pointer, typ: PNimType) {.importCompilerProc.} + proc genericShallowAssign(dest, src: pointer, mt: PNimType) {.importCompilerProc, raises: [].} + proc incrSeq(seq: PGenSeq, elemSize, elemAlign: int): PGenSeq {.importCompilerProc, raises: [].} + proc newObj(typ: PNimType, size: int): pointer {.importCompilerProc, raises: [].} + proc newSeq(typ: PNimType, len: int): pointer {.importCompilerProc, raises: [].} + proc objectInit(dest: pointer, typ: PNimType) {.importCompilerProc, raises: [].} else: - proc nimNewObj(size, align: int): pointer {.importCompilerProc.} - proc newSeqPayload(cap, elemSize, elemAlign: int): pointer {.importCompilerProc.} + proc nimNewObj(size, align: int): pointer {.importCompilerProc, raises: [].} + proc newSeqPayload(cap, elemSize, elemAlign: int): pointer {.importCompilerProc, raises: [].} proc prepareSeqAddUninit(len: int; p: pointer; addlen, elemSize, elemAlign: int): pointer {. - importCompilerProc.} + importCompilerProc, raises: [].} proc zeroNewElements(len: int; p: pointer; addlen, elemSize, elemAlign: int) {. - importCompilerProc.} + importCompilerProc, raises: [].} include system/ptrarith diff --git a/lib/system/channels_builtin.nim b/lib/system/channels_builtin.nim index 80eda56896..799546a16d 100644 --- a/lib/system/channels_builtin.nim +++ b/lib/system/channels_builtin.nim @@ -138,6 +138,8 @@ ## localChannelExample() # "Hello from the main thread!" ## ``` +{.push raises: [], gcsafe.} + when not declared(ThisIsSystem): {.error: "You must not import this module explicitly".} @@ -390,7 +392,7 @@ proc llRecv(q: PRawChannel, res: pointer, typ: PNimType) = q.ready = false if typ != q.elemType: releaseSys(q.lock) - raise newException(ValueError, "cannot receive message of wrong type") + raiseAssert "cannot receive message of wrong type" rawRecv(q, res, typ) if q.maxItems > 0 and q.count == q.maxItems - 1: # Parent thread is awaiting in send. Wake it up. @@ -455,3 +457,5 @@ proc ready*[TMsg](c: var Channel[TMsg]): bool = ## new messages. var q = cast[PRawChannel](addr(c)) result = q.ready + +{.pop.} \ No newline at end of file diff --git a/lib/system/gc.nim b/lib/system/gc.nim index c2fadd0725..3942e5eb7f 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -97,7 +97,7 @@ type waZctDecRef, waPush #, waDebug - Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, benign, raises: [].} + Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, benign, raises: [], gcsafe.} # A ref type can have a finalizer that is called before the object's # storage is freed. @@ -481,17 +481,17 @@ proc rawNewObj(typ: PNimType, size: int, gch: var GcHeap): pointer = {.pop.} # .stackTrace off {.pop.} # .profiler off -proc newObjNoInit(typ: PNimType, size: int): pointer {.compilerRtl.} = +proc newObjNoInit(typ: PNimType, size: int): pointer {.compilerRtl, raises: [].} = result = rawNewObj(typ, size, gch) when defined(memProfiler): nimProfile(size) -proc newObj(typ: PNimType, size: int): pointer {.compilerRtl, noinline.} = +proc newObj(typ: PNimType, size: int): pointer {.compilerRtl, noinline, raises: [].} = result = rawNewObj(typ, size, gch) zeroMem(result, size) when defined(memProfiler): nimProfile(size) {.push overflowChecks: on.} -proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.} = +proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl, raises: [].} = # `newObj` already uses locks, so no need for them here. let size = align(GenericSeqSize, typ.base.align) + len * typ.base.size result = newObj(typ, size) @@ -500,7 +500,7 @@ proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.} = when defined(memProfiler): nimProfile(size) {.pop.} -proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl, noinline.} = +proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl, noinline, raises: [].} = # generates a new object and sets its reference counter to 1 incTypeSize typ, size sysAssert(allocInv(gch.region), "newObjRC1 begin") @@ -528,7 +528,7 @@ proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl, noinline.} = when defined(memProfiler): nimProfile(size) {.push overflowChecks: on.} -proc newSeqRC1(typ: PNimType, len: int): pointer {.compilerRtl.} = +proc newSeqRC1(typ: PNimType, len: int): pointer {.compilerRtl, raises: [].} = let size = align(GenericSeqSize, typ.base.align) + len * typ.base.size result = newObjRC1(typ, size) cast[PGenericSeq](result).len = len @@ -670,7 +670,7 @@ proc doOperation(p: pointer, op: WalkOp) = add(gch.tempStack, c) #of waDebug: debugGraph(c) -proc nimGCvisit(d: pointer, op: int) {.compilerRtl.} = +proc nimGCvisit(d: pointer, op: int) {.compilerRtl, raises: [].} = doOperation(d, WalkOp(op)) proc collectZCT(gch: var GcHeap): bool {.benign, raises: [].} diff --git a/lib/system/gc_hooks.nim b/lib/system/gc_hooks.nim index ace62eea0a..936b31b20a 100644 --- a/lib/system/gc_hooks.nim +++ b/lib/system/gc_hooks.nim @@ -46,8 +46,8 @@ var newObjHook*: proc (typ: PNimType, size: int): pointer {.nimcall, tags: [], raises: [], gcsafe.} traverseObjHook*: proc (p: pointer, op: int) {.nimcall, tags: [], raises: [], gcsafe.} -proc nimGCvisit(p: pointer, op: int) {.inl, compilerRtl.} = +proc nimGCvisit(p: pointer, op: int) {.inl, compilerRtl, raises: [].} = traverseObjHook(p, op) -proc newObj(typ: PNimType, size: int): pointer {.inl, compilerRtl.} = +proc newObj(typ: PNimType, size: int): pointer {.inl, compilerRtl, raises: [].} = result = newObjHook(typ, size) diff --git a/lib/system/gc_ms.nim b/lib/system/gc_ms.nim index 5ea177b3e5..9efca9cbae 100644 --- a/lib/system/gc_ms.nim +++ b/lib/system/gc_ms.nim @@ -36,7 +36,7 @@ type # local waMarkPrecise # fast precise marking - Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, benign, raises: [].} + Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, benign, raises: [], gcsafe.} # A ref type can have a finalizer that is called before the object's # storage is freed. @@ -289,23 +289,23 @@ when useCellIds: {.pop.} -proc newObj(typ: PNimType, size: int): pointer {.compilerRtl.} = +proc newObj(typ: PNimType, size: int): pointer {.compilerRtl, raises: [].} = result = rawNewObj(typ, size, gch) zeroMem(result, size) when defined(memProfiler): nimProfile(size) -proc newObjNoInit(typ: PNimType, size: int): pointer {.compilerRtl.} = +proc newObjNoInit(typ: PNimType, size: int): pointer {.compilerRtl, raises: [].} = result = rawNewObj(typ, size, gch) when defined(memProfiler): nimProfile(size) -proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} = +proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl, raises: [].} = result = rawNewObj(typ, size, gch) zeroMem(result, size) when defined(memProfiler): nimProfile(size) when not defined(nimSeqsV2): {.push overflowChecks: on.} - proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.} = + proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl, raises: [].} = # `newObj` already uses locks, so no need for them here. let size = align(GenericSeqSize, typ.base.align) + len * typ.base.size result = newObj(typ, size) @@ -313,7 +313,7 @@ when not defined(nimSeqsV2): cast[PGenericSeq](result).reserved = len when defined(memProfiler): nimProfile(size) - proc newSeqRC1(typ: PNimType, len: int): pointer {.compilerRtl.} = + proc newSeqRC1(typ: PNimType, len: int): pointer {.compilerRtl, raises: [].} = let size = align(GenericSeqSize, typ.base.align) + len * typ.base.size result = newObj(typ, size) cast[PGenericSeq](result).len = len @@ -346,7 +346,7 @@ when not defined(nimSeqsV2): result = cellToUsr(res) when defined(memProfiler): nimProfile(newsize-oldsize) - proc growObj(old: pointer, newsize: int): pointer {.rtl.} = + proc growObj(old: pointer, newsize: int): pointer {.rtl, raises: [].} = result = growObj(old, newsize, gch) {.push profiler:off.} diff --git a/lib/system/gc_regions.nim b/lib/system/gc_regions.nim index e18eade184..0385e2963d 100644 --- a/lib/system/gc_regions.nim +++ b/lib/system/gc_regions.nim @@ -6,6 +6,8 @@ # distribution, for details about the copyright. # +{.push raises: [], gcsafe.} + # "Stack GC" for embedded devices or ultra performance requirements. import std/private/syslocks @@ -39,7 +41,7 @@ else: # We also support 'finalizers'. type - Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, benign.} + Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, benign, raises: [], gcsafe.} # A ref type can have a finalizer that is called before the object's # storage is freed. @@ -305,26 +307,26 @@ proc rawNewSeq(r: var MemRegion, typ: PNimType, size: int): pointer = res.region = addr(r) result = res +! sizeof(SeqHeader) -proc newObj(typ: PNimType, size: int): pointer {.compilerRtl.} = +proc newObj(typ: PNimType, size: int): pointer {.compilerRtl, raises: [].} = sysAssert typ.kind notin {tySequence, tyString}, "newObj cannot be used to construct seqs" result = rawNewObj(tlRegion, typ, size) zeroMem(result, size) when defined(memProfiler): nimProfile(size) -proc newObjNoInit(typ: PNimType, size: int): pointer {.compilerRtl.} = +proc newObjNoInit(typ: PNimType, size: int): pointer {.compilerRtl, raises: [].} = sysAssert typ.kind notin {tySequence, tyString}, "newObj cannot be used to construct seqs" result = rawNewObj(tlRegion, typ, size) when defined(memProfiler): nimProfile(size) {.push overflowChecks: on.} -proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.} = +proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl, raises: [].} = let size = roundup(align(GenericSeqSize, typ.base.align) + len * typ.base.size, MemAlign) result = rawNewSeq(tlRegion, typ, size) zeroMem(result, size) cast[PGenericSeq](result).len = len cast[PGenericSeq](result).reserved = len -proc newStr(typ: PNimType, len: int; init: bool): pointer {.compilerRtl.} = +proc newStr(typ: PNimType, len: int; init: bool): pointer {.compilerRtl, raises: [].} = let size = roundup(len + GenericSeqSize, MemAlign) result = rawNewSeq(tlRegion, typ, size) if init: zeroMem(result, size) @@ -332,14 +334,14 @@ proc newStr(typ: PNimType, len: int; init: bool): pointer {.compilerRtl.} = cast[PGenericSeq](result).reserved = len {.pop.} -proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} = +proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl, raises: [].} = result = rawNewObj(tlRegion, typ, size) zeroMem(result, size) -proc newSeqRC1(typ: PNimType, len: int): pointer {.compilerRtl.} = +proc newSeqRC1(typ: PNimType, len: int): pointer {.compilerRtl, raises: [].} = result = newSeq(typ, len) -proc growObj(regionUnused: var MemRegion; old: pointer, newsize: int): pointer = +proc growObj(regionUnused: var MemRegion; old: pointer, newsize: int): pointer {.raises: [].} = let sh = cast[ptr SeqHeader](old -! sizeof(SeqHeader)) let typ = sh.typ result = rawNewSeq(sh.region[], typ, @@ -351,7 +353,7 @@ proc growObj(regionUnused: var MemRegion; old: pointer, newsize: int): pointer = copyMem(result, old, oldsize) dealloc(sh.region[], old, roundup(oldsize, MemAlign)) -proc growObj(old: pointer, newsize: int): pointer {.rtl.} = +proc growObj(old: pointer, newsize: int): pointer {.rtl, raises: [].} = result = growObj(tlRegion, old, newsize) proc unsureAsgnRef(dest: PPointer, src: pointer) {.compilerproc, inline.} = @@ -434,3 +436,5 @@ proc nimGC_setStackBottom(theStackBottom: pointer) = discard proc nimGCref(x: pointer) {.compilerproc.} = discard proc nimGCunref(x: pointer) {.compilerproc.} = discard + +{.pop.} diff --git a/lib/system/mm/boehm.nim b/lib/system/mm/boehm.nim index 362d2d470b..1617bfec0e 100644 --- a/lib/system/mm/boehm.nim +++ b/lib/system/mm/boehm.nim @@ -1,4 +1,4 @@ - +{.push raises: [], gcsafe.} proc boehmGCinit {.importc: "GC_init", boehmGC.} @@ -95,7 +95,7 @@ proc initGC() = when hasThreadSupport: boehmGC_allow_register_threads() -proc boehmgc_finalizer(obj: pointer, typedFinalizer: (proc(x: pointer) {.cdecl.})) = +proc boehmgc_finalizer(obj: pointer, typedFinalizer: (proc(x: pointer) {.cdecl, raises: [], gcsafe.})) = typedFinalizer(obj) @@ -138,3 +138,5 @@ proc deallocOsPages(r: var MemRegion) {.inline.} = discard proc deallocOsPages() {.inline.} = discard include "system/cellsets" + +{.pop.} diff --git a/lib/system/mm/go.nim b/lib/system/mm/go.nim index 8f3aeb964c..853364bdb1 100644 --- a/lib/system/mm/go.nim +++ b/lib/system/mm/go.nim @@ -1,3 +1,4 @@ +{.push raises: [], gcsafe.} when defined(windows): const goLib = "libgo.dll" @@ -151,3 +152,5 @@ proc alloc0(r: var MemRegion, size: int): pointer = proc dealloc(r: var MemRegion, p: pointer) = dealloc(p) proc deallocOsPages(r: var MemRegion) {.inline.} = discard proc deallocOsPages() {.inline.} = discard + +{.pop.} diff --git a/lib/system/mm/none.nim b/lib/system/mm/none.nim index 7818a08054..53cea7f503 100644 --- a/lib/system/mm/none.nim +++ b/lib/system/mm/none.nim @@ -20,7 +20,7 @@ proc newObjNoInit(typ: PNimType, size: int): pointer = result = alloc(size) {.push overflowChecks: on.} -proc newSeq(typ: PNimType, len: int): pointer {.compilerproc.} = +proc newSeq(typ: PNimType, len: int): pointer {.compilerproc, raises: [].} = result = newObj(typ, align(GenericSeqSize, typ.align) + len * typ.base.size) cast[PGenericSeq](result).len = len cast[PGenericSeq](result).reserved = len diff --git a/lib/system/mmdisp.nim b/lib/system/mmdisp.nim index de82c0fb47..7fd61e0dc3 100644 --- a/lib/system/mmdisp.nim +++ b/lib/system/mmdisp.nim @@ -60,10 +60,10 @@ elif (defined(nogc) or defined(gcDestructors)) and defined(useMalloc): when defined(nogc): proc GC_getStatistics(): string = "" - proc newObj(typ: PNimType, size: int): pointer {.compilerproc.} = + proc newObj(typ: PNimType, size: int): pointer {.compilerproc, raises: [].} = result = alloc0(size) - proc newSeq(typ: PNimType, len: int): pointer {.compilerproc.} = + proc newSeq(typ: PNimType, len: int): pointer {.compilerproc, raises: [].} = result = newObj(typ, align(GenericSeqSize, typ.align) + len * typ.base.size) cast[PGenericSeq](result).len = len cast[PGenericSeq](result).reserved = len diff --git a/lib/system/osalloc.nim b/lib/system/osalloc.nim index 5509d0070c..5b6a191dfc 100644 --- a/lib/system/osalloc.nim +++ b/lib/system/osalloc.nim @@ -7,6 +7,8 @@ # distribution, for details about the copyright. # +{.push raises: [], gcsafe.} + proc roundup(x, v: int): int {.inline.} = result = (x + (v-1)) and not (v-1) sysAssert(result >= x, "roundup: result < x") @@ -216,3 +218,5 @@ elif hostOS == "standalone" or defined(StandaloneHeapSize): else: {.error: "Port memory manager to your platform".} + +{.pop.} diff --git a/lib/system/sysstr.nim b/lib/system/sysstr.nim index c84cb99b11..9110261ce9 100644 --- a/lib/system/sysstr.nim +++ b/lib/system/sysstr.nim @@ -15,6 +15,7 @@ # we don't use refcounts because that's a behaviour # the programmer may not want +{.push raises: [], gcsafe.} proc dataPointer(a: PGenericSeq, elemAlign: int): pointer = cast[pointer](cast[int](a) +% align(GenericSeqSize, elemAlign)) @@ -103,7 +104,7 @@ proc toNimStr(str: cstring, len: int): NimString {.compilerproc.} = copyMem(addr(result.data), str, len) result.data[len] = '\0' -proc toOwnedCopy(src: NimString): NimString {.inline.} = +proc toOwnedCopy(src: NimString): NimString {.inline, raises: [].} = ## Expects `src` to be not nil and initialized (len and terminating zero set) result = rawNewStringNoInit(src.len) result.len = src.len @@ -149,7 +150,7 @@ proc copyStringRC1(src: NimString): NimString {.compilerRtl.} = if (src.reserved and strlitFlag) != 0: result.reserved = (result.reserved and not strlitFlag) or seqShallowFlag -proc copyDeepString(src: NimString): NimString {.inline.} = +proc copyDeepString(src: NimString): NimString {.inline, raises: [].} = if src != nil: result = toOwnedCopy(src) @@ -358,3 +359,4 @@ func capacity*[T](self: seq[T]): int {.inline.} = let sek = cast[PGenericSeq](self) result = if sek != nil: sek.space else: 0 +{.pop.} diff --git a/tests/threads/tmembug.nim b/tests/threads/tmembug.nim index 3618f0eccb..621a443fe8 100644 --- a/tests/threads/tmembug.nim +++ b/tests/threads/tmembug.nim @@ -12,14 +12,14 @@ var chan1.open() chan2.open() -proc routeMessage*(msg: BackendMessage) = +proc routeMessage*(msg: BackendMessage) {.raises: [], gcsafe.} = # no exceptions! discard chan2.trySend(msg) var recv: Thread[void] stopToken: Atomic[bool] -proc recvMsg() = +proc recvMsg() {.raises: [], gcsafe.} = # no exceptions! while not stopToken.load(moRelaxed): let resp = chan1.tryRecv() if resp.dataAvailable: