Skip to content

Commit f7b5b9b

Browse files
author
Tyler Hovanec
committed
Replace global LogError/LogWarning/LogInfo/LogDebug macros with inline functions
Motivation: `cpp/open3d/utility/Logging.h` previously defined `LogError`, `LogWarning`, `LogInfo`, and `LogDebug` as unprefixed preprocessor macros in the global preprocessor scope (`#define LogError(...) Logger::LogError_(...)`) to mimic a "macro in namespace" when callers write `utility::LogError(...)`. Because C++ preprocessor macros do not respect namespaces, including any Open3D header that transitively includes `open3d/utility/Logging.h` (such as `open3d/geometry/TriangleMesh.h` or `open3d/geometry/PointCloud.h`) pollutes the global macro namespace for the remainder of the translation unit. Any subsequently included third-party or application header that declares a method, function, or enum named `LogError`, `LogWarning`, `LogInfo`, or `LogDebug` (for example, custom logger interfaces or Abseil logging utilities) fails to compile because the preprocessor rewrites `foo.LogError(...)` into `foo.Logger::LogError_(__FILE__, __LINE__, ...)`. Why This Is Possible Now (Without Requiring C++20): When `Logging.h` was originally written in 2019 (targeting C++14 and older compilers), preprocessor macros (`__FILE__`, `__LINE__`, `OPEN3D_FUNCTION`) were the only portable way to capture caller source locations because Clang (< 9.0, Sept 2019) and MSVC (< VS 2019 16.6 / `_MSC_VER 1926`, May 2020) did not yet implement the compiler intrinsics (`__builtin_FILE()`, `__builtin_LINE()`, `__builtin_FUNCTION()`) created to back C++20 `std::source_location`. Even though the `<source_location>` header itself requires C++20, all toolchains supported by Open3D today (GCC 7+, Clang 9+, AppleClang, MSVC VS 2019 16.6+, and NVCC) expose `__builtin_FILE()`, `__builtin_LINE()`, and `__builtin_FUNCTION()` in C++17 mode. Why Functionality Is Equivalent: 1. Call-site syntax is unchanged: `utility::LogError(...)`, `open3d::utility::LogError(...)`, and unqualified `LogError(...)` (inside `namespace open3d::utility` or translation units with `using namespace open3d::utility;`) all resolve to the new `inline` function templates in `namespace open3d::utility`. 2. Caller source location (`file`, `line`, `function`) is still captured at the call site without C++20 `<source_location>` or macros: `open3d::utility::detail::LogLocation` wraps `const char *format` with a non-explicit `constexpr` constructor whose default arguments (`__builtin_FILE()`, `__builtin_LINE()`, `__builtin_FUNCTION()`) are evaluated at the caller's source location in C++17 across GCC, Clang, AppleClang, MSVC (VS 2019 16.6+), and NVCC. 3. Formatting and control-flow semantics are untouched: `LogError`, `LogWarning`, `LogInfo`, and `LogDebug` forward `loc.file`, `loc.line`, `loc.function`, `loc.format`, and `std::forward<Args>(args)...` directly to `Logger::LogError_`, `Logger::LogWarning_`, `Logger::LogInfo_`, and `Logger::LogDebug_`, preserving `[[noreturn]]` on `LogError` and existing `fmt` formatting behavior. Testing: - Extended `cpp/tests/utility/Logging.cpp` to verify that `utility::LogError` captures the caller's filename (`Logging.cpp`) and formatted message in the thrown `std::runtime_error`. - Added `Logging.NoGlobalMacroPollution` in `cpp/tests/utility/Logging.cpp` to verify that a class defining `LogError()`, `LogWarning()`, `LogInfo()`, and `LogDebug()` member functions compiles and executes cleanly after including `open3d/utility/Logging.h`.
1 parent b6c5e19 commit f7b5b9b

3 files changed

