[Bug c++/85071] New: The g++ delete the memory alloced by new operator before I manually delete it.

141242068 at smail dot nju.edu.cn gcc-bugzilla@gcc.gnu.org
Sun Mar 25 08:54:00 GMT 2018


https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85071

            Bug ID: 85071
           Summary: The g++ delete the memory alloced by new operator
                    before I manually delete it.
           Product: gcc
           Version: 7.2.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: 141242068 at smail dot nju.edu.cn
  Target Milestone: ---

The code is too long, but I have tried my best to simplify the codes. Raw code
is 14000 lines and now is 218 lines, and the bug can only be triggered under
complicated codes logic.

Firstly, the IntrusiveRefCntPtr, to avoid my own mistakes on this template
class, I copy it from llvm/ADT/IntrusiveRefCntPtr.h:

$ cat IntrusiveRefCntPtr.h
#ifndef INTRUSIVEREFCNTPTR_H
#define INTRUSIVEREFCNTPTR_H

// IntrusiveRefCntPtr
template <class Derived> class RefCountedBase {
        mutable unsigned RefCount = 0;

public:
        RefCountedBase() = default;
        RefCountedBase(const RefCountedBase &) {}

        void Retain() const { ++RefCount; }

        void Release() const {
                assert(RefCount > 0 && "Reference count is already zero.");
                if (--RefCount == 0)
                        delete static_cast<const Derived *>(this);
        }
};


template <typename T> struct IntrusiveRefCntPtrInfo {
        static void retain(T *obj) { obj->Retain(); }
        static void release(T *obj) { obj->Release(); }
};

template <typename T> class IntrusiveRefCntPtr {
        T *Obj = nullptr;

public:
        using element_type = T;

        explicit IntrusiveRefCntPtr() = default;
        IntrusiveRefCntPtr(T *obj) : Obj(obj) { retain(); }
        IntrusiveRefCntPtr(const IntrusiveRefCntPtr &S) : Obj(S.Obj) {
retain(); }
        IntrusiveRefCntPtr(IntrusiveRefCntPtr &&S) : Obj(S.Obj) { S.Obj =
nullptr; }

        template <class X>
                IntrusiveRefCntPtr(IntrusiveRefCntPtr<X> &&S) : Obj(S.get()) {
                        S.Obj = nullptr;
                }

        template <class X>
                IntrusiveRefCntPtr(const IntrusiveRefCntPtr<X> &S) :
Obj(S.get()) {
                        retain();
                }

        ~IntrusiveRefCntPtr() { release(); }

        IntrusiveRefCntPtr &operator=(IntrusiveRefCntPtr S) {
                swap(S);
                return *this;
        }

        T &operator*() const { return *Obj; }
        T *operator->() const { return Obj; }
        T *get() const { return Obj; }
        explicit operator bool() const { return Obj; }

        void swap(IntrusiveRefCntPtr &other) {
                T *tmp = other.Obj;
                other.Obj = Obj;
                Obj = tmp;
        }

        void reset() {
                release();
                Obj = nullptr;
        }

        void resetWithoutRelease() { Obj = nullptr; }

private:
        void retain() {
                if (Obj)
                        IntrusiveRefCntPtrInfo<T>::retain(Obj);
        }

        void release() {
                if (Obj)
                        IntrusiveRefCntPtrInfo<T>::release(Obj);
        }

        template <typename X> friend class IntrusiveRefCntPtr;
};

#endif


And then is the header.h:

$ cat Header.h
#ifndef HEADER_H
#define HEADER_H

#include <cassert>
#include <cstdint>
#include <iostream>
#include <memory>
#include <string>

#include "IntrusiveRefCntPtr.h"

class Block;
class Context;
class Function;
class Module;
class Scope;
using BlockPtr = std::shared_ptr<Block>;
using ScopePtr = std::shared_ptr<Scope>;

#define errs() \
(std::clog << "\e[31m[ERR]\e[0m:" << __FILE__ << ":" << __LINE__ << ":")
#define logs() \
(std::clog << "\e[34m[LOG]\e[0m:" << __FILE__ << ":" << __LINE__ << ":")

class SyntaxElement {
public:
        SyntaxElement() = default;

        virtual void onEnter(Context *context)
        { /* do something by derived class */ }

        virtual ~SyntaxElement() = default;
};

class Function : public RefCountedBase<Function>, public SyntaxElement {
        ScopePtr scope;

public:

        Function() = default;

        void details() const {
                logs() << "scope info: ptr=" << scope.get()
                        << ", count=" << scope.use_count() << "\n";
        }

        void onEnter(Context *context) override;

        ~Function()
        { errs() << "destructor called@" << this << "\n"; }
};

