Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[clang][dataflow] Make optional checker work for types derived from optional. #84138

Merged
merged 3 commits into from
Mar 19, 2024

Conversation

martinboehme
Copy link
Contributor

llvm::MaybeAlign does this, for example.

It's not an option to simply ignore these derived classes because they get cast
back to the optional classes (for example, simply when calling the optional
member functions), and our transfer functions will then run on those optional
classes and therefore require them to be properly initialized.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:dataflow Clang Dataflow Analysis framework - https://clang.llvm.org/docs/DataFlowAnalysisIntro.html clang:analysis labels Mar 6, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Mar 6, 2024

@llvm/pr-subscribers-clang-analysis

@llvm/pr-subscribers-clang

Author: None (martinboehme)

Changes

llvm::MaybeAlign does this, for example.

It's not an option to simply ignore these derived classes because they get cast
back to the optional classes (for example, simply when calling the optional
member functions), and our transfer functions will then run on those optional
classes and therefore require them to be properly initialized.


Full diff: https://github.com/llvm/llvm-project/pull/84138.diff

2 Files Affected:

  • (modified) clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp (+81-67)
  • (modified) clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp (+30)
diff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
index 1d31b22b6d25ff..73dbc027a239e9 100644
--- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
@@ -64,28 +64,43 @@ static bool hasOptionalClassName(const CXXRecordDecl &RD) {
   return false;
 }
 
