From efc9a63bde4a1250df60e43a19aa2fa4985134d5 Mon Sep 17 00:00:00 2001 From: Josh Gao Date: Thu, 14 Nov 2019 17:59:57 -0800 Subject: [PATCH] base: steal Chromium's NoDestructor. Pillage from Chromium a wrapper type that skips destruction of its wrapped type, to avoid problems with premature destruction of variables with static lifetime. Test: libbase_test on host Change-Id: I7d4541f7b59f467b232d5c4f8250dc1ea45e28fa --- base/Android.bp | 1 + base/include/android-base/no_destructor.h | 94 +++++++++++++++++++++++ base/no_destructor_test.cpp | 66 ++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 base/include/android-base/no_destructor.h create mode 100644 base/no_destructor_test.cpp diff --git a/base/Android.bp b/base/Android.bp index 25ae535a3..c16cbe96a 100644 --- a/base/Android.bp +++ b/base/Android.bp @@ -148,6 +148,7 @@ cc_test { "logging_test.cpp", "macros_test.cpp", "mapped_file_test.cpp", + "no_destructor_test.cpp", "parsedouble_test.cpp", "parseint_test.cpp", "parsenetaddress_test.cpp", diff --git a/base/include/android-base/no_destructor.h b/base/include/android-base/no_destructor.h new file mode 100644 index 000000000..ce0dc9f2a --- /dev/null +++ b/base/include/android-base/no_destructor.h @@ -0,0 +1,94 @@ +#pragma once + +/* + * Copyright (C) 2019 The Android Open Source Project + * + * 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 + * + * http://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 + +#include "android-base/macros.h" + +namespace android { +namespace base { + +// A wrapper that makes it easy to create an object of type T with static +// storage duration that: +// - is only constructed on first access +// - never invokes the destructor +// in order to satisfy the styleguide ban on global constructors and +// destructors. +// +// Runtime constant example: +// const std::string& GetLineSeparator() { +// // Forwards to std::string(size_t, char, const Allocator&) constructor. +// static const base::NoDestructor s(5, '-'); +// return *s; +// } +// +// More complex initialization with a lambda: +// const std::string& GetSessionNonce() { +// static const base::NoDestructor nonce([] { +// std::string s(16); +// crypto::RandString(s.data(), s.size()); +// return s; +// }()); +// return *nonce; +// } +// +// NoDestructor stores the object inline, so it also avoids a pointer +// indirection and a malloc. Also note that since C++11 static local variable +// initialization is thread-safe and so is this pattern. Code should prefer to +// use NoDestructor over: +// - A function scoped static T* or T& that is dynamically initialized. +// - A global base::LazyInstance. +// +// Note that since the destructor is never run, this *will* leak memory if used +// as a stack or member variable. Furthermore, a NoDestructor should never +// have global scope as that may require a static initializer. +template +class NoDestructor { + public: + // Not constexpr; just write static constexpr T x = ...; if the value should + // be a constexpr. + template + explicit NoDestructor(Args&&... args) { + new (storage_) T(std::forward(args)...); + } + + // Allows copy and move construction of the contained type, to allow + // construction from an initializer list, e.g. for std::vector. + explicit NoDestructor(const T& x) { new (storage_) T(x); } + explicit NoDestructor(T&& x) { new (storage_) T(std::move(x)); } + + NoDestructor(const NoDestructor&) = delete; + NoDestructor& operator=(const NoDestructor&) = delete; + + ~NoDestructor() = default; + + const T& operator*() const { return *get(); } + T& operator*() { return *get(); } + + const T* operator->() const { return get(); } + T* operator->() { return get(); } + + const T* get() const { return reinterpret_cast(storage_); } + T* get() { return reinterpret_cast(storage_); } + + private: + alignas(T) char storage_[sizeof(T)]; +}; + +} // namespace base +} // namespace android diff --git a/base/no_destructor_test.cpp b/base/no_destructor_test.cpp new file mode 100644 index 000000000..f19468abf --- /dev/null +++ b/base/no_destructor_test.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * 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 + * + * http://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 "android-base/no_destructor.h" + +#include + +struct __attribute__((packed)) Bomb { + Bomb() : magic_(123) {} + + ~Bomb() { exit(42); } + + int get() const { return magic_; } + + private: + [[maybe_unused]] char padding_; + int magic_; +}; + +TEST(no_destructor, bomb) { + ASSERT_EXIT(({ + { + Bomb b; + if (b.get() != 123) exit(1); + } + + exit(0); + }), + ::testing::ExitedWithCode(42), ""); +} + +TEST(no_destructor, defused) { + ASSERT_EXIT(({ + { + android::base::NoDestructor b; + if (b->get() != 123) exit(1); + } + + exit(0); + }), + ::testing::ExitedWithCode(0), ""); +} + +TEST(no_destructor, operators) { + android::base::NoDestructor b; + const android::base::NoDestructor& c = b; + ASSERT_EQ(123, b.get()->get()); + ASSERT_EQ(123, b->get()); + ASSERT_EQ(123, (*b).get()); + ASSERT_EQ(123, c.get()->get()); + ASSERT_EQ(123, c->get()); + ASSERT_EQ(123, (*c).get()); +}