Skip to content

Commit 6fe7836

Browse files
committed
Protect against locking formatters
1 parent 4fcc317 commit 6fe7836

File tree

3 files changed

+61
-8
lines changed

3 files changed

+61
-8
lines changed

include/fmt/base.h

+23-4
Original file line numberDiff line numberDiff line change
@@ -1162,6 +1162,20 @@ using appender = basic_appender<char>;
11621162

11631163
namespace detail {
11641164

1165+
template <typename T, typename Enable = void>
1166+
struct locking : std::true_type {};
1167+
template <typename T>
1168+
struct locking<T, void_t<typename formatter<remove_cvref_t<T>>::nonlocking>>
1169+
: std::false_type {};
1170+
1171+
template <typename T = int> FMT_CONSTEXPR inline auto is_locking() -> bool {
1172+
return locking<T>::value;
1173+
}
1174+
template <typename T1, typename T2, typename... Tail>
1175+
FMT_CONSTEXPR inline auto is_locking() -> bool {
1176+
return locking<T1>::value || is_locking<T2, Tail...>();
1177+
}
1178+
11651179
// An optimized version of std::copy with the output value type (T).
11661180
template <typename T, typename InputIt>
11671181
auto copy(InputIt begin, InputIt end, appender out) -> appender {
@@ -2796,6 +2810,8 @@ struct formatter<T, Char,
27962810
detail::dynamic_format_specs<Char> specs_;
27972811

27982812
public:
2813+
using nonlocking = void;
2814+
27992815
template <typename ParseContext>
28002816
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> const Char* {
28012817
if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin();
@@ -2978,6 +2994,7 @@ FMT_NODISCARD FMT_INLINE auto formatted_size(format_string<T...> fmt,
29782994

29792995
FMT_API void vprint(string_view fmt, format_args args);
29802996
FMT_API void vprint(FILE* f, string_view fmt, format_args args);
2997+
FMT_API void vprint_locked(FILE* f, string_view fmt, format_args args);
29812998
FMT_API void vprintln(FILE* f, string_view fmt, format_args args);
29822999

29833000
/**
@@ -2993,8 +3010,9 @@ FMT_API void vprintln(FILE* f, string_view fmt, format_args args);
29933010
template <typename... T>
29943011
FMT_INLINE void print(format_string<T...> fmt, T&&... args) {
29953012
const auto& vargs = fmt::make_format_args(args...);
2996-
return detail::is_utf8() ? vprint(fmt, vargs)
2997-
: detail::vprint_mojibake(stdout, fmt, vargs);
3013+
if (!detail::is_utf8()) return detail::vprint_mojibake(stdout, fmt, vargs);
3014+
return detail::is_locking<T...>() ? vprint(fmt, vargs)
3015+
: vprint_locked(stdout, fmt, vargs);
29983016
}
29993017

30003018
/**
@@ -3010,8 +3028,9 @@ FMT_INLINE void print(format_string<T...> fmt, T&&... args) {
30103028
template <typename... T>
30113029
FMT_INLINE void print(FILE* f, format_string<T...> fmt, T&&... args) {
30123030
const auto& vargs = fmt::make_format_args(args...);
3013-
return detail::is_utf8() ? vprint(f, fmt, vargs)
3014-
: detail::vprint_mojibake(f, fmt, vargs);
3031+
if (!detail::is_utf8()) return detail::vprint_mojibake(f, fmt, vargs);
3032+
return detail::is_locking<T...>() ? vprint(f, fmt, vargs)
3033+
: vprint_locked(f, fmt, vargs);
30153034
}
30163035

30173036
/**

include/fmt/format-inl.h

+6-4
Original file line numberDiff line numberDiff line change
@@ -1677,15 +1677,17 @@ FMT_FUNC void print(std::FILE* f, string_view text) {
16771677
} // namespace detail
16781678

16791679
FMT_FUNC void vprint(std::FILE* f, string_view fmt, format_args args) {
1680-
if (detail::file_ref(f).is_buffered()) {
1681-
auto&& buffer = detail::file_print_buffer(f);
1682-
return detail::vformat_to(buffer, fmt, args);
1683-
}
16841680
auto buffer = memory_buffer();
16851681
detail::vformat_to(buffer, fmt, args);
16861682
detail::print(f, {buffer.data(), buffer.size()});
16871683
}
16881684

1685+
FMT_FUNC void vprint_locked(std::FILE* f, string_view fmt, format_args args) {
1686+
if (!detail::file_ref(f).is_buffered()) return vprint(f, fmt, args);
1687+
auto&& buffer = detail::file_print_buffer(f);
1688+
return detail::vformat_to(buffer, fmt, args);
1689+
}
1690+
16891691
FMT_FUNC void vprintln(std::FILE* f, string_view fmt, format_args args) {
16901692
auto buffer = memory_buffer();
16911693
detail::vformat_to(buffer, fmt, args);

test/format-test.cc

+32
Original file line numberDiff line numberDiff line change
@@ -1785,6 +1785,38 @@ TEST(format_test, line_buffering) {
17851785
}
17861786
#endif
17871787

1788+
struct deadlockable {
1789+
int value = 0;
1790+
mutable std::mutex mutex;
1791+
};
1792+
1793+
FMT_BEGIN_NAMESPACE
1794+
template <> struct formatter<deadlockable> {
1795+
constexpr auto parse(fmt::format_parse_context& ctx) -> decltype(ctx.begin()) {
1796+
return ctx.begin();
1797+
}
1798+
1799+
auto format(const deadlockable& d, fmt::format_context& ctx) const
1800+
-> decltype(ctx.out()) {
1801+
std::lock_guard<std::mutex> lock(d.mutex);
1802+
return fmt::format_to(ctx.out(), "{}", d.value);
1803+
}
1804+
};
1805+
FMT_END_NAMESPACE
1806+
1807+
TEST(format_test, locking_formatter) {
1808+
auto f = fmt::buffered_file("/dev/null", "w");
1809+
auto&& d = deadlockable();
1810+
auto t = std::thread([&]() {
1811+
fmt::print(f.get(), "start t\n");
1812+
std::lock_guard<std::mutex> lock(d.mutex);
1813+
for (int i = 0; i < 1000000; ++i) d.value += 10;
1814+
fmt::print(f.get(), "done\n");
1815+
});
1816+
for (int i = 0; i < 100; ++i) fmt::print(f.get(), "{}", d);
1817+
t.join();
1818+
}
1819+
17881820
TEST(format_test, variadic) {
17891821
EXPECT_EQ(fmt::format("{}c{}", "ab", 1), "abc1");
17901822
}

0 commit comments

Comments
 (0)