Question

I'm not exactly sure how to explain this, so please ask me to clarify anything that doesn't make sense. I have an interface and a template function which returns functions which return anonymous inner classes based on the compile time argument:

interface MyInterface {
    void getName();
}
MyInterface function() getMyInterfaceFactory(string name)() {
    return function() {
        return new class MyInterface {
            void getName() { //Do something involving name here }
        };
    };
}

Now, getMyInterfaceFactory() used to be getMyInterface() and it used to return the anonymous object directly. Everything worked fine. When I added the the factory functions, I started getting an exception during startup from Object:

object.Exception.....(102): need opCmp for class mymodule.getMyInterfaceFactory!("someargument").getMyInterfaceFactory.__funcliteral14.__anonclass13

So, I looked at the throwing line in the druntime source, and it looks like the default implementation of opCmp for Object just throws. I am not comparing factory functions or MyInterfaces anywhere. I am storing factories as the values of a string indexed associative array, but opCmp was not required when I was storing the anonymous classes directly in that array, only when I started storing the functions. If I insert an opCmp (using memory address), everything seems to work normally, but MyInterface is not really comparable so I'd prefer not to do that unless I have to. If possible, I'd like to know why/where opCmp is being invoked on the anonymous classes, and how I can prevent or work around it.

Note: The default implementation of opCmp in Object includes a comment vaguely referencing a bug, a commented out memory address comparison, and then the throwing version.

Thanks!

Edit: I should mention, I tried both windbg and ddbg to track down exactly where opCmp was being called, but failed in both cases. Windbg gave no useful information, because it stubbornly refused to load any symbols, ddbg loaded symbols, but the exception occurs during initialization (after static module constructors but before main) and presumably ddbg didn't have access to the druntime symbols?

Was it helpful?

Solution

Update: I'm having trouble reproducing the opCmp error specifically in toy examples, but I think I have figured out what is happening.
It seems that creating anonymous inner classes which inherit interfaces inside of anonymous functions is buggy (go figure). Specifically, the anonymous classes and are not well behaved with respect to virtual functions. Even with opCmp defined, I've had errors with toString and the default constructors, and had members that simply do nothing (but don't throw or error when called). __traits(allMembers, MyInterface) returns the expected information, as does __traits(allMembers, typeof(anonInstance)) but calling the members listed frequently does not work. Weird.
But, if I change the interface to a class with abstract methods, the opCmp error is resolved, the anonymous class behaves as expected, etc. I don't know much about compilers, but I think that during compilation a symbol table is built which maps virtual function names to the memory addresses stored in vtbl. I think what is happening is that the map generated varies when returning an anonymous class derived from an interface. This is possible because interfaces support multiple inheritance, and so can't prescribe an absolute vtbl mapping. Classes, however, could require that all inheritors stick to the same mapping scheme (I don't know if they do, but they could) and so the anonymous classes can't end up with a different mapping.
Again, I'm really not certain, but it seems to fit the symptom, opCmp being called even though I haven't used it anywhere. I don't think it is specifically opCmp that was the problem, I think all the virtual functions defined in Object are vulnerable. I was able to support this with the following:

testopcmphelper.d
interface TestInterface {
    string helloWorld();
}
class TestClass {
    abstract string helloWorld();
}

testopcmp.d
import testopcmphelper;
import std.stdio;

void invokeFn(TestInterface function() f) {
    auto t = f();
    auto s = t.helloWorld();
    writeln(s);
}

unittest {
    auto f = function() {
        return new class TestInterface {
            string helloWorld() {
                return "Hello World!";
            }
        };
    };
    invokeFn(f);
}

void invokeFn(TestClass function() f) {
    auto t = f();
    auto s = t.helloWorld();
    writeln(s);
}

unittest {
    auto f = function() {
        return new class TestClass {
            string helloWorld() {
                return "Goodbye World!";
            }
        };
    };
    invokeFn(f);
}

Which prints:

src.utilities.testopcmp.__unittest2.__funcliteral1.__anonclass10
Goodbye World!

Indicating that invokeFn(TestInterface) is calling Object.toString instead of TestInterface.helloWorld.

I am going to leave the question open for another day, in case I've made a mistake. I will probably then report this as a bug in DMD. I will work around the problem by using only abstract classes for anonymous factory function base types. TL;DR Seems to be a bug.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top