https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98404
Bug ID: 98404 Summary: Compiler emits unexpected function call that may cause security problems Product: gcc Version: 10.2.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c Assignee: unassigned at gcc dot gnu.org Reporter: msharov at users dot sourceforge.net Target Milestone: --- int rotate_argv (const char** argv, int first, int mid, int end) { const char** p = argv+first; int n1 = mid-first; int n2 = end-mid; int nm = n1+n2-1; for (int j = 0; j < n2; ++j) { const char* v = p[nm]; for (int i = 0; i < nm; ++i) p[nm-i] = p[nm-i-1]; p[0] = v; } return n1; } This bit of code unexpectedly emits a call to memmove to replace the inner copy loop. Such behavior is highly inappropriate, breaking the "what-you-see-is-what-you-get" spirit of C. Sure, the loop is equivalent to a memmove call, but if I wanted to call memmove, I would have called memmove. Doing it behind my back brings in code paths that may cause problems impossible to understand by looking at the code. Worse yet, the compiler only does this in the optimized build (-Os, -O2, and -O3, but not -O1 or -O0), making debugging of the resulting problem a beat-your-head-on-the-desk frustrating exercise. The bug in my code was causing memory corruption in argv to happen in that inner loop, but looking at the code above will not reveal the problem, no matter how much you scream at the debugger. The bug was in my memmove implementation returning the wrong value, which the compiler then helpfully reloaded into p. Naturally, it's a good thing that I fixed the bug; having never used the return value of memmove myself I doubt I would have discovered it anytime soon. But this illustrates how a malicious exploit could be introduced into that loop without anybody being able to figure it out. Let's remember that we still have that LD_PRELOAD abomination. On a more mundane note, replacing the loop with memmove causes the compiled code to grow from 107 bytes to 166. This is using the -Os, switch, of course. I have complained many times about how gcc doesn't care about size optimization and doesn't inline stuff because it can't understand that inserting a function call into code that currently has none has great costs of register saving and all that. I have by now resigned to having to #define inline inline __attribute__((always_inline)) everywhere, but will you perhaps someday reconsider your position that size optimization does not matter? If 55% code bloat in this example doesn't convince you, what will? Finally, calling memmove will make the code slower, not faster, due to its much higher startup overhead that is justifiable for copying large blocks, but not for copying one or two elements, which is what the code above is made for. The conceit of the compiler, in thinking it knows better, thus results in worse outcome all around; in size, speed, and security.