| // Part of the Crubit project, under the Apache License v2.0 with LLVM | |
| // Exceptions. See /LICENSE for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // Tests for function calls. | |
| #include <memory> | |
| #include <optional> | |
| #include "nullability_test.h" | |
| #include "pragma_none.h" | |
| #include "pragma_nonnull.h" | |
| 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(); | |
| }; | |
| } // namespace absl | |
| namespace call_expr_with_pointer_return_type_free_function { | |
| int *_Nonnull makeNonnull(); | |
| int *_Nullable makeNullable(); | |
| int *makeUnannotated(); | |
| TEST void callExprWithPointerReturnTypeFreeFunction() { | |
| nonnull(makeNonnull()); | |
| nullable(makeNullable()); | |
| unknown(makeUnannotated()); | |
| } | |
| } // namespace call_expr_with_pointer_return_type_free_function | |
| namespace call_expr_with_pointer_return_type_member_function { | |
| struct Foo { | |
| int *_Nonnull makeNonnull(); | |
| int *_Nullable makeNullable(); | |
| int *makeUnannotated(); | |
| }; | |
| TEST void callExprWithPointerReturnTypeMemberFunction(Foo foo) { | |
| nonnull(foo.makeNonnull()); | |
| nullable(foo.makeNullable()); | |
| unknown(foo.makeUnannotated()); | |
| } | |
| } // namespace call_expr_with_pointer_return_type_member_function | |
| TEST void callExprWithPointerReturnTypeFunctionPointer( | |
| int *_Nonnull (*makeNonnull)(), int *_Nullable (*makeNullable)(), | |
| int *(*makeUnannotated)()) { | |
| nonnull(makeNonnull()); | |
| nullable(makeNullable()); | |
| unknown(makeUnannotated()); | |
| } | |
| TEST void callExprWithPointerReturnTypePointerToFunctionPointer( | |
| int *_Nonnull (**makeNonnull)(), int *_Nullable (**makeNullable)(), | |
| int *(**makeUnannotated)()) { | |
| nonnull((*makeNonnull)()); | |
| nullable((*makeNullable)()); | |
| unknown((*makeUnannotated)()); | |
| } | |
| namespace call_expr_with_pointer_return_type_function_pointer_nested { | |
| // Function returning a function pointer which returns a pointer. | |
| typedef int *_Nonnull (*MakeNonnullT)(); | |
| typedef int *_Nullable (*MakeNullableT)(); | |
| typedef int *(*MakeUnannotatedT)(); | |
| TEST void callExprWithPointerReturnTypeFunctionPointerNested( | |
| MakeNonnullT (*makeNonnull)(), MakeNullableT (*makeNullable)(), | |
| MakeUnannotatedT (*makeUnannotated)()) { | |
| nonnull((*makeNonnull)()()); | |
| nullable((*makeNullable)()()); | |
| unknown((*makeUnannotated)()()); | |
| } | |
| } // namespace call_expr_with_pointer_return_type_function_pointer_nested | |
| namespace call_expr_with_pointer_return_type_pointer_ref { | |
| // Free function returns reference to pointer. | |
| int *_Nonnull &makeNonnull(); | |
| int *_Nullable &makeNullable(); | |
| int *&makeUnannotated(); | |
| TEST void callExprWithPointerReturnTypePointerRef() { | |
| nonnull(makeNonnull()); | |
| nullable(makeNullable()); | |
| unknown(makeUnannotated()); | |
| // Check that we can take the address of the returned reference and still | |
| // see the correct nullability "behind" the resulting pointer. | |
| type<int *_Nonnull *_Nonnull>(&makeNonnull()); | |
| type<int *_Nullable *_Nonnull>(&makeNullable()); | |
| type<int *_Null_unspecified *_Nonnull>(&makeUnannotated()); | |
| } | |
| } // namespace call_expr_with_pointer_return_type_pointer_ref | |
| namespace call_expr_with_pointer_return_type_in_loop { | |
| // Function called in loop. | |
| int *_Nullable makeNullable(); | |
| bool makeBool(); | |
| TEST void callExprWithPointerReturnTypeInLoop() { | |
| bool first = true; | |
| while (true) { | |
| int *x = makeNullable(); | |
| if (first && x == nullptr) return; | |
| first = false; | |
| nullable(x); | |
| } | |
| } | |
| } // namespace call_expr_with_pointer_return_type_in_loop | |
| namespace output_parameter_basic { | |
| void maybeModifyPtr(int **p); | |
| TEST void outputParameterBasic() { | |
| int *p = nullptr; | |
| maybeModifyPtr(&p); | |
| unknown(p); | |
| } | |
| } // namespace output_parameter_basic | |
| namespace output_parameter_reference { | |
| void maybeModifyPtr(int *&r); | |
| TEST void outputParameterReference() { | |
| int *p = nullptr; | |
| maybeModifyPtr(p); | |
| unknown(p); | |
| } | |
| } // namespace output_parameter_reference | |
| namespace output_parameter_reference_const { | |
| void pointerNotModified(int *const &r); | |
| TEST void outputParameterReferenceConst() { | |
| int *p = nullptr; | |
| pointerNotModified(p); | |
| nullable(p); | |
| } | |
| } // namespace output_parameter_reference_const | |
| namespace output_parameter_reference_pointer_to_pointer { | |
| void maybeModifyPtr(int **&r); | |
| TEST void outputParameterReferencePointerToPointer() { | |
| int **pp = nullptr; | |
| maybeModifyPtr(pp); | |
| unknown(pp); | |
| unknown(*pp); | |
| } | |
| } // namespace output_parameter_reference_pointer_to_pointer | |
| namespace output_parameter_member_pointer_to_pointer { | |
| // This is a crash repro. We don't yet support pointers-to-members -- we just | |
| // care that this doesn't cause the analysis to crash. | |
| struct S { | |
| int *p; | |
| }; | |
| void callee(int *S::*); | |
| TEST void outputParameterMemberPointerToPointer(int *S::*ptr_to_member_ptr) { | |
| callee(ptr_to_member_ptr); | |
| } | |
| } // namespace output_parameter_member_pointer_to_pointer | |
| namespace output_parameter_const { | |
| void pointerNotModified(int *const *p); | |
| TEST void outputParameterConst(int *_Nullable p) { | |
| pointerNotModified(&p); | |
| nullable(p); | |
| } | |
| // The only const qualifier that should be considered is on the inner | |
| // pointer, otherwise we should assume that the pointer may be modified. | |
| void maybeModifyPtr(const int **const p); | |
| TEST void innerPointerNotConst() { | |
| const int *p = nullptr; | |
| maybeModifyPtr(&p); | |
| unknown(p); | |
| } | |
| } // namespace output_parameter_const | |
| namespace output_parameter_nonnull { | |
| void pointerNotModified(int *_Nonnull *p); | |
| TEST void outputParameterNonnull(int *_Nonnull p) { | |
| pointerNotModified(&p); | |
| nonnull(p); | |
| } | |
| } // namespace output_parameter_nonnull | |
| namespace output_parameter_checked_nullable { | |
| void maybeModify(int *_Nullable *p); | |
| TEST void outputParameterCheckedNullable(int *_Nullable p) { | |
| if (!p) return; | |
| maybeModify(&p); | |
| // The analysis comes to the wrong conclusion here -- the pointer is actually | |
| // nullable. | |
| nonnull(p); | |
| } | |
| } // namespace output_parameter_checked_nullable | |
| namespace output_parameter_nullable { | |
| void maybeModifyPtr(int *_Nullable *p); | |
| TEST void outputParameterNullable() { | |
| int *p = nullptr; | |
| maybeModifyPtr(&p); | |
| nullable(p); | |
| } | |
| } // namespace output_parameter_nullable | |
| namespace output_parameter_conditional { | |
| // This tests that flow sensitivity is preserved, to catch for example if the | |
| // underlying pointer was always set to Nonnull once it's passed as an | |
| // output parameter. | |
| void maybeModifyPtr(int **p); | |
| TEST void outputParameterConditional(int *_Nullable j, bool b) { | |
| if (b) { | |
| maybeModifyPtr(&j); | |
| } | |
| if (b) { | |
| unknown(j); | |
| } | |
| if (!b) { | |
| nullable(j); | |
| } | |
| } | |
| } // namespace output_parameter_conditional | |
| namespace output_parameter_without_ampersand_operator { | |
| // This tests that the call to maybeModifyPtr works as expected if the param | |
| // passed in doesn't directly use the & operator | |
| void maybeModifyPtr(int **p); | |
| TEST void outputParameterWithoutAmpersandOperator(int *_Nullable p) { | |
| auto pp = &p; | |
| maybeModifyPtr(pp); | |
| unknown(p); | |
| } | |
| } // namespace output_parameter_without_ampersand_operator | |
| namespace output_parameter_template { | |
| template <typename T> | |
| struct S { | |
| void maybeModify(T &ref); | |
| }; | |
| TEST void outputParameterTemplate(S<int *> s, int *_Nullable p) { | |
| s.maybeModify(p); | |
| unknown(p); | |
| } | |
| TEST void outputParameterTemplateNullable(S<int *_Nullable> s, | |
| int *_Nullable p) { | |
| s.maybeModify(p); | |
| // Doesn't correctly pass on nullability in template argument. | |
| unknown(p); | |
| } | |
| TEST void outputParameterTemplateNonnull(S<int *_Nonnull> s, int *_Nonnull p) { | |
| s.maybeModify(p); | |
| // Doesn't correctly pass on nullability in template argument. | |
| unknown(p); | |
| } | |
| } // namespace output_parameter_template | |
| namespace output_parameter_variadic_callee { | |
| void maybeModifyPtr(int **p, ...); | |
| TEST void outputParameterVariadicCallee() { | |
| int *p = nullptr; | |
| maybeModifyPtr(&p, 0); | |
| unknown(p); | |
| } | |
| } // namespace output_parameter_variadic_callee | |
| namespace output_parameter_member_operator { | |
| struct MaybeModifyPtr { | |
| void operator()(int **p); | |
| }; | |
| TEST void outputParameterMemberOperator() { | |
| int *p = nullptr; | |
| MaybeModifyPtr()(&p); | |
| unknown(p); | |
| } | |
| } // namespace output_parameter_member_operator | |
| namespace can_overwrite_ptr_with_ptr_created_from_ref_return_type { | |
| // Test that if we create a pointer from a function returning a reference, we | |
| // can use that pointer to overwrite an existing nullable pointer and make it | |
| // nonnull. | |
| int &get_int(); | |
| TEST void canOverwritePtrWithPtrCreatedFromRefReturnType(int *_Nullable i) { | |
| i = &get_int(); | |
| nonnull(i); | |
| } | |
| } // namespace can_overwrite_ptr_with_ptr_created_from_ref_return_type | |
| namespace can_overwrite_ptr_with_ptr_returned_by_function { | |
| int *_Nonnull get_int(); | |
| TEST void canOverwritePtrWithPtrReturnedByFunction(int *_Nullable i) { | |
| i = get_int(); | |
| nonnull(i); | |
| } | |
| } // namespace can_overwrite_ptr_with_ptr_returned_by_function | |
| namespace call_member_operator_no_params { | |
| struct MakeNonnull { | |
| int *_Nonnull operator()(); | |
| }; | |
| struct MakeNullable { | |
| int *_Nullable operator()(); | |
| }; | |
| struct MakeUnannotated { | |
| int *operator()(); | |
| }; | |
| TEST void callMemberOperatorNoParams() { | |
| MakeNonnull makeNonnull; | |
| nonnull(makeNonnull()); | |
| MakeNullable makeNullable; | |
| nullable(makeNullable()); | |
| MakeUnannotated makeUnannotated; | |
| unknown(makeUnannotated()); | |
| } | |
| } // namespace call_member_operator_no_params | |
| namespace call_free_operator { | |
| // No nullability involved. This is just a regression test to make sure we can | |
| // process a call to a free overloaded operator. | |
| struct A {}; | |
| A operator+(A, A); | |
| TEST void callFreeOperator() { | |
| A a; | |
| a = a + a; | |
| } | |
| } // namespace call_free_operator | |
| namespace distinguish_function_return_type_and_params { | |
| int *_Nullable callee(int *_Nonnull); | |
| TEST void callExprDistinguishFunctionReturnTypeAndParams() { | |
| int i = 0; | |
| type<int *_Nullable>(callee(&i)); | |
| } | |
| } // namespace distinguish_function_return_type_and_params | |
| namespace distinguish_method_return_type_and_params { | |
| struct S { | |
| int *_Nullable callee(int *_Nonnull); | |
| }; | |
| TEST void distinguishMethodReturnTypeAndParams(S s) { | |
| int i = 0; | |
| type<int *_Nullable>(s.callee(&i)); | |
| } | |
| } // namespace distinguish_method_return_type_and_params | |
| namespace class_template_distinguish_method_return_type_and_params { | |
| template <typename T0, typename T1> | |
| struct S { | |
| T0 callee(T1); | |
| }; | |
| TEST void classTemplateDistinguishMethodReturnTypeAndParams( | |
| S<int *_Nullable, int *_Nonnull> s) { | |
| int i = 0; | |
| type<int *_Nullable>(s.callee(&i)); | |
| } | |
| } // namespace class_template_distinguish_method_return_type_and_params | |
| namespace call_function_template_template_arg_in_return_type_has_null_type_source_info { | |
| // This test sets up a function call where we don't have a `TypeSourceInfo` | |
| // for the argument to a template parameter used in the return type. | |
| // This is a regression test for a crash that we observed on real-world code. | |
| template <class T> | |
| struct A { | |
| using Type = T; | |
| }; | |
| template <int, class T> | |
| typename A<T>::Type f(T); | |
| TEST void callFunctionTemplate_TemplateArgInReturnTypeHasNullTypeSourceInfo() { | |
| f<0>(1); | |
| } | |
| } // namespace | |
| // call_function_template_template_arg_in_return_type_has_null_type_source_info | |
| namespace call_function_template_partially_deduced { | |
| template <int, class T> | |
| T f(T); | |
| TEST void callFunctionTemplate_PartiallyDeduced() { f<0>(1); } | |
| } // namespace call_function_template_partially_deduced | |
| // Crash repro. | |
| TEST void callBuiltinFunction() { __builtin_operator_new(0); } | |
| namespace const_method_no_params_check_first { | |
| struct C { | |
| int *_Nullable property() const { return x; } | |
| int *_Nullable x = nullptr; | |
| }; | |
| TEST void constMethodNoParamsCheckFirst() { | |
| C obj; | |
| if (obj.property() != nullptr) nonnull(obj.property()); | |
| } | |
| } // namespace const_method_no_params_check_first | |
| namespace const_method_no_impl { | |
| struct C { | |
| int *_Nullable property() const; | |
| void may_mutate(); | |
| C &operator=(const C &); | |
| }; | |
| TEST void constMethodNoImpl() { | |
| C obj; | |
| if (obj.property() != nullptr) { | |
| obj.may_mutate(); | |
| nullable(obj.property()); | |
| }; | |
| if (obj.property() != nullptr) { | |
| // A non-const operator call may mutate as well. | |
| obj = C(); | |
| nullable(obj.property()); | |
| }; | |
| if (obj.property() != nullptr) nonnull(obj.property()); | |
| } | |
| } // namespace const_method_no_impl | |
| namespace const_method_returns_reference { | |
| struct C { | |
| int *const _Nullable &property() const { return x; } | |
| int *_Nullable x = nullptr; | |
| }; | |
| TEST void constMethodReturnsReference() { | |
| C obj; | |
| if (obj.property() != nullptr) nonnull(obj.property()); | |
| } | |
| } // namespace const_method_returns_reference | |
| namespace const_method_early_return { | |
| struct C { | |
| int *_Nullable property() const; | |
| }; | |
| TEST void constMethodEarlyReturn() { | |
| C c; | |
| if (!c.property()) return; | |
| // We correctly deduce nonnull here as there is no join. | |
| nonnull(c.property()); | |
| } | |
| } // namespace const_method_early_return | |
| namespace const_method_with_conditional { | |
| struct C { | |
| int *_Nullable property() const; | |
| }; | |
| bool cond(); | |
| void some_operation(int); | |
| TEST void constMethodWithConditional() { | |
| C c; | |
| if (!c.property()) return; | |
| if (cond()) { | |
| some_operation(1); | |
| } else { | |
| some_operation(2); | |
| } | |
| // Verify that we still model `c.property()` as returning the same value | |
| // after the join, i.e. a null check performed before control flow | |
| // diverges is still valid when the paths rejoin. | |
| nonnull(c.property()); | |
| } | |
| } // namespace const_method_with_conditional | |
| namespace const_method_null_check_on_only_one_branch { | |
| struct C { | |
| int *_Nullable property() const; | |
| }; | |
| bool cond(); | |
| TEST void constMethodNullCheckOnOnlyOneBranch() { | |
| C c; | |
| if (cond()) { | |
| if (!c.property()) return; | |
| } | |
| // We didn't check for null on all paths that reach this dereference, so | |
| // the return value is still nullable. | |
| nullable(c.property()); | |
| } | |
| } // namespace const_method_null_check_on_only_one_branch | |
| namespace const_method_conditional_with_separate_null_checks { | |
| struct C { | |
| int *_Nullable property() const; | |
| }; | |
| bool cond(); | |
| TEST void constMethodConditionalWithSeparateNullChecks() { | |
| C c; | |
| if (cond()) { | |
| if (!c.property()) return; | |
| } else { | |
| if (!c.property()) return; | |
| } | |
| // TODO: The analysis reaches a wrong conclusion here: We checked for null on | |
| // all paths that reach this point, but the lattice doesn't join the return | |
| // values we generated for `c.property()` on the two branches, so we don't see | |
| // that the pointer is nonnull. This pattern is likely to be rare in practice, | |
| // so it doesn't seem worth making the join operation more complex to support | |
| // this. | |
| nullable(c.property()); | |
| } | |
| } // namespace const_method_conditional_with_separate_null_checks | |
| namespace const_method_join_loses_information { | |
| struct A { | |
| bool cond() const; | |
| }; | |
| struct C { | |
| int *_Nullable property() const; | |
| A a() const; | |
| }; | |
| TEST void constMethodJoinLosesInformation(const C &c1, const C &c2) { | |
| if (c1.property() == nullptr || c2.property() == nullptr || c2.a().cond()) | |
| return; | |
| nonnull(c1.property()); | |
| // TODO(b/359457439): This is a false positive, caused by a suboptimal CFG | |
| // structure. All of the possible edges out of the if statement's | |
| // condition join in a single CFG block before branching out again to | |
| // the `return` block on the one hand and the block that performs the | |
| // dereferences on the other. | |
| // When we perform the join for the various edges out of the condition, | |
| // we discard the return value for `c2.property()` because in the case | |
| // where `c1.property()` is null, we never evaluate `c2.property()` and | |
| // hence don't have a return value for it. When we call `c2.property()` | |
| // again, we therefore create a fresh return value for it, and we hence | |
| // cannot infer that this value is nonnull. | |
| // The false positive does not occur if `c2.a().cond()` is replaced with | |
| // a simpler condition, e.g. `c2.cond()` (assuming that `cond()` is | |
| // moved to `C`). In this case, the CFG is structured differently: All of | |
| // the edges taken when one of the conditions in the if state is true | |
| // lead directly to the `return` block, and the edge taken when all | |
| // conditions are false leads diresctly to the block that performs the | |
| // dereferences. No join is performed, and we can therefore conclude that | |
| // `c2.property()` is nonnull. | |
| // I am not sure what causes the different CFG structure in the two cases, | |
| // but it may be triggered by the `A` temporary that is returned by `a()`. | |
| nullable(c2.property()); | |
| } | |
| } // namespace const_method_join_loses_information | |
| namespace const_method_no_record_for_call_object { | |
| struct S { | |
| int *_Nullable property() const; | |
| }; | |
| S makeS(); | |
| TEST void constMethodNoRecordForCallObject() { | |
| if (makeS().property()) { | |
| // This is a const member call on a different object, so we can't infer | |
| // anything about the return value of `makeS().property()`. | |
| // But this line and the line above also don't cause any crashes. | |
| nullable(makeS().property()); | |
| } | |
| } | |
| } // namespace const_method_no_record_for_call_object | |
| namespace const_method_returning_bool { | |
| // This tests (indirectly) that we also model const methods returning | |
| // booleans. We use `operator bool()` as the specific const method because | |
| // this then also gives us coverage of this special case (which is quite | |
| // common, for example in `std::function`). | |
| struct S { | |
| operator bool() const; | |
| }; | |
| TEST void constMethodReturningBool(S s) { | |
| int *p = nullptr; | |
| int i = 0; | |
| if (s) p = &i; | |
| if (s) | |
| // We know `p` is nonnull because we know `operator bool()` will return the | |
| // same thing both times. | |
| nonnull(p); | |
| } | |
| } // namespace const_method_returning_bool | |
| namespace const_method_returning_smart_pointer { | |
| struct S { | |
| _Nullable std::shared_ptr<int> property() const; | |
| }; | |
| TEST void constMethodReturningSmartPointer() { | |
| S s; | |
| if (s.property() != nullptr) { | |
| nonnull(s.property()); | |
| } | |
| } | |
| } // namespace const_method_returning_smart_pointer | |
| namespace const_method_returning_smart_pointer_by_reference { | |
| struct S { | |
| const _Nullable std::shared_ptr<int> &property() const; | |
| }; | |
| TEST void constMethodReturningSmartPointerByReference() { | |
| S s; | |
| if (s.property() != nullptr) { | |
| nonnull(s.property()); | |
| } | |
| } | |
| } // namespace const_method_returning_smart_pointer_by_reference | |
| namespace const_operator_returning_pointer { | |
| struct S { | |
| int *_Nullable x; | |
| }; | |
| struct SmartPtr { | |
| S *operator->() const; | |
| void clear(); | |
| }; | |
| TEST void constOperatorReturningPointer() { | |
| SmartPtr ptr; | |
| if (ptr->x != nullptr) { | |
| nonnull(ptr->x); | |
| ptr.clear(); | |
| nullable(ptr->x); | |
| } | |
| } | |
| } // namespace const_operator_returning_pointer | |
| namespace non_const_method_clears_smart_pointer { | |
| struct S { | |
| _Nullable std::shared_ptr<int> property() const; | |
| void writer(); | |
| }; | |
| TEST void nonConstMethodClearsSmartPointer() { | |
| S s; | |
| if (s.property() != nullptr) { | |
| s.writer(); | |
| nullable(s.property()); | |
| } | |
| } | |
| } // namespace non_const_method_clears_smart_pointer | |
| namespace non_const_method_clears_pointer_members { | |
| struct S { | |
| int *_Nullable nullable_p; | |
| int *_Nonnull nonnull_p; | |
| int *unannotated_p; | |
| pragma_none::IntPtr pragma_none_p; | |
| pragma_nonnull::IntPtr pragma_nonnull_p; | |
| void writer(); | |
| }; | |
| TEST void nonConstMethodClearsPointerMembers(S s) { | |
| if (s.nullable_p != nullptr && s.nonnull_p != nullptr && | |
| s.unannotated_p != nullptr && s.pragma_none_p != nullptr && | |
| s.pragma_nonnull_p != nullptr) { | |
| s.writer(); | |
| nullable(s.nullable_p); | |
| nonnull(s.nonnull_p); | |
| unknown(s.unannotated_p); | |
| unknown(s.pragma_none_p); | |
| nonnull(s.pragma_nonnull_p); | |
| } | |
| } | |
| } // namespace non_const_method_clears_pointer_members | |
| namespace non_const_method_does_not_clear_const_pointer_members { | |
| struct S { | |
| int *_Nullable const cp; | |
| void writer(); | |
| }; | |
| TEST void nonConstMethodDoesNotClearConstPointerMembers(S s) { | |
| if (s.cp != nullptr) { | |
| s.writer(); | |
| nonnull(s.cp); | |
| } | |
| } | |
| } // namespace non_const_method_does_not_clear_const_pointer_members | |
| namespace optional_operator_arrow_and_star_call { | |
| // Check that repeated accesses to a pointer behind an optional are considered | |
| // to yield the same pointer -- but only if the optional is not modified in | |
| // the meantime. | |
| struct S { | |
| int *_Nullable p; | |
| }; | |
| TEST void optionalOperatorArrowAndStarCall(std::optional<S> opt1, | |
| std::optional<S> opt2) { | |
| if (!opt1.has_value() || !opt2.has_value()) return; | |
| *opt1->p; // [[unsafe]] | |
| if (opt1->p != nullptr) { | |
| nonnull(opt1->p); | |
| nonnull((*opt1).p); | |
| nonnull(opt1.value().p); | |
| opt1 = opt2; | |
| nullable(opt1->p); | |
| } | |
| } | |
| } // namespace optional_operator_arrow_and_star_call | |
| namespace status_or_operator_arrow_and_star_call { | |
| // Check that repeated accesses to a pointer behind a StatusOr (or similar | |
| // smart pointer-like class) are considered to yield the same pointer -- | |
| // but only if it is not modified in the meantime. | |
| struct S { | |
| int *_Nullable p; | |
| }; | |
| TEST void statusOrOperatorArrowAndStarCall(absl::StatusOr<S> sor1, | |
| absl::StatusOr<S> sor2) { | |
| if (!sor1.ok() || !sor2.ok()) return; | |
| nullable(sor1->p); | |
| if (sor1->p != nullptr) { | |
| nonnull(sor1->p); | |
| nonnull((*sor1).p); | |
| nonnull(sor1.value().p); | |
| sor1 = sor2; | |
| nullable(sor1->p); | |
| } | |
| } | |
| } // namespace status_or_operator_arrow_and_star_call | |
| namespace field_undefined_value { | |
| struct C { | |
| int *_Nullable property() const { return x; } | |
| int *_Nullable x = nullptr; | |
| }; | |
| C foo(); | |
| TEST void fieldUndefinedValue() { | |
| if (foo().x != nullptr) nullable(foo().x); | |
| } | |
| } // namespace field_undefined_value | |
| namespace get_reference_then_call_accessor { | |
| struct C { | |
| int *_Nullable property() const { return x; } | |
| int *_Nullable x = nullptr; | |
| }; | |
| C &foo(); | |
| TEST void getReferenceThenCallAccessor() { | |
| const C &c = foo(); | |
| if (c.property() != nullptr) { | |
| nonnull(c.property()); | |
| } | |
| } | |
| } // namespace get_reference_then_call_accessor | |
| namespace accessor_to_get_reference_then_call_accessor { | |
| struct C { | |
| int *_Nullable property() const { return x; } | |
| int *_Nullable x = nullptr; | |
| }; | |
| struct SmartPtrLike { | |
| C &operator*() const; | |
| C *operator->() const; | |
| C *get() const; | |
| }; | |
| TEST void accessorToGetReferenceThenCallAccessor(SmartPtrLike &d) { | |
| const C &obj = *d; | |
| if (obj.property() != nullptr) { | |
| nonnull(obj.property()); | |
| } | |
| } | |
| } // namespace accessor_to_get_reference_then_call_accessor | |
| namespace get_reference_then_call_accessor_then_get_reference { | |
| struct A { | |
| int *_Nullable x; | |
| }; | |
| struct B { | |
| const A &f; | |
| }; | |
| struct C { | |
| B &nonConstGetRef(); | |
| }; | |
| TEST void getReferenceThenCallAccessorThenGetReference(C c) { | |
| B &b = c.nonConstGetRef(); | |
| if (b.f.x == nullptr) return; | |
| // TODO(b/396431434): `b.f.x` should be nullable here. However we currently | |
| // don't get a storage location for `b.f` when we dynamically create the | |
| // parent storage location for `b` from the `nonConstGetRef` call. Then we | |
| // fail to get nullability properties for `b.f.x`. | |
| nullable(b.f.x); | |
| } | |
| } // namespace get_reference_then_call_accessor_then_get_reference | |
| namespace method_no_params_undefined_value { | |
| struct C { | |
| int *_Nullable property() const { return x; } | |
| int *_Nullable x = nullptr; | |
| }; | |
| TEST void methodNoParamsUndefinedValue() { | |
| if (C().property() != nullptr) { | |
| nullable(C().property()); | |
| } | |
| C obj; | |
| if (obj.property() != nullptr) { | |
| nonnull(obj.property()); | |
| } | |
| } | |
| } // namespace method_no_params_undefined_value | |
| namespace call_pseudo_destructor { | |
| // Repro for assertion failure: | |
| // We used to assert-fail on calls to `CXXPseudoDestructorExpr` because we | |
| // didn't detect that they were "bound member function types" (with which we | |
| // don't associate nullability as they aren't pointers). | |
| using Int = int; | |
| TEST void callPseudoDestructor(Int i) { i.~Int(); } | |
| } // namespace call_pseudo_destructor |