Add `absl::down_cast` PiperOrigin-RevId: 850445526 Change-Id: I15e34dc543dc5aa72ae58ff471410d219fef2444
diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index cd633a1..10e6cd1 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake
@@ -6,6 +6,7 @@ "algorithm/container.h" "base/attributes.h" "base/call_once.h" + "base/casts.cc" "base/casts.h" "base/config.h" "base/const_init.h"
diff --git a/absl/base/BUILD.bazel b/absl/base/BUILD.bazel index 1486722..1d27796 100644 --- a/absl/base/BUILD.bazel +++ b/absl/base/BUILD.bazel
@@ -257,6 +257,7 @@ cc_library( name = "base", srcs = [ + "casts.cc", "internal/cycleclock.cc", "internal/spinlock.cc", "internal/sysinfo.cc", @@ -296,8 +297,6 @@ ":config", ":core_headers", ":cycleclock_internal", - ":dynamic_annotations", - ":log_severity", ":nullability", ":raw_logging_internal", ":spinlock_wait", @@ -612,7 +611,7 @@ linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":base", - ":core_headers", + ":config", "@googletest//:gtest", "@googletest//:gtest_main", ],
diff --git a/absl/base/CMakeLists.txt b/absl/base/CMakeLists.txt index e257f99..84decab 100644 --- a/absl/base/CMakeLists.txt +++ b/absl/base/CMakeLists.txt
@@ -263,6 +263,7 @@ "internal/unscaledcycleclock.h" "internal/unscaledcycleclock_config.h" SRCS + "casts.cc" "internal/cycleclock.cc" "internal/spinlock.cc" "internal/sysinfo.cc"
diff --git a/absl/base/casts.cc b/absl/base/casts.cc new file mode 100644 index 0000000..d864a8c --- /dev/null +++ b/absl/base/casts.cc
@@ -0,0 +1,61 @@ +// Copyright 2025 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/base/casts.h" + +#include <cstdlib> +#include <string> + +#include "absl/base/config.h" +#include "absl/base/internal/raw_logging.h" + +#ifdef ABSL_INTERNAL_HAS_CXA_DEMANGLE +#include <cxxabi.h> +#endif + +namespace absl { +ABSL_NAMESPACE_BEGIN + +namespace base_internal { + +namespace { + +std::string DemangleCppString(const char* mangled) { + std::string out; + int status = 0; + char* demangled = nullptr; +#ifdef ABSL_INTERNAL_HAS_CXA_DEMANGLE + demangled = abi::__cxa_demangle(mangled, nullptr, nullptr, &status); +#endif + if (status == 0 && demangled != nullptr) { + out.append(demangled); + free(demangled); + } else { + out.append(mangled); + } + return out; +} + +} // namespace + +void BadDownCastCrash(const char* source_type, const char* target_type) { + ABSL_RAW_LOG(FATAL, "down cast from %s to %s failed", + DemangleCppString(source_type).c_str(), + DemangleCppString(target_type).c_str()); +} + +} // namespace base_internal + +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/base/casts.h b/absl/base/casts.h index 35f5f93..480855a 100644 --- a/absl/base/casts.h +++ b/absl/base/casts.h
@@ -34,7 +34,10 @@ #endif // defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L #include "absl/base/attributes.h" +#include "absl/base/config.h" #include "absl/base/macros.h" +#include "absl/base/optimization.h" +#include "absl/base/options.h" #include "absl/meta/type_traits.h" namespace absl { @@ -191,6 +194,112 @@ #endif // defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L +namespace base_internal { + +[[noreturn]] ABSL_ATTRIBUTE_NOINLINE void BadDownCastCrash( + const char* source_type, const char* target_type); + +template <typename To, typename From> +inline void ValidateDownCast(From* f ABSL_ATTRIBUTE_UNUSED) { + // Assert only if RTTI is enabled and in debug mode or hardened asserts are + // enabled. +#ifdef ABSL_INTERNAL_HAS_RTTI +#if !defined(NDEBUG) || (ABSL_OPTION_HARDENED == 1 || ABSL_OPTION_HARDENED == 2) + // Suppress erroneous nonnull comparison warning on older GCC. +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnonnull-compare" +#endif + if (ABSL_PREDICT_FALSE(f != nullptr && dynamic_cast<To>(f) == nullptr)) { +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + absl::base_internal::BadDownCastCrash( + typeid(*f).name(), typeid(std::remove_pointer_t<To>).name()); + } +#endif +#endif +} + +} // namespace base_internal + +// An "upcast", i.e. a conversion from a pointer to an object to a pointer to a +// base subobject, always succeeds if the base is unambiguous and accessible, +// and so it's fine to use implicit_cast. +// +// A "downcast", i.e. a conversion from a pointer to an object to a pointer +// to a more-derived object that may contain the original object as a base +// subobject, cannot safely be done using static_cast, because you do not +// generally know whether the source object is really the base subobject of +// a containing, more-derived object of the target type. Thus, when you +// downcast in a polymorphic type hierarchy, you should use the following +// function template. +// +// This function only returns null when the input is null. In debug mode, we +// use dynamic_cast to double-check whether the downcast is legal (we die if +// it's not). In normal mode, we do the efficient static_cast instead. Because +// the process will die in debug mode, it's important to test to make sure the +// cast is legal before calling this function! +// +// dynamic_cast should be avoided except as allowed by the style guide +// (https://google.github.io/styleguide/cppguide.html#Run-Time_Type_Information__RTTI_). + +template <typename To, typename From> // use like this: down_cast<T*>(foo); +[[nodiscard]] +inline To down_cast(From* f) { // so we only accept pointers + static_assert(std::is_pointer<To>::value, "target type not a pointer"); + // dynamic_cast allows casting to the same type or a more cv-qualified + // version of the same type without them being polymorphic. + if constexpr (!std::is_same<std::remove_cv_t<std::remove_pointer_t<To>>, + std::remove_cv_t<From>>::value) { + static_assert(std::is_polymorphic<From>::value, + "source type must be polymorphic"); + static_assert(std::is_polymorphic<std::remove_pointer_t<To>>::value, + "target type must be polymorphic"); + } + static_assert( + std::is_convertible<std::remove_cv_t<std::remove_pointer_t<To>>*, + std::remove_cv_t<From>*>::value, + "target type not derived from source type"); + + absl::base_internal::ValidateDownCast<To>(f); + + return static_cast<To>(f); +} + +// Overload of down_cast for references. Use like this: +// absl::down_cast<T&>(foo). The code is slightly convoluted because we're still +// using the pointer form of dynamic cast. (The reference form throws an +// exception if it fails.) +// +// There's no need for a special const overload either for the pointer +// or the reference form. If you call down_cast with a const T&, the +// compiler will just bind From to const T. +template <typename To, typename From> +[[nodiscard]] +inline To down_cast(From& f) { + static_assert(std::is_lvalue_reference<To>::value, + "target type not a reference"); + // dynamic_cast allows casting to the same type or a more cv-qualified + // version of the same type without them being polymorphic. + if constexpr (!std::is_same<std::remove_cv_t<std::remove_reference_t<To>>, + std::remove_cv_t<From>>::value) { + static_assert(std::is_polymorphic<From>::value, + "source type must be polymorphic"); + static_assert(std::is_polymorphic<std::remove_reference_t<To>>::value, + "target type must be polymorphic"); + } + static_assert( + std::is_convertible<std::remove_cv_t<std::remove_reference_t<To>>*, + std::remove_cv_t<From>*>::value, + "target type not derived from source type"); + + absl::base_internal::ValidateDownCast<std::remove_reference_t<To>*>( + std::addressof(f)); + + return static_cast<To>(f); +} + ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/base/casts_test.cc b/absl/base/casts_test.cc index 25f92cc..772225e 100644 --- a/absl/base/casts_test.cc +++ b/absl/base/casts_test.cc
@@ -18,40 +18,134 @@ #include <utility> #include "gtest/gtest.h" +#include "absl/base/options.h" namespace { -struct Base { - explicit Base(int value) : x(value) {} - Base(const Base& other) = delete; - Base& operator=(const Base& other) = delete; +struct BaseForImplicitCast { + explicit BaseForImplicitCast(int value) : x(value) {} + BaseForImplicitCast(const BaseForImplicitCast& other) = delete; + BaseForImplicitCast& operator=(const BaseForImplicitCast& other) = delete; int x; }; -struct Derived : Base { - explicit Derived(int value) : Base(value) {} +struct DerivedForImplicitCast : BaseForImplicitCast { + explicit DerivedForImplicitCast(int value) : BaseForImplicitCast(value) {} }; +static_assert(std::is_same_v<decltype(absl::implicit_cast<BaseForImplicitCast&>( + std::declval<DerivedForImplicitCast&>())), + BaseForImplicitCast&>); static_assert( - std::is_same_v< - decltype(absl::implicit_cast<Base&>(std::declval<Derived&>())), Base&>); -static_assert(std::is_same_v<decltype(absl::implicit_cast<const Base&>( - std::declval<Derived>())), - const Base&>); + std::is_same_v<decltype(absl::implicit_cast<const BaseForImplicitCast&>( + std::declval<DerivedForImplicitCast>())), + const BaseForImplicitCast&>); TEST(ImplicitCastTest, LValueReference) { - Derived derived(5); - EXPECT_EQ(&absl::implicit_cast<Base&>(derived), &derived); - EXPECT_EQ(&absl::implicit_cast<const Base&>(derived), &derived); + DerivedForImplicitCast derived(5); + EXPECT_EQ(&absl::implicit_cast<BaseForImplicitCast&>(derived), &derived); + EXPECT_EQ(&absl::implicit_cast<const BaseForImplicitCast&>(derived), + &derived); } TEST(ImplicitCastTest, RValueReference) { - Derived derived(5); - Base&& base = absl::implicit_cast<Base&&>(std::move(derived)); + DerivedForImplicitCast derived(5); + BaseForImplicitCast&& base = + absl::implicit_cast<BaseForImplicitCast&&>(std::move(derived)); EXPECT_EQ(&base, &derived); - const Derived cderived(6); - const Base&& cbase = absl::implicit_cast<const Base&&>(std::move(cderived)); + const DerivedForImplicitCast cderived(6); + const BaseForImplicitCast&& cbase = + absl::implicit_cast<const BaseForImplicitCast&&>(std::move(cderived)); EXPECT_EQ(&cbase, &cderived); } +class BaseForDownCast { + public: + virtual ~BaseForDownCast() = default; +}; + +class DerivedForDownCast : public BaseForDownCast {}; +class Derived2ForDownCast : public BaseForDownCast {}; + +TEST(DownCastTest, Pointer) { + DerivedForDownCast derived; + BaseForDownCast* const base_ptr = &derived; + + // Tests casting a BaseForDownCast* to a DerivedForDownCast*. + EXPECT_EQ(&derived, absl::down_cast<DerivedForDownCast*>(base_ptr)); + + // Tests casting a const BaseForDownCast* to a const DerivedForDownCast*. + const BaseForDownCast* const_base_ptr = base_ptr; + EXPECT_EQ(&derived, + absl::down_cast<const DerivedForDownCast*>(const_base_ptr)); + + // Tests casting a BaseForDownCast* to a const DerivedForDownCast*. + EXPECT_EQ(&derived, absl::down_cast<const DerivedForDownCast*>(base_ptr)); + + // Tests casting a BaseForDownCast* to a BaseForDownCast* (an identity cast). + EXPECT_EQ(base_ptr, absl::down_cast<BaseForDownCast*>(base_ptr)); + + // Tests down casting NULL. + EXPECT_EQ(nullptr, + (absl::down_cast<DerivedForDownCast*, BaseForDownCast>(nullptr))); + + // Tests a bad downcast. We have to disguise the badness just enough + // that the compiler doesn't warn about it at compile time. + BaseForDownCast* base2 = new BaseForDownCast(); +#if GTEST_HAS_DEATH_TEST && (!defined(NDEBUG) || (ABSL_OPTION_HARDENED == 1 || \ + ABSL_OPTION_HARDENED == 2)) + EXPECT_DEATH(static_cast<void>(absl::down_cast<DerivedForDownCast*>(base2)), + ".*down cast from .*BaseForDownCast.* to " + ".*DerivedForDownCast.* failed.*"); +#endif + delete base2; +} + +TEST(DownCastTest, Reference) { + DerivedForDownCast derived; + BaseForDownCast& base_ref = derived; + + // Tests casting a BaseForDownCast& to a DerivedForDownCast&. + // NOLINTNEXTLINE(runtime/casting) + EXPECT_EQ(&derived, &absl::down_cast<DerivedForDownCast&>(base_ref)); + + // Tests casting a const BaseForDownCast& to a const DerivedForDownCast&. + const BaseForDownCast& const_base_ref = base_ref; + // NOLINTNEXTLINE(runtime/casting) + EXPECT_EQ(&derived, + &absl::down_cast<const DerivedForDownCast&>(const_base_ref)); + + // Tests casting a BaseForDownCast& to a const DerivedForDownCast&. + // NOLINTNEXTLINE(runtime/casting) + EXPECT_EQ(&derived, &absl::down_cast<const DerivedForDownCast&>(base_ref)); + + // Tests casting a BaseForDownCast& to a BaseForDownCast& (an identity cast). + // NOLINTNEXTLINE(runtime/casting) + EXPECT_EQ(&base_ref, &absl::down_cast<BaseForDownCast&>(base_ref)); + + // Tests a bad downcast. We have to disguise the badness just enough + // that the compiler doesn't warn about it at compile time. + BaseForDownCast& base2 = *new BaseForDownCast(); +#if GTEST_HAS_DEATH_TEST && (!defined(NDEBUG) || (ABSL_OPTION_HARDENED == 1 || \ + ABSL_OPTION_HARDENED == 2)) + EXPECT_DEATH(static_cast<void>(absl::down_cast<DerivedForDownCast&>(base2)), + ".*down cast from .*BaseForDownCast.* to " + ".*DerivedForDownCast.* failed.*"); +#endif + delete &base2; +} + +TEST(DownCastTest, ErrorMessage) { + DerivedForDownCast derived; + BaseForDownCast& base = derived; + (void)base; + +#if GTEST_HAS_DEATH_TEST && (!defined(NDEBUG) || (ABSL_OPTION_HARDENED == 1 || \ + ABSL_OPTION_HARDENED == 2)) + EXPECT_DEATH(static_cast<void>(absl::down_cast<Derived2ForDownCast&>(base)), + ".*down cast from .*DerivedForDownCast.* to " + ".*Derived2ForDownCast.* failed.*"); +#endif +} + } // namespace