Private members extraction

Having found Access to private members. That’s easy! , I was very impressed. But the code provided there was quite hard to read and understand, so I clarify it and make some additional explanations.

Abstract #

This method allows us to retrieve pointers to private member data and functions, with one restriction: private types cannot feature in the data type / function signature.

Key idea #

We employ [temp.explicit]/12 of the Working Draft from 2017-03-21 which states the following:

The usual access checking rules do not apply to names used to specify explicit instantiations. [Note: In particular, the template arguments and names used in the function declarator (including parameter types, return types and exception specifications) may be private types or objects which would normally not be accessible and the template may be a member template or member function which would not normally be accessible. — end note]

So we explicitly instantiate template<class Ptr, Ptr TargetPointer, size_t SpecializationId> struct steal which has a static object essence whose constructor performs an assignment of TargetPointer to the global variable template<class Ptr, size_t Id> Ptr result.

Since essence has a static storage duration as per [basic.stc.static]/4, it gets initialized at the program initiation as per [basic.start.static]/1; in other words, result<Ptr, InstantiationId> gets assigned at the program initiation, before the execution of main starts.

Each InstantiationId is required to be unique – by this, we guarantee a bijection between the instantiations of result and the members we are extracting. Thus we get rid of the problems caused by a potential multiple use of the same class Ptr.

Code #

You can see it in action here.

#include <iostream>


// The essence

namespace essence {
template<class Ptr, size_t Id>
Ptr result;

template<class Ptr, Ptr TargetPointer, size_t InstantiationId>
struct steal {
    struct exec_on_instantiation {
        exec_on_instantiation() { 
            result<Ptr, InstantiationId> = TargetPointer;
        }
    };
    static exec_on_instantiation essence;
};
template <typename Ptr, Ptr TargetPointer, size_t InstantiationId>
typename steal<
    Ptr, 
    TargetPointer, 
    InstantiationId
>::exec_on_instantiation steal<
    Ptr, 
    TargetPointer, 
    InstantiationId
>::essence;
}  // namespace essence


// Demo

using std::string;
using std::cout;
using std::endl;

class Victim {
    void f() { cout << "PoC - member function f" << endl; }
    void h() { cout << "PoC - member function h" << endl; }
    string str = "PoC - member data";
    static void g() { cout << "PoC - static member function" << endl; }
    static string static_str;
};
string Victim::static_str = "PoC - static member data";

template struct essence::steal<decltype(&Victim::f), &Victim::f, 1>;
template struct essence::steal<decltype(&Victim::h), &Victim::h, 2>;
template struct essence::steal<decltype(&Victim::str), &Victim::str, 3>;
template struct essence::steal<decltype(&Victim::g), &Victim::g, 4>;
template struct essence::steal<
    decltype(&Victim::static_str), 
    &Victim::static_str, 
    5
>;

int main() {
    auto ptr1 = essence::result<void (Victim::*)(), 1>;
    auto ptr2 = essence::result<void (Victim::*)(), 2>;
    auto ptr3 = essence::result<string (Victim::*), 3>;
    auto ptr4 = essence::result<void(*)(), 4>;
    auto ptr5 = essence::result<string*, 5>;

    Victim a;

    (a.*ptr1)();
    (a.*ptr2)();
    a.*ptr3 += " (modified)", cout << a.*ptr3 << endl;
    ptr4();
    *ptr5 += " (modified)", cout << *ptr5 << endl;
}

In conclusion #

C++ has so complicated rules that it allows us to do the impossible. Thanks for reading! I would be happy to get any feedback from you on Twitter.

 
5
Kudos
 
5
Kudos

Now read this

Objectives for 2018

You can’t state you’ve learned something until you have applied it in practice. A note on 2017 # 2017 brought me a lot of wonderful experience, but on the whole, it was a year full of hopes & failures. It made me rethink my world... Continue →