Skip to content

Commit ae07bd2

Browse files
authored
CXX-3065 guard against use of REQUIRE within APM callbacks (#1171)
* Use <bsoncxx/test/catch.hh> in test code * Add bsoncxx/test/exception_guard.hh * CXX-3065 guard against use of REQUIRE within APM callbacks * Mark mnmlstc headers as SYSTEM headers
1 parent f8c0b56 commit ae07bd2

14 files changed

+401
-41
lines changed

cmake/BsoncxxUtil.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ function(bsoncxx_add_library TARGET OUTPUT_NAME LINK_TYPE)
143143
if(BSONCXX_POLY_USE_MNMLSTC AND NOT BSONCXX_POLY_USE_SYSTEM_MNMLSTC)
144144
target_include_directories(
145145
${TARGET}
146+
SYSTEM
146147
PUBLIC
147148
$<BUILD_INTERFACE:${CORE_INCLUDE_DIR}>
148149
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/bsoncxx/v_noabi/bsoncxx/third_party/mnmlstc>

src/bsoncxx/test/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ add_executable(test_bson
3434
bson_util_itoa.cpp
3535
bson_validate.cpp
3636
bson_value.cpp
37+
exception_guard.cpp
3738
json.cpp
3839
oid.cpp
3940
optional.test.cpp
@@ -141,6 +142,8 @@ set_dist_list(src_bsoncxx_test_DIST
141142
bson_validate.cpp
142143
bson_value.cpp
143144
catch.hh
145+
exception_guard.cpp
146+
exception_guard.hh
144147
json.cpp
145148
oid.cpp
146149
optional.test.cpp

src/bsoncxx/test/exception_guard.cpp

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
// Copyright 2009-present MongoDB, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include <bsoncxx/test/exception_guard.hh>
16+
17+
//
18+
19+
#include <atomic>
20+
#include <chrono>
21+
#include <stdexcept>
22+
#include <string>
23+
#include <thread>
24+
#include <vector>
25+
26+
#include <bsoncxx/test/catch.hh>
27+
28+
namespace {
29+
30+
TEST_CASE("bsoncxx::test::exception_guard", "[test]") {
31+
using EGuard = bsoncxx::test::exception_guard_state;
32+
33+
EGuard eguard;
34+
35+
SECTION("init") {
36+
CHECK(eguard.ptr == nullptr);
37+
CHECK(eguard.file == bsoncxx::stdx::string_view(""));
38+
CHECK(eguard.line == 0u);
39+
CHECK(eguard.func == bsoncxx::stdx::string_view(""));
40+
}
41+
42+
SECTION("reset") {
43+
// clang-format off
44+
BSONCXX_TEST_EXCEPTION_GUARD_RESET(eguard); const auto line = __LINE__;
45+
// clang-format on
46+
47+
CHECK(eguard.ptr == nullptr);
48+
CHECK(eguard.file == bsoncxx::stdx::string_view(__FILE__));
49+
CHECK(eguard.line == line);
50+
CHECK(eguard.func == bsoncxx::stdx::string_view(__func__));
51+
}
52+
53+
SECTION("simple") {
54+
SECTION("no throw") {
55+
BSONCXX_TEST_EXCEPTION_GUARD_BEGIN(eguard);
56+
BSONCXX_TEST_EXCEPTION_GUARD_END(eguard);
57+
58+
CHECK(eguard.ptr == nullptr);
59+
CHECK(eguard.file == bsoncxx::stdx::string_view(""));
60+
CHECK(eguard.line == 0u);
61+
CHECK(eguard.func == bsoncxx::stdx::string_view(""));
62+
63+
BSONCXX_TEST_EXCEPTION_GUARD_CHECK(eguard);
64+
SUCCEED("no exception was thrown by the check");
65+
}
66+
67+
SECTION("throw") {
68+
struct EGuardException {};
69+
70+
EGuard expected;
71+
72+
// clang-format off
73+
BSONCXX_TEST_EXCEPTION_GUARD_BEGIN(eguard);
74+
throw EGuardException();
75+
BSONCXX_TEST_EXCEPTION_GUARD_END(eguard); BSONCXX_TEST_EXCEPTION_GUARD_RESET(expected);
76+
// clang-format on
77+
78+
CHECK(eguard.ptr != nullptr);
79+
CHECK(eguard.file == expected.file);
80+
CHECK(eguard.line == expected.line);
81+
CHECK(eguard.func == expected.func);
82+
CHECK(eguard.ignored.empty());
83+
84+
const auto check_expr = [&] { BSONCXX_TEST_EXCEPTION_GUARD_CHECK(eguard); };
85+
REQUIRE_THROWS_AS(check_expr(), EGuardException);
86+
REQUIRE_THROWS_AS(check_expr(), EGuardException); // Rethrow is OK.
87+
88+
CHECK(eguard.ptr != nullptr);
89+
CHECK(eguard.file == expected.file);
90+
CHECK(eguard.line == expected.line);
91+
CHECK(eguard.func == expected.func);
92+
CHECK(eguard.ignored.empty());
93+
}
94+
95+
SECTION("ignored") {
96+
struct EGuardException : std::runtime_error {
97+
using std::runtime_error::runtime_error;
98+
};
99+
100+
// clang-format off
101+
BSONCXX_TEST_EXCEPTION_GUARD_BEGIN(eguard);
102+
throw EGuardException("one");
103+
BSONCXX_TEST_EXCEPTION_GUARD_END(eguard);
104+
// clang-format on
105+
106+
REQUIRE(eguard.ptr != nullptr);
107+
REQUIRE(eguard.ignored.size() == 0u);
108+
109+
EGuard expected;
110+
111+
// clang-format off
112+
BSONCXX_TEST_EXCEPTION_GUARD_BEGIN(eguard);
113+
throw EGuardException("two");
114+
BSONCXX_TEST_EXCEPTION_GUARD_END(eguard); BSONCXX_TEST_EXCEPTION_GUARD_RESET(expected);
115+
// clang-format on
116+
117+
const auto npos = std::string::npos;
118+
119+
REQUIRE(eguard.ignored.size() == 1u);
120+
{
121+
const auto& log = eguard.ignored[0];
122+
const auto log_view = bsoncxx::stdx::string_view(log);
123+
124+
CAPTURE(log);
125+
CAPTURE(expected.file);
126+
CAPTURE(expected.line);
127+
CAPTURE(expected.func);
128+
129+
CHECK_THAT(log, Catch::Contains("two"));
130+
CHECK(log_view.find(expected.file) != npos);
131+
CHECK(log_view.find(std::to_string(expected.line)) != npos);
132+
CHECK(log_view.find(expected.func) == npos); // Func is not logged.
133+
}
134+
135+
// clang-format off
136+
BSONCXX_TEST_EXCEPTION_GUARD_BEGIN(eguard);
137+
throw Catch::TestFailureException(); // As-if by `REQUIRE(false)`.
138+
BSONCXX_TEST_EXCEPTION_GUARD_END(eguard); BSONCXX_TEST_EXCEPTION_GUARD_RESET(expected);
139+
// clang-format on
140+
141+
REQUIRE(eguard.ignored.size() == 2u);
142+
{
143+
const auto& log = eguard.ignored[1];
144+
const auto log_view = bsoncxx::stdx::string_view(log);
145+
146+
CAPTURE(log);
147+
CAPTURE(expected.file);
148+
CAPTURE(expected.line);
149+
CAPTURE(expected.func);
150+
151+
CHECK_THAT(log, Catch::Contains("Catch::TestFailureException"));
152+
CHECK(log_view.find(expected.file) != npos);
153+
CHECK(log_view.find(std::to_string(expected.line)) != npos);
154+
CHECK(log_view.find(expected.func) == npos); // Func is not logged.
155+
}
156+
157+
// The original exception.
158+
const auto check_expr = [&] { BSONCXX_TEST_EXCEPTION_GUARD_CHECK(eguard); };
159+
REQUIRE_THROWS_WITH(check_expr(), Catch::Contains("one"));
160+
}
161+
}
162+
163+
SECTION("concurrent") {
164+
EGuard expected;
165+
166+
struct EGuardException : std::runtime_error {
167+
int id;
168+
EGuardException(int i) : std::runtime_error(std::to_string(i)), id(i) {}
169+
};
170+
171+
{
172+
std::atomic_int counter;
173+
std::atomic_bool latch;
174+
175+
std::atomic_init(&counter, 0);
176+
std::atomic_init(&latch, false);
177+
178+
auto fn = [&] {
179+
// A simple latch to maximize parallelism.
180+
while (!latch.load()) {
181+
}
182+
183+
BSONCXX_TEST_EXCEPTION_GUARD_BEGIN(eguard);
184+
throw EGuardException(++counter);
185+
// clang-format off
186+
BSONCXX_TEST_EXCEPTION_GUARD_END(eguard); BSONCXX_TEST_EXCEPTION_GUARD_RESET(expected);
187+
// clang-format on
188+
};
189+
190+
std::vector<std::thread> threads;
191+
192+
threads.emplace_back(fn);
193+
threads.emplace_back(fn);
194+
threads.emplace_back(fn);
195+
196+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
197+
latch.store(true);
198+
199+
for (auto& thread : threads) {
200+
thread.join();
201+
}
202+
203+
REQUIRE(counter.load() == 3);
204+
}
205+
206+
REQUIRE(eguard.ptr != nullptr);
207+
CHECK(eguard.file == expected.file);
208+
CHECK(eguard.line == expected.line);
209+
CHECK(eguard.func == expected.func);
210+
211+
auto test = [&] {
212+
try {
213+
BSONCXX_TEST_EXCEPTION_GUARD_CHECK(eguard);
214+
FAIL("should have thrown an EGuardException");
215+
} catch (const EGuardException& e) {
216+
CAPTURE(e.id);
217+
CHECK(e.id > 0);
218+
}
219+
};
220+
221+
REQUIRE_NOTHROW(test());
222+
}
223+
}
224+
225+
} // namespace

src/bsoncxx/test/exception_guard.hh

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright 2009-present MongoDB, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#pragma once
16+
17+
#include <cstddef>
18+
#include <exception>
19+
#include <mutex>
20+
#include <sstream>
21+
#include <string>
22+
#include <vector>
23+
24+
#include <bsoncxx/stdx/string_view.hpp>
25+
#include <bsoncxx/test/catch.hh>
26+
27+
#include <bsoncxx/config/private/prelude.hh>
28+
29+
namespace bsoncxx {
30+
namespace test {
31+
32+
struct exception_guard_state {
33+
std::mutex m = {};
34+
std::exception_ptr ptr = {};
35+
stdx::string_view file = {};
36+
std::size_t line = {};
37+
stdx::string_view func = {};
38+
std::vector<std::string> ignored; // Cannot use INFO() in guarded regions.
39+
};
40+
41+
#define BSONCXX_TEST_EXCEPTION_GUARD_RESET(e) \
42+
if (1) { \
43+
((void)e); \
44+
std::lock_guard<std::mutex> _eguard_lock{e.m}; \
45+
e.ptr = {}; \
46+
e.file = __FILE__; \
47+
e.line = __LINE__; \
48+
e.func = __func__; \
49+
} else \
50+
((void)0)
51+
52+
// Marks the beginning of a guarded region wherein any exceptions thrown are stored by exception
53+
// guard state. Only the FIRST exception is stored; any others are caught and ignored.
54+
#define BSONCXX_TEST_EXCEPTION_GUARD_BEGIN(e) \
55+
try { \
56+
(void)e; \
57+
((void)0)
58+
59+
// Marks the end of a guarded region.
60+
#define BSONCXX_TEST_EXCEPTION_GUARD_END(e) \
61+
(void)e; \
62+
} \
63+
catch (...) { \
64+
std::lock_guard<std::mutex> _eguard_lock{e.m}; \
65+
if (!e.ptr) { \
66+
e.ptr = std::current_exception(); \
67+
e.file = __FILE__; \
68+
e.line = __LINE__; \
69+
e.func = __func__; \
70+
} else { \
71+
std::ostringstream oss; \
72+
oss << __FILE__ << ":" << __LINE__ << ": exception guard ignored: "; \
73+
try { \
74+
throw; \
75+
} catch (const std::exception& exc) { \
76+
oss << exc.what(); \
77+
} catch (const Catch::TestFailureException&) { \
78+
oss << "Catch::TestFailureException"; \
79+
} catch (...) { \
80+
oss << "unknown exception"; \
81+
} \
82+
e.ignored.push_back(oss.str()); \
83+
} \
84+
} \
85+
((void)0)
86+
87+
// Rethrow the stored exception if present.
88+
#define BSONCXX_TEST_EXCEPTION_GUARD_CHECK(e) \
89+
if (1) { \
90+
(void)e; \
91+
std::lock_guard<std::mutex> _eguard_lock{e.m}; \
92+
for (auto const& log : e.ignored) { \
93+
UNSCOPED_INFO(log); \
94+
} \
95+
if (e.ptr) { \
96+
std::rethrow_exception(e.ptr); \
97+
} \
98+
} else \
99+
((void)0)
100+
101+
} // namespace test
102+
} // namespace bsoncxx
103+
104+
#include <bsoncxx/config/private/postlude.hh>

src/bsoncxx/test/string_view.test.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
#include <bsoncxx/stdx/operators.hpp>
1818
#include <bsoncxx/stdx/string_view.hpp>
1919
#include <bsoncxx/stdx/type_traits.hpp>
20-
#include <third_party/catch/include/catch.hpp>
20+
#include <bsoncxx/test/catch.hh>
2121

2222
#include <bsoncxx/config/prelude.hpp>
2323

src/bsoncxx/test/type_traits.test.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#include <type_traits>
33

44
#include <bsoncxx/stdx/type_traits.hpp>
5-
#include <third_party/catch/include/catch.hpp>
5+
#include <bsoncxx/test/catch.hh>
66

77
#include <bsoncxx/config/prelude.hpp>
88

src/mongocxx/test/client_helpers.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <bsoncxx/stdx/optional.hpp>
2929
#include <bsoncxx/stdx/string_view.hpp>
3030
#include <bsoncxx/string/to_string.hpp>
31+
#include <bsoncxx/test/catch.hh>
3132
#include <bsoncxx/types.hpp>
3233
#include <bsoncxx/types/bson_value/view_or_value.hpp>
3334
#include <mongocxx/client.hpp>
@@ -36,7 +37,6 @@
3637
#include <mongocxx/exception/operation_exception.hpp>
3738
#include <mongocxx/private/libmongoc.hh>
3839
#include <mongocxx/test/client_helpers.hh>
39-
#include <third_party/catch/include/catch.hpp>
4040

4141
#include <mongocxx/config/private/prelude.hh>
4242

0 commit comments

Comments
 (0)