Skip to content

Commit 0c2e50f

Browse files
Defects vs subsets (rustfoundation#127)
* Rework the guideline description to remove the incorrect claim that an explicit Amplification section exists (instead, whatever paragraph immediately opens the guideline is just normative). We can continue to call it that but the subheading is not necessary. * Update src/process/style-guideline.rst Co-authored-by: Pete LeVasseur <plevasseur@gmail.com> * Provide initial examples of MISRA vs CERT style rules * apply reviewer feedback * apply reviewer feedback * apply review comment: annotate core as well as std, and only use single ticks * apply review comment: get rid of formatting error in rule header (by using non-markup for the time being) --------- Co-authored-by: Pete LeVasseur <plevasseur@gmail.com>
1 parent a2582ce commit 0c2e50f

File tree

3 files changed

+298
-15
lines changed

3 files changed

+298
-15
lines changed

src/coding-guidelines/expressions.rst

Lines changed: 252 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@ Expressions
1717
:scope: module
1818
:tags: readability, reduce-human-error
1919

20-
Code must not rely on Rust's type inference when doing explicit pointer casts via ``var as Type`` or ``core::mem::transmute``.
21-
Instead, explicitly specify the complete target type in the ``as`` expression or ``core::mem::transmute`` call expression.
20+
Code must not rely on Rust's type inference when doing explicit pointer casts via ``var as Type`` or :std:`core::mem::transmute`.
21+
Instead, explicitly specify the complete target type in the ``as`` expression or :std:`core::mem::transmute` call expression.
2222

2323
.. rationale::
2424
:id: rat_h8LdJQ1MNKu9
2525
:status: draft
2626

27-
``var as Type`` casts and ``core::mem::transmute``\s between raw pointer types are generally valid and unchecked by the compiler as long the target pointer type is a thin pointer.
27+
``var as Type`` casts and :std:`core::mem::transmute`\s between raw pointer types are generally valid and unchecked by the compiler as long the target pointer type is a thin pointer.
2828
Not specifying the concrete target pointer type allows the compiler to infer it from the surroundings context which may result in the cast accidentally changing due to surrounding type changes resulting in semantically invalid pointer casts.
2929

3030
Raw pointers have a variety of invariants to manually keep track of.
@@ -81,3 +81,252 @@ Expressions
8181
}
8282
8383
fn with_base(_: &Base) { ... }
84+
85+
86+
.. guideline:: The 'as' operator should not be used with numeric operands
87+
:id: gui_ADHABsmK9FXz
88+
:category: advisory
89+
:status: draft
90+
:release: <TODO>
91+
:fls: fls_otaxe9okhdr1
92+
:decidability: decidable
93+
:scope: module
94+
:tags: subset, reduce-human-error
95+
96+
The binary operator ``as`` should not be used with:
97+
98+
* a numeric type, including all supported integer, floating, and machine-dependent arithmetic types; or
99+
* ``bool``; or
100+
* ``char``
101+
102+
as either the right operand or the type of the left operand.
103+
104+
**Exception:** ``as`` may be used with ``usize`` as the right operand and an expression of raw pointer
105+
type as the left operand.
106+
107+
.. rationale::
108+
:id: rat_v56bjjcveLxQ
109+
:status: draft
110+
111+
Although the conversions performed by ``as`` between numeric types are all well-defined, ``as`` coerces
112+
the value to fit in the destination type, which may result in unexpected data loss if the value needs to
113+
be truncated, rounded, or produce a nearest possible non-equal value.
114+
115+
Although some conversions are lossless, others are not symmetrical. Instead of relying on either a defined
116+
lossy behaviour or risking loss of precision, the code can communicate intent by using ``Into`` or ``From``
117+
and ``TryInto`` or ``TryFrom`` to signal which conversions are intended to perfectly preserve the original
118+
value, and which are intended to be fallible. The latter cannot be used from const functions, indicating
119+
that these should avoid using fallible conversions.
120+
121+
A pointer-to-address cast does not lose value, but will be truncated unless the destination type is large
122+
enough to hold the address value. The ``usize`` type is guaranteed to be wide enough for this purpose.
123+
124+
A pointer-to-address cast is not symmetrical because the resulting pointer may not point to a valid object,
125+
may not point to an object of the right type, or may not be properly aligned.
126+
If a conversion in this direction is needed, :std:`std::mem::transmute` will communicate the intent to perform
127+
an unsafe operation.
128+
129+
.. non_compliant_example::
130+
:id: non_compl_ex_hzGUYoMnK59w
131+
:status: draft
132+
133+
``as`` used here can change the value range or lose precision.
134+
Even when it doesn't, nothing enforces the correct behaviour or communicates whether
135+
we intend to allow lossy conversions, or only expect valid conversions.
136+
137+
.. code-block:: rust
138+
139+
fn f1(x: u16, y: i32, z: u64, w: u8) {
140+
let a = w as char; // non-compliant
141+
let b = y as u32; // non-compliant - changes value range, converting negative values
142+
let c = x as i64; // non-compliant - could use .into()
143+
144+
let d = y as f32; // non-compliant - lossy
145+
let e = d as f64; // non-compliant - could use .into()
146+
let f = e as f32; // non-compliant - lossy
147+
148+
let g = e as i64; // non-compliant - lossy despite object size
149+
150+
let p1: * const u32 = &b;
151+
let a1 = p1 as usize; // compliant by exception
152+
let a2 = p1 as u16; // non-compliant - may lose address range
153+
let a3 = p1 as u64; // non-compliant - use usize to indicate intent
154+
155+
let p2 = a1 as * const u32; // non-compliant - prefer transmute
156+
let p3 = a2 as * const u32; // non-compliant (and most likely not in a valid address range)
157+
}
158+
159+
.. compliant_example::
160+
:id: compl_ex_uilHTIOgxD37
161+
:status: draft
162+
163+
Valid conversions that are guaranteed to preserve exact values can be communicated
164+
better with ``into()`` or ``from()``.
165+
Valid conversions that risk losing value, where doing so would be an error, can
166+
communicate this and include an error check, with ``try_into`` or ``try_from``.
167+
Other forms of conversion may find ``transmute`` better communicates their intent.
168+
169+
.. code-block:: rust
170+
171+
fn f2(x: u16, y: i32, z: u64, w: u8) {
172+
let a: char = w.into();
173+
let b: Result <u32, _> = y.try_into(); // produce an error on range clip
174+
let c: i64 = x.into();
175+
176+
let d = f32::from(x); // u16 is within range, u32 is not
177+
let e = f64::from(d);
178+
// let f = f32::from(e); // no From exists
179+
180+
// let g = ... // no From exists
181+
182+
let h: u32 = 0;
183+
let p1: * const u32 = &h;
184+
let a1 = p1 as usize; // (compliant)
185+
186+
unsafe {
187+
let a2: usize = std::mem::transmute(p1); // OK
188+
let a3: u64 = std::mem::transmute(p1); // OK, size is checked
189+
// let a3: u16 = std::mem::transmute(p1); // invalid, different sizes
190+
191+
let p2: * const u32 = std::mem::transmute(a1); // OK
192+
let p3: * const u32 = std::mem::transmute(a1); // OK
193+
}
194+
195+
unsafe {
196+
// does something entirely different,
197+
// reinterpreting the bits of z as the IEEE bit pattern of a double
198+
// precision object, rather than converting the integer value
199+
let f1: f64 = std::mem::transmute(z);
200+
}
201+
}
202+
203+
204+
.. guideline:: An integer shall not be converted to a pointer
205+
:id: gui_PM8Vpf7lZ51U
206+
:category: <TODO>
207+
:status: draft
208+
:release: <TODO>
209+
:fls: fls_59mpteeczzo
210+
:decidability: decidable
211+
:scope: module
212+
:tags: subset, undefined-behavior
213+
214+
The ``as`` operator shall not be used with an expression of numeric type as the left operand,
215+
and any pointer type as the right operand.
216+
217+
:std:`std::mem::transmute` shall not be used with any numeric type (including floating point types)
218+
as the argument to the ``Src`` parameter, and any pointer type as the argument to the ``Dst`` parameter.
219+
220+
.. rationale::
221+
:id: rat_YqhEiWTj9z6L
222+
:status: draft
223+
224+
A pointer created from an arbitrary arithmetic expression may designate an invalid address,
225+
including an address that does not point to a valid object, an address that points to an
226+
object of the wrong type, or an address that is not properly aligned. Use of such a pointer
227+
to access memory will result in undefined behavior.
228+
229+
The ``as`` operator also does not check that the size of the source operand is the same as
230+
the size of a pointer, which may lead to unexpected results if the address computation was
231+
originally performed in a differently-sized address space.
232+
233+
While ``as`` can notionally be used to create a null pointer, the functions
234+
:std:`core::ptr::null` and :std:`core::ptr::null_mut` are the more idiomatic way to do this.
235+
236+
.. non_compliant_example::
237+
:id: non_compl_ex_0ydPk7VENSrA
238+
:status: draft
239+
240+
Any use of ``as`` or ``transmute`` to create a pointer from an arithmetic address value
241+
is non-compliant:
242+
243+
.. code-block:: rust
244+
245+
fn f1(x: u16, y: i32, z: u64, w: usize) {
246+
let p1 = x as * const u32; // not compliant
247+
let p2 = y as * const u32; // not compliant
248+
let p3 = z as * const u32; // not compliant
249+
let p4 = w as * const u32; // not compliant despite being the right size
250+
251+
let f: f64 = 10.0;
252+
// let p5 = f as * const u32; // not valid
253+
254+
unsafe {
255+
// let p5: * const u32 = std::mem::transmute(x); // not valid
256+
// let p6: * const u32 = std::mem::transmute(y); // not valid
257+
258+
let p7: * const u32 = std::mem::transmute(z); // not compliant
259+
let p8: * const u32 = std::mem::transmute(w); // not compliant
260+
261+
let p9: * const u32 = std::mem::transmute(f); // not compliant, and very strange
262+
}
263+
}
264+
265+
.. compliant_example::
266+
:id: compl_ex_oneKuF52yzrx
267+
:status: draft
268+
269+
There is no compliant example of this operation.
270+
271+
272+
.. guideline:: An integer shall not be converted to an invalid pointer
273+
:id: gui_iv9yCMHRgpE0
274+
:category: <TODO>
275+
:status: draft
276+
:release: <TODO>
277+
:fls: fls_9wgldua1u8yt
278+
:decidability: undecidable
279+
:scope: system
280+
:tags: defect, undefined-behavior
281+
282+
An expression of numeric type shall not be converted to a pointer if the resulting pointer
283+
is incorrectly aligned, does not point to an entity of the referenced type, or is an invalid representation.
284+
285+
.. rationale::
286+
:id: rat_OhxKm751axKw
287+
:status: draft
288+
289+
The mapping between pointers and integers must be consistent with the addressing structure of the
290+
execution environment. Issues may arise, for example, on architectures that have a segmented memory model.
291+
292+
.. non_compliant_example::
293+
:id: non_compl_ex_CkytKjRQezfQ
294+
:status: draft
295+
296+
This example makes assumptions about the layout of the address space that do not hold on all platforms.
297+
The manipulated address may have discarded part of the original address space, and the flag may
298+
silently interfere with the address value. On platforms where pointers are 64-bits this may have
299+
particularly unexpected results.
300+
301+
.. code-block:: rust
302+
303+
fn f1(flag: u32, ptr: * const u32) {
304+
/* ... */
305+
let mut rep = ptr as usize;
306+
rep = (rep & 0x7fffff) | ((flag as usize) << 23);
307+
let p2 = rep as * const u32;
308+
}
309+
310+
.. compliant_example::
311+
:id: compl_ex_oBoluiKSvREu
312+
:status: draft
313+
314+
This compliant solution uses a struct to provide storage for both the pointer and the flag value.
315+
This solution is portable to machines of different word sizes, both smaller and larger than 32 bits,
316+
working even when pointers cannot be represented in any integer type.
317+
318+
.. code-block:: rust
319+
320+
struct PtrFlag {
321+
pointer: * const u32,
322+
flag: u32
323+
}
324+
325+
fn f2(flag: u32, ptr: * const u32) {
326+
let ptrflag = PtrFlag {
327+
pointer: ptr,
328+
flag: flag
329+
};
330+
/* ... */
331+
}
332+

