You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* 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>
Copy file name to clipboardExpand all lines: src/coding-guidelines/expressions.rst
+252-3Lines changed: 252 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -17,14 +17,14 @@ Expressions
17
17
:scope: module
18
18
:tags: readability, reduce-human-error
19
19
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.
22
22
23
23
.. rationale::
24
24
:id: rat_h8LdJQ1MNKu9
25
25
:status: draft
26
26
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.
28
28
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.
29
29
30
30
Raw pointers have a variety of invariants to manually keep track of.
@@ -81,3 +81,252 @@ Expressions
81
81
}
82
82
83
83
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.
0 commit comments