blob: f146f8ea2b4d7b7c8f6c841a6d3f55de8825e8bc [file] [log] [blame] [edit]
// 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 "nullability_test.h"
#include "pragma_none.h"
#include "pragma_nonnull.h"
namespace std {
template <class T>
struct optional {
bool has_value() const;
T *operator->();
const T *operator->() const;
const T &operator*() const;
const T &value() const;
};
} // namespace std
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