Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to ensure constexpr function never called at runtime?

Tags:

c++

constexpr

Lets say that you have a function which generates some security token for your application, such as some hash salt, or maybe a symetric or asymetric key.

Now lets say that you have this function in your C++ as a constexpr and that you generate keys for your build based on some information (like, the build number, a timestamp, something else).

You being a diligent programmer make sure and call this in the appropriate ways to ensure it's only called at compile time, and thus the dead stripper removes the code from the final executable.

However, you can't ever be sure that someone else isn't going to call it in an unsafe way, or that maybe the compiler won't strip the function out, and then your security token algorithm will become public knowledge, making it more easy for would be attackers to guess future tokens.

Or, security aside, let's say the function takes a long time to execute and you want to make sure it never happens during runtime and causes a bad user experience for your end users.

Are there any ways to ensure that a constexpr function can never be called at runtime? Or alternately, throwing an assert or similar at runtime would be ok, but not as ideal obviously as a compile error would be.

I've heard that there is some way involving throwing an exception type that doesn't exist, so that if the constexpr function is not deadstripped out, you'll get a linker error, but have heard that this only works on some compilers.

Distantly related question: Force constexpr to be evaluated at compile time

like image 719
Alan Wolfe Avatar asked Sep 22 '16 23:09

Alan Wolfe


People also ask

Is constexpr a runtime?

Constant expression, constexpr , code in C++ aims to move non-changing repetitive computations at runtime to compile time. For example, you can write a function that calculates π² at compile time, so, whenever you run the program, π² value is already there. constexpr functions are also allowed to be called at runtime.

Is constexpr always evaluated at compile time?

constexpr functions will be evaluated at compile time when all its arguments are constant expressions and the result is used in a constant expression as well.

Is constexpr always const?

A constexpr variable must be initialized at compile time. All constexpr variables are const . A variable can be declared with constexpr , when it has a literal type and is initialized. If the initialization is performed by a constructor, the constructor must be declared as constexpr .

When to use #define vs constexpr?

#define (also called a 'macro') is simply a text substitution that happens during preprocessor phase, before the actual compiler. And it is obviously not typed. constexpr on the other hand, happens during actual parsing. And it is indeed typed.


3 Answers

In C++20 you can just replace constexpr by consteval to enforce a function to be always evaluated at compile time.

Example:

          int    rt_function(int v){ return v; }
constexpr int rt_ct_function(int v){ return v; }
consteval int    ct_function(int v){ return v; }

int main(){
    constexpr int ct_value = 1; // compile value
    int           rt_value = 2; // runtime value

    int a = rt_function(ct_value);
    int b = rt_ct_function(ct_value);
    int c = ct_function(ct_value);

    int d = rt_function(rt_value);
    int e = rt_ct_function(rt_value);
    int f = ct_function(rt_value); // ERROR: runtime value

    constexpr int g = rt_function(ct_value); // ERROR: runtime function
    constexpr int h = rt_ct_function(ct_value);
    constexpr int i = ct_function(ct_value);
}

Pre C++20 workaround

You can enforce the use of it in a constant expression:

#include<utility>

template<typename T, T V>
constexpr auto ct() { return V; }

template<typename T>
constexpr auto func() {
    return ct<decltype(std::declval<T>().value()), T{}.value()>();
}

template<typename T>
struct S {
    constexpr S() {}
    constexpr T value() { return T{}; }
};

template<typename T>
struct U {
    U() {}
    T value() { return T{}; }
};

int main() {
    func<S<int>>();
    // won't work
    //func<U<int>>();
}

By using the result of the function as a template argument, you got an error if it can't be solved at compile-time.

like image 187
skypjack Avatar answered Oct 12 '22 07:10

skypjack


A theoretical solution (as templates should be Turing complete) - don't use constexpr functions and fall back onto the good-old std=c++0x style of computing using exclusively struct template with values. For example, don't do

constexpr uintmax_t fact(uint n) {
  return n>1 ? n*fact(n-1) : (n==1 ? 1 : 0);
}

but

template <uint N> struct fact {
  uintmax_t value=N*fact<N-1>::value;
}
template <> struct fact<1>
  uintmax_t value=1;
}
template <> struct fact<0>
  uintmax_t value=0;
}

The struct approach is guaranteed to be evaluated exclusively at compile time.

The fact the guys at boost managed to do a compile time parser is a strong signal that, albeit tedious, this approach should be feasible - it's a one-off cost, maybe one can consider it an investment.


For example:

to power struct:

// ***Warning: note the unusual order of (power, base) for the parameters
// *** due to the default val for the base
template <unsigned long exponent, std::uintmax_t base=10>
struct pow_struct
{
private:
  static constexpr uintmax_t at_half_pow=pow_struct<exponent / 2, base>::value;
public:
  static constexpr uintmax_t value=
      at_half_pow*at_half_pow*(exponent % 2 ? base : 1)
  ;
};

// not necessary, but will cut the recursion one step
template <std::uintmax_t base>
struct pow_struct<1, base>
{
  static constexpr uintmax_t value=base;
};


template <std::uintmax_t base>
struct pow_struct<0,base>
{
  static constexpr uintmax_t value=1;
};

The build token

template <uint vmajor, uint vminor, uint build>
struct build_token {
  constexpr uintmax_t value=
       vmajor*pow_struct<9>::value 
     + vminor*pow_struct<6>::value 
     + build_number
  ;
}
like image 21
Adrian Colomitchi Avatar answered Oct 12 '22 06:10

Adrian Colomitchi


In the upcoming C++20 there will be consteval specifier.

consteval - specifies that a function is an immediate function, that is, every call to the function must produce a compile-time constant

like image 7
magras Avatar answered Oct 12 '22 06:10

magras