Index: clang/lib/Analysis/CMakeLists.txt =================================================================== --- clang/lib/Analysis/CMakeLists.txt +++ clang/lib/Analysis/CMakeLists.txt @@ -39,6 +39,7 @@ clangASTMatchers clangBasic clangLex + clangToolingInclusions DEPENDS omp_gen Index: clang/lib/Analysis/UnsafeBufferUsage.cpp =================================================================== --- clang/lib/Analysis/UnsafeBufferUsage.cpp +++ clang/lib/Analysis/UnsafeBufferUsage.cpp @@ -22,10 +22,13 @@ #include "clang/Tooling/Inclusions/HeaderIncludes.h" #include "clang/Tooling/Inclusions/IncludeStyle.h" #include "clang/Sema/Lookup.h" +#include "clang/Tooling/Inclusions/HeaderIncludes.h" +#include "clang/Tooling/Inclusions/IncludeStyle.h" #include "llvm/ADT/SmallVector.h" #include #include #include +#include using namespace llvm; using namespace clang; @@ -1588,6 +1591,21 @@ }); } +static std::string getHeaderFilenameForStrategyKind(Strategy::Kind TargetType) { + switch (TargetType) { + case Strategy::Kind::Span: + return "span"; + case Strategy::Kind::Array: + return "array"; + case Strategy::Kind::Vector: + return "vector"; + case Strategy::Kind::Iterator: + return "span"; + case Strategy::Kind::Wontfix: + assert(false && "Wonfix strategy shouldn't be used to generate Fix-Its"); + }; +} + static std::map getFixIts(FixableGadgetSets &FixablesForUnsafeVars, const Strategy &S, /* The function decl under analysis */ const Decl *D, @@ -1619,18 +1637,68 @@ CorrectFixes.end()); } } - if (ImpossibleToFix) - FixItsForVariable.erase(VD); - else - FixItsForVariable[VD].insert(FixItsForVariable[VD].end(), - FixItsForVD.begin(), FixItsForVD.end()); - // We conservatively discard fix-its of a variable if - // a fix-it overlaps with macros; or - // a fix-it conflicts with another one - if (overlapWithMacro(FixItsForVariable[VD]) || - clang::internal::anyConflict(FixItsForVariable[VD], - Ctx.getSourceManager())) { + + if (ImpossibleToFix) { FixItsForVariable.erase(VD); + continue; + } + + FixItsForVariable[VD].insert(FixItsForVariable[VD].end(), + FixItsForVD.begin(), FixItsForVD.end()); + + // Now adding insertions of `#include ...` to fix-its of `VD`. + + FixItList InsertedIncludes = {}; + // Source files where `#include` will be inserted for `VD`. Since all + // fix-its for one `VD` are using the same strategy, these `#include`s + // should be identical. So we do not need two `#include`s in one source + // file. + llvm::SmallSet InsertingFiles; + + for (FixItHint VDFixIt : FixItsForVariable[VD]) { + assert(VDFixIt.RemoveRange.isValid()); + const FileID FileOfVDFixIt = SM.getFileID(VDFixIt.RemoveRange.getBegin()); + + // There already is an `#include` that will be inserted to the source + // file! + if (!InsertingFiles.insert(FileOfVDFixIt).second) + continue; + + const FileEntry *FE = SM.getFileEntryForID(FileOfVDFixIt); + + if (!FE) { + FixItsForVariable.erase(VD); + continue; + } + + std::optional FileContent = + SM.getBufferOrNone(FileOfVDFixIt); + + if (!FileContent) { + FixItsForVariable.erase(VD); + continue; + } + + tooling::IncludeStyle InclStyle; // TODO set IncludeStyle + tooling::HeaderIncludes HeaderIncls(FE->getName(), + FileContent->getBuffer(), InclStyle); + std::optional Include = HeaderIncls.insert( + getHeaderFilenameForStrategyKind(ReplacementTypeForVD), true, + clang::tooling::IncludeDirective::Include); + + if (Include) { + SourceLocation HeaderInsertLoc = + SM.getComposedLoc(FileOfVDFixIt, Include->getOffset()); + FixItsForVariable[VD].push_back(FixItHint::CreateInsertion( + HeaderInsertLoc, Include->getReplacementText())); + } + // Fix-it shall not overlap with macros or/and templates: + if (overlapWithMacro(FixItsForVariable[VD]) || + clang::internal::anyConflict(FixItsForVariable[VD], + Ctx.getSourceManager())) { + FixItsForVariable.erase(VD); + continue; + } } } return FixItsForVariable; Index: clang/test/SemaCXX/warn-unsafe-buffer-usage-fixits-include-header/span =================================================================== --- /dev/null +++ clang/test/SemaCXX/warn-unsafe-buffer-usage-fixits-include-header/span @@ -0,0 +1 @@ +// Dummy std::span header \ No newline at end of file Index: clang/test/SemaCXX/warn-unsafe-buffer-usage-fixits-include-header/warn-unsafe-buffer-usage-fixits-header-exists.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/warn-unsafe-buffer-usage-fixits-include-header/warn-unsafe-buffer-usage-fixits-header-exists.cpp @@ -0,0 +1,13 @@ +// RUN: %clang_cc1 -std=c++20 -Wunsafe-buffer-usage -isystem %S -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s + +// No `#include ` will be emitted if it's already included + +#include +// CHECK-NOT: fix-it:"{{.*}}:"#include \n" + +void unsafe_code(int *p) { + // CHECK: fix-it:"{{.*}}:{[[@LINE-1]]:18-[[@LINE-1]]:24}:"std::span p" + int tmp; + tmp = p[5]; +} +// CHECK: fix-it:"{{.*}}:{[[@LINE-1]]:2-[[@LINE-1]]:2}:"\n#if __has_cpp_attribute(clang::unsafe_buffer_usage)\n{{\[}}{{\[}}clang::unsafe_buffer_usage{{\]}}{{\]}}\n#endif\nvoid unsafe_code(int *p) {return unsafe_code(std::span(p, <# size #>));}\n" Index: clang/test/SemaCXX/warn-unsafe-buffer-usage-fixits-include-header/warn-unsafe-buffer-usage-fixits-include-header-conflict.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/warn-unsafe-buffer-usage-fixits-include-header/warn-unsafe-buffer-usage-fixits-include-header-conflict.cpp @@ -0,0 +1,13 @@ +// RUN: %clang_cc1 -std=c++20 -Wunsafe-buffer-usage -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s + +// For now, insertions of `#include` and an unsafe-buffer attribute +// conflict at the beginning of the source file. So we do not emit +// fix-its for the parameter `p`. + +// CHECK-NOT: fix-it: +void unsafe_code(int *p); + +void unsafe_code(int *p) { + int tmp; + tmp = p[5]; +} Index: clang/test/SemaCXX/warn-unsafe-buffer-usage-fixits-include-header/warn-unsafe-buffer-usage-fixits-include-header-included.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/warn-unsafe-buffer-usage-fixits-include-header/warn-unsafe-buffer-usage-fixits-include-header-included.cpp @@ -0,0 +1,7 @@ +// RUN: +// This file is included in test: warn-unsafe-buffer-usage-fixits-include-header.cpp +void unsafe_code() { + int tmp; + int *p = new int[10]; + tmp = p[5]; +} Index: clang/test/SemaCXX/warn-unsafe-buffer-usage-fixits-include-header/warn-unsafe-buffer-usage-fixits-include-header.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/warn-unsafe-buffer-usage-fixits-include-header/warn-unsafe-buffer-usage-fixits-include-header.cpp @@ -0,0 +1,25 @@ +// RUN: %clang_cc1 -std=c++20 -Wunsafe-buffer-usage -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s + +// Make sure that if we emit Fix-Its for an included file we also add +// the #include directive to that file (and not necessarily the main +// file). +#include "warn-unsafe-buffer-usage-fixits-include-header-included.cpp" +// CHECK: fix-it:"{{.*}}warn-unsafe-buffer-usage-fixits-include-header-included.cpp":{3:1-3:1}:"#include \n" +// CHECK: fix-it:"{{.*}}warn-unsafe-buffer-usage-fixits-include-header.cpp":{[[@LINE-1]]:1-[[@LINE-1]]:1}:"#include \n" +// CHECK: fix-it:"{{.*}}warn-unsafe-buffer-usage-fixits-include-header.cpp":{[[@LINE-2]]:1-[[@LINE-2]]:1}:"#include \n" + + +// Both the fix-its for `p` in `unsafe_code2` and `unsafe_code3` emit an insertion of `#include `: + +void unsafe_code2() { + int tmp; + int *p = new int[10]; + tmp = p[5]; +} + + +void unsafe_code3() { + int tmp; + int *p = new int[10]; + tmp = p[5]; +} Index: clang/test/SemaCXX/warn-unsafe-buffer-usage-fixits-include-header/warn-unsafe-buffer-usage-fixits-parm-in-header.h =================================================================== --- /dev/null +++ clang/test/SemaCXX/warn-unsafe-buffer-usage-fixits-include-header/warn-unsafe-buffer-usage-fixits-parm-in-header.h @@ -0,0 +1,4 @@ +void f(); // some irrelevant decl + +void unsafe_code(int *p); + Index: clang/test/SemaCXX/warn-unsafe-buffer-usage-fixits-include-header/warn-unsafe-buffer-usage-fixits-parm-in-header.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/warn-unsafe-buffer-usage-fixits-include-header/warn-unsafe-buffer-usage-fixits-parm-in-header.cpp @@ -0,0 +1,19 @@ +// RUN: %clang_cc1 -std=c++20 -Wunsafe-buffer-usage -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s + +// Test that if a function parameter in header needs to be fixed, a +// `#include ` is inserted in header as well. + +// "CHECK DAG" is needed here since we are not sure whether fix-its in the header are emitted first. + +#include "warn-unsafe-buffer-usage-fixits-parm-in-header.h" +// CHECK-DAG: fix-it:"{{.*}}warn-unsafe-buffer-usage-fixits-parm-in-header.h":{1:1-1:1}:"#include \n" +// CHECK-DAG: fix-it:"{{.*}}warn-unsafe-buffer-usage-fixits-parm-in-header.h":{3:1-3:1}:"#if __has_cpp_attribute(clang::unsafe_buffer_usage)\n{{\[}}{{\[}}clang::unsafe_buffer_usage{{\]}}{{\]}}\n#endif\n" +// CHECK-DAG: fix-it:"{{.*}}warn-unsafe-buffer-usage-fixits-parm-in-header.h":{3:25-3:25}:";\nvoid unsafe_code(std::span)" +// CHECK-DAG: fix-it:"{{.*}}:{[[@LINE-3]]:1-[[@LINE-3]]:1}:"#include \n" + +void unsafe_code(int *p) { + // CHECK-DAG: fix-it:"{{.*}}:{[[@LINE-1]]:18-[[@LINE-1]]:24}:"std::span p" + int tmp; + tmp = p[5]; +} +// CHECK-DAG: fix-it:"{{.*}}:{[[@LINE-1]]:2-[[@LINE-1]]:2}:"\n#if __has_cpp_attribute(clang::unsafe_buffer_usage)\n{{\[}}{{\[}}clang::unsafe_buffer_usage{{\]}}{{\]}}\n#endif\nvoid unsafe_code(int *p) {return unsafe_code(std::span(p, <# size #>));}\n" Index: clang/test/SemaCXX/warn-unsafe-buffer-usage-fixits-parm-span.cpp =================================================================== --- clang/test/SemaCXX/warn-unsafe-buffer-usage-fixits-parm-span.cpp +++ clang/test/SemaCXX/warn-unsafe-buffer-usage-fixits-parm-span.cpp @@ -5,6 +5,11 @@ #ifndef INCLUDE_ME #define INCLUDE_ME +// FIXME: currently the insertion of `#include` conflicts with +// the insertion of the unsafe-buffer attribute if both of them are +// inserted at the same location. +void f(); + void simple(int *p); // CHECK-DAG: fix-it:{{.*}}:{[[@LINE-1]]:1-[[@LINE-1]]:1}:"#if __has_cpp_attribute(clang::unsafe_buffer_usage)\n{{\[}}{{\[}}clang::unsafe_buffer_usage{{\]}}{{\]}}\n#endif\n" // CHECK-DAG: fix-it:{{.*}}:{[[@LINE-2]]:20-[[@LINE-2]]:20}:";\nvoid simple(std::span)"