Files
llvm/clang/unittests/Analysis/FlowSensitive/LoggerTest.cpp
Utkarsh Saxena 4822f4986f [LifetimeSafety] Add parameter lifetime tracking in CFG (#169320)
This PR enhances the CFG builder to properly handle function parameters
in lifetime analysis:

1. Added code to include parameters in the initial scope during CFG
construction for both `FunctionDecl` and `BlockDecl` types
2. Added a special case to skip reference parameters, as they don't need
automatic destruction
3. Fixed several test cases that were previously marked as "FIXME" due
to missing parameter lifetime tracking

Previously, Clang's lifetime analysis was not properly tracking the
lifetime of function parameters, causing it to miss important
use-after-return bugs when parameter values were returned by reference
or address. This change ensures that parameters are properly tracked in
the CFG, allowing the analyzer to correctly identify when stack memory
associated with parameters is returned.

Fixes https://github.com/llvm/llvm-project/issues/169014
2025-11-25 18:06:42 +00:00

196 lines
6.0 KiB
C++

#include "TestingSupport.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"
namespace clang::dataflow::test {
namespace {
using testing::HasSubstr;
struct TestLattice {
int Elements = 0;
int Branches = 0;
int Joins = 0;
LatticeJoinEffect join(const TestLattice &Other) {
if (Joins < 3) {
++Joins;
Elements += Other.Elements;
Branches += Other.Branches;
return LatticeJoinEffect::Changed;
}
return LatticeJoinEffect::Unchanged;
}
friend bool operator==(const TestLattice &LHS, const TestLattice &RHS) {
return std::tie(LHS.Elements, LHS.Branches, LHS.Joins) ==
std::tie(RHS.Elements, RHS.Branches, RHS.Joins);
}
};
class TestAnalysis : public DataflowAnalysis<TestAnalysis, TestLattice> {
public:
using DataflowAnalysis::DataflowAnalysis;
static TestLattice initialElement() { return TestLattice{}; }
void transfer(const CFGElement &, TestLattice &L, Environment &E) {
E.getDataflowAnalysisContext().getOptions().Log->log(
[](llvm::raw_ostream &OS) { OS << "transfer()"; });
++L.Elements;
}
void transferBranch(bool Branch, const Stmt *S, TestLattice &L,
Environment &E) {
E.getDataflowAnalysisContext().getOptions().Log->log(
[&](llvm::raw_ostream &OS) {
OS << "transferBranch(" << Branch << ")";
});
++L.Branches;
}
};
class TestLogger : public Logger {
public:
TestLogger(std::string &S) : OS(S) {}
private:
llvm::raw_string_ostream OS;
void beginAnalysis(const AdornedCFG &,
TypeErasedDataflowAnalysis &) override {
logText("beginAnalysis()");
}
void endAnalysis() override { logText("\nendAnalysis()"); }
void enterBlock(const CFGBlock &B, bool PostVisit) override {
OS << "\nenterBlock(" << B.BlockID << ", " << (PostVisit ? "true" : "false")
<< ")\n";
}
void enterElement(const CFGElement &E) override {
// we don't want the trailing \n
std::string S;
llvm::raw_string_ostream SS(S);
E.dumpToStream(SS);
OS << "enterElement(" << llvm::StringRef(S).trim() << ")\n";
}
void recordState(TypeErasedDataflowAnalysisState &S) override {
const TestLattice &L = llvm::any_cast<TestLattice>(S.Lattice.Value);
OS << "recordState(Elements=" << L.Elements << ", Branches=" << L.Branches
<< ", Joins=" << L.Joins << ")\n";
}
/// Records that the analysis state for the current block is now final.
void blockConverged() override { logText("blockConverged()"); }
void logText(llvm::StringRef Text) override { OS << Text << "\n"; }
};
AnalysisInputs<TestAnalysis> makeInputs() {
const char *Code = R"cpp(
int target(bool b, int p, int q) {
return b ? p : q;
}
)cpp";
static const std::vector<std::string> Args = {
"-fsyntax-only", "-fno-delayed-template-parsing", "-std=c++17"};
auto Inputs = AnalysisInputs<TestAnalysis>(
Code, ast_matchers::hasName("target"),
[](ASTContext &C, Environment &) { return TestAnalysis(C); });
Inputs.ASTBuildArgs = Args;
return Inputs;
}
TEST(LoggerTest, Sequence) {
auto Inputs = makeInputs();
std::string Log;
TestLogger Logger(Log);
Inputs.BuiltinOptions.Log = &Logger;
ASSERT_THAT_ERROR(checkDataflow<TestAnalysis>(std::move(Inputs),
[](const AnalysisOutputs &) {}),
llvm::Succeeded());
EXPECT_EQ(Log, R"(beginAnalysis()
enterBlock(4, false)
recordState(Elements=0, Branches=0, Joins=0)
enterElement(b)
transfer()
recordState(Elements=1, Branches=0, Joins=0)
enterElement(b (ImplicitCastExpr, LValueToRValue, _Bool))
transfer()
recordState(Elements=2, Branches=0, Joins=0)
recordState(Elements=2, Branches=0, Joins=0)
enterBlock(2, false)
transferBranch(1)
recordState(Elements=2, Branches=1, Joins=0)
enterElement(p)
transfer()
recordState(Elements=3, Branches=1, Joins=0)
enterBlock(3, false)
transferBranch(0)
recordState(Elements=2, Branches=1, Joins=0)
enterElement(q)
transfer()
recordState(Elements=3, Branches=1, Joins=0)
enterBlock(1, false)
recordState(Elements=6, Branches=2, Joins=1)
enterElement(b ? p : q)
transfer()
recordState(Elements=7, Branches=2, Joins=1)
enterElement(b ? p : q (ImplicitCastExpr, LValueToRValue, int))
transfer()
recordState(Elements=8, Branches=2, Joins=1)
enterElement(return b ? p : q;)
transfer()
recordState(Elements=9, Branches=2, Joins=1)
enterElement([Parm: q] (Lifetime ends))
transfer()
recordState(Elements=10, Branches=2, Joins=1)
enterElement([Parm: p] (Lifetime ends))
transfer()
recordState(Elements=11, Branches=2, Joins=1)
enterElement([Parm: b] (Lifetime ends))
transfer()
recordState(Elements=12, Branches=2, Joins=1)
enterBlock(0, false)
recordState(Elements=12, Branches=2, Joins=1)
endAnalysis()
)");
}
TEST(LoggerTest, HTML) {
auto Inputs = makeInputs();
std::vector<std::string> Logs;
auto Logger = Logger::html([&]() {
Logs.emplace_back();
return std::make_unique<llvm::raw_string_ostream>(Logs.back());
});
Inputs.BuiltinOptions.Log = Logger.get();
ASSERT_THAT_ERROR(checkDataflow<TestAnalysis>(std::move(Inputs),
[](const AnalysisOutputs &) {}),
llvm::Succeeded());
// Simple smoke tests: we can't meaningfully test the behavior.
ASSERT_THAT(Logs, testing::SizeIs(1));
EXPECT_THAT(Logs[0], HasSubstr("function updateSelection")) << "embeds JS";
EXPECT_THAT(Logs[0], HasSubstr("html {")) << "embeds CSS";
EXPECT_THAT(Logs[0], HasSubstr("b (ImplicitCastExpr")) << "has CFG elements";
EXPECT_THAT(Logs[0], HasSubstr("\"B3:1_B3.1\":"))
<< "has analysis point state";
EXPECT_THAT(Logs[0], HasSubstr("transferBranch(0)")) << "has analysis logs";
EXPECT_THAT(Logs[0], HasSubstr("LocToVal")) << "has built-in lattice dump";
EXPECT_THAT(Logs[0], HasSubstr("\"type\": \"int\"")) << "has value dump";
}
} // namespace
} // namespace clang::dataflow::test