Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add null violation checks for std::unique_ptr, std::shared_ptr, std::optional and std::expected to prevent UB #945

Merged
merged 3 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
}
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
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
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
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.
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
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
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
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