blob: da05e5956518386299b92e0b55c11e6522daceca [file] [log] [blame] [edit]
// 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/inferable.h"
#include <cassert>
#include "nullability/type_nullability.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclTemplate.h" // IWYU pragma: keep, to work around forward decl usage in clang
#include "clang/AST/Type.h"
#include "clang/AST/TypeVisitor.h"
#include "clang/Basic/LLVM.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/Support/raw_ostream.h"
namespace clang::tidy::nullability {
static bool isSupportedPointerTypeOutsideOfSubstitutedTemplateParam(
QualType T) {
class Walker : public TypeVisitor<Walker, bool> {
void addTemplateDeclsSeenInQualifiers(const Type* T) {
for (NestedNameSpecifier NNS = T->getPrefix(); NNS;) {
if (NNS.getKind() == NestedNameSpecifier::Kind::Type) {
if (const auto* TST = dyn_cast_or_null<TemplateSpecializationType>(
NNS.getAsType())) {
TemplateDeclsSeen.insert(
TST->getTemplateName().getAsTemplateDecl());
}
}
switch (NNS.getKind()) {
case NestedNameSpecifier::Kind::Null:
case NestedNameSpecifier::Kind::Global:
case NestedNameSpecifier::Kind::MicrosoftSuper:
NNS = std::nullopt;
break;
case NestedNameSpecifier::Kind::Namespace:
NNS = NNS.getAsNamespaceAndPrefix().Prefix;
break;
case NestedNameSpecifier::Kind::Type:
NNS = NNS.getAsType()->getPrefix();
break;
default:
NNS = std::nullopt;
llvm_unreachable("unexpected NestedNameSpecifier kind");
break;
}
}
}
public:
bool VisitType(const Type* T) {
addTemplateDeclsSeenInQualifiers(T);
// Walk through sugar other than the types handled specifically below.
if (const Type* Next =
T->getLocallyUnqualifiedSingleStepDesugaredType().getTypePtr();
Next != T) {
return Visit(Next);
}
// But if there's no more sugar, we're done and this is not a supported
// pointer type. We don't generally expect this to happen if
// `isSupportedPointerType` already returned true for the starting type.
llvm::errs()
<< "If `isSupportedPointerType` returned true for the starting type, "
"we should have seen a pointer type and never reached this point. "
"It is a waste to use this walker if the starting type is not a "
"supported pointer type.\n";
assert(false);
return false;
}
bool VisitPointerType(const PointerType* T) { return true; }
bool VisitRecordType(const RecordType* T) {
addTemplateDeclsSeenInQualifiers(T);
bool IsSupported = isSupportedSmartPointerType(QualType(T, 0));
if (!IsSupported) {
llvm::errs() << "If `isSupportedPointerType` returned true for the "
"starting type, then `IsSupported` should also be "
"true. It is a waste to use this walker if the "
"starting type is not a supported pointer type.\n";
assert(false);
}
return IsSupported;
}
bool VisitSubstTemplateTypeParmType(const SubstTemplateTypeParmType* T) {
addTemplateDeclsSeenInQualifiers(T);
// If the replaced template parameter is not a parameter of a template
// that we've seen while walking the type, then it is a template parameter
// of the current context and the type is only a pointer by way of the
// replacement type. We no longer consider it to be a supported pointer
// type.
//
// This avoids inferring the nullability for declarations in a template
// that may only sometimes be pointers, e.g. a field holding the
// underlying data in a generic wrapper type, for which we will never
// write an annotation in the template and for which the nullability
// should instead be specified as part of the template argument.
if (!TemplateDeclsSeen.contains(T->getAssociatedDecl())) {
if (const auto* CTSD = dyn_cast<ClassTemplateSpecializationDecl>(
T->getAssociatedDecl());
!CTSD ||
!TemplateDeclsSeen.contains(CTSD->getSpecializedTemplate())) {
return false;
}
}
// If the template parameter being replaced is a parameter of a template
// decl referenced in the type being walked, we allow for the pointer type
// to be inside the replacement type.
// This allows for the template parameters of type aliases and name
// specifiers to make the type a pointer and still consider the type
// inferable.
return Visit(T->getReplacementType().getTypePtr());
}
bool VisitTemplateSpecializationType(const TemplateSpecializationType* T) {
addTemplateDeclsSeenInQualifiers(T);
if (T->isTypeAlias()) {
TemplateDeclsSeen.insert(T->getTemplateName().getAsTemplateDecl());
return Visit(T->getAliasedType().getTypePtr());
}
bool IsSupported = isSupportedSmartPointerType(QualType(T, 0));
if (!IsSupported) {
llvm::errs() << "If `isSupportedPointerType` returned true for the "
"starting type, then `IsSupported` should also be "
"true. It is a waste to use this walker if the "
"starting type is not a supported pointer type.\n";
assert(false);
}
return IsSupported;
}
private:
llvm::DenseSet<const Decl*> TemplateDeclsSeen;
};
return Walker().Visit(T.getTypePtr());
}
bool hasInferable(QualType T) {
QualType NonReferenceType = T.getNonReferenceType();
return isSupportedPointerType(NonReferenceType) &&
isSupportedPointerTypeOutsideOfSubstitutedTemplateParam(
NonReferenceType);
}
int countInferableSlots(const Decl& D) {
if (const auto* Func = dyn_cast<FunctionDecl>(&D)) {
int Slots = 0;
if (hasInferable(Func->getReturnType())) ++Slots;
for (auto* P : Func->parameters())
if (hasInferable(P->getType())) ++Slots;
return Slots;
}
if (const auto* Field = dyn_cast<FieldDecl>(&D)) {
return hasInferable(Field->getType()) ? 1 : 0;
}
if (const auto* Var = dyn_cast<VarDecl>(&D)) {
return hasInferable(Var->getType()) ? 1 : 0;
}
return 0;
}
bool isInferenceTarget(const Decl& D) {
if (const auto* Func = dyn_cast<FunctionDecl>(&D)) {
return
// Function templates are in principle inferable.
// However since we don't analyze their bodies, and other
// implementations cannot interact with them directly, we can't
// perform any nontrivial inference, just propagate annotations
// across redecls. For now, we don't do this as some infra
// (NullabilityWalker) doesn't work on dependent code. Instead, we infer
// for the instantiations and the decls inside the instantiations, and
// can use matching source ranges (the ranges inside the template) to
// merge evidence and make an inference when all instantiations are
// consistent enough.
!Func->isDependentContext() &&
// Same treatment for functions in templates as for function templates.
!Func->getDeclContext()->isDependentContext() &&
// builtins can't be annotated and are irregular in their type checking
// and in other ways, leading to violations of otherwise sound
// assumptions.
// If we find that their nullability is unexpectedly leaking into
// programs under analysis in significant ways, we can hardcode this
// small set of functions.
Func->getBuiltinID() == 0 &&
// Implicit functions cannot be annotated.
!Func->isImplicit() &&
// Do the most expensive check last.
countInferableSlots(*Func) > 0;
}
if (const auto* Field = dyn_cast<FieldDecl>(&D)) {
return
// See comments above regarding templates.
!Field->getDeclContext()->isDependentContext() &&
// Do the most expensive check last.
countInferableSlots(*Field) > 0;
}
if (const auto* Var = dyn_cast<VarDecl>(&D)) {
// Include static member variables, global variables, and local variables,
// including static variables defined in a function.
// Exclude parameters, which are handled as part of their enclosing function
// declaration.
if (isa<ParmVarDecl>(Var)) return false;
return
// Exclude variables inside templates as well as variable templates. See
// comments above regarding similar restrictions on functions. As with
// functions, the analogous variables inside instantiations and variable
// template instantiations are not excluded.
!Var->getDeclContext()->isDependentContext() && !Var->isTemplated() &&
// Do the most expensive check last.
countInferableSlots(*Var) > 0;
}
return false;
}
} // namespace clang::tidy::nullability