Nim is a statically typed compiled systems programming language. It combines successful concepts from mature languages like Python, Ada and Modula. Its design focuses on efficiency, expressiveness, and elegance (in that order of priority).
Here is an absolute minimal example I was able to come up with:
import std/os
import std/options
import std/streams
type
Node = ref object
parent: Option[Node]
children: seq[Node]
type
Stream = object
fstream: StringStream
parent: Node
let inputData = "\x41\x41\x41\x41\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x42\x42\x42\x42\x00\x00\x00\x00\x43\x43\x43\x43\x0A\x00\x00\x00\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x44\x44\x44\x44\x00\x00\x00\x01\x00\x00"
proc newStream(): Stream =
var root = Node(parent: none(Node), children: @[])
# we can use FileStream here with the same result
Stream(fstream: newStringStream(inputData), parent: root)
# Note a global variable here.
var globalStream: Stream = newStream()
proc section(body: proc() {.closure.}) =
let parentParent = globalStream.parent
var newParent = Node(parent: some(globalStream.parent), children: @[])
globalStream.parent.children.add(newParent)
globalStream.parent = newParent
body()
globalStream.parent = parentParent
proc appendChildNode() =
let node = Node(parent: some(globalStream.parent), children: @[])
globalStream.parent.children.add(node)
proc readUInt32(): int {.discardable.} =
let n = globalStream.fstream.readUInt32()
appendChildNode()
int(n)
proc readString(length: int): string {.discardable.} =
let s = globalStream.fstream.readStr(length)
appendChildNode()
s
proc readString2(): string {.discardable.} =
var value = ""
section() do ():
let _ = readUInt32() # always 1
value = readString(0)
globalStream.fstream.setPosition(globalStream.fstream.getPosition() + 2) # skip 2 bytes
value
proc readString3(): string {.discardable.} =
let length = readUInt32()
if length == 0:
readString(4)
else:
readString(length)
proc parseFile() =
for i in 0..<2:
section() do ():
let tag = readString(4)
case tag:
of "BBBB":
readString2()
readString3()
of "AAAA":
readString2()
readString3()
readString3()
readString3()
of "CCCC":
discard readUInt32()
of "DDDD":
readString2()
else:
raise newException(ValueError, "invalid tag")
echo(commandLineParams()[0]) # required for some reason?!
parseFile()
And run via nimble run -- qwe. The actual value of the first CLI argument doesn't matter, but it has to be present for some reason.
The reason I call it "memory corruption" is that the code crashes in random places. Any changes to the code lead to a crash in a different place or no crash at all.
For example, by skipping commandLineParams() call we can avoid a crash. The same by moving of "AAAA": case to on top of of "BBBB":.
It's all very random.
The code doesn't make much sense, but it should not matter. It's just a trimmed down version of a real code.
Nim Version
Nim Compiler Version 2.0.8 [MacOSX: arm64]
Compiled at 2024-07-03
Copyright (c) 2006-2023 by Andreas Rumpf
active boot switches: -d:release -d:nimUseLinenoise
Current Output
> nimble run -- qwe
Verifying dependencies for test_app@0.1.0
Building test_app/test_app using c backend
ld: warning: ignoring duplicate libraries: '-lm'
qwe
Traceback (most recent call last)
/Users/_/alloc-bug/src/test_app.nim(86) test_app
/Users/_/alloc-bug/src/test_app.nim(67) parseFile
/Users/_/alloc-bug/src/test_app.nim(31) section
/Users/_/alloc-bug/src/test_app.nim(81) :anonymous
/Users/_/alloc-bug/src/test_app.nim(51) readString2
/Users/_/alloc-bug/src/test_app.nim(31) section
/Users/_/alloc-bug/src/test_app.nim(53) :anonymous
/Users/_/alloc-bug/src/test_app.nim(46) readString
/Users/_/alloc-bug/src/test_app.nim(37) appendChildNode
/opt/homebrew/Cellar/nim/2.0.8/nim/lib/system/alloc.nim(1070) realloc
/opt/homebrew/Cellar/nim/2.0.8/nim/lib/system/alloc.nim(1049) alloc
/opt/homebrew/Cellar/nim/2.0.8/nim/lib/system/alloc.nim(861) rawAlloc
SIGSEGV: Illegal storage access. (Attempt to read from nil?)
Description
Here is an absolute minimal example I was able to come up with:
And run via
nimble run -- qwe
. The actual value of the first CLI argument doesn't matter, but it has to be present for some reason.The reason I call it "memory corruption" is that the code crashes in random places. Any changes to the code lead to a crash in a different place or no crash at all. For example, by skipping
commandLineParams()
call we can avoid a crash. The same by movingof "AAAA":
case to on top ofof "BBBB":
. It's all very random.The code doesn't make much sense, but it should not matter. It's just a trimmed down version of a real code.
Nim Version
Current Output