blob: ca0fe4458ed22bc29745f5698bee4dcf9b0b9192 [file] [log] [blame]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "nullability/inference/safety_constraint_generator.h"
#include <memory>
#include <optional>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/log/check.h"
#include "nullability/inference/analyze_target_for_test.h"
#include "nullability/pointer_nullability.h"
#include "nullability/pointer_nullability_analysis.h"
#include "nullability/pointer_nullability_lattice.h"
#include "clang/AST/Decl.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Analysis/CFG.h"
#include "clang/Analysis/FlowSensitive/ControlFlowContext.h"
#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Analysis/FlowSensitive/Value.h"
#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
#include "clang/Basic/LLVM.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
namespace clang::tidy::nullability {
namespace {
using ::testing::AllOf;
using ::testing::IsEmpty;
using ::testing::Not;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAre;
// Analyzes the to-be-matched code snippet with PointerNullabilityAnalysis, and
// then matches against the constraints produced by SafetyConstraintGenerator
// for the snippet.
//
// `ConstraintsMatcherProducer` should be a function taking the Environment
// and a vector of PointerValue* representing the Target function's parameters
// and returning a matcher for the generated constraints. This allows for
// matching of the constraints against individual parameter null state
// properties.
MATCHER_P(ProducesSafetyConstraints, ConstraintsMatcherProducer, "") {
bool Success = false;
auto MatcherProducer = ConstraintsMatcherProducer;
analyzeTargetForTest(
arg, [&Success, &MatcherProducer, &result_listener](
const clang::FunctionDecl& Func,
const clang::ast_matchers::MatchFinder::MatchResult& Result) {
QCHECK(Func.hasBody()) << "Matched function has no body.";
QCHECK(Result.Context) << "Missing ASTContext from match result.";
llvm::Expected<clang::dataflow::ControlFlowContext> ControlFlowContext =
clang::dataflow::ControlFlowContext::build(Func);
clang::dataflow::DataflowAnalysisContext AnalysisContext(
std::make_unique<clang::dataflow::WatchedLiteralsSolver>());
clang::dataflow::Environment Environment(AnalysisContext, Func);
PointerNullabilityAnalysis Analysis(*Result.Context);
SafetyConstraintGenerator Generator;
llvm::Expected<std::vector<std::optional<
clang::dataflow::DataflowAnalysisState<PointerNullabilityLattice>>>>
BlockToOutputStateOrError = clang::dataflow::runDataflowAnalysis(
*ControlFlowContext, Analysis, Environment,
[&Generator, &Result](
const clang::CFGElement& Element,
const clang::dataflow::DataflowAnalysisState<
PointerNullabilityLattice>& State) {
Generator.collectConstraints(Element, State, *Result.Context);
});
QCHECK(BlockToOutputStateOrError)
<< "No output state from dataflow analysis.";
// TODO(b/268440048) When we can retrieve the improved atoms
// representing annotations, stop using the Environment to retrieve the
// initial Environment PointerValues, which won't work for local
// variables down the road.
std::vector<clang::dataflow::PointerValue*> ParamDeclPointerValues;
for (const auto* P : Func.parameters()) {
CHECK(P != nullptr);
auto* Val = clang::dyn_cast_or_null<clang::dataflow::PointerValue>(
Environment.getValue(*P));
if (Val) {
ParamDeclPointerValues.push_back(Val);
}
}
Success = ExplainMatchResult(
MatcherProducer(Environment, ParamDeclPointerValues),
Generator.constraints(), result_listener);
});
return Success;
}
TEST(SafetyConstraintGenerator, GeneratesNoConstraintsForEmptyFunctionDefn) {
static constexpr llvm::StringRef Src = R"cc(
void Target() {}
)cc";
EXPECT_THAT(Src, ProducesSafetyConstraints(
[](auto Environment, auto ParamPointerValues) {
return IsEmpty();
}));
}
TEST(SafetyConstraintGenerator, GeneratesNoConstraintsForUnusedParam) {
static constexpr llvm::StringRef Src = R"cc(
void Target(int* p) {}
)cc";
EXPECT_THAT(Src, ProducesSafetyConstraints(
[](auto Environment, auto ParamPointerValues) {
return IsEmpty();
}));
}
TEST(SafetyConstraintGenerator, GeneratesNotIsNullConstraintForDeref) {
static constexpr llvm::StringRef Src = R"cc(
void Target(int* p) { *p; }
)cc";
EXPECT_THAT(Src, ProducesSafetyConstraints([](auto Environment,
auto ParamPointerValues) {
return UnorderedElementsAre(&Environment.makeNot(
getPointerNullState(*ParamPointerValues[0]).second));
}));
}
TEST(SafetyConstraintGenerator,
GeneratesNotIsNullConstraintForImproperlyGuardedDeref) {
static constexpr llvm::StringRef Src = R"cc(
void Target(int* p) {
if (p == nullptr) *p;
}
)cc";
EXPECT_THAT(Src, ProducesSafetyConstraints([](auto Environment,
auto ParamPointerValues) {
return UnorderedElementsAre(&Environment.makeNot(
getPointerNullState(*ParamPointerValues[0]).second));
}));
}
TEST(SafetyConstraintGenerator, GeneratesConstraintsForAllParams) {
static constexpr llvm::StringRef Src = R"cc(
void Target(int* p, int* q, int* r) {
*p;
*q;
*r;
}
)cc";
EXPECT_THAT(Src, ProducesSafetyConstraints([](auto Environment,
auto ParamPointerValues) {
return UnorderedElementsAre(
&Environment.makeNot(
getPointerNullState(*ParamPointerValues[0]).second),
&Environment.makeNot(
getPointerNullState(*ParamPointerValues[1]).second),
&Environment.makeNot(
getPointerNullState(*ParamPointerValues[2]).second));
}));
}
TEST(SafetyConstraintGenerator, DoesntGenerateConstraintForNullCheckedPtr) {
static constexpr llvm::StringRef Src = R"cc(
void Target(int* p) {
if (p) *p;
if (p != nullptr) *p;
}
)cc";
EXPECT_THAT(Src, ProducesSafetyConstraints(
[](auto Environment, auto ParamPointerValues) {
return IsEmpty();
}));
}
TEST(SafetyConstraintGenerator,
ConstrainsParameterIfDereferencedBeforeAssignment) {
static constexpr llvm::StringRef Src = R"cc(
int* getPtr();
void Target(int* p) {
*p;
p = getPtr();
}
)cc";
EXPECT_THAT(Src, ProducesSafetyConstraints([](auto Environment,
auto ParamPointerValues) {
return UnorderedElementsAre(&Environment.makeNot(
getPointerNullState(*ParamPointerValues[0]).second));
}));
}
TEST(SafetyConstraintGenerator,
DoesNotConstrainParameterIfDereferencedAfterAssignment) {
static constexpr llvm::StringRef Src = R"cc(
int* getPtr();
void Target(int* p) {
p = getPtr();
*p;
}
)cc";
EXPECT_THAT(
Src,
ProducesSafetyConstraints([](auto Environment, auto ParamPointerValues) {
return AllOf(SizeIs(1),
// TODO(b/268440048) Figure out how to access and assert
// equality for the constraint that this is.
Not(UnorderedElementsAre(&Environment.makeNot(
getPointerNullState(*ParamPointerValues[0]).second))));
}));
}
} // namespace
} // namespace clang::tidy::nullability