Skip to content

Commit a1c82c3

Browse files
asyncdispatch+stackTraceOverride: fix premature collection (nim-lang#18039) [backport:1.2]
Copying StackTraceEntry instances when nimStackTraceOverride is defined breaks the link between a cstring field that's supposed to point at another string field in the same object. Sometimes, the original object is garbage collected, that memory region reused for storing other strings, so when the StackTraceEntry copy tries to use its cstring pointer to construct a traceback message, it accesses unrelated strings. This only happens for async tracebacks and this patch prevents that by making sure we only use the string fields when nimStackTraceOverride is defined. Async tracebacks also beautified slightly by getting rid of an extra line that was supposed to be commented out, along with the corresponding debugging output. There's also a micro-optimisation to avoid concatenating two strings just to get their combined length.
1 parent 7052503 commit a1c82c3

File tree

4 files changed

+36
-17
lines changed

4 files changed

+36
-17
lines changed

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@
316316

317317
- Added `copyWithin` [for `seq` and `array` for JavaScript targets](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin).
318318

319+
- Fixed premature garbage collection in asyncdispatch, when a stack trace override is in place.
319320

320321
## Language changes
321322

lib/pure/asyncfutures.nim

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -278,19 +278,33 @@ proc `callback=`*[T](future: Future[T],
278278
## If future has already completed then `cb` will be called immediately.
279279
future.callback = proc () = cb(future)
280280

281+
template getFilenameProcname(entry: StackTraceEntry): (string, string) =
282+
when compiles(entry.filenameStr) and compiles(entry.procnameStr):
283+
# We can't rely on "entry.filename" and "entry.procname" still being valid
284+
# cstring pointers, because the "string.data" buffers they pointed to might
285+
# be already garbage collected (this entry being a non-shallow copy,
286+
# "entry.filename" no longer points to "entry.filenameStr.data", but to the
287+
# buffer of the original object).
288+
(entry.filenameStr, entry.procnameStr)
289+
else:
290+
($entry.filename, $entry.procname)
291+
281292
proc getHint(entry: StackTraceEntry): string =
282293
## We try to provide some hints about stack trace entries that the user
283294
## may not be familiar with, in particular calls inside the stdlib.
295+
296+
let (filename, procname) = getFilenameProcname(entry)
297+
284298
result = ""
285-
if entry.procname == cstring"processPendingCallbacks":
286-
if cmpIgnoreStyle(entry.filename, "asyncdispatch.nim") == 0:
299+
if procname == "processPendingCallbacks":
300+
if cmpIgnoreStyle(filename, "asyncdispatch.nim") == 0:
287301
return "Executes pending callbacks"
288-
elif entry.procname == cstring"poll":
289-
if cmpIgnoreStyle(entry.filename, "asyncdispatch.nim") == 0:
302+
elif procname == "poll":
303+
if cmpIgnoreStyle(filename, "asyncdispatch.nim") == 0:
290304
return "Processes asynchronous completion events"
291305

292-
if entry.procname.endsWith(NimAsyncContinueSuffix):
293-
if cmpIgnoreStyle(entry.filename, "asyncmacro.nim") == 0:
306+
if procname.endsWith(NimAsyncContinueSuffix):
307+
if cmpIgnoreStyle(filename, "asyncmacro.nim") == 0:
294308
return "Resumes an async procedure"
295309

296310
proc `$`*(stackTraceEntries: seq[StackTraceEntry]): string =
@@ -303,16 +317,20 @@ proc `$`*(stackTraceEntries: seq[StackTraceEntry]): string =
303317
# Find longest filename & line number combo for alignment purposes.
304318
var longestLeft = 0
305319
for entry in entries:
306-
if entry.procname.isNil: continue
320+
let (filename, procname) = getFilenameProcname(entry)
321+
322+
if procname == "": continue
307323

308-
let left = $entry.filename & $entry.line
309-
if left.len > longestLeft:
310-
longestLeft = left.len
324+
let leftLen = filename.len + len($entry.line)
325+
if leftLen > longestLeft:
326+
longestLeft = leftLen
311327

312328
var indent = 2
313329
# Format the entries.
314330
for entry in entries:
315-
if entry.procname.isNil:
331+
let (filename, procname) = getFilenameProcname(entry)
332+
333+
if procname == "":
316334
if entry.line == reraisedFromBegin:
317335
result.add(spaces(indent) & "#[\n")
318336
indent.inc(2)
@@ -321,11 +339,11 @@ proc `$`*(stackTraceEntries: seq[StackTraceEntry]): string =
321339
result.add(spaces(indent) & "]#\n")
322340
continue
323341

324-
let left = "$#($#)" % [$entry.filename, $entry.line]
342+
let left = "$#($#)" % [filename, $entry.line]
325343
result.add((spaces(indent) & "$#$# $#\n") % [
326344
left,
327345
spaces(longestLeft - left.len + 2),
328-
$entry.procname
346+
procname
329347
])
330348
let hint = getHint(entry)
331349
if hint.len > 0:
@@ -349,9 +367,9 @@ proc injectStacktrace[T](future: Future[T]) =
349367
newMsg.add($entries)
350368

351369
newMsg.add("Exception message: " & exceptionMsg & "\n")
352-
newMsg.add("Exception type:")
353370

354371
# # For debugging purposes
372+
# newMsg.add("Exception type:")
355373
# for entry in getStackTraceEntries(future.error):
356374
# newMsg.add "\n" & $entry
357375
future.error.msg = newMsg

lib/system/exceptions.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ type
2929
programCounter*: uint ## Program counter - will be used to get the rest of the info,
3030
## when `$` is called on this type. We can't use
3131
## "cuintptr_t" in here.
32-
procnameStr*, filenameStr*: string ## GC-ed objects holding the cstrings in "procname" and "filename"
32+
procnameStr*, filenameStr*: string ## GC-ed alternatives to "procname" and "filename"
3333

3434
Exception* {.compilerproc, magic: "Exception".} = object of RootObj ## \
3535
## Base exception class.

tests/async/tasync_traceback.nim

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ Async traceback:
8686
asyncfutures\.nim\(\d+?\)\s+?read
8787
\]#
8888
Exception message: b failure
89-
Exception type:
89+
9090
9191
bar failure
9292
Async traceback:
@@ -114,7 +114,7 @@ Async traceback:
114114
asyncfutures\.nim\(\d+?\)\s+?read
115115
\]#
116116
Exception message: bar failure
117-
Exception type:
117+
118118
"""
119119

120120
# TODO: is asyncmacro good enough location for fooIter traceback/debugging? just put the callsite info for all?

0 commit comments

Comments
 (0)