Andrii Pyvovar
Andrii Pyvovar

Reputation: 193

macOS XCode 16 breaks dynamic_cast for `final` types, defined in shared library

Let's say we have shared library with the following very simple Type.h:

struct Base
{
    virtual ~Base() = default;
};

struct Derived final : Base
{
    Derived();
};

, Type.cpp:

#include "Type.h"

Derived::Derived() = default;

and main.cpp program:

#include "Type.h"

int main()
{
    std::shared_ptr<Base> pr;
    pr = std::make_shared<Derived>();

    auto dyncast = dynamic_cast<Derived *>(pr.get());
    if (dyncast) {
        std::cout << "OK\n";
    } else {
        std::cout << "!!! FAIL\n";
    }
    return 0;
}

Compiled with -O2 flag:

> clang++ -dynamiclib -L. -std=gnu++20 -O2 -o libOut.dylib Type.cpp
> clang++ -o out main.cpp -L. -lOut -O2 -std=gnu++20
> ./out

For some reason, with XCode 16 the program fails to dynamic_cast and outputs fail line.

The following "fixes" the issue:

As of my understanding, some sort of optimization takes place here, causing dynamic_cast to fail, but, IMO, this looks like bug, and a huge one. Am I missing something?

Is there some sort of workaround/compiler flag to make this work?

Thank you.

Upvotes: 4

Views: 217

Answers (2)

Carsten S
Carsten S

Reputation: 205

You are right that this is an optimisation which breaks in this case. Passing the compiler flag -fno-assume-unique-vtables will disable it.

Upvotes: 0

Marek R
Marek R

Reputation: 38112

Problem is that virtual desturctors are implicitly defined in header.

This leads to situation where each library/executable has own virtual table for given type. As a result there is problem with RTTI and dynamic_cast fails.

I've reproduced your issue on my MacOS machine and after introducing fixes below it works as it should:

Type.h

#pragma once

struct Base
{
    virtual ~Base();
};

struct Derived final : Base
{
    Derived();
    ~Derived();
};

Type.cpp

#include "Type.h"

Base::~Base() = default;
Derived::Derived() = default;
Derived::~Derived() = default;

In this form you will have warranty there is only one virtual table for each type and it is inside of libOut.dylib

Upvotes: 1

Related Questions