Compare commits

..

8 Commits

Author SHA1 Message Date
ringabout
5151e686a7 fixes #26133; Side effects not checked in =destroy 2026-08-25 22:03:36 +08:00
YesDrX
31215b3856 catch Defect in asynchttpserver for bad http request (#25820)
https://github.com/nim-lang/Nim/issues/25819

---------

Co-authored-by: Andreas Rumpf <araq4k@proton.me>
2026-08-23 17:06:44 +02:00
ringabout
2d1412a2ea fixes #26015; Multiple definition error when using codegenDecl regression (#26018)
fixes #26015

Fixes imported global variables with codegenDecl being emitted as
definitions instead of extern declarations.

A variable’s codegenDecl format should customize its definition in the
owning module. Other modules referencing the variable must emit a normal
declaration:

```c
extern NI variable;
```

After the variable-declaration builder refactor, genVarPrototype passed
Extern visibility to addVar. However, the sfCodegenDecl branch returned
before applying that visibility. This caused importing modules to emit
another tentative definition, resulting in duplicate-symbol linker
errors.

The fix restores the previous distinction between the custom definition
and cross-module prototypes. It also adds C and C++ regression coverage
for both direct access and access through an inline procedure.

follows up https://github.com/nim-lang/Nim/pull/24423
2026-08-23 12:37:15 +02:00
Constantine Molchanov
37223d2ea9 Feature: Rest: .. include::: Support :start-after: and :end-before: in :literal: mode (#26130)
With this addition, we can include code samples in the docs using
comments as achors. This is analogous to mdBook's
[shiftinclude](https://github.com/daviddrysdale/mdbook-shiftinclude)
preprocessor, which is used extensively in the Status projects docs,
e.g.:
https://github.com/status-im/nim-chronos/blob/master/docs/src/tutorials/http_client/chapter1.md?plain=1#L16

P.S. One missing piece would be the ability to de-dent the included code
automatically but that's a feature for another PR. This isn't as
critical as the ability to include parts of the code.
2026-08-23 12:36:25 +02:00
SirOlaf
6f1e6fdd06 Specialize rawAlloc for alignment (#26115)
Specialize `rawAlloc` for alignment (cherry-picked from the other PR).
This cuts the frame of the normal unaligned path down enough to regain
the performance lost from loading the cold page in #26110

Also cleans up `MemRegion` a bit, the regressions are either gone or
were measurement errors.
2026-08-23 07:36:10 +02:00
ringabout
f1256ddcf4 fixes #26123; Update PathKinds1 to include nkCast (#26126)
fixes #26123

`cast[T](x)` is a transparent path expression for compiler analysis.
Previously, move/alias analysis could fail to see a later use through a
cast and incorrectly mark the source as moved, causing the issue’s
segmentation fault.


for views,
https://nim-lang.org/docs/manual_experimental.html#view-types-path-expressions:
A cast expression cast[T](e) is a path expression.

It also affects skipConvDfa, isAnalysableFieldAccess, and aliases. And I
might narrow it down for the two cases above mentioned if it causes
problems
2026-08-21 21:57:51 +08:00
SirOlaf
901ca7905a IC: Do not serialize nfHasComment to nif (#26127)
It causes non-deterministic behavior because it's process-local.
2026-08-20 18:11:31 +02:00
Jake Leahy
81325d0745 Add checks to fromJson when trying to convert to an array (#26109)
Issue popped up when using `fromJson` into an array but the JSON passed
is an object

```nim
import std/[jsonutils, json]

let data = parseJson """
{"key": "value"}
"""
var foo: seq[int]
foo.fromJson(data)
echo foo #> @[0]
```
Basically the `setLen` would set the size to be equal to the number of
keys, but `getElems` just returns an empty array if the JSON isn't an
array which lead to it just creating zero'd items in the seq without
letting the user know.

Felt adding the checks was better than just skipping the `setLen` since
it lets the user know that there is a problem with the JSON
2026-08-19 08:24:25 +02:00
17 changed files with 178 additions and 46 deletions

View File

@@ -99,6 +99,7 @@ parameter and result types, not just their source-level shape. Use
works without single-quoting.
- `std/uri`: The `?` operator now appends query parameters to an existing query
string instead of replacing it. Fixes [#19782](https://github.com/nim-lang/Nim/issues/19782).
- `std/jsonutils`: `fromJson` now throws an exception when converting to `array`/`seq` if the JSON isn't an array instead of silently failing
## Language changes

View File

@@ -8,7 +8,7 @@ const
nkBracketExpr, nkDerefExpr, nkHiddenDeref,
nkAddr, nkHiddenAddr,
nkObjDownConv, nkObjUpConv}
PathKinds1* = {nkHiddenStdConv, nkHiddenSubConv}
PathKinds1* = {nkHiddenStdConv, nkHiddenSubConv, nkCast}
proc skipConvDfa*(n: PNode): PNode =
result = n
@@ -125,4 +125,3 @@ proc aliases*(obj, field: PNode): AliasKind =
else:
result = maybe
else: assert false # unreachable

View File

@@ -800,7 +800,11 @@ proc writeSymNode(w: var Writer; dest: var IcBuilder; n: PNode; sym: PSym) =
dest.addSymUse pool.syms.getOrIncl(w.toNifSymName(sym)), info
proc writeNodeFlags(dest: var IcBuilder; flags: set[TNodeFlag]) {.inline.} =
writeFlags(dest, flags)
# Comment text is not stored in NIF; `nfHasComment` is process-local
# (see `comment` in ast.nim). Emitting it made IC non-deterministic:
# `copyTree` from a parsed generic kept the comment (`"sh"`) while
# `copyTree` from a cache-loaded generic did not (`"s"`).
writeFlags(dest, flags - {nfHasComment})
template withNode(w: var Writer; dest: var IcBuilder; n: PNode; body: untyped) =
dest.addParLe pool.tags.getOrIncl(toNifTag(n.kind)), trLineInfo(w, n.info)

View File

@@ -1854,10 +1854,16 @@ proc genVarPrototype(m: BModule, n: PNode) =
typ = ptrType(typ)
if lfDynamicLib in sym.loc.flags:
typ = ptrType(typ)
m.s[cfsVars].addVar(m, sym,
name = sym.loc.snippet,
typ = typ,
visibility = vis)
if sfCodegenDecl in sym.flags:
m.s[cfsVars].addDeclWithVisibility(vis):
m.s[cfsVars].addVar(m, sym,
name = sym.loc.snippet,
typ = typ)
else:
m.s[cfsVars].addVar(m, sym,
name = sym.loc.snippet,
typ = typ,
visibility = vis)
if m.hcrOn:
m.initProc.procSec(cpsLocals).add('\t')
m.initProc.procSec(cpsLocals).addAssignment(sym.loc.snippet,

View File

@@ -454,7 +454,7 @@ proc gen(c: var Con; n: PNode) =
of nkPragmaBlock: gen(c, n.lastSon)
of nkDiscardStmt, nkObjDownConv, nkObjUpConv, nkStringToCString, nkCStringToString:
gen(c, n[0])
of nkConv, nkExprColonExpr, nkExprEqExpr, nkCast, PathKinds1:
of nkConv, nkExprColonExpr, nkExprEqExpr, PathKinds1:
gen(c, n[1])
of nkVarSection, nkLetSection: genVarSection(c, n)
of nkDefer: raiseAssert "dfa construction pass requires the elimination of 'defer'"

View File

@@ -1931,7 +1931,7 @@ proc borrowCheck(c: PContext, n, le, ri: PNode) =
PathKinds0 = {nkDotExpr, nkCheckedFieldExpr,
nkBracketExpr, nkAddr, nkHiddenAddr,
nkObjDownConv, nkObjUpConv}
PathKinds1 = {nkHiddenStdConv, nkHiddenSubConv}
PathKinds1 = {nkHiddenStdConv, nkHiddenSubConv, nkCast}
proc getRoot(n: PNode; followDeref: bool): PNode =
result = n

View File

@@ -129,6 +129,8 @@ proc collectObjectTree(graph: ModuleGraph, n: PNode) =
else:
graph.objectTree[root].add (depthLevel, typ)
proc markSideEffect(a: PEffects; reason: PNode | PSym; useLoc: TLineInfo)
proc createTypeBoundOps(tracked: PEffects, typ: PType; info: TLineInfo; explicit = false) =
if typ == nil or (sfGeneratedOp in tracked.owner.flags and not explicit):
# don't create type bound ops for anything in a function with a `nodestroy` pragma
@@ -147,6 +149,10 @@ proc createTypeBoundOps(tracked: PEffects, typ: PType; info: TLineInfo; explicit
if op != nil and sfNeverRaises notin op.flags:
tracked.canRaiseDefect = true
break
let destructor = getAttachedOp(tracked.graph, typ, attachedDestructor)
if destructor != nil and sfOverridden in destructor.flags and
tfNoSideEffect notin destructor.typ.flags:
markSideEffect(tracked, destructor, info)
if tracked.config.selectedGC == gcRefc or
optSeqDestructors in tracked.config.globalOptions or
tfHasAsgn in typ.flags:

View File

@@ -3319,31 +3319,31 @@ proc dirInclude(p: var RstParser): PRstNode =
rstMessage(p, meCannotOpenFile, filename)
else:
# XXX: error handling; recursive file inclusion!
let inputString = readFile(path)
let startPosition =
block:
let searchFor = n.getFieldValue("start-after").strip()
if searchFor != "":
let pos = inputString.find(searchFor)
if pos != -1: pos + searchFor.len
else: 0
else:
0
let endPosition =
block:
let searchFor = n.getFieldValue("end-before").strip()
if searchFor != "":
let pos = inputString.find(searchFor, start = startPosition)
if pos != -1: pos - 1
else: 0
else:
inputString.len - 1
if getFieldValue(n, "literal") != "":
result = newRstNode(rnLiteralBlock)
result.add newLeaf(readFile(path))
result.add newLeaf(inputString[startPosition..endPosition])
else:
let inputString = readFile(path)
let startPosition =
block:
let searchFor = n.getFieldValue("start-after").strip()
if searchFor != "":
let pos = inputString.find(searchFor)
if pos != -1: pos + searchFor.len
else: 0
else:
0
let endPosition =
block:
let searchFor = n.getFieldValue("end-before").strip()
if searchFor != "":
let pos = inputString.find(searchFor, start = startPosition)
if pos != -1: pos - 1
else: 0
else:
inputString.len - 1
var q: RstParser
initParser(q, p.s)
let saveFileIdx = p.s.currFileIdx

View File

@@ -152,9 +152,11 @@ proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] =
raise newException(ValueError, "Invalid request protocol. Got: " &
protocol)
result.orig = protocol
i.inc protocol.parseSaturatedNatural(result.major, i)
if i < protocol.len: inc i # Skip .
i.inc protocol.parseSaturatedNatural(result.minor, i)
var n = protocol.parseSaturatedNatural(result.major, i)
i.inc n
if i < protocol.len and protocol[i] == '.':
inc i
n = protocol.parseSaturatedNatural(result.minor, i)
proc sendStatus(client: AsyncSocket, status: string): Future[void] =
client.send("HTTP/1.1 " & status & "\c\L\c\L")

View File

@@ -238,6 +238,7 @@ proc fromJson*[T](a: var T, b: JsonNode, opt = Joptions()) =
a = T()
fromJson(a[], b, opt)
elif T is array:
checkJson b.kind == JArray
checkJson a.len == b.len, "Json array size doesn't match for " & $T
var i = 0
for ai in mitems(a):
@@ -248,6 +249,7 @@ proc fromJson*[T](a: var T, b: JsonNode, opt = Joptions()) =
for val in b.getElems:
incl a, jsonTo(val, E)
elif T is seq:
checkJson b.kind == JArray
a.setLen b.len
for i, val in b.getElems:
fromJson(a[i], val, opt)

View File

@@ -155,15 +155,17 @@ type
MemRegion = object
when usesRegionHandles:
# Keeping the handle here does change the layout, but until proven otherwise
# this layout is more readable and shouldn't regress performance.
regionHandle: ptr RegionHandle
when not defined(gcDestructors):
minLargeObj, maxLargeObj: int
freeSmallChunks: array[0..max(1, SmallChunkSize div MemAlign-1), PSmallChunk]
# List of available chunks per size class. Only one is expected to be active per class.
when defined(gcDestructors):
when defined(gcDestructors) and not usesRegionHandles:
sharedFreeLists: SharedFreeLists
# Used directly without threads. Threaded builds use RegionHandle but
# retain this 2 KiB spacer: removing it regresses 2-4 KiB allocations.
# Remote-free buckets live on the MemRegion when there is no
# RegionHandle. Threaded memory managers with handles keep them on the handle instead.
flBitmap: uint32
slBitmap: array[RealFli, uint32]
matrix: array[RealFli, array[MaxSli, PBigChunk]]
@@ -963,13 +965,19 @@ proc bigChunkAlignOffset(alignment: int): int {.inline.} =
else:
result = align(sizeof(BigChunk) + sizeof(FreeCell), alignment) - sizeof(BigChunk) - sizeof(FreeCell)
proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = 0): pointer =
template rawAllocAux(aligned: static bool) {.dirty.} =
when defined(nimTypeNames):
inc(a.allocCounter)
sysAssert(allocInv(a), "rawAlloc: begin")
sysAssert(roundup(65, 8) == 72, "rawAlloc: roundup broken")
var size = roundup(requestedSize, max(MemAlign, alignment))
let alignOff = smallChunkAlignOffset(alignment)
when aligned:
var size = roundup(requestedSize, max(MemAlign, alignment))
let alignOff = smallChunkAlignOffset(alignment)
else:
# Common `alloc` path: no custom alignment. Keep this a separate
# instantiation so clang does not emit `smallChunkAlignOffset(0)`.
var size = (requestedSize + (MemAlign - 1)) and not (MemAlign - 1)
const alignOff = 0
sysAssert(size >= sizeof(FreeCell), "rawAlloc: requested size too small")
sysAssert(size >= requestedSize, "insufficient allocated size!")
#c_fprintf(stdout, "alloc; size: %ld; %ld\n", requestedSize, size)
@@ -986,11 +994,13 @@ proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = 0): pointer
if atomicLoadN(sharedHead, ATOMIC_RELAXED) != nil:
tc.freeList = atomicExchangeN(sharedHead, nil, ATOMIC_ACQUIRE)
else:
tc.freeList = a.sharedFreeLists[s]
a.sharedFreeLists[s] = nil
# If `tc.freeList` isn't nil, `tc` gains capacity. Calculate how
# much it gained and how many foreign cells are included.
compensateCounters(a, tc, size)
let sharedHead = addr a.sharedFreeLists[s]
tc.freeList = sharedHead[]
sharedHead[] = nil
# Empty peeks are the common local case; skip the walk and the
# `free += 0` / `occ -= 0` stores clang would otherwise keep.
if tc.freeList != nil:
compensateCounters(a, tc, size)
# allocate a small block: for small chunks, we use only its next pointer
let s = size div MemAlign
@@ -1071,7 +1081,7 @@ proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = 0): pointer
# For big chunks with custom alignment, allocate extra space.
# Since chunks are page-aligned, the needed padding is a compile-time
# deterministic value rather than a worst-case estimate.
let alignPad = bigChunkAlignOffset(alignment)
let alignPad = when aligned: bigChunkAlignOffset(alignment) else: 0
size = requestedSize + bigChunkOverhead() + alignPad
# allocate a large block
var c = if size >= HugeChunkSize: getHugeChunk(a, size)
@@ -1096,6 +1106,12 @@ proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int = 0): pointer
when defined(heaptrack):
heaptrack_malloc(result, requestedSize)
proc rawAlloc(a: var MemRegion, requestedSize: int): pointer =
rawAllocAux(false)
proc rawAlloc(a: var MemRegion, requestedSize: int, alignment: int): pointer =
rawAllocAux(true)
proc rawAlloc0(a: var MemRegion, requestedSize: int): pointer =
result = rawAlloc(a, requestedSize)
zeroMem(result, requestedSize)

View File

@@ -0,0 +1,4 @@
var codegenDeclGlobal* {.codegenDecl: "$# /* custom declaration */ $#".} = 123
proc readCodegenDeclGlobal*(): int {.inline.} =
codegenDeclGlobal

View File

@@ -0,0 +1,13 @@
discard """
output: '''
123
123
'''
ccodecheck: "'extern NI /* custom declaration */ codegenDeclGlobal'"
targets: "c cpp"
"""
import ./mcodegendeclglobal
echo codegenDeclGlobal
echo readCodegenDeclGlobal()

View File

@@ -0,0 +1,28 @@
discard """
matrix: "--mm:orc"
output: "destroy b"
"""
# bug #26123
type
A = ptr AObj
AObj = object
b: B
B = distinct ptr BObj
BObj = object
a: A
proc `=destroy`(r: var B) =
echo "destroy b"
proc main() =
var a = create(AObj)
var b = B(create(BObj))
a.b = b
cast[ptr BObj](b).a = a
main()

13
tests/effects/t26133.nim Normal file
View File

@@ -0,0 +1,13 @@
discard """
cmd: "nim check --hints:off $file"
errormsg: "'del' can have side effects"
file: "system.nim"
"""
type MyObject = object
proc `=destroy`(v: var MyObject) =
echo "hello"
func remove(v: var seq[MyObject]) =
v.del(0)

View File

@@ -451,6 +451,12 @@ template fn() =
let json = inner.toJson(ToJsonOptions(enumMode: joptEnumSymbol))
doAssert $json == """{"x":"hello","y":"A"}"""
block arrayTypeCheck:
let json = """{"key": "value"}""".parseJson()
var output: seq[int]
doAssertRaises(ValueError):
output.fromJson(json)
block: # bug #21638
type Something = object

View File

@@ -1630,6 +1630,38 @@ And this should **NOT** be visible in `docs.html`
doAssert "<em>Visible</em>" == rstToHtml(input, {roSandboxDisabled}, defaultConfig())
removeFile("other.rst")
test "`:literal:` flag":
"code.nim".writeFile("""
discard
""")
let input = """
.. include:: code.nim
:literal:
"""
check "<pre>discard\n</pre>" == rstToHtml(input, {roSandboxDisabled}, defaultConfig())
removeFile("code.nim")
test "Include everything between in `:literal:` mode":
"code.nim".writeFile("""
proc notIncluded = discard
#CodeStart
proc included = discard
#CodeEnd
proc notIncluded = discard
""")
let input = """
.. include:: code.nim
:literal:
:start-after: #CodeStart
:end-before: #CodeEnd
"""
check "<pre>\nproc included = discard\n</pre>" == rstToHtml(input, {roSandboxDisabled}, defaultConfig())
removeFile("code.nim")
suite "RST escaping":
test "backspaces":
check("""\ this""".toAst == dedent"""