Description
In this (minimized, not linkable, use clang -O2 -S allocatest.c
and look at the output to test) testcase:
#include <alloca.h>
struct nlopt;
extern int n;
struct nlopt *create(void);
void f(struct nlopt *opt, double *x, void *userdata);
void solve(struct nlopt *opt);
int main(void)
{
struct nlopt *opt = create();
int i;
{
double x[n];
for (i = 0; i < n; i++) {
void *p = alloca(8);
f(opt, x, p);
}
}
solve(opt);
return 0;
}
the alloca
needs to live until the end of the function, but (as evidenced by the assembly output), Clang actually emits code to restore rsp
to its value before the VLA creation at the point where the VLA goes out of scope, and that also ends up destroying the arrays from alloca
.
In the real (much longer) code, the solve
function (actually called nlopt_optimize
) ends up using the pointers that were recorded in the opt
structure by the function f
(actually called nlopt_add_inequality_constraint
), but due to the early pop, they will have been overwritten, leading to a crash.
I can reproduce this with all 3 of clang version 20.1.7, clang version 18.1.8, and with the ancient clang version 13.0.1, so this is a longstanding bug.
GCC gets this right: gcc -O2 -S allocatest.c
shows that rsp
is not restored until after solve
is called (so the lifetime of the VLA also has to be extended, but there is not really a way around that in this case), and in fact, the real program also does not crash when compiled with GCC as it does when compiled with Clang.