Pointers Are Complicated III, or: Pointer-integer casts exposed
ralfj.de
ralfj.de
> the original program already had Undefined Behavior, or (at least) one of the optimizations is wrong.
As far as I know, since the uwu function was declared with x and y as restrict, the way uwu is called in main is undefined behavior. Because they are both pointing into the same array, and are both 'derived' from the same array.
I guess if I am wrong its because `restrict` does not care if both are derived from the same pointer. Instead it might only care if x was derived directly from y or y was derived directly from x. Is there a good reason to only care about this 'directly derived from' rather than 'shared derivation'? It would sorta suck if you can't use a function with to restrict arguments pointing into the same array, but this example seems to suggest that might be a reasonable requirement.
I just checked cppreference and it says: "if some object that is accessible through P [marked restrict] (directly or indirectly) is modified, by any means, then all accesses to that object (both reads and writes) in that block must occur through P (directly or indirectly), otherwise the behavior is undefined"
The only accesses I can see are "*x = 0;", "*ptr = 1;" and "return *x;". "*ptr" is an indirect access through "x" by way of an integer round trip. If the orignal program is indeed UB that would mean that pointers built from an integer round trip are not considered (indirect) accesses through the original pointer.
The author assumes that it is an indirect access: "However, the only possibly suspicious part of the original program is a pointer-integer-pointer round-trip – and if casting integers to pointers is allowed, surely that must work. I will, for the rest of this post, assume that replacing x by (int*)(uintptr_t)x is always allowed."
The weak provenance solution is to ban optimizing the pointer to integer casts since they have a (conceptual) side-effect. The strict provenance proposal points out that the side effect is only observable if the integer is cast back to a pointer, so we can keep optimizing pointer to integer casts and instead ban integer to pointer casts. For example, an operation like `(int *)xaddr` is banned under strict provenance. Instead, we provide a casting operation that includes the pointer to assume the provenance of; something like `(int * with x)xaddr`. With this new provenance-preserving cast, we can see that the final optimization of replacing `*x` with its previously assigned value of `0` is no longer possible because the code in between involves `x`.
Yeah this article is great and the framing is pretty perfect. It really shows that optimization passes can't remove information, else they run the risk of tricking later passes. I definitely agree with OP that "the incorrect optimization is the one that removed xaddr"; that optimization seems wild to me. You only know y is x + 1 because of the way it's constructed in the calling function (main). So the compiler... inlines an optimized version of the function that removes most use of x? Isn't that optimizer fundamentally broken? Especially in a language with `volatile` and `restrict`?
I guess what bugs me about optimizations is that it feels like something _I_ should be doing. Like if GCC told me this code optimizes down to printf 1 and why, I'd question what I was doing (and rightly so). Doing it automatically feels like too much spooky action at a distance.
In the first example the integer-cast derivative of the second pointer does alias the first but isn't used to access either object, therefore there is (or should be) no UB.
> I guess if I am wrong its because `restrict` ...
The `restrict` keyword was -IIUC- specifically intended to make it possible to write functions like `memcpy()` w/ optimizations that are made possible by knowing that the pointed-to objects do not overlap. The semantics of that are crystal clear in a hand-wavy way, but... very difficult to nail down exactly.
With a hand-wavy definition of `restrict` it's pretty clear that the first `uwu()` is perfectly fine and has no UB.
Not only do they encourage unsafe practices, being forbidden in most C/C++ style guides.
They can also wreak havoc with any hardware or software-hardening that puts non-address bits in pointers. For instance, ARM PAC has been in Apple's CPUs for years now and support is mandatory in ARM v8.3 and up. Intel and AMD have also had experimental extensions that use those bits, and new ones are on the way.
When an integer is cast to pointer in those schemes, the pointer can become invalid. Therefore, an integer cast to a pointer type should always result in an invalid pointer, IMHO.
Integers are sometimes used to set alignment. I'd suggest that the macros in CHERI C be added to <stdalign.h>: align_up, align_down and is_aligned. Those can be expressed in normal C without having to cast an integer to pointer, or use faster expressions depending on the platform.
struct SomeStruct {
...
void (*callback)(A);
A user_data;
}
Here A will be either int or void*. But regardless of stated type, the expectation is that the user puts whatever they find the most convenient there, whether thats a pointer or integer (fd maybe?).The article shows a piece of code that makes that promise and then doesn't hold up its part of the agreement. I can't even follow the rest of the argument from there because it's all based on a faulty foundation.
Edit: disregard; I now see your other comments.
Incorrect. The article shows a piece of code that makes and fulfills that promise, and then the optimization passes break the code. If you disagree, I would be interested in hearing how/where you think the code itself is faulty.
The first snippet is subtle, and I'm not a language lawyer, but I can't see anything that screams "contract violation".
Ultimately the C (and C++) memory model is meant to help us write fast, safe software. Developers must be able to reason about the semantics of their code. Optimising short parts of the program that can be reasonably checked by a person for not invoking UB is probably the right way and will result in fast software with fewer bugs due to programmer error.
Edit: Not sure why people are downvoting this, it's like there's some UB religion out there. Do you people want your programs to crash due to the compiler doing surprising things?
int foo(void) { return 42; }
int bar(void) { return foo() + foo(); }
If the whole file is compiled as -O0, foo and bar must be compiled as-is. If the whole file is compiled as -O2, bar will be optimized to "return 84;". But what if foo is compiled as -O0 and bar is compiled as -O2? Can bar still be optimized to "return 84;"? What about "return 42+42;"? Can it do "int x = foo(); return x+x;"? What about "int x = foo(); if (x == 42) return 84; else return x+x;"? All of these are permitted in -O2 if gcc thinks it's a good idea, but in our "mixed mode" the semantics are unclear. It might be theoretically possible to come up with a consistent definition; it might start with "functions specified as -O0 cannot be inlined or have other functions inlined into them", but there are still more things to consider, like the behavior of global memory, thread-local storage, floating-point environment, and so on (what if one or more functions is static inline or __attribute__((always_inline))?).The real solution with current gcc is to simply compile different files ("translation units") with different optimization flags if you want. This has always been supported. Unfortunately, it comes with a potentially significant performance penalty; the whole point of LTO is to avoid this performance penalty, but this once again returns the issue of the semantics of mixing functions with different optimization levels.
I obviously can't speak for everyone who defends this sort of compiler behavior, but my preferred solution is for the compiler to continue performing these kinds of optimizations while also providing better static analysis and sanitizer tools to catch problems earlier, even if that involves extending or replacing the language to make that possible.
Expecting everyone who writes C or C++ to do this kind of reasoning in their head and perform these optimizations manually (or drop their performance back toward -O0) just sounds like it would make things worse.
Fast, maybe. Safe, not really.
Doesn't that mean the compilers are non-compliant? uintptr_t is defined in terms of supporting round-trip conversions, no?
> So, which of the optimizations is the wrong one?
In the original function, the code compares addresses but only modifies through `x`. But since it's a static function, and since it can see all the call sites, it eliminates a test it can prove is always true. So far so good.
However, it's not valid to keep the `restrict` declarations after that rewrite. It changed a modification through `x` to a modification through `y`, so that clearly violates the promise.
The original version with two stores and one load doesn't seem to have a problem. Having the optimizer punt when it gets confused by integer to pointer casts seems acceptable.
Instead, you'd have to instead prove that there's no possible int2ptr cast around everywhere, which is generally an impossible analysis.
Complicated or not, it's necessary that optimizations do not break correct code.
There doesn't seem to be a problem (UB or otherwise) in the first function at the top of the article, but the second one has a clear aliasing problem that violates the promise the `strict` makes. That translation was invalid.
And if maintaining `restrict` for other passes is really important, maybe the order of the passes should be changed. I'm not pretending compiler optimization is simple, but I can't see any situation where having an incorrect optimization pass run first is the right thing to do. The broken pass needs to be fixed, and it shouldn't have emitted code with incorrect `restrict` on it.
When you write to a pointer, it writes into memory. And it does not optimize it away. As if all pointers are volatile.
procedure x;
var a: integer;
b: pinteger;
begin
b := @a;
b^ := 1;
b^ := 2;
end;
becomes project1.lpr:15 begin
0000000000401090 50 push %rax
project1.lpr:17 b^ := 1;
0000000000401091 c7042401000000 movl $0x1,(%rsp)
project1.lpr:18 b^ := 2;
0000000000401098 c7042402000000 movl $0x2,(%rsp)
project1.lpr:19 end;
000000000040109F 59 pop %rcx
00000000004010A0 c3 ret
Perhaps Delphi, tooAlthough I do not know how much is by design and how much comes from bugs in the optimizer. But they said that one reason why they maintain their own optimizer rather than switching to LLVM. LLVM would "break" this function
E.g. treat memory strictly as some sort of separate storage device, and do all computations in local register-like variables (which could be backed by a special invisible scratch space to allow variables to spill from registers to scratch space.
Basically a language which would require explicit load/stores from and to regular memory, instead of trying to provide the illusion of an infinitely large register bank.
You still have to manage a separate storage device somehow (most often via file systems drivers). That's why GCs are so popular: you have a smart "driver" between your code and a RAM storage that makes life so much easier.
Still, this requires the hardware to cooperate with software instead of pretending that random access memory is truly random access. This functionality just isn't there at this point.
Edit: this would require programmable caching hardware to make caching analysis and algorithms introspectable and changeable. For now, fixed caching algorithms implemented in hardware do provide lots of benefits.
Also it’s said that casts to int are better then transmutes because cast have side effects. But ptr2int casts don’t actually have side effects, they are a No-op on the hardware.
About side effects: we are not talking about having side effects on hardware level, we are talking about side effects from the compiler's viewpoint. Again, the compiler might track aliasing information for optimizations, and casting has the side effect of "exposing" the pointer.
AFAIK, you do understand. ptr2int casts are totally fine and defined behavior, as long as the program contains no int2ptr casts. Is there a passage from the OP that contradicts this?
> But in this case, the operation in question is (uintptr_t)x, which has no side-effect – right? Wrong. This is exactly the key lesson that this example teaches us: casting a pointer to an integer has a side-effect, and that side-effect has to be preserved even if we don’t care about the result of the cast. ... We have to lose some optimization, as the example shows. However, the crucial difference to the previous section is that only code which casts pointers to integers is affected.
So even if we never even use the result, casting a pointer to an integer is problematic.
But in the explanation he only talks about the problems of int2ptr cast, which I do undestand.
Rust now (experimentally) has an `ptr.addr()` operation that is like ptr2int without the "expose" part, i.e., the resulting integer cannot be cast back but still used for other purposes.
That is not guaranteed. The only guarantee is that you can round trip conversions via (u)intptr_t. The integer representation of a converted pointer can be completely different to accommodate hardware like the Symbolics lisp machine.
ARM already includes a small part of CHERI (pointer signing) and the rest is coming.
The author clearly states that restrict is a promise, and then immediately violates that promise. If you lie to the compiler, what do you expect to happen?.
Declaring your pointers "restrict" is a promise from your code to the compiler, not from the compiler to your code.
It seems like the entire rest of the article is based on this misunderstanding, and there are a whole bunch of strong statements and questionable conclusions drawn from it:
> This is bad news. Our optimizations changed program behavior. That must not happen!
> the only possibly suspicious part of the original program is a pointer-integer-pointer round-trip
> while the details of defining “derived from” are nasty, it should be clear that doing a bunch of operations that literally don’t involve x at all cannot by any stretch of the imagination produce a result that is “derived from” x
It's hard for me to make sense of the rest of the business about "provenance" when the problem it's supposed to solve are based on faulty reasoning. The part about casting to integer having side effects doesn't follow either.
I've always liked C, and I'm coming to really like Rust. I hope mistaken arguments like the ones in this article aren't persuasive to anyone in the compiler camps.
Which part of the program do you believe violates the restrict promise?
I think it's not obvious that it is violated. The question is whether a pointer obtained through an integer round trip is considered to be derived from the original pointer. The author assumes that it is.
The part where they modify a piece of memory using `x` and then modify the same piece using `y`.
That's a promise the programmer made inside that function and for anyone else who calls the function. This is C, so it's not the compiler's job to prove the implementer or caller got it right.
If the conclusion was that the C standard could/should tighten up its verbiage for cases like this, I'd agree. But the conclusion is something about adding provenance to integers...
When you call that function with two pointers that happen to be adjacent but non-overlapping, that promise is absolutely satisfied.
The problems start with the notion of 'derived from' and pointer arithmetic. Obviously you can derive something from `y` that aliases `x` by subtracting the offset from `y`. But that's always true, and that – I believe – is the author's point. If you view this as 'derived from', you can never satisfy any restrict guarantee on a function whose implementation you have not carefully reviewed.
As for having influence: Ralf Jung has written a PhD thesis on the correctness of Rust (including formal proof that the model is sound), so I'm inclined to believe people are willing to listen to him, and rightly so.
... and then promptly uses pointer arithmetic to create an alias. It violated the promise.
> If you view this as 'derived from', you can never satisfy any restrict guarantee on a function whose implementation you have not carefully reviewed.
This is C, not Rust. You aren't getting proofs without careful review. You're using "restrict" in order to ask the compiler to create a faster function on the promise that it will never be used in a way that the pointers alias. It's a promise that needs to be upheld within the function and for everyone who calls the function.
Think of something like `memcpy` (overlaps not allowed) vs `memmove` (overlaps allowed). The compiler can make the first one faster, and it is your job to uphold the contract.
> As for having influence: Ralf Jung has written a PhD thesis on the correctness of Rust
Appeals to authority aren't motivating to me. I've known and worked with a whole lot of PhDs, and they aren't always right. In fact, frequently the ones I've known are overly confident when they step outside of their area of expertise.
afaik not creating aliased pointers is not a promise you make when marking a pointer as restrict. Only when using that aliased pointer for accesses (in the presence of modifications) can you actually break the promise. To quote cppreference:
"During each execution of a block in which a restricted pointer P is declared (typically each execution of a function body in which P is a function parameter), if some object that is accessible through P (directly or indirectly) is modified, by any means, then all accesses to that object (both reads and writes) in that block must occur through P (directly or indirectly), otherwise the behavior is undefined"
Ok, creating aliases you don't use is probably fine. I should've worded that more carefully.
However, the example code certainly modifies the same piece of memory from two different pointers, and so it broke the promise.
However, I think it is quite clear that the intention of the standard is to allow my program. It would certainly allow the alternative where the `if (xaddr == y2addr) {` is removed and its body (then-branch) put directly into the main function. It makes no sense to disallow this program just because it introduces an extra condition, ergo I claim this program has defined behavior.
I think this is where a lot can go wrong in terms of understanding.
It was my understanding that the restrict keyword requires a promise of the caller. Specifically, the caller promises that the arguments it passes do not alias. It was my point that the `uwu` function could not violate that promise, because it wasn't the one who made it.
After review of the standard, I'm no longer certain this is the case.
Disregarding this line of reasoning entirely, I still believe the author is correct.
The following is an excerpt from the Standard (or rather the latest available draft):
> An object that is accessed through a restrict-qualified pointer has a special association with that pointer. This association, defined in 6.7.3.1 below, requires that all accesses to that object use, directly or indirectly, the value of that particular pointer
The `uwu` function complies with that requirement. While it does create an aliasing pointer derived from `y`, it never uses it to actually access memory. Hence, it does not violate the requirement imposed by restrict.
The aliasing access derived from `y` (which may well be undefined behavior after all) is only introduced after one of the optimization passes. Thus, this optimization is incorrect and may not be applied.
Edit: of course, there is an aliasing pointer in the original version of `uwu`. This pointer, however, is derived from `x`, thus not violating any guarantees implied by restrict.
You're right, and I was wrong in my initial take on things.
I currently believe the first version of the code is fine, but that the second version is clearly incorrect. There, you'll see it's using an address derived from `y` to modify memory modified through `x`. The translation to the second version should either not be allowed, or it should not continue to declare the arguments as `strict`.
As for the promise that `strict` makes, I interpret to be a promise both about the function and a promise from all callers of the function. In other words, it's a promise from the programmer(s) to the compiler, and nothing more.
For instance, the arguments to `memcpy` were declared as `restrict`. The documentation doesn't allow overlapping ranges, so the implementation would be fine. However, `uwu` could call it with overlapping ranges. It doesn't matter if `uwu` didn't make the promise, it has to uphold it for correct behaviour.
I now understand the standard such that it implies that promises made by `restrict` must be upheld in the whole program and distinctions between caller and callee are irrelevant. Both must cooperate such that the promises remain true.
As for the given code for `uwu`, I also believe the first version is allowed and the second is not. Since the second is the result of (hypothetical proposed) optimizations, the latter may not be applied even though they seem fine on the surface.
So we must have a way to determine when optimizations may be applied and imbuing pointer-to-integer casts with side effects certainly fixes this situation. It seems like a sound approach, but I do hope for a formal analysis, of course.
Yup, silly typos. Sorry about that.
(FWIW I fully agree re: appealing to authority. I'd rather people engage with my arguments than take them on face value.)
Note that the use case here is for llvm IR that the Rust compiler generates. It seems very hard in general to know if two mutable references point to items in the same allocation.
Any sort of pointer arithmetics in the presence of restrict pointers seems like a giant footgun.
As far as I can read the standard, it does not forbid aliasing `restrict` pointer as long as these pointers are not used to access anything. Here y2 aliases with x, but at no point is anything accessed through y2 (whether reading or writing). So it I feel like it respects the letter of the law.
> Does the standard not allow you to pass any pointers which could be aliased based on arbitrary pointer arithmetic
With the right hackish arithmetic, all pointers could be aliased with pointer arithmetic in C. Using `restrict` says your code won't do that so the compiler can optimize more.
> It seems very hard in general to know if two mutable references point to items in the same allocation.
Yeah, and in the limit it's impossible for C. It's not the compiler's job to figure it out, it's the code's job to uphold the promise.
It's different with Rust because you've got mutable/exclusive or immutable/shared promises from the borrow checker. And Rust doesn't let you dereference raw pointers outside of an unsafe block.
It seems to me that mislabeling your pointers `restrict` in C is akin to doing reckless operations inside of `unsafe` blocks in Rust. In both cases, you told the compiler "trust me, I know what I'm doing".
This is not true. The behaviour is undefined in C if pointer arithmetic results in crossing the beginning or end of an object. If we have int a, b;, then the behaviour is undefined if we do &b - &a even if we never use the result to try to reconstruct &b from &a.
> Using `restrict` says your code won't do that so the compiler can optimize more.
`restrict` covers objects modified through one pointer and accessed through another. It does not cover pointers that point to the same object in general; if only one is used to access the object, or if both are used only for reading, all is fine. (It's actually a little bit more complicated than I'm presenting here, but not in a way that's relevant right now.)
Yes, it's technically UB. But which operation could any C compiler disallow in practice: converting from pointer to integer, arithmetic on integers, or converting from integer to pointer?
int a[2], b[2];
int offset(void) { return b - a; }
int check(void) { return &a[1] + offset() == &b[1]; }
The function check() is optimised by GCC to return zero at -O1 optimisation level or higher, because it reasons that no matter how offset() is implemented, either the addition is undefined, or the comparison results in false.(Note that GCC does this even in a few cases where it is unclear whether the optimisation is valid. The example I provided is slightly more complicated than I would have liked to avoid that issue; the optimisation is definitely valid in this example.)
However, your code doesn't actually address my comment about conversions and arithmetic. Where does this code code fail?
int a[2], b[2];
uintptr_t x = (uintptr_t)a;
uintptr_t y = (uintptr_t)b;
uintptr_t offset = y - x;
int* p = (int*)(x + offset);
printf("b == p: %d\n", b==p);
I didn't try _every_ compiler on Godbolt, but I didn't see it misbehave anywhere I did try.edit: changed to uintptr_t
If I had used `uintptr_t` instead of `ssize_t`, I think it's even compliant as far as wraparound goes.
edit: Note I changed the code to use uintptr_t
Yeah, but I did say: "which operation could any C compiler disallow in practice: converting from pointer to integer, arithmetic on integers, or converting from integer to pointer?"
If C/C++ compilers keep breaking pointer arithmetic in the game of exploiting undefined behavior for optimizations, people are going to start doing pointer arithmetic with integers when they need it. And they do need it sometimes, for debuggers, profilers, memory checkers, JITs, garbage collectors, shared memory, and so on.
If I could, I would disallow integer to pointer---there are (in my opinion) better ways to address a hardware register than to do
unsigned char *uart = (unsigned char *)0xdeadbeef; /* [1] */
But I don't think it will go away any time soon in C (Over 30+ years later, and we're still a few years out from 1's compliment and signed magnitude integers being deprecated).[1] Here's one way using NASM:
global uart
absolute 0xdeadbeef
uart resb 1
Then in C code: extern unsigned char uart;Yeah, and I know that's why the C standard has so much slop in it. They want to keep the window of conformance open for past or future architectures.
> If I could, I would disallow integer to pointer [...]
It's not just about 0xDeadBeef hardware addresses, and I don't think anyone is talking about breaking assembly language.
However, C has unions and Rust has enums. You want those to share the same piece of memory in the fewest bytes you can get away with. Some interpreters, written in C, even use nan-tagging to store the important 48 bits of a pointer in the 52 bits of payload with double precision floats.
You don't have to like it, and you can discourage your children or coworkers from doing it, but there are legitimate reasons for all of this. The examples look ugly because they're short and stupid.
And nobody has even mentioned that `A[i] == (A + i) == (i + A) == i[A]` is going to continue to be valid in C. https://stackoverflow.com/a/381549
The first fundamental issue is that pointer provenance is an area where people have (historically) dealt with the issue as essentially a requirement of data-dependency: for a dereference `p` to access an object x, then p must be at the end of a chain of arithmetic computations where &x is one of the inputs. It turns out, however, that compilers do not preserve data dependency, and there is pretty unanimous agreement among compiler writers and specification writers that they should not* do so in general.
What this means is that the challenge we have is how to specify something that generally follows the data-dependent rules while still allowing compilers to do their non-data-dependency-preserving optimizations. There is a working group in the C committee that is doing this. The current leading candidate for a specification is based on the idea that you track provenance (i.e., data dependency) via pointers, but not via integers.
This means that now we need to pin down what the actual semantics are where you convert a pointer to an integer or vice versa, and this blog post is part of the effort of trying to pin down sane semantics here.
So, yes, restrict is a promise from the programmer to the compiler... but what is the programmer actually promising? That's what this blog post is exploring.
Yes, I was mistaken in my first read through. I'm taking your indignation with some amusement, but I hope you can be civil going forward.
If you look at the second snippet of code, you can clearly see how it declares the arguments `restrict` and then uses them to access the same piece of memory. That's *wrong* - it violated the promise made by `restrict`.
What I didn't see at first was that the first snippet of code appears to be fine. As far as I can tell, it doesn't break any rules. *So the optimization pass from the first version to the second version is incorrect.* After that, everything that follows is still questionable.
The following passes use the `restrict` promise from the second version to eliminate from the load from `x` and so on...
The first pass of your optimizer took a valid program and made an invalid one. That should be the end of the article, but then you ran further (presumably valid) optimization passes which led to erroneous results.
Your whole argument seems to look like:
(pointer_integer_pointer & invalidly_keeping_restrict) => erroneous_optimization
Since we can't have erroneous optimizations, you make a faulty conclusion the problem is pointer to integer round trips. You even say it here:> However, the only possibly suspicious part of the original program is a pointer-integer-pointer round-trip
But it's not the only suspicious part, so all of your following statements are questionable. The contradiction should've led you to:
!erroneous_optimization => (!pointer_integer_pointer | !invalidly_keeping_restrict)
Using Modus Tollens and De Morgan's. Instead you declare that casts have side-effects, dive into "integer provenance", make some distinction between "as" casts and transmute, and so on. Meanwhile, I conclude that broken optimizer passes should be fixed or removed.> I would argue that the alternative is to treat the original program (after translation to Rust) as having Undefined Behavior.
I'm sure you'll get your way, and you probably won't stop until Rust has as many tricky "undefined behaviors" for the compiler to exploit as they do in C. Programmers will need a masters degree in "provenance" to avoid the traps you set for them. Then when they stumble and fall in the pit you dug, they can go to the forums and be chastised by the experts. You'll get some .1% speedup on a non-realistic benchmark and declare it a job well done.
> There are, to my knowledge, generally two reasons why people might want to transmute a pointer to an integer
This "argument by lack of imagination" strategy isn't sound either. What if you just haven't thought of a third reason yet?
> I think we should move towards discouraging, deprecating, or even entirely disallowing pointer-integer transmutation in Rust. That means a cast is the only legal way to turn a pointer into an integer
It's really unclear to me why the compiler can't recognize that transmuting a pointer to an integer is the same as a cast. And if it can recognize that, why make it UB?!? Maybe this is all about those 129 bit CHERI pointers. If you want to break the compiler for just that platform, more power to you.
Really, I don't care if you break transmute, so long you leave a way to cast function pointers:
https://rust-lang.github.io/unsafe-code-guidelines/layout/fu...