+static const CXXRecordDecl *getOptionalBaseClass(const CXXRecordDecl *RD) {
+  if (RD == nullptr)
+    return nullptr;
+  if (hasOptionalClassName(*RD))
+    return RD;
+
+  if (!RD->hasDefinition())
+    return nullptr;
+
+  for (const CXXBaseSpecifier &Base : RD->bases())
+    if (Base.getAccessSpecifier() == AS_public)
+      if (const CXXRecordDecl *BaseClass =
+              getOptionalBaseClass(Base.getType()->getAsCXXRecordDecl()))
+        return BaseClass;
+
+  return nullptr;
+}
+
 namespace {
 
 using namespace ::clang::ast_matchers;
 using LatticeTransferState = TransferState<NoopLattice>;
 
-AST_MATCHER(CXXRecordDecl, hasOptionalClassNameMatcher) {
-  return hasOptionalClassName(Node);
+AST_MATCHER(CXXRecordDecl, optionalOrDerivedClass) {
+  return getOptionalBaseClass(&Node) != nullptr;
 }
 
-DeclarationMatcher optionalClass() {
-  return classTemplateSpecializationDecl(
-      hasOptionalClassNameMatcher(),
-      hasTemplateArgument(0, refersToType(type().bind("T"))));
-}
-
-auto optionalOrAliasType() {
+auto desugarsToOptionalOrDerivedType() {
   return hasUnqualifiedDesugaredType(
-      recordType(hasDeclaration(optionalClass())));
+      recordType(hasDeclaration(cxxRecordDecl(optionalOrDerivedClass()))));
 }
 
-/// Matches any of the spellings of the optional types and sugar, aliases, etc.
-auto hasOptionalType() { return hasType(optionalOrAliasType()); }
+/// Matches any of the spellings of the optional types and sugar, aliases,
+/// derived classes, etc.
+auto hasOptionalOrDerivedType() {
+  return hasType(desugarsToOptionalOrDerivedType());
+}
 
 auto isOptionalMemberCallWithNameMatcher(
     ast_matchers::internal::Matcher<NamedDecl> matcher,
@@ -93,9 +108,9 @@ auto isOptionalMemberCallWithNameMatcher(
   auto Exception = unless(Ignorable ? expr(anyOf(*Ignorable, cxxThisExpr()))
                                     : cxxThisExpr());
   return cxxMemberCallExpr(
-      on(expr(Exception,
-              anyOf(hasOptionalType(),
-                    hasType(pointerType(pointee(optionalOrAliasType())))))),
+      on(expr(Exception, anyOf(hasOptionalOrDerivedType(),
+                               hasType(pointerType(pointee(
+                                   desugarsToOptionalOrDerivedType())))))),
       callee(cxxMethodDecl(matcher)));
 }
 
@@ -104,7 +119,7 @@ auto isOptionalOperatorCallWithName(
     const std::optional<StatementMatcher> &Ignorable = std::nullopt) {
   return cxxOperatorCallExpr(
       hasOverloadedOperatorName(operator_name),
-      callee(cxxMethodDecl(ofClass(optionalClass()))),
+      callee(cxxMethodDecl(ofClass(optionalOrDerivedClass()))),
       Ignorable ? callExpr(unless(hasArgument(0, *Ignorable))) : callExpr());
 }
 
@@ -112,7 +127,7 @@ auto isMakeOptionalCall() {
   return callExpr(callee(functionDecl(hasAnyName(
                       "std::make_optional", "base::make_optional",
                       "absl::make_optional", "folly::make_optional"))),
-                  hasOptionalType());
+                  hasOptionalOrDerivedType());
 }
 
 auto nulloptTypeDecl() {
@@ -129,19 +144,19 @@ auto inPlaceClass() {
 
 auto isOptionalNulloptConstructor() {
   return cxxConstructExpr(
-      hasOptionalType(),
+      hasOptionalOrDerivedType(),
       hasDeclaration(cxxConstructorDecl(parameterCountIs(1),
                                         hasParameter(0, hasNulloptType()))));
 }
 
 auto isOptionalInPlaceConstructor() {
-  return cxxConstructExpr(hasOptionalType(),
+  return cxxConstructExpr(hasOptionalOrDerivedType(),
                           hasArgument(0, hasType(inPlaceClass())));
 }
 
 auto isOptionalValueOrConversionConstructor() {
   return cxxConstructExpr(
-      hasOptionalType(),
+      hasOptionalOrDerivedType(),
       unless(hasDeclaration(
           cxxConstructorDecl(anyOf(isCopyConstructor(), isMoveConstructor())))),
       argumentCountIs(1), hasArgument(0, unless(hasNulloptType())));
@@ -150,28 +165,30 @@ auto isOptionalValueOrConversionConstructor() {
 auto isOptionalValueOrConversionAssignment() {
   return cxxOperatorCallExpr(
       hasOverloadedOperatorName("="),
-      callee(cxxMethodDecl(ofClass(optionalClass()))),
+      callee(cxxMethodDecl(ofClass(optionalOrDerivedClass()))),
       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()));
+  return cxxOperatorCallExpr(
+      hasOverloadedOperatorName("="),
+      callee(cxxMethodDecl(ofClass(optionalOrDerivedClass()))),
+      argumentCountIs(2), hasArgument(1, hasNulloptType()));
 }
 
 auto isStdSwapCall() {
   return callExpr(callee(functionDecl(hasName("std::swap"))),
-                  argumentCountIs(2), hasArgument(0, hasOptionalType()),
-                  hasArgument(1, hasOptionalType()));
+                  argumentCountIs(2),
+                  hasArgument(0, hasOptionalOrDerivedType()),
+                  hasArgument(1, hasOptionalOrDerivedType()));
 }
 
 auto isStdForwardCall() {
   return callExpr(callee(functionDecl(hasName("std::forward"))),
-                  argumentCountIs(1), hasArgument(0, hasOptionalType()));
+                  argumentCountIs(1),
+                  hasArgument(0, hasOptionalOrDerivedType()));
 }
 
 constexpr llvm::StringLiteral ValueOrCallID = "ValueOrCall";
@@ -181,10 +198,11 @@ 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))))
+          cxxMemberCallExpr(
+              on(expr(unless(cxxThisExpr()))),
+              callee(cxxMethodDecl(hasName("value_or"),
+                                   ofClass(optionalOrDerivedClass()))),
+              hasArgument(0, stringLiteral(hasSize(0))))
               .bind(ValueOrCallID))));
 }
 
@@ -192,10 +210,11 @@ 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))
+            cxxMemberCallExpr(
+                on(expr(unless(cxxThisExpr()))),
+                callee(cxxMethodDecl(hasName("value_or"),
+                                     ofClass(optionalOrDerivedClass()))),
+                hasArgument(0, Arg))
                 .bind(ValueOrCallID)),
         ignoringImplicit(Arg));
   };
@@ -212,8 +231,9 @@ auto isValueOrNotEqX() {
 }
 
 auto isCallReturningOptional() {
-  return callExpr(hasType(qualType(anyOf(
-      optionalOrAliasType(), referenceType(pointee(optionalOrAliasType()))))));
+  return callExpr(hasType(qualType(
+      anyOf(desugarsToOptionalOrDerivedType(),
+            referenceType(pointee(desugarsToOptionalOrDerivedType()))))));
 }
 
 template <typename L, typename R>
@@ -275,12 +295,9 @@ BoolValue *getHasValue(Environment &Env, RecordStorageLocation *OptionalLoc) {
   return HasValueVal;
 }
 
