blob: a8a7bd1a706f40ffc6d8120fc9012548b8ea97b6 [file] [log] [blame]
danakjc06d5f42024-02-29 17:54:111// Copyright 2024 The Chromium Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
danakje7db1e3f32024-04-16 20:43:245#include "Util.h"
danakjc06d5f42024-02-29 17:54:116#include "clang/AST/ASTConsumer.h"
7#include "clang/Basic/DiagnosticSema.h"
8#include "clang/Frontend/CompilerInstance.h"
9#include "clang/Frontend/FrontendAction.h"
10#include "clang/Frontend/FrontendPluginRegistry.h"
danakje7db1e3f32024-04-16 20:43:2411#include "clang/Lex/Pragma.h"
danakjc06d5f42024-02-29 17:54:1112#include "llvm/ADT/StringMap.h"
13#include "llvm/ADT/StringRef.h"
14#include "llvm/Support/MemoryBuffer.h"
15
danakjc06d5f42024-02-29 17:54:1116namespace chrome_checker {
17
Tom Sepez4474d0262025-01-10 17:41:3318enum Disposition {
19 kSkip = 0, // Do not check for any unsafe operations.
20 kSkipLibc, // Check for unsafe buffers but not unsafe libc calls.
21 kCheck, // Check for both unsafe buffers and unsafe libc calls.
22};
23
24// Stores whether the filename (key) should be checked for errors.
25// If the filename is not present, the choice is up to the plugin to
26// determine from the path prefixes control file.
27llvm::StringMap<Disposition> g_checked_files_cache;
danakje7db1e3f32024-04-16 20:43:2428
danakjc06d5f42024-02-29 17:54:1129struct CheckFilePrefixes {
Tom Sepez4157e2d2024-12-16 20:25:3330 // `buffer` owns the memory for the strings in `prefix_map`.
danakjc06d5f42024-02-29 17:54:1131 std::unique_ptr<llvm::MemoryBuffer> buffer;
Tom Sepez4157e2d2024-12-16 20:25:3332 std::map<llvm::StringRef, char> prefix_map;
Tom Sepezb1a81562025-02-20 17:28:5433 bool check_buffers = true;
Tom Sepez639ca5a2024-12-17 18:59:5934 bool check_libc_calls = false;
danakjc06d5f42024-02-29 17:54:1135};
36
37class UnsafeBuffersDiagnosticConsumer : public clang::DiagnosticConsumer {
38 public:
39 UnsafeBuffersDiagnosticConsumer(clang::DiagnosticsEngine* engine,
40 clang::DiagnosticConsumer* next,
41 clang::CompilerInstance* instance,
42 CheckFilePrefixes check_file_prefixes)
43 : engine_(engine),
44 next_(next),
45 instance_(instance),
danakje7db1e3f32024-04-16 20:43:2446 check_file_prefixes_(std::move(check_file_prefixes)),
47 diag_note_link_(engine_->getCustomDiagID(
48 clang::DiagnosticsEngine::Level::Note,
49 "See //docs/unsafe_buffers.md for help.")) {}
danakjc06d5f42024-02-29 17:54:1150 ~UnsafeBuffersDiagnosticConsumer() override = default;
51
52 void clear() override {
53 if (next_) {
54 next_->clear();
55 NumErrors = next_->getNumErrors();
56 NumWarnings = next_->getNumWarnings();
57 }
58 }
59
60 void BeginSourceFile(const clang::LangOptions& opts,
61 const clang::Preprocessor* pp) override {
62 if (next_) {
63 next_->BeginSourceFile(opts, pp);
64 NumErrors = next_->getNumErrors();
65 NumWarnings = next_->getNumWarnings();
66 }
67 }
68
69 void EndSourceFile() override {
70 if (next_) {
71 next_->EndSourceFile();
72 NumErrors = next_->getNumErrors();
73 NumWarnings = next_->getNumWarnings();
74 }
75 }
76
77 void finish() override {
78 if (next_) {
79 next_->finish();
80 NumErrors = next_->getNumErrors();
81 NumWarnings = next_->getNumWarnings();
82 }
83 }
84
85 bool IncludeInDiagnosticCounts() const override {
86 return next_ && next_->IncludeInDiagnosticCounts();
87 }
88
89 void HandleDiagnostic(clang::DiagnosticsEngine::Level level,
90 const clang::Diagnostic& diag) override {
91 const unsigned diag_id = diag.getID();
92
93 if (inside_handle_diagnostic_) {
94 // Avoid handling the diagnostics which we emit in here.
95 return PassthroughDiagnostic(level, diag);
96 }
97
danakja8ecec32024-03-08 19:47:4798 // The `-Runsafe-buffer-usage-in-container` warning gets enabled along with
99 // `-Runsafe-buffer-usage`, but it's a hardcoded warning about std::span
100 // constructor. We don't want to emit these, we instead want the span ctor
101 // (and our own base::span ctor) to be marked [[clang::unsafe_buffer_usage]]
102 // and have that work: https://github.com/llvm/llvm-project/issues/80482
103 if (diag_id == clang::diag::warn_unsafe_buffer_usage_in_container) {
104 return;
105 }
106
danakje7db1e3f32024-04-16 20:43:24107 // Drop the note saying "pass -fsafe-buffer-usage-suggestions to receive
108 // code hardening suggestions" since that's not simple for Chrome devs to
109 // do anyway. We can provide a GN variable in the future and point to that
110 // if needed, or just turn it on always in this plugin, if desired.
111 if (diag_id == clang::diag::note_safe_buffer_usage_suggestions_disabled) {
112 return;
113 }
114
Tom Sepezb1a81562025-02-20 17:28:54115 const bool is_buffers_diagnostic =
116 diag_id == clang::diag::warn_unsafe_buffer_variable ||
117 diag_id == clang::diag::warn_unsafe_buffer_operation ||
118 diag_id == clang::diag::note_unsafe_buffer_operation ||
119 diag_id == clang::diag::note_unsafe_buffer_variable_fixit_group ||
120 diag_id == clang::diag::note_unsafe_buffer_variable_fixit_together ||
121 diag_id == clang::diag::note_safe_buffer_debug_mode;
122
Tom Sepez4474d0262025-01-10 17:41:33123 const bool is_libc_diagnostic =
124 diag_id == clang::diag::warn_unsafe_buffer_libc_call ||
125 diag_id == clang::diag::note_unsafe_buffer_printf_call;
126
Tom Sepezb1a81562025-02-20 17:28:54127 const bool ignore_diagnostic =
128 (is_buffers_diagnostic && !check_file_prefixes_.check_buffers) ||
129 (is_libc_diagnostic && !check_file_prefixes_.check_libc_calls);
130
131 if (ignore_diagnostic) {
132 return;
133 }
134
135 const bool handle_diagnostic =
136 (is_buffers_diagnostic && check_file_prefixes_.check_buffers) ||
137 (is_libc_diagnostic && check_file_prefixes_.check_libc_calls);
138
139 if (!handle_diagnostic) {
danakjc06d5f42024-02-29 17:54:11140 return PassthroughDiagnostic(level, diag);
141 }
142
143 // Note that we promote from Remark directly to Error, rather than to
144 // Warning, as -Werror will not get applied to whatever we choose here.
145 const auto elevated_level =
Tom Sepez4474d0262025-01-10 17:41:33146 (is_libc_diagnostic ||
Tom Sepez639ca5a2024-12-17 18:59:59147 diag_id == clang::diag::warn_unsafe_buffer_variable ||
Tom Sepez4474d0262025-01-10 17:41:33148 diag_id == clang::diag::warn_unsafe_buffer_operation)
danakjc06d5f42024-02-29 17:54:11149 ? (engine_->getWarningsAsErrors()
150 ? clang::DiagnosticsEngine::Level::Error
151 : clang::DiagnosticsEngine::Level::Warning)
152 : clang::DiagnosticsEngine::Level::Note;
153
154 const clang::SourceManager& sm = instance_->getSourceManager();
155 const clang::SourceLocation loc = diag.getLocation();
156
157 // -Wunsage-buffer-usage errors are omitted conditionally based on what file
158 // they are coming from.
Tom Sepez4474d0262025-01-10 17:41:33159 auto disposition = FileHasSafeBuffersWarnings(sm, loc);
160 if (disposition == kSkip ||
161 (is_libc_diagnostic && disposition == kSkipLibc)) {
162 return;
danakjc06d5f42024-02-29 17:54:11163 }
Tom Sepezccf2bd082025-02-21 18:43:10164
165 // More selectively filter the libc calls we enforce.
166 if (is_libc_diagnostic && IsIgnoredLibcFunction(diag)) {
167 return;
168 }
169
Tom Sepez4474d0262025-01-10 17:41:33170 // Elevate the Remark to a Warning, and pass along its Notes without
171 // changing them. Otherwise, do nothing, and the Remark (and its notes)
172 // will not be displayed.
173 //
174 // We don't count warnings/errors in this DiagnosticConsumer, so we don't
175 // call up to the base class here. Instead, whenever we pass through to
176 // the `next_` DiagnosticConsumer, we record its counts.
177 //
178 // Construct the StoredDiagnostic before Clear() or we get bad data from
179 // `diag`.
180 auto stored = clang::StoredDiagnostic(elevated_level, diag);
181 inside_handle_diagnostic_ = true;
182 engine_->Report(stored);
183 if (elevated_level != clang::DiagnosticsEngine::Level::Note) {
184 // For each warning, we inject our own Note as well, pointing to docs.
185 engine_->Report(loc, diag_note_link_);
186 }
187 inside_handle_diagnostic_ = false;
danakjc06d5f42024-02-29 17:54:11188 }
189
190 private:
191 void PassthroughDiagnostic(clang::DiagnosticsEngine::Level level,
192 const clang::Diagnostic& diag) {
193 if (next_) {
194 next_->HandleDiagnostic(level, diag);
195 NumErrors = next_->getNumErrors();
196 NumWarnings = next_->getNumWarnings();
197 }
198 }
199
200 // Depending on where the diagnostic is coming from, we may ignore it or
201 // cause it to generate a warning.
Tom Sepez4474d0262025-01-10 17:41:33202 Disposition FileHasSafeBuffersWarnings(const clang::SourceManager& sm,
203 clang::SourceLocation loc) {
danakj69209bf2024-04-19 14:51:05204 // ClassifySourceLocation() does not report kMacro as the location unless it
205 // happens to be inside a scratch buffer, which not all macro use does. For
206 // the unsafe-buffers warning, we want the SourceLocation where the macro is
207 // expanded to always be the decider about whether to fire a warning or not.
208 //
209 // The reason we do this is that the expansion site should be wrapped in
210 // UNSAFE_BUFFERS() if the unsafety is warranted. It can be done inside the
211 // macro itself too (in which case the warning will not fire), but the
212 // finest control is always at each expansion site.
213 while (loc.isMacroID()) {
214 loc = sm.getExpansionLoc(loc);
215 }
216
danakjc06d5f42024-02-29 17:54:11217 // TODO(crbug.com/40284755): Expand this diagnostic to more code. It should
danakje7db1e3f32024-04-16 20:43:24218 // include everything except kSystem eventually.
219 LocationClassification loc_class =
220 ClassifySourceLocation(instance_->getHeaderSearchOpts(), sm, loc);
danakjc06d5f42024-02-29 17:54:11221 switch (loc_class) {
danakjc06d5f42024-02-29 17:54:11222 case LocationClassification::kSystem:
Tom Sepez4474d0262025-01-10 17:41:33223 return kSkip;
danakjc06d5f42024-02-29 17:54:11224 case LocationClassification::kGenerated:
Tom Sepez4474d0262025-01-10 17:41:33225 return kSkip;
danakje7db1e3f32024-04-16 20:43:24226 case LocationClassification::kThirdParty:
danakjc06d5f42024-02-29 17:54:11227 case LocationClassification::kChromiumThirdParty:
danakjc06d5f42024-02-29 17:54:11228 case LocationClassification::kFirstParty:
danakjc06d5f42024-02-29 17:54:11229 case LocationClassification::kBlink:
danakje7db1e3f32024-04-16 20:43:24230 case LocationClassification::kMacro:
danakj69209bf2024-04-19 14:51:05231 break;
danakjc06d5f42024-02-29 17:54:11232 }
233
danakje7db1e3f32024-04-16 20:43:24234 // We default to everything opting into checks (except categories that early
235 // out above) unless it is removed by the paths control file or by pragma.
236
danakjc06d5f42024-02-29 17:54:11237 // TODO(danakj): It would be an optimization to find a way to avoid creating
238 // a std::string here.
danakj69209bf2024-04-19 14:51:05239 std::string filename = GetFilename(sm, loc, FilenameLocationType::kExactLoc,
240 FilenamesFollowPresumed::kNo);
danakjc06d5f42024-02-29 17:54:11241
242 // Avoid searching `check_file_prefixes_` more than once for a file.
danakje7db1e3f32024-04-16 20:43:24243 auto cache_it = g_checked_files_cache.find(filename);
244 if (cache_it != g_checked_files_cache.end()) {
danakjc06d5f42024-02-29 17:54:11245 return cache_it->second;
246 }
247
danakjc06d5f42024-02-29 17:54:11248 llvm::StringRef cmp_filename = filename;
danakj4af79fa2024-04-23 18:54:35249
250 // If the path is absolute, drop the prefix up to the current working
251 // directory. Some mac machines are passing absolute paths to source files,
252 // but it's the absolute path to the build directory (the current working
253 // directory here) then a relative path from there.
254 llvm::SmallVector<char> cwd;
255 if (llvm::sys::fs::current_path(cwd).value() == 0) {
256 if (cmp_filename.consume_front(llvm::StringRef(cwd.data(), cwd.size()))) {
257 cmp_filename.consume_front("/");
258 }
259 }
260
261 // Drop the ../ prefixes.
danakjc06d5f42024-02-29 17:54:11262 while (cmp_filename.consume_front("./") ||
263 cmp_filename.consume_front("../"))
Tom Sepez4157e2d2024-12-16 20:25:33264 continue;
danakjc06d5f42024-02-29 17:54:11265
Tom Sepez4474d0262025-01-10 17:41:33266 Disposition should_check = kCheck;
Tom Sepez4157e2d2024-12-16 20:25:33267 while (!cmp_filename.empty()) {
268 auto it = check_file_prefixes_.prefix_map.find(cmp_filename);
269 if (it != check_file_prefixes_.prefix_map.end()) {
Tom Sepez4474d0262025-01-10 17:41:33270 should_check = it->second == '+' ? kCheck : kSkip;
Tom Sepez4157e2d2024-12-16 20:25:33271 break;
danakjc06d5f42024-02-29 17:54:11272 }
Tom Sepez4157e2d2024-12-16 20:25:33273 cmp_filename = llvm::sys::path::parent_path(cmp_filename);
danakjc06d5f42024-02-29 17:54:11274 }
Tom Sepez4157e2d2024-12-16 20:25:33275 g_checked_files_cache.insert({filename, should_check});
276 return should_check;
danakjc06d5f42024-02-29 17:54:11277 }
278
Tom Sepezccf2bd082025-02-21 18:43:10279 bool IsIgnoredLibcFunction(const clang::Diagnostic& diag) const {
280 // The unsafe libc calls warning is a wee bit overzealous about
281 // functions which might result in a OOB read only.
282 if (diag.getNumArgs() < 1) {
283 return false;
284 }
285 if (diag.getArgKind(0) !=
286 clang::DiagnosticsEngine::ArgumentKind::ak_nameddecl) {
287 return false;
288 }
289 auto* decl = reinterpret_cast<clang::NamedDecl*>(diag.getRawArg(0));
290 llvm::StringRef name = decl->getName();
Tom Sepez2ad168c2025-03-18 18:27:19291 return name == "strlen" || name == "wcslen" || name == "atoi" ||
292 name == "atof";
Tom Sepezccf2bd082025-02-21 18:43:10293 }
294
danakjc06d5f42024-02-29 17:54:11295 // Used to prevent recursing into HandleDiagnostic() when we're emitting a
296 // diagnostic from that function.
297 bool inside_handle_diagnostic_ = false;
298 clang::DiagnosticsEngine* engine_;
299 clang::DiagnosticConsumer* next_;
300 clang::CompilerInstance* instance_;
301 CheckFilePrefixes check_file_prefixes_;
danakje7db1e3f32024-04-16 20:43:24302 unsigned diag_note_link_;
danakjc06d5f42024-02-29 17:54:11303};
304
305class UnsafeBuffersASTConsumer : public clang::ASTConsumer {
306 public:
307 UnsafeBuffersASTConsumer(clang::CompilerInstance* instance,
308 CheckFilePrefixes check_file_prefixes)
309 : instance_(instance) {
310 // Replace the DiagnosticConsumer with our own that sniffs diagnostics and
311 // can omit them.
312 clang::DiagnosticsEngine& engine = instance_->getDiagnostics();
313 old_client_ = engine.getClient();
314 old_owned_client_ = engine.takeClient();
315 engine.setClient(
316 new UnsafeBuffersDiagnosticConsumer(&engine, old_client_, instance_,
317 std::move(check_file_prefixes)),
318 /*owned=*/true);
319
320 // Enable the -Wunsafe-buffer-usage warning as a remark. This prevents it
321 // from stopping compilation, even with -Werror. If we see the remark go by,
322 // we can re-emit it as a warning for the files we want to include in the
323 // check.
324 engine.setSeverityForGroup(clang::diag::Flavor::WarningOrError,
325 "unsafe-buffer-usage",
326 clang::diag::Severity::Remark);
Arthur Eubanks79d67712024-09-09 21:23:31327
Tom Sepezb1a81562025-02-20 17:28:54328 // Enable the -Wunsafe-buffer-usage-in-libc-call warning as a remark. This
329 // prevents it from stopping compilation, even with -Werror. If we see the
330 // remark go by, we can re-emit it as a warning for the files we want to
331 // include in the check.
Arthur Eubanks79d67712024-09-09 21:23:31332 engine.setSeverityForGroup(clang::diag::Flavor::WarningOrError,
333 "unsafe-buffer-usage-in-libc-call",
Tom Sepezb1a81562025-02-20 17:28:54334 clang::diag::Severity::Remark);
danakjc06d5f42024-02-29 17:54:11335 }
336
337 ~UnsafeBuffersASTConsumer() {
338 // Restore the original DiagnosticConsumer that we replaced with our own.
339 clang::DiagnosticsEngine& engine = instance_->getDiagnostics();
340 if (old_owned_client_) {
341 engine.setClient(old_owned_client_.release(),
342 /*owned=*/true);
343 } else {
344 engine.setClient(old_client_, /*owned=*/false);
345 }
346 }
347
348 private:
349 clang::CompilerInstance* instance_;
350 clang::DiagnosticConsumer* old_client_;
351 std::unique_ptr<clang::DiagnosticConsumer> old_owned_client_;
352};
353
354class UnsafeBuffersASTAction : public clang::PluginASTAction {
355 public:
356 std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(
357 clang::CompilerInstance& instance,
358 llvm::StringRef ref) override {
359 assert(!moved_prefixes_); // This would mean we move the prefixes twice.
360 moved_prefixes_ = true;
361
362 // The ASTConsumer can outlive `this`, so we can't give it references to
363 // members here and must move the `check_file_prefixes_` vector instead.
364 return std::make_unique<UnsafeBuffersASTConsumer>(
365 &instance, std::move(check_file_prefixes_));
366 }
367
368 bool ParseArgs(const clang::CompilerInstance& instance,
369 const std::vector<std::string>& args) override {
370 bool found_file_arg = false;
Tom Sepez639ca5a2024-12-17 18:59:59371 for (const auto& arg : args) {
372 // Nothing should follow the unsafe buffers path positional argument.
danakjc06d5f42024-02-29 17:54:11373 if (found_file_arg) {
374 llvm::errs()
375 << "[unsafe-buffers] Extra argument to unsafe-buffers plugin: '"
Tom Sepez639ca5a2024-12-17 18:59:59376 << arg << ". Usage: [SWITCHES] PATH_TO_CHECK_FILE'\n";
danakjc06d5f42024-02-29 17:54:11377 return false;
danakjc06d5f42024-02-29 17:54:11378 }
Tom Sepezb1a81562025-02-20 17:28:54379
380 // Switches, if any, would go here.
381
Tom Sepez639ca5a2024-12-17 18:59:59382 // Anything not recognized as a switch is the unsafe buffer paths file.
Tom Sepez4157e2d2024-12-16 20:25:33383 found_file_arg = true;
Tom Sepez639ca5a2024-12-17 18:59:59384 if (!LoadCheckFilePrefixes(arg)) {
385 llvm::errs() << "[unsafe-buffers] Failed to load paths from file '"
386 << arg << "'\n";
387 return false;
388 }
danakjc06d5f42024-02-29 17:54:11389 }
390 return true;
391 }
392
393 bool LoadCheckFilePrefixes(std::string_view path) {
394 if (auto buffer = llvm::MemoryBuffer::getFileAsStream(path)) {
395 check_file_prefixes_.buffer = std::move(buffer.get());
396 } else {
397 llvm::errs() << "[unsafe-buffers] Error reading file: '"
398 << buffer.getError().message() << "'\n";
399 return false;
400 }
401
Tom Sepez4157e2d2024-12-16 20:25:33402 // Parse out the paths into `check_file_prefixes_`.
danakjc06d5f42024-02-29 17:54:11403 //
404 // The file format is as follows:
Tom Sepez4157e2d2024-12-16 20:25:33405 // * `#` introduces a comment until the end of the line.
danakjc06d5f42024-02-29 17:54:11406 // * Empty lines are ignored.
Tom Sepezb1a81562025-02-20 17:28:54407 // * A line beginning with a `.` lists diagnostics to enable. These
408 // are comma-separated and currently allow: `buffers`, `libc`.
danakjc06d5f42024-02-29 17:54:11409 // * Every other line is a path prefix from the source tree root using
410 // unix-style delimiters.
danakje7db1e3f32024-04-16 20:43:24411 // * Each line either removes a path from checks or adds a path to checks.
Tom Sepez4157e2d2024-12-16 20:25:33412 // * If the line starts with `+` paths matching the line will be added.
413 // * If the line starts with `-` paths matching the line will removed.
414 // * Other starting characters are not allowed.
415 // * Paths naming directories match the entire sub-directory. For instance
416 // `+a/b/` will match the file at `//a/b/c.h` but will *not* match
417 // `//other/a/b/c.h`.
418 // * Paths naming files match the single file and look like `+a/b/c.h`.
419 // * Trailing slashes for directories are recommended, but not enforced.
420 // * The longest (most specific) match takes precedence.
421 // * Files that do not match any of the prefixes file will be checked.
422 // * Duplicate entries are not allowed and produce compilation errors.
danakjc06d5f42024-02-29 17:54:11423 //
424 // Example:
425 // ```
426 // # A file of path prefixes.
danakje7db1e3f32024-04-16 20:43:24427 // # Matches anything under the directory //foo/bar, opting them into
428 // # checks.
429 // +foo/bar/
430 // # Avoids checks in the //my directory.
431 // -my/
432 // # Matches a specific file at //my/file.cc, overriding the `-my/` above
433 // # for this one file.
434 // +my/file.cc
Tom Sepez4157e2d2024-12-16 20:25:33435 //
danakjc06d5f42024-02-29 17:54:11436 llvm::StringRef string = check_file_prefixes_.buffer->getBuffer();
437 while (!string.empty()) {
Tom Sepez4157e2d2024-12-16 20:25:33438 auto [line, remainder] = string.split('\n');
439 string = remainder;
440 auto [active, comment] = line.split('#');
441 active = active.trim();
442 if (active.empty()) {
443 continue;
danakjc06d5f42024-02-29 17:54:11444 }
Tom Sepez4157e2d2024-12-16 20:25:33445 char symbol = active[0u];
Tom Sepezb1a81562025-02-20 17:28:54446 if (symbol == '.') {
447 // A "dot" line contains directives to enable.
448 if (active.contains("buffers")) {
449 check_file_prefixes_.check_buffers = true;
450 }
451 if (active.contains("libc")) {
452 check_file_prefixes_.check_libc_calls = true;
453 }
454 continue;
455 }
Tom Sepez4157e2d2024-12-16 20:25:33456 if (symbol != '+' && symbol != '-') {
457 llvm::errs() << "[unsafe-buffers] Invalid line in paths file, must "
458 << "start with +/-: '" << line << "'\n";
459 return false;
460 }
461 llvm::StringRef prefix = active.substr(1u).rtrim('/');
462 if (prefix.empty()) {
463 llvm::errs() << "[unsafe-buffers] Invalid line in paths file, path "
464 << "must immediately follow +/-: '" << line << "'\n";
465 return false;
466 }
467 auto [ignore, was_inserted] =
468 check_file_prefixes_.prefix_map.insert({prefix, symbol});
469 if (!was_inserted) {
470 llvm::errs() << "[unsafe-buffers] Duplicate entry in paths file "
471 "for '"
472 << line << "'\n";
473 return false;
danakjc06d5f42024-02-29 17:54:11474 }
475 }
danakjc06d5f42024-02-29 17:54:11476 return true;
477 }
478
479 private:
480 CheckFilePrefixes check_file_prefixes_;
481 bool moved_prefixes_ = false;
482};
483
danakje7db1e3f32024-04-16 20:43:24484class AllowUnsafeBuffersPragmaHandler : public clang::PragmaHandler {
485 public:
486 static constexpr char kName[] = "allow_unsafe_buffers";
487
488 AllowUnsafeBuffersPragmaHandler() : clang::PragmaHandler(kName) {}
489
490 void HandlePragma(clang::Preprocessor& preprocessor,
491 clang::PragmaIntroducer introducer,
492 clang::Token& token) override {
493 // TODO(danakj): It would be an optimization to find a way to avoid creating
494 // a std::string here.
495 std::string filename =
danakj69209bf2024-04-19 14:51:05496 GetFilename(preprocessor.getSourceManager(), introducer.Loc,
497 FilenameLocationType::kExpansionLoc);
danakje7db1e3f32024-04-16 20:43:24498 // The pragma opts the file out of checks.
Tom Sepez4474d0262025-01-10 17:41:33499 g_checked_files_cache.insert({filename, kSkip});
500 }
501};
502
503class AllowUnsafeLibcPragmaHandler : public clang::PragmaHandler {
504 public:
505 static constexpr char kName[] = "allow_unsafe_libc_calls";
506
507 AllowUnsafeLibcPragmaHandler() : clang::PragmaHandler(kName) {}
508
509 void HandlePragma(clang::Preprocessor& preprocessor,
510 clang::PragmaIntroducer introducer,
511 clang::Token& token) override {
512 // TODO(danakj): It would be an optimization to find a way to avoid creating
513 // a std::string here.
514 std::string filename =
515 GetFilename(preprocessor.getSourceManager(), introducer.Loc,
516 FilenameLocationType::kExpansionLoc);
517 // The pragma opts the file into checks.
518 g_checked_files_cache.insert({filename, kSkipLibc});
danakje7db1e3f32024-04-16 20:43:24519 }
520};
521
danakje7db1e3f32024-04-16 20:43:24522static clang::FrontendPluginRegistry::Add<UnsafeBuffersASTAction> X1(
danakjc06d5f42024-02-29 17:54:11523 "unsafe-buffers",
524 "Enforces -Wunsafe-buffer-usage during incremental rollout");
525
danakje7db1e3f32024-04-16 20:43:24526static clang::PragmaHandlerRegistry::Add<AllowUnsafeBuffersPragmaHandler> X2(
527 AllowUnsafeBuffersPragmaHandler::kName,
528 "Avoid reporting unsafe-buffer-usage warnings in the file");
529
Tom Sepez4474d0262025-01-10 17:41:33530static clang::PragmaHandlerRegistry::Add<AllowUnsafeLibcPragmaHandler> X3(
531 AllowUnsafeLibcPragmaHandler::kName,
532 "Avoid reporting unsafe-libc-call warnings in the file");
533
danakjc06d5f42024-02-29 17:54:11534} // namespace chrome_checker