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)