-
Notifications
You must be signed in to change notification settings - Fork 16
[Guideline] Add do not divide by 0 #132
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
✅ Deploy Preview for scrc-coding-guidelines ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Hi @vapdrs! I have already sent you a message in Zulip, but here seems like a better place to do so. I come to add a couple of things to this guideline :)
There are other such operations for division, such as And for other arithmetic operations, there are quite a few functions one can use to avoid Undefined Behavior: https://doc.rust-lang.org/std/?search=checked
This combines rather well with Option, as in Option<NonZero>, since the compiler can do some memory layout optimization due to the fact that the value being enclosed by NonZero has one bit-pattern that is known to not be possible (the 000...000 pattern) I will review the PR shortly :3 |
:status: draft | ||
:release: latest | ||
:fls: fls_Q9dhNiICGIfr | ||
:decidability: Undecidable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whether the program is or isn't dividing by zero is indeed Undecidable.
But whether the program is performing checked arithmetic or not is Decidable. So... perhaps this guideline should be changed to enforce checked arithmetic?
By which I mean: maybe it shouldn't be "Do not divide by 0". Maybe it should always be checked arithmetic.
Let's talk about it on Zulip!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whether the program is or isn't dividing by zero is indeed Undecidable.
Hmm. Does that make this quite onerous to the engineer which must comply with this?
Perhaps it could be advisory
to allow easy deviation.
Maybe it should always be checked arithmetic.
This might be the right choice for another guideline, perhaps required
which would make every division use checked_div()
.
What do you think @vapdrs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a good suggestion. As discussed in #75 and #127, I think a good way forward would be to leave the categorization of this guideline Mandatory and Undecidable, and also create a Required, Decidable rule which enforces "Always use checked arithmetic". Compliance with the required rule would ensure compliance with the mandatory rule. I'll create that other rule in a separate PR.
The reason I think this approach makes sense is because of
Does that make this quite onerous to the engineer which must comply with this?
If you are starting off a new project with new code then it should be easy to comply with a "always used checked arithmetic" rule.
If instead you are inheriting a large project that now needs to become compliant with our guidelines, the safety engineer can pick their poison:
- do a large rewrite to comply with "always used checked arithmetic rule" to ensure you never divide by zero
- do a manual review and justification at every location where static analysis can't determine you aren't dividing by zero.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created #136
:id: rat_h84NjY2tLSBW | ||
:status: draft | ||
|
||
Integer division by zero results in a panic, which is an abnormal program state and may terminate the process. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed. It's even worse than that. Division by zero is Undefined Behavior, which is instant death for Safety Critical purposes :3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point -- let's call that out here @vapdrs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm don't know that it is Undefined Behavior. I'm pretty sure the behavior is defined as a guaranteed panic (regardless of debug mode).
- The FLS doesn't list it in the UB list
- The FLS section on arithmetic only mentions the panic
- The Rust Reference section on arithmetic only mentions the panic
- The Rust Reference's list of UB doesn't include it
- This random (older) forum post seems to indicate that in Rust it is deliberate that dividing by zero isn't UB
It would be pretty odd if you could get to UB that quickly in Rust with no unsafe
code in the compilation process. Please let me know if there is something I missed, I know that those UB lists aren't necessarily comprehensive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, you are totally right. I was using a wrong definition of UB. The reference definitely doesn't call it UB as of today 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, I just checked via MIR and LLVM bitcode, and the complier puts in an explicit, dynamic check for division by zero and panics.
So that's what it does right now, maybe that's something to write into the language standard.
Rust
pub fn great_divide(numerator: u32, denominator: u32) -> u32 {
numerator / denominator
}
Optimization Level: -Os
MIR
fn great_divide(_1: u32, _2: u32) -> u32 {
debug numerator => _1;
debug denominator => _2;
let mut _0: u32;
let mut _3: bool;
bb0: {
_3 = Eq(copy _2, const 0_u32);
assert(!move _3, "attempt to divide `{}` by zero", copy _1) -> [success: bb1, unwind unreachable];
}
bb1: {
_0 = Div(copy _1, copy _2);
return;
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very neat, thank you for looking! Perhaps this is worth bringing up to the opsem team Félix mentioned in the meeting today.
I'm not proficient in thumbv8m
, but if cbz
stands for "compare branch zero" I'd be interested in what it is comparing and where it branches, to see if that is guarding the udiv
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vapdrs, I went a little too quickly and built my object in "relocatable" mode, which buggered me up.
The zero check is not elided.
The complete assembly is below. It's a little convoluted because of how I had to build the minimal test case (apologies).
The 10000aac cbz
does indeed do the zero check and branches to 10000b02
, which loads the message and branches to the panic_const_div_by_zero
function.
Again, this is at opt=s
optimization.
10000aa8 <$t>:
10000aa8 <unicorn::sort::i32::hec332188e2428c44>:
10000aa8 <_entry>:
10000aa8: b5d0 push {r4, r6, r7, lr}
10000aaa: af02 add r7, sp, #0x8
10000aac: b34b cbz r3, 0x10000b02 <_entry+0x5a> @ imm = #0x52
10000aae: fbb2 f2f3 udiv r2, r2, r3
10000ab2: 2a2a cmp r2, #0x2a
10000ab4: d020 beq 0x10000af8 <_entry+0x50> @ imm = #0x40
10000ab6: 2902 cmp r1, #0x2
10000ab8: bf38 it lo
10000aba: bdd0 poplo {r4, r6, r7, pc}
10000abc: 2915 cmp r1, #0x15
10000abe: d21c bhs 0x10000afa <_entry+0x52> @ imm = #0x38
10000ac0: eb00 0c81 add.w r12, r0, r1, lsl #2
10000ac4: 1d02 adds r2, r0, #0x4
10000ac6: f04f 0e04 mov.w lr, #0x4
10000aca: e952 4101 ldrd r4, r1, [r2, #-4]
10000ace: 42a1 cmp r1, r4
10000ad0: da0d bge 0x10000aee <_entry+0x46> @ imm = #0x1a
10000ad2: 4673 mov r3, lr
10000ad4: 2b04 cmp r3, #0x4
10000ad6: 50c4 str r4, [r0, r3]
10000ad8: d007 beq 0x10000aea <_entry+0x42> @ imm = #0xe
10000ada: 18c4 adds r4, r0, r3
10000adc: 3b04 subs r3, #0x4
10000ade: f854 4c08 ldr r4, [r4, #-8]
10000ae2: 42a1 cmp r1, r4
10000ae4: dbf6 blt 0x10000ad4 <_entry+0x2c> @ imm = #-0x14
10000ae6: 4403 add r3, r0
10000ae8: e000 b 0x10000aec <_entry+0x44> @ imm = #0x0
10000aea: 4603 mov r3, r0
10000aec: 6019 str r1, [r3]
10000aee: 3204 adds r2, #0x4
10000af0: f10e 0e04 add.w lr, lr, #0x4
10000af4: 4562 cmp r2, r12
10000af6: d1e8 bne 0x10000aca <_entry+0x22> @ imm = #-0x30
10000af8: bdd0 pop {r4, r6, r7, pc}
10000afa: e8bd 40d0 pop.w {r4, r6, r7, lr}
10000afe: f7ff bba7 b.w 0x10000250 <core::slice::sort::unstable::ipnsort::h6ad55a7d1a61fa1d> @ imm = #-0x8b2
10000b02: 4802 ldr r0, [pc, #0x8] @ 0x10000b0c <_entry+0x64>
10000b04: 4478 add r0, pc
10000b06: f000 f864 bl 0x10000bd2 <core::panicking::panic_const::panic_const_div_by_zero::h68898a40cd2b15cb> @ imm = #0xc8
10000b0a: bf00 nop
10000b0c <$d>:
10000b0c: f8 f4 ff 0f .word 0x0ffff4f8
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@adfernandes so then if I understood correctly... the zero check isn't being elided at all, right? And... is that the case for all optimization conditions?
I ask because if it were being elided under some condition, such that the program no longer aborted execution after it divided by zero, I think that would be a bug in either rustc
's codegen step or in LLVM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@felix91gr, you are correct, the zero-check is not being elided, even under opt=s
conditions.
Because of how LLVM handles the udiv
and sdiv
LLVM-IR instructions, these two LLVM-IR instructions can produce undefined behaviour.
However, rustc
is explicitly adding a "zero-check" for the divisor. It would be a bug for any LLVM optimization or code-generation pass to elide the check, because doing so would explicitly introduce the possibility of undefined behavior (in the formal, LLVM sense).
The only way the check can be elided is if the optimizer can prove equivalence, such as inlining the function with known or constant values.
🥳
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wohoo, hell yeah! 🚀
(Thanks for the explanation n.n)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for opening this up @vapdrs! Could you check the comments I left?
:id: rat_h84NjY2tLSBW | ||
:status: draft | ||
|
||
Integer division by zero results in a panic, which is an abnormal program state and may terminate the process. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point -- let's call that out here @vapdrs.
:status: draft | ||
:release: latest | ||
:fls: fls_Q9dhNiICGIfr | ||
:decidability: Undecidable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whether the program is or isn't dividing by zero is indeed Undecidable.
Hmm. Does that make this quite onerous to the engineer which must comply with this?
Perhaps it could be advisory
to allow easy deviation.
Maybe it should always be checked arithmetic.
This might be the right choice for another guideline, perhaps required
which would make every division use checked_div()
.
What do you think @vapdrs?
As stated there is no compliant way to do this, so no example should be present.
While the guideline does not strictly apply to this example, it is a good suggestion for what to do instead.
Closes #131