Skip to content

Commit debf93d

Browse files
edolstracole-h
authored andcommitted
Abort when throwing std::logic_error
There are many places where a thrown `std::logic_error` is handled like a normal error, making it very difficult to get a good stack trace so we can fix that logic error. So, we wrap the __cxa_throw function such that we can check if the thrown exception was a `std::logic_error`, and if so, abort the process so we can figure out exactly where it happened. Co-authored-by: Eelco Dolstra <edolstra@gmail.com>
1 parent 67aea4c commit debf93d

8 files changed

Lines changed: 183 additions & 0 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#include <cstdlib>
2+
#include <dlfcn.h>
3+
#include <typeinfo>
4+
5+
#include "is-logic-error.hh"
6+
7+
#ifndef CXA_THROW_ON_LOGIC_ERROR
8+
# define CXA_THROW_ON_LOGIC_ERROR() abort()
9+
#endif
10+
11+
typedef void (*cxa_throw_type)(void *, std::type_info *, void (*)(void *));
12+
13+
extern "C" void __cxa_throw(void * exc, std::type_info * tinfo, void (*dest)(void *))
14+
{
15+
if (is_logic_error(tinfo))
16+
CXA_THROW_ON_LOGIC_ERROR();
17+
18+
static auto * orig = (cxa_throw_type) dlsym(RTLD_NEXT, "__cxa_throw");
19+
if (!orig)
20+
abort();
21+
22+
orig(exc, tinfo, dest);
23+
24+
__builtin_unreachable();
25+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#pragma once
2+
3+
#include <cxxabi.h>
4+
#include <stdexcept>
5+
#include <typeinfo>
6+
7+
static bool is_logic_error(const std::type_info * tinfo)
8+
{
9+
if (*tinfo == typeid(std::logic_error))
10+
return true;
11+
12+
auto * si = dynamic_cast<const __cxxabiv1::__si_class_type_info *>(tinfo);
13+
if (si)
14+
return is_logic_error(si->__base_type);
15+
16+
return false;
17+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
have_cxa_throw = false
2+
3+
can_interpose_cxa_throw_test_code = '''
4+
#include <string>
5+
#include <unistd.h>
6+
7+
#define CXA_THROW_ON_LOGIC_ERROR() _exit(0)
8+
#include "interpose-cxa-throw.cc"
9+
10+
int main()
11+
{
12+
const char * volatile p = nullptr;
13+
std::string s(p);
14+
return 1;
15+
}
16+
'''
17+
18+
can_interpose_cxa_throw_result = cxx.run(
19+
can_interpose_cxa_throw_test_code,
20+
args : [ '-ldl' ],
21+
include_directories : include_directories('.'),
22+
name : 'can interpose __cxa_throw (catches libstdc++ throws)',
23+
)
24+
can_interpose_cxa_throw = can_interpose_cxa_throw_result.compiled() and can_interpose_cxa_throw_result.returncode() == 0
25+
26+
if can_interpose_cxa_throw
27+
interpose_cxa_throw_lib = static_library(
28+
'interpose-cxa-throw',
29+
'interpose-cxa-throw.cc',
30+
dependencies : cxx.find_library('dl', required : false),
31+
)
32+
33+
cxa_throw_dep = declare_dependency(
34+
link_whole : interpose_cxa_throw_lib,
35+
)
36+
37+
have_cxa_throw = true
38+
else
39+
can_wrap_cxa_throw_test_code = '''
40+
#include <string>
41+
#include <unistd.h>
42+
43+
#define CXA_THROW_ON_LOGIC_ERROR() _exit(0)
44+
#include "wrap-cxa-throw.cc"
45+
46+
int main()
47+
{
48+
const char * volatile p = nullptr;
49+
std::string s(p);
50+
return 1;
51+
}
52+
'''
53+
54+
wrap_cxa_throw_args = [ '-Wl,--wrap=__cxa_throw' ]
55+
56+
can_wrap_cxa_throw_result = cxx.run(
57+
can_wrap_cxa_throw_test_code,
58+
args : wrap_cxa_throw_args,
59+
include_directories : include_directories('.'),
60+
name : 'can wrap __cxa_throw (catches libstdc++ throws)',
61+
)
62+
can_wrap_cxa_throw = can_wrap_cxa_throw_result.compiled() and can_wrap_cxa_throw_result.returncode() == 0
63+
64+
if can_wrap_cxa_throw
65+
wrap_cxa_throw_lib = static_library(
66+
'wrap-cxa-throw',
67+
'wrap-cxa-throw.cc',
68+
)
69+
70+
cxa_throw_dep = declare_dependency(
71+
link_whole : wrap_cxa_throw_lib,
72+
link_args : wrap_cxa_throw_args + [ '-Wl,-u,__wrap___cxa_throw' ],
73+
)
74+
75+
have_cxa_throw = true
76+
endif
77+
endif
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#include <cstdlib>
2+
#include <typeinfo>
3+
4+
#include "is-logic-error.hh"
5+
6+
#ifndef CXA_THROW_ON_LOGIC_ERROR
7+
# define CXA_THROW_ON_LOGIC_ERROR() abort()
8+
#endif
9+
10+
extern "C" void __real___cxa_throw(void *, std::type_info *, void (*)(void *));
11+
12+
extern "C" void __wrap___cxa_throw(void * exc, std::type_info * tinfo, void (*dest)(void *))
13+
{
14+
if (is_logic_error(tinfo))
15+
CXA_THROW_ON_LOGIC_ERROR();
16+
17+
__real___cxa_throw(exc, tinfo, dest);
18+
19+
__builtin_unreachable();
20+
}

nix-meson-build-support/common/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,4 @@ endif
7676

7777
subdir('assert-fail')
7878
subdir('asan-options')
79+
subdir('cxa-throw')

src/libutil-tests/cxa-throw.cc

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#include <gtest/gtest.h>
2+
3+
#include <stdexcept>
4+
#include <string>
5+
6+
TEST(CxaThrow, catchesLogicErrorFromStdlib)
7+
{
8+
const char * volatile p = nullptr;
9+
ASSERT_DEATH({ std::string s(p); }, "");
10+
}
11+
12+
TEST(CxaThrow, catchesLogicError)
13+
{
14+
ASSERT_DEATH({ throw std::logic_error("test"); }, "");
15+
}
16+
17+
TEST(CxaThrow, catchesOutOfRange)
18+
{
19+
ASSERT_DEATH({ throw std::out_of_range("test"); }, "");
20+
}
21+
22+
TEST(CxaThrow, catchesInvalidArgument)
23+
{
24+
ASSERT_DEATH({ throw std::invalid_argument("test"); }, "");
25+
}
26+
27+
TEST(CxaThrow, catchesDomainError)
28+
{
29+
ASSERT_DEATH({ throw std::domain_error("test"); }, "");
30+
}
31+
32+
TEST(CxaThrow, catchesLengthError)
33+
{
34+
ASSERT_DEATH({ throw std::length_error("test"); }, "");
35+
}

src/libutil-tests/meson.build

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ sources = files(
8282
'xml-writer.cc',
8383
)
8484

85+
if have_cxa_throw
86+
sources += files('cxa-throw.cc')
87+
endif
88+
8589
include_dirs = [ include_directories('.') ]
8690

8791

src/libutil/meson.build

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ config_priv_h = configure_file(
119119

120120
subdir('nix-meson-build-support/common')
121121

122+
if have_cxa_throw
123+
deps_other += cxa_throw_dep
124+
endif
125+
122126
sources = [ config_priv_h ] + files(
123127
'archive.cc',
124128
'args.cc',

0 commit comments

Comments
 (0)