How to type-pun via catching by non-const reference
source link: https://quuxplusone.github.io/blog/2023/11/17/reinterpret-cast-via-throw/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
How to type-pun via catching by non-const reference
I’ll just leave this here (Godbolt):
int i = 0x4048f5c3;
try {
try {
throw (float*)nullptr;
} catch (void *& pv) {
pv = &i;
throw;
}
} catch (const float *pf) {
printf("%.2f\n", *pf);
}
On MSVC and Clang, this prints 3.14
.
On GCC (which I think means “on the libsupc++ runtime”), pv
somehow ends up referring to a copy of
the original exception object, such that modifications to pv
don’t affect the subsequently caught
value of pf
.
As far as I can tell, none of these three runtimes is doing the right thing. According to
[except.handle]/3, if I throw a type E
that is
convertible to T
only via some pointer conversion (e.g. E
=float*
to T
=void*
), I’m
actually not supposed to be able to hit a catch-handler of type T&
at all — I’m supposed to
fly right past it! So (unless I’m mistaken) this is a “hole in the type-system” of all three
vendors’ C++ runtimes, but it’s not a hole in the paper standard, because the paper standard
says you’re not supposed to be able to do this.
UPDATE, 2023-12-12: In GCC bug 23257, it’s stated that this isn’t the fault of the compiler or the runtime library, but a flaw in the Itanium C++ ABI itself — the ABI’s
.gcc_except_table
section encodes types in terms oftypeid
, which is identical forT
,T&
, andconst T&
. Notice the identical codegen in this Godbolt. Apparently Arm’s C++ ABI can handle references differently from values-and-const-references, although personally I’m fuzzy on how that works and whether any mainstream compiler implements that ABI even on Arm targets. I don’t know whether the MSVC issue is an ABI issue too, but I’d bet that it is.
This seems to have severe implications for P2927 try_cast
.
Last week I said
that it was always fine to take the const T*
you got from the
cast and const_cast
away the constness — the underlying exception object will never be
const. That’s true of the underlying exception object, but the pointer you get back might
not be pointing to the underlying exception object! — it might be pointing to a temporary
T
implicitly-converted-by-a-pointer-conversion from the actual exception object (whose
type is not T
but E
). In fact, if try_cast
’s result points to a temporary, it’ll be
dangling already! I guess we’ll have to do something about this.
auto e = std::make_exception_ptr<int*>(&i);
auto *p = std::try_cast<void*>(e);
// as-if-by catch (void* const&); a temporary is created
// here p is a dangling pointer
UPDATE, 2023-12-12: My proposed solution is simply to make
std::try_cast<T*>
ill-formed. Throwing pointers is already sketchy (and, as we’ve seen, buggy in the core language on all mainstream ABIs);std::try_cast
can simply refuse to engage.
Previously on this blog:
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK