Hi all,

A codebase I'm working with has decided that poisoning certain
standard library functions is necessary, as it explicitly does not use
those functions unless absolutely necessary for its own reasons (This
was not my decision to make). As such, we've been looking into ways to
implement that.

The problem is there's really no way at all to forbid standard library
functions. The crucial thing is that this mechanism must have a
disable switch for when a callsite must actually call a forbidden
function, complicating the whole process.

It would be nice to have compiler support to help with this. Something like:

#pragma GCC forbidden void *malloc(size_t) // Forbids malloc from
being called anywhere

#pragma GCC permitted push
#pragma GCC permitted void *malloc(size_t)
void *ptr = malloc(1); // This callsite must use malloc, bypassing the
usual restriction
#pragma GCC permitted pop

However, I'm guessing this would be a bit complex to implement. There
are other ways I have tried as well, such as:

#include <cstdlib>

namespace permitted {
    using ::malloc;
}

inline namespace forbidden {
    [[deprecated("use os::malloc")]] void *malloc(size_t) throw() = delete;
}

int main() {
    void *ptr = malloc(1); // Should raise an error, must call
permitted::malloc(1) to avoid
    free(ptr);
}

The problem with the above is that the error isn't from the function
being deleted or marked as deprecated, it's an ambiguity error that is
confusing and doesn't tell the developer why the error is actually
happening (Because the function has been forbidden). Additionally,
there is no way to disable this system in third party headers included
from the testing framework, making it unusable. Given this, could I
request some sort of compiler specific "shadow" attribute that can be
applied to a namespace? With this attribute, the symbols in this
namespace will take precedence over the symbols of the namespace it is
in (Including the global namespace, if its parent is in the global
namespace) if it is injected into the parent, either because it is
inline, unnamed, or because of a using namespace x; declaration. This
contrasts with the standard behaviour of raising an ambiguity error
when trying to resolve which symbol to use, instead, the symbol from
the namespace marked as shadow will always be given precedence.

#include <cstdlib>

namespace permitted {
    using ::malloc;
}

inline namespace [[gnu::shadow]] forbidden {
    [[deprecated("use os::malloc")]] void *malloc(size_t) throw() = delete;
}

int main() {
    void *ptr = malloc(1); // Correct error shown, forbidden::malloc
chosen over standard library
    free(ptr);
}

Ideally shadow could have another effect on using declarations.
Normally, trying to do a using declaration to choose between 2
conflicting symbols will just fail:

#include <cstdlib>

namespace permitted {
    using ::malloc;
}

inline namespace forbidden {
    [[deprecated("use os::malloc")]] void *malloc(size_t) throw() = delete;
}

using forbidden::malloc;

int main() {
    void *ptr = malloc(1);
    free(ptr);
}

<source>:11:18: error: 'void* forbidden::malloc(size_t)' conflicts
with a previous declaration
11 | using forbidden::malloc;
| ^~~~~~

Perhaps, if one of those symbols comes from a namespace marked as
shadow, the using declaration would instead behave differently,
choosing which declaration is used instead of just bringing the
declaration into scope:

#include <cstdlib>

namespace permitted {
    using ::malloc;
}

inline namespace [[gnu::shadow]] forbidden {
    [[deprecated("use os::malloc")]] void *malloc(size_t) throw() = delete;
}

using forbidden::malloc; // From now on, any calls to malloc use
forbidden::malloc
using ::malloc; // From now on, any call to malloc go to ::malloc
using permitted::malloc; // From now on, any call to malloc goes to
permitted::malloc

Or alternatively, is there a better way to do this instead of adding
custom support to the compiler? The gcc static analyzer doesn't seem
to support C++ yet nor does it seem to have a configurable check for
whether you are calling a given function, neither does VC for that
matter. Maybe this could be an analyzer request instead of a compiler
feature request. Either way, I'm open to suggestions.

best regards,
Julian

Reply via email to