/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * 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. */ #pragma once #include #include #include namespace folly { template // Forward-decl to keep `RValueReferenceWrapper.h` dep-free class rvalue_reference_wrapper; } // namespace folly /* "Aliasing" is indirect access to memory via pointers or references. It is the major cause of memory-safety bugs in C++, but is also essential for writing correct & performant C++ programs. Fortunately, - Much business logic can be written in a pure-functional style, where only value semantics are allowed. Such code is easier to understand, and has much better memory-safety. - When references ARE used, the most common scenario is passing a reference from a parent lexical scope to descendant scopes. `safe_alias_of_v` is a _heuristic_ to check whether a type is likely to be memory-safe in the above settings. The `safe_alias` enum shows a hierarchy of memory safety, but you only need to know about two: - `unsafe` -- e.g. raw pointers or references, and - `maybe_value` -- `int`, `std::pair`, or `std::unique_ptr`. A user can easily bypass the heuristic -- since C++ lacks full reflection, it is impossible to make this bulletproof. Our goals are much more modest: - Make unsafe aliasing **more** visible in code review, and - Encourage programmers to use safe semantics by default. The BIG CAVEATS are: - The "composition hole" -- i.e. aliasing hidden in structures. We can't see unsafe class members, so `UnsafeStruct` below will be deduced to have `maybe_value` safety unless you specialize `safe_alias_of`. struct UnsafeStruct { int* rawPtr; }; Future: Perhaps with C++26 reflection, this could be fixed. The "lambda hole" is a particularly easy instance of the "composition hole". With lambda captures, a parent needs just one `&` to let a child pass a soon-to-be-dangling reference up the stack. E.g. this compiles: int* badPtr; auto t = async_closure( // LAMBDA HOLE: We can't tell this callable object is unsafe! bound_args{[&](int p) { *badPtr = p; }}, [](auto fn) -> ClosureTask { int i = 5; fn(i); // FAILURE: Dereferencing uninitialized `badPtr`. co_return; }); - Nullability & pointer stability: These hazards are not very specific to coroutines, and the current design of `folly/coro/safe` largely avoids unstable containers. Nonetheless, you must beware container mutation is an easy way to invalidate `safe_alias` memory-safety measurements. For example `unique_ptr` and `vector` have `maybe_value` safety. However, if you mutate them (`reset()`, `clear()`, etc), that would invalidate any async references (e.g. `Captures.h`) pointing inside. Luckily, there's no implicit way of getting a safe reference to inside regular containers. However, it is recommended to reduce accidental nullability where possible. For example, `capture>` exposes `reset()`, but `capture_indirect>` hides it behind `get_underlying_unsafe()`. Better yet, `capture>` blocks the underlying `clear()` method entirely. If you need to bypass this control, prefer the `manual_safe_*` wrappers below, instead of writing a custom workaround. Always explain why it's safe. To teach `safe_alias_of` about your type, include `SafeAlias-fwd.h` and either: 1) Add a member type alias to your class: using using folly_private_safe_alias_t = safe_alias_constant<...>; 2) Specialize `folly::safe_alias_of`. When adding `safe_alias` annotations to types, stick to these principles: - Always mark the `safe_alias` level in the header that declares your type. For `std` types you cannot change, add the specialization here, in `SafeAlias.h`. Since we cannot forward-declare from `std`, this unfortunately imposes a tradeoff between build cost and safety. Commonly used containers are worth the cost. For less-commonly used containers, we could develop a multi-header setup, plus some linter coverage to ensure the right headers ultimately do get included. - Only use `maybe_value` if your type ACTUALLY follows value semantics. - Unless you're implementing an `async_closure`-integrated type, it is VERY unlikely that you should use anything besides `unsafe` or `maybe_value`. - Use `safe_alias_of_pack` to aggregate safety for a multi-part type. */ namespace folly { // Types are `maybe_value` unless otherwise specified. Note that // `SafeAlias-fwd.h` already marks raw pointers & refs as `unsafe`, and peels // off CV qualifiers from the type being tested. // // See also: `safe_alias_of_v`. // // As explained in `SafeAlias-fwd.h`, do NOT move this to the `fwd` header. To // guarantee safety, this permissive primary template must be colocated with // the other specializations below. template struct safe_alias_of : safe_alias_constant {}; // Reference wrappers are unsafe. template struct safe_alias_of> : safe_alias_constant {}; template struct safe_alias_of> : safe_alias_constant {}; // Let `safe_alias_of_v` recursively inspect `std` containers that are likely // to be involved in bugs. If you encounter a memory-safety issue that // would've been caught by this, feel free to extend this. template struct safe_alias_of> : safe_alias_of_pack {}; template struct safe_alias_of> : safe_alias_of_pack {}; template struct safe_alias_of> : safe_alias_of_pack {}; // Recursing into `tag_t<>` type lists is nice for metaprogramming template struct safe_alias_of<::folly::tag_t> : safe_alias_of_pack {}; // IMPORTANT: If you use the `manual_safe_` escape-hatch wrappers, you MUST // comment with clear proof of WHY your usage is safe. The goal is to // ensure careful review of such code. // // Careful: With the default `Safety`, the contained value or reference can be // passed anywhere -- the wrapper pretends to be a value type. // // If you know a more restrictive safety level for your ref, annotate it to // improve safety: // - `after_cleanup_ref` for things owned by co_cleanup args of this closure, // - `co_cleanup_safe_ref` for refs to non-cleanup args owned by this closure, // or any ancestor closure. // // The types are public since they may occur in user-facing signatures. template struct manual_safe_ref_t : std::reference_wrapper { using typename std::reference_wrapper::type; using std::reference_wrapper::reference_wrapper; }; template struct manual_safe_val_t { using type = T; template manual_safe_val_t(Args&&... args) : t_(static_cast(args)...) {} template manual_safe_val_t(std::in_place_type_t, Fn fn) : t_(fn()) {} T& get() & noexcept { return t_; } operator T&() & noexcept { return t_; } const T& get() const& noexcept { return t_; } operator const T&() const& noexcept { return t_; } T&& get() && noexcept { return std::move(t_); } operator T&&() && noexcept { return std::move(t_); } private: T t_; }; template auto manual_safe_ref(T& t) { return manual_safe_ref_t{t}; } template auto manual_safe_val(T t) { return manual_safe_val_t{std::move(t)}; } template auto manual_safe_with(Fn&& fn) { using FnRet = decltype(static_cast(fn)()); return manual_safe_val_t{ std::in_place_type, static_cast(fn)}; } template struct safe_alias_of> : safe_alias_constant {}; template struct safe_alias_of> : safe_alias_constant {}; } // namespace folly