Lines changed: 123 additions & 58 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
- Fix Windows build failure for PyTorch ops due to PyTorch's bundled fmt (v11+) requiring `/utf-8` with MSVC (PR #7447)
100100
- Fix `TriangleMesh::SamplePointsPoissonDisk` performance by incrementally updating neighbor weights instead of recomputing them with additional KD-tree queries (issue #7449)
101101
- Add `GetMenu` for MenuBase for easy menu item/submenu control. (PR #7295)
102+
- Replace global `LogError`, `LogWarning`, `LogInfo`, and `LogDebug` preprocessor macros in `open3d/utility/Logging.h` with `inline` functions in `open3d::utility` to prevent global macro namespace pollution (PR #7602)
102103

103104
## 0.13
104105

‎cpp/open3d/utility/Logging.h‎

Lines changed: 84 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <functional>
1111
#include <memory>
1212
#include <string>
13+
#include <utility>
1314

1415
// NVCC does not support deprecated attribute on Windows prior to v11.
1516
#if defined(__CUDACC__) && defined(_MSC_VER) && __CUDACC_VER_MAJOR__ < 11
@@ -29,77 +30,23 @@
2930

3031
#include "open3d/Macro.h"
3132

32-
// Mimic "macro in namespace" by concatenating `utility::` and a macro.
33-
// Ref: https://stackoverflow.com/a/11791202
34-
//
35-
// We avoid using (format, ...) since in this case __VA_ARGS__ can be
36-
// empty, and the behavior of pruning trailing comma with ##__VA_ARGS__ is not
37-
// officially standard.
38-
// Ref: https://stackoverflow.com/a/28074198
39-
//
40-
// __PRETTY_FUNCTION__ has to be converted, otherwise a bug regarding [noreturn]
41-
// will be triggered.
42-
// Ref: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94742
43-
44-
// LogError throws now a runtime_error with the given error message. This
45-
// should be used if there is no point in continuing the given algorithm at
46-
// some point and the error is not returned in another way (e.g., via a
47-
// bool/int as return value).
48-
//
49-
// Usage : utility::LogError(format_string, arg0, arg1, ...);
50-
// Example: utility::LogError("name: {}, age: {}", "dog", 5);
51-
#define LogError(...) \
52-
Logger::LogError_(__FILE__, __LINE__, \
53-
static_cast<const char *>(OPEN3D_FUNCTION), __VA_ARGS__)
54-
55-
// LogWarning is used if an error occurs, but the error is also signaled
56-
// via a return value (i.e., there is no need to throw an exception). This
57-
// warning should further be used, if the algorithms encounters a state
58-
// that does not break its continuation, but the output is likely not to be
59-
// what the user expected.
60-
//
61-
// Usage : utility::LogWarning(format_string, arg0, arg1, ...);
62-
// Example: utility::LogWarning("name: {}, age: {}", "dog", 5);
63-
#define LogWarning(...) \
64-
Logger::LogWarning_(__FILE__, __LINE__, \
65-
static_cast<const char *>(OPEN3D_FUNCTION), \
66-
__VA_ARGS__)
67-
68-
// LogInfo is used to inform the user with expected output, e.g, pressed a
69-
// key in the visualizer prints helping information.
70-
//
71-
// Usage : utility::LogInfo(format_string, arg0, arg1, ...);
72-
// Example: utility::LogInfo("name: {}, age: {}", "dog", 5);
73-
#define LogInfo(...) \
74-
Logger::LogInfo_(__FILE__, __LINE__, \
75-
static_cast<const char *>(OPEN3D_FUNCTION), __VA_ARGS__)
76-
77-
// LogDebug is used to print debug/additional information on the state of
78-
// the algorithm.
79-
//
80-
// Usage : utility::LogDebug(format_string, arg0, arg1, ...);
81-
// Example: utility::LogDebug("name: {}, age: {}", "dog", 5);
82-
#define LogDebug(...) \
83-
Logger::LogDebug_(__FILE__, __LINE__, \
84-
static_cast<const char *>(OPEN3D_FUNCTION), __VA_ARGS__)
85-
8633
namespace open3d {
8734
namespace utility {
8835

8936
enum class VerbosityLevel {
90-
/// LogError throws now a runtime_error with the given error message. This
37+
/// LogError throws a runtime_error with the given error message. This
9138
/// should be used if there is no point in continuing the given algorithm at
9239
/// some point and the error is not returned in another way (e.g., via a
9340
/// bool/int as return value).
9441
Error = 0,
9542
/// LogWarning is used if an error occurs, but the error is also signaled
9643
/// via a return value (i.e., there is no need to throw an exception). This
97-
/// warning should further be used, if the algorithms encounters a state
44+
/// warning should further be used if the algorithm encounters a state
9845
/// that does not break its continuation, but the output is likely not to be
9946
/// what the user expected.
10047
Warning = 1,
101-
/// LogInfo is used to inform the user with expected output, e.g, pressed a
102-
/// key in the visualizer prints helping information.
48+
/// LogInfo is used to inform the user with expected output, e.g., pressing a
49+
/// key in the visualizer prints helpful information.
10350
Info = 2,
10451
/// LogDebug is used to print debug/additional information on the state of
10552
/// the algorithm.
@@ -235,6 +182,85 @@ class Logger {
235182
std::unique_ptr<Impl> impl_;
236183
};
237184

185+
namespace detail {
186+
187+
/// Captures the format string and caller source location at the call site.
188+
///
189+
/// Because a C++ variadic parameter pack (`Args &&...args`) consumes all
190+
/// trailing arguments, defaulted source-location parameters cannot be placed
191+
/// after `Args...`. Instead, `LogLocation` wraps the first argument (`format`)
192+
/// via a non-explicit converting constructor whose default arguments
193+
/// (`__builtin_FILE()`, `__builtin_LINE()`, `__builtin_FUNCTION()`) are
194+
/// evaluated at the caller's call site, leaving `Args &&...args` to deduce any
195+
/// remaining format arguments.
196+
///
197+
/// Ref: https://twdev.blog/2023/03/variadic_templates/#implicit-type-conversion
198+
/// Ref: https://stackoverflow.com/a/66402319
199+
/// Ref: https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html
200+
struct LogLocation {
201+
const char *format;
202+
const char *file;
203+
int line;
204+
const char *function;
205+
206+
constexpr LogLocation(const char *fmt,
207+
const char *f = __builtin_FILE(),
208+
int l = __builtin_LINE(),
209+
const char *fn = __builtin_FUNCTION()) noexcept
210+
: format(fmt), file(f), line(l), function(fn) {}
211+
};
212+
213+
} // namespace detail
214+
215+
/// LogError throws a runtime_error with the given error message. This
216+
/// should be used if there is no point in continuing the given algorithm at
217+
/// some point and the error is not returned in another way (e.g., via a
218+
/// bool/int as return value).
219+
///
220+
/// Usage : utility::LogError(format_string, arg0, arg1, ...);
221+
/// Example: utility::LogError("name: {}, age: {}", "dog", 5);
222+
template <typename... Args>
223+
[[noreturn]] inline void LogError(detail::LogLocation loc, Args &&...args) {
224+
Logger::LogError_(loc.file, loc.line, loc.function, loc.format,
225+
std::forward<Args>(args)...);
226+
}
227+
228+
/// LogWarning is used if an error occurs, but the error is also signaled
229+
/// via a return value (i.e., there is no need to throw an exception). This
230+
/// warning should further be used if the algorithm encounters a state
231+
/// that does not break its continuation, but the output is likely not to be
232+
/// what the user expected.
233+
///
234+
/// Usage : utility::LogWarning(format_string, arg0, arg1, ...);
235+
/// Example: utility::LogWarning("name: {}, age: {}", "dog", 5);
236+
template <typename... Args>
237+
inline void LogWarning(detail::LogLocation loc, Args &&...args) {
238+
Logger::LogWarning_(loc.file, loc.line, loc.function, loc.format,
239+
std::forward<Args>(args)...);
240+
}
241+
242+
/// LogInfo is used to inform the user with expected output, e.g., pressing a
243+
/// key in the visualizer prints helpful information.
244+
///
245+
/// Usage : utility::LogInfo(format_string, arg0, arg1, ...);
246+
/// Example: utility::LogInfo("name: {}, age: {}", "dog", 5);
247+
template <typename... Args>
248+
inline void LogInfo(detail::LogLocation loc, Args &&...args) {
249+
Logger::LogInfo_(loc.file, loc.line, loc.function, loc.format,
250+
std::forward<Args>(args)...);
251+
}
252+
253+
/// LogDebug is used to print debug/additional information on the state of
254+
/// the algorithm.
255+
///
256+
/// Usage : utility::LogDebug(format_string, arg0, arg1, ...);
257+
/// Example: utility::LogDebug("name: {}, age: {}", "dog", 5);
258+
template <typename... Args>
259+
inline void LogDebug(detail::LogLocation loc, Args &&...args) {
260+
Logger::LogDebug_(loc.file, loc.line, loc.function, loc.format,
261+
std::forward<Args>(args)...);
262+
}
263+
238264
/// Set global verbosity level of Open3D
239265
///
240266
/// \param level Messages with equal or less than verbosity_level verbosity will

‎cpp/tests/utility/Logging.cpp‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,57 @@
77

88
#include "open3d/utility/Logging.h"
99

10+
#include <string>
11+
1012
#include "tests/Tests.h"
1113

14+
namespace {
15+
16+
// A dummy class declared after including "open3d/utility/Logging.h" that
17+
// defines member functions with the same names as Open3D's logging functions.
18+
// If LogError/LogWarning/LogInfo/LogDebug were defined as unprefixed macros in
19+
// the global preprocessor scope, the preprocessor would rewrite these member
20+
// declarations and call sites into invalid `Logger::Log*_(...)` expressions.
21+
struct UnrelatedLogger {
22+
int LogError() const { return 1; }
23+
int LogWarning() const { return 2; }
24+
int LogInfo() const { return 3; }
25+
int LogDebug() const { return 4; }
26+
};
27+
28+
} // namespace
29+
1230
namespace open3d {
1331
namespace tests {
1432

1533
TEST(Logging, LogError) {
1634
EXPECT_THROW(utility::LogError("Example exception message."),
1735
std::runtime_error);
36+
try {
37+
utility::LogError("Formatted error: {}", 42);
38+
FAIL() << "Expected std::runtime_error";
39+
} catch (const std::runtime_error &e) {
40+
const std::string msg = e.what();
41+
EXPECT_NE(msg.find("Formatted error: 42"), std::string::npos);
42+
EXPECT_NE(msg.find("Logging.cpp"), std::string::npos);
43+
}
1844
}
1945

2046
TEST(Logging, LogInfo) {
2147
utility::LogInfo("{}", "Example shape print {1, 2, 3}.");
2248
}
2349

50+
// Verifies that including "open3d/utility/Logging.h" does not pollute the
51+
// global preprocessor namespace with LogError/LogWarning/LogInfo/LogDebug
52+
// macros, allowing unrelated types and libraries to define or call methods
53+
// with those names in the same translation unit.
54+
TEST(Logging, NoGlobalMacroPollution) {
55+
UnrelatedLogger logger;
56+
EXPECT_EQ(logger.LogError(), 1);
57+
EXPECT_EQ(logger.LogWarning(), 2);
58+
EXPECT_EQ(logger.LogInfo(), 3);
59+
EXPECT_EQ(logger.LogDebug(), 4);
60+
}
61+
2462
} // namespace tests
2563
} // namespace open3d

0 commit comments

Comments
 (0)