Skip to content

Commit 692da62

Browse files
committed
[-Wunsafe-buffer-usage] Filter out conflicting fix-its
Two fix-its conflict if they have overlapping source ranges. We shall not emit conflicting fix-its. This patch checks conflicts in fix-its generated for one variable (including variable declaration fix-its and variable usage fix-its). If there is any, we do NOT emit any fix-it for that variable. Reviewed by: NoQ Differential revision: https://reviews.llvm.org/D141338
1 parent b1d8f40 commit 692da62

File tree

4 files changed

+104
-2
lines changed

4 files changed

+104
-2
lines changed

clang/include/clang/Analysis/Analyses/UnsafeBufferUsage.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ class UnsafeBufferUsageHandler {
5252
// through the handler class.
5353
void checkUnsafeBufferUsage(const Decl *D, UnsafeBufferUsageHandler &Handler);
5454

55+
namespace internal {
56+
// Tests if any two `FixItHint`s in `FixIts` conflict. Two `FixItHint`s
57+
// conflict if they have overlapping source ranges.
58+
bool anyConflict(const llvm::SmallVectorImpl<FixItHint> &FixIts,
59+
const SourceManager &SM);
60+
} // namespace internal
5561
} // end namespace clang
5662

5763
#endif /* LLVM_CLANG_ANALYSIS_ANALYSES_UNSAFEBUFFERUSAGE_H */

clang/lib/Analysis/UnsafeBufferUsage.cpp

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,37 @@ groupFixablesByVar(FixableGadgetList &&AllFixableOperations) {
694694
return FixablesForUnsafeVars;
695695
}
696696