-/// Returns true if and only if `Type` is an optional type.
-bool isOptionalType(QualType Type) {
-  if (!Type->isRecordType())
-    return false;
-  const CXXRecordDecl *D = Type->getAsCXXRecordDecl();
-  return D != nullptr && hasOptionalClassName(*D);
+QualType valueTypeFromOptionalDecl(const CXXRecordDecl &RD) {
+  auto &CTSD = cast<ClassTemplateSpecializationDecl>(RD);
+  return CTSD.getTemplateArgs()[0].getAsType();
 }
 
 /// Returns the number of optional wrappers in `Type`.
@@ -288,15 +305,13 @@ bool isOptionalType(QualType Type) {
 /// For example, if `Type` is `optional<optional<int>>`, the result of this
 /// function will be 2.
 int countOptionalWrappers(const ASTContext &ASTCtx, QualType Type) {
-  if (!isOptionalType(Type))
+  const CXXRecordDecl *Optional =
+      getOptionalBaseClass(Type->getAsCXXRecordDecl());
+  if (Optional == nullptr)
     return 0;
   return 1 + countOptionalWrappers(
                  ASTCtx,
-                 cast<ClassTemplateSpecializationDecl>(Type->getAsRecordDecl())
-                     ->getTemplateArgs()
-                     .get(0)
-                     .getAsType()
-                     .getDesugaredType(ASTCtx));
+                 valueTypeFromOptionalDecl(*Optional).getDesugaredType(ASTCtx));
 }
 
 StorageLocation *getLocBehindPossiblePointer(const Expr &E,
@@ -623,7 +638,7 @@ ignorableOptional(const UncheckedOptionalAccessModelOptions &Options) {
   if (Options.IgnoreSmartPointerDereference) {
     auto SmartPtrUse = expr(ignoringParenImpCasts(cxxOperatorCallExpr(
         anyOf(hasOverloadedOperatorName("->"), hasOverloadedOperatorName("*")),
-        unless(hasArgument(0, expr(hasOptionalType()))))));
+        unless(hasArgument(0, expr(hasOptionalOrDerivedType()))))));
     return expr(
         anyOf(SmartPtrUse, memberExpr(hasObjectExpression(SmartPtrUse))));
   }
