|
| 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 <new> |
| 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 <new> |
| 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 <new> |
| 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