Thread overview
dlang bug - accessing module variable from method segfaults only when using module reference directly
Jul 01, 2022
Chris Katko
Jul 01, 2022
Chris Katko
Jul 01, 2022
Chris Katko
Jul 01, 2022
Mike Parker
Jul 01, 2022
Mike Parker
Jul 01, 2022
Adam D Ruppe
Jul 01, 2022
Chris Katko
Jul 01, 2022
Chris Katko
Jul 01, 2022
Mike Parker
July 01, 2022

dmd (but I think LDC also is affected)

this bug has bit me multiple times now, to the point I can recognize it. Accessing module variables, from inside a method, causes a segfault. Even if the variable should be available by then through the call order. Proving that its a bug, you can directly send the exact same variable through an argument, and it works fine.

(not sure if this code will do it, last time I tried to replicate it with solely this kind of code, the bug disappeared.)


/// module g.d
class world
{
atlasHandler atlas;

void do()
	{
	atlas = new AtlasHanlder();
	elf e = new elf(atlas);
	}
}

/// module 'objects.d'
class atlasHandler{}

class elf
{
this(atlasHandler atlas)
	{
	assert(atlas !is null); //works fine
	assert(g.world.atlas !is null); //crashes

	writefln("atlas [%p] vs g.world.atlas [%s]", atlas, g.world.atlas);
	// crashes trying to read g.world.atlas
	}
}

gdb output (not the exact same module names but you get the point):

Thread 1 "main" received signal SIGSEGV, Segmentation fault.

(gdb) p atlas
$1 = (objects.atlasHandler *)
(gdb) p g.world
$2 = (worldmod.world_t *)
(gdb) p g.world.atlas
Cannot access memory at address 0x10
(gdb) p this
$3 = (objects.elf *)
(gdb) x atlas
0x7fffec1cf380:	0x55826580
(gdb) x g.world.atlas
Cannot access memory at address 0x10

It appears that whatever value its sending, is in a protected memory segment and automatically segfaulting even upon reading.

Worst case I can public my repo and you can see it for yourself.

July 01, 2022

To add, I cannot even access g.world from inside elf's constructor.

... which is the function that called it.

Thread 1 "main" received signal SIGSEGV, Segmentation fault.
objects.elf.this(g.pair, objects.atlasHandler) (this=, atlas=, _pos=...) at ./src/objects.d:320

(gdb) bt
#0  objects.elf.this(g.pair, objects.atlasHandler) (this=, atlas=, _pos=...) at ./src/objects.d:320
#1  worldmod.world_t.this() (this=) at ./src/worldmod.d:60
#2  main.initialize() () at ./src/main.d:110
#3  main.main(immutable(char)[][]).__lambda6() (__capture=) at ./src/main.d:462
#4  allegro5.system.al_run_allegro(scope int() delegate).main_runner(int, char**) ()
#5  allegro5.system.al_run_allegro(scope int() delegate) ()
#6  D main (args=...) at ./src/main.d:461

(gdb) x g.world
0x0:	Cannot access memory at address 0x0

July 01, 2022

Forgot the last line. That's important because world MUST exist by time elf is called... because world... created and called elf.

So it's not a memory issue, but some sort of linkage issue.

July 01, 2022
On Friday, 1 July 2022 at 12:57:01 UTC, Chris Katko wrote:
> Cannot access memory at address 0x10

Looks like an ordinary null pointer. How did you create the variable?
July 01, 2022

On Friday, 1 July 2022 at 13:01:30 UTC, Chris Katko wrote:

>

Forgot the last line. That's important because world MUST exist by time elf is called... because world... created and called elf.

So it's not a memory issue, but some sort of linkage issue.

world is null because the constructor didn't complete. The segfault happens inside its constructor.

And that also looks like the source of your original segfault. You've got a circular reference going on in the constructors. In other words, you're constructing a global world instance, which in turn constructs an elf instance, which in turn accesses the global world reference whose constructor hasn't yet completed, so the global world reference is still null.

