Is there a way to build C++ custom qualifiers?
Asked Answered
M

2

20

Is there any way to implement a custom type qualifier (similar to const)? I would like to only allow function calls to functions that are of the right qualification, within functions with the same qualification.

Let's say I would have:

void allowedFunction();
void disallowedFunction();

//Only allowed to call allowed functions.
void foo()
{
    allowedFunction();
    disallowedFunction(); //Cause compile time error
}

//Is allowed to call any function it wants.
void bar()
{
    allowedFunction();
    disallowedFunction(); //No error
}

The reason I would like to do this is because I want to make sure that functions called on a specific thread only call realtime-safe functions. Since many applications require hard realtime-safe threads, having some way to detect locks at compile-time would guarantee us that many hard to detect runtime errors cannot happen.

Mic answered 31/7, 2017 at 15:1 Comment(4)
To add new keywords to the language, no there is no chance (unless you can persuade the Committee). You may be able to use macros.Dirk
I think you may be interrested in this: Metaclasses: Thoughts on generative C++Jemadar
Maybe you could just put realtime-safe function declaration in specific header files?Mascon
Is this what you are looking for? An accessor class can easily solve the problem probably.Vent
M
6

Perhaps you can put the functions in a class and make the allowed ones friends of the class like so:

#include <iostream>

class X
{
    static void f(){}
    friend void foo(); // f() is only allowed for foo
};

void foo() // allowed
{
    X::f();
}

void bar() // disallowed
{
    //X::f();  // compile-time error
}

int main()
{

}

You can probably write some crazy macro that does this transparently for every function you'd like to allow/disallow.

Merocrine answered 31/7, 2017 at 15:6 Comment(2)
Friending functions doesn't however guarantee me that no disallowed functions are called. In this way we can only disallow specific functions, not disallow all functions and then allow several. I need to be absolutely sure that no locks are taken.Mic
@AndreasLoanjoe For finer control you'd need one function per class I guess... That's where a macro comes handier. Nice question though!Merocrine
T
0

To expand on skypjack's comment: Yes, if you're okay with annotating every disallowed function, so that non-annotated functions are allowed by default. This is called the Passkey Idiom. Godbolt:

// Any object of type TakesLocks serves as a passkey
// for functions that take mutex locks
//
class TakesLocks {
  friend int main();
  explicit TakesLocks() {} // private constructor
};

void allowedFunction();
void disallowedFunction(TakesLocks);

//Only allowed to call allowed functions.
void foo() {
    allowedFunction();
    disallowedFunction(TakesLocks()); // error, can't call private ctor
}

//Is allowed to call any function it wants.
void bar(TakesLocks tl) {
    allowedFunction();
    disallowedFunction(tl); // No error
}

int main() {
  foo();
  bar(TakesLocks()); // OK, main is a friend of TakesLocks
}

This is useful if you have a "closed" blacklist of disallowedFunctions, such that you can enumerate them all, and you control their signatures. But in real life, we'd also want e.g. TakesLocks to be required in order to call printf, and strftime, and so on — there's no way for us to enforce that. So TakesLocks is basically the worst example I could have chosen. :)


For another approach, much closer to what you originally asked, that has had great success in academia but never shipped in any mainstream compiler as far as I know, see Jeffrey Scott Foster's Cqual.


EDIT: Just recently I learned that Clang supports the reverse approach via the enforce_tcb attribute! "TCB" stands for "Trusted Compute Base," implying that anything inside the TCB must never call anything outside the TCB. Compile with -Werror=tcb-enforcement: Godbolt. Use this approach if you're okay with limiting enforcement to Clang and if you're okay with annotating every allowed function, so that non-annotated functions are disallowed by default. (This is likely too restrictive, because it means e.g. you can't call puts or sin(long double) or std::copy from an annotated function. Vice versa, Clang quietly whitelists a handful of library functions, like memcpy, sin(double), and std::move, into every TCB; which might also be problematic, depending on your use-case.)

// A function inside the TakesNoLocks TCB must not call
// any function outside that TCB.
//
[[clang::enforce_tcb("TakesNoLocks")]] void allowedFunction();
void disallowedFunction();

//Only allowed to call allowed functions.
[[clang::enforce_tcb("TakesNoLocks")]] void foo() {
    allowedFunction();
    disallowedFunction(); // error, can't call outside the TCB
}

//Is allowed to call any function it wants.
void bar() {
    allowedFunction();
    disallowedFunction(); // No error
}

int main() {
  foo();
  bar(); // OK, main is outside the TCB itself
}
Tadio answered 13/3 at 16:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.