Skip to content

Commit 0f032f1

Browse files
authored
[LifetimeSafety] Fix duplicate loan generation for ImplicitCastExpr (#153661)
This PR fixes a bug in the lifetime safety analysis where `ImplicitCastExpr` nodes were causing duplicate loan generation. The changes: 1. Remove the recursive `Visit(ICE->getSubExpr())` call in `VisitImplicitCastExpr` to prevent duplicate processing of the same expression 2. Ensure the CFG build options are properly configured for lifetime safety analysis by moving the flag check earlier 3. Enhance the unit test infrastructure to properly handle multiple loans per variable 4. Add a test case that verifies implicit casts to const don't create duplicate loans 5. Add a test case for ternary operators with a FIXME note about origin propagation These changes prevent the analysis from generating duplicate loans when expressions are wrapped in implicit casts, which improves the accuracy of the lifetime safety analysis.
1 parent 47793f9 commit 0f032f1

File tree

4 files changed

+55
-26
lines changed

4 files changed

+55
-26
lines changed

clang/lib/Analysis/LifetimeSafety.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,6 @@ class FactGenerator : public ConstStmtVisitor<FactGenerator> {
445445
void VisitImplicitCastExpr(const ImplicitCastExpr *ICE) {
446446
if (!hasOrigin(ICE->getType()))
447447
return;
448-
Visit(ICE->getSubExpr());
449448
// An ImplicitCastExpr node itself gets an origin, which flows from the
450449
// origin of its sub-expression (after stripping its own parens/casts).
451450
// TODO: Consider if this is actually useful in practice. Alternatively, we

clang/lib/Sema/AnalysisBasedWarnings.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2905,18 +2905,19 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
29052905
AC.getCFGBuildOptions().AddCXXNewAllocator = false;
29062906
AC.getCFGBuildOptions().AddCXXDefaultInitExprInCtors = true;
29072907

2908+
bool EnableLifetimeSafetyAnalysis = S.getLangOpts().EnableLifetimeSafety;
2909+
29082910
// Force that certain expressions appear as CFGElements in the CFG. This
29092911
// is used to speed up various analyses.
29102912
// FIXME: This isn't the right factoring. This is here for initial
29112913
// prototyping, but we need a way for analyses to say what expressions they
29122914
// expect to always be CFGElements and then fill in the BuildOptions
29132915
// appropriately. This is essentially a layering violation.
29142916
if (P.enableCheckUnreachable || P.enableThreadSafetyAnalysis ||
2915-
P.enableConsumedAnalysis) {
2917+
P.enableConsumedAnalysis || EnableLifetimeSafetyAnalysis) {
29162918
// Unreachable code analysis and thread safety require a linearized CFG.
29172919
AC.getCFGBuildOptions().setAllAlwaysAdd();
2918-
}
2919-
else {
2920+
} else {
29202921
AC.getCFGBuildOptions()
29212922
.setAlwaysAdd(Stmt::BinaryOperatorClass)
29222923
.setAlwaysAdd(Stmt::CompoundAssignOperatorClass)
@@ -2927,7 +2928,6 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
29272928
.setAlwaysAdd(Stmt::UnaryOperatorClass);
29282929
}
29292930

2930-
bool EnableLifetimeSafetyAnalysis = S.getLangOpts().EnableLifetimeSafety;
29312931
// Install the logical handler.
29322932
std::optional<LogicalErrorHandler> LEH;
29332933
if (LogicalErrorHandler::hasActiveDiagnostics(Diags, D->getBeginLoc())) {

clang/test/Sema/warn-lifetime-safety-dataflow.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,3 +293,22 @@ void pointer_indirection() {
293293
int *q = *pp;
294294
// CHECK: AssignOrigin (Dest: {{[0-9]+}} (Decl: q), Src: {{[0-9]+}} (Expr: ImplicitCastExpr))
295295
}
296+
297+
// CHECK-LABEL: Function: ternary_operator
298+
// FIXME: Propagate origins across ConditionalOperator.
299+
void ternary_operator() {
300+
int a, b;
301+
int *p;
302+
p = (a > b) ? &a : &b;
303+
// CHECK: Block B2:
304+
// CHECK: Issue (LoanID: [[L_A:[0-9]+]], ToOrigin: [[O_ADDR_A:[0-9]+]] (Expr: UnaryOperator))
305+
// CHECK: End of Block
306+
307+
// CHECK: Block B3:
308+
// CHECK: Issue (LoanID: [[L_B:[0-9]+]], ToOrigin: [[O_ADDR_A:[0-9]+]] (Expr: UnaryOperator))
309+
// CHECK: End of Block
310+
311+
// CHECK: Block B1:
312+
// CHECK: AssignOrigin (Dest: [[O_P:[0-9]+]] (Decl: p), Src: {{[0-9]+}} (Expr: ConditionalOperator))
313+
// CHECK: End of Block
314+
}

clang/unittests/Analysis/LifetimeSafetyTest.cpp

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "clang/ASTMatchers/ASTMatchers.h"
1212
#include "clang/Testing/TestAST.h"
1313
#include "llvm/ADT/StringMap.h"
14+
#include "llvm/Testing/Support/Error.h"
1415
#include "gmock/gmock.h"
1516
#include "gtest/gtest.h"
1617
#include <optional>
@@ -20,6 +21,7 @@ namespace clang::lifetimes::internal {
2021
namespace {
2122

2223
using namespace ast_matchers;
24+
using ::testing::SizeIs;
2325
using ::testing::UnorderedElementsAreArray;
2426

2527
// A helper class to run the full lifetime analysis on a piece of code
@@ -96,21 +98,18 @@ class LifetimeTestHelper {
9698
return OID;
9799
}
98100

99-
std::optional<LoanID> getLoanForVar(llvm::StringRef VarName) {
101+
std::vector<LoanID> getLoansForVar(llvm::StringRef VarName) {
100102
auto *VD = findDecl<VarDecl>(VarName);
101-
if (!VD)
102-
return std::nullopt;
103+
if (!VD) {
104+
ADD_FAILURE() << "No VarDecl found for '" << VarName << "'";
105+
return {};
106+
}
103107
std::vector<LoanID> LID = Analysis.getLoanIDForVar(VD);
104108
if (LID.empty()) {
105109
ADD_FAILURE() << "Loan for '" << VarName << "' not found.";
106-
return std::nullopt;
107-
}
108-
// TODO: Support retrieving more than one loans to a var.
109-
if (LID.size() > 1) {
110-
ADD_FAILURE() << "More than 1 loans found for '" << VarName;
111-
return std::nullopt;
110+
return {};
112111
}
113-
return LID[0];
112+
return LID;
114113
}
115114

116115
std::optional<LoanSet> getLoansAtPoint(OriginID OID,
@@ -121,13 +120,12 @@ class LifetimeTestHelper {
121120
return Analysis.getLoansAtPoint(OID, PP);
122121
}
123122

124-
std::optional<llvm::DenseSet<LoanID>>
123+
std::optional<std::vector<LoanID>>
125124
getExpiredLoansAtPoint(llvm::StringRef Annotation) {
126125
ProgramPoint PP = Runner.getProgramPoint(Annotation);
127126
if (!PP)
128127
return std::nullopt;
129-
auto Expired = Analysis.getExpiredLoansAtPoint(PP);
130-
return llvm::DenseSet<LoanID>{Expired.begin(), Expired.end()};
128+
return Analysis.getExpiredLoansAtPoint(PP);
131129
}
132130

133131
private:
@@ -197,12 +195,13 @@ MATCHER_P2(HasLoansToImpl, LoanVars, Annotation, "") {
197195

198196
std::vector<LoanID> ExpectedLoans;
199197
for (const auto &LoanVar : LoanVars) {
200-
std::optional<LoanID> ExpectedLIDOpt = Info.Helper.getLoanForVar(LoanVar);
201-
if (!ExpectedLIDOpt) {
198+
std::vector<LoanID> ExpectedLIDs = Info.Helper.getLoansForVar(LoanVar);
199+
if (ExpectedLIDs.empty()) {
202200
*result_listener << "could not find loan for var '" << LoanVar << "'";
203201
return false;
204202
}
205-
ExpectedLoans.push_back(*ExpectedLIDOpt);
203+
ExpectedLoans.insert(ExpectedLoans.end(), ExpectedLIDs.begin(),
204+
ExpectedLIDs.end());
206205
}
207206

208207
return ExplainMatchResult(UnorderedElementsAreArray(ExpectedLoans),
@@ -221,17 +220,17 @@ MATCHER_P(AreExpiredAt, Annotation, "") {
221220
<< Annotation << "'";
222221
return false;
223222
}
224-
std::vector<LoanID> ActualExpiredLoans(ActualExpiredSetOpt->begin(),
225-
ActualExpiredSetOpt->end());
223+
std::vector<LoanID> ActualExpiredLoans = *ActualExpiredSetOpt;
226224
std::vector<LoanID> ExpectedExpiredLoans;
227225
for (const auto &VarName : Info.LoanVars) {
228-
auto LoanIDOpt = Helper.getLoanForVar(VarName);
229-
if (!LoanIDOpt) {
226+
auto LoanIDs = Helper.getLoansForVar(VarName);
227+
if (LoanIDs.empty()) {
230228
*result_listener << "could not find a loan for variable '" << VarName
231229
<< "'";
232230
return false;
233231
}
234-
ExpectedExpiredLoans.push_back(*LoanIDOpt);
232+
ExpectedExpiredLoans.insert(ExpectedExpiredLoans.end(), LoanIDs.begin(),
233+
LoanIDs.end());
235234
}
236235
return ExplainMatchResult(UnorderedElementsAreArray(ExpectedExpiredLoans),
237236
ActualExpiredLoans, result_listener);
@@ -730,5 +729,17 @@ TEST_F(LifetimeAnalysisTest, ReassignedPointerThenOriginalExpires) {
730729
EXPECT_THAT(LoansTo({"s1", "s2"}), AreExpiredAt("p_after_s1_expires"));
731730
}
732731

732+
TEST_F(LifetimeAnalysisTest, NoDuplicateLoansForImplicitCastToConst) {
733+
SetupTest(R"(
734+
void target() {
735+
MyObj a;
736+
const MyObj* p = &a;
737+
const MyObj* q = &a;
738+
POINT(at_end);
739+
}
740+
)");
741+
EXPECT_THAT(Helper->getLoansForVar("a"), SizeIs(2));
742+
}
743+
733744
} // anonymous namespace
734745
} // namespace clang::lifetimes::internal

0 commit comments

Comments
 (0)