Skip to content

Commit

Permalink
Updated P3390R0
Browse files Browse the repository at this point in the history
  • Loading branch information
seanbaxter committed Oct 14, 2024
1 parent 64f7a2a commit c4e41dc
Show file tree
Hide file tree
Showing 4 changed files with 18 additions and 12 deletions.
14 changes: 9 additions & 5 deletions docs/P3444R0.html
Original file line number Diff line number Diff line change
Expand Up @@ -571,13 +571,13 @@ <h2 data-number="1.1" id="exclusivity"><span class="header-section-number">1.1</
feature). There are other assumptions C++ programmers already make about
the validity of inputs: for instance, references never hold null
addresses. Non-valid inputs are implicated in undefined behavior.</p>
<p>By the parsimony principal you may suggest “rather than adding a new
<p>With a desire to simplify, you may suggest “rather than adding a new
safe reference type, just enforce exclusivity on lvalue- and
rvalue-references when compiled under the <code class="sourceCode cpp"><span class="op">[</span>safety<span class="op">]</span></code>
feature.” But that makes the soundness problem worse. New code will
<em>assume</em> legacy references don’t mutably alias, but existing code
doesn’t uphold that invariant because it was written without even
knowing about it.</p>
doesn’t uphold that invariant because it was written without considering
exclusivity.</p>
<p>If safe code calls legacy code that returns a struct with a pair of
references, do those references alias? Of course they may alias, but the
parsimonious treatment claims that mutable references don’t alias under
Expand Down Expand Up @@ -1007,6 +1007,11 @@ <h1 data-number="4" id="achieving-first-class-references"><span class="header-se
mutex. This is a challenge for safe references, because safe reference
data members aren’t supported. Normally those would require lifetime
parameters on the containing class.</p>
<p>Robust support for user-defined types with reference data members
isn’t just a convenience in a safe C++ system. It’s a necessary part of
<em>interior mutability</em>, the core design pattern for implementing
shared ownership of mutable state (think safe versions of
<code class="sourceCode cpp">shared_ptr</code>).</p>
<p>What are some options for RAII reference semantics?</p>
<ul>
<li>Coroutines. This is the Hylo strategy. The ramp function locks a
Expand Down Expand Up @@ -1268,7 +1273,6 @@ <h1 data-number="5" id="lifetime-parameters"><span class="header-section-number"
interfacing the two languages. The easier it is to interoperate with
Rust, the more options and freedom companies have to fulfill with their
security mandate.<span class="citation" data-cites="rust-interop">[<a href="https://security.googleblog.com/2024/02/improving-interoperability-between-rust-and-c.html" role="doc-biblioref">rust-interop</a>]</span></p>
<p>An up-to-date draft of this document is maintained at <a href="https://www.safe-cpp.org/draft-lifetimes.html">safe-cpp.org/draft-lifetimes.html</a>.</p>
<h1 data-number="6" id="bibliography"><span class="header-section-number">6</span> References<a href="#bibliography" class="self-link"></a></h1>
<div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="1" role="doc-bibliography">
<div id="ref-android" class="csl-entry" role="doc-biblioentry">
Expand Down Expand Up @@ -1301,7 +1305,7 @@ <h1 data-number="6" id="bibliography"><span class="header-section-number">6</spa
Design. <a href="https://blog.google/technology/safety-security/tackling-cybersecurity-vulnerabilities-through-secure-by-design/"><div class="csl-block">https://blog.google/technology/safety-security/tackling-cybersecurity-vulnerabilities-through-secure-by-design/</div></a>
</div>
<div id="ref-safecpp" class="csl-entry" role="doc-biblioentry">
[safecpp] Safe C++. <a href="https://safecpp.org/draft.html"><div class="csl-block">https://safecpp.org/draft.html</div></a>
[safecpp] P3390 – Safe C++. <a href="https://safecpp.org/draft.html"><div class="csl-block">https://safecpp.org/draft.html</div></a>
</div>
<div id="ref-second-class" class="csl-entry" role="doc-biblioentry">
[second-class] Second-Class References. <a href="https://borretti.me/article/second-class-references"><div class="csl-block">https://borretti.me/article/second-class-references</div></a>
Expand Down
2 changes: 1 addition & 1 deletion docs/draft-lifetimes.html
Original file line number Diff line number Diff line change
Expand Up @@ -1300,7 +1300,7 @@ <h1 data-number="6" id="bibliography"><span class="header-section-number">6</spa
Design. <a href="https://blog.google/technology/safety-security/tackling-cybersecurity-vulnerabilities-through-secure-by-design/"><div class="csl-block">https://blog.google/technology/safety-security/tackling-cybersecurity-vulnerabilities-through-secure-by-design/</div></a>
</div>
<div id="ref-safecpp" class="csl-entry" role="doc-biblioentry">
[safecpp] Safe C++. <a href="https://safecpp.org/draft.html"><div class="csl-block">https://safecpp.org/draft.html</div></a>
[safecpp] P3390 – Safe C++. <a href="https://safecpp.org/draft.html"><div class="csl-block">https://safecpp.org/draft.html</div></a>
</div>
<div id="ref-second-class" class="csl-entry" role="doc-biblioentry">
[second-class] Second-Class References. <a href="https://borretti.me/article/second-class-references"><div class="csl-block">https://borretti.me/article/second-class-references</div></a>
Expand Down
8 changes: 4 additions & 4 deletions lifetimes/P3444R0.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Exclusivity is a program-wide invariant. It doesn't hinge on the safeness of a f