@@ -758,32 +773,35 @@ auto buildTransferMatchSwitch() {
 
       // Comparisons (==, !=):
       .CaseOfCFGStmt<CXXOperatorCallExpr>(
-          isComparisonOperatorCall(hasOptionalType(), hasOptionalType()),
+          isComparisonOperatorCall(hasOptionalOrDerivedType(),
+                                   hasOptionalOrDerivedType()),
           transferOptionalAndOptionalCmp)
       .CaseOfCFGStmt<CXXOperatorCallExpr>(
-          isComparisonOperatorCall(hasOptionalType(), hasNulloptType()),
+          isComparisonOperatorCall(hasOptionalOrDerivedType(),
+                                   hasNulloptType()),
           [](const clang::CXXOperatorCallExpr *Cmp,
              const MatchFinder::MatchResult &, LatticeTransferState &State) {
             transferOptionalAndNulloptCmp(Cmp, Cmp->getArg(0), State.Env);
           })
       .CaseOfCFGStmt<CXXOperatorCallExpr>(
-          isComparisonOperatorCall(hasNulloptType(), hasOptionalType()),
+          isComparisonOperatorCall(hasNulloptType(),
+                                   hasOptionalOrDerivedType()),
           [](const clang::CXXOperatorCallExpr *Cmp,
              const MatchFinder::MatchResult &, LatticeTransferState &State) {
             transferOptionalAndNulloptCmp(Cmp, Cmp->getArg(1), State.Env);
           })
       .CaseOfCFGStmt<CXXOperatorCallExpr>(
           isComparisonOperatorCall(
-              hasOptionalType(),
-              unless(anyOf(hasOptionalType(), hasNulloptType()))),
+              hasOptionalOrDerivedType(),
+              unless(anyOf(hasOptionalOrDerivedType(), hasNulloptType()))),
           [](const clang::CXXOperatorCallExpr *Cmp,
              const MatchFinder::MatchResult &, LatticeTransferState &State) {
             transferOptionalAndValueCmp(Cmp, Cmp->getArg(0), State.Env);
           })
       .CaseOfCFGStmt<CXXOperatorCallExpr>(
           isComparisonOperatorCall(
-              unless(anyOf(hasOptionalType(), hasNulloptType())),
-              hasOptionalType()),
+              unless(anyOf(hasOptionalOrDerivedType(), hasNulloptType())),
+              hasOptionalOrDerivedType()),
           [](const clang::CXXOperatorCallExpr *Cmp,
              const MatchFinder::MatchResult &, LatticeTransferState &State) {
             transferOptionalAndValueCmp(Cmp, Cmp->getArg(1), State.Env);
@@ -843,13 +861,7 @@ auto buildDiagnoseMatchSwitch(
 
 ast_matchers::DeclarationMatcher
 UncheckedOptionalAccessModel::optionalClassDecl() {
-  return optionalClass();
-}
-
-static QualType valueTypeFromOptionalType(QualType OptionalTy) {
-  auto *CTSD =
-      cast<ClassTemplateSpecializationDecl>(OptionalTy->getAsCXXRecordDecl());
-  return CTSD->getTemplateArgs()[0].getAsType();
+  return cxxRecordDecl(optionalOrDerivedClass());
 }
 
 UncheckedOptionalAccessModel::UncheckedOptionalAccessModel(ASTContext &Ctx,
@@ -858,9 +870,11 @@ UncheckedOptionalAccessModel::UncheckedOptionalAccessModel(ASTContext &Ctx,
       TransferMatchSwitch(buildTransferMatchSwitch()) {
   Env.getDataflowAnalysisContext().setSyntheticFieldCallback(
       [&Ctx](QualType Ty) -> llvm::StringMap<QualType> {
-        if (!isOptionalType(Ty))
+        const CXXRecordDecl *Optional =
+            getOptionalBaseClass(Ty->getAsCXXRecordDecl());
+        if (Optional == nullptr)
           return {};
-        return {{"value", valueTypeFromOptionalType(Ty)},
+        return {{"value", valueTypeFromOptionalDecl(*Optional)},
                 {"has_value", Ctx.BoolTy}};
       });
 }
diff --git a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
index b6e4973fd7cb2b..c1d5b2183007ae 100644
--- a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
@@ -3383,6 +3383,36 @@ TEST_P(UncheckedOptionalAccessTest, LambdaCaptureStateNotPropagated) {
     }
   )");
 }
+
+TEST_P(UncheckedOptionalAccessTest, ClassDerivedFromOptional) {
+  ExpectDiagnosticsFor(R"(
+    #include "unchecked_optional_access_test.h"
+
+    struct Derived : public $ns::$optional<int> {};
+
+    void target(Derived opt) {
+      *opt;  // [[unsafe]]
+      if (opt)
+        *opt;
+    }
+  )");
+}
+
+TEST_P(UncheckedOptionalAccessTest, ClassTemplateDerivedFromOptional) {
+  ExpectDiagnosticsFor(R"(
+    #include "unchecked_optional_access_test.h"
+
+    template <class T>
+    struct Derived : public $ns::$optional<T> {};
+
+    void target(Derived<int> opt) {
+      *opt;  // [[unsafe]]
+      if (opt)
+        *opt;
+    }
+  )");
+}
+
 // FIXME: Add support for:
 // - constructors (copy, move)
 // - assignment operators (default, copy, move)

return nullptr;

for (const CXXBaseSpecifier &Base : RD->bases())
if (Base.getAccessSpecifier() == AS_public)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can still get in trouble with private or protected inheritance. E.g., if I had class Foo : std::optional, I could still call the methods of std::optional in Foo's methods. Could we get in trouble while analyzing those methods?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great point! I've removed the AS_public check (and added a test for exactly this scenario, which fails with this check present).

@ymand
Copy link
Collaborator

ymand commented Mar 6, 2024

Per offline discussion, please see if we can be more targeted in our use of the generalized matching, given the potentially high cost incurred (for every relevant operation, like comparison, we'll be searching the entire type hierchy of the arguments).

…ptional.

`llvm::MaybeAlign` does this, for example.

It's not an option to simply ignore these derived classes because they get cast
back to the optional classes (for example, simply when calling the optional
member functions), and our transfer functions will then run on those optional
classes and therefore require them to be properly initialized.
@martinboehme
Copy link
Contributor Author

Per offline discussion, please see if we can be more targeted in our use of the generalized matching, given the potentially high cost incurred (for every relevant operation, like comparison, we'll be searching the entire type hierchy of the arguments).

Done!

I'm still checking for "optional or derived type" in some places where this seemed appropriate, but many places are now back to checking only for the optional type.

isOptionalMemberCallWithNameMatcher() was more involved that I expected -- see the comment on getPublicReceiverType() for the full details.

Copy link
Collaborator

@ymand ymand left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One high level question: is there a simpler path by which we avoid crashing on derived types but also don't support them? It seems like there is a lot of complexity required in getting this just right. Is it worth it (both in the code itself and in terms of runtime cost) to support these unusual cases? Of course, if it's necessary then the point is moot...

return Ty;
}

QualType Ty = getPublicType(Cast->getSubExpr());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this line. We're recursing, but then we throw away the result Ty based on how the loop turns out. Why not just test for this directly, after the loop and only recurse when the loop doesn't find what we want? See more comments below, but perhaps the problem is that I don't really understand what the loop is doing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope this has become clearer given the additional comments on what the loop is doing.

The idea behind doing the recursion first, then potentially throwing the result away, is that it makes the code proceed strictly along the patch from most-derived to most-base class.

Conceptually, we want to "drill down" into the getImplicitObjectArgument() to find the most-derived type, then walk back along the "cast path" towards the base until we find a point where we're no longer allowed to perform the cast. The code that was here expresses this most directly (the recursive call does the "drilling down"), but as you note, it's inefficient in the case where we need to throw away the result of the recursive call.

I've changed this to use tail recursion and have added comments that hopefully make it clear what the recursion is doing.

@martinboehme
Copy link
Contributor Author

One high level question: is there a simpler path by which we avoid crashing on derived types but also don't support them? It seems like there is a lot of complexity required in getting this just right. Is it worth it (both in the code itself and in terms of runtime cost) to support these unusual cases? Of course, if it's necessary then the point is moot...

This is a good question. tl;dr: I think actually handling these cases instead of just ignoring them is the right tradeoff, but the situation certainly isn't clear cut.

It's possible to avoid crashing on derived types but otherwise not support them. What we would need to do is call RecordStorageLocation::getType() on the storage location of the object that we're accessing; we would only proceed if we see an actual optional type (not a derived type). The thing is though that we would need to do this in every transfer_... handler; this isn't ideal because it's easy to forget one, and then we get crashes there. I considered this option, but it seemed to me that the more principled and less error-prone approach is to actually treat derived types correctly.

As I say, the tradeoff isn't clear-cut. To summarize:

Bail out but don't crash
Pros:

  • Less conceptual complexity...
    Cons:
  • ...but need a check in every handler (i.e. still lots of lines of code).
  • If we forget to add the check everywhere, we get crashes.

Correctly handle derived types
Pros:

  • The additional code is centralized in one place.
  • Actually makes the check more expressive
    Cons:
  • We need to walk the inheritance hierarchy and chains of casts, which adds complexity and potentially makes the check slower.

Will respond to the individual comments too, but probably not before Monday.

martinboehme added a commit to martinboehme/llvm-project that referenced this pull request Mar 18, 2024
This didn't have any documentation, so I had to do some experimenting in
godbolt when I used this in llvm#84138,
and my reviewer later also had some
[questions](llvm#84138 (comment))
about this, so I figured it would be worth adding documentation.
@martinboehme
Copy link
Contributor Author

Forgot to push a fixup commit -- now added.

for (const CXXBaseSpecifier *Base : Cast->path()) {
if (Base->getAccessSpecifier() != AS_public && !TyIsThisType)
if (Base->getAccessSpecifier() != AS_public && !CastingFromThis)
break;
Ty = Base->getType();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional super nit: save &Base instead, avoiding a call to getType() until after the loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

More importantly than the minor efficiency gain, I think this makes the code more self-documenting. Thanks!

@@ -129,19 +215,19 @@ auto inPlaceClass() {

auto isOptionalNulloptConstructor() {
return cxxConstructExpr(
hasOptionalType(),
hasOptionalOrDerivedType(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and below -- now that this matcher is more expensive, please move until after the less expensive matchers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@martinboehme martinboehme merged commit d712c5e into llvm:main Mar 19, 2024
4 checks passed
martinboehme added a commit that referenced this pull request Mar 20, 2024
This didn't have any documentation, so I had to do some experimenting in
godbolt when I used this in
#84138, and my reviewer later
also had some
[questions](#84138 (comment))
about this, so I figured it would be worth adding documentation.
chencha3 pushed a commit to chencha3/llvm-project that referenced this pull request Mar 23, 2024
…ptional. (llvm#84138)

`llvm::MaybeAlign` does this, for example.

It's not an option to simply ignore these derived classes because they
get cast
back to the optional classes (for example, simply when calling the
optional
member functions), and our transfer functions will then run on those
optional
classes and therefore require them to be properly initialized.
chencha3 pushed a commit to chencha3/llvm-project that referenced this pull request Mar 23, 2024
This didn't have any documentation, so I had to do some experimenting in
godbolt when I used this in
llvm#84138, and my reviewer later
also had some
[questions](llvm#84138 (comment))
about this, so I figured it would be worth adding documentation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:analysis clang:dataflow Clang Dataflow Analysis framework - https://clang.llvm.org/docs/DataFlowAnalysisIntro.html clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants