<div dir="ltr"><div dir="ltr">On Mon, Apr 15, 2019 at 2:29 PM Peter Sewell &lt;<a href="mailto:Peter.Sewell@cl.cam.ac.uk">Peter.Sewell@cl.cam.ac.uk</a>&gt; wrote:<br></div><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">On 15/04/2019, Richard Smith &lt;<a href="mailto:richardsmith@googlers.com" target="_blank">richardsmith@googlers.com</a>&gt; wrote:<br>
&gt; On Mon, Apr 15, 2019 at 7:16 AM Peter Sewell &lt;<a href="mailto:Peter.Sewell@cl.cam.ac.uk" target="_blank">Peter.Sewell@cl.cam.ac.uk</a>&gt;<br>
[...]<br>
&gt;&gt; We&#39;ve also heard suggestions that compilers do things here, but in C<br>
&gt;&gt; the container-of idiom seems pervasive, and WG14 at the last meeting<br>
&gt;&gt; expressed a large majority that it has to be permitted by the<br>
&gt;&gt; standard.<br>
&gt;&gt;<br>
&gt;<br>
&gt; This is likely one of the areas where C++ wants to have stricter rules than<br>
&gt; C, at least for some categories of types. (We already have limitations on<br>
&gt; the types for which offsetof can be used, and don&#39;t require offsets to be<br>
&gt; constant in all cases, and even permit some flavours of structs to not<br>
&gt; store their members within the memory associated with the struct object at<br>
&gt; all.) We probably want to follow C in this area at least for<br>
&gt; standard-layout types, though.<br>
<br>
y<br>
<br>
&gt;&gt; As a consequence of the above, the specification for std::launder says:<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt; &quot;&quot;&quot;<br>
&gt;&gt; &gt; Expects: [...] All bytes of storage that would be reachable through the<br>
&gt;&gt; result are reachable through p (see below).<br>
&gt;&gt; &gt; [...]<br>
&gt;&gt; &gt; Remarks: [...] A byte of storage is reachable through a pointer value<br>
&gt;&gt; that points to an object Y if it is within the storage occupied by Y, an<br>
&gt;&gt; object that is pointer-interconvertible with Y, or the<br>
&gt;&gt; immediately-enclosing array object if Y is an array element.<br>
&gt;&gt; &gt; &quot;&quot;&quot;<br>
&gt;&gt;<br>
&gt;&gt; (small thing: you might or might not want that to support movement<br>
&gt;&gt; through nested arrays)<br>
&gt;&gt;<br>
&gt;&gt; &gt; Can this be accommodated by the &quot;recreate the provenance on<br>
&gt;&gt; integer-to-pointer cast&quot; model? I think it&#39;s not accommodated by the<br>
&gt;&gt; approach you describe above -- the above description would seem to<br>
&gt;&gt; suggest<br>
&gt;&gt; that we need to allow access to the entirety of the largest live object<br>
&gt;&gt; whose address was taken and which encloses the address represented by the<br>
&gt;&gt; integer. So casting an A* to an integer could allow navigation to the<br>
&gt;&gt; enclosing B, if the B object has also had its address cast to an integer.<br>
&gt;&gt;<br>
&gt;&gt; Yes.  The current proposal allows that.   We&#39;ve not nailed down<br>
&gt;&gt; subobject issues, but one approach that several of us favour would be<br>
&gt;&gt; to enforce subobject boundaries except for void* or character-type*<br>
&gt;&gt; pointer arithmetic - that would let the offsetof container-of idioms<br>
&gt;&gt; still work, while permitting error detection in cases where (eg)<br>
&gt;&gt; someone does int* pointer arithmetic to move between struct members.<br>
&gt;&gt;<br>
&gt;<br>
&gt; I think something like that would make sense.<br>
&gt;<br>
&gt; C++ has a notion of an array of byte-like type providing storage for<br>
&gt; another object (with no formal subobject relationship). I think it would<br>
&gt; make sense to say that all mechanisms by which storage is allocated (of all<br>
&gt; storage durations) implicitly create an array of such a byte-like type<br>
&gt; covering the entire allocation, so every object either is, or is nested<br>
&gt; within, some array of byte-like type representing a storage allocation.<br>
&gt; Then pointer arithmetic on pointers to elements of that enclosing array<br>
&gt; would permit arbitrary navigation, and otherwise navigation would be<br>
&gt; constrained to subobject relationships.<br>
<br>
y  (Jens G has a proposal for C along those lines, though as I say<br>
we&#39;ve not yet actually done the subobject bits yet)<br>
<br>
&gt;&gt; Personally I&#39;m not sure how far we should go to defend this &quot;irreversible<br>
&gt;&gt; subobject navigation&quot; property, but my implementation isn&#39;t one that<br>
&gt;&gt; takes<br>
&gt;&gt; advantage of it.<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; A storage instance is deemed exposed by a cast of a pointer to it to<br>
&gt;&gt; &gt;&gt; an integer type, by a read (at non-pointer type) of the representation<br>
&gt;&gt; &gt;&gt; of the pointer, or by an output of the pointer using %p.<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt; Hmm. Does that mean that evaluation of a pointer-to-integer cast has a<br>
&gt;&gt; side-effect, and cannot in general be optimized away even if its result<br>
&gt;&gt; is<br>
&gt;&gt; unused?<br>
&gt;&gt;<br>
&gt;&gt; In the source language, yes.  An intermediate language might perhaps<br>
&gt;&gt; use a more liberal semantics that doesn&#39;t rely on that side effect.<br>
&gt;&gt;<br>
&gt;&gt; &gt;Or is there an assumption being built in here that the only way to form<br>
&gt;&gt; an equal integer value to cast back to a pointer will necessarily involve<br>
&gt;&gt; a<br>
&gt;&gt; computation that actually depends on the pointer-to-integer cast, even if<br>
&gt;&gt; we don&#39;t explicitly require that?<br>
&gt;&gt;<br>
&gt;&gt; That will typically be true - that&#39;s what allocation-address<br>
&gt;&gt; nondeterminism buys you.<br>
&gt;&gt;<br>
&gt;&gt; &gt; How careful do we need to be in future to avoid breaking that<br>
&gt;&gt; assumption? Consider case such as:<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt; int f(int mode, int offset = 0) {<br>
&gt;&gt; &gt;   int a, b;<br>
&gt;&gt; &gt;   if (mode == 1) { return (intptr_t)&amp;a - (intptr_t)&amp;b; }<br>
&gt;&gt; &gt;   intptr_t a_int = (intptr_t)&amp;a;<br>
&gt;&gt; &gt;   intptr_t evil_a_int = (intptr_t)&amp;b + offset;<br>
&gt;&gt; &gt;   int *evil_a = (int*)evil_a_int;<br>
&gt;&gt; &gt;   printf(&quot;%&quot; PRIdPTR &quot; %&quot; PRIdPTR &quot; %p %p\n&quot;, a_int, evil_a_int, &amp;a,<br>
&gt;&gt; evil_a);<br>
&gt;&gt; &gt;   if (getchar() == &#39;x&#39;) std::terminate(); // #1, allow user to abort if<br>
&gt;&gt; integer/pointer values differ<br>
&gt;&gt; &gt;   a = 1;<br>
&gt;&gt; &gt;   return *evil_a;<br>
&gt;&gt; &gt; }<br>
&gt;&gt; &gt; int main() { return f(2, f(1)); }<br>
&gt;&gt;<br>
&gt;&gt; The precise interaction of UB with user input is another can of worms<br>
&gt;&gt; that we&#39;ve not yet really opened :-)     But, assuming that programs<br>
&gt;&gt; can rely on facts about user input (perhaps based on whatever has been<br>
&gt;&gt; output), which seems reasonable, and that here the user is &quot;required&quot;<br>
&gt;&gt; to press x if the values differ, then<br>
&gt;&gt;<br>
&gt;&gt; &gt; Is my implementation conforming if it prints out equal integer and<br>
&gt;&gt; pointer values here, and yet (after the program is resumed by the user)<br>
&gt;&gt; main doesn&#39;t return 1? Based on your description, I think the answer is<br>
&gt;&gt; no,<br>
&gt;&gt; which means that my weird pointer comparison machine (including a human<br>
&gt;&gt; component) is effectively enough to keep the pointer exposed.<br>
&gt;&gt;<br>
&gt;&gt; As Martin said, the mode==1 execution of f isn&#39;t very interesting, as<br>
&gt;&gt; the second allocations of a and b could be arbitrarily spaced.  But<br>
&gt;&gt; then in the mode!=1 execution, a and b are both exposed, so the cast<br>
&gt;&gt; (int*)evil_a_int will give usable provenance iff the passed-in offset<br>
&gt;&gt; is correct, so if the user is required to check that, this should be<br>
&gt;&gt; well-defined to return 1.<br>
&gt;&gt;<br>
&gt;&gt; If line #1 is deleted, then in some executions a_int will be distinct<br>
&gt;&gt; to evil_a_int (by allocation nondeterminism), so indeed it will be UB.<br>
&gt;&gt;<br>
&gt;&gt; &gt;However, if line #1 is deleted, I believe an implementation that prints<br>
&gt;&gt; out equal values and then does not return 1 would be correct: the<br>
&gt;&gt; implementation can claim that a_int != evil_a_int, so that the &#39;return<br>
&gt;&gt; *evil_a;&#39; had undefined behavior, and therefore it was permitted to do<br>
&gt;&gt; whatever it liked, including printing out values that appeared to be the<br>
&gt;&gt; same.<br>
&gt;&gt;<br>
&gt;&gt; &gt;I /think/ the upshot is that you *can* optimize away a<br>
&gt;&gt; &gt; pointer-to-integer<br>
&gt;&gt; cast if its value is unused, but you cannot optimize one away in the<br>
&gt;&gt; presence of potential data- or control-dependencies on it (including<br>
&gt;&gt; indirect ones such as escaping the pointers through the IO system and<br>
&gt;&gt; comparing them somewhere else, then reading back the result) that might<br>
&gt;&gt; in<br>
&gt;&gt; any way influence a later integer-to-pointer cast. Does that match your<br>
&gt;&gt; intent?<br>
&gt;&gt;<br>
&gt;&gt; yes<br>
&gt;&gt;<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt; Integer-to-pointer casts seem to have surprising evaluation semantics<br>
&gt;&gt; &gt; as<br>
&gt;&gt; a result of this approach. It seems reasonable to me to give cases like<br>
&gt;&gt; this defined behavior:<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt; char buffer[32];<br>
&gt;&gt; &gt; struct A { int n; };<br>
&gt;&gt; &gt; struct B : A {};<br>
&gt;&gt; &gt; struct C : A {};<br>
&gt;&gt; &gt; A *p = new (buffer) B;<br>
&gt;&gt; &gt; intptr_t x = (intptr_t)p;<br>
&gt;&gt; &gt; A *q = new (buffer) C;<br>
&gt;&gt; &gt; A *r = (A*)x; // ok, r points to the A base of the C object<br>
&gt;&gt; &gt; r-&gt;n = 123; // OK, r points to the new A object not the old one<br>
&gt;&gt;<br>
&gt;&gt; If we think of an analogous example in which the B and C objects are<br>
&gt;&gt; heap or stack allocated (with non-overlapping lifetimes) that happen<br>
&gt;&gt; to get the same address, with the code checking that, then:<br>
&gt;&gt;<br>
&gt;&gt; &gt; ... but it matters when the cast from integer type to pointer type is<br>
&gt;&gt; performed.<br>
&gt;&gt;<br>
&gt;&gt; that does indeed make a difference.<br>
&gt;&gt;<br>
&gt;&gt; &gt;If we move the cast to pointer type earlier, the resulting example would<br>
&gt;&gt; not be defined under the &quot;points within a live object&quot; / launder model:<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt; A *p = new (buffer) B;<br>
&gt;&gt; &gt; intptr_t x = (intptr_t)p;<br>
&gt;&gt; &gt; A *r = (A*)x; // ok, r points to the A base of the B object<br>
&gt;&gt; &gt; A *q = new (buffer) C;<br>
&gt;&gt; &gt; r-&gt;n = 123; // undefined: r points to the old A object that is not<br>
&gt;&gt; within its lifetime<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt; Giving casts between pointer and integer types side-effects, and<br>
&gt;&gt; &gt; effects<br>
&gt;&gt; that depend on when they&#39;re evaluated, makes me nervous.<br>
&gt;&gt;<br>
&gt;&gt; As (I think) Jens said, the pointer-to-integer cast side-effect, of<br>
&gt;&gt; marking the storage instance as exposed, seems like it more-or-less<br>
&gt;&gt; has to be a temporal thing.<br>
&gt;&gt;<br>
&gt;<br>
&gt; If I&#39;m understanding the model correctly, I think that depends on your<br>
&gt; perspective.<br>
&gt;<br>
&gt; If I understand correctly, a pointer-to-integer cast is only temporal to<br>
&gt; the extent that there must be something &quot;forcing&quot; it to happen before a<br>
&gt; integer-to-pointer cast that depends upon it exposing an object. (Ideally,<br>
&gt; I think we&#39;d like to specify that the integer-to-pointer cast has a<br>
&gt; dependency on the pointer-to-integer cast, but that&#39;s a major can of worms<br>
&gt; comparable to the consume memory order.)<br>
<br>
(quite. let&#39;s not :-)<br>
<br>
&gt;  If we model a program execution as<br>
&gt; a set of possible executions (with defined behavior only if all possible<br>
&gt; executions have defined behavior), and model pointers as having<br>
&gt; nondeterministic corresponding integer values, then a pointer-to-integer<br>
&gt; cast (along with the other mechanisms that expose pointers as integers) can<br>
&gt; be viewed as a pure mathematical function (and in particular, it has no<br>
&gt; temporal dependence nor side-effects), but it&#39;s an oracle that exposes<br>
&gt; information that is not observable in any other way -- and we can see that<br>
&gt; a program that never uses the oracle cannot possibly correctly &quot;guess&quot; the<br>
&gt; integer corresponding to a pointer[1][2], so any integer-to-pointer<br>
&gt; conversion must necessarily introduce a possible execution with undefined<br>
&gt; behavior.<br>
&gt;  [1]: The oracle is the only way to determine the relevant information<br>
&gt; about which execution in the set of possible executions is currently<br>
&gt; occurring.<br>
<br>
All that&#39;s a pretty exactly description of the PNVI-plain variant.<br>
PNVI-ae-udi keeps the result of the pointer-to-integer cast pure, but<br>
adds the &quot;make this storage instance exposed&quot; side effect.  And that<br>
makes a difference only to the integer-to-pointer casts one can do.<br>
<br>
(treating reads of representation bytes and suchlike as similar to p-to-i casts,<br>
and accesses via pointers that have, for example, had some of their<br>
bytes written via char* pointers, as similar to i-to-p casts)<br>
<br>
&gt;  [2]: In principle, a program could keep allocating memory and casting<br>
&gt; pointers to integers until it&#39;s seen all integer values except one, and<br>
&gt; then correctly guess the address of the remaining object. But an<br>
&gt; implementation can prevent that by refusing to allocate all addressable<br>
&gt; memory.<br>
<br>
y.  This is the concern that pushed Juneyoung Lee et al. into their twin<br>
allocation model.   But instead, for a source language, we can just<br>
limit attention to programs that never almost-exhaust memory<br>
(ie that leave space for a copy of their biggest (and suitably<br>
aligned) allocation).<br>
<br>
<br>
<br>
&gt;<br>
&gt;&gt; The integer-to-pointer cast *could* be left ambiguous until it&#39;s<br>
&gt;&gt; resolved - but that allows other strange things, eg it can be<br>
&gt;&gt; (eventually) resolved to an object that didn&#39;t even exist earlier.<br>
&gt;&gt; The temporal view seems (I hope :-) quite easy for programmers to<br>
&gt;&gt; understand.<br>
&gt;&gt;<br>
&gt;<br>
&gt; I think it&#39;s surprising either way -- either you find that a cast from<br>
&gt; integer to pointer has an execution side-effect, and it matters where you<br>
&gt; write it, or you find that you can cast an integer into a pointer to an<br>
&gt; object that doesn&#39;t yet exist at the point of the cast.<br>
<br>
true<br>
<br>
&gt; I think the former is surprising for practicing programmers, whereas the<br>
&gt; latter is likely only surprising for language lawyers.<br>
<br>
hmm - the latter would be annoyingly complex to specify (like the &quot;udi&quot;<br>
part of PNVI-ae-udi but worse, as one gradually accumulates constraints<br>
on what the result of such a cast might be pointing to, based on<br>
what arithmetic is done to the pointer).   It&#39;s hard to imagine that becoming<br>
widely understood...<br></blockquote><div><br></div><div>Under <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0593r3.html">p0593r3</a> (recently approved by WG21&#39;s Evolution Working Group and on its way towards C++20), a similar &quot;pick the answer that gives the program defined behavior&quot; rule will already be in use in C++ for other cases. That&#39;s not to say that that means it&#39;ll be understood, but we will at least have precedent.</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
&gt; Moreover, I&#39;d expect<br>
&gt; the latter to be the model that implementations actually use,<br>
<br>
Can you expand on that?  Not sure I understand.<br></blockquote><div><br></div><div>Here&#39;s an example in C++ where implementations might care about which object a pointer points to:</div><div><br></div><div>struct A { virtual void f() = 0; };</div><div>struct B : A { void f() override; };</div><div>struct C : A { void f() override; };</div><div>alignas(B, C) std::byte storage[std::max(sizeof(B), sizeof(C))];</div><div>A *p = new (storage) B;</div><div>intptr_t n = (intptr_t)p;</div><div>A *q = (A*)n; // #0</div><div>new (storage) C;</div><div>q-&gt;f(); // #1</div><div>q-&gt;f(); // #2</div><div><br></div><div>The C++ rules allow us to assume that the two q-&gt;f() calls call the same function: the same pointer value is used, so the pointer must point to an object with the same dynamic type. This permits us to remove a redundant vptr load in line #2.</div><div><br></div><div>Under the temporal model, an implementation could go further and prove that there&#39;s a B object within its lifetime at the point of the integer-to-pointer cast in line #0, and thereby decide that #1 and #2 both call B::f instead of C::f. I&#39;m suggesting that implementations aren&#39;t going to do that, and that instead they&#39;ll only make the more conservative assumption that #1 and #2 load the same vptr value without assuming what that value is.</div><div><br></div><div>More broadly, I would expect the implementation model actually used for pointer-to-int conversions and int-to-pointer conversions to be based on (a conservative approximation to) determining on which pointer-to-integer conversions a given integer-to-pointer conversion is value- or control-dependent, rather than giving either conversion side-effects in the intermediate representation.</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
&gt;  so the<br>
&gt; difference between the two will become a theoretical gotcha and we&#39;ll end<br>
&gt; up with theoretically non-portable code relying on such casts being<br>
&gt; timeless.<br>
&gt;<br>
&gt; Regarding the pointer_from_integer_1pg.c example, I think we could address<br>
&gt; that case in a somewhat different way. Currently in C++ we have a rule that<br>
&gt; says:<br>
&gt;<br>
&gt; &quot;&quot;&quot;<br>
&gt; When the end of the duration of a region of storage is reached, the values<br>
&gt; of all pointers representing the address of any part of that region of<br>
&gt; storage become invalid pointer values (6.7.2).<br>
&gt; &quot;&quot;&quot;<br>
&gt;<br>
&gt; and similarly in C:<br>
&gt;<br>
&gt; &quot;&quot;&quot;<br>
&gt; The value of a pointer becomes indeterminate when the object it points to<br>
&gt; (or just past) reaches the end of its lifetime.<br>
&gt; &quot;&quot;&quot;<br>
<br>
Yes, although there is currently a move for C to at least partially remove that,<br>
as there are a bunch of longstanding concurrent algorithms that depend on<br>
== comparison with pointers to lifetime-ended objects.  Not sure what will<br>
happen there.<br>
<br>
&gt; (I&#39;m ignoring the memory model problems with the use of &quot;become[s]&quot; here.)<br>
&gt; It would seem natural to extend this so it applies at both the point of<br>
&gt; allocation and the point of deallocation. Then it&#39;s not the cast to pointer<br>
&gt; that has temporal behavior; rather, it&#39;s the allocation of the<br>
&gt; automatic-storage-duration variable that causes there to be no valid<br>
&gt; pointers into that region of storage, and changes the value of &#39;p&#39; in the<br>
&gt; example to an invalid/indeterminate pointer value. (It still ends up<br>
&gt; mattering whether you perform the cast inside or outside the function, but<br>
&gt; for a different reason.)<br>
<br>
That&#39;s exotic - never thought of that possibility.  But I would like to have a<br>
semantics that doesn&#39;t rely on lifetime end-zap if we can.<br>
<br>
&gt; This might be equivalent to integer-to-pointer casts being temporal in C<br>
&gt; (because objects are by definition the same as the storage regions they<br>
&gt; occupy), but not in C++.<br>
&gt;<br>
&gt;&gt;I think it&#39;d be preferable to give them a single-but-unknown provenance,<br>
&gt;&gt; following the &quot;pick whichever single value makes the rest of the program<br>
&gt;&gt; work&quot; model of P0593R3 (notionally pretty similar to C&#39;s effective type<br>
&gt;&gt; rule -- you resolve to the first provenance that you use with the<br>
&gt;&gt; pointer).<br>
<br>
If an access is first, that&#39;s not too complex, but if pointer arithmetic happens<br>
first, it gets messy to record the resulting constraints.  And we get a lot more<br>
instantaneous action-at-a-distance as these get resolved.<br>
<br>
&gt;&gt; That&#39;d mean casts to pointer type are timeless and freely reorderable,<br>
&gt;&gt; and<br>
&gt;&gt; both the above examples are defined, not only the first one. However,<br>
&gt;&gt; N2363<br>
&gt;&gt; has a scary example involving guessing the address of a function<br>
&gt;&gt; parameter<br>
&gt;&gt; that is apparently defanged by the time-dependence of integer-to-pointer<br>
&gt;&gt; casts. To what extent is that essential? Is there a different way that<br>
&gt;&gt; guessing a pointer value could be disallowed?<br>
&gt;&gt;<br>
&gt;&gt; Simple allocation-address nondeterminism disallows pointer value<br>
&gt;&gt; guessing.<br>
&gt;&gt;<br>
&gt;&gt; thanks,<br>
&gt;&gt; Peter<br>
&gt;&gt;<br>
&gt;&gt;<br>
&gt;&gt; &gt;&gt; The user-disambiguation refinement adds some complexity but supports<br>
&gt;&gt; &gt;&gt; roundtrip casts, from pointer to integer and back, of pointers that<br>
&gt;&gt; &gt;&gt; are one-past a storage instance.<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt;<br>
&gt;<br>
<br>
best,<br>
Peter<br>
</blockquote></div></div>