The problem
I need a checked_cast_call<function>
generic wrapper to a function, that would runtime-check any cast involved to call the function, or to get the value.
As an example, calling the following function with an input buffer larger than 2GB will cause some issues (because of the int inl
input size argument that would overflow):
int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out,
int *outl, const unsigned char *in, int inl);
My imperfect solution
To achieve that, using insightful help from other stackoverflow topics, I ended up with the following solution, which is unfortunately far from being perfect:
- First, I wrote a small template to runtime-check a cast (it will throw a
std::runtime_error
if a cast overflows).
#include <stdexcept>
#include <type_traits>
/**
* Runtime-checked cast to a target type.
* @throw std::runtime_error If the cast overflowed (or underflowed).
*/
template <class Target, class Source>
Target inline checked_cast(Source v)
{
if constexpr (std::is_pointer<Source>::value) {
return v;
} else if constexpr (std::is_same<Target, Source>::value) {
return v;
else {
const auto r = static_cast<Target>(v);
if (static_cast<Source>(r) != v) {
throw std::runtime_error(std::string("cast failed: ") + std::string(__PRETTY_FUNCTION__));
}
return r;
}
}
- Then, a small template container to hold a type, and allow a runtime-checked cast to possibly another type. This container can be used to hold the return value of a function, but it can also be used to hold every single input argument to a function, relying on
operator T ()
to provide runtime-checked casted values:
/**
* Container holding a type, and allowing to return a cast runtime-checked casted value.
* @example
* const size_t my_integer = foo();
* const checked_cast_call_container c(my_integer);
* int a = static_cast<int>(c);
*/
template <typename T>
class checked_cast_call_container {
public:
inline checked_cast_call_container(T&& result)
: _result(std::move(result))
{
}
template <typename U>
inline operator U() const
{
return checked_cast<U>(_result);
}
private:
const T _result;
};
- And the final wrapper, taking a
decltype
of a function pointer, and the function pointer itself, expanding the packed arguments with our container, and putting the result in a container too:
/**
* Wrapped call to a function, with runtime-checked casted input and output values.
* @example checked_cast_call<decltype(&my_function), &my_function>(str, 1, size, output)
*/
template <typename Fn, Fn fn, typename... Args>
checked_cast_call_container<typename std::result_of<Fn(Args...)>::type>
checked_cast_call(Args... args)
{
return checked_cast_call_container(fn(checked_cast_call_container(std::forward<Args>(args))...));
}
- Sample test:
static char my_write(void* ptr, char size, char nmemb, FILE* stream)
{
return fwrite(ptr, size, nmemb, stream);
}
int main(int argc, char** argv)
{
// Input overflow: input argument nmemb is larger than 127
try {
char str[256] = "Hello!\n";
volatile size_t size = sizeof(str);
const char b = checked_cast_call<decltype(&my_write), &my_write>(str, 1, size, stdout);
(void)b;
} catch (const std::runtime_error& e) {
std::cout << e.what() << "\n";
}
return 0;
}
Note on performance impact
On a basic test (equivalent to the sample test in this post), overhead on common (non-erroneous) path is minimal, and is basically one additional cmp
+jne
for the runtime-checked input argument. (Note: additional code for erroneous path, including throw
cold path not shown on disassembled code below)
--- old.S 2019-03-11 11:14:25.847240916 +0100
+++ new.S 2019-03-11 11:14:27.087238775 +0100
@@ -3 +3 @@
-lea 0x10(%rsp),%rbx
+lea 0x10(%rsp),%rdi
@@ -6 +5,0 @@
-mov %rbx,%rdi
@@ -9 +8,4 @@
-mov 0x8(%rsp),%rax
+mov 0x8(%rsp),%rdx
+movsbq %dl,%rax
+cmp %rdx,%rax
+jne 0xXXXXXX <_Z5test3v+82>
@@ -11 +13 @@
-movsbq %al,%rdx
+lea 0x10(%rsp),%rdi
@@ -13 +14,0 @@
-mov %rbx,%rdi
The question
It is possible to improve this wrapper, so that:
- You only need one template argument (the function) and not the
decltype
of the function (ie. directlychecked_cast_call<&my_write>(...)
) - You would use the inferred function arguments types to runtime-check the casts, and not relying on the container wrapper
There might be some solutions by passing the function as argument to the wrapper, and not as template, but I wanted a pure template solution. Maybe this is simply not feasible, or way too convoluted ?
Dear reader, thank you in advance for any useful hints!
⟾ Partial solution
Solution to question #1 thanks to @kmdreko, by declaring non-type template arguments with auto:
template <auto fn, typename... Args>
auto checked_cast_call(Args... args)
{
return checked_cast_call_container(fn(checked_cast_call_container(std::forward<Args>(args))...));
}
const char b = checked_cast_call<&my_write>(str, 1, size, stdout);
Allowing a direct sed -e 's/my_write/checked_cast_call<&my_write>/g'