#include "clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/Stmt.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Analysis/FlowSensitive/MatchSwitch.h"
#include "clang/Analysis/FlowSensitive/NoopLattice.h"
#include "clang/Analysis/FlowSensitive/Value.h"
#include "clang/Basic/SourceLocation.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include <cassert>
#include <memory>
#include <utility>
#include <vector>
namespace clang {
namespace dataflow {
namespace {
using namespace ::clang::ast_matchers;
using LatticeTransferState = TransferState<NoopLattice>;
DeclarationMatcher optionalClass() {
return classTemplateSpecializationDecl(
anyOf(hasName("std::optional"), hasName("std::__optional_storage_base"),
hasName("__optional_destruct_base"), hasName("absl::optional"),
hasName("base::Optional")),
hasTemplateArgument(0, refersToType(type().bind("T"))));
}
auto optionalOrAliasType() {
return hasUnqualifiedDesugaredType(
recordType(hasDeclaration(optionalClass())));
}
auto hasOptionalType() { return hasType(optionalOrAliasType()); }
auto isOptionalMemberCallWithName(
llvm::StringRef MemberName,
llvm::Optional<StatementMatcher> Ignorable = llvm::None) {
auto Exception = unless(Ignorable ? expr(anyOf(*Ignorable, cxxThisExpr()))
: cxxThisExpr());
return cxxMemberCallExpr(
on(expr(Exception)),
callee(cxxMethodDecl(hasName(MemberName), ofClass(optionalClass()))));
}
auto isOptionalOperatorCallWithName(
llvm::StringRef operator_name,
llvm::Optional<StatementMatcher> Ignorable = llvm::None) {
return cxxOperatorCallExpr(
hasOverloadedOperatorName(operator_name),
callee(cxxMethodDecl(ofClass(optionalClass()))),
Ignorable ? callExpr(unless(hasArgument(0, *Ignorable))) : callExpr());
}
auto isMakeOptionalCall() {
return callExpr(
callee(functionDecl(hasAnyName(
"std::make_optional", "base::make_optional", "absl::make_optional"))),
hasOptionalType());
}
auto hasNulloptType() {
return hasType(namedDecl(
hasAnyName("std::nullopt_t", "absl::nullopt_t", "base::nullopt_t")));
}
auto inPlaceClass() {
return recordDecl(
hasAnyName("std::in_place_t", "absl::in_place_t", "base::in_place_t"));
}
auto isOptionalNulloptConstructor() {
return cxxConstructExpr(hasOptionalType(), argumentCountIs(1),
hasArgument(0, hasNulloptType()));
}
auto isOptionalInPlaceConstructor() {
return cxxConstructExpr(hasOptionalType(),
hasArgument(0, hasType(inPlaceClass())));
}
auto isOptionalValueOrConversionConstructor() {
return cxxConstructExpr(
hasOptionalType(),
unless(hasDeclaration(
cxxConstructorDecl(anyOf(isCopyConstructor(), isMoveConstructor())))),
argumentCountIs(1), hasArgument(0, unless(hasNulloptType())));
}
auto isOptionalValueOrConversionAssignment() {
return cxxOperatorCallExpr(
hasOverloadedOperatorName("="),
callee(cxxMethodDecl(ofClass(optionalClass()))),
unless(hasDeclaration(cxxMethodDecl(
anyOf(isCopyAssignmentOperator(), isMoveAssignmentOperator())))),
argumentCountIs(2), hasArgument(1, unless(hasNulloptType())));
}
auto isOptionalNulloptAssignment() {
return cxxOperatorCallExpr(hasOverloadedOperatorName("="),
callee(cxxMethodDecl(ofClass(optionalClass()))),
argumentCountIs(2),
hasArgument(1, hasNulloptType()));
}
auto isStdSwapCall() {
return callExpr(callee(functionDecl(hasName("std::swap"))),
argumentCountIs(2), hasArgument(0, hasOptionalType()),
hasArgument(1, hasOptionalType()));
}
constexpr llvm::StringLiteral ValueOrCallID = "ValueOrCall";
auto isValueOrStringEmptyCall() {
return cxxMemberCallExpr(
callee(cxxMethodDecl(hasName("empty"))),
onImplicitObjectArgument(ignoringImplicit(
cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))),
callee(cxxMethodDecl(hasName("value_or"),
ofClass(optionalClass()))),
hasArgument(0, stringLiteral(hasSize(0))))
.bind(ValueOrCallID))));
}
auto isValueOrNotEqX() {
auto ComparesToSame = [](ast_matchers::internal::Matcher<Stmt> Arg) {
return hasOperands(
ignoringImplicit(
cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))),
callee(cxxMethodDecl(hasName("value_or"),
ofClass(optionalClass()))),
hasArgument(0, Arg))
.bind(ValueOrCallID)),
ignoringImplicit(Arg));
};
return binaryOperation(hasOperatorName("!="),
anyOf(ComparesToSame(cxxNullPtrLiteralExpr()),
ComparesToSame(stringLiteral(hasSize(0))),
ComparesToSame(integerLiteral(equals(0)))));
}
auto isCallReturningOptional() {
return callExpr(hasType(qualType(anyOf(
optionalOrAliasType(), referenceType(pointee(optionalOrAliasType()))))));
}
void setHasValue(Value &OptionalVal, BoolValue &HasValueVal) {
OptionalVal.setProperty("has_value", HasValueVal);
}
StructValue &createOptionalValue(Environment &Env, BoolValue &HasValueVal) {
auto OptionalVal = std::make_unique<StructValue>();
setHasValue(*OptionalVal, HasValueVal);
return Env.takeOwnership(std::move(OptionalVal));
}
BoolValue *getHasValue(Environment &Env, Value *OptionalVal) {
if (OptionalVal != nullptr) {
auto *HasValueVal =
cast_or_null<BoolValue>(OptionalVal->getProperty("has_value"));
if (HasValueVal == nullptr) {
HasValueVal = &Env.makeAtomicBoolValue();
OptionalVal->setProperty("has_value", *HasValueVal);
}
return HasValueVal;
}
return nullptr;
}
QualType stripReference(QualType Type) {
return Type->isReferenceType() ? Type->getPointeeType() : Type;
}
bool IsOptionalType(QualType Type) {
if (!Type->isRecordType())
return false;
auto TypeName = Type->getAsCXXRecordDecl()->getQualifiedNameAsString();
return TypeName == "std::optional" || TypeName == "absl::optional" ||
TypeName == "base::Optional";
}
int countOptionalWrappers(const ASTContext &ASTCtx, QualType Type) {
if (!IsOptionalType(Type))
return 0;
return 1 + countOptionalWrappers(
ASTCtx,
cast<ClassTemplateSpecializationDecl>(Type->getAsRecordDecl())
->getTemplateArgs()
.get(0)
.getAsType()
.getDesugaredType(ASTCtx));
}
StorageLocation *maybeInitializeOptionalValueMember(QualType Q,
Value &OptionalVal,
Environment &Env) {
if (auto *ValueProp = OptionalVal.getProperty("value")) {
auto *ValueRef = clang::cast<ReferenceValue>(ValueProp);
auto &ValueLoc = ValueRef->getReferentLoc();
if (Env.getValue(ValueLoc) == nullptr) {
auto *ValueVal = Env.createValue(ValueLoc.getType());
if (ValueVal == nullptr)
return nullptr;
Env.setValue(ValueLoc, *ValueVal);
}
return &ValueLoc;
}
auto Ty = stripReference(Q);
auto *ValueVal = Env.createValue(Ty);
if (ValueVal == nullptr)
return nullptr;
auto &ValueLoc = Env.createStorageLocation(Ty);
Env.setValue(ValueLoc, *ValueVal);
auto ValueRef = std::make_unique<ReferenceValue>(ValueLoc);
OptionalVal.setProperty("value", Env.takeOwnership(std::move(ValueRef)));
return &ValueLoc;
}
void initializeOptionalReference(const Expr *OptionalExpr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
if (auto *OptionalVal =
State.Env.getValue(*OptionalExpr, SkipPast::Reference)) {
if (OptionalVal->getProperty("has_value") == nullptr) {
setHasValue(*OptionalVal, State.Env.makeAtomicBoolValue());
}
}
}
bool isEmptyOptional(const Value &OptionalVal, const Environment &Env) {
auto *HasValueVal =
cast_or_null<BoolValue>(OptionalVal.getProperty("has_value"));
return HasValueVal != nullptr &&
Env.flowConditionImplies(Env.makeNot(*HasValueVal));
}
bool isNonEmptyOptional(const Value &OptionalVal, const Environment &Env) {
auto *HasValueVal =
cast_or_null<BoolValue>(OptionalVal.getProperty("has_value"));
return HasValueVal != nullptr && Env.flowConditionImplies(*HasValueVal);
}
void transferUnwrapCall(const Expr *UnwrapExpr, const Expr *ObjectExpr,
LatticeTransferState &State) {
if (auto *OptionalVal =
State.Env.getValue(*ObjectExpr, SkipPast::ReferenceThenPointer)) {
if (State.Env.getStorageLocation(*UnwrapExpr, SkipPast::None) == nullptr)
if (auto *Loc = maybeInitializeOptionalValueMember(
UnwrapExpr->getType(), *OptionalVal, State.Env))
State.Env.setStorageLocation(*UnwrapExpr, *Loc);
}
}
void transferMakeOptionalCall(const CallExpr *E,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
auto &Loc = State.Env.createStorageLocation(*E);
State.Env.setStorageLocation(*E, Loc);
State.Env.setValue(
Loc, createOptionalValue(State.Env, State.Env.getBoolLiteralValue(true)));
}
void transferOptionalHasValueCall(const CXXMemberCallExpr *CallExpr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
if (auto *HasValueVal = getHasValue(
State.Env, State.Env.getValue(*CallExpr->getImplicitObjectArgument(),
SkipPast::ReferenceThenPointer))) {
auto &CallExprLoc = State.Env.createStorageLocation(*CallExpr);
State.Env.setValue(CallExprLoc, *HasValueVal);
State.Env.setStorageLocation(*CallExpr, CallExprLoc);
}
}
void transferValueOrImpl(const clang::Expr *ValueOrPredExpr,
const MatchFinder::MatchResult &Result,
LatticeTransferState &State,
BoolValue &(*ModelPred)(Environment &Env,
BoolValue &ExprVal,
BoolValue &HasValueVal)) {
auto &Env = State.Env;
const auto *ObjectArgumentExpr =
Result.Nodes.getNodeAs<clang::CXXMemberCallExpr>(ValueOrCallID)
->getImplicitObjectArgument();
auto *HasValueVal = getHasValue(
State.Env,
State.Env.getValue(*ObjectArgumentExpr, SkipPast::ReferenceThenPointer));
if (HasValueVal == nullptr)
return;
auto *ExprValue = cast_or_null<BoolValue>(
State.Env.getValue(*ValueOrPredExpr, SkipPast::None));
if (ExprValue == nullptr) {
auto &ExprLoc = State.Env.createStorageLocation(*ValueOrPredExpr);
ExprValue = &State.Env.makeAtomicBoolValue();
State.Env.setValue(ExprLoc, *ExprValue);
State.Env.setStorageLocation(*ValueOrPredExpr, ExprLoc);
}
Env.addToFlowCondition(ModelPred(Env, *ExprValue, *HasValueVal));
}
void transferValueOrStringEmptyCall(const clang::Expr *ComparisonExpr,
const MatchFinder::MatchResult &Result,
LatticeTransferState &State) {
return transferValueOrImpl(ComparisonExpr, Result, State,
[](Environment &Env, BoolValue &ExprVal,
BoolValue &HasValueVal) -> BoolValue & {
return Env.makeImplication(Env.makeNot(ExprVal),
HasValueVal);
});
}
void transferValueOrNotEqX(const Expr *ComparisonExpr,
const MatchFinder::MatchResult &Result,
LatticeTransferState &State) {
transferValueOrImpl(ComparisonExpr, Result, State,
[](Environment &Env, BoolValue &ExprVal,
BoolValue &HasValueVal) -> BoolValue & {
return Env.makeImplication(ExprVal, HasValueVal);
});
}
void transferCallReturningOptional(const CallExpr *E,
const MatchFinder::MatchResult &Result,
LatticeTransferState &State) {
if (State.Env.getStorageLocation(*E, SkipPast::None) != nullptr)
return;
auto &Loc = State.Env.createStorageLocation(*E);
State.Env.setStorageLocation(*E, Loc);
State.Env.setValue(
Loc, createOptionalValue(State.Env, State.Env.makeAtomicBoolValue()));
}
void assignOptionalValue(const Expr &E, LatticeTransferState &State,
BoolValue &HasValueVal) {
if (auto *OptionalLoc =
State.Env.getStorageLocation(E, SkipPast::ReferenceThenPointer)) {
State.Env.setValue(*OptionalLoc,
createOptionalValue(State.Env, HasValueVal));
}
}
BoolValue &value_orConversionHasValue(const FunctionDecl &F, const Expr &E,
const MatchFinder::MatchResult &MatchRes,
LatticeTransferState &State) {
assert(F.getTemplateSpecializationArgs()->size() > 0);
const int TemplateParamOptionalWrappersCount = countOptionalWrappers(
*MatchRes.Context,
stripReference(F.getTemplateSpecializationArgs()->get(0).getAsType()));
const int ArgTypeOptionalWrappersCount =
countOptionalWrappers(*MatchRes.Context, stripReference(E.getType()));
if (TemplateParamOptionalWrappersCount == ArgTypeOptionalWrappersCount)
return State.Env.getBoolLiteralValue(true);
if (auto *HasValueVal =
getHasValue(State.Env, State.Env.getValue(E, SkipPast::Reference)))
return *HasValueVal;
return State.Env.makeAtomicBoolValue();
}
void transferValueOrConversionConstructor(
const CXXConstructExpr *E, const MatchFinder::MatchResult &MatchRes,
LatticeTransferState &State) {
assert(E->getNumArgs() > 0);
assignOptionalValue(*E, State,
value_orConversionHasValue(*E->getConstructor(),
*E->getArg(0), MatchRes,
State));
}
void transferAssignment(const CXXOperatorCallExpr *E, BoolValue &HasValueVal,
LatticeTransferState &State) {
assert(E->getNumArgs() > 0);
auto *OptionalLoc =
State.Env.getStorageLocation(*E->getArg(0), SkipPast::Reference);
if (OptionalLoc == nullptr)
return;
State.Env.setValue(*OptionalLoc, createOptionalValue(State.Env, HasValueVal));
State.Env.setStorageLocation(*E, *OptionalLoc);
}
void transferValueOrConversionAssignment(
const CXXOperatorCallExpr *E, const MatchFinder::MatchResult &MatchRes,
LatticeTransferState &State) {
assert(E->getNumArgs() > 1);
transferAssignment(E,
value_orConversionHasValue(*E->getDirectCallee(),
*E->getArg(1), MatchRes, State),
State);
}
void transferNulloptAssignment(const CXXOperatorCallExpr *E,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
transferAssignment(E, State.Env.getBoolLiteralValue(false), State);
}
void transferSwap(const StorageLocation &OptionalLoc1,
const StorageLocation &OptionalLoc2,
LatticeTransferState &State) {
auto *OptionalVal1 = State.Env.getValue(OptionalLoc1);
assert(OptionalVal1 != nullptr);
auto *OptionalVal2 = State.Env.getValue(OptionalLoc2);
assert(OptionalVal2 != nullptr);
State.Env.setValue(OptionalLoc1, *OptionalVal2);
State.Env.setValue(OptionalLoc2, *OptionalVal1);
}
void transferSwapCall(const CXXMemberCallExpr *E,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
assert(E->getNumArgs() == 1);
auto *OptionalLoc1 = State.Env.getStorageLocation(
*E->getImplicitObjectArgument(), SkipPast::ReferenceThenPointer);
assert(OptionalLoc1 != nullptr);
auto *OptionalLoc2 =
State.Env.getStorageLocation(*E->getArg(0), SkipPast::Reference);
assert(OptionalLoc2 != nullptr);
transferSwap(*OptionalLoc1, *OptionalLoc2, State);
}
void transferStdSwapCall(const CallExpr *E, const MatchFinder::MatchResult &,
LatticeTransferState &State) {
assert(E->getNumArgs() == 2);
auto *OptionalLoc1 =
State.Env.getStorageLocation(*E->getArg(0), SkipPast::Reference);
assert(OptionalLoc1 != nullptr);
auto *OptionalLoc2 =
State.Env.getStorageLocation(*E->getArg(1), SkipPast::Reference);
assert(OptionalLoc2 != nullptr);
transferSwap(*OptionalLoc1, *OptionalLoc2, State);
}
llvm::Optional<StatementMatcher>
ignorableOptional(const UncheckedOptionalAccessModelOptions &Options) {
if (Options.IgnoreSmartPointerDereference)
return memberExpr(hasObjectExpression(ignoringParenImpCasts(
cxxOperatorCallExpr(anyOf(hasOverloadedOperatorName("->"),
hasOverloadedOperatorName("*")),
unless(hasArgument(0, expr(hasOptionalType())))))));
return llvm::None;
}
StatementMatcher
valueCall(llvm::Optional<StatementMatcher> &IgnorableOptional) {
return isOptionalMemberCallWithName("value", IgnorableOptional);
}
StatementMatcher
valueOperatorCall(llvm::Optional<StatementMatcher> &IgnorableOptional) {
return expr(anyOf(isOptionalOperatorCallWithName("*", IgnorableOptional),
isOptionalOperatorCallWithName("->", IgnorableOptional)));
}
auto buildTransferMatchSwitch(
const UncheckedOptionalAccessModelOptions &Options) {
auto IgnorableOptional = ignorableOptional(Options);
return MatchSwitchBuilder<LatticeTransferState>()
.CaseOf<Expr>(
expr(anyOf(declRefExpr(), memberExpr()), hasOptionalType()),
initializeOptionalReference)
.CaseOf<CallExpr>(isMakeOptionalCall(), transferMakeOptionalCall)
.CaseOf<CXXConstructExpr>(
isOptionalInPlaceConstructor(),
[](const CXXConstructExpr *E, const MatchFinder::MatchResult &,
LatticeTransferState &State) {
assignOptionalValue(*E, State, State.Env.getBoolLiteralValue(true));
})
.CaseOf<CXXConstructExpr>(
isOptionalNulloptConstructor(),
[](const CXXConstructExpr *E, const MatchFinder::MatchResult &,
LatticeTransferState &State) {
assignOptionalValue(*E, State,
State.Env.getBoolLiteralValue(false));
})
.CaseOf<CXXConstructExpr>(isOptionalValueOrConversionConstructor(),
transferValueOrConversionConstructor)
.CaseOf<CXXOperatorCallExpr>(isOptionalValueOrConversionAssignment(),
transferValueOrConversionAssignment)
.CaseOf<CXXOperatorCallExpr>(isOptionalNulloptAssignment(),
transferNulloptAssignment)
.CaseOf<CXXMemberCallExpr>(
valueCall(IgnorableOptional),
[](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
LatticeTransferState &State) {
transferUnwrapCall(E, E->getImplicitObjectArgument(), State);
})
.CaseOf<CallExpr>(valueOperatorCall(IgnorableOptional),
[](const CallExpr *E, const MatchFinder::MatchResult &,
LatticeTransferState &State) {
transferUnwrapCall(E, E->getArg(0), State);
})
.CaseOf<CXXMemberCallExpr>(isOptionalMemberCallWithName("has_value"),
transferOptionalHasValueCall)
.CaseOf<CXXMemberCallExpr>(isOptionalMemberCallWithName("operator bool"),
transferOptionalHasValueCall)
.CaseOf<CXXMemberCallExpr>(
isOptionalMemberCallWithName("emplace"),
[](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
LatticeTransferState &State) {
assignOptionalValue(*E->getImplicitObjectArgument(), State,
State.Env.getBoolLiteralValue(true));
})
.CaseOf<CXXMemberCallExpr>(
isOptionalMemberCallWithName("reset"),
[](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
LatticeTransferState &State) {
assignOptionalValue(*E->getImplicitObjectArgument(), State,
State.Env.getBoolLiteralValue(false));
})
.CaseOf<CXXMemberCallExpr>(isOptionalMemberCallWithName("swap"),
transferSwapCall)
.CaseOf<CallExpr>(isStdSwapCall(), transferStdSwapCall)
.CaseOf<Expr>(isValueOrStringEmptyCall(), transferValueOrStringEmptyCall)
.CaseOf<Expr>(isValueOrNotEqX(), transferValueOrNotEqX)
.CaseOf<CallExpr>(isCallReturningOptional(),
transferCallReturningOptional)
.Build();
}
std::vector<SourceLocation> diagnoseUnwrapCall(const Expr *UnwrapExpr,
const Expr *ObjectExpr,
const Environment &Env) {
if (auto *OptionalVal =
Env.getValue(*ObjectExpr, SkipPast::ReferenceThenPointer)) {
auto *Prop = OptionalVal->getProperty("has_value");
if (auto *HasValueVal = cast_or_null<BoolValue>(Prop)) {
if (Env.flowConditionImplies(*HasValueVal))
return {};
}
}
return {ObjectExpr->getBeginLoc()};
}
auto buildDiagnoseMatchSwitch(
const UncheckedOptionalAccessModelOptions &Options) {
auto IgnorableOptional = ignorableOptional(Options);
return MatchSwitchBuilder<const Environment, std::vector<SourceLocation>>()
.CaseOf<CXXMemberCallExpr>(
valueCall(IgnorableOptional),
[](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
const Environment &Env) {
return diagnoseUnwrapCall(E, E->getImplicitObjectArgument(), Env);
})
.CaseOf<CallExpr>(
valueOperatorCall(IgnorableOptional),
[](const CallExpr *E, const MatchFinder::MatchResult &,
const Environment &Env) {
return diagnoseUnwrapCall(E, E->getArg(0), Env);
})
.Build();
}
}
ast_matchers::DeclarationMatcher
UncheckedOptionalAccessModel::optionalClassDecl() {
return optionalClass();
}
UncheckedOptionalAccessModel::UncheckedOptionalAccessModel(
ASTContext &Ctx, UncheckedOptionalAccessModelOptions Options)
: DataflowAnalysis<UncheckedOptionalAccessModel, NoopLattice>(Ctx),
TransferMatchSwitch(buildTransferMatchSwitch(Options)) {}
void UncheckedOptionalAccessModel::transfer(const Stmt *S, NoopLattice &L,
Environment &Env) {
LatticeTransferState State(L, Env);
TransferMatchSwitch(*S, getASTContext(), State);
}
bool UncheckedOptionalAccessModel::compareEquivalent(QualType Type,
const Value &Val1,
const Environment &Env1,
const Value &Val2,
const Environment &Env2) {
return isNonEmptyOptional(Val1, Env1) == isNonEmptyOptional(Val2, Env2);
}
bool UncheckedOptionalAccessModel::merge(QualType Type, const Value &Val1,
const Environment &Env1,
const Value &Val2,
const Environment &Env2,
Value &MergedVal,
Environment &MergedEnv) {
if (!IsOptionalType(Type))
return true;
auto &HasValueVal = MergedEnv.makeAtomicBoolValue();
if (isNonEmptyOptional(Val1, Env1) && isNonEmptyOptional(Val2, Env2))
MergedEnv.addToFlowCondition(HasValueVal);
else if (isEmptyOptional(Val1, Env1) && isEmptyOptional(Val2, Env2))
MergedEnv.addToFlowCondition(MergedEnv.makeNot(HasValueVal));
setHasValue(MergedVal, HasValueVal);
return true;
}
UncheckedOptionalAccessDiagnoser::UncheckedOptionalAccessDiagnoser(
UncheckedOptionalAccessModelOptions Options)
: DiagnoseMatchSwitch(buildDiagnoseMatchSwitch(Options)) {}
std::vector<SourceLocation> UncheckedOptionalAccessDiagnoser::diagnose(
ASTContext &Context, const Stmt *Stmt, const Environment &Env) {
return DiagnoseMatchSwitch(*Stmt, Context, Env);
}
} }