From 75f01bd49f9d561af1bc992e0483966b699dc85e 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. (cherry picked from commit 91febf1f4cb5a3ba689c0bfac670d83dff0be657) --- 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: