Which undefined behavior allows this optimization? - c++

I'm working on a virtual machine which uses a typical Smi (small integer) encoding where integers are represented as tagged pointers. More precisely, pointers are tagged and integers are just shifted.
This is the same approach as taken by V8 and Dart: https://github.com/v8/v8/blob/main/src/objects/smi.h#L17
In our implementation we have the following code for the Smi:
// In smi.h
#include <stdint.h>
class Object {
public:
bool is_smi() const { return (reinterpret_cast<uintptr_t>(this) & 0x1) == 0; }
};
class Smi : public Object {
public:
intptr_t value() const { return reinterpret_cast<intptr_t>(this) >> 1; }
static Smi* from(intptr_t value) { return reinterpret_cast<Smi*>(value << 1); }
static Smi* cast(Object* obj) { return static_cast<Smi*>(obj); }
};
With this setup, the following function is optimized by gcc 12.1.0 and -O3 so that the 'if' is never taken when o has the Smi value 0.
// bad_optim.cc
#include "smi.h"
void bad_optim(Object* o) {
if (!o->is_smi() || o == Smi::from(0)) {
printf("in if\n");
}
}
If I replace the 'if' line with the following code, the check works:
if (!o->is_smi() || Smi::cast(o)->value() == 0) {
I'm guessing we are hitting an undefined behavior, but it's not clear to me which one.
Furthermore, it would be good to know whether there is a flag that warns about this behavior. Alternatively, maybe there is a flag to disable this optimization.
For completeness sake, here is a main that triggers the behavior. (Note that the bad_optim and main function must be compiled separately).
// main.cc
#include "smi.h"
void bad_optim(Object* o);
int main() {
Smi* o = Smi::from(0);
bad_optim(o);
return 0;
}

It's simple: dereferencing invalid or null o would cause UB, so after the dereference, o supposedly can't be null.
Calling is_smi() counts as dereferencing, even if it actually doesn't access the memory.
Make is_smi() a free function (since this only applies to this, not pointer parameters). I'd also make Object an opaque struct (declared but not defined).

Related

abi::__dynamic_cast returns nullptr for upcasts

I need to hook into C++'s exception throwing mechanism like this:
namespace __cxxabiv1
{
extern "C" void __cxa_throw(void* voidPointerToActualObject, std::type_info* stdTypeInfoOfActualObject, void (*destructor)(void *))
{
// If thrownException is a custom exception type or something deriving from it, poke a value into it.
}
}
If you're wondering "Why would you do that?"
I have this simple example of throwing an exception that's part of a very simple class hierarchy:
#include <stdexcept>
class Upper : public std::exception
{
public:
int pokeMe = 111111;
};
class Lower : public Upper {};
int main()
{
throw Lower();
}
#include <cxxabi.h>
namespace __cxxabiv1
{
extern "C" void __cxa_throw(void* voidPointerToActualObject, std::type_info* stdTypeInfoOfActualObject, void (*destructor)(void *))
{
// The point is to do the equivalent of this:
Lower* staticallyTypedPointerToActualObject = reinterpret_cast<Lower*>(voidPointerToActualObject);
auto thisWorks = dynamic_cast<Upper*>(staticallyTypedPointerToActualObject);
thisWorks->pokeMe = 222222;
// But we don't know the actual static type, so we can't get a statically typed pointer. We only have a void* and a type_info:
auto abiTypeInfoOfActualObject = dynamic_cast<const abi::__class_type_info*>(stdTypeInfoOfActualObject);
auto abiTypeInfoOfUpper = dynamic_cast<const abi::__class_type_info*>(&typeid(Upper));
Upper* thisDoesNotWork = reinterpret_cast<Upper*>(abi::__dynamic_cast(voidPointerToActualObject, abiTypeInfoOfActualObject, abiTypeInfoOfUpper, -1));
thisDoesNotWork->pokeMe = 333333;
// Elided for clarity: Call the original __cxa_throw function here
// Instead, suppress these warnings:
(void)destructor; // Unused parameter
while (1) { } // Return from non-returning function
}
}
I don't see a reason why __dynamic_cast shouldn't be able to upcast, but it returns nullptr.
Why? And how do I get it to work?
It seems to be able to do downcasts just fine, BTW:
auto abiTypeInfoOfActualObject = dynamic_cast<const abi::__class_type_info*>(&typeid(Upper)); // Plonking this here for testing
auto abiTypeInfoOfUpper = dynamic_cast<const abi::__class_type_info*>(&typeid(Lower)); // Casting to Lower instead of Upper
Lower* thisDoesNotWork = reinterpret_cast<Lower*>(abi::__dynamic_cast(voidPointerToActualObject, abiTypeInfoOfActualObject, abiTypeInfoOfUpper, -1));
I managed to dig up this archived conversation from 2004:
The ABI document does not require that __dynamic_cast perform a
derived-to-base cast. Those __dynamic_cast operations that can
actually be performed statically by the compiler must be performed
statically by the compiler -- the runtime library does not expect to
be called in that situation.
So that answers that question. Greeeeeeat.
But the conversation luckily mentions:
Yes; the holder knows the static type; it can throw a pointer of that
type. The cast operation can catch the pointer type it's looking for,
or fail the cast with catch(...).
That gave me the idea to try this (simplified version):
namespace __cxxabiv1
{
using ThrowFunction = decltype(__cxa_throw)*;
ThrowFunction oldThrowFunction = nullptr;
extern "C" void __cxa_throw(void* voidPointerToActualObject, std::type_info* stdTypeInfoOfActualObject, void (*destructor)(void *))
{
if (oldThrowFunction == nullptr)
{
oldThrowFunction = (ThrowFunction)dlsym(RTLD_NEXT, "__cxa_throw");
}
try
{
oldThrowFunction(voidPointerToActualObject, stdTypeInfoOfActualObject, destructor);
}
catch (Upper& ex)
{
ex.pokeMe = 333333;
}
catch (...)
{
}
oldThrowFunction(voidPointerToActualObject, stdTypeInfoOfActualObject, destructor);
}
}
And I can't believe it but it actually works!
Edit: Disclaimer: It seems that this way, the destructor callback is actually called twice, because if use std::string pokeMe, the string is trashed by the time I get to the second call to oldThrowFunction. I'll experiment around with over the next few days.
Edit2: That's indeed the case. I couldn't find anything indicating whether __cxa_throw accepts nullptr as the destructor argument (it didn't crash for me, at least), so the safest bet is to pass a pointer to an empty dummy function:
void dummyDestructor(void*)
{
}
//...
oldThrowFunction(voidPointerToActualObject, stdTypeInfoOfActualObject, &dummyDestructor);
Edit 3: Goddamnit! It seems that this causes memory corruption. malloc crashes with a segfault at a later point when doing this.
Back to square -5.

C++ understanding RVO (as compared to returning local variable reference)

It's my first year of using C++ and learning on the way. I'm currently reading up on Return Value Optimizations (I use C++11 btw). E.g. here https://en.wikipedia.org/wiki/Return_value_optimization, and immediately these beginner examples with primitive types spring to mind:
int& func1()
{
int i = 1;
return i;
}
//error, 'i' was declared with automatic storage (in practice on the stack(?))
//and is undefined by the time function returns
...and this one:
int func1()
{
int i = 1;
return i;
}
//perfectly fine, 'i' is copied... (to previous stack frame... right?)
Now, I get to this and try to understand it in the light of the other two:
Simpleclass func1()
{
return Simpleclass();
}
What actually happens here? I know most compilers will optimise this, what I am asking is not 'if' but:
how the optimisation works (the accepted response)
does it interfere with storage duration: stack/heap (Old: Is it basically random whether I've copied from stack or created on heap and moved (passed the reference)? Does it depend on created object size?)
is it not better to use, say, explicit std::move?
You won't see any effect of RVO when returning ints.
However, when returning large objects like this:
struct Huge { ... };
Huge makeHuge() {
Huge h { x, y, x };
h.doSomething();
return h;
}
The following code...
auto h = makeHuge();
... after RVO would be implemented something like this (pseudo code) ...
h_storage = allocate_from_stack(sizeof(Huge));
makeHuge(addressof(h_storage));
auto& h = *properly_aligned(h_storage);
... and makeHuge would compile to something like this...
void makeHuge(Huge* h_storage) // in fact this address can be
// inferred from the stack pointer
// (or just 'known' when inlining).
{
phuge = operator (h_storage) new Huge(x, y, z);
phuge->doSomething();
}

Casting policies to avoid "pointer cannot be null in well-defined C++ code"

Recent compilers, e.g. clang, complain if a function tests if "this" is NULL, as this is illegal according to the standard.
I have a program that made large use of this, and am trying to clean it up. Below are some examples that don't warn - are these safe or not? Is there a good way to get the ->toAA and ->toAB functionality behaviour that is C++ standard compliant? (Ideally without changing the code that is calling these functions, and reasonably fast - see the note below that testing this is faster in GCC 4.6.)
#include <stddef.h>
class ClassAA;
class ClassAB;
class ClassBase {
public:
enum Type {AA, AB};
Type m_type;
ClassAA* toAA();
ClassAB* toAB();
ClassBase(Type t) : m_type(t) {}
virtual ~ClassBase() {}
};
class ClassAA : public ClassBase { public: int a; ClassAA():ClassBase(AA) {} };
class ClassAB : public ClassBase { public: int b; ClassAB():ClassBase(AB) {} };
// toAA and toAB are intended to have same function,
// but toAB is significantly better performing on GCC 4.6.
inline ClassAA* ClassBase::toAA() { return dynamic_cast<ClassAA*>(this); }
inline ClassAB* ClassBase::toAB() { return (this && m_type == AB) ? static_cast<ClassAB*>(this) : NULL; }
int foo(ClassBase* bp) {
if (bp && bp->toAA()) // Legal
return -1;
if (dynamic_cast<ClassAA*>(bp)) // Legal
return -1;
if (!bp->toAA()) // No warning, is this legal?
return -1;
if (bp->toAA()->a) // No warning, is this legal?
return 10;
if (bp->toAB()->b) // Warning due to use of "this", illagal presumably
return 20;
return 0;
}
Make them free functions instead, or static members that take an argument.
Non-static member functions must be invoked on an extant object; period.
Your compiler isn't warning likely because you don't dereference this, so its detection algorithm isn't triggered. But that doesn't make the behaviour any less undefined. The compiler could be omitting the warning then sneaking off to make pancakes, for all you know.

Removing class name causes segmentation fault

When I define a function of a class, I call another function of the same class within it. But when I do not type the class name it gives segmentation fault. Check below.
Header file
class DomainSolver
{
int fnc1 (UserDefinedType & var);
int fnc2 (UserDefinedType & var);
};
C file
int DomainSolver::fnc2 (UserDefinedType & var)
{
return 0;
}
int DomainSolver::fnc1 (UserDefinedType & var)
{
// fnc2 (var); // does not work
DomainSolver::fnc2(var); // works
return 0;
}
Wild guess… since the code you presented does not have any issues…
The function being called is declared virtual in a base class, so even if the virtual keyword is not present in the declaration here it is virtual.
The function being called does not access any member of the object.
You are calling the function on an invalid pointer or reference (for example through a null pointer or on an object that has already been deleted.
If all those guesses are right, the use of the qualification inhibits the dynamic dispatch mechanism, avoiding the dereference of an invalid pointer to the vtable. The code is still wrong (due to the third point above), but it seems to work.
The solution is not to call a member function through an invalid pointer or reference.
Although as pointed out by Zac's reply, the functions as you present them are not properly formed, there shouldn't be a difference between calling the scoped version; if you are getting a segfault in one case and not the other it's possibly because of code elsewhere.
Here is an example that works just fine:
dsolver.hh
#ifndef DSOLVER_HH
#define DSOLVER_HH
class DomainSolver
{
public:
int fnc1 (int& var);
int fnc2 (int& var);
};
#endif
dsolver.cc
#include <iostream>
#include "dsolver.hh"
int DomainSolver::fnc1 (int& var)
{
std::cout << "fnc1\n";
fnc2( var );
var = -1;
return var;
}
int DomainSolver::fnc2 (int& var)
{
std::cout << "fnc2\n";
var = 100;
return var;
}
main.cc
#include <iostream>
#include "dsolver.hh"
int main()
{
DomainSolver my_dsolver;
int my_int = 5;
my_dsolver.fnc1(my_int);
return 0;
}
Assuming this is close to your actual code, you have undefined behavior in fnc1:
int DomainSolver::fnc1 (UserDefinedType & var)
{
// fnc2 (var); // does not work
DomainSolver::fnc2(var); // works
// missing return!!!
}
You declare it to return an int, but then never return anything (in either case). Both cases are UB, so anything they do is technically "valid", since your code is not.
This code should be:
int DomainSolver::fnc1 (UserDefinedType & var)
{
return fnc2 (var);
}
As a side note: This is a good example of why you should never ignore the warnings given by the compiler (as you should have received a warning with both versions).
EDIT
With your latest edit adding a return value to fnc1, you'll need to show more of your actual code as there is not enough there to properly diagnose the problem (with the return being there, there is nothing wrong with your shown code).

Behaviour in Release Build I cant understand

While trying to find out a problem that occurs only in a release build and not in the debug build I noticed the following behaviour (String would be invalid and would not point to anything while the int would be fine). I have given code below which gives an idea of what I was going through
typedef boost::shared_ptr<MyClass> shared_cls
typedef std::deque<shared_cls> vector_def;
typedef boost::shared_ptr<vector_def> shared_vector_def;
typedef boost::unordered_map<int,shared_vector_def> inner_map_def;
typedef boost::shared_ptr<inner_map_def> shared_inner_map_def;
static boost::unordered_map<std::string,shared_inner_map_def> bcontainer;
shared_cls& SomeMethod(const std::string& symb,const int& no)
{
shared_inner_map_def tshare = bcontainer[symb];
shared_vector_def tmp = tshare->at(no);
shared_cls t = tmp->back();
return t
}
The object MyClass looks like this
class SomeClass
{
private:
int i;
std::string s;
void set_i(int rx)
{
i = rx;
}
int get_i()
{
return i;
}
void set_s(std::string rx)
{
s = rx;
}
std::string get_s()
{
return s;
}
}
Now when I use the above method as in the following code
void main()
{
shared_cls r = SomeMethod("IBM",12);
//Here r does not have a valid string s
//However it does have a valid int i
}
Now my question is in the above main when I call the SomeMethod the r returned does not have a valid string s. It has a scrambled value I found this out by using a logger. However the value of s is totally find during the function SomeMethod. I resolved this issue by not returning the shared pointer by reference.In that case it works. Why does removing the reference make it work
Your shared_cls t goes out of scope because it is defined in the function SomeMethod itself. You need to return shared pointers by value if they are defined in the scope. In the link, it is explained why it is dangerous to return the reference of a temporary object.
In the case of std::string, string has a reference counting mechanism and when it's reference is decremented to zero, it becomes invalidated and a segmentation fault may be observed in such a case. Even if member int i is returned properly, it is still undefined behavior.