From 49961a54dd43e2ab2d97eacdc158ee9fe1ebe04e Mon Sep 17 00:00:00 2001 From: Christian Zietz Date: Fri, 27 Feb 2026 19:16:30 +0100 Subject: [PATCH 1/9] Atomics can't cause exceptions with Microsoft Visual C++ (#25559) The `enforcenoraises` pragma prevents generation of exception checking code for atomic... functions when compiling with Microsoft Visual C++ as backend. Fixes #25445 Without this change, the following test program: ```nim import std/sysatomics var x: ptr uint64 = cast[ptr uint64](uint64(0)) var y: ptr uint64 = cast[ptr uint64](uint64(42)) let z = atomicExchangeN(addr x, y, ATOMIC_ACQ_REL) let a = atomicCompareExchangeN(addr x, addr y, y, true, ATOMIC_ACQ_REL, ATOMIC_ACQ_REL) var v = 42 atomicStoreN(addr v, 43, ATOMIC_ACQ_REL) let w = atomicLoadN(addr v, ATOMIC_ACQ_REL) ``` ... generates this C code when compiling with `--cc:vcc`: ```c N_LIB_PRIVATE N_NIMCALL(void, NimMainModule)(void) { { NU64* T1_; NIM_BOOL T2_; NI T3_; NIM_BOOL* nimErr_; nimfr_("testexcept", "/tmp/testexcept.nim"); nimErr_ = nimErrorFlag(); nimlf_(7, "/tmp/testexcept.nim");T1_ = ((NU64*) 0); T1_ = atomicExchangeN__testexcept_u4((&x__testexcept_u2), y__testexcept_u3, ((int) 4)); if (NIM_UNLIKELY((*nimErr_))) { goto BeforeRet_; } z__testexcept_u32 = T1_; nimln_(9);T2_ = ((NIM_BOOL) 0); T2_ = atomicCompareExchangeN__testexcept_u33((&x__testexcept_u2), (&y__testexcept_u3), y__testexcept_u3, NIM_TRUE, ((int) 4), ((int) 4)); if (NIM_UNLIKELY((*nimErr_))) { goto BeforeRet_; } a__testexcept_u45 = T2_; nimln_(12);atomicStoreN__testexcept_u47(((&v__testexcept_u46)), ((NI) 43)); if (NIM_UNLIKELY((*nimErr_))) { goto BeforeRet_; } nimln_(13);T3_ = ((NI) 0); T3_ = atomicLoadN__testexcept_u53(((&v__testexcept_u46))); if (NIM_UNLIKELY((*nimErr_))) { goto BeforeRet_; } w__testexcept_u59 = T3_; BeforeRet_: ; nimTestErrorFlag(); popFrame(); } } ``` Note the repeated checks for `*nimErr_`. With this PR applied, the checks vanish: ```c N_LIB_PRIVATE N_NIMCALL(void, NimMainModule)(void) { { nimfr_("testexcept", "/tmp/testexcept.nim"); nimlf_(7, "/tmp/testexcept.nim");z__testexcept_u32 = atomicExchangeN__testexcept_u4((&x__testexcept_u2), y__testexcept_u3, ((int) 4)); nimln_(9);a__testexcept_u45 = atomicCompareExchangeN__testexcept_u33((&x__testexcept_u2), (&y__testexcept_u3), y__testexcept_u3, NIM_TRUE, ((int) 4), ((int) 4)); nimln_(12);atomicStoreN__testexcept_u47(((&v__testexcept_u46)), ((NI) 43)); nimln_(13);w__testexcept_u59 = atomicLoadN__testexcept_u53(((&v__testexcept_u46))); nimTestErrorFlag(); popFrame(); } } ``` For reference, with gcc as backend the generated code looks as follows: ```c N_LIB_PRIVATE N_NIMCALL(void, NimMainModule)(void) { { nimfr_("testexcept", "/tmp/testexcept.nim"); nimlf_(7, "/tmp/testexcept.nim");z__testexcept_u9 = __atomic_exchange_n((&x__testexcept_u2), y__testexcept_u3, __ATOMIC_ACQ_REL); nimln_(9);a__testexcept_u18 = __atomic_compare_exchange_n((&x__testexcept_u2), (&y__testexcept_u3), y__testexcept_u3, NIM_TRUE, __ATOMIC_ACQ_REL, __ATOMIC_ACQ_REL); nimln_(12);__atomic_store_n(((&v__testexcept_u19)), ((NI) 43), __ATOMIC_ACQ_REL); nimln_(13);w__testexcept_u29 = __atomic_load_n(((&v__testexcept_u19)), __ATOMIC_ACQ_REL); nimTestErrorFlag(); popFrame(); } } ``` With this PR the program from #25445 yields the correct output `Error: unhandled exception: index 4 not in 0 .. 3 [IndexDefect]` instead of crashing with a SIGSEGV. PS: Unfortunately, I did not find out how to run the tests with MSVC. `./koch tests --cc:vcc` doesn't use MSVC. --- lib/std/sysatomics.nim | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/std/sysatomics.nim b/lib/std/sysatomics.nim index cc6000c206..ac5acf76e6 100644 --- a/lib/std/sysatomics.nim +++ b/lib/std/sysatomics.nim @@ -219,16 +219,16 @@ elif someVcc: elif mem == ATOMIC_ACQ_REL: fence() elif mem == ATOMIC_SEQ_CST: fence() - proc atomicStoreN*[T: AtomType](p: ptr T, val: T, mem: static[AtomMemModel]) = + proc atomicStoreN*[T: AtomType](p: ptr T, val: T, mem: static[AtomMemModel]) {.enforcenoraises.} = barrier(mem) p[] = val - proc atomicLoadN*[T: AtomType](p: ptr T, mem: static[AtomMemModel]): T = + proc atomicLoadN*[T: AtomType](p: ptr T, mem: static[AtomMemModel]): T {.enforcenoraises.} = result = p[] barrier(mem) proc atomicCompareExchangeN*[T: ptr](p, expected: ptr T, desired: T, - weak: bool, success_memmodel: AtomMemModel, failure_memmodel: AtomMemModel): bool = + weak: bool, success_memmodel: AtomMemModel, failure_memmodel: AtomMemModel): bool {.enforcenoraises.} = when sizeof(T) == 8: interlockedCompareExchange64(p, cast[int64](desired), cast[int64](expected[])) == cast[int64](expected[]) @@ -236,7 +236,7 @@ elif someVcc: interlockedCompareExchange32(p, cast[int32](desired), cast[int32](expected[])) == cast[int32](expected[]) - proc atomicExchangeN*[T: ptr](p: ptr T, val: T, mem: AtomMemModel): T = + proc atomicExchangeN*[T: ptr](p: ptr T, val: T, mem: AtomMemModel): T {.enforcenoraises.} = when sizeof(T) == 8: cast[T](interlockedExchange64(p, cast[int64](val))) elif sizeof(T) == 4: From 9b2b286bafc345d77ac195edd2d92c63ddf1f476 Mon Sep 17 00:00:00 2001 From: Raka Hourianto <175479716+hourianto@users.noreply.github.com> Date: Sat, 28 Feb 2026 09:39:16 +0300 Subject: [PATCH 2/9] nre: fix replacement string parser OOB access, numeric refs, and unterminated named refs (#25560) 1. A trailing `$` at the end of a replacement string could read out of bounds via `how[i + 1]`; this now raises `ValueError` instead. 2. Numeric capture parsing used `id += (id * 10) + digit` instead of `id = (id * 10) + digit`, so multi-digit refs were parsed incorrectly (e.g. `$12` resolved as capture 13 instead of 12). 4. Unterminated named replacement syntax (e.g. `${foo)` is now rejected with ValueError instead of being accepted and parsed inconsistently. Found and fixed by GPT 5.3 Codex. --- lib/impure/nre/private/util.nim | 7 ++++++- tests/stdlib/nre/replace.nim | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/impure/nre/private/util.nim b/lib/impure/nre/private/util.nim index ed84207766..e252af80d8 100644 --- a/lib/impure/nre/private/util.nim +++ b/lib/impure/nre/private/util.nim @@ -15,6 +15,9 @@ template formatStr*(howExpr, namegetter, idgetter): untyped = val.add(how[i]) i += 1 else: + if i + 1 >= how.len: + raise newException(ValueError, "Syntax error in format string at " & $i) + if how[i + 1] == '$': val.add('$') i += 2 @@ -27,7 +30,7 @@ template formatStr*(howExpr, namegetter, idgetter): untyped = i += 1 var id {.inject.} = 0 while i < how.len and how[i] in {'0'..'9'}: - id += (id * 10) + (ord(how[i]) - ord('0')) + id = (id * 10) + (ord(how[i]) - ord('0')) i += 1 val.add(idgetter) lastNum = id + 1 @@ -44,6 +47,8 @@ template formatStr*(howExpr, namegetter, idgetter): untyped = while i < how.len and how[i] != '}': name.add(how[i]) i += 1 + if i >= how.len or how[i] != '}': + raise newException(ValueError, "Syntax error in format string at " & $i) i += 1 val.add(namegetter) else: diff --git a/tests/stdlib/nre/replace.nim b/tests/stdlib/nre/replace.nim index 5cf659f213..290892bc4e 100644 --- a/tests/stdlib/nre/replace.nim +++ b/tests/stdlib/nre/replace.nim @@ -14,9 +14,15 @@ block: # replace check("123".replace(re"(\d)(\d)", "$#$#") == "123") check("123".replace(re"(?\d)(\d)", "$foo$#$#") == "1123") check("123".replace(re"(?\d)(\d)", "${foo}$#$#") == "1123") + check("abcdefghijklm".replace(re"(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)(m)", "$12") == "l") block: # replacing missing captures should throw instead of segfaulting expect IndexDefect: discard "ab".replace(re"(a)|(b)", "$1$2") expect IndexDefect: discard "b".replace(re"(a)?(b)", "$1$2") expect KeyError: discard "b".replace(re"(a)?", "${foo}") expect KeyError: discard "b".replace(re"(?a)?", "${foo}") + + block: # malformed replacement syntax should throw instead of OOB crash + expect ValueError: discard "a".replace(re"a", "$") + expect ValueError: discard "a".replace(re"a", "x$") + expect ValueError: discard "a".replace(re"a", "${foo") From c36617c4902ee828c2b919606673d83f7fab2b60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Hovs=C3=A4ter?= Date: Sat, 28 Feb 2026 10:26:44 +0100 Subject: [PATCH 3/9] Fix std/pegs sequence example (#25562) This corrects the example used to describe `std/pegs` sequence notion. It incorrectly used `Z` whereas `C` was expected. --- doc/pegdocs.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/pegdocs.txt b/doc/pegdocs.txt index 0a8fd81878..8b2814ee20 100644 --- a/doc/pegdocs.txt +++ b/doc/pegdocs.txt @@ -20,8 +20,8 @@ notation meaning as they succeed. Indicate success if all succeeded. Otherwise, do not consume any text and indicate failure. The sequence's precedence is higher than that of ordered - choice: ``A B / C`` means ``(A B) / Z`` and - not ``A (B / Z)``. + choice: ``A B / C`` means ``(A B) / C`` and + not ``A (B / C)``. ``(E)`` Grouping: Parenthesis can be used to change operator priority. ``{E}`` Capture: Apply expression `E` and store the substring From a2db2af5b6443bc58a23941c4abad53d4de1eca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Hovs=C3=A4ter?= Date: Sat, 28 Feb 2026 22:50:37 +0100 Subject: [PATCH 4/9] Fix a few typos (#25563) While fixing a few things in the tutorial, I found a few other typos lingering in the `doc/` directory. --------- Co-authored-by: Andreas Rumpf --- doc/manual_experimental.md | 2 +- doc/markdown_rst.md | 4 ++-- doc/nimgrep_cmdline.txt | 2 +- doc/packaging.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/manual_experimental.md b/doc/manual_experimental.md index 81defd70b5..672ab4a99c 100644 --- a/doc/manual_experimental.md +++ b/doc/manual_experimental.md @@ -2127,7 +2127,7 @@ can be used in an `isolate` context: `=destroy`(dest.value) ``` -The `.sendable` pragma itself is an experimenal, unchecked, unsafe annotation. It is +The `.sendable` pragma itself is an experimental, unchecked, unsafe annotation. It is currently only used by `Isolated[T]`. Virtual pragma diff --git a/doc/markdown_rst.md b/doc/markdown_rst.md index c7977f75a7..f8d0012e55 100644 --- a/doc/markdown_rst.md +++ b/doc/markdown_rst.md @@ -276,9 +276,9 @@ This parser has 2 modes for inline markup: 2) Compatibility mode which is RST rules. -.. Note:: in both modes the parser interpretes text between single +.. Note:: in both modes the parser interprets text between single backticks (code) identically: - backslash does not escape; the only exception: ``\`` folowed by ` + backslash does not escape; the only exception: ``\`` followed by ` does escape so that we can always input a single backtick ` in inline code. However that makes impossible to input code with ``\`` at the end in *single* backticks, one must use *double* diff --git a/doc/nimgrep_cmdline.txt b/doc/nimgrep_cmdline.txt index 6f6887bc4e..7088af267e 100644 --- a/doc/nimgrep_cmdline.txt +++ b/doc/nimgrep_cmdline.txt @@ -52,7 +52,7 @@ Options: nimgrep --filenames # In current dir nimgrep --filenames "" DIRECTORY # Note empty pattern "", lists all files in DIRECTORY -* Interprete patterns: +* Interpret patterns: --peg PATTERN and PAT are Peg --re PATTERN and PAT are regular expressions (default) --rex, -x use the "extended" syntax for the regular expression diff --git a/doc/packaging.md b/doc/packaging.md index b742bef282..7ee4aaf102 100644 --- a/doc/packaging.md +++ b/doc/packaging.md @@ -27,7 +27,7 @@ Nim runs on a wide variety of platforms. Support on amd64 and i386 is tested reg - ppc64el (aka ppc64le) - riscv64 -The following platforms are seldomly tested: +The following platforms are rarely tested: - alpha - hppa From 4566ffaca9c383771cd4cb7b4016d04316a0f84b Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Sun, 1 Mar 2026 05:51:38 +0800 Subject: [PATCH 5/9] fixes #25553; Invalid codegen for accessing tuple in array (#25555) fixes #25553 --- compiler/sigmatch.nim | 4 ++-- tests/types/tlent_var.nim | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index d97148baef..7839a1a5cb 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -2186,9 +2186,9 @@ proc implicitConv(kind: TNodeKind, f: PType, arg: PNode, m: TCandidate, result.typ = errorType(c) else: result.typ = f.skipTypes({tySink}) - # keep varness + # keep varness, but don't wrap lent types with var if arg.typ != nil and arg.typ.kind == tyVar: - result.typ = toVar(result.typ, tyVar, c.idgen) + result.typ = toVar(result.typ.skipTypes({tyLent}), tyVar, c.idgen) # copy the tfVarIsPtr flag result.typ.flags = arg.typ.flags else: diff --git a/tests/types/tlent_var.nim b/tests/types/tlent_var.nim index 73b5bef9b4..715567d2d1 100644 --- a/tests/types/tlent_var.nim +++ b/tests/types/tlent_var.nim @@ -23,3 +23,18 @@ proc varProc(x: var int) = doAssert: not compiles(test_lent(x) = 1) doAssert: not compiles(varProc(test_lent(x))) +type X = tuple[a: int, b: int] + +type ArrayBuf*[N: static int, T] = object + buf*: array[N, T] + +var v: ArrayBuf[32, X] + + +# proc `[]`*[N, T](b: var ArrayBuf[N, T], i: BackwardsIndex): lent T = # works +# b.buf[i] + +template `[]`*[N, T](b: var ArrayBuf[N, T], i: BackwardsIndex): lent T = + b.buf[i] + +doAssert $v[^4] == "(a: 0, b: 0)" From bd709f9b4c4911755c7cfb7567ddae71b2f9ac46 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Sun, 1 Mar 2026 06:01:09 +0800 Subject: [PATCH 6/9] fixes #25262; proc v[T: typedesc]() = discard / v[0]() compiles even though 0 isn't a typedesc (#25558) fixes #25262 ```nim if constraint != nil and constraint.kind == tyTypeDesc: n[i].typ = e.typ else: n[i].typ = e.typ.skipTypes({tyTypeDesc}) ``` at least when `constraint` is a typedesc, it should not skip `tyTypeDesc` ```nim if arg.kind != tyTypeDesc: arg = makeTypeDesc(m.c, arg) ``` Wrappers literals into typedesc, which can cause problems. Though, it doesn't seem to be necessary --- compiler/semcall.nim | 2 +- compiler/sigmatch.nim | 3 +-- tests/generics/tpointerprocs.nim | 2 +- tests/typerel/t25262.nim | 13 +++++++++++++ 4 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 tests/typerel/t25262.nim diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 4557ab4c69..29d19875d4 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -981,7 +981,7 @@ proc setGenericParams(c: PContext, n, expectedParams: PNode) = if e.typ == nil: n[i].typ = errorType(c) else: - n[i].typ = e.typ.skipTypes({tyTypeDesc}) + n[i].typ = e.typ proc explicitGenericInstantiation(c: PContext, n: PNode, s: PSym, doError: bool): PNode = assert n.kind == nkBracketExpr diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 7839a1a5cb..2e46d508ae 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -160,8 +160,7 @@ proc matchGenericParam(m: var TCandidate, formal: PType, n: PNode) = arg = newTypeS(tyStatic, m.c, son = evaluated.typ) arg.n = evaluated elif formalBase.kind == tyTypeDesc: - if arg.kind != tyTypeDesc: - arg = makeTypeDesc(m.c, arg) + discard # if arg is not tyTypeDesc, typeRel will report the mismatch else: arg = arg.skipTypes({tyTypeDesc}) let tm = typeRel(m, formal, arg) diff --git a/tests/generics/tpointerprocs.nim b/tests/generics/tpointerprocs.nim index 29c4f2954f..ba99044645 100644 --- a/tests/generics/tpointerprocs.nim +++ b/tests/generics/tpointerprocs.nim @@ -3,7 +3,7 @@ cmd: "nim check $options --hints:off $file" action: "reject" nimout:''' tpointerprocs.nim(22, 11) Error: 'foo' doesn't have a concrete type, due to unspecified generic parameters. -tpointerprocs.nim(34, 14) Error: type mismatch: got +tpointerprocs.nim(34, 14) Error: type mismatch: got but expected one of: proc foo(x: int | float; y: int or string): float first type mismatch at position: 2 in generic parameters diff --git a/tests/typerel/t25262.nim b/tests/typerel/t25262.nim new file mode 100644 index 0000000000..182561dde3 --- /dev/null +++ b/tests/typerel/t25262.nim @@ -0,0 +1,13 @@ +discard """ + errormsg: "type mismatch" + output: ''' +t25262.nim(13, 5) Error: type mismatch: got <> +but expected one of: +proc v[T: typedesc]() + +expression: v[0]() +''' +""" + +proc v[T: typedesc]() = discard +v[0]() \ No newline at end of file From e69d672354f6ee663e93c0ca2c4d02ebc22681ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Hovs=C3=A4ter?= Date: Sun, 1 Mar 2026 04:36:31 +0100 Subject: [PATCH 7/9] Fix warning admonition in `std/streams` (#25564) The rest of the body must be indented in order to fall under the warning admonition. Right now, only the first part of the warning is inside the admonition, see [std/streams](https://nim-lang.org/docs/streams.html). --- lib/pure/streams.nim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pure/streams.nim b/lib/pure/streams.nim index 5eb16a8c17..7d422ff4fe 100644 --- a/lib/pure/streams.nim +++ b/lib/pure/streams.nim @@ -16,9 +16,9 @@ ## stream interface. ## ## .. warning:: Due to the use of `pointer`, the `readData`, `peekData` and -## `writeData` interfaces are not available on the compile-time VM, and must -## be cast from a `ptr string` on the JS backend. However, `readDataStr` is -## available generally in place of `readData`. +## `writeData` interfaces are not available on the compile-time VM, and must +## be cast from a `ptr string` on the JS backend. However, `readDataStr` is +## available generally in place of `readData`. ## ## Basic usage ## =========== From 9ed4077d9a57e19063c1c67710fa6fb83f5a5fb7 Mon Sep 17 00:00:00 2001 From: vercingetorx <40043405+vercingetorx@users.noreply.github.com> Date: Sun, 1 Mar 2026 13:11:18 -0800 Subject: [PATCH 8/9] Fix memory leak in asyncdispatch.withTimeout by clearing losing callbacks (#25567) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit withTimeout currently leaves the “losing” callback installed: - when fut finishes first, timeout callback remains until timer fires, - when timeout fires first, fut callback remains on the wrapped future. Under high-throughput use with large future payloads, this retains closures/future references longer than needed and causes large transient RSS growth. This patch clears the opposite callback immediately once outcome is decided, reducing retention without changing API behavior. --- lib/pure/asyncdispatch.nim | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index 004cc9bcfe..70d94b023e 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -1946,9 +1946,14 @@ proc withTimeout*[T](fut: Future[T], timeout: int): owned(Future[bool]) = retFuture.fail(fut.error) else: retFuture.complete(true) + # Timeout side lost; drop its callback to avoid retaining closures/futures. + timeoutFuture.clearCallbacks() timeoutFuture.callback = proc () = - if not retFuture.finished: retFuture.complete(false) + if not retFuture.finished: + retFuture.complete(false) + # Wrapped future side lost; drop its callback to avoid retaining closures/futures. + fut.clearCallbacks() return retFuture proc accept*(socket: AsyncFD, From 46cddbccd6d41458b6c9656b407a1a2729ee9ddb Mon Sep 17 00:00:00 2001 From: Ryan McConnell Date: Tue, 3 Mar 2026 23:45:16 -0500 Subject: [PATCH 9/9] fixes #25572 ICE evaluating closure iter with object conversion (#25575) --- compiler/closureiters.nim | 2 +- tests/iter/tclosureiter_objupconv_methodawait.nim | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 tests/iter/tclosureiter_objupconv_methodawait.nim diff --git a/compiler/closureiters.nim b/compiler/closureiters.nim index ddf9c2704c..52f0bed2bb 100644 --- a/compiler/closureiters.nim +++ b/compiler/closureiters.nim @@ -727,7 +727,7 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode = n[0] = ex result.add(n) - of nkCast, nkHiddenStdConv, nkHiddenSubConv, nkConv, nkObjDownConv, + of nkCast, nkHiddenStdConv, nkHiddenSubConv, nkConv, nkObjDownConv, nkObjUpConv, nkDerefExpr, nkHiddenDeref: var ns = false for i in ord(n.kind == nkCast)..