diff --git a/pkg/BUILD.bazel b/pkg/BUILD.bazel index a3a11399ad51e..25513f5c088af 100644 --- a/pkg/BUILD.bazel +++ b/pkg/BUILD.bazel @@ -362,6 +362,7 @@ cc_dist_library( "//src/google/protobuf:arena_align", "//src/google/protobuf:protobuf_nowkt", "//src/google/protobuf:wkt_cc_proto", + "//src/google/protobuf:reflection_mode", "//src/google/protobuf/compiler:importer", "//src/google/protobuf/json", "//src/google/protobuf/util:delimited_message_util", diff --git a/src/google/protobuf/BUILD.bazel b/src/google/protobuf/BUILD.bazel index e3db90cc513e3..90dbb7b33a736 100644 --- a/src/google/protobuf/BUILD.bazel +++ b/src/google/protobuf/BUILD.bazel @@ -1314,6 +1314,31 @@ cc_test( ], ) +cc_library( + name = "reflection_mode", + srcs = ["reflection_mode.cc"], + hdrs = ["reflection_mode.h"], + include_prefix = "google/protobuf", + visibility = [ + "//:__subpackages__", + "//src/google/protobuf:__subpackages__", + ], + deps = [ + ":port_def", + ":protobuf", + ], +) + +cc_test( + name = "reflection_mode_test", + srcs = ["reflection_mode_test.cc"], + deps = [ + ":reflection_mode", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) + ################################################################################ # Helper targets for Kotlin tests ################################################################################ diff --git a/src/google/protobuf/reflection_mode.cc b/src/google/protobuf/reflection_mode.cc new file mode 100644 index 0000000000000..7611c7b69cdda --- /dev/null +++ b/src/google/protobuf/reflection_mode.cc @@ -0,0 +1,57 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "google/protobuf/reflection_mode.h" + +// Must be included last. +#include "google/protobuf/port_def.inc" + +namespace google { +namespace protobuf { +namespace internal { + +#if !defined(PROTOBUF_NO_THREADLOCAL) + +#if defined(PROTOBUF_USE_DLLS) +ReflectionMode& ScopedReflectionMode::reflection_mode() { + static PROTOBUF_THREAD_LOCAL ReflectionMode reflection_mode = + ReflectionMode::kDefault; + return reflection_mode; +} +#else +PROTOBUF_CONSTINIT PROTOBUF_THREAD_LOCAL ReflectionMode + ScopedReflectionMode::reflection_mode_ = ReflectionMode::kDefault; +#endif + +#endif + +} // namespace internal +} // namespace protobuf +} // namespace google diff --git a/src/google/protobuf/reflection_mode.h b/src/google/protobuf/reflection_mode.h new file mode 100644 index 0000000000000..e9ff3408f0cfc --- /dev/null +++ b/src/google/protobuf/reflection_mode.h @@ -0,0 +1,165 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// This header provides support for a per thread 'reflection mode'. +// +// Some protocol buffer optimizations use interceptors to determine which +// fields are effectively used in the application. These optimizations are +// disabled if certain reflection calls are intercepted as the assumption is +// then that any field data can be accessed. +// +// The 'reflection mode' defined in this header is intended to be used by +// logic such as ad-hoc profilers to indicate that any scoped reflection usage +// is not originating from, or affecting application code. This reflection mode +// can then be used by such interceptors to ignore any reflection calls not +// affecting the application behavior. + +#ifndef GOOGLE_PROTOBUF_REFLECTION_MODE_H__ +#define GOOGLE_PROTOBUF_REFLECTION_MODE_H__ + +#include + +// Must be included last. +#include "google/protobuf/port_def.inc" + +namespace google { +namespace protobuf { +namespace internal { + +// The ReflectionModes are ordered in observability levels: +// kDefault: Lowest level. All reflection calls are observable. +// kDebugString: Middle level. Only reflection calls in Message::DebugString are +// observable. +// kDiagnostics: Highest level. No reflection calls are observable. +enum class ReflectionMode { + kDefault, + kDebugString, + kDiagnostics, +}; + +// Returns the current ReflectionMode of protobuf for the current thread. This +// reflection mode can be used by interceptors to ignore any reflection calls +// not affecting the application behavior. +// Always returns `kDefault' if the current platform does not support thread +// local data. +ReflectionMode GetReflectionMode(); + +// Scoping class to set the specific ReflectionMode for a given scope. +class PROTOBUF_EXPORT ScopedReflectionMode final { + public: + // Sets the current reflection mode, which will be restored at destruction. + // The reflection mode can only be 'elevated' in observability levels. + // For instance, if the current mode is `kDiagnostics` then scope will remain + // unchanged regardless of `mode`. + explicit ScopedReflectionMode(ReflectionMode mode); + + // Restores the previous reflection mode. + ~ScopedReflectionMode(); + + // Returns the scoped ReflectionMode for the current thread. + // See `GetReflectionMode()` for more information on purpose and usage. + static ReflectionMode current_reflection_mode(); + + // ScopedReflectionMode is only intended to be used as a locally scoped + // instance to set a reflection mode for the code scoped by this instance. + ScopedReflectionMode(const ScopedReflectionMode&) = delete; + ScopedReflectionMode& operator=(const ScopedReflectionMode&) = delete; + + private: +#if !defined(PROTOBUF_NO_THREADLOCAL) + const ReflectionMode previous_mode_; +#if defined(PROTOBUF_USE_DLLS) + static ReflectionMode& reflection_mode(); +#else + PROTOBUF_CONSTINIT static PROTOBUF_THREAD_LOCAL ReflectionMode + reflection_mode_; +#endif // PROTOBUF_USE_DLLS +#endif // !PROTOBUF_NO_THREADLOCAL +}; + +#if !defined(PROTOBUF_NO_THREADLOCAL) + +#if defined(PROTOBUF_USE_DLLS) + +inline ScopedReflectionMode::ScopedReflectionMode(ReflectionMode mode) + : previous_mode_(reflection_mode()) { + if (mode > reflection_mode()) { + reflection_mode() = mode; + } +} + +inline ScopedReflectionMode::~ScopedReflectionMode() { + reflection_mode() = previous_mode_; +} + +inline ReflectionMode ScopedReflectionMode::current_reflection_mode() { + return reflection_mode(); +} + +#else + +inline ScopedReflectionMode::ScopedReflectionMode(ReflectionMode mode) + : previous_mode_(reflection_mode_) { + if (mode > reflection_mode_) { + reflection_mode_ = mode; + } +} + +inline ScopedReflectionMode::~ScopedReflectionMode() { + reflection_mode_ = previous_mode_; +} + +inline ReflectionMode ScopedReflectionMode::current_reflection_mode() { + return reflection_mode_; +} + +#endif // PROTOBUF_USE_DLLS + +#else + +inline ScopedReflectionMode::ScopedReflectionMode(ReflectionMode mode) {} +inline ScopedReflectionMode::~ScopedReflectionMode() {} +inline ReflectionMode ScopedReflectionMode::current_reflection_mode() { + return ReflectionMode::kDefault; +} + +#endif // !PROTOBUF_NO_THREADLOCAL + +inline ReflectionMode GetReflectionMode() { + return ScopedReflectionMode::current_reflection_mode(); +} + +} // namespace internal +} // namespace protobuf +} // namespace google + +#include "google/protobuf/port_undef.inc" + +#endif // GOOGLE_PROTOBUF_REFLECTION_MODE_H__ diff --git a/src/google/protobuf/reflection_mode_test.cc b/src/google/protobuf/reflection_mode_test.cc new file mode 100644 index 0000000000000..9320888add62e --- /dev/null +++ b/src/google/protobuf/reflection_mode_test.cc @@ -0,0 +1,115 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#include "google/protobuf/reflection_mode.h" + +#include +#include + +namespace google { +namespace protobuf { +namespace internal { + +#ifndef PROTOBUF_NO_THREADLOCAL + +TEST(ReflectionModeTest, SimpleScopedReflection) { + ASSERT_EQ(ScopedReflectionMode::current_reflection_mode(), + ReflectionMode::kDefault); + ScopedReflectionMode scope(ReflectionMode::kDiagnostics); + EXPECT_EQ(ScopedReflectionMode::current_reflection_mode(), + ReflectionMode::kDiagnostics); +} + +TEST(ReflectionModeTest, CleanNestedScopedReflection) { + ASSERT_EQ(ScopedReflectionMode::current_reflection_mode(), + ReflectionMode::kDefault); + { + ScopedReflectionMode scope1(ReflectionMode::kDebugString); + EXPECT_EQ(ScopedReflectionMode::current_reflection_mode(), + ReflectionMode::kDebugString); + { + ScopedReflectionMode scope2(ReflectionMode::kDiagnostics); + EXPECT_EQ(ScopedReflectionMode::current_reflection_mode(), + ReflectionMode::kDiagnostics); + } + EXPECT_EQ(ScopedReflectionMode::current_reflection_mode(), + ReflectionMode::kDebugString); + } + EXPECT_EQ(ScopedReflectionMode::current_reflection_mode(), + ReflectionMode::kDefault); +} + +TEST(ReflectionModeTest, UglyNestedScopedReflection) { + ASSERT_EQ(ScopedReflectionMode::current_reflection_mode(), + ReflectionMode::kDefault); + ScopedReflectionMode scope1(ReflectionMode::kDebugString); + EXPECT_EQ(ScopedReflectionMode::current_reflection_mode(), + ReflectionMode::kDebugString); + ScopedReflectionMode scope2(ReflectionMode::kDiagnostics); + EXPECT_EQ(ScopedReflectionMode::current_reflection_mode(), + ReflectionMode::kDiagnostics); +} + +TEST(ReflectionModeTest, DebugStringModeDoesNotReplaceDiagnosticsMode) { + ASSERT_EQ(ScopedReflectionMode::current_reflection_mode(), + ReflectionMode::kDefault); + ScopedReflectionMode scope1(ReflectionMode::kDiagnostics); + { + ScopedReflectionMode scope2(ReflectionMode::kDebugString); + EXPECT_EQ(ScopedReflectionMode::current_reflection_mode(), + ReflectionMode::kDiagnostics); + } +} + +#else + +TEST(ReflectionModeTest, AlwaysReturnDefaultWhenNoThreadLocal) { + ASSERT_EQ(ScopedReflectionMode::current_reflection_mode(), + ReflectionMode::kDefault); + { + ScopedReflectionMode scope1(ReflectionMode::kDebugString); + EXPECT_EQ(ScopedReflectionMode::current_reflection_mode(), + ReflectionMode::kDefault); + { + ScopedReflectionMode scope2(ReflectionMode::kDiagnostics); + EXPECT_EQ(ScopedReflectionMode::current_reflection_mode(), + ReflectionMode::kDefault); + } + EXPECT_EQ(ScopedReflectionMode::current_reflection_mode(), + ReflectionMode::kDefault); + } + EXPECT_EQ(ScopedReflectionMode::current_reflection_mode(), + ReflectionMode::kDefault); +} + +#endif + +} // namespace internal +} // namespace protobuf +} // namespace google