다음을 통해 공유


Protected or Private II

Someone reminds me that it has been nearly 2 years since my last blog post. How time flies!

Many things happened during this period. I moved to Redmond and I’m now officially working on C++ compiler. The work is busy and also challenging.

Recently, there was a thread in our internal discussion list which mentioned a way to bypass the access check in C++.

I had a blog post which discusses how to access ‘protected’ member. However, the approach is non-conformant and doesn’t work for ‘private’. The internal discussion points me to a post which shows a conformant way to bypass the access check! The approach is creative and it makes me think how to improve it to be extensible and easier to use.

The main idea is based on the following statement in the C++ standard (C++03, 14.7.2 / 8):

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 ]

The tricky part is, even if you can use the private member in the template argument of explicit instantiation, you can’t declare an object of such a type because access check happens during the declaration. That means you can’t get back the member from the instantiated type directly. So we have to take advantage of global variable instead. But unlike the original post, there is no need to use base classes. The following code shows the idea:

// AccessControlHack.h
template<typename T, typename F, F pf, F* ppf>
class C {
    static int init() {
        *ppf = pf;
        return 0;
    }
    static int i;
};
template<typename T, typename F, F pf, F* ppf>
int C<T, F, pf, ppf>::i = init();

// S.h
#include <stdio.h>
struct S {
public:
    S() {i1 = 4;}
private:
    void f1() {printf("Hello World 1\n");}
    static void f2() {printf("Hello World 2\n");}
    int i1;
    static int i2;
};
int S::i2 = 15;

// SHack.h (include S.h)
struct SHack {
public:
    SHack(S *p) : m_p(p) {}

    void f1();
    static void f2();
    int get_i1();
    static int get_i2();
private:
    template<typename T>
    static void VerifyPointer(T p);

    S *m_p;
};

// SHack.cpp (include AccessControlHack.h)
namespace  {
    void (S::*gpf1)();
    void (*gpf2)();
    int S::*gpi1;
    int *gpi2;
}
template class C<S, void (S::*)(), &S::f1, &gpf1>;
#ifndef _MSC_VER
template class C<S, void (*)(), &S::f2, &gpf2>; // bug of VC
#endif
template class C<S, int S::*, &S::i1, &gpi1>;
template class C<S, int *, &S::i2, &gpi2>;

template<typename T>
void SHack::VerifyPointer(T p)
{
    if (!p) {
        throw "The pointer has not been initialized. Using hack class before main could lead to undefined behavior.";
    }
}
void SHack::f1()
{
    VerifyPointer(gpf1);
    (m_p->*gpf1)();
}
void SHack::f2()
{
    VerifyPointer(gpf2);
    (*gpf2)();
}
int SHack::get_i1()
{
    VerifyPointer(gpi1);
    return m_p->*gpi1;
}
int SHack::get_i2()
{
    VerifyPointer(gpi1);
    return *gpi2;
}

// Main.cpp (include SHack.h)
int main()
{
    S s;
    SHack sh(&s);
    sh.f1();
#ifndef _MSC_VER
    SHack::f2();
#endif
    printf("%d\n", sh.get_i1());
    printf("%d\n", SHack::get_i2());
}

The above code compiles and runs correctly using VC10 and GCC 4.5.3. Is this a defect in the standard? I have no idea :-)

Some comments,

  • I find a bug in VC which incorrectly does access check on non-static member functions during explicit instantiation.
  • Because the member pointer is initialized in the dynamic initializer for ‘C::i’, the approach has the dependency on initialization order of global variables which is not guaranteed across translation units. That is why I add a check in ‘VerifyPointer’.