src/conf.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@
9999
dict(name="readability", description="Readability-related guideline"),
100100
dict(name="reduce-human-error", description="Reducing human error guideline"),
101101
dict(name="numerics", description="Numerics-related guideline"),
102+
dict(name="undefined-behavior", description="Numerics-related guideline"),
103+
104+
dict(name="defect", description="Guideline associated with the language-subset profile"),
105+
dict(name="subset", description="Guideline associated with the defect-prevention profile"),
102106
]
103107

104108
needs_categories = [

src/process/style-guideline.rst

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,6 @@ We will examine each part:
9292
:scope: module
9393
:tags: numerics
9494

95-
Code must not rely on Rust's implicit integer wrapping behavior that occurs in release builds.
96-
Instead, explicitly handle potential overflows using the standard library's checked,
97-
saturating, or wrapping operations.
98-
9995
``guideline`` Title
10096
-------------------
10197

@@ -308,15 +304,29 @@ what it covers.
308304
Content **SHOULD** aim to be as short and self-contained as possible, while still explaining
309305
the scope of the guideline.
310306

311-
Content **SHOULD NOT** cover the rationale for the guideline, which is done in the ``rationale`` section.
307+
Guideline content consists of an Amplification and any Exceptions, which are normative,
308+
supported by a Rationale and examples, which are not normative.
309+
The justification extended explanation for the guideline **SHOULD** appear in the non-normative
310+
Rationale rather than in the normative content.
312311

313312
Amplification
314313
^^^^^^^^^^^^^
315314

316-
Guideline Content **MAY** contain a section titled *Amplification* followed by text that provides a more
317-
precise description of the guideline title. An amplification is normative; if it conflicts with the
318-
``guideline`` Title, the amplification **MUST** take precedence. This mechanism is convenient as it allows
319-
a complicated concept to be conveyed using a short Title.
315+
The *Amplification* is the block of text that **MAY** appear immediately below the guideline
316+
attribute block, before any other subheadings.
317+
If it is provided, the Amplification is normative; if it conflicts with the ``guideline`` Title,
318+
the Amplification **MUST** take precedence. This mechanism is convenient as it allows a complicated
319+
concept to be conveyed using a short Title and refined by the text below.
320+
321+
Content in the Amplification **SHOULD NOT** cover the rationale for the guideline or any
322+
non-normative explanations, which **SHOULD** be provided in the ``rationale`` and examples sections
323+
where helpful.
324+
325+
::
326+
327+
Code must not rely on Rust's implicit integer wrapping behavior that occurs in release builds.
328+
Instead, explicitly handle potential overflows using the standard library's checked,
329+
saturating, or wrapping operations.
320330

321331
Exception
322332
^^^^^^^^^
@@ -327,9 +337,17 @@ some guidelines to be simplified. It is important to note that an exception is a
327337
a guideline does not apply. Code that complies with a guideline by virtue of an exception does not
328338
require a deviation.
329339

340+
If it is provided, the Exception is normative; if it conflicts with the ``guideline`` Title or the
341+
Amplification, the Exception takes precedence over both. Depending on the individual guideline, it
342+
may be clearer to have an Amplification or Title with an explicit Exception overriding parts of
343+
their description, or it may be clearer to express excepted cases as integrated sentences in the
344+
Amplification. This decision is editorial.
345+
330346
``rationale``
331347
=============
332348

349+
Each Guideline **MUST** provide a *Rationale* for its inclusion and enforcement.
350+
333351
::
334352

335353
.. rationale::
@@ -363,7 +381,13 @@ The ``status`` option of a ``rationale`` **MUST** match the ``status`` of its pa
363381
Rationale Content
364382
-----------------
365383

366-
TODO(pete.levasseur)
384+
The content of the rationale **SHOULD** provide the relevant context for why this guideline is useful.
385+
The Rationale **SHOULD** make reference to any undefined behaviors or known errors associated
386+
with the subject of the guideline.
387+
The Rationale **MAY** make reference to other guidelines or to external documents cited in the
388+
References.
389+
390+
The Rationale **SHOULD** be supported by code examples wherever concise examples are possible.
367391

368392
``non_compliant_example``
369393
=========================
@@ -408,10 +432,10 @@ The ``non_compliant_example`` is neither normative, nor exhaustive. ``guideline`
408432
``non_compliant_example`` Code Explanation
409433
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
410434

411-
The Code Explanation of a `non_compliant_example` **MUST** explain in prose the reason the guideline
435+
The Code Explanation of a ``non_compliant_example`` **MUST** explain in prose the reason the guideline
412436
when not applied results in code which is undesirable.
413437

414-
The Code Explanation of a `non_compliant_example` **MAY** be a simple explanation no longer than
438+
The Code Explanation of a ``non_compliant_example`` **MAY** be a simple explanation no longer than
415439
a sentence.
416440

417441
The Code Explanation of a ``non_compliant_example`` **SHOULD** be no longer than necessary to explain
@@ -435,6 +459,12 @@ than the current guideline.
435459
``compliant_example``
436460
=====================
437461

462+
A compliant example **SHOULD** be omitted when the guideline forbids an action entirely, i.e. there
463+
is no compliant way to achieve the goal of the non-compliant code, rather than giving an irrelevant
464+
example (or encouraging strange workarounds).
465+
When there is a clear and idiomatic compliant way to achieve the goal, a compliant example **SHOULD**
466+
be provided after the corresponding non-compliant example.
467+
438468
::
439469

440470
.. compliant_example::

0 commit comments

Comments
 (0)