"Valid" borrow and safe reference inputs don't mutably alias. This is something a function can just _assume_; it doesn't need to check and there's no way to check. Borrow checking upholds exclusivity even for unsafe functions (when compiled under the `[safety]` feature). There are other assumptions C++ programmers already make about the validity of inputs: for instance, references never hold null addresses. Non-valid inputs are implicated in undefined behavior.

By the parsimony principal you may suggest "rather than adding a new safe reference type, just enforce exclusivity on lvalue- and rvalue-references when compiled under the `[safety]` feature." But that makes the soundness problem worse. New code will _assume_ legacy references don't mutably alias, but existing code doesn't uphold that invariant because it was written without even knowing about it.
With a desire to simplify, you may suggest "rather than adding a new safe reference type, just enforce exclusivity on lvalue- and rvalue-references when compiled under the `[safety]` feature." But that makes the soundness problem worse. New code will _assume_ legacy references don't mutably alias, but existing code doesn't uphold that invariant because it was written without considering exclusivity.

If safe code calls legacy code that returns a struct with a pair of references, do those references alias? Of course they may alias, but the parsimonious treatment claims that mutable references don't alias under the `[safety]` feature. We've already stumbled on a soundness bug.

Expand Down Expand Up @@ -388,6 +388,8 @@ The presented design is as far as I could go to address the goal of "memory safe
Let's consider RAII types with reference semantics. An example is `std::lock_guard`, which keeps a reference to a mutex. When the `lock_guard` goes out of scope its destructor calls `unlock` on the mutex. This is a challenge for safe references, because safe reference data members aren't supported. Normally those would require lifetime parameters on the containing class.
Robust support for user-defined types with reference data members isn't just a convenience in a safe C++ system. It's a necessary part of _interior mutability_, the core design pattern for implementing shared ownership of mutable state (think safe versions of `shared_ptr`).
What are some options for RAII reference semantics?
* Coroutines. This is the Hylo strategy. The ramp function locks a mutex and returns a safe reference to the data within. The continuation unlocks the mutex. The reference to the mutex is kept in the coroutine frame. But this still reduces to supporting structs with reference data members. In this case it's not a user-defined type, but a compiler-defined coroutine frame. I feel that the coroutine solution is an unidiomatic fit for C++ for several reasons: static allocation of the coroutine frame requires exposing the definition of the coroutine to the caller, which breaks C++'s approach to modularity; the continuation is called immediately after the last use of the yielded reference, which runs counter to expectation that cleanup runs at the end of the enclosing scope; and since the continuation is called implicitly, there's nothing textual on the caller side to indicate an unlock.
Expand Down Expand Up @@ -497,13 +499,11 @@ The US government and major players in tech including Google[@secure-by-design]

Finally, adoption of this feature brings a major benefit even if you personally want to get off C++: It's critical for **improving C++/Rust interop**. Your C++ project is generating revenue and there's scant economic incentive to rewrite it. But there is an incentive to pivot to a memory-safe language for new development, because new code is how vulnerabilities get introduced.[@android] Bringing C++ closer to Rust with the inclusion of _safe-specifier_, relocation, choice types, and, importantly, lifetime parameters, reduces the friction of interfacing the two languages. The easier it is to interoperate with Rust, the more options and freedom companies have to fulfill with their security mandate.[@rust-interop]

An up-to-date draft of this document is maintained at [safe-cpp.org/draft-lifetimes.html](https://www.safe-cpp.org/draft-lifetimes.html).

---
references:
- id: safecpp
citation-label: safecpp
title: Safe C++
title: P3390 -- Safe C++
URL: https://safecpp.org/draft.html

- id: second-class
Expand Down
6 changes: 4 additions & 2 deletions lifetimes/draft-lifetimes.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Exclusivity is a program-wide invariant. It doesn't hinge on the safeness of a f

"Valid" borrow and safe reference inputs don't mutably alias. This is something a function can just _assume_; it doesn't need to check and there's no way to check. Borrow checking upholds exclusivity even for unsafe functions (when compiled under the `[safety]` feature). There are other assumptions C++ programmers already make about the validity of inputs: for instance, references never hold null addresses. Non-valid inputs are implicated in undefined behavior.

By the parsimony principal you may suggest "rather than adding a new safe reference type, just enforce exclusivity on lvalue- and rvalue-references when compiled under the `[safety]` feature." But that makes the soundness problem worse. New code will _assume_ legacy references don't mutably alias, but existing code doesn't uphold that invariant because it was written without even knowing about it.
With a desire to simplify, you may suggest "rather than adding a new safe reference type, just enforce exclusivity on lvalue- and rvalue-references when compiled under the `[safety]` feature." But that makes the soundness problem worse. New code will _assume_ legacy references don't mutably alias, but existing code doesn't uphold that invariant because it was written without considering exclusivity.

If safe code calls legacy code that returns a struct with a pair of references, do those references alias? Of course they may alias, but the parsimonious treatment claims that mutable references don't alias under the `[safety]` feature. We've already stumbled on a soundness bug.

Expand Down Expand Up @@ -388,6 +388,8 @@ The presented design is as far as I could go to address the goal of "memory safe
Let's consider RAII types with reference semantics. An example is `std::lock_guard`, which keeps a reference to a mutex. When the `lock_guard` goes out of scope its destructor calls `unlock` on the mutex. This is a challenge for safe references, because safe reference data members aren't supported. Normally those would require lifetime parameters on the containing class.
Robust support for user-defined types with reference data members isn't just a convenience in a safe C++ system. It's a necessary part of _interior mutability_, the core design pattern for implementing shared ownership of mutable state (think safe versions of `shared_ptr`).
What are some options for RAII reference semantics?
* Coroutines. This is the Hylo strategy. The ramp function locks a mutex and returns a safe reference to the data within. The continuation unlocks the mutex. The reference to the mutex is kept in the coroutine frame. But this still reduces to supporting structs with reference data members. In this case it's not a user-defined type, but a compiler-defined coroutine frame. I feel that the coroutine solution is an unidiomatic fit for C++ for several reasons: static allocation of the coroutine frame requires exposing the definition of the coroutine to the caller, which breaks C++'s approach to modularity; the continuation is called immediately after the last use of the yielded reference, which runs counter to expectation that cleanup runs at the end of the enclosing scope; and since the continuation is called implicitly, there's nothing textual on the caller side to indicate an unlock.
Expand Down Expand Up @@ -501,7 +503,7 @@ Finally, adoption of this feature brings a major benefit even if you personally
references:
- id: safecpp
citation-label: safecpp
title: Safe C++
title: P3390 -- Safe C++
URL: https://safecpp.org/draft.html

- id: second-class
Expand Down

0 comments on commit c4e41dc

Please sign in to comment.