Skip to content

Commit

Permalink
Add null violation checks for std::unique_ptr, std::shared_ptr, `…
Browse files Browse the repository at this point in the history
…std::optional` and `std::expected` to prevent UB (hsutter#945)

* `assert_not_null` now verifies std::unique_ptr, std::shared_ptr, std::optional and std::expected in addition to the original null pointer check

These states are considered to be "null" and will result in a violation error:

- std::unique_ptr that owns nothing
- std::shared_ptr with no managed object
- std::optional with no value
- std::expected containing an unexpected value

* Update test-results for clang & gcc

* Update some more clang & gcc test results
  • Loading branch information
bluetarpmedia authored Jan 17, 2024
1 parent c902f2b commit 96a4abc
Show file tree
Hide file tree
Showing 57 changed files with 502 additions and 57 deletions.
61 changes: 56 additions & 5 deletions include/cpp2util.h
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,9 @@
#ifndef CPP2_NO_EXCEPTIONS
#include <exception>
#endif
#ifdef __cpp_lib_expected
#include <expected>
#endif
#if defined(__cpp_lib_format) || (defined(_MSC_VER) && _MSC_VER >= 1929)
#include <format>
#endif
Expand Down Expand Up @@ -451,19 +454,67 @@ auto inline Testing = contract_group(
);


// Null pointer deref checking
// Check for invalid dereference or indirection which would result in undefined behavior.
//
// - Null pointer
// - std::unique_ptr that owns nothing
// - std::shared_ptr with no managed object
// - std::optional with no value
// - std::expected containing an unexpected value
//
// Note: For naming simplicity we consider all the above cases to be "null" states so that
// we can write: `*assert_not_null(object)`.
//
auto assert_not_null(auto&& p CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> decltype(auto)
template<typename T>
concept UniquePtr = std::is_same_v<T, std::unique_ptr<typename T::element_type, typename T::deleter_type>>;

template<typename T>
concept SharedPtr = std::is_same_v<T, std::shared_ptr<typename T::element_type>>;

template<typename T>
concept Optional = std::is_same_v<T, std::optional<typename T::value_type>>;

#ifdef __cpp_lib_expected

template<typename T>
concept Expected = std::is_same_v<T, std::expected<typename T::value_type, typename T::error_type>>;

#endif

auto assert_not_null(auto&& arg CPP2_SOURCE_LOCATION_PARAM_WITH_DEFAULT) -> decltype(auto)
{
// NOTE: This "!= T{}" test may or may not work for STL iterators. The standard
// doesn't guarantee that using == and != will reliably report whether an
// STL iterator has the default-constructed value. So use it only for raw *...
if constexpr (std::is_pointer_v<CPP2_TYPEOF(p)>) {
if (p == CPP2_TYPEOF(p){}) {
if constexpr (std::is_pointer_v<CPP2_TYPEOF(arg)>) {
if (arg == CPP2_TYPEOF(arg){}) {
Null.report_violation("dynamic null dereference attempt detected" CPP2_SOURCE_LOCATION_ARG);
};
}
return CPP2_FORWARD(p);
else if constexpr (UniquePtr<CPP2_TYPEOF(arg)>) {
if (!arg) {
Null.report_violation("std::unique_ptr is empty" CPP2_SOURCE_LOCATION_ARG);
}
}
else if constexpr (SharedPtr<CPP2_TYPEOF(arg)>) {
if (!arg) {
Null.report_violation("std::shared_ptr is empty" CPP2_SOURCE_LOCATION_ARG);
}
}
else if constexpr (Optional<CPP2_TYPEOF(arg)>) {
if (!arg.has_value()) {
Null.report_violation("std::optional does not contain a value" CPP2_SOURCE_LOCATION_ARG);
}
}
#ifdef __cpp_lib_expected
else if constexpr (Expected<CPP2_TYPEOF(arg)>) {
if (!arg.has_value()) {
Null.report_violation("std::expected has an unexpected value" CPP2_SOURCE_LOCATION_ARG);
}
}
#endif

return CPP2_FORWARD(arg);
}

// Subscript bounds checking
Expand Down
21 changes: 21 additions & 0 deletions regression-tests/pure2-assert-expected-not-null.cpp2
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

fine: () -> int =
{
up:= unique.new<int>(1);
sp:= shared.new<int>(2);
op: std::optional<int> = (3);
ex: std::expected<int, bool> = (4);

return up* + sp* + op* + ex*;
}

bad_expected_access: () -> int =
{
ex: std::expected<int, bool> = std::unexpected(false);
return ex*;
}

main: () -> int =
{
return fine() + bad_expected_access();
}
20 changes: 20 additions & 0 deletions regression-tests/pure2-assert-optional-not-null.cpp2
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

fine: () -> int =
{
up:= unique.new<int>(1);
sp:= shared.new<int>(2);
op: std::optional<int> = (3);

return up* + sp* + op*;
}

bad_optional_access: () -> int =
{
op: std::optional<int> = std::nullopt;
return op*;
}

main: () -> int =
{
return fine() + bad_optional_access();
}
21 changes: 21 additions & 0 deletions regression-tests/pure2-assert-shared-ptr-not-null.cpp2
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

fine: () -> int =
{
up:= unique.new<int>(1);
sp:= shared.new<int>(2);
op: std::optional<int> = (3);

return up* + sp* + op*;
}

bad_shared_ptr_access: () -> int =
{
sp:= std::make_shared<int>(1);
sp.reset();
return sp*;
}

main: () -> int =
{
return fine() + bad_shared_ptr_access();
}
21 changes: 21 additions & 0 deletions regression-tests/pure2-assert-unique-ptr-not-null.cpp2
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

fine: () -> int =
{
up:= unique.new<int>(1);
sp:= shared.new<int>(2);
op: std::optional<int> = (3);

return up* + sp* + op*;
}

bad_unique_ptr_access: () -> int =
{
up:= std::make_unique<int>(1);
up.reset();
return up*;
}

main: () -> int =
{
return fine() + bad_unique_ptr_access();
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
pure2-assert-expected-not-null.cpp2:7:10: error: no member named 'expected' in namespace 'std'
std::expected<int,bool> ex {4};
~~~~~^
pure2-assert-expected-not-null.cpp2:7:22: error: expected '(' for function-style cast or type construction
std::expected<int,bool> ex {4};
~~~^
pure2-assert-expected-not-null.cpp2:9:165: error: use of undeclared identifier 'ex'
return *cpp2::assert_not_null(std::move(up)) + *cpp2::assert_not_null(std::move(sp)) + *cpp2::assert_not_null(std::move(op)) + *cpp2::assert_not_null(std::move(ex));
^
pure2-assert-expected-not-null.cpp2:14:10: error: no member named 'expected' in namespace 'std'
std::expected<int,bool> ex {std::unexpected(false)};
~~~~~^
pure2-assert-expected-not-null.cpp2:14:22: error: expected '(' for function-style cast or type construction
std::expected<int,bool> ex {std::unexpected(false)};
~~~^
pure2-assert-expected-not-null.cpp2:15:45: error: use of undeclared identifier 'ex'
return *cpp2::assert_not_null(std::move(ex));
^
6 errors generated.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Null safety violation: std::optional does not contain a value
libc++abi: terminating
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Null safety violation: std::shared_ptr is empty
libc++abi: terminating
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Null safety violation: std::unique_ptr is empty
libc++abi: terminating
Empty file.
Original file line number Diff line number Diff line change
@@ -1,118 +1,118 @@
mixed-bugfix-for-ufcs-non-local.cpp2:13:12: error: a lambda expression cannot appear in this context
template<t<CPP2_UFCS_NONLOCAL(f)(o)> UnnamedTypeParam1_1> bool inline constexpr v0 = false;// Fails on GCC ([GCC109781][]) and Clang 12 (a lambda expression cannot appear in this context)
^
../../../include/cpp2util.h:885:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
../../../include/cpp2util.h:936:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
#define CPP2_UFCS_NONLOCAL(...) CPP2_UFCS_(,(),,__VA_ARGS__)
^
../../../include/cpp2util.h:866:53: note: expanded from macro 'CPP2_UFCS_'
../../../include/cpp2util.h:917:53: note: expanded from macro 'CPP2_UFCS_'
#define CPP2_UFCS_(LAMBDADEFCAPT,QUALID,TEMPKW,...) \
^
mixed-bugfix-for-ufcs-non-local.cpp2:15:3: error: a lambda expression cannot appear in this context
t<CPP2_UFCS_NONLOCAL(f)(o)> inline constexpr v1 = t<true>();// Fails on Clang 12 (lambda in unevaluated context).
^
../../../include/cpp2util.h:885:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
../../../include/cpp2util.h:936:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
#define CPP2_UFCS_NONLOCAL(...) CPP2_UFCS_(,(),,__VA_ARGS__)
^
../../../include/cpp2util.h:866:53: note: expanded from macro 'CPP2_UFCS_'
../../../include/cpp2util.h:917:53: note: expanded from macro 'CPP2_UFCS_'
#define CPP2_UFCS_(LAMBDADEFCAPT,QUALID,TEMPKW,...) \
^
mixed-bugfix-for-ufcs-non-local.cpp2:21:12: error: a lambda expression cannot appear in this context
template<t<CPP2_UFCS_NONLOCAL(f)(o)> UnnamedTypeParam1_2> auto g() -> void;
^
../../../include/cpp2util.h:885:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
../../../include/cpp2util.h:936:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
#define CPP2_UFCS_NONLOCAL(...) CPP2_UFCS_(,(),,__VA_ARGS__)
^
../../../include/cpp2util.h:866:53: note: expanded from macro 'CPP2_UFCS_'
../../../include/cpp2util.h:917:53: note: expanded from macro 'CPP2_UFCS_'
#define CPP2_UFCS_(LAMBDADEFCAPT,QUALID,TEMPKW,...) \
^
mixed-bugfix-for-ufcs-non-local.cpp2:23:36: error: a lambda expression cannot appear in this context
auto g([[maybe_unused]] cpp2::in<t<CPP2_UFCS_NONLOCAL(f)(o)>> unnamed_param_1) -> void;
^
../../../include/cpp2util.h:885:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
../../../include/cpp2util.h:936:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
#define CPP2_UFCS_NONLOCAL(...) CPP2_UFCS_(,(),,__VA_ARGS__)
^
../../../include/cpp2util.h:866:53: note: expanded from macro 'CPP2_UFCS_'
../../../include/cpp2util.h:917:53: note: expanded from macro 'CPP2_UFCS_'
#define CPP2_UFCS_(LAMBDADEFCAPT,QUALID,TEMPKW,...) \
^
mixed-bugfix-for-ufcs-non-local.cpp2:27:29: error: a lambda expression cannot appear in this context
[[nodiscard]] auto h() -> t<CPP2_UFCS_NONLOCAL(f)(o)>;
^
../../../include/cpp2util.h:885:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
../../../include/cpp2util.h:936:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
#define CPP2_UFCS_NONLOCAL(...) CPP2_UFCS_(,(),,__VA_ARGS__)
^
../../../include/cpp2util.h:866:53: note: expanded from macro 'CPP2_UFCS_'
../../../include/cpp2util.h:917:53: note: expanded from macro 'CPP2_UFCS_'
#define CPP2_UFCS_(LAMBDADEFCAPT,QUALID,TEMPKW,...) \
^
mixed-bugfix-for-ufcs-non-local.cpp2:31:12: error: a lambda expression cannot appear in this context
template<t<CPP2_UFCS_NONLOCAL(f)(o)> UnnamedTypeParam1_3> using a = bool;// Fails on GCC ([GCC109781][]) and Clang 12 (a lambda expression cannot appear in this context)
^
../../../include/cpp2util.h:885:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
../../../include/cpp2util.h:936:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
#define CPP2_UFCS_NONLOCAL(...) CPP2_UFCS_(,(),,__VA_ARGS__)
^
../../../include/cpp2util.h:866:53: note: expanded from macro 'CPP2_UFCS_'
../../../include/cpp2util.h:917:53: note: expanded from macro 'CPP2_UFCS_'
#define CPP2_UFCS_(LAMBDADEFCAPT,QUALID,TEMPKW,...) \
^
mixed-bugfix-for-ufcs-non-local.cpp2:33:12: error: a lambda expression cannot appear in this context
template<t<CPP2_UFCS_NONLOCAL(f)(o)> UnnamedTypeParam1_4> auto inline constexpr b = false;// Fails on GCC ([GCC109781][]).
^
../../../include/cpp2util.h:885:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
../../../include/cpp2util.h:936:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
#define CPP2_UFCS_NONLOCAL(...) CPP2_UFCS_(,(),,__VA_ARGS__)
^
../../../include/cpp2util.h:866:53: note: expanded from macro 'CPP2_UFCS_'
../../../include/cpp2util.h:917:53: note: expanded from macro 'CPP2_UFCS_'
#define CPP2_UFCS_(LAMBDADEFCAPT,QUALID,TEMPKW,...) \
^
mixed-bugfix-for-ufcs-non-local.cpp2:35:13: error: a lambda expression cannot appear in this context
using c = t<CPP2_UFCS_NONLOCAL(f)(o)>;// Fails on Clang 12 (lambda in unevaluated context) and Clang 12 (a lambda expression cannot appear in this context)
^
../../../include/cpp2util.h:885:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
../../../include/cpp2util.h:936:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
#define CPP2_UFCS_NONLOCAL(...) CPP2_UFCS_(,(),,__VA_ARGS__)
^
../../../include/cpp2util.h:866:53: note: expanded from macro 'CPP2_UFCS_'
../../../include/cpp2util.h:917:53: note: expanded from macro 'CPP2_UFCS_'
#define CPP2_UFCS_(LAMBDADEFCAPT,QUALID,TEMPKW,...) \
^
mixed-bugfix-for-ufcs-non-local.cpp2:37:29: error: a lambda expression cannot appear in this context
auto inline constexpr d = t<CPP2_UFCS_NONLOCAL(f)(o)>();// Fails on Clang 12 (lambda in unevaluated context).
^
../../../include/cpp2util.h:885:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
../../../include/cpp2util.h:936:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
#define CPP2_UFCS_NONLOCAL(...) CPP2_UFCS_(,(),,__VA_ARGS__)
^
../../../include/cpp2util.h:866:53: note: expanded from macro 'CPP2_UFCS_'
../../../include/cpp2util.h:917:53: note: expanded from macro 'CPP2_UFCS_'
#define CPP2_UFCS_(LAMBDADEFCAPT,QUALID,TEMPKW,...) \
^
mixed-bugfix-for-ufcs-non-local.cpp2:21:12: error: a lambda expression cannot appear in this context
template<t<CPP2_UFCS_NONLOCAL(f)(o)> UnnamedTypeParam1_2> auto g() -> void{}// Fails on GCC ([GCC109781][]) and Clang 12 (a lambda expression cannot appear in this context)
^
../../../include/cpp2util.h:885:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
../../../include/cpp2util.h:936:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
#define CPP2_UFCS_NONLOCAL(...) CPP2_UFCS_(,(),,__VA_ARGS__)
^
../../../include/cpp2util.h:866:53: note: expanded from macro 'CPP2_UFCS_'
../../../include/cpp2util.h:917:53: note: expanded from macro 'CPP2_UFCS_'
#define CPP2_UFCS_(LAMBDADEFCAPT,QUALID,TEMPKW,...) \
^
mixed-bugfix-for-ufcs-non-local.cpp2:23:36: error: a lambda expression cannot appear in this context
auto g([[maybe_unused]] cpp2::in<t<CPP2_UFCS_NONLOCAL(f)(o)>> unnamed_param_1) -> void{}// Fails on Clang 12 (lambda in unevaluated context).
^
../../../include/cpp2util.h:885:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
../../../include/cpp2util.h:936:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
#define CPP2_UFCS_NONLOCAL(...) CPP2_UFCS_(,(),,__VA_ARGS__)
^
../../../include/cpp2util.h:866:53: note: expanded from macro 'CPP2_UFCS_'
../../../include/cpp2util.h:917:53: note: expanded from macro 'CPP2_UFCS_'
#define CPP2_UFCS_(LAMBDADEFCAPT,QUALID,TEMPKW,...) \
^
mixed-bugfix-for-ufcs-non-local.cpp2:27:29: error: a lambda expression cannot appear in this context
[[nodiscard]] auto h() -> t<CPP2_UFCS_NONLOCAL(f)(o)> { return o; }// Fails on Clang 12 (lambda in unevaluated context).
^
../../../include/cpp2util.h:885:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
../../../include/cpp2util.h:936:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
#define CPP2_UFCS_NONLOCAL(...) CPP2_UFCS_(,(),,__VA_ARGS__)
^
../../../include/cpp2util.h:866:53: note: expanded from macro 'CPP2_UFCS_'
../../../include/cpp2util.h:917:53: note: expanded from macro 'CPP2_UFCS_'
#define CPP2_UFCS_(LAMBDADEFCAPT,QUALID,TEMPKW,...) \
^
mixed-bugfix-for-ufcs-non-local.cpp2:41:79: error: lambda expression in an unevaluated operand
inline CPP2_CONSTEXPR bool u::c = [](cpp2::in<std::type_identity_t<decltype(CPP2_UFCS_NONLOCAL(f)(o))>> x) mutable -> auto { return x; }(true);// Fails on Clang 12 (lambda in unevaluated context).
^
../../../include/cpp2util.h:885:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
../../../include/cpp2util.h:936:59: note: expanded from macro 'CPP2_UFCS_NONLOCAL'
#define CPP2_UFCS_NONLOCAL(...) CPP2_UFCS_(,(),,__VA_ARGS__)
^
../../../include/cpp2util.h:866:53: note: expanded from macro 'CPP2_UFCS_'
../../../include/cpp2util.h:917:53: note: expanded from macro 'CPP2_UFCS_'
#define CPP2_UFCS_(LAMBDADEFCAPT,QUALID,TEMPKW,...) \
^
13 errors generated.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
pure2-assert-expected-not-null.cpp2:7:22: error: expected '(' for function-style cast or type construction
std::expected<int,bool> ex {4};
~~~^
pure2-assert-expected-not-null.cpp2:7:10: error: no member named 'expected' in namespace 'std'; did you mean 'unexpected'?
std::expected<int,bool> ex {4};
~~~~~^~~~~~~~
unexpected
/usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/exception:92:8: note: 'unexpected' declared here
void unexpected() __attribute__ ((__noreturn__));
^
pure2-assert-expected-not-null.cpp2:9:165: error: use of undeclared identifier 'ex'
return *cpp2::assert_not_null(std::move(up)) + *cpp2::assert_not_null(std::move(sp)) + *cpp2::assert_not_null(std::move(op)) + *cpp2::assert_not_null(std::move(ex));
^
pure2-assert-expected-not-null.cpp2:14:22: error: expected '(' for function-style cast or type construction
std::expected<int,bool> ex {std::unexpected(false)};
~~~^
pure2-assert-expected-not-null.cpp2:14:10: error: no member named 'expected' in namespace 'std'; did you mean 'unexpected'?
std::expected<int,bool> ex {std::unexpected(false)};
~~~~~^~~~~~~~
unexpected
/usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/exception:92:8: note: 'unexpected' declared here
void unexpected() __attribute__ ((__noreturn__));
^
pure2-assert-expected-not-null.cpp2:15:45: error: use of undeclared identifier 'ex'
return *cpp2::assert_not_null(std::move(ex));
^
6 errors generated.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Null safety violation: std::optional does not contain a value
terminate called without an active exception
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Null safety violation: std::shared_ptr is empty
terminate called without an active exception
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Null safety violation: std::unique_ptr is empty
terminate called without an active exception
Empty file.
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
pure2-bugfix-for-ufcs-noexcept.cpp2:5:26: error: lambda expression in an unevaluated operand
static_assert(noexcept(CPP2_UFCS(swap)(t(), t())));// Fails on Clang 12 (lambda in unevaluated context) and GCC 10 (static assertion failed)
^
../../../include/cpp2util.h:882:59: note: expanded from macro 'CPP2_UFCS'
../../../include/cpp2util.h:933:59: note: expanded from macro 'CPP2_UFCS'
#define CPP2_UFCS(...) CPP2_UFCS_(&,(),,__VA_ARGS__)
^
../../../include/cpp2util.h:866:53: note: expanded from macro 'CPP2_UFCS_'
../../../include/cpp2util.h:917:53: note: expanded from macro 'CPP2_UFCS_'
#define CPP2_UFCS_(LAMBDADEFCAPT,QUALID,TEMPKW,...) \
^
1 error generated.
Loading

0 comments on commit 96a4abc

Please sign in to comment.