Skip to content

Commit ae28028

Browse files
authored
[clang][dataflow] Fix for value constructor in class derived from optional. (#86942)
The constructor `Derived(int)` in the newly added test `ClassDerivedFromOptionalValueConstructor` is not a template, and this used to cause an assertion failure in `valueOrConversionHasValue()` because `F.getTemplateSpecializationArgs()` returns null. (This is modeled after the `MaybeAlign(Align Value)` constructor, which similarly causes an assertion failure in the analysis when assigning an `Align` to a `MaybeAlign`.) To fix this, we can simply look at the type of the destination type which we're constructing or assigning to (instead of the function template argument), and this not only fixes this specific case but actually simplifies the implementation. I've added some additional tests for the case of assigning to a nested optional because we didn't have coverage for these and I wanted to make sure I didn't break anything.
1 parent d3aa92e commit ae28028

File tree

2 files changed

+92
-22
lines changed

2 files changed

+92
-22
lines changed

clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -512,27 +512,26 @@ void constructOptionalValue(const Expr &E, Environment &Env,
512512
/// Returns a symbolic value for the "has_value" property of an `optional<T>`
513513
/// value that is constructed/assigned from a value of type `U` or `optional<U>`
514514
/// where `T` is constructible from `U`.
515-
BoolValue &valueOrConversionHasValue(const FunctionDecl &F, const Expr &E,
515+
BoolValue &valueOrConversionHasValue(QualType DestType, const Expr &E,
516516
const MatchFinder::MatchResult &MatchRes,
517517
LatticeTransferState &State) {
518-
assert(F.getTemplateSpecializationArgs() != nullptr);
519-
assert(F.getTemplateSpecializationArgs()->size() > 0);
520-
521-
const int TemplateParamOptionalWrappersCount =
522-
countOptionalWrappers(*MatchRes.Context, F.getTemplateSpecializationArgs()
523-
->get(0)
524-
.getAsType()
525-
.getNonReferenceType());
518+
const int DestTypeOptionalWrappersCount =
519+
countOptionalWrappers(*MatchRes.Context, DestType);
526520
const int ArgTypeOptionalWrappersCount = countOptionalWrappers(
527521
*MatchRes.Context, E.getType().getNonReferenceType());
528522

529-
// Check if this is a constructor/assignment call for `optional<T>` with
530-
// argument of type `U` such that `T` is constructible from `U`.
531-
if (TemplateParamOptionalWrappersCount == ArgTypeOptionalWrappersCount)
523+
// Is this an constructor of the form `template<class U> optional(U &&)` /
524+
// assignment of the form `template<class U> optional& operator=(U &&)`
525+
// (where `T` is assignable / constructible from `U`)?
526+
// We recognize this because the number of optionals in the optional being
527+
// assigned to is different from the function argument type.
528+
if (DestTypeOptionalWrappersCount != ArgTypeOptionalWrappersCount)
532529
return State.Env.getBoolLiteralValue(true);
533530

534-
// This is a constructor/assignment call for `optional<T>` with argument of
535-
// type `optional<U>` such that `T` is constructible from `U`.
531+
// Otherwise, this must be a constructor of the form
532+
// `template <class U> optional<optional<U> &&)` / assignment of the form
533+
// `template <class U> optional& operator=(optional<U> &&)
534+
// (where, again, `T` is assignable / constructible from `U`).
536535
auto *Loc = State.Env.get<RecordStorageLocation>(E);
537536
if (auto *HasValueVal = getHasValue(State.Env, Loc))
538537
return *HasValueVal;
@@ -544,10 +543,11 @@ void transferValueOrConversionConstructor(
544543
LatticeTransferState &State) {
545544
assert(E->getNumArgs() > 0);
546545

547-
constructOptionalValue(*E, State.Env,
548-
valueOrConversionHasValue(*E->getConstructor(),
549-
*E->getArg(0), MatchRes,
550-
State));
546+
constructOptionalValue(
547+
*E, State.Env,
548+
valueOrConversionHasValue(
549+
E->getConstructor()->getThisType()->getPointeeType(), *E->getArg(0),
550+
MatchRes, State));
551551
}
552552

553553
void transferAssignment(const CXXOperatorCallExpr *E, BoolValue &HasValueVal,
@@ -566,10 +566,11 @@ void transferValueOrConversionAssignment(
566566
const CXXOperatorCallExpr *E, const MatchFinder::MatchResult &MatchRes,
567567
LatticeTransferState &State) {
568568
assert(E->getNumArgs() > 1);
569-
transferAssignment(E,
570-
valueOrConversionHasValue(*E->getDirectCallee(),
571-
*E->getArg(1), MatchRes, State),
572-
State);
569+
transferAssignment(
570+
E,
571+
valueOrConversionHasValue(E->getArg(0)->getType().getNonReferenceType(),
572+
*E->getArg(1), MatchRes, State),
573+
State);
573574
}
574575

575576
void transferNulloptAssignment(const CXXOperatorCallExpr *E,

clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2798,6 +2798,59 @@ TEST_P(UncheckedOptionalAccessTest, OptionalValueOptional) {
27982798
)");
27992799
}
28002800

2801+
TEST_P(UncheckedOptionalAccessTest, NestedOptionalAssignValue) {
2802+
ExpectDiagnosticsFor(
2803+
R"(
2804+
#include "unchecked_optional_access_test.h"
2805+
2806+
using OptionalInt = $ns::$optional<int>;
2807+
2808+
void target($ns::$optional<OptionalInt> opt) {
2809+
if (!opt) return;
2810+
2811+
// Accessing the outer optional is OK now.
2812+
*opt;
2813+
2814+
// But accessing the nested optional is still unsafe because we haven't
2815+
// checked it.
2816+
**opt; // [[unsafe]]
2817+
2818+
*opt = 1;
2819+
2820+
// Accessing the nested optional is safe after assigning a value to it.
2821+
**opt;
2822+
}
2823+
)");
2824+
}
2825+
2826+
TEST_P(UncheckedOptionalAccessTest, NestedOptionalAssignOptional) {
2827+
ExpectDiagnosticsFor(
2828+
R"(
2829+
#include "unchecked_optional_access_test.h"
2830+
2831+
using OptionalInt = $ns::$optional<int>;
2832+
2833+
void target($ns::$optional<OptionalInt> opt) {
2834+
if (!opt) return;
2835+
2836+
// Accessing the outer optional is OK now.
2837+
*opt;
2838+
2839+
// But accessing the nested optional is still unsafe because we haven't
2840+
// checked it.
2841+
**opt; // [[unsafe]]
2842+
2843+
// Assign from `optional<short>` so that we trigger conversion assignment
2844+
// instead of move assignment.
2845+
*opt = $ns::$optional<short>();
2846+
2847+
// Accessing the nested optional is still unsafe after assigning an empty
2848+
// optional to it.
2849+
**opt; // [[unsafe]]
2850+
}
2851+
)");
2852+
}
2853+
28012854
// Tests that structs can be nested. We use an optional field because its easy
28022855
// to use in a test, but the type of the field shouldn't matter.
28032856
TEST_P(UncheckedOptionalAccessTest, OptionalValueStruct) {
@@ -3443,6 +3496,22 @@ TEST_P(UncheckedOptionalAccessTest, ClassDerivedPrivatelyFromOptional) {
34433496
ast_matchers::hasName("Method"));
34443497
}
34453498

3499+
TEST_P(UncheckedOptionalAccessTest, ClassDerivedFromOptionalValueConstructor) {
3500+
ExpectDiagnosticsFor(R"(
3501+
#include "unchecked_optional_access_test.h"
3502+
3503+
struct Derived : public $ns::$optional<int> {
3504+
Derived(int);
3505+
};
3506+
3507+
void target(Derived opt) {
3508+
*opt; // [[unsafe]]
3509+
opt = 1;
3510+
*opt;
3511+
}
3512+
)");
3513+
}
3514+
34463515
// FIXME: Add support for:
34473516
// - constructors (copy, move)
34483517
// - assignment operators (default, copy, move)

0 commit comments

Comments
 (0)