iOS: Derived exceptions not caught by App


#1

So here’s a weird one.

I ran into a strange bug recently. After tracking down the cause, it looked like my app was calling DeviceManager::setFramesPerBlock() and that method was throwing a AudioDeviceExc exception. Strange, because my app had a try/catch surrounding that call that specifically handled that:

try {
    ctx->deviceManager()->setFramesPerBlock(output, preferredSamplesPerBlock);
}
catch (const audio::AudioDeviceExc &exc) {
    CI_LOG_I("Caught AudioDeviceExc: " << exc.what());
}

But the exception was never caught! :face_with_raised_eyebrow: What’s going on here?

I tried adding a catch (...) clause, and sure enough the exception was caught correctly. How about with std::exception? Yup, works too. cinder::Exception? Nope! So all of the exceptions defined in Cinder code and derived from std::exception are not being caught?!

Here’s what I ended up with as a test case:

auto ctx = audio::master();
auto output = audio::Device::getDefaultOutput();
try {
    // Set to 0 to purposely cause an exception to be thrown.
    ctx->deviceManager()->setFramesPerBlock(output, 0);
}
catch (const audio::AudioDeviceExc &exc) {
    CI_LOG_I("Caught AudioDeviceExc: " << exc.what());
}
catch (const Exception &exc) {
    CI_LOG_I("Caught Exception: " << exc.what());
}
catch (const std::exception &exc) {
    CI_LOG_I("Caught std::exception: " << exc.what());
}
catch (...) {
    CI_LOG_I("Caught ...");
}

So after some more searching and sleuthing, I found the section Problems with C++ exceptions (please read!) on the GCC wiki article about visibility which describes what is going on here.

It turns out that for iOS build targets, Xcode defaults to setting the -fvisibility=hidden flag, which causes the typeinfo for those exception classes to be hidden. So then the dynamic_cast<>() on the exception object fails when checking against the catch block, and the app crashes. :exploding_head:

At least that’s my rudimentary understanding of what’s happening here. The solution of course was to disable symbol hiding on the build target, and thankfully this is what Cinder does by default on iOS builds.

Just thought I would share this in case anyone else runs into the same issue!

:heart: ryan