Edit1: Now the code follows the "rule of five". Problem persists.
Edit2: Now passing only void* to printf's %p
. Problem persists.
Edit3: tl;dr: It's a GCC bug.
Tracking down a segmentation fault in some code, I noticed that when a line like
Lexer* const lexer_;
for a property was present, the code crashes; whereas without the const
it works smoothly.
It const
allowed in the above place?
For reference, below is a C-Reduce'd C++ code from a much bigger program that exposes the problem. Unfortunately, C-Reduce starts obfuscating identifiers to single letters at some point, so I stopped reducing and tried to get the code as neat as possible. To compile, I use g++ v11.3 on linux x86_64 with
> g++ main.cpp -o main.x -fsanitize=address -Werror=all -Werror=extra
Running, it prints
0x602000000010 = new Lexer
0x602000000030 = new Token
0x7ffca90b51f0 = new Expression
0x7ffca90b51f0 = start delete Expression
0x602000000010 = start delete Lexer
0x602000000030 = delete Token
0x602000000010 = done delete Lexer
=================================================================
==1232849==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000030 at pc 0x556fc889953d bp 0x7ffca90b5190 sp 0x7ffca90b5180
READ of size 8 at 0x602000000030 thread T0
#0 0x556fc889953c in ExpressionParser::Expression::~Expression() (.../main.x+0x153c)
...
0x602000000030 is located 0 bytes inside of 8-byte region [0x602000000030,0x602000000038)
freed by thread T0 here:
#0 0x7f5258f6f22f in operator delete(void*, unsigned long) .../libsanitizer/asan/asan_new_delete.cpp:172
#1 0x556fc889965f in ExpressionParser::Lexer::~Lexer() (.../main.x+0x165f)
...
previously allocated by thread T0 here:
#0 0x7f5258f6e1c7 in operator new(unsigned long) .../libsanitizer/asan/asan_new_delete.cpp:99
#1 0x556fc8899588 in ExpressionParser::Lexer::tokenize() (.../main.x+0x1588)
...
SUMMARY: AddressSanitizer: heap-use-after-free (/home/john/own/C/mp-gmp/const-problem/main-2.x+0x153c) in ExpressionParser::Expression::~Expression()
...
With -D CONST=
so that lexer_
is non-const, the code runs fine and prints:
0x602000000010 = new Lexer
0x602000000030 = new Token
0x7ffff44937e0 = new Expression
0x7ffff44937e0 = start delete Expression
0x602000000010 = start delete Lexer
0x602000000030 = delete Token
0x602000000010 = done delete Lexer
0x7ffff44937e0 = end delete Expression
What also works is to virtual ~Lexer();
; which should not be needed as Lexer
has no virtual methods?
Source
#include <cstdio>
#ifndef CONST
#define CONST const
#endif
class ExpressionParser
{
public:
class Token;
class Lexer;
class Expression
{
friend ExpressionParser;
Expression (Token *token) : expression_(token)
{
printf ("%p = new Expression\n", (void*) this);
}
Expression (const Expression&) = delete;
Expression (Expression&&) = delete;
void operator= (const Expression&) = delete;
void operator= (Expression&&) = delete;
~Expression();
Token *expression_;
};
static void eval();
};
using EP = ExpressionParser;
class EP::Lexer
{
public:
Token *tokens_ = nullptr;
Lexer()
{
printf ("%p = new Lexer\n", (void*) this);
}
Lexer (const Lexer&) = delete;
Lexer (Lexer&&) = delete;
void operator= (const Lexer&) = delete;
void operator= (Lexer&&) = delete;
~Lexer();
void tokenize();
};
class EP::Token
{
friend ExpressionParser;
Lexer * CONST lexer_;
Token (Lexer *lexer) : lexer_(lexer)
{
printf ("%p = new Token\n", (void*) this);
}
Token (const Token&) = delete;
Token (Token&&) = delete;
void operator= (const Token&) = delete;
void operator= (Token&&) = delete;
~Token()
{
printf ("%p = delete Token\n", (void*) this);
}
};
void EP::eval()
{
Lexer *lexer = new Lexer();
lexer->tokenize();
(void) Expression (lexer->tokens_);
}
EP::Expression::~Expression()
{
printf ("%p = start delete Expression\n", (void*) this);
delete expression_->lexer_;
printf ("%p = end delete Expression\n", (void*) this);
}
void EP::Lexer::tokenize()
{
tokens_= new Token (this);
}
EP::Lexer::~Lexer()
{
printf ("%p = start delete Lexer\n", (void*) this);
delete tokens_;
printf ("%p = done delete Lexer\n", (void*) this);
}
int main (void)
{
ExpressionParser::eval();
}