CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
3002 lines
67 KiB
C++
3002 lines
67 KiB
C++
//===- UncheckedOptionalAccessModelTest.cpp -------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
// FIXME: Move this to clang/unittests/Analysis/FlowSensitive/Models.
|
|
|
|
#include "clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h"
|
|
#include "MockHeaders.h"
|
|
#include "TestingSupport.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/ASTMatchers/ASTMatchers.h"
|
|
#include "clang/Basic/SourceLocation.h"
|
|
#include "clang/Frontend/TextDiagnostic.h"
|
|
#include "clang/Tooling/Tooling.h"
|
|
#include "llvm/ADT/DenseMap.h"
|
|
#include "llvm/ADT/DenseSet.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/Support/Error.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
#include <optional>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
using namespace clang;
|
|
using namespace dataflow;
|
|
using namespace test;
|
|
|
|
using ::testing::ContainerEq;
|
|
|
|
/// Replaces all occurrences of `Pattern` in `S` with `Replacement`.
|
|
static void ReplaceAllOccurrences(std::string &S, const std::string &Pattern,
|
|
const std::string &Replacement) {
|
|
size_t Pos = 0;
|
|
while (true) {
|
|
Pos = S.find(Pattern, Pos);
|
|
if (Pos == std::string::npos)
|
|
break;
|
|
S.replace(Pos, Pattern.size(), Replacement);
|
|
}
|
|
}
|
|
|
|
struct OptionalTypeIdentifier {
|
|
std::string NamespaceName;
|
|
std::string TypeName;
|
|
};
|
|
|
|
static raw_ostream &operator<<(raw_ostream &OS,
|
|
const OptionalTypeIdentifier &TypeId) {
|
|
OS << TypeId.NamespaceName << "::" << TypeId.TypeName;
|
|
return OS;
|
|
}
|
|
|
|
class UncheckedOptionalAccessTest
|
|
: public ::testing::TestWithParam<OptionalTypeIdentifier> {
|
|
protected:
|
|
// Check that after running the analysis on SourceCode, it produces the
|
|
// expected diagnostics according to [[unsafe]] annotations.
|
|
// - No annotations => no diagnostics.
|
|
// - Given "// [[unsafe]]" annotations on a line, we expect a diagnostic on
|
|
// that line.
|
|
// - Given "// [[unsafe:range_text]]" annotations on a line, we expect a
|
|
// diagnostic on that line, and we expect the diagnostic Range (printed as
|
|
// a string) to match the "range_text".
|
|
void ExpectDiagnosticsFor(std::string SourceCode,
|
|
bool IgnoreSmartPointerDereference = true) {
|
|
ExpectDiagnosticsFor(SourceCode, ast_matchers::hasName("target"),
|
|
IgnoreSmartPointerDereference);
|
|
}
|
|
|
|
void ExpectDiagnosticsForLambda(std::string SourceCode,
|
|
bool IgnoreSmartPointerDereference = true) {
|
|
ExpectDiagnosticsFor(
|
|
SourceCode,
|
|
ast_matchers::hasDeclContext(
|
|
ast_matchers::cxxRecordDecl(ast_matchers::isLambda())),
|
|
IgnoreSmartPointerDereference);
|
|
}
|
|
|
|
template <typename FuncDeclMatcher>
|
|
void ExpectDiagnosticsFor(std::string SourceCode, FuncDeclMatcher FuncMatcher,
|
|
bool IgnoreSmartPointerDereference = true) {
|
|
// Run in C++17 and C++20 mode to cover differences in the AST between modes
|
|
// (e.g. C++20 can contain `CXXRewrittenBinaryOperator`).
|
|
for (const char *CxxMode : {"-std=c++17", "-std=c++20"})
|
|
ExpectDiagnosticsFor(SourceCode, FuncMatcher, CxxMode,
|
|
IgnoreSmartPointerDereference);
|
|
}
|
|
|
|
template <typename FuncDeclMatcher>
|
|
void ExpectDiagnosticsFor(std::string SourceCode, FuncDeclMatcher FuncMatcher,
|
|
const char *CxxMode,
|
|
bool IgnoreSmartPointerDereference) {
|
|
ReplaceAllOccurrences(SourceCode, "$ns", GetParam().NamespaceName);
|
|
ReplaceAllOccurrences(SourceCode, "$optional", GetParam().TypeName);
|
|
|
|
auto Headers = getMockHeaders();
|
|
Headers.emplace_back("unchecked_optional_access_test.h", R"(
|
|
#include "absl_optional.h"
|
|
#include "base_optional.h"
|
|
#include "std_initializer_list.h"
|
|
#include "std_optional.h"
|
|
#include "std_string.h"
|
|
#include "std_utility.h"
|
|
#include "testing_defs.h"
|
|
|
|
template <typename T>
|
|
T Make();
|
|
)");
|
|
UncheckedOptionalAccessModelOptions Options{IgnoreSmartPointerDereference};
|
|
std::vector<UncheckedOptionalAccessDiagnostic> Diagnostics;
|
|
llvm::Error Error = checkDataflow<UncheckedOptionalAccessModel>(
|
|
AnalysisInputs<UncheckedOptionalAccessModel>(
|
|
SourceCode, std::move(FuncMatcher),
|
|
[](ASTContext &Ctx, Environment &Env) {
|
|
return UncheckedOptionalAccessModel(Ctx, Env);
|
|
})
|
|
.withDiagnosisCallbacks(
|
|
{/*Before=*/[&Diagnostics,
|
|
Diagnoser =
|
|
UncheckedOptionalAccessDiagnoser(Options)](
|
|
ASTContext &Ctx, const CFGElement &Elt,
|
|
const TransferStateForDiagnostics<
|
|
UncheckedOptionalAccessLattice>
|
|
&State) mutable {
|
|
auto EltDiagnostics = Diagnoser(Elt, Ctx, State);
|
|
llvm::move(EltDiagnostics, std::back_inserter(Diagnostics));
|
|
},
|
|
/*After=*/nullptr})
|
|
.withASTBuildArgs(
|
|
{"-fsyntax-only", CxxMode, "-Wno-undefined-inline"})
|
|
.withASTBuildVirtualMappedFiles(
|
|
tooling::FileContentMappings(Headers.begin(), Headers.end())),
|
|
/*VerifyResults=*/[&Diagnostics](
|
|
const llvm::DenseMap<unsigned, std::string>
|
|
&Annotations,
|
|
const AnalysisOutputs &AO) {
|
|
llvm::DenseSet<unsigned> AnnotationLines;
|
|
llvm::DenseMap<unsigned, std::string> AnnotationRangesInLines;
|
|
for (const auto &[Line, AnnotationWithMaybeRange] : Annotations) {
|
|
AnnotationLines.insert(Line);
|
|
auto it = AnnotationWithMaybeRange.find(':');
|
|
if (it != std::string::npos) {
|
|
AnnotationRangesInLines[Line] =
|
|
AnnotationWithMaybeRange.substr(it + 1);
|
|
}
|
|
}
|
|
auto &SrcMgr = AO.ASTCtx.getSourceManager();
|
|
llvm::DenseSet<unsigned> DiagnosticLines;
|
|
for (const UncheckedOptionalAccessDiagnostic &Diag : Diagnostics) {
|
|
unsigned Line = SrcMgr.getPresumedLineNumber(Diag.Range.getBegin());
|
|
DiagnosticLines.insert(Line);
|
|
if (!AnnotationLines.contains(Line)) {
|
|
DiagnosticOptions DiagOpts;
|
|
TextDiagnostic TD(llvm::errs(), AO.ASTCtx.getLangOpts(),
|
|
DiagOpts);
|
|
TD.emitDiagnostic(FullSourceLoc(Diag.Range.getBegin(), SrcMgr),
|
|
DiagnosticsEngine::Error,
|
|
"unexpected diagnostic", {Diag.Range}, {});
|
|
} else {
|
|
auto it = AnnotationRangesInLines.find(Line);
|
|
if (it != AnnotationRangesInLines.end()) {
|
|
EXPECT_EQ(Diag.Range.getAsRange().printToString(SrcMgr),
|
|
it->second);
|
|
}
|
|
}
|
|
}
|
|
|
|
EXPECT_THAT(DiagnosticLines, ContainerEq(AnnotationLines));
|
|
});
|
|
if (Error)
|
|
FAIL() << llvm::toString(std::move(Error));
|
|
}
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
UncheckedOptionalUseTestInst, UncheckedOptionalAccessTest,
|
|
::testing::Values(OptionalTypeIdentifier{"std", "optional"},
|
|
OptionalTypeIdentifier{"absl", "optional"},
|
|
OptionalTypeIdentifier{"base", "Optional"}),
|
|
[](const ::testing::TestParamInfo<OptionalTypeIdentifier> &Info) {
|
|
return Info.param.NamespaceName;
|
|
});
|
|
|
|
// Verifies that similarly-named types are ignored.
|
|
TEST_P(UncheckedOptionalAccessTest, NonTrackedOptionalType) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
namespace other {
|
|
namespace $ns {
|
|
template <typename T>
|
|
struct $optional {
|
|
T value();
|
|
};
|
|
}
|
|
|
|
void target($ns::$optional<int> opt) {
|
|
opt.value();
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, EmptyFunctionBody) {
|
|
ExpectDiagnosticsFor(R"(
|
|
void target() {
|
|
(void)0;
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, UnwrapUsingValueNoCheck) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<int> opt) {
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<int> opt) {
|
|
std::move(opt).value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, UnwrapUsingOperatorStarNoCheck) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<int> opt) {
|
|
*opt; // [[unsafe]]
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<int> opt) {
|
|
*std::move(opt); // [[unsafe]]
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, UnwrapUsingOperatorArrowNoCheck) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {
|
|
void foo();
|
|
};
|
|
|
|
void target($ns::$optional<Foo> opt) {
|
|
opt->foo(); // [[unsafe]]
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {
|
|
void foo();
|
|
};
|
|
|
|
void target($ns::$optional<Foo> opt) {
|
|
std::move(opt)->foo(); // [[unsafe]]
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, HasValueCheck) {
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<int> opt) {
|
|
if (opt.has_value()) {
|
|
opt.value();
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, OperatorBoolCheck) {
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<int> opt) {
|
|
if (opt) {
|
|
opt.value();
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, UnwrapFunctionCallResultNoCheck) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
Make<$ns::$optional<int>>().value(); // [[unsafe]]
|
|
(void)0;
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<int> opt) {
|
|
std::move(opt).value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, DefaultConstructor) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt;
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, NulloptConstructor) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt($ns::nullopt);
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, NulloptConstructorWithSugaredType) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
template <typename T>
|
|
using wrapper = T;
|
|
|
|
template <typename T>
|
|
wrapper<T> wrap(T);
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt(wrap($ns::nullopt));
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, InPlaceConstructor) {
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt($ns::in_place, 3);
|
|
opt.value();
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {};
|
|
|
|
void target() {
|
|
$ns::$optional<Foo> opt($ns::in_place);
|
|
opt.value();
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {
|
|
explicit Foo(int, bool);
|
|
};
|
|
|
|
void target() {
|
|
$ns::$optional<Foo> opt($ns::in_place, 3, false);
|
|
opt.value();
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {
|
|
explicit Foo(std::initializer_list<int>);
|
|
};
|
|
|
|
void target() {
|
|
$ns::$optional<Foo> opt($ns::in_place, {3});
|
|
opt.value();
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ValueConstructor) {
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt(21);
|
|
opt.value();
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt = $ns::$optional<int>(21);
|
|
opt.value();
|
|
}
|
|
)");
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<$ns::$optional<int>> opt(Make<$ns::$optional<int>>());
|
|
opt.value();
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct MyString {
|
|
MyString(const char*);
|
|
};
|
|
|
|
void target() {
|
|
$ns::$optional<MyString> opt("foo");
|
|
opt.value();
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {};
|
|
|
|
struct Bar {
|
|
Bar(const Foo&);
|
|
};
|
|
|
|
void target() {
|
|
$ns::$optional<Bar> opt(Make<Foo>());
|
|
opt.value();
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {
|
|
explicit Foo(int);
|
|
};
|
|
|
|
void target() {
|
|
$ns::$optional<Foo> opt(3);
|
|
opt.value();
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ConvertibleOptionalConstructor) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {};
|
|
|
|
struct Bar {
|
|
Bar(const Foo&);
|
|
};
|
|
|
|
void target() {
|
|
$ns::$optional<Bar> opt(Make<$ns::$optional<Foo>>());
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {};
|
|
|
|
struct Bar {
|
|
explicit Bar(const Foo&);
|
|
};
|
|
|
|
void target() {
|
|
$ns::$optional<Bar> opt(Make<$ns::$optional<Foo>>());
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {};
|
|
|
|
struct Bar {
|
|
Bar(const Foo&);
|
|
};
|
|
|
|
void target() {
|
|
$ns::$optional<Foo> opt1 = $ns::nullopt;
|
|
$ns::$optional<Bar> opt2(opt1);
|
|
opt2.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {};
|
|
|
|
struct Bar {
|
|
Bar(const Foo&);
|
|
};
|
|
|
|
void target() {
|
|
$ns::$optional<Foo> opt1(Make<Foo>());
|
|
$ns::$optional<Bar> opt2(opt1);
|
|
opt2.value();
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {};
|
|
|
|
struct Bar {
|
|
explicit Bar(const Foo&);
|
|
};
|
|
|
|
void target() {
|
|
$ns::$optional<Foo> opt1(Make<Foo>());
|
|
$ns::$optional<Bar> opt2(opt1);
|
|
opt2.value();
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, MakeOptional) {
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt = $ns::make_optional(0);
|
|
opt.value();
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {
|
|
Foo(int, int);
|
|
};
|
|
|
|
void target() {
|
|
$ns::$optional<Foo> opt = $ns::make_optional<Foo>(21, 22);
|
|
opt.value();
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {
|
|
constexpr Foo(std::initializer_list<char>);
|
|
};
|
|
|
|
void target() {
|
|
char a = 'a';
|
|
$ns::$optional<Foo> opt = $ns::make_optional<Foo>({a});
|
|
opt.value();
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ValueOr) {
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt;
|
|
opt.value_or(0);
|
|
(void)0;
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ValueOrComparisonPointers) {
|
|
ExpectDiagnosticsFor(
|
|
R"code(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<int*> opt) {
|
|
if (opt.value_or(nullptr) != nullptr) {
|
|
opt.value();
|
|
} else {
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)code");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ValueOrComparisonIntegers) {
|
|
ExpectDiagnosticsFor(
|
|
R"code(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<int> opt) {
|
|
if (opt.value_or(0) != 0) {
|
|
opt.value();
|
|
} else {
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)code");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ValueOrComparisonStrings) {
|
|
ExpectDiagnosticsFor(
|
|
R"code(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<std::string> opt) {
|
|
if (!opt.value_or("").empty()) {
|
|
opt.value();
|
|
} else {
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)code");
|
|
|
|
ExpectDiagnosticsFor(
|
|
R"code(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<std::string> opt) {
|
|
if (opt.value_or("") != "") {
|
|
opt.value();
|
|
} else {
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)code");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ValueOrComparisonPointerToOptional) {
|
|
// FIXME: make `opt` a parameter directly, once we ensure that all `optional`
|
|
// values have a `has_value` property.
|
|
ExpectDiagnosticsFor(
|
|
R"code(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<int> p) {
|
|
$ns::$optional<int> *opt = &p;
|
|
if (opt->value_or(0) != 0) {
|
|
opt->value();
|
|
} else {
|
|
opt->value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)code");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, Emplace) {
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt;
|
|
opt.emplace(0);
|
|
opt.value();
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<int> *opt) {
|
|
opt->emplace(0);
|
|
opt->value();
|
|
}
|
|
)");
|
|
|
|
// FIXME: Add tests that call `emplace` in conditional branches:
|
|
// ExpectDiagnosticsFor(
|
|
// R"(
|
|
// #include "unchecked_optional_access_test.h"
|
|
//
|
|
// void target($ns::$optional<int> opt, bool b) {
|
|
// if (b) {
|
|
// opt.emplace(0);
|
|
// }
|
|
// if (b) {
|
|
// opt.value();
|
|
// } else {
|
|
// opt.value(); // [[unsafe]]
|
|
// }
|
|
// }
|
|
// )");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, Reset) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt = $ns::make_optional(0);
|
|
opt.reset();
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<int> &opt) {
|
|
if (opt.has_value()) {
|
|
opt.reset();
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)");
|
|
|
|
// FIXME: Add tests that call `reset` in conditional branches:
|
|
// ExpectDiagnosticsFor(
|
|
// R"(
|
|
// #include "unchecked_optional_access_test.h"
|
|
//
|
|
// void target(bool b) {
|
|
// $ns::$optional<int> opt = $ns::make_optional(0);
|
|
// if (b) {
|
|
// opt.reset();
|
|
// }
|
|
// if (b) {
|
|
// opt.value(); // [[unsafe]]
|
|
// } else {
|
|
// opt.value();
|
|
// }
|
|
// }
|
|
// )");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ValueAssignment) {
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {};
|
|
|
|
void target() {
|
|
$ns::$optional<Foo> opt;
|
|
opt = Foo();
|
|
opt.value();
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {};
|
|
|
|
void target() {
|
|
$ns::$optional<Foo> opt;
|
|
(opt = Foo()).value();
|
|
(void)0;
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct MyString {
|
|
MyString(const char*);
|
|
};
|
|
|
|
void target() {
|
|
$ns::$optional<MyString> opt;
|
|
opt = "foo";
|
|
opt.value();
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct MyString {
|
|
MyString(const char*);
|
|
};
|
|
|
|
void target() {
|
|
$ns::$optional<MyString> opt;
|
|
(opt = "foo").value();
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, OptionalConversionAssignment) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {};
|
|
|
|
struct Bar {
|
|
Bar(const Foo&);
|
|
};
|
|
|
|
void target() {
|
|
$ns::$optional<Foo> opt1 = Foo();
|
|
$ns::$optional<Bar> opt2;
|
|
opt2 = opt1;
|
|
opt2.value();
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {};
|
|
|
|
struct Bar {
|
|
Bar(const Foo&);
|
|
};
|
|
|
|
void target() {
|
|
$ns::$optional<Foo> opt1;
|
|
$ns::$optional<Bar> opt2;
|
|
if (opt2.has_value()) {
|
|
opt2 = opt1;
|
|
opt2.value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {};
|
|
|
|
struct Bar {
|
|
Bar(const Foo&);
|
|
};
|
|
|
|
void target() {
|
|
$ns::$optional<Foo> opt1 = Foo();
|
|
$ns::$optional<Bar> opt2;
|
|
(opt2 = opt1).value();
|
|
(void)0;
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, NulloptAssignment) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt = 3;
|
|
opt = $ns::nullopt;
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt = 3;
|
|
(opt = $ns::nullopt).value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, OptionalSwap) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt1 = $ns::nullopt;
|
|
$ns::$optional<int> opt2 = 3;
|
|
|
|
opt1.swap(opt2);
|
|
|
|
opt1.value();
|
|
|
|
opt2.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt1 = $ns::nullopt;
|
|
$ns::$optional<int> opt2 = 3;
|
|
|
|
opt2.swap(opt1);
|
|
|
|
opt1.value();
|
|
|
|
opt2.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, OptionalReturnedFromFuntionCall) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct S {
|
|
$ns::$optional<float> x;
|
|
} s;
|
|
S getOptional() {
|
|
return s;
|
|
}
|
|
|
|
void target() {
|
|
getOptional().x = 0;
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, NonConstMethodMayClearOptionalField) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {
|
|
$ns::$optional<std::string> opt;
|
|
void clear(); // assume this may modify the opt field's state
|
|
};
|
|
|
|
void target(Foo& foo) {
|
|
if (foo.opt) {
|
|
foo.opt.value();
|
|
foo.clear();
|
|
foo.opt.value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest,
|
|
NonConstMethodMayNotClearConstOptionalField) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {
|
|
const $ns::$optional<std::string> opt;
|
|
void clear();
|
|
};
|
|
|
|
void target(Foo& foo) {
|
|
if (foo.opt) {
|
|
foo.opt.value();
|
|
foo.clear();
|
|
foo.opt.value();
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, StdSwap) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt1 = $ns::nullopt;
|
|
$ns::$optional<int> opt2 = 3;
|
|
|
|
std::swap(opt1, opt2);
|
|
|
|
opt1.value();
|
|
|
|
opt2.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt1 = $ns::nullopt;
|
|
$ns::$optional<int> opt2 = 3;
|
|
|
|
std::swap(opt2, opt1);
|
|
|
|
opt1.value();
|
|
|
|
opt2.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, SwapUnmodeledLocLeft) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct L { $ns::$optional<int> hd; L* tl; };
|
|
|
|
void target() {
|
|
$ns::$optional<int> foo = 3;
|
|
L bar;
|
|
|
|
// Any `tl` beyond the first is not modeled.
|
|
bar.tl->tl->hd.swap(foo);
|
|
|
|
bar.tl->tl->hd.value(); // [[unsafe]]
|
|
foo.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, SwapUnmodeledLocRight) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct L { $ns::$optional<int> hd; L* tl; };
|
|
|
|
void target() {
|
|
$ns::$optional<int> foo = 3;
|
|
L bar;
|
|
|
|
// Any `tl` beyond the first is not modeled.
|
|
foo.swap(bar.tl->tl->hd);
|
|
|
|
bar.tl->tl->hd.value(); // [[unsafe]]
|
|
foo.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, SwapUnmodeledValueLeftSet) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct S { int x; };
|
|
struct A { $ns::$optional<S> late; };
|
|
struct B { A f3; };
|
|
struct C { B f2; };
|
|
struct D { C f1; };
|
|
|
|
void target() {
|
|
$ns::$optional<S> foo = S{3};
|
|
D bar;
|
|
|
|
bar.f1.f2.f3.late.swap(foo);
|
|
|
|
bar.f1.f2.f3.late.value();
|
|
foo.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, SwapUnmodeledValueLeftUnset) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct S { int x; };
|
|
struct A { $ns::$optional<S> late; };
|
|
struct B { A f3; };
|
|
struct C { B f2; };
|
|
struct D { C f1; };
|
|
|
|
void target() {
|
|
$ns::$optional<S> foo;
|
|
D bar;
|
|
|
|
bar.f1.f2.f3.late.swap(foo);
|
|
|
|
bar.f1.f2.f3.late.value(); // [[unsafe]]
|
|
foo.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
}
|
|
|
|
// fixme: use recursion instead of depth.
|
|
TEST_P(UncheckedOptionalAccessTest, SwapUnmodeledValueRightSet) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct S { int x; };
|
|
struct A { $ns::$optional<S> late; };
|
|
struct B { A f3; };
|
|
struct C { B f2; };
|
|
struct D { C f1; };
|
|
|
|
void target() {
|
|
$ns::$optional<S> foo = S{3};
|
|
D bar;
|
|
|
|
foo.swap(bar.f1.f2.f3.late);
|
|
|
|
bar.f1.f2.f3.late.value();
|
|
foo.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, SwapUnmodeledValueRightUnset) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct S { int x; };
|
|
struct A { $ns::$optional<S> late; };
|
|
struct B { A f3; };
|
|
struct C { B f2; };
|
|
struct D { C f1; };
|
|
|
|
void target() {
|
|
$ns::$optional<S> foo;
|
|
D bar;
|
|
|
|
foo.swap(bar.f1.f2.f3.late);
|
|
|
|
bar.f1.f2.f3.late.value(); // [[unsafe]]
|
|
foo.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, UniquePtrToOptional) {
|
|
// We suppress diagnostics for optionals in smart pointers (other than
|
|
// `optional` itself).
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
template <typename T>
|
|
struct smart_ptr {
|
|
T& operator*() &;
|
|
T* operator->();
|
|
};
|
|
|
|
void target() {
|
|
smart_ptr<$ns::$optional<bool>> foo;
|
|
foo->value();
|
|
(*foo).value();
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, UniquePtrToStructWithOptionalField) {
|
|
// We suppress diagnostics for optional fields reachable from smart pointers
|
|
// (other than `optional` itself) through (exactly) one member access.
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
template <typename T>
|
|
struct smart_ptr {
|
|
T& operator*() &;
|
|
T* operator->();
|
|
};
|
|
|
|
struct Foo {
|
|
$ns::$optional<int> opt;
|
|
};
|
|
|
|
void target() {
|
|
smart_ptr<Foo> foo;
|
|
*foo->opt;
|
|
*(*foo).opt;
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, CallReturningOptional) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
$ns::$optional<int> MakeOpt();
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt = 0;
|
|
opt = MakeOpt();
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
const $ns::$optional<int>& MakeOpt();
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt = 0;
|
|
opt = MakeOpt();
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
using IntOpt = $ns::$optional<int>;
|
|
IntOpt MakeOpt();
|
|
|
|
void target() {
|
|
IntOpt opt = 0;
|
|
opt = MakeOpt();
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
using IntOpt = $ns::$optional<int>;
|
|
const IntOpt& MakeOpt();
|
|
|
|
void target() {
|
|
IntOpt opt = 0;
|
|
opt = MakeOpt();
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
}
|
|
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, EqualityCheckLeftSet) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt1 = 3;
|
|
$ns::$optional<int> opt2 = Make<$ns::$optional<int>>();
|
|
|
|
if (opt1 == opt2) {
|
|
opt2.value();
|
|
} else {
|
|
opt2.value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, EqualityCheckRightSet) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt1 = 3;
|
|
$ns::$optional<int> opt2 = Make<$ns::$optional<int>>();
|
|
|
|
if (opt2 == opt1) {
|
|
opt2.value();
|
|
} else {
|
|
opt2.value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, EqualityCheckVerifySetAfterEq) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt1 = Make<$ns::$optional<int>>();
|
|
$ns::$optional<int> opt2 = Make<$ns::$optional<int>>();
|
|
|
|
if (opt1 == opt2) {
|
|
if (opt1.has_value())
|
|
opt2.value();
|
|
if (opt2.has_value())
|
|
opt1.value();
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, EqualityCheckLeftUnset) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt1 = $ns::nullopt;
|
|
$ns::$optional<int> opt2 = Make<$ns::$optional<int>>();
|
|
|
|
if (opt1 == opt2) {
|
|
opt2.value(); // [[unsafe]]
|
|
} else {
|
|
opt2.value();
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, EqualityCheckRightUnset) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt1 = $ns::nullopt;
|
|
$ns::$optional<int> opt2 = Make<$ns::$optional<int>>();
|
|
|
|
if (opt2 == opt1) {
|
|
opt2.value(); // [[unsafe]]
|
|
} else {
|
|
opt2.value();
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, EqualityCheckRightNullopt) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt = Make<$ns::$optional<int>>();
|
|
|
|
if (opt == $ns::nullopt) {
|
|
opt.value(); // [[unsafe]]
|
|
} else {
|
|
opt.value();
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, EqualityCheckLeftNullopt) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt = Make<$ns::$optional<int>>();
|
|
|
|
if ($ns::nullopt == opt) {
|
|
opt.value(); // [[unsafe]]
|
|
} else {
|
|
opt.value();
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, EqualityCheckRightValue) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt = Make<$ns::$optional<int>>();
|
|
|
|
if (opt == 3) {
|
|
opt.value();
|
|
} else {
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, EqualityCheckLeftValue) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt = Make<$ns::$optional<int>>();
|
|
|
|
if (3 == opt) {
|
|
opt.value();
|
|
} else {
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, InequalityCheckLeftSet) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt1 = 3;
|
|
$ns::$optional<int> opt2 = Make<$ns::$optional<int>>();
|
|
|
|
if (opt1 != opt2) {
|
|
opt2.value(); // [[unsafe]]
|
|
} else {
|
|
opt2.value();
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, InequalityCheckRightSet) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt1 = 3;
|
|
$ns::$optional<int> opt2 = Make<$ns::$optional<int>>();
|
|
|
|
if (opt2 != opt1) {
|
|
opt2.value(); // [[unsafe]]
|
|
} else {
|
|
opt2.value();
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, InequalityCheckVerifySetAfterEq) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt1 = Make<$ns::$optional<int>>();
|
|
$ns::$optional<int> opt2 = Make<$ns::$optional<int>>();
|
|
|
|
if (opt1 != opt2) {
|
|
if (opt1.has_value())
|
|
opt2.value(); // [[unsafe]]
|
|
if (opt2.has_value())
|
|
opt1.value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, InequalityCheckLeftUnset) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt1 = $ns::nullopt;
|
|
$ns::$optional<int> opt2 = Make<$ns::$optional<int>>();
|
|
|
|
if (opt1 != opt2) {
|
|
opt2.value();
|
|
} else {
|
|
opt2.value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, InequalityCheckRightUnset) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt1 = $ns::nullopt;
|
|
$ns::$optional<int> opt2 = Make<$ns::$optional<int>>();
|
|
|
|
if (opt2 != opt1) {
|
|
opt2.value();
|
|
} else {
|
|
opt2.value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, InequalityCheckRightNullopt) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt = Make<$ns::$optional<int>>();
|
|
|
|
if (opt != $ns::nullopt) {
|
|
opt.value();
|
|
} else {
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, InequalityCheckLeftNullopt) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt = Make<$ns::$optional<int>>();
|
|
|
|
if ($ns::nullopt != opt) {
|
|
opt.value();
|
|
} else {
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, InequalityCheckRightValue) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt = Make<$ns::$optional<int>>();
|
|
|
|
if (opt != 3) {
|
|
opt.value(); // [[unsafe]]
|
|
} else {
|
|
opt.value();
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, InequalityCheckLeftValue) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt = Make<$ns::$optional<int>>();
|
|
|
|
if (3 != opt) {
|
|
opt.value(); // [[unsafe]]
|
|
} else {
|
|
opt.value();
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
// Verifies that the model sees through aliases.
|
|
TEST_P(UncheckedOptionalAccessTest, WithAlias) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
template <typename T>
|
|
using MyOptional = $ns::$optional<T>;
|
|
|
|
void target(MyOptional<int> opt) {
|
|
opt.value(); // [[unsafe]]
|
|
*opt; // [[unsafe]]
|
|
if (opt.has_value()) {
|
|
opt.value();
|
|
*opt;
|
|
}
|
|
if (opt) {
|
|
opt.value();
|
|
*opt;
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, WithAliasThroughPointer) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
template <typename T>
|
|
using MyOptional = $ns::$optional<T>;
|
|
|
|
void target(const MyOptional<int>* opt) {
|
|
opt->value(); // [[unsafe]]
|
|
**opt; // [[unsafe]]
|
|
if (opt->has_value()) {
|
|
opt->value();
|
|
**opt;
|
|
}
|
|
if (*opt) {
|
|
opt->value();
|
|
**opt;
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, OptionalValueOptional) {
|
|
// Basic test that nested values are populated. We nest an optional because
|
|
// its easy to use in a test, but the type of the nested value shouldn't
|
|
// matter.
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
using Foo = $ns::$optional<std::string>;
|
|
|
|
void target($ns::$optional<Foo> foo) {
|
|
if (foo && *foo) {
|
|
foo->value();
|
|
}
|
|
}
|
|
)");
|
|
|
|
// Mutation is supported for nested values.
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
using Foo = $ns::$optional<std::string>;
|
|
|
|
void target($ns::$optional<Foo> foo) {
|
|
if (foo && *foo) {
|
|
foo->reset();
|
|
foo->value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, NestedOptionalAssignValue) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
using OptionalInt = $ns::$optional<int>;
|
|
|
|
void target($ns::$optional<OptionalInt> opt) {
|
|
if (!opt) return;
|
|
|
|
// Accessing the outer optional is OK now.
|
|
*opt;
|
|
|
|
// But accessing the nested optional is still unsafe because we haven't
|
|
// checked it.
|
|
**opt; // [[unsafe]]
|
|
|
|
*opt = 1;
|
|
|
|
// Accessing the nested optional is safe after assigning a value to it.
|
|
**opt;
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, NestedOptionalAssignOptional) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
using OptionalInt = $ns::$optional<int>;
|
|
|
|
void target($ns::$optional<OptionalInt> opt) {
|
|
if (!opt) return;
|
|
|
|
// Accessing the outer optional is OK now.
|
|
*opt;
|
|
|
|
// But accessing the nested optional is still unsafe because we haven't
|
|
// checked it.
|
|
**opt; // [[unsafe]]
|
|
|
|
// Assign from `optional<short>` so that we trigger conversion assignment
|
|
// instead of move assignment.
|
|
*opt = $ns::$optional<short>();
|
|
|
|
// Accessing the nested optional is still unsafe after assigning an empty
|
|
// optional to it.
|
|
**opt; // [[unsafe]]
|
|
}
|
|
)");
|
|
}
|
|
|
|
// Tests that structs can be nested. We use an optional field because its easy
|
|
// to use in a test, but the type of the field shouldn't matter.
|
|
TEST_P(UncheckedOptionalAccessTest, OptionalValueStruct) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {
|
|
$ns::$optional<std::string> opt;
|
|
};
|
|
|
|
void target($ns::$optional<Foo> foo) {
|
|
if (foo && foo->opt) {
|
|
foo->opt.value();
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
// FIXME: A case that we should handle but currently don't.
|
|
// When there is a field of type reference to non-optional, we may
|
|
// stop recursively creating storage locations.
|
|
// E.g., the field `second` below in `pair` should eventually lead to
|
|
// the optional `x` in `A`.
|
|
TEST_P(UncheckedOptionalAccessTest, NestedOptionalThroughNonOptionalRefField) {
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct A {
|
|
$ns::$optional<int> x;
|
|
};
|
|
|
|
struct pair {
|
|
int first;
|
|
const A &second;
|
|
};
|
|
|
|
struct B {
|
|
$ns::$optional<pair>& nonConstGetRef();
|
|
};
|
|
|
|
void target(B b) {
|
|
const auto& maybe_pair = b.nonConstGetRef();
|
|
if (!maybe_pair.has_value())
|
|
return;
|
|
|
|
if(!maybe_pair->second.x.has_value())
|
|
return;
|
|
maybe_pair->second.x.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, OptionalValueInitialization) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
using Foo = $ns::$optional<std::string>;
|
|
|
|
void target($ns::$optional<Foo> foo, bool b) {
|
|
if (!foo.has_value()) return;
|
|
if (b) {
|
|
if (!foo->has_value()) return;
|
|
// We have created `foo.value()`.
|
|
foo->value();
|
|
} else {
|
|
if (!foo->has_value()) return;
|
|
// We have created `foo.value()` again, in a different environment.
|
|
foo->value();
|
|
}
|
|
// Now we merge the two values. UncheckedOptionalAccessModel::merge() will
|
|
// throw away the "value" property.
|
|
foo->value();
|
|
}
|
|
)");
|
|
}
|
|
|
|
// This test is aimed at the core model, not the diagnostic. It is a regression
|
|
// test against a crash when using non-trivial smart pointers, like
|
|
// `std::unique_ptr`. As such, it doesn't test the access itself, which would be
|
|
// ignored regardless because of `IgnoreSmartPointerDereference = true`, above.
|
|
TEST_P(UncheckedOptionalAccessTest, AssignThroughLvalueReferencePtr) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
template <typename T>
|
|
struct smart_ptr {
|
|
typename std::add_lvalue_reference<T>::type operator*() &;
|
|
};
|
|
|
|
void target() {
|
|
smart_ptr<$ns::$optional<int>> x;
|
|
// Verify that this assignment does not crash.
|
|
*x = 3;
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, CorrelatedBranches) {
|
|
ExpectDiagnosticsFor(R"code(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target(bool b, $ns::$optional<int> opt) {
|
|
if (b || opt.has_value()) {
|
|
if (!b) {
|
|
opt.value();
|
|
}
|
|
}
|
|
}
|
|
)code");
|
|
|
|
ExpectDiagnosticsFor(R"code(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target(bool b, $ns::$optional<int> opt) {
|
|
if (b && !opt.has_value()) return;
|
|
if (b) {
|
|
opt.value();
|
|
}
|
|
}
|
|
)code");
|
|
|
|
ExpectDiagnosticsFor(
|
|
R"code(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target(bool b, $ns::$optional<int> opt) {
|
|
if (opt.has_value()) b = true;
|
|
if (b) {
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)code");
|
|
|
|
ExpectDiagnosticsFor(R"code(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target(bool b, $ns::$optional<int> opt) {
|
|
if (b) return;
|
|
if (opt.has_value()) b = true;
|
|
if (b) {
|
|
opt.value();
|
|
}
|
|
}
|
|
)code");
|
|
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target(bool b, $ns::$optional<int> opt) {
|
|
if (opt.has_value() == b) {
|
|
if (b) {
|
|
opt.value();
|
|
}
|
|
}
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target(bool b, $ns::$optional<int> opt) {
|
|
if (opt.has_value() != b) {
|
|
if (!b) {
|
|
opt.value();
|
|
}
|
|
}
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target(bool b) {
|
|
$ns::$optional<int> opt1 = $ns::nullopt;
|
|
$ns::$optional<int> opt2;
|
|
if (b) {
|
|
opt2 = $ns::nullopt;
|
|
} else {
|
|
opt2 = $ns::nullopt;
|
|
}
|
|
if (opt2.has_value()) {
|
|
opt1.value();
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, JoinDistinctValues) {
|
|
ExpectDiagnosticsFor(
|
|
R"code(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target(bool b) {
|
|
$ns::$optional<int> opt;
|
|
if (b) {
|
|
opt = Make<$ns::$optional<int>>();
|
|
} else {
|
|
opt = Make<$ns::$optional<int>>();
|
|
}
|
|
if (opt.has_value()) {
|
|
opt.value();
|
|
} else {
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)code");
|
|
|
|
ExpectDiagnosticsFor(R"code(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target(bool b) {
|
|
$ns::$optional<int> opt;
|
|
if (b) {
|
|
opt = Make<$ns::$optional<int>>();
|
|
if (!opt.has_value()) return;
|
|
} else {
|
|
opt = Make<$ns::$optional<int>>();
|
|
if (!opt.has_value()) return;
|
|
}
|
|
opt.value();
|
|
}
|
|
)code");
|
|
|
|
ExpectDiagnosticsFor(
|
|
R"code(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target(bool b) {
|
|
$ns::$optional<int> opt;
|
|
if (b) {
|
|
opt = Make<$ns::$optional<int>>();
|
|
if (!opt.has_value()) return;
|
|
} else {
|
|
opt = Make<$ns::$optional<int>>();
|
|
}
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
)code");
|
|
|
|
ExpectDiagnosticsFor(
|
|
R"code(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target(bool b) {
|
|
$ns::$optional<int> opt;
|
|
if (b) {
|
|
opt = 1;
|
|
} else {
|
|
opt = 2;
|
|
}
|
|
opt.value();
|
|
}
|
|
)code");
|
|
|
|
ExpectDiagnosticsFor(
|
|
R"code(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target(bool b) {
|
|
$ns::$optional<int> opt;
|
|
if (b) {
|
|
opt = 1;
|
|
} else {
|
|
opt = Make<$ns::$optional<int>>();
|
|
}
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
)code");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, AccessValueInLoop) {
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt = 3;
|
|
while (Make<bool>()) {
|
|
opt.value();
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopWithCheckSafe) {
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt = 3;
|
|
while (Make<bool>()) {
|
|
opt.value();
|
|
|
|
opt = Make<$ns::$optional<int>>();
|
|
if (!opt.has_value()) return;
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopNoCheckUnsafe) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt = 3;
|
|
while (Make<bool>()) {
|
|
opt.value(); // [[unsafe]]
|
|
|
|
opt = Make<$ns::$optional<int>>();
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopToUnsetUnsafe) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt = 3;
|
|
while (Make<bool>())
|
|
opt = $ns::nullopt;
|
|
$ns::$optional<int> opt2 = $ns::nullopt;
|
|
if (opt.has_value())
|
|
opt2 = $ns::$optional<int>(3);
|
|
opt2.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopToSetUnsafe) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt = $ns::nullopt;
|
|
while (Make<bool>())
|
|
opt = $ns::$optional<int>(3);
|
|
$ns::$optional<int> opt2 = $ns::nullopt;
|
|
if (!opt.has_value())
|
|
opt2 = $ns::$optional<int>(3);
|
|
opt2.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopToUnknownUnsafe) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt = $ns::nullopt;
|
|
while (Make<bool>())
|
|
opt = Make<$ns::$optional<int>>();
|
|
$ns::$optional<int> opt2 = $ns::nullopt;
|
|
if (!opt.has_value())
|
|
opt2 = $ns::$optional<int>(3);
|
|
opt2.value(); // [[unsafe]]
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopBadConditionUnsafe) {
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
$ns::$optional<int> opt = 3;
|
|
while (Make<bool>()) {
|
|
opt.value(); // [[unsafe]]
|
|
|
|
opt = Make<$ns::$optional<int>>();
|
|
if (!opt.has_value()) continue;
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, StructuredBindingsFromStruct) {
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct kv { $ns::$optional<int> opt; int x; };
|
|
int target() {
|
|
auto [contents, x] = Make<kv>();
|
|
return contents ? *contents : x;
|
|
}
|
|
)");
|
|
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
template <typename T1, typename T2>
|
|
struct pair { T1 fst; T2 snd; };
|
|
int target() {
|
|
auto [contents, x] = Make<pair<$ns::$optional<int>, int>>();
|
|
return contents ? *contents : x;
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, StructuredBindingsFromTupleLikeType) {
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
namespace std {
|
|
template <class> struct tuple_size;
|
|
template <size_t, class> struct tuple_element;
|
|
template <class...> class tuple;
|
|
|
|
template <class... T>
|
|
struct tuple_size<tuple<T...>> : integral_constant<size_t, sizeof...(T)> {};
|
|
|
|
template <size_t I, class... T>
|
|
struct tuple_element<I, tuple<T...>> {
|
|
using type = __type_pack_element<I, T...>;
|
|
};
|
|
|
|
template <class...> class tuple {};
|
|
template <size_t I, class... T>
|
|
typename tuple_element<I, tuple<T...>>::type get(tuple<T...>);
|
|
} // namespace std
|
|
|
|
std::tuple<$ns::$optional<const char *>, int> get_opt();
|
|
void target() {
|
|
auto [content, ck] = get_opt();
|
|
content ? *content : "";
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, CtorInitializerNullopt) {
|
|
using namespace ast_matchers;
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Target {
|
|
Target(): opt($ns::nullopt) {
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
$ns::$optional<int> opt;
|
|
};
|
|
)",
|
|
cxxConstructorDecl(ofClass(hasName("Target"))));
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, CtorInitializerValue) {
|
|
using namespace ast_matchers;
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Target {
|
|
Target(): opt(3) {
|
|
opt.value();
|
|
}
|
|
$ns::$optional<int> opt;
|
|
};
|
|
)",
|
|
cxxConstructorDecl(ofClass(hasName("Target"))));
|
|
}
|
|
|
|
// This is regression test, it shouldn't crash.
|
|
TEST_P(UncheckedOptionalAccessTest, Bitfield) {
|
|
using namespace ast_matchers;
|
|
ExpectDiagnosticsFor(
|
|
R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
struct Dst {
|
|
unsigned int n : 1;
|
|
};
|
|
void target() {
|
|
$ns::$optional<bool> v;
|
|
Dst d;
|
|
if (v.has_value())
|
|
d.n = v.value();
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, LambdaParam) {
|
|
ExpectDiagnosticsForLambda(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target() {
|
|
[]($ns::$optional<int> opt) {
|
|
if (opt.has_value()) {
|
|
opt.value();
|
|
} else {
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
}(Make<$ns::$optional<int>>());
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, LambdaCaptureByCopy) {
|
|
ExpectDiagnosticsForLambda(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<int> opt) {
|
|
[opt]() {
|
|
if (opt.has_value()) {
|
|
opt.value();
|
|
} else {
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
}();
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, LambdaCaptureByReference) {
|
|
ExpectDiagnosticsForLambda(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<int> opt) {
|
|
[&opt]() {
|
|
if (opt.has_value()) {
|
|
opt.value();
|
|
} else {
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
}();
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, LambdaCaptureWithInitializer) {
|
|
ExpectDiagnosticsForLambda(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<int> opt) {
|
|
[opt2=opt]() {
|
|
if (opt2.has_value()) {
|
|
opt2.value();
|
|
} else {
|
|
opt2.value(); // [[unsafe]]
|
|
}
|
|
}();
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, LambdaCaptureByCopyImplicit) {
|
|
ExpectDiagnosticsForLambda(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<int> opt) {
|
|
[=]() {
|
|
if (opt.has_value()) {
|
|
opt.value();
|
|
} else {
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
}();
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, LambdaCaptureByReferenceImplicit) {
|
|
ExpectDiagnosticsForLambda(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<int> opt) {
|
|
[&]() {
|
|
if (opt.has_value()) {
|
|
opt.value();
|
|
} else {
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
}();
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, LambdaCaptureThis) {
|
|
ExpectDiagnosticsForLambda(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Foo {
|
|
$ns::$optional<int> opt;
|
|
|
|
void target() {
|
|
[this]() {
|
|
if (opt.has_value()) {
|
|
opt.value();
|
|
} else {
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
}();
|
|
}
|
|
};
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, LambdaCaptureStateNotPropagated) {
|
|
// We can't propagate information from the surrounding context.
|
|
ExpectDiagnosticsForLambda(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<int> opt) {
|
|
if (opt.has_value()) {
|
|
[&opt]() {
|
|
opt.value(); // [[unsafe]]
|
|
}();
|
|
}
|
|
}
|
|
)");
|
|
}
|
|
|
|
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.has_value())
|
|
*opt;
|
|
|
|
// The same thing, but with a pointer receiver.
|
|
Derived *popt = &opt;
|
|
**popt; // [[unsafe]]
|
|
if (popt->has_value())
|
|
**popt;
|
|
}
|
|
)");
|
|
}
|
|
|
|
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.has_value())
|
|
*opt;
|
|
|
|
// The same thing, but with a pointer receiver.
|
|
Derived<int> *popt = &opt;
|
|
**popt; // [[unsafe]]
|
|
if (popt->has_value())
|
|
**popt;
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ClassDerivedPrivatelyFromOptional) {
|
|
// Classes that derive privately from optional can themselves still call
|
|
// member functions of optional. Check that we model the optional correctly
|
|
// in this situation.
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Derived : private $ns::$optional<int> {
|
|
void Method() {
|
|
**this; // [[unsafe]]
|
|
if (this->has_value())
|
|
**this;
|
|
}
|
|
};
|
|
)",
|
|
ast_matchers::hasName("Method"));
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ClassDerivedFromOptionalValueConstructor) {
|
|
ExpectDiagnosticsFor(R"(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct Derived : public $ns::$optional<int> {
|
|
Derived(int);
|
|
};
|
|
|
|
void target(Derived opt) {
|
|
*opt; // [[unsafe]]
|
|
opt = 1;
|
|
*opt;
|
|
}
|
|
)");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ConstRefAccessor) {
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct A {
|
|
const $ns::$optional<int>& get() const { return x; }
|
|
$ns::$optional<int> x;
|
|
};
|
|
|
|
void target(A& a) {
|
|
if (a.get().has_value()) {
|
|
a.get().value();
|
|
}
|
|
}
|
|
)cc");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ConstRefAccessorWithModInBetween) {
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct A {
|
|
const $ns::$optional<int>& get() const { return x; }
|
|
void clear();
|
|
$ns::$optional<int> x;
|
|
};
|
|
|
|
void target(A& a) {
|
|
if (a.get().has_value()) {
|
|
a.clear();
|
|
a.get().value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)cc");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ConstRefAccessorWithModReturningOptional) {
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct A {
|
|
const $ns::$optional<int>& get() const { return x; }
|
|
$ns::$optional<int> take();
|
|
$ns::$optional<int> x;
|
|
};
|
|
|
|
void target(A& a) {
|
|
if (a.get().has_value()) {
|
|
$ns::$optional<int> other = a.take();
|
|
a.get().value(); // [[unsafe]]
|
|
if (other.has_value()) {
|
|
other.value();
|
|
}
|
|
}
|
|
}
|
|
)cc");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ConstRefAccessorDifferentObjects) {
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct A {
|
|
const $ns::$optional<int>& get() const { return x; }
|
|
$ns::$optional<int> x;
|
|
};
|
|
|
|
void target(A& a1, A& a2) {
|
|
if (a1.get().has_value()) {
|
|
a2.get().value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)cc");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ConstRefAccessorLoop) {
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct A {
|
|
const $ns::$optional<int>& get() const { return x; }
|
|
$ns::$optional<int> x;
|
|
};
|
|
|
|
void target(A& a, int N) {
|
|
for (int i = 0; i < N; ++i) {
|
|
if (a.get().has_value()) {
|
|
a.get().value();
|
|
}
|
|
}
|
|
}
|
|
)cc");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ConstByValueAccessor) {
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct A {
|
|
$ns::$optional<int> get() const { return x; }
|
|
$ns::$optional<int> x;
|
|
};
|
|
|
|
void target(A& a) {
|
|
if (a.get().has_value()) {
|
|
a.get().value();
|
|
}
|
|
}
|
|
)cc");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ConstByValueAccessorWithModInBetween) {
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct A {
|
|
$ns::$optional<int> get() const { return x; }
|
|
void clear();
|
|
$ns::$optional<int> x;
|
|
};
|
|
|
|
void target(A& a) {
|
|
if (a.get().has_value()) {
|
|
a.clear();
|
|
a.get().value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)cc");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ConstPointerAccessor) {
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct A {
|
|
$ns::$optional<int> x;
|
|
};
|
|
|
|
struct MyUniquePtr {
|
|
A* operator->() const;
|
|
};
|
|
|
|
void target(MyUniquePtr p) {
|
|
if (p->x) {
|
|
*p->x;
|
|
}
|
|
}
|
|
)cc",
|
|
/*IgnoreSmartPointerDereference=*/false);
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ConstPointerAccessorWithModInBetween) {
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct A {
|
|
$ns::$optional<int> x;
|
|
};
|
|
|
|
struct MyUniquePtr {
|
|
A* operator->() const;
|
|
void reset(A*);
|
|
};
|
|
|
|
void target(MyUniquePtr p) {
|
|
if (p->x) {
|
|
p.reset(nullptr);
|
|
*p->x; // [[unsafe]]
|
|
}
|
|
}
|
|
)cc",
|
|
/*IgnoreSmartPointerDereference=*/false);
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, SmartPointerAccessorMixed) {
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct A {
|
|
$ns::$optional<int> x;
|
|
};
|
|
|
|
namespace absl {
|
|
template<typename T>
|
|
class StatusOr {
|
|
public:
|
|
bool ok() const;
|
|
|
|
const T& operator*() const&;
|
|
T& operator*() &;
|
|
|
|
const T* operator->() const;
|
|
T* operator->();
|
|
|
|
const T& value() const;
|
|
T& value();
|
|
};
|
|
}
|
|
|
|
void target(absl::StatusOr<A> &mut, const absl::StatusOr<A> &imm) {
|
|
if (!mut.ok() || !imm.ok())
|
|
return;
|
|
|
|
if (mut->x.has_value()) {
|
|
mut->x.value();
|
|
((*mut).x).value();
|
|
(mut.value().x).value();
|
|
|
|
// check flagged after modifying
|
|
mut = imm;
|
|
mut->x.value(); // [[unsafe]]
|
|
}
|
|
if (imm->x.has_value()) {
|
|
imm->x.value();
|
|
((*imm).x).value();
|
|
(imm.value().x).value();
|
|
}
|
|
}
|
|
)cc",
|
|
/*IgnoreSmartPointerDereference=*/false);
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ConstBoolAccessor) {
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct A {
|
|
bool isFoo() const { return f; }
|
|
bool f;
|
|
};
|
|
|
|
void target(A& a) {
|
|
$ns::$optional<int> opt;
|
|
if (a.isFoo()) {
|
|
opt = 1;
|
|
}
|
|
if (a.isFoo()) {
|
|
opt.value();
|
|
}
|
|
}
|
|
)cc");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ConstBoolAccessorWithModInBetween) {
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct A {
|
|
bool isFoo() const { return f; }
|
|
void clear();
|
|
bool f;
|
|
};
|
|
|
|
void target(A& a) {
|
|
$ns::$optional<int> opt;
|
|
if (a.isFoo()) {
|
|
opt = 1;
|
|
}
|
|
a.clear();
|
|
if (a.isFoo()) {
|
|
opt.value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)cc");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest,
|
|
ConstRefAccessorToOptionalViaConstRefAccessorToHoldingObject) {
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct A {
|
|
const $ns::$optional<int>& get() const { return x; }
|
|
|
|
$ns::$optional<int> x;
|
|
};
|
|
|
|
struct B {
|
|
const A& getA() const { return a; }
|
|
|
|
A a;
|
|
};
|
|
|
|
void target(B& b) {
|
|
if (b.getA().get().has_value()) {
|
|
b.getA().get().value();
|
|
}
|
|
}
|
|
)cc");
|
|
}
|
|
|
|
TEST_P(
|
|
UncheckedOptionalAccessTest,
|
|
ConstRefAccessorToOptionalViaConstRefAccessorToHoldingObjectWithoutValueCheck) {
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct A {
|
|
const $ns::$optional<int>& get() const { return x; }
|
|
|
|
$ns::$optional<int> x;
|
|
};
|
|
|
|
struct B {
|
|
const A& getA() const { return a; }
|
|
|
|
A a;
|
|
};
|
|
|
|
void target(B& b) {
|
|
b.getA().get().value(); // [[unsafe]]
|
|
}
|
|
)cc");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest,
|
|
ConstRefToOptionalSavedAsTemporaryVariable) {
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct A {
|
|
const $ns::$optional<int>& get() const { return x; }
|
|
|
|
$ns::$optional<int> x;
|
|
};
|
|
|
|
struct B {
|
|
const A& getA() const { return a; }
|
|
|
|
A a;
|
|
};
|
|
|
|
void target(B& b) {
|
|
const auto& opt = b.getA().get();
|
|
if (opt.has_value()) {
|
|
opt.value();
|
|
}
|
|
}
|
|
)cc");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest,
|
|
ConstRefAccessorToOptionalViaAccessorToHoldingObjectByValue) {
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct A {
|
|
const $ns::$optional<int>& get() const { return x; }
|
|
|
|
$ns::$optional<int> x;
|
|
};
|
|
|
|
struct B {
|
|
const A copyA() const { return a; }
|
|
|
|
A a;
|
|
};
|
|
|
|
void target(B& b) {
|
|
if (b.copyA().get().has_value()) {
|
|
b.copyA().get().value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)cc");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest,
|
|
ConstRefAccessorToOptionalViaNonConstRefAccessorToHoldingObject) {
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct A {
|
|
const $ns::$optional<int>& get() const { return x; }
|
|
|
|
$ns::$optional<int> x;
|
|
};
|
|
|
|
struct B {
|
|
A& getA() { return a; }
|
|
|
|
A a;
|
|
};
|
|
|
|
void target(B& b) {
|
|
if (b.getA().get().has_value()) {
|
|
b.getA().get().value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)cc");
|
|
}
|
|
|
|
TEST_P(
|
|
UncheckedOptionalAccessTest,
|
|
ConstRefAccessorToOptionalViaConstRefAccessorToHoldingObjectWithModAfterCheck) {
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct A {
|
|
const $ns::$optional<int>& get() const { return x; }
|
|
|
|
$ns::$optional<int> x;
|
|
};
|
|
|
|
struct B {
|
|
const A& getA() const { return a; }
|
|
|
|
A& getA() { return a; }
|
|
|
|
void clear() { a = A{}; }
|
|
|
|
A a;
|
|
};
|
|
|
|
void target(B& b) {
|
|
// changing field A via non-const getter after const getter check
|
|
if (b.getA().get().has_value()) {
|
|
b.getA() = A{};
|
|
b.getA().get().value(); // [[unsafe]]
|
|
}
|
|
|
|
// calling non-const method which might change field A
|
|
if (b.getA().get().has_value()) {
|
|
b.clear();
|
|
b.getA().get().value(); // [[unsafe]]
|
|
}
|
|
}
|
|
)cc");
|
|
}
|
|
|
|
TEST_P(
|
|
UncheckedOptionalAccessTest,
|
|
ConstRefAccessorToOptionalViaConstRefAccessorToHoldingObjectWithAnotherConstCallAfterCheck) {
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct A {
|
|
const $ns::$optional<int>& get() const { return x; }
|
|
|
|
$ns::$optional<int> x;
|
|
};
|
|
|
|
struct B {
|
|
const A& getA() const { return a; }
|
|
|
|
void callWithoutChanges() const {
|
|
// no-op
|
|
}
|
|
|
|
A a;
|
|
};
|
|
|
|
void target(B& b) {
|
|
if (b.getA().get().has_value()) {
|
|
b.callWithoutChanges(); // calling const method which cannot change A
|
|
b.getA().get().value();
|
|
}
|
|
}
|
|
)cc");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, ConstPointerRefAccessor) {
|
|
// A crash reproducer for https://github.com/llvm/llvm-project/issues/125589
|
|
// NOTE: we currently cache const ref accessors's locations.
|
|
// If we want to support const ref to pointers or bools, we should initialize
|
|
// their values.
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct A {
|
|
$ns::$optional<int> x;
|
|
};
|
|
|
|
struct PtrWrapper {
|
|
A*& getPtrRef() const;
|
|
void reset(A*);
|
|
};
|
|
|
|
void target(PtrWrapper p) {
|
|
if (p.getPtrRef()->x) {
|
|
*p.getPtrRef()->x; // [[unsafe]]
|
|
p.reset(nullptr);
|
|
*p.getPtrRef()->x; // [[unsafe]]
|
|
}
|
|
}
|
|
)cc",
|
|
/*IgnoreSmartPointerDereference=*/false);
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, DiagnosticsHaveRanges) {
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
struct A {
|
|
$ns::$optional<int> fi;
|
|
};
|
|
struct B {
|
|
$ns::$optional<A> fa;
|
|
};
|
|
|
|
void target($ns::$optional<B> opt) {
|
|
opt.value(); // [[unsafe:<input.cc:12:7>]]
|
|
if (opt) {
|
|
opt // [[unsafe:<input.cc:14:9, line:16:13>]]
|
|
->
|
|
fa.value();
|
|
if (opt->fa) {
|
|
opt->fa->fi.value(); // [[unsafe:<input.cc:18:11, col:20>]]
|
|
}
|
|
}
|
|
}
|
|
)cc");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, AssertTrueGtestMacro) {
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<int> opt) {
|
|
ASSERT_TRUE(opt.has_value());
|
|
EXPECT_EQ(opt.value(), 42);
|
|
|
|
opt.reset();
|
|
EXPECT_EQ(opt.value(), 42); // [[unsafe]]
|
|
ASSERT_TRUE(opt.value()); // [[unsafe]]
|
|
}
|
|
)cc");
|
|
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<int> opt) {
|
|
ASSERT_TRUE(opt);
|
|
EXPECT_EQ(*opt, 42);
|
|
}
|
|
)cc");
|
|
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<int> opt) {
|
|
ASSERT_FALSE(opt.has_value());
|
|
EXPECT_EQ(opt.value(), 42); // [[unsafe]]
|
|
}
|
|
)cc");
|
|
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
void target($ns::$optional<int> opt) {
|
|
EXPECT_TRUE(opt.has_value());
|
|
EXPECT_EQ(opt.value(), 42); // [[unsafe]]
|
|
|
|
EXPECT_TRUE(opt);
|
|
EXPECT_EQ(*opt, 42); // [[unsafe]]
|
|
}
|
|
)cc");
|
|
}
|
|
|
|
TEST_P(UncheckedOptionalAccessTest, AssertFalseGtestMacroWithNullableValue) {
|
|
ExpectDiagnosticsFor(R"cc(
|
|
#include "unchecked_optional_access_test.h"
|
|
|
|
namespace BloombergLP::bdlb {
|
|
template <typename T>
|
|
struct NullableValue : $ns::$optional<T> {
|
|
bool isNull() const;
|
|
};
|
|
}
|
|
|
|
void target(BloombergLP::bdlb::NullableValue<int> opt) {
|
|
ASSERT_TRUE(opt.isNull());
|
|
EXPECT_EQ(*opt, 42); // [[unsafe]]
|
|
|
|
ASSERT_FALSE(opt.isNull());
|
|
EXPECT_EQ(*opt, 42);
|
|
}
|
|
)cc");
|
|
}
|
|
|
|
// FIXME: Add support for:
|
|
// - constructors (copy, move)
|
|
// - assignment operators (default, copy, move)
|
|
// - invalidation (passing optional by non-const reference/pointer)
|