using FunctionPtr = IntrusiveRefCntPtr<Function>;

class Block : public SyntaxElement {
        FunctionPtr func;
public:
        Block(FunctionPtr func) : func(func) { }
};

class Context {
        Module *M;
        Function *F;
public:
        Context() = default;
        void setAvailableCallees();
        void setModule(Module *M);
        void setFunction(Function *F);
};

class Module : public SyntaxElement {
public:
        Module() = default;
        bool hasCalleesFor(IntrusiveRefCntPtr<Function> func) const
        { return true; }
};


class Scope { };

#endif


Finally plus the main.cc:

$ cat main.cc
#include "Header.h"
#include <cstdlib>

static Context cxt;
void Context::setModule(Module *M) { this->M = M; }
void Context::setFunction(Function *F) { this->F = F; }

char *smash(size_t size) {
        char *ptr = new char[size];
        logs() << "smash alloc memory locates at "
                << static_cast<void*>(ptr) << "\n";
        for(auto i = 0u; i < size; i++)
                ptr[i] = rand() % 128 + rand();
        return ptr;
}

Function *func = nullptr;

void Context::setAvailableCallees() {
        if(!M->hasCalleesFor(F)) return;
}

void Function::onEnter(Context *context) {
        logs() << "this@" << this << " hasn't been deleted\n";
        context->setFunction(this);
        context->setAvailableCallees();
        errs() << "this@" << this << " has been deleted\n";

        if(func) func->details();
        char *ptr = smash(sizeof(Function));
        if(func) func->details();
        delete []ptr;
}

int main() {
        std::unique_ptr<Module> M(new Module{});
        cxt.setModule(M.get());

        func = new Function();
        logs() << "+++++++++++++++++++++++++++++++\n";
        func->onEnter(&cxt);

        delete func;
        return 0;
}


If you compile the codes with g++-7.2.0 (other versions haven't been tested),
it will causes Segment Fault:

$ g++ main.cc -o main && ./main
[LOG]:main.cc:40:+++++++++++++++++++++++++++++++
[LOG]:main.cc:24:this@0x19a9c40 hasn't been deleted
[ERR]:Header.h:50:destructor called@0x19a9c40
[ERR]:main.cc:27:this@0x19a9c40 has been deleted
[LOG]:Header.h:43:scope info: ptr=0, count=0
[LOG]:main.cc:10:smash alloc memory locates at 0x19a9c40
[LOG]:Header.h:43:scope info: ptr=0x32411c827d89c498, count=[1]    10865
segmentation fault (core dumped)  ./main

The extra information outputed by the code was aimed to help you acknowledge
the execution trace (maybe useful).

The error point is in the `Function::onEnter` function, before invoking
`context->setFunction(this)`, the global pointer `Function *func` hasn't been
deleted (which was newed in main function), but after the
`context->setAvailableCallees()` was invoked, as you can see in my log, the
destructor was invoked and the memory occupied by `func` was released
unexpectly.

Maybe the above evidence is not obvious, to prove that the memory was indeed
released, I add the smash function after `context->setAvailableCallees()`, the
smash will new a char array the same size as `Function`, and you can see its
alloced address is same as the `func` in printed log.

To trigger the segment fault, I do something principlely safe in smash
functions: write random data into alloced char array;

Except for log statements, if any statement in the Header.h or main.cc was
deleted, or just replace the `IntrusiveRefCntPtr` by nare pointer, the `func`
will not be deleted and the Segment Fault will disappear (I tried my best to
simplfy the codes).

I prefer to believe that this is my own `bug`, since the error triggered codes
are too long, if it is proved to be no problem with the compiler, I make an
apology to you for my mistakes.


My g++ details:
$ g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu
7.2.0-1ubuntu1~16.04' --with-bugurl=file:///usr/share/doc/gcc-7/README.Bugs
--enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr
--with-gcc-major-version-only --program-suffix=-7
--program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id
--libexecdir=/usr/lib --without-included-gettext --enable-threads=posix
--libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu
--enable-libstdcxx-debug --enable-libstdcxx-time=yes
--with-default-libstdcxx-abi=new --enable-gnu-unique-object
--disable-vtable-verify --enable-libmpx --enable-plugin --with-system-zlib
--with-target-system-zlib --enable-objc-gc=auto --enable-multiarch
--disable-werror --with-arch-32=i686 --with-abi=m64
--with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic
--enable-offload-targets=nvptx-none --without-cuda-driver
--enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu
--target=x86_64-linux-gnu
Thread model: posix
gcc version 7.2.0 (Ubuntu 7.2.0-1ubuntu1~16.04)


More information about the Gcc-bugs mailing list