Search code examples
c++pointers

Accessing Dereferenced pointer exception with nested classes


I am programming something in C++, and while dealing with child classes that contain a pointer to parent classes, I ran into a de-referenced pointer exception. The simplified code is this:

class top {
public:
    int val = 0;
    class mid {
    public:
        top* parent;
        class low {
        public:
            mid* parent = nullptr;
            void addToVal() {
                parent->parent->val += 1;
            }
            low(mid* Parent) {
                parent = Parent;
            }
            low() {}
        };
        low child;
        mid(top* Parent) {
            parent = Parent;
            child = low(this);
        }
    };
    mid::low child;
    top() {
        child = (mid(this)).child;
    };
};
int main()
{
    top t = top();
    t.child.addToVal();
}

But the tl;dr of that code is, you have three classes, the top class tries to access the child of the mid class, then the low class tries to access a value in the top class. I know that the error comes from these two lines: mid::low child; and child =(mid(this)).child; But I don't know why this pointer becomes dereferenced after doing this, as the pointers to the parents are defined when the mid constructor is called, as the mid constructor also calls the low constructor.
Does anyone know what is happening here when the child is created?


Solution

  • Running this through GCC with -fsanitize=address yields:

    =================================================================
    ==1==ERROR: AddressSanitizer: stack-use-after-return on address 0x79bea1c00060 at pc 0x0000004012be bp 0x7ffce7648020 sp 0x7ffce7648018
    READ of size 8 at 0x79bea1c00060 thread T0
        #0 0x4012bd in top::mid::low::addToVal() /app/example.cpp:11
        #1 0x40120f in main /app/example.cpp:31
        #2 0x79bea3a29d8f  (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 962015aa9d133c6cbcfb31ec300596d7f44d3348)
        #3 0x79bea3a29e3f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e3f) (BuildId: 962015aa9d133c6cbcfb31ec300596d7f44d3348)
        #4 0x4010c4 in _start (/app/output.s+0x4010c4) (BuildId: 52657344baa840d03bd8657b741b84c6bfb25e30)
    
    Address 0x79bea1c00060 is located in stack of thread T0 at offset 32 in frame
        #0 0x401541 in top::top() /app/example.cpp:25
    
      This frame has 1 object(s):
        [32, 48) '<unknown>' <== Memory access at offset 32 is inside this variable
    HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
          (longjmp and C++ exceptions *are* supported)
    SUMMARY: AddressSanitizer: stack-use-after-return /app/example.cpp:11 in top::mid::low::addToVal()
    

    The reason for the problem is that mid(this) is a temporary. When it's constructed, it passes a pointer to itself into low, and that pointer is required to access the top instance via parent->parent.

    So, after you copy mid(this).child into top::child, the temporary mid value is destroyed which leaves you with a dangling pointer stored in child.parent. Attempting child.addToVal() then tries to dereference this dangling pointer, which results in undefined behavior.

    You should rethink your design. If low requires a pointer to top and mid is typically temporary, then just store it directly instead of storing mid:

    class low {
    public:
        top* parent = nullptr;
        void addToVal() {
            parent->val += 1;
        }
        low(mid* Parent) {
            parent = Parent->parent;
        }
        low() {}
    };
    

    Alternatively, come up with a less convoluted design!