I have been writing some helper macros to assist in the coding of destructors,
and I ran into the following problem.
import macros
macro dumpCompiledCode(compiledCode: typed): untyped =
## Simple macro to dump the final generated source code form of the
argument,
## after all nested macros have been called, template code has been
inserted, etc.
echo "\n#### final generated code:"
echo repr(compiledCode)
# Return what was passed in so that compilation can continue
result = compiledCode
macro refObjectDestructor(typeName: typed, bodyCode: untyped): untyped =
## Macro to construct a destructor proc definition for a ref object type
## The generated proc definition looks something like:
## proc `=destroy`(x: typeof(<typeName>()[])) =
## <bodyCode>
assert typeName.kind == nnkSym
assert typeName.symKind == nskType
assert bodyCode.kind == nnkStmtList
# destructor proc name definition ("`=destroy`")
var procNameNode = newNimNode(nnkAccQuoted)
procNameNode.add(newIdentNode("="))
procNameNode.add(newIdentNode("destroy"))
# destructor proc return type & parameters
var paramNodes = newSeq[NimNode]()
# Return type - none
let returnTypeNode = newEmptyNode()
paramNodes.add(returnTypeNode)
# First (and only) parameter definition
var param1Node = newNimNode(nnkIdentDefs)
param1Node.add(newIdentNode("x")) # parameter name
# Need to build the AST for the parameter type ("typeof <typeName>()[]")
# Make an instance of the ref object: "<typeName>()"
let refInstantiationNode = newCall(newIdentNode(typeName.strval))
# Dereference the ref instance to get the object: "<typeName>()[]"
var derefedObjectNode = newNimnode(nnkBracketExpr)
derefedObjectNode.add(refInstantiationNode)
# Get the (anonymous) type of the object: "typeof(<typeName>()[])"
let objectTypeNode = newCall("typeof", derefedObjectNode)
# Now add the type to the parameter definition
param1Node.add(objectTypeNode) # parameter type
param1Node.add(newEmptyNode())
# Add the parameter definition to the proc parameters node
paramNodes.add(param1Node)
# Finally, generate the proc definition
result = newProc(procNameNode, paramNodes, bodyCode)
when isMainModule:
type
TestRef = ref object of RootRef
name: string
Marker = ref object
tref: TestRef
proc `=destroy`(x: typeof(TestRef()[])) =
`=destroy`(x.name)
# The compiler crashes after the final generated code has been printed
# If *either* of the noted changes is made, then compilation succeeds
dumpCompiledCode: refObjectDestructor(Marker): # (1) Compiles okay if
"dumpCompiledCode: " is removed
`=destroy`(x.tref) # (2) Compiles okay if a defererence is added to
the argument, i.e. "x.tref[]"
static: echo "##### Done"
Run
When I compile the above code the compiler crashes - _after_ the
`dumpCompiledCode` macro has printed the generated code (which looks okay). The
output is:
Hint: used config file '/opt/nim-2.0.4/config/nim.cfg' [Conf]
Hint: used config file '/opt/nim-2.0.4/config/config.nims' [Conf]
........................................................................
#### final generated code:
proc `=destroy`(x: typeof(Marker()[])) {.raises: [].} =
`=destroy`(x.tref)
SIGSEGV: Illegal storage access. (Attempt to read from nil?)
Segmentation fault (core dumped)
Run
Applying code change (1) above causes the compilation to succeed, but then of
course I don't get to see the generated code.
Applying code change (2) also causes the compilation to succeed, and the
generated code is printed. But I strongly suspect that the object's reference
count would not be decremented by the destructor call - negating the whole
point of the call.
If the the field `x.tref` is replaced by a non-ref entity (`object`, `string`,
`seq`, even a `seq` of `ref`) this issue does not occur.
So what's going on? And how do I get past this?