697+
bool clang::internal::anyConflict(
698+
const SmallVectorImpl<FixItHint> &FixIts, const SourceManager &SM) {
699+
// A simple interval overlap detection algorithm. Sorts all ranges by their
700+
// begin location then finds the first overlap in one pass.
701+
std::vector<const FixItHint *> All; // a copy of `FixIts`
702+
703+
for (const FixItHint &H : FixIts)
704+
All.push_back(&H);
705+
std::sort(All.begin(), All.end(),
706+
[&SM](const FixItHint *H1, const FixItHint *H2) {
707+
return SM.isBeforeInTranslationUnit(H1->RemoveRange.getBegin(),
708+
H2->RemoveRange.getBegin());
709+
});
710+
711+
const FixItHint *CurrHint = nullptr;
712+
713+
for (const FixItHint *Hint : All) {
714+
if (!CurrHint ||
715+
SM.isBeforeInTranslationUnit(CurrHint->RemoveRange.getEnd(),
716+
Hint->RemoveRange.getBegin())) {
717+
// Either to initialize `CurrHint` or `CurrHint` does not
718+
// overlap with `Hint`:
719+
CurrHint = Hint;
720+
} else
721+
// In case `Hint` overlaps the `CurrHint`, we found at least one
722+
// conflict:
723+
return true;
724+
}
725+
return false;
726+
}
727+
697728
std::optional<FixItList>
698729
ULCArraySubscriptGadget::getFixits(const Strategy &S) const {
699730
if (const auto *DRE = dyn_cast<DeclRefExpr>(Node->getBase()->IgnoreImpCasts()))
@@ -948,8 +979,12 @@ getFixIts(FixableGadgetSets &FixablesForUnsafeVars, const Strategy &S,
948979
else
949980
FixItsForVariable[VD].insert(FixItsForVariable[VD].end(),
950981
FixItsForVD.begin(), FixItsForVD.end());
951-
// Fix-it shall not overlap with macros or/and templates:
952-
if (overlapWithMacro(FixItsForVariable[VD])) {
982+
// We conservatively discard fix-its of a variable if
983+
// a fix-it overlaps with macros; or
984+
// a fix-it conflicts with another one
985+
if (overlapWithMacro(FixItsForVariable[VD]) ||
986+
clang::internal::anyConflict(FixItsForVariable[VD],
987+
Ctx.getSourceManager())) {
953988
FixItsForVariable.erase(VD);
954989
}
955990
}

clang/unittests/Analysis/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ add_clang_unittest(ClangAnalysisTests
99
CloneDetectionTest.cpp
1010
ExprMutationAnalyzerTest.cpp
1111
MacroExpansionContextTest.cpp
12+
UnsafeBufferUsageTest.cpp
1213
)
1314

1415
clang_target_link_libraries(ClangAnalysisTests
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#include "gtest/gtest.h"
2+
#include "clang/Basic/SourceLocation.h"
3+
#include "clang/Basic/SourceManager.h"
4+
#include "clang/Basic/Diagnostic.h"
5+
#include "clang/Basic/FileManager.h"
6+
#include "clang/Analysis/Analyses/UnsafeBufferUsage.h"
7+
8+
using namespace clang;
9+
10+
namespace {
11+
// The test fixture.
12+
class UnsafeBufferUsageTest : public ::testing::Test {
13+
protected:
14+
UnsafeBufferUsageTest()
15+
: FileMgr(FileMgrOpts), DiagID(new DiagnosticIDs()),
16+
Diags(DiagID, new DiagnosticOptions, new IgnoringDiagConsumer()),
17+
SourceMgr(Diags, FileMgr) {}
18+
19+
FileSystemOptions FileMgrOpts;
20+
FileManager FileMgr;
21+
IntrusiveRefCntPtr<DiagnosticIDs> DiagID;
22+
DiagnosticsEngine Diags;
23+
SourceManager SourceMgr;
24+
};
25+
} // namespace
26+
27+
TEST_F(UnsafeBufferUsageTest, FixItHintsConflict) {
28+
const FileEntry *DummyFile = FileMgr.getVirtualFile("<virtual>", 100, 0);
29+
FileID DummyFileID = SourceMgr.getOrCreateFileID(DummyFile, SrcMgr::C_User);
30+
SourceLocation StartLoc = SourceMgr.getLocForStartOfFile(DummyFileID);
31+
32+
#define MkDummyHint(Begin, End) \
33+
FixItHint::CreateReplacement(SourceRange(StartLoc.getLocWithOffset((Begin)), \
34+
StartLoc.getLocWithOffset((End))), \
35+
"dummy")
36+
37+
FixItHint H1 = MkDummyHint(0, 5);
38+
FixItHint H2 = MkDummyHint(6, 10);
39+
FixItHint H3 = MkDummyHint(20, 25);
40+
llvm::SmallVector<FixItHint> Fixes;
41+
42+
// Test non-overlapping fix-its:
43+
Fixes = {H1, H2, H3};
44+
EXPECT_FALSE(internal::anyConflict(Fixes, SourceMgr));
45+
Fixes = {H3, H2, H1}; // re-order
46+
EXPECT_FALSE(internal::anyConflict(Fixes, SourceMgr));
47+
48+
// Test overlapping fix-its:
49+
Fixes = {H1, H2, H3, MkDummyHint(0, 4) /* included in H1 */};
50+
EXPECT_TRUE(internal::anyConflict(Fixes, SourceMgr));
51+
52+
Fixes = {H1, H2, H3, MkDummyHint(10, 15) /* overlaps H2 */};
53+
EXPECT_TRUE(internal::anyConflict(Fixes, SourceMgr));
54+
55+
Fixes = {H1, H2, H3, MkDummyHint(7, 23) /* overlaps H2, H3 */};
56+
EXPECT_TRUE(internal::anyConflict(Fixes, SourceMgr));
57+
58+
Fixes = {H1, H2, H3, MkDummyHint(2, 23) /* overlaps H1, H2, and H3 */};
59+
EXPECT_TRUE(internal::anyConflict(Fixes, SourceMgr));
60+
}

0 commit comments

Comments
 (0)