/src/skia/src/base/SkNoDestructor.h
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2018 Google LLC |
3 | | * |
4 | | * Use of this source code is governed by a BSD-style license that can be |
5 | | * found in the LICENSE file. |
6 | | */ |
7 | | #ifndef SkNoDestructor_DEFINED |
8 | | #define SkNoDestructor_DEFINED |
9 | | |
10 | | #include <cstddef> |
11 | | #include <new> |
12 | | #include <type_traits> // IWYU pragma: keep |
13 | | #include <utility> |
14 | | |
15 | | // Helper type to create a function-local static variable of type `T` when `T` |
16 | | // has a non-trivial destructor. Storing a `T` in a `SkNoDestructor<T>` will |
17 | | // prevent `~T()` from running, even when the variable goes out of scope. This |
18 | | // code is adapted from `base::NoDestructor<T>` in Chromium. |
19 | | // |
20 | | // Useful when a variable has static storage duration but its type has a |
21 | | // non-trivial destructor. Chromium (and transitively, Skia) bans global |
22 | | // constructors and destructors: using a function-local static variable prevents |
23 | | // the former, while using `SkNoDestructor<T>` prevents the latter. |
24 | | // |
25 | | // ## Caveats |
26 | | // |
27 | | // - Must not be used for locals or fields; by definition, this does not run |
28 | | // destructors, and this will likely lead to memory leaks and other |
29 | | // surprising and undesirable behaviour. |
30 | | // |
31 | | // - If `T` is not constexpr constructible, must be a function-local static |
32 | | // variable, since a global `NoDestructor<T>` will still generate a static |
33 | | // initializer. |
34 | | // |
35 | | // - If `T` is constinit constructible, may be used as a global, but mark the |
36 | | // global `constinit` (once C++20 is available) |
37 | | // |
38 | | // - If the data is rarely used, consider creating it on demand rather than |
39 | | // caching it for the lifetime of the program. Though `SkNoDestructor<T>` |
40 | | // does not heap allocate, the compiler still reserves space in bss for |
41 | | // storing `T`, which costs memory at runtime. |
42 | | // |
43 | | // - If `T` is trivially destructible, do not use `SkNoDestructor<T>`: |
44 | | // |
45 | | // const uint64_t GetUnstableSessionSeed() { |
46 | | // // No need to use `SkNoDestructor<T>` as `uint64_t` is trivially |
47 | | // // destructible and does not require a global destructor. |
48 | | // static const uint64_t kSessionSeed = GetRandUint64(); |
49 | | // return kSessionSeed; |
50 | | // } |
51 | | // |
52 | | // ## Example Usage |
53 | | // |
54 | | // const std::string& GetDefaultText() { |
55 | | // // Required since `static const std::string` requires a global destructor. |
56 | | // static const SkNoDestructor<std::string> s("Hello world!"); |
57 | | // return *s; |
58 | | // } |
59 | | // |
60 | | // More complex initialization using a lambda: |
61 | | // |
62 | | // const std::string& GetRandomNonce() { |
63 | | // // `nonce` is initialized with random data the first time this function is |
64 | | // // called, but its value is fixed thereafter. |
65 | | // static const SkNoDestructor<std::string> nonce([] { |
66 | | // std::string s(16); |
67 | | // GetRandString(s.data(), s.size()); |
68 | | // return s; |
69 | | // }()); |
70 | | // return *nonce; |
71 | | // } |
72 | | // |
73 | | // ## Thread safety |
74 | | // |
75 | | // Initialization of function-local static variables is thread-safe since C++11. |
76 | | // The standard guarantees that: |
77 | | // |
78 | | // - function-local static variables will be initialised the first time |
79 | | // execution passes through the declaration. |
80 | | // |
81 | | // - if another thread's execution concurrently passes through the declaration |
82 | | // in the middle of initialisation, that thread will wait for the in-progress |
83 | | // initialisation to complete. |
84 | | template <typename T> class SkNoDestructor { |
85 | | public: |
86 | | static_assert(!(std::is_trivially_constructible_v<T> && std::is_trivially_destructible_v<T>), |
87 | | "T is trivially constructible and destructible; please use a constinit object of " |
88 | | "type T directly instead"); |
89 | | |
90 | | static_assert(!std::is_trivially_destructible_v<T>, |
91 | | "T is trivially destructible; please use a function-local static of type T " |
92 | | "directly instead"); |
93 | | |
94 | | // Not constexpr; just write static constexpr T x = ...; if the value should be a constexpr. |
95 | 302 | template <typename... Args> explicit SkNoDestructor(Args&&... args) { |
96 | 302 | new (fStorage) T(std::forward<Args>(args)...); |
97 | 302 | } SkNoDestructor<SkBlendModeBlender>::SkNoDestructor<SkBlendMode>(SkBlendMode&&) Line | Count | Source | 95 | 262 | template <typename... Args> explicit SkNoDestructor(Args&&... args) { | 96 | 262 | new (fStorage) T(std::forward<Args>(args)...); | 97 | 262 | } |
Unexecuted instantiation: SkNoDestructor<SkTrivialExecutor>::SkNoDestructor<>() SkNoDestructor<SkMutex>::SkNoDestructor<>() Line | Count | Source | 95 | 3 | template <typename... Args> explicit SkNoDestructor(Args&&... args) { | 96 | 3 | new (fStorage) T(std::forward<Args>(args)...); | 97 | 3 | } |
SkNoDestructor<SkLRUCache<unsigned long, sk_sp<SkRuntimeEffect>, SkGoodHash> >::SkNoDestructor<int>(int&&) Line | Count | Source | 95 | 3 | template <typename... Args> explicit SkNoDestructor(Args&&... args) { | 96 | 3 | new (fStorage) T(std::forward<Args>(args)...); | 97 | 3 | } |
SkTypeface.cpp:SkNoDestructor<(anonymous namespace)::SkEmptyTypeface>::SkNoDestructor<>() Line | Count | Source | 95 | 11 | template <typename... Args> explicit SkNoDestructor(Args&&... args) { | 96 | 11 | new (fStorage) T(std::forward<Args>(args)...); | 97 | 11 | } |
SkNoDestructor<std::__1::vector<SkCodecs::Decoder, std::__1::allocator<SkCodecs::Decoder> > >::SkNoDestructor<>() Line | Count | Source | 95 | 7 | template <typename... Args> explicit SkNoDestructor(Args&&... args) { | 96 | 7 | new (fStorage) T(std::forward<Args>(args)...); | 97 | 7 | } |
SkNoDestructor<SkSL::ModuleLoader::Impl>::SkNoDestructor<>() Line | Count | Source | 95 | 8 | template <typename... Args> explicit SkNoDestructor(Args&&... args) { | 96 | 8 | new (fStorage) T(std::forward<Args>(args)...); | 97 | 8 | } |
SkNoDestructor<sktext::gpu::DistanceFieldAdjustTable>::SkNoDestructor<>() Line | Count | Source | 95 | 1 | template <typename... Args> explicit SkNoDestructor(Args&&... args) { | 96 | 1 | new (fStorage) T(std::forward<Args>(args)...); | 97 | 1 | } |
SkNoDestructor<SkColorSpaceXformColorFilter>::SkNoDestructor<sk_sp<SkColorSpace>, sk_sp<SkColorSpace> >(sk_sp<SkColorSpace>&&, sk_sp<SkColorSpace>&&) Line | Count | Source | 95 | 7 | template <typename... Args> explicit SkNoDestructor(Args&&... args) { | 96 | 7 | new (fStorage) T(std::forward<Args>(args)...); | 97 | 7 | } |
|
98 | | |
99 | | // Allows copy and move construction of the contained type, to allow construction from an |
100 | | // initializer list, e.g. for std::vector. |
101 | | explicit SkNoDestructor(const T& x) { new (fStorage) T(x); } |
102 | 100 | explicit SkNoDestructor(T&& x) { new (fStorage) T(std::move(x)); } SkTypeface.cpp:SkNoDestructor<std::__1::vector<(anonymous namespace)::DecoderProc, std::__1::allocator<(anonymous namespace)::DecoderProc> > >::SkNoDestructor(std::__1::vector<(anonymous namespace)::DecoderProc, std::__1::allocator<(anonymous namespace)::DecoderProc> >&&) Line | Count | Source | 102 | 74 | explicit SkNoDestructor(T&& x) { new (fStorage) T(std::move(x)); } |
SkNoDestructor<skia_private::THashMap<std::__1::basic_string_view<char, std::__1::char_traits<char> >, SkSL::LayoutFlag, SkGoodHash> >::SkNoDestructor(skia_private::THashMap<std::__1::basic_string_view<char, std::__1::char_traits<char> >, SkSL::LayoutFlag, SkGoodHash>&&) Line | Count | Source | 102 | 8 | explicit SkNoDestructor(T&& x) { new (fStorage) T(std::move(x)); } |
SkNoDestructor<SkSL::String::Separator()::Output>::SkNoDestructor(SkSL::String::Separator()::Output&&) Line | Count | Source | 102 | 8 | explicit SkNoDestructor(T&& x) { new (fStorage) T(std::move(x)); } |
SkNoDestructor<skia_private::THashMap<std::__1::basic_string_view<char, std::__1::char_traits<char> >, bool const SkSL::ShaderCaps::*, SkGoodHash> >::SkNoDestructor(skia_private::THashMap<std::__1::basic_string_view<char, std::__1::char_traits<char> >, bool const SkSL::ShaderCaps::*, SkGoodHash>&&) Line | Count | Source | 102 | 2 | explicit SkNoDestructor(T&& x) { new (fStorage) T(std::move(x)); } |
SkNoDestructor<skia_private::THashMap<std::__1::basic_string_view<char, std::__1::char_traits<char> >, SkSL::IntrinsicKind, SkGoodHash> >::SkNoDestructor(skia_private::THashMap<std::__1::basic_string_view<char, std::__1::char_traits<char> >, SkSL::IntrinsicKind, SkGoodHash>&&) Line | Count | Source | 102 | 8 | explicit SkNoDestructor(T&& x) { new (fStorage) T(std::move(x)); } |
|
103 | | |
104 | | SkNoDestructor(const SkNoDestructor&) = delete; |
105 | | SkNoDestructor& operator=(const SkNoDestructor&) = delete; |
106 | | |
107 | | ~SkNoDestructor() = default; |
108 | | |
109 | 35.7k | const T& operator*() const { return *get(); } |
110 | 28.9k | T& operator*() { return *get(); } Unexecuted instantiation: SkNoDestructor<SkTrivialExecutor>::operator*() SkNoDestructor<SkMutex>::operator*() Line | Count | Source | 110 | 9.37k | T& operator*() { return *get(); } |
SkNoDestructor<SkSL::ModuleLoader::Impl>::operator*() Line | Count | Source | 110 | 19.2k | T& operator*() { return *get(); } |
SkNoDestructor<skia_private::THashMap<std::__1::basic_string_view<char, std::__1::char_traits<char> >, bool const SkSL::ShaderCaps::*, SkGoodHash> >::operator*() Line | Count | Source | 110 | 325 | T& operator*() { return *get(); } |
|
111 | | |
112 | 6.73k | const T* operator->() const { return get(); } |
113 | 9.44k | T* operator->() { return get(); } SkNoDestructor<SkLRUCache<unsigned long, sk_sp<SkRuntimeEffect>, SkGoodHash> >::operator->() Line | Count | Source | 113 | 9.37k | T* operator->() { return get(); } |
SkNoDestructor<std::__1::vector<SkCodecs::Decoder, std::__1::allocator<SkCodecs::Decoder> > >::operator->() Line | Count | Source | 113 | 70 | T* operator->() { return get(); } |
SkNoDestructor<skia_private::THashMap<std::__1::basic_string_view<char, std::__1::char_traits<char> >, SkSL::LayoutFlag, SkGoodHash> >::operator->() Line | Count | Source | 113 | 6 | T* operator->() { return get(); } |
|
114 | | |
115 | 42.5k | const T* get() const { return reinterpret_cast<const T*>(fStorage); } SkNoDestructor<SkSL::String::Separator()::Output>::get() const Line | Count | Source | 115 | 6.73k | const T* get() const { return reinterpret_cast<const T*>(fStorage); } |
SkNoDestructor<sktext::gpu::DistanceFieldAdjustTable>::get() const Line | Count | Source | 115 | 70 | const T* get() const { return reinterpret_cast<const T*>(fStorage); } |
SkNoDestructor<skia_private::THashMap<std::__1::basic_string_view<char, std::__1::char_traits<char> >, SkSL::IntrinsicKind, SkGoodHash> >::get() const Line | Count | Source | 115 | 35.7k | const T* get() const { return reinterpret_cast<const T*>(fStorage); } |
|
116 | 46.4M | T* get() { return reinterpret_cast<T*>(fStorage); } SkNoDestructor<SkBlendModeBlender>::get() Line | Count | Source | 116 | 587k | T* get() { return reinterpret_cast<T*>(fStorage); } |
Unexecuted instantiation: SkNoDestructor<SkTrivialExecutor>::get() SkNoDestructor<SkMutex>::get() Line | Count | Source | 116 | 9.37k | T* get() { return reinterpret_cast<T*>(fStorage); } |
SkNoDestructor<SkLRUCache<unsigned long, sk_sp<SkRuntimeEffect>, SkGoodHash> >::get() Line | Count | Source | 116 | 9.37k | T* get() { return reinterpret_cast<T*>(fStorage); } |
SkTypeface.cpp:SkNoDestructor<(anonymous namespace)::SkEmptyTypeface>::get() Line | Count | Source | 116 | 45.0M | T* get() { return reinterpret_cast<T*>(fStorage); } |
SkTypeface.cpp:SkNoDestructor<std::__1::vector<(anonymous namespace)::DecoderProc, std::__1::allocator<(anonymous namespace)::DecoderProc> > >::get() Line | Count | Source | 116 | 583k | T* get() { return reinterpret_cast<T*>(fStorage); } |
SkNoDestructor<std::__1::vector<SkCodecs::Decoder, std::__1::allocator<SkCodecs::Decoder> > >::get() Line | Count | Source | 116 | 168k | T* get() { return reinterpret_cast<T*>(fStorage); } |
SkNoDestructor<SkSL::ModuleLoader::Impl>::get() Line | Count | Source | 116 | 19.2k | T* get() { return reinterpret_cast<T*>(fStorage); } |
SkNoDestructor<skia_private::THashMap<std::__1::basic_string_view<char, std::__1::char_traits<char> >, SkSL::LayoutFlag, SkGoodHash> >::get() Line | Count | Source | 116 | 6 | T* get() { return reinterpret_cast<T*>(fStorage); } |
SkNoDestructor<skia_private::THashMap<std::__1::basic_string_view<char, std::__1::char_traits<char> >, bool const SkSL::ShaderCaps::*, SkGoodHash> >::get() Line | Count | Source | 116 | 325 | T* get() { return reinterpret_cast<T*>(fStorage); } |
SkNoDestructor<SkColorSpaceXformColorFilter>::get() Line | Count | Source | 116 | 65 | T* get() { return reinterpret_cast<T*>(fStorage); } |
|
117 | | |
118 | | private: |
119 | | alignas(T) std::byte fStorage[sizeof(T)]; |
120 | | |
121 | | #if defined(__clang__) && defined(__has_feature) |
122 | | #if __has_feature(leak_sanitizer) || __has_feature(address_sanitizer) |
123 | | // TODO(https://crbug.com/812277): This is a hack to work around the fact that LSan doesn't seem |
124 | | // to treat SkNoDestructor as a root for reachability analysis. This means that code like this: |
125 | | // static SkNoDestructor<std::vector<int>> v({1, 2, 3}); |
126 | | // is considered a leak. Using the standard leak sanitizer annotations to suppress leaks doesn't |
127 | | // work: std::vector is implicitly constructed before calling the SkNoDestructor constructor. |
128 | | // |
129 | | // Unfortunately, I haven't been able to demonstrate this issue in simpler reproductions: until |
130 | | // that's resolved, hold an explicit pointer to the placement-new'd object in leak sanitizer |
131 | | // mode to help LSan realize that objects allocated by the contained type are still reachable. |
132 | | T* fStoragePtr = reinterpret_cast<T*>(fStorage); |
133 | | #endif // leak_sanitizer/address_sanitizer |
134 | | #endif // __has_feature |
135 | | }; |
136 | | |
137 | | #endif // SkNoDestructor_DEFINED |