Skip to content

Commit efe3b8e

Browse files
Checks: Added new MISRA12:19.1: An object shall not be assigned or copied to an overlapping object [autosync]
1 parent 80383d5 commit efe3b8e

6 files changed

Lines changed: 366 additions & 115 deletions

File tree

CodeCheck/Published Standards/MISRA C++ 2023/MISRA23_8.18.1/check.upy

Lines changed: 177 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,37 @@
11
ERR1 = 'Object is assigned to an overlapping object'
22

3+
MEMCPY_FUNCS = {'memcpy'}
4+
STR_FUNCS = {'strcpy', 'strncpy', 'strcat', 'strncat', 'sprintf', 'snprintf'}
5+
6+
# For each target function: (min, max) inclusive arg indices that are
7+
# "source-like" (a pointer/object whose overlap with the destination is
8+
# forbidden). The destination is always arg 0. max=None means variadic.
9+
# memcpy/strncpy/strncat have a size arg at index 2 that must NOT be scanned
10+
# for matches against the destination, otherwise constructs like
11+
# memcpy(s->buf, src, s->len)
12+
# falsely flag because `s` appears in both the destination and the size.
13+
SRC_ARG_RANGE = {
14+
'memcpy': (1, 1),
15+
'strcpy': (1, 1),
16+
'strncpy': (1, 1),
17+
'strcat': (1, 1),
18+
'strncat': (1, 1),
19+
'sprintf': (2, None),
20+
'snprintf': (3, None),
21+
}
22+
23+
# MISRA C 19.1 wording covers any overlapping copy, so the C aliases default
24+
# to flagging the broader library set. MISRA C++ 8.18.1's amplification only
25+
# names memcpy, so the C++ alias (and CPP_T046 / M0-2-1) defaults to memcpy.
26+
MISRA_C_IDS = {'MISRA12_19.1', 'MISRA23_19.1', 'MISRA25_19.1'}
27+
328

429
# The ID for the check
530
def ids():
6-
return ('M0-2-1', 'CPP_T046', 'MISRA23_8.18.1')
31+
return (
32+
'M0-2-1', 'CPP_T046', 'MISRA23_8.18.1',
33+
'MISRA12_19.1', 'MISRA23_19.1', 'MISRA25_19.1',
34+
)
735

836

937
def name(id):
@@ -14,6 +42,12 @@ Published Standards/AUTOSAR/Assigning Object to an Overlapping Object""",
1442
All Checks/Language Specific/C and C++/Types/Assigning Object to an Overlapping Object""",
1543
'MISRA23_8.18.1': """Published Standards/MISRA C++ 2023/8.18.1 \
1644
An object or subobject must not be copied to an overlapping object""",
45+
'MISRA12_19.1': """Published Standards/MISRA C 2012/19.1 \
46+
An object shall not be assigned or copied to an overlapping object""",
47+
'MISRA23_19.1': """Published Standards/MISRA C 2023/19.1 \
48+
An object shall not be assigned or copied to an overlapping object""",
49+
'MISRA25_19.1': """Published Standards/MISRA C 2025/19.1 \
50+
An object shall not be assigned or copied to an overlapping object""",
1751
}[id]
1852

1953

@@ -38,7 +72,28 @@ def tags(id):
3872
'Standard: MISRA C++ 2023',
3973
'Category: Mandatory',
4074
'Expressions',
41-
]
75+
],
76+
'MISRA12_19.1': [
77+
'Language: C',
78+
'Language: C++',
79+
'Standard: MISRA C 2012',
80+
'Category: Mandatory',
81+
'Structures and Unions',
82+
],
83+
'MISRA23_19.1': [
84+
'Language: C',
85+
'Language: C++',
86+
'Standard: MISRA C 2023',
87+
'Category: Mandatory',
88+
'Structures and Unions',
89+
],
90+
'MISRA25_19.1': [
91+
'Language: C',
92+
'Language: C++',
93+
'Standard: MISRA C 2025',
94+
'Category: Mandatory',
95+
'Structures and Unions',
96+
],
4297
}.get(id)
4398

4499

@@ -147,9 +202,75 @@ void fn ( )
147202
<p><b>See also</b></p>
148203
<p>Rule 9-5-1</p>
149204
""",
205+
'MISRA12_19.1': _misra_c_description(),
206+
'MISRA23_19.1': _misra_c_description(),
207+
'MISRA25_19.1': _misra_c_description(),
150208
}[id]
151209

152210

211+
def _misra_c_description():
212+
return """
213+
<p><b>Rule</b></p>
214+
<p>An object shall not be assigned or copied to an overlapping object.</p>
215+
216+
<p><b>Rationale</b></p>
217+
<p>The behaviour is undefined when two objects are created which have some overlap in memory and
218+
one is assigned or copied to the other.</p>
219+
220+
<p><b>Exception</b></p>
221+
<p>The following are permitted because the behaviour is well-defined:</p>
222+
<ol>
223+
<li>Assignment between two objects that overlap exactly and have compatible types (ignoring
224+
their type qualifiers)</li>
225+
<li>Copying between objects that overlap partially or completely using the Standard Library
226+
function memmove</li>
227+
</ol>
228+
229+
<p><b>Example</b></p>
230+
<p>This example also violates Rule 19.2 because it uses unions.</p>
231+
<pre><code language="C">void fn ( void )
232+
{
233+
union
234+
{
235+
int16_t i;
236+
int32_t j;
237+
} a = { 0 };
238+
239+
a.j = a.i; /* Non-compliant */
240+
}
241+
</code></pre>
242+
<pre><code language="C">#include &lt;string.h&gt;
243+
244+
int16_t a[ 20 ];
245+
246+
void f ( void )
247+
{
248+
memcpy ( &amp;a[ 5 ], &amp;a[ 4 ], 2u * sizeof ( a[ 0 ] ) ); /* Non-compliant */
249+
}
250+
251+
void g ( void )
252+
{
253+
int16_t *p = &amp;a[ 0 ];
254+
int16_t *q = &amp;a[ 0 ];
255+
256+
*p = *q; /* Compliant - exception 1 */
257+
}
258+
</code></pre>
259+
260+
<p><b>Developer's Note:</b><br>
261+
<p>This is a conservative implementation of MISRA's "Undecidable, System" rule. It detects:</p>
262+
<ul>
263+
<li>Assignment between two different members of the same union object.</li>
264+
<li>Calls to <code>memcpy</code> where the same object is referenced as both source and destination (controlled by the <i>Check memcpy calls...</i> option).</li>
265+
<li>Calls to <code>strcpy</code>, <code>strncpy</code>, <code>strcat</code>, <code>strncat</code>, <code>sprintf</code>, and <code>snprintf</code> with the same object as both source and destination (controlled by the <i>Also check strcpy...</i> option, which is on by default for this rule).</li>
266+
</ul>
267+
<p>Calls to <code>memmove</code> are not reported because they are explicitly permitted by the exception. Overlap that requires whole-program pointer-flow analysis (e.g. two pointer parameters that may alias at run time, or partial overlap via pointer arithmetic) is not reported.</p>
268+
269+
<p><b>See also</b></p>
270+
<p>Rule 19.2</p>
271+
"""
272+
273+
153274
# Tests the type of file
154275
def test_entity(file):
155276
return file.kind().check('code file, header file')
@@ -164,9 +285,21 @@ def test_language(language):
164285

165286

166287
def define_options(check):
167-
check.options().checkbox('check_memcpy',
168-
'Check memcpy calls using the same array as both source and destination',
169-
check.id() == 'MISRA23_8.18.1')
288+
check.options().checkbox(
289+
'check_memcpy',
290+
'Check memcpy calls using the same object as both source and '
291+
'destination',
292+
check.id() in (
293+
'MISRA23_8.18.1',
294+
'MISRA12_19.1', 'MISRA23_19.1', 'MISRA25_19.1',
295+
),
296+
)
297+
check.options().checkbox(
298+
'check_str_funcs',
299+
'Also check strcpy, strncpy, strcat, strncat, sprintf, and snprintf '
300+
'calls using the same object as both source and destination',
301+
check.id() in MISRA_C_IDS,
302+
)
170303

171304

172305
def get_ent_type(entity):
@@ -221,19 +354,38 @@ def check(check, file):
221354
check.violation(memb, ass_ref.file(),
222355
ass_ref.line(), ass_ref.column(), ERR1)
223356

224-
if not check_memcpy:
357+
check_str_funcs = check.options().lookup('check_str_funcs')
358+
359+
target_funcs = set()
360+
if check_memcpy:
361+
target_funcs |= MEMCPY_FUNCS
362+
if check_str_funcs:
363+
target_funcs |= STR_FUNCS
364+
365+
if not target_funcs:
225366
return
226367

227368
lexer = file.lexer()
228369

229370
for call_ref in file.filerefs("Call", "Function ~Lambda ~Macro ~Template"):
230-
if call_ref.ent().name() != "memcpy":
371+
fn_name = call_ref.ent().name()
372+
373+
if fn_name not in target_funcs:
231374
continue
232375

376+
min_src, max_src = SRC_ARG_RANGE[fn_name]
377+
233378
lex = lexer.lexeme(call_ref.line(), call_ref.column())
234379
on_param = 0
235380
param = 0
236-
arr_dest = None
381+
dest_ent = None
382+
# When the destination is captured via a struct-field access
383+
# (e.g. `s->buf` or `s.buf`), the root entity (`s`) is shared
384+
# with many unrelated sibling fields. Suppress matching so we
385+
# don't false-positive on `memcpy(s->a, s->b, n)` where `s->a`
386+
# and `s->b` point to distinct memory.
387+
dest_via_member = False
388+
flagged = False
237389

238390
while lex:
239391
if lex.text() == "(":
@@ -245,20 +397,23 @@ def check(check, file):
245397
break
246398
elif on_param == 1 and lex.text() == ",":
247399
param += 1
248-
249-
if param > 0 and not arr_dest:
250-
break
251-
elif on_param == 1 and param == 0 and lex.ent():
252-
utype = str(lex.ent().freetext("UnderlyingType"))
253-
254-
if "std::array" not in utype:
255-
break
256-
257-
arr_dest = lex.ent()
258-
elif on_param == 1 and param == 1 and lex.ent():
259-
if lex.ent().id() == arr_dest.id():
400+
elif on_param == 1 and lex.ent() and lex.ent().kind().check(
401+
"Object, Parameter"):
402+
in_src_range = param >= min_src and \
403+
(max_src is None or param <= max_src)
404+
405+
if param == 0 and not dest_ent:
406+
dest_ent = lex.ent()
407+
nxt = lex.next(True, True)
408+
409+
if nxt and nxt.text() in ('->', '.'):
410+
dest_via_member = True
411+
elif in_src_range and dest_ent and not flagged and \
412+
not dest_via_member and \
413+
lex.ent().id() == dest_ent.id():
260414
check.violation(
261-
arr_dest, file, call_ref.line(), call_ref.column(), ERR1)
262-
break
415+
dest_ent, file, call_ref.line(), call_ref.column(),
416+
ERR1)
417+
flagged = True
263418

264419
lex = lex.next(True, True)

0 commit comments

Comments
 (0)