11ERR1 = '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
530def 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
937def name(id):
@@ -14,6 +42,12 @@ Published Standards/AUTOSAR/Assigning Object to an Overlapping Object""",
1442All 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 \
1644An 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 <string.h>
243+
244+ int16_t a[ 20 ];
245+
246+ void f ( void )
247+ {
248+ memcpy ( &a[ 5 ], &a[ 4 ], 2u * sizeof ( a[ 0 ] ) ); /* Non-compliant */
249+ }
250+
251+ void g ( void )
252+ {
253+ int16_t *p = &a[ 0 ];
254+ int16_t *q = &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
154275def test_entity(file):
155276 return file.kind().check('code file, header file')
@@ -164,9 +285,21 @@ def test_language(language):
164285
165286
166287def 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
172305def 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