blob: f1e92c7eb0cbb358ce198ad71aa8372755abb82f [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_verification/pointer_nullability_diagnosis.h"
#include "nullability_verification/pointer_nullability.h"
#include "nullability_verification/pointer_nullability_matchers.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/Stmt.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Basic/Specifiers.h"
namespace clang {
namespace tidy {
namespace nullability {
using ast_matchers::MatchFinder;
using dataflow::Environment;
namespace {
// Returns true if `Expr` is uninterpreted or known to be nullable.
bool isNullableOrUntracked(const Expr* E, const Environment& Env) {
auto* ActualVal = getPointerValueFromExpr(E, Env);
return !ActualVal || isNullable(*ActualVal, Env);
}
// Returns true if an uninterpreted or nullable `Expr` was assigned to a
// construct with a non-null `DeclaredType`.
bool isIncompatibleAssignment(QualType DeclaredType, const Expr* E,
const Environment& Env, ASTContext& Ctx) {
assert(DeclaredType->isAnyPointerType());
return getNullabilityKind(DeclaredType, Ctx) == NullabilityKind::NonNull &&
isNullableOrUntracked(E, Env);
}
llvm::Optional<const Stmt*> diagnoseDereference(const UnaryOperator* UnaryOp,
const MatchFinder::MatchResult&,
const Environment& Env) {
if (isNullableOrUntracked(UnaryOp->getSubExpr(), Env)) {
return UnaryOp;
}
return llvm::None;
}
llvm::Optional<const Stmt*> diagnoseArrow(
const MemberExpr* MemberExpr, const MatchFinder::MatchResult& Result,
const Environment& Env) {
if (isNullableOrUntracked(MemberExpr->getBase(), Env)) {
return MemberExpr;
}
return llvm::None;
}
bool isIncompatibleArgumentList(ArrayRef<QualType> ParamTypes,
ArrayRef<const Expr*> Args,
const Environment& Env, ASTContext& Ctx) {
assert(ParamTypes.size() == Args.size());
for (unsigned int I = 0; I < Args.size(); ++I) {
auto ParamType = ParamTypes[I].getNonReferenceType();
if (!ParamType->isAnyPointerType()) {
continue;
}
if (isIncompatibleAssignment(ParamType, Args[I], Env, Ctx)) {
return true;
}
}
return false;
}
// TODO(b/233582219): Handle call expressions whose callee is not a decl (e.g.
// a function returned from another function), or when the callee cannot be
// interpreted as a function type (e.g. a pointer to a function pointer).
llvm::Optional<const Stmt*> diagnoseCallExpr(
const CallExpr* CE, const MatchFinder::MatchResult& Result,
const Environment& Env) {
auto* Callee = CE->getCalleeDecl();
if (!Callee) return llvm::None;
auto* CalleeType = Callee->getFunctionType();
if (!CalleeType) return llvm::None;
auto ParamTypes = CalleeType->getAs<FunctionProtoType>()->getParamTypes();
ArrayRef<const Expr*> Args(CE->getArgs(), CE->getNumArgs());
if (isa<CXXOperatorCallExpr>(CE)) {
// The first argument of an operator call expression is the operand which
// does not appear in the list of parameter types.
Args = Args.drop_front();
}
return isIncompatibleArgumentList(ParamTypes, Args, Env, *Result.Context)
? llvm::Optional<const Stmt*>(CE)
: llvm::None;
}
llvm::Optional<const Stmt*> diagnoseReturn(
const ReturnStmt* RS, const MatchFinder::MatchResult& Result,
const Environment& Env) {
auto ReturnType = cast<FunctionDecl>(Env.getDeclCtx())->getReturnType();
assert(ReturnType->isPointerType());
auto* ReturnExpr = RS->getRetValue();
assert(ReturnExpr->getType()->isPointerType());
return isIncompatibleAssignment(ReturnType, ReturnExpr, Env, *Result.Context)
? llvm::Optional<const Stmt*>(RS)
: llvm::None;
}
auto buildDiagnoser() {
return dataflow::MatchSwitchBuilder<const Environment,
llvm::Optional<const Stmt*>>()
// (*)
.CaseOf<UnaryOperator>(isPointerDereference(), diagnoseDereference)
// (->)
.CaseOf<MemberExpr>(isPointerArrow(), diagnoseArrow)
// Check compatibility of parameter assignments
.CaseOf<CallExpr>(isCallExpr(), diagnoseCallExpr)
.CaseOf<ReturnStmt>(isPointerReturn(), diagnoseReturn)
.Build();
}
} // namespace
PointerNullabilityDiagnoser::PointerNullabilityDiagnoser()
: Diagnoser(buildDiagnoser()) {}
} // namespace nullability
} // namespace tidy
} // namespace clang