| PRE30 |
Do not create a universal character name through concatenation |
Does not map to Rust |
Rationale: it's just very specific to C |
feedback |
| DCL38 |
Use the correct syntax when declaring a flexible array member |
Does not map to Rust |
Why not?The rule is about "using the standardized syntax for this feature, instead of relying on Undefined Behavior and a compiler-specific, non-standardized syntax".
Such a suggestion makes no sense in Rust, since there is only one compiler and its syntax is the only one available. Correct syntax can compile; incorrect syntax cannot. |
1, 2, 3, 4 |
| EXP35 |
Do not modify objects with temporary lifetime |
Does not map to Rust |
Rationale: the borrow checker eliminates this problem |
1, 2 |
| EXP43 |
Avoid Undefined Behavior when using restrict-qualified pointers |
Does not map to Rust |
RationaleThe borrow checker gives us correct-by-construction restrict qualifications in codegen. Basically, "restrict" is part of the type system, and type checking (in particular borrow checking) enforces that its rules are always abided by. |
1, 2, 3 |
| ARR32 |
Ensure size arguments for variable length arrays are in a valid range |
Does not map to Rust |
Rationale: VLAs are not a thing in Rust |
agree |
| ARR38 |
Guarantee that library functions do not form invalid pointers |
Does not map to Rust |
RationaleThe rule basically asks the user that, whenever they call a function that receives a pointer to an array and a length, that said length doesn't go outside the bounds of the array (which would form an invalid pointer)
In Safe Rust, this is handled by bounds checks, and in the mapping of ARR30 we're already handling that.
In Unsafe Rust, doing this is to break the Safety Contract of the API we're calling, which is getting its own rule. and is outside the mapping of this ARR38 (this rule) Recently moved out of the applicable bucket |
1, 2, 3 |
| STR32 |
Do not pass a non-null-terminated character sequence to a library function that expects a string |
Does not map to Rust |
Rationale: strings are not null-terminated in Rust |
1, 2, 3, 4 |
| FIO34 |
Distinguish between characters read from a file and EOF or WEOF |
Does not map to Rust |
Why not?See the Read trait. EOF / WEOF cannot be acquired from them. As long as file reading operations are done through the Read trait, this should not be a concern. |
feedback |
| FIO37 |
Do not assume that fgets() or fgetws() returns a nonempty string when successful |
Does not map to Rust |
RationaleThis rule comes from the need to properly handle errors found during the execution of fgets() and fgetws(). Errors in those functions are reported in a way whose proper handling cannot be enforced by the compiler.
Fallible APIs in Rust return values of the enum Result<T, E>, whose error case must be properly handled, in order for the program to compile. This feature completely eliminates the need for this kind of rule in Rust. |
feedback |
| FIO39 |
Do not alternately input and output from a stream without an intervening flush or positioning call |
Does not map to Rust |
RationaleThis is UB in C, due to implementation details of the stream handling APIs. The implementation of those APIs in Rust is different, so the guideline doesn't really map to it.
For more information, see this comment and this issue. |
NA |
| FIO44 |
Only use values for fsetpos() that are returned from fgetpos() |
Does not map to Rust |
Why not?The closest APIs in Rust's std to C's fsetpos and fgetpos are the ones provided by the std::io::Seek trait. Their main differences can be summarized by two things:
- The foundational method of the trait, Seek::seek, seeks by the number of bytes given to it. It doesn't require the "multibyte parser state" that's sometimes encoded in the fpos_t parameter for fsetpos, since the trait has no notion of wide-oriented streams. - There is no Undefined Behavior in Seek's APIs.
Thus, there is no reason to restrict the provenance of the offsets used there. |
|
| ERR33 |
Detect and handle standard library errors |
Does not map to Rust |
Why not?Functions in std that may fail, wrap their result in the Result<T, E> type, which forces the caller to handle the error case whenever it happens. Thus, this rule is already enforced by the compiler. |
feedback |
| MSC37 |
Ensure that control never reaches the end of a non-void function |
Does not map to Rust |
Rationale: this is already enforced by the type system |
|
| INT30 |
Ensure that integer operations do not wrap |
Definitely maps to Rust |
Already implemented; still review mapping/category |
|
| INT33 |
Ensure that division and remainder operations do not result in divide-by-zero errors |
Definitely maps to Rust |
Already implemented; still review mapping/category |
agree |
| ARR30 |
Do not form or use out-of-bounds pointers or array subscripts |
Definitely maps to Rust |
How would it map?In Safe Rust: - Direct addressing will panic if OOB - arr.get(index) will return None if OOB In Unsafe Rust: - arr.get_unchecked(index) is UB if OOB.
So, I'd say out-of-bound indexing should be either prevented or handled properly. If using Unsafe indexing, it should be formally validated. |
agree |
| FIO45 |
Avoid TOCTOU race conditions while accessing files |
Definitely maps to Rust |
Rationale: std::fs explicitly talks about TOCTOU and how to avoid it. |
agree |
| FLP30 |
Do not use floating-point variables as loop counters |
Maybe |
Current consensusThe portability issue isn't a problem in Rust; not for basic arithmetic, at least.
The Zulip thread remained inconclusive. It's a very hard topic, and it's hard to make actionable rules out of this. Feel free to peruse and continue that conversation.
Zulip thread: Are floats ever used as loop counters? |
1, 2, 3 |
| ARR36 |
Do not subtract or compare two pointers that do not refer to the same array |
Maps directly to Unsafe Rust |
How would it map? This maps to a series of guidelines.
Pointers cannot be subtracted or compared in Rust. However, the same idea here applies - pointer arithmetic between pointers must only happen inside the same allocation: std::ptr->allocation
I would say that every Safety rule in the ptr module probably merits its own Guideline.
One rule that must be definitely be in there is: our offset methods must never take us to a point outside of the bounds of the object our pointer is attached to. |
1, 2, 3 |
| MEM30 |
Do not access freed memory |
Maps directly to Unsafe Rust |
How would it map? This is impossible in Safe Rust. It is possible in Unsafe.
There is nuance, however, so I strongly recommend reading the Zulip thread below.
Zulip thread: Can I read something after dropping it? |
agree |
| EXP34 |
Do not dereference null pointers |
Maps directly to Unsafe Rust |
Maps with modifications:1. Zero-sized accesses are Defined Behavior std::ptr safety. 2. Volatile access to the address 0x0 is permitted std::ptr::read_volatile documentation. |
feedback |
Context
Thanks for taking a look at this batch.
This issue is part of the follow-on work from #336, where Félix finished a first pass mapping CERT C rules into Rust-oriented categories.
The goal here is to get a focused review on one slice of the 103 rules without making anyone wade through the whole set at once.
If you notice something that cuts across batches, or you want to leave a more global comment, please put that on #336.
If you want to read the original CERT wording before reviewing a mapping, the rule IDs in the table below link directly to the CMU SEI Confluence page for each rule. You may also find the top-level SEI CERT C Coding Standard useful for broader context.
Reviewer note
Please feel free to leave working notes here as you go.
When you feel done with this batch, please leave a short final summary on #336 and link back to this issue so the parent thread stays the main record of the outcome.
Review goal
For each rule below, please check whether the current proposed bucket still feels right for Rust.
If you disagree, are unsure, or think the rule should be merged with another concern, that is useful feedback too. A short rationale is enough.
Current buckets:
Rules in this batch
Please do not feel boxed in by the current buckets. If one of these looks miscategorized, that is exactly the kind of feedback we want.
Why not?
The rule is about "using the standardized syntax for this feature, instead of relying on Undefined Behavior and a compiler-specific, non-standardized syntax".Such a suggestion makes no sense in Rust, since there is only one compiler and its syntax is the only one available. Correct syntax can compile; incorrect syntax cannot.
Rationale
The borrow checker gives us correct-by-construction restrict qualifications in codegen. Basically, "restrict" is part of the type system, and type checking (in particular borrow checking) enforces that its rules are always abided by.Rationale
The rule basically asks the user that, whenever they call a function that receives a pointer to an array and a length, that said length doesn't go outside the bounds of the array (which would form an invalid pointer)In Safe Rust, this is handled by bounds checks, and in the mapping of ARR30 we're already handling that.
In Unsafe Rust, doing this is to break the Safety Contract of the API we're calling, which is getting its own rule. and is outside the mapping of this ARR38 (this rule)
Recently moved out of the applicable bucket
Why not?
See the Read trait. EOF / WEOF cannot be acquired from them.As long as file reading operations are done through the Read trait, this should not be a concern.
Rationale
This rule comes from the need to properly handle errors found during the execution offgets()andfgetws(). Errors in those functions are reported in a way whose proper handling cannot be enforced by the compiler.Fallible APIs in Rust return values of the enum
Result<T, E>, whose error case must be properly handled, in order for the program to compile. This feature completely eliminates the need for this kind of rule in Rust.Rationale
This is UB in C, due to implementation details of the stream handling APIs. The implementation of those APIs in Rust is different, so the guideline doesn't really map to it.For more information, see this comment and this issue.
Why not?
The closest APIs in Rust'sstdto C'sfsetposandfgetposare the ones provided by thestd::io::Seektrait. Their main differences can be summarized by two things:- The foundational method of the trait,
Seek::seek, seeks by the number of bytes given to it. It doesn't require the "multibyte parser state" that's sometimes encoded in thefpos_tparameter forfsetpos, since the trait has no notion of wide-oriented streams.- There is no Undefined Behavior in
Seek's APIs.Thus, there is no reason to restrict the provenance of the offsets used there.
Why not?
Functions in std that may fail, wrap their result in theResult<T, E>type, which forces the caller to handle the error case whenever it happens. Thus, this rule is already enforced by the compiler.How would it map?
In Safe Rust:- Direct addressing will panic if OOB
- arr.get(index) will return None if OOB
In Unsafe Rust:
- arr.get_unchecked(index) is UB if OOB.
So, I'd say out-of-bound indexing should be either prevented or handled properly.
If using Unsafe indexing, it should be formally validated.
std::fsexplicitly talks about TOCTOU and how to avoid it.Current consensus
The portability issue isn't a problem in Rust; not for basic arithmetic, at least.The Zulip thread remained inconclusive. It's a very hard topic, and it's hard to make actionable rules out of this. Feel free to peruse and continue that conversation.
Zulip thread: Are floats ever used as loop counters?
How would it map?
This maps to a series of guidelines.Pointers cannot be subtracted or compared in Rust. However, the same idea here applies - pointer arithmetic between pointers must only happen inside the same allocation:
std::ptr->allocationI would say that every Safety rule in the ptr module probably merits its own Guideline.
One rule that must be definitely be in there is: our offset methods must never take us to a point outside of the bounds of the object our pointer is attached to.
How would it map?
This is impossible in Safe Rust. It is possible in Unsafe.There is nuance, however, so I strongly recommend reading the Zulip thread below.
Zulip thread: Can I read something after dropping it?
Maps with modifications:
1. Zero-sized accesses are Defined Behaviorstd::ptrsafety.2. Volatile access to the address
0x0is permittedstd::ptr::read_volatiledocumentation.What would help
After opening this issue
Post this comment to request a reviewer from the Producer queue: