Skip to content

Commit 2e7be39

Browse files
committed
Internal: Checks: Add missing checks MEM54-CPP and MEM56-CPP #4977
1 parent c650ff7 commit 2e7be39

4 files changed

Lines changed: 983 additions & 0 deletions

File tree

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
import re
2+
3+
4+
ERR1 = 'Provide placement new with properly aligned pointers to sufficient storage capacity'
5+
6+
7+
# The ID for the check
8+
def id():
9+
return ('MEM54-CPP')
10+
11+
12+
# The short name of the check
13+
def name(id):
14+
return {
15+
'MEM54-CPP': 'Published Standards/SEI Cert Standards/C++/Provide placement new with properly aligned pointers to sufficient storage capacity',
16+
}[id]
17+
18+
19+
# The short description of the check
20+
def description():
21+
return 'Provide placement new with properly aligned pointers to sufficient storage capacity'
22+
23+
24+
# The long description of the check
25+
def detailed_description():
26+
return '''
27+
<p><b>Title</b></p>
28+
<p>Provide placement new with properly aligned pointers to sufficient storage capacity</p>
29+
30+
<p><b>Risk Assessment</b></p>
31+
<p>Providing placement new with a pointer to storage that is insufficiently sized or improperly aligned for the type of the object being constructed results in undefined behavior. This can corrupt memory beyond the bounds of the storage or trap on architectures that enforce alignment.</p>
32+
33+
<p><b>Full Standard</b></p>
34+
<p><a href='https://wiki.sei.cmu.edu/confluence/display/cplusplus/MEM54-CPP.+Provide+placement+new+with+properly+aligned+pointers+to+sufficient+storage+capacity'>
35+
https://wiki.sei.cmu.edu/confluence/display/cplusplus/MEM54-CPP.+Provide+placement+new+with+properly+aligned+pointers+to+sufficient+storage+capacity</a></p>
36+
37+
<p><b>Noncompliant Code Example</b></p>
38+
<p>In this noncompliant code example, the storage provided to placement new is smaller than a <code>long</code>:</p>
39+
<pre><code language="C++">
40+
#include &lt;new&gt;
41+
42+
void f() {
43+
short s;
44+
long *lp = ::new (&s) long;
45+
}
46+
</code></pre>
47+
48+
<p>In this noncompliant code example, the storage is large enough but is not properly aligned for a <code>long</code>:</p>
49+
<pre><code language="C++">
50+
#include &lt;new&gt;
51+
52+
void f() {
53+
unsigned char buffer[sizeof(long)];
54+
long *lp = ::new (buffer) long;
55+
}
56+
</code></pre>
57+
58+
<p>In this noncompliant code example, an array of <code>S</code> is constructed in storage sized for exactly <code>N</code> objects, leaving no room for the array allocation overhead added by the implementation:</p>
59+
<pre><code language="C++">
60+
struct S {
61+
S();
62+
~S();
63+
};
64+
65+
void f() {
66+
const unsigned N = 32;
67+
alignas(S) unsigned char buffer[sizeof(S) * N];
68+
S *sp = ::new (buffer) S[N];
69+
}
70+
</code></pre>
71+
72+
<p><b>Compliant Solution</b></p>
73+
<pre><code language="C++">
74+
#include &lt;new&gt;
75+
76+
void f() {
77+
alignas(long) unsigned char buffer[sizeof(long)];
78+
long *lp = ::new (buffer) long;
79+
}
80+
</code></pre>
81+
82+
<p><b>Developer's Notes</b></p>
83+
<p>This check flags placement new expressions whose storage argument resolves to a local declaration that is either not sized with <code>sizeof</code> of the constructed type, not aligned to the constructed type (via <code>alignas</code> or <code>std::aligned_storage</code>), or, for array placement new, sized as exactly <code>sizeof(T) * N</code> with no additional overhead term.
84+
It is a heuristic, textual check: storage declared far from its use, computed through helper functions, or sized with an equivalent but differently-written expression may not be recognized, and a human reviewer should confirm any reported violation as well as any placement new not flagged.</p>
85+
'''
86+
87+
88+
def tags(id):
89+
return {
90+
'MEM54-CPP': [
91+
'Language: C++',
92+
'Standard: SEI CERT C++',
93+
'Severity: 75',
94+
'Likelihood: Likely',
95+
'Detectable: Yes',
96+
'Repairable: Medium',
97+
'Priority: P18',
98+
'Level: L1',
99+
'Memory Allocation',
100+
],
101+
}.get(id)
102+
103+
104+
def test_language(language):
105+
return language == 'C++'
106+
107+
108+
# Tests the type of file
109+
def test_entity(file):
110+
return file.kind().check('code file, header file')
111+
112+
113+
def test_global():
114+
return False
115+
116+
117+
PLACEMENT_RE = re.compile(
118+
r'^(?:::)?\s*new\s*\(((?:[^()]|\([^()]*\))*)\)\s*([\w:]+)\s*(\[[^\]]*\])?\s*$')
119+
120+
121+
def find_storage_ent(file, obj_ref):
122+
# Walk the lexer to the actual placement-argument identifier and read
123+
# its resolved entity directly, rather than re-deriving it from text.
124+
# This is exact even when an identically-named variable exists in a
125+
# different scope, since Understand already resolved this reference.
126+
lex = file.lexer().lexeme(obj_ref.line(), obj_ref.column())
127+
128+
while lex and not (lex.text() == 'new' and lex.token() == 'Keyword'):
129+
lex = lex.next(ignore_whitespace=True, ignore_comments=True)
130+
131+
if not lex:
132+
return None
133+
134+
lex = lex.next(ignore_whitespace=True, ignore_comments=True)
135+
136+
if not lex or lex.text() != '(':
137+
return None
138+
139+
lex = lex.next(ignore_whitespace=True, ignore_comments=True)
140+
141+
if lex and lex.text() == '&':
142+
lex = lex.next(ignore_whitespace=True, ignore_comments=True)
143+
144+
return lex.ent() if lex else None
145+
146+
147+
def resolve_type_ent(lex):
148+
# A type-id used as the constructed type of a `new` expression (or as
149+
# the argument to sizeof/alignas/alignof) resolves to the constructor
150+
# being invoked rather than the class/struct itself; unwrap that.
151+
ent = lex.ent() if lex else None
152+
153+
if ent and ent.kind().check("Function"):
154+
ent = ent.parent()
155+
156+
return ent
157+
158+
159+
def find_target_type_ent(file, obj_ref):
160+
# Mirrors find_storage_ent's walk, but continues past the placement-args
161+
# parens to resolve the constructed type-id to its actual entity. This
162+
# lets later comparisons match on identity rather than spelling, so a
163+
# qualified name (std::string) and an unqualified one reached via a
164+
# using-declaration (string) are recognized as the same type.
165+
lex = file.lexer().lexeme(obj_ref.line(), obj_ref.column())
166+
167+
while lex and not (lex.text() == 'new' and lex.token() == 'Keyword'):
168+
lex = lex.next(ignore_whitespace=True, ignore_comments=True)
169+
170+
if not lex:
171+
return None
172+
173+
lex = lex.next(ignore_whitespace=True, ignore_comments=True)
174+
175+
if not lex or lex.text() != '(':
176+
return None
177+
178+
depth = 1
179+
lex = lex.next(ignore_whitespace=True, ignore_comments=True)
180+
181+
while lex and depth > 0:
182+
if lex.text() == '(':
183+
depth += 1
184+
elif lex.text() == ')':
185+
depth -= 1
186+
187+
if depth == 0:
188+
break
189+
190+
lex = lex.next(ignore_whitespace=True, ignore_comments=True)
191+
192+
if not lex:
193+
return None
194+
195+
lex = lex.next(ignore_whitespace=True, ignore_comments=True)
196+
ent = None
197+
198+
while lex:
199+
ent = resolve_type_ent(lex)
200+
nxt = lex.next(ignore_whitespace=True, ignore_comments=True)
201+
202+
if not (nxt and nxt.text() == '::'):
203+
break
204+
205+
lex = nxt.next(ignore_whitespace=True, ignore_comments=True)
206+
207+
return ent
208+
209+
210+
def statement_lexemes(file, line, col, max_lines=8):
211+
lex = file.lexer().lexeme(line, col)
212+
lexemes = []
213+
depth = 0
214+
215+
while lex:
216+
if lex.line_begin() - line > max_lines:
217+
break
218+
219+
lexemes.append(lex)
220+
text = lex.text()
221+
222+
if text in ('(', '['):
223+
depth += 1
224+
elif text in (')', ']'):
225+
depth -= 1
226+
elif text == ';' and depth <= 0:
227+
break
228+
229+
lex = lex.next(ignore_whitespace=True, ignore_comments=True)
230+
231+
return lexemes
232+
233+
234+
def declaration_lexemes(file, ref):
235+
lex = file.lexer().lexeme(ref.line(), ref.column())
236+
start_line, start_col = ref.line(), ref.column()
237+
lex = lex.previous(ignore_whitespace=True, ignore_comments=True)
238+
239+
while lex and lex.text() not in (';', '{', '}'):
240+
start_line, start_col = lex.line_begin(), lex.column_begin()
241+
lex = lex.previous(ignore_whitespace=True, ignore_comments=True)
242+
243+
return statement_lexemes(file, start_line, start_col)
244+
245+
246+
def call_wraps_type(lexemes, call_names, target_ent, target_name):
247+
# Detect `sizeof(T)` / `alignas(T)` / `alignof(T)` (T anywhere among the
248+
# call's arguments, to also match forms like `aligned_alloc(alignof(T),
249+
# sizeof(T))`). A bare-name text match handles fundamental types (which
250+
# have no entity of their own) and is kept as the primary signal even for
251+
# class types, since Understand can resolve an array-new type-id (`new
252+
# (p) S[n]`) to a placeholder entity distinct from S's real declaration
253+
# when S's constructor is never defined. An entity-id match is only used
254+
# to additionally recognize a differently-spelled/qualified same type
255+
# (e.g. `std::string` vs. an unqualified `string` reached via a
256+
# using-declaration), which the text match alone would miss.
257+
n = len(lexemes)
258+
259+
for i in range(n - 1):
260+
if lexemes[i].text() not in call_names or lexemes[i + 1].text() != '(':
261+
continue
262+
263+
depth = 1
264+
j = i + 2
265+
266+
while j < n and depth > 0:
267+
text = lexemes[j].text()
268+
269+
if text == '(':
270+
depth += 1
271+
elif text == ')':
272+
depth -= 1
273+
274+
if depth == 0:
275+
break
276+
elif text == target_name:
277+
return True
278+
elif target_ent:
279+
candidate = resolve_type_ent(lexemes[j])
280+
281+
if candidate and candidate.id() == target_ent.id():
282+
return True
283+
284+
j += 1
285+
286+
return False
287+
288+
289+
def check(check, file):
290+
for obj_ref in file.filerefs("Define", "Object"):
291+
obj_ent = obj_ref.ent()
292+
init_text = obj_ent.freetext("InitText")
293+
294+
if not init_text:
295+
continue
296+
297+
match = PLACEMENT_RE.match(init_text.strip())
298+
299+
if not match:
300+
continue
301+
302+
target_name = match.group(2).rsplit('::', 1)[-1]
303+
array_suffix = match.group(3)
304+
target_ent = find_target_type_ent(file, obj_ref)
305+
306+
storage_ent = find_storage_ent(file, obj_ref)
307+
308+
if not storage_ent:
309+
continue
310+
311+
def_ref = storage_ent.ref("Definein, Declarein")
312+
313+
if not def_ref:
314+
continue
315+
316+
decl_lexemes = declaration_lexemes(def_ref.file(), def_ref)
317+
decl_text = ' '.join(lex.text() for lex in decl_lexemes)
318+
same_type_storage = bool(re.search(
319+
r'\b' + re.escape(target_name) + r'\b', storage_ent.type() or ''))
320+
321+
has_sufficient_size = same_type_storage or call_wraps_type(
322+
decl_lexemes, ('sizeof',), target_ent, target_name)
323+
is_aligned = same_type_storage or 'aligned_storage' in decl_text or call_wraps_type(
324+
decl_lexemes, ('alignas', 'alignof'), target_ent, target_name)
325+
326+
violation = False
327+
328+
if not has_sufficient_size or not is_aligned:
329+
violation = True
330+
elif array_suffix:
331+
size_expr_match = re.search(r'\[(.*)\]', decl_text)
332+
size_expr = size_expr_match.group(1) if size_expr_match else ''
333+
334+
if '+' not in size_expr and 'aligned_storage' not in decl_text:
335+
violation = True
336+
337+
if violation:
338+
check.violation(obj_ref.ent(), obj_ref.file(),
339+
obj_ref.line(), obj_ref.column(), ERR1)

0 commit comments

Comments
 (0)