Objective-C++ confuses two private classes with the same name

I think I found a bug in the Objective-C++ compiler, linker, or runtime. Here’s the scenario:

  • We have a macOS app written in Swift.
  • To control hardware from a 3rd party manufacturer, we use a couple SDKs provided by the manufacturer.
  • The SDKs use dynamically loaded libraries and the interface is defined in C++ headers.
  • To bridge between our Swift code and the C++ APIs we have a private Cocoapod that wraps the 3rd party interface with Objective-C++ classes.

The two SDKs each provide an interface for discovering attached devices using a callback class that the programmer provides. By accident we named both callback implementations DiscoveryCallback, but this was not a compiler error because neither class was publicly declared, and each was defined in the .mm file where it was used.

However, the problem we’re seeing is this:

  1. We want to discover Videohub devices, so we register a new instance of DiscoveryCallback (defined in the same .mm file as this code) with the Videohub SDK.
  2. A Videohub device is connected and the SDK calls a method on our callback.
  3. Surprise! The callback we registered in step 1 was actually the one intended for Decklink devices, defined in a completely different .mm file.
  4. This violates all sorts of assumptions and our app quickly crashes.

The funny thing is, the two implementations of DiscoveryCallback have completely different method names. The Videohub SDK is supposed to be calling NewVideohubDevice, yet somehow it successfully calls DeckLinkDeviceArrived on an instance of a class it shouldn’t even know about.

So the compiler has checked that our intended DiscoveryCallback matches the protocol that the SDK expects, but at runtime the compiled code instantiates a completely different implementation of DiscoveryCallback and somehow doesn’t immediately fail; we still call a method on it that doesn’t even share a name with the intended target. I imagine at this point the method names are long forgotten and are just pointers in a table.

I don’t know if this is a bug in the compiler, the Objective-C++ runtime, or if this is just “working as designed” undefined behavior that I should have avoided by not giving two private classes the same name. I know it’s possible to use a private API simply by redeclaring it in my own code, and this seems related to that, but I feel like the compiler or linker should have warned me that I had two implementations of the same class, or if that is not an error, then the runtime should have instantiated the class that was privately defined in the same source file where it was used.

Obviously I can’t share our entire project; I’d like to provide some sample code that replicates the issue, but I don’t have time to do that right now. I’m posting this to see if other developers have had a similar experience.

Answered by DTS Engineer in 796741022

This doesn’t sound like a bug, but rather a consequence of how Obj-C has always worked. (You’re using Obj-C++, but I assume the names you give are Obj-C classes.)

In Obj-C, class names are global to your app. They are not implicitly qualified by a module name, and they are not local to one target.

In particular, wherever a class name is declared in header files, it represents the same class app-wide, no matter where the declaring header file is. If classes of that name have implementations in more than one target, these are conflicting implementations of the same class. IIRC, the linker will warn you if you provide 2 implementations of the same class in the same target, but I don’t think it has any reasonable way of checking this across targets.

This is the reason why an app’s Obj-C classes have traditionally been given a 2- or 3-letter prefix to make them unique, and it sounds like this is the pattern you should follow here, at least for the class names you control.

Depending on what you're doing, another approach might be to use Swift's ability to interop directly with C++.

This doesn’t sound like a bug, but rather a consequence of how Obj-C has always worked. (You’re using Obj-C++, but I assume the names you give are Obj-C classes.)

In Obj-C, class names are global to your app. They are not implicitly qualified by a module name, and they are not local to one target.

In particular, wherever a class name is declared in header files, it represents the same class app-wide, no matter where the declaring header file is. If classes of that name have implementations in more than one target, these are conflicting implementations of the same class. IIRC, the linker will warn you if you provide 2 implementations of the same class in the same target, but I don’t think it has any reasonable way of checking this across targets.

This is the reason why an app’s Obj-C classes have traditionally been given a 2- or 3-letter prefix to make them unique, and it sounds like this is the pattern you should follow here, at least for the class names you control.

Depending on what you're doing, another approach might be to use Swift's ability to interop directly with C++.

I know these two are defined with C++ class syntax (no @implementation or whatnot), but they do call methods on an Obj-C class.

That's the other scenario here, where the classes are C++ classes, not Obj-C classes. The technical details are different, but the underlying issue is still global uniqueness of type names, which is a known gotcha in C-derived languages.

Yet again, there could be a bug of some kind here. The next step to pin down exactly what's going on in this case, if you want to pursue it, would be to reproduce the issue in an absolutely minimal project.

However, one way or another, the likely way forward is to use a unique name for the type you control.

Objective-C++ confuses two private classes with the same name
 
 
Q