Index: clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h =================================================================== --- clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h +++ clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h @@ -42,6 +42,10 @@ // through the handler class. void checkUnsafeBufferUsage(const Decl *D, UnsafeBufferUsageHandler &Handler); +// Tests if any two `FixItHint`s in `FixIts` conflict. Two `FixItHint`s +// conflict if they have overlapping source ranges. +bool anyConflict(const SourceManager &SM, const llvm::SmallVectorImpl &FixIts); + /// The text indicating that the user needs to provide input there: constexpr static const char *const UserFillPlaceHolder = "..."; } // end namespace clang Index: clang/lib/Analysis/UnsafeBufferUsage.cpp =================================================================== --- clang/lib/Analysis/UnsafeBufferUsage.cpp +++ clang/lib/Analysis/UnsafeBufferUsage.cpp @@ -109,7 +109,7 @@ AST_MATCHER_P(Stmt, forEveryDescendant, internal::Matcher, innerMatcher) { const DynTypedMatcher &DTM = static_cast(innerMatcher); - + MatchDescendantVisitor Visitor(&DTM, Finder, Builder, ASTMatchFinder::BK_All); return Visitor.findMatch(DynTypedNode::create(Node)); } @@ -690,6 +690,37 @@ } } +bool clang::anyConflict(const SourceManager &SM, + const llvm::SmallVectorImpl &FixIts) { + // A simple interval overlap detecting algorithm. Sorts all ranges by their + // begin location then find any overlap in one pass. + std::vector All; // a copy of `FixIts` + + for (const FixItHint &H : FixIts) + All.push_back(&H); + std::sort(All.begin(), All.end(), + [&SM](const FixItHint *H1, const FixItHint *H2) { + return SM.isBeforeInTranslationUnit(H1->RemoveRange.getBegin(), + H2->RemoveRange.getBegin()); + }); + + const FixItHint *CurrHint = nullptr; + + for (const FixItHint *Hint : All) { + if (!CurrHint || + SM.isBeforeInTranslationUnit(CurrHint->RemoveRange.getEnd(), + Hint->RemoveRange.getBegin())) { + // Either to initialize `CurrHint` or `CurrHint` does not + // overlap with `Hint`: + CurrHint = Hint; + } else + // In case `Hint` overlaps the `CurrHint`, we found at least one + // conflict: + return true; + } + return false; +} + void clang::checkUnsafeBufferUsage(const Decl *D, UnsafeBufferUsageHandler &Handler) { assert(D && D->getBody()); @@ -777,9 +808,7 @@ Fixes = std::nullopt; break; } - - for (auto &&Fixit: *F) - Fixes->push_back(std::move(Fixit)); + Fixes->insert(Fixes->end(), F->begin(), F->end()); } } @@ -789,7 +818,8 @@ Fixes->insert(Fixes->end(), std::make_move_iterator(VarF.begin()), std::make_move_iterator(VarF.end())); // If we reach this point, the strategy is applicable. - Handler.handleFixableVariable(VD, std::move(*Fixes)); + if (!anyConflict(Ctx.getSourceManager(), *Fixes)) + Handler.handleFixableVariable(VD, std::move(*Fixes)); } else { // The strategy has failed. Emit the warning without the fixit. S.set(VD, Strategy::Kind::Wontfix); Index: clang/unittests/Analysis/CMakeLists.txt =================================================================== --- clang/unittests/Analysis/CMakeLists.txt +++ clang/unittests/Analysis/CMakeLists.txt @@ -9,6 +9,7 @@ CloneDetectionTest.cpp ExprMutationAnalyzerTest.cpp MacroExpansionContextTest.cpp + UnsafeBufferUsageTest.cpp ) clang_target_link_libraries(ClangAnalysisTests Index: clang/unittests/Analysis/UnsafeBufferUsageTest.cpp =================================================================== --- /dev/null +++ clang/unittests/Analysis/UnsafeBufferUsageTest.cpp @@ -0,0 +1,60 @@ +#include "gtest/gtest.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/FileManager.h" +#include "clang/Analysis/Analyses/UnsafeBufferUsage.h" + +using namespace clang; + +namespace { +// The test fixture. +class UnsafeBufferUsageTest : public ::testing::Test { +protected: + UnsafeBufferUsageTest() + : FileMgr(FileMgrOpts), DiagID(new DiagnosticIDs()), + Diags(DiagID, new DiagnosticOptions, new IgnoringDiagConsumer()), + SourceMgr(Diags, FileMgr) {} + + FileSystemOptions FileMgrOpts; + FileManager FileMgr; + IntrusiveRefCntPtr DiagID; + DiagnosticsEngine Diags; + SourceManager SourceMgr; +}; +} // namespace + +TEST_F(UnsafeBufferUsageTest, FixItHintsConflict) { + const FileEntry *DummyFile = FileMgr.getVirtualFile("", 100, 0); + FileID DummyFileID = SourceMgr.getOrCreateFileID(DummyFile, SrcMgr::C_User); + SourceLocation StartLoc = SourceMgr.getLocForStartOfFile(DummyFileID); + +#define MkDummyHint(Begin, End) \ + FixItHint::CreateReplacement(SourceRange(StartLoc.getLocWithOffset((Begin)), \ + StartLoc.getLocWithOffset((End))), \ + "dummy") + + FixItHint H1 = MkDummyHint(0, 5); + FixItHint H2 = MkDummyHint(6, 10); + FixItHint H3 = MkDummyHint(20, 25); + llvm::SmallVector Fixes; + + // Test non-overlapping fix-its: + Fixes = {H1, H2, H3}; + EXPECT_FALSE(anyConflict(SourceMgr, Fixes)); + Fixes = {H3, H2, H1}; // re-order + EXPECT_FALSE(anyConflict(SourceMgr, Fixes)); + + // Test overlapping fix-its: + Fixes = {H1, H2, H3, MkDummyHint(0, 4) /* included in H1 */}; + EXPECT_TRUE(anyConflict(SourceMgr, Fixes)); + + Fixes = {H1, H2, H3, MkDummyHint(10, 15) /* overlaps H2 */}; + EXPECT_TRUE(anyConflict(SourceMgr, Fixes)); + + Fixes = {H1, H2, H3, MkDummyHint(7, 23) /* overlaps H2, H3 */}; + EXPECT_TRUE(anyConflict(SourceMgr, Fixes)); + + Fixes = {H1, H2, H3, MkDummyHint(2, 23) /* overlaps H1, H2, and H3 */}; + EXPECT_TRUE(anyConflict(SourceMgr, Fixes)); +} \ No newline at end of file