If the objects world is constructing absolutely need to access it, then you could:

  1. Initialize world with a do-nothing destructor, then call a setup method on it to do what its constructor currently is doing;
  2. Pass this along to all the constructors that need it from inside the world constructor.
July 01, 2022

On Friday, 1 July 2022 at 13:20:15 UTC, Mike Parker wrote:
r.

>

And that also looks like the source of your original segfault. You've got a circular reference going on in the constructors. In other words, you're constructing a global world instance, which in turn constructs an elf instance, which in turn accesses the global world reference whose constructor hasn't yet completed, so the global world reference is still null.

Here's what it looks like in code:

import std.stdio : writeln;

class Foo {
    Bar b;
    this() { b = new Bar; }
    void sayMyName() { writeln("I am Foo."); }
}

class Bar {
    this() { f.sayMyName(); }
}

Foo f;

void main()
{
    f = new Foo;
}
July 01, 2022

On Friday, 1 July 2022 at 13:12:05 UTC, Adam D Ruppe wrote:

>

On Friday, 1 July 2022 at 12:57:01 UTC, Chris Katko wrote:

>

Cannot access memory at address 0x10

Looks like an ordinary null pointer. How did you create the variable?

bool initialize() //called from main
	{
	g.world = new world_t;
	}

class atlasHandler{}
class animation
	{
	this(int _numFrames, ipair[] coordinates, atlasHandler atlas)
		{
		}
	}

class world_t
	{	
	atlasHandler atlas;

	this()
		{
		viewTest();
		atlas = new atlasHandler();

		units ~= new elf(pair(200, 200), atlas); //crashes
		}
	logic()
		{
		// doesn't crash
		units ~= new elf(pair(200, 200), atlas);
		}

	}

class elf : unit
	{
	this(pair _pos, atlasHandler atlas)
		{		
		super(0, _pos, pair(0, 0), g.dude_bmp);
//		anim = new animation(1, elf_coords, atlas); //doesn't crash
		anim = new animation(1, elf_coords, g.world.atlas); //CRASH here
		isTreeWalker = true;
		}
	}

also important. it seems to only occur in the constructor. If I create an elf after the world constructor, it's fine.

...wait, does "world" not 'exist' until after the constructor finishes? Is that's what's going on? But then why does it 'exist' when I send it directly? Is it only "registered" with the module once this() finishes or something like that?

July 01, 2022

On Friday, 1 July 2022 at 13:28:26 UTC, Chris Katko wrote:

>

...wait, does "world" not 'exist' until after the constructor finishes? Is that's what's going on? But then why does it 'exist' when I send it directly? Is it only "registered" with the module once this() finishes or something like that?

Yep, that's it.

moving all code in world.this() to world.initialize() and immediately calling initialize, works fine.

g.world = new g.world_t; // code would crash here
g.world.initialize();  // doesn't crash if moved here

class world
{
this(){}
void initialize(){/*...*/}
}

class elf : unit
	{
	this(pair _pos, atlasHandler atlas/*not used*/)
		{		
		super(0, _pos, pair(0, 0), g.dude_bmp);
		anim = new animation(1, elf_coords, g.world.atlas); //not crashing now
		}
	}

It appears module access to a class is broken until the constructor finishes.

July 01, 2022

On Friday, 1 July 2022 at 13:44:20 UTC, Chris Katko wrote:

>

It appears module access to a class is broken until the constructor finishes.

No, it has nothing to do with the module. It's the reference itself.

Until the constructor returns, the reference through which you're constructing the instance is null. It doesn't matter if it's at module scope, function scope, or wherever. If the constructor fails to complete (segfault, thrown exception, assertion failure, etc.), then the reference remains null.

The reference is not the instance. It's a pointer to the instance. The instance is valid when the constructor is called, because the this reference has to be valid. Think of it in terms of a normal function call:

T newT() {
    T t = allocT();
    t.construct(t);
    return t;
}

T g = newT();

If t.construct throws or crashes, then return t is never executed, and g is never initialized.