Hacker News new | past | comments | ask | show | jobs | submit login
C No Evil (regehr.org)
118 points by wglb on Aug 10, 2011 | hide | past | favorite | 65 comments



    #define continue break
There's my choice. It's one of the few that will actually compile and the runtime effect is not super immediately detectable.


or break switch statements with:

  #define break


Some of the most frustrating problems I've seen are due to misunderstanding or rare semantics rather than mistakes. Examples might include the specifics of floating-point math, or exactly how numeric coercion works. This is especially bad when the result isn't easy to eyeball as right or wrong.

For example, we could force a double-valued transcendental function to float and back for a small loss of precision in normal cases:

    #define sin(d) ((double)sinf(d))
Which yields...

    double i =  10
    --> i    =  10.0000000000
    sin(i)   = -0.5440211109
    sinf(i)  = -0.5440211296
    delta    =  0.0000000187
    
    double i =  0.25
    --> i    =  0.2500000000
    sin(i)   =  0.2474039593
    sinf(i)  =  0.2474039644
    delta    = -0.0000000051
    
    double i = 9999999.999999
    --> i    = 9999999.9999989998
    sin(i)   =  0.4205487007
    sinf(i)  =  0.4205478132
    delta    =  0.0000008875
(Format string was %25.10f, fwiw.)

Another idea: Let's say we know the application relies on rand() to produce the correct distribution for testing, or maybe to generate session keys. Changing the semantics of the RNG won't result in a compiler bug, but it might result in the tests failing to cover all cases, or a horrible security breach.


I think #define volatile is by far the sneakiest.

The rest would probably be found quite quickly, but omitting volatiles could lead to hard to detect bugs that will only manifest themselves under specific circumstances.


> I think #define volatile is by far the sneakiest.

The only legitimate reasons for using volatile generally involve memory-mapped IO and POSIX signal handlers. C's volatile is nothing like Java's volatile and has very weak semantics. With lock-based code you generally have nothing to worry about because lock/unlock has load-acquire/store-release semantics. But if you're writing lock-free code, volatile is useless in general and you need to think carefully about how atomicity is affected by memory alignment and operand size (e.g. a byte write that might look atomic implicitly turns into a non-atomic read-modify-write), explicit memory barriers, etc.

So defining away volatile won't actually impact most applications unless they're relying on very compiler-specific behavior.


A typical usage of volatile, and one that bit me many times is using longjmp, for which the man page says:

  The values of automatic variables are unspecified after a call to longjmp() if they meet all the following criteria:
  · they are local to the function that made the corresponding setjmp(3) call;
  · their values are changed between the calls to setjmp(3) and longjmp(); and
  · they are not declared as volatile.
I've had bugs that made me go half-crazy before someone reminded me that you have to use volatile in these situations.


I assume that's a side effect of how the execution context is restored? If a nonvolatile local was stored in a register, and passed by address to a function that modified it before longjmp(), then the saved register value would clobber the updated one.


You don't need to pass it by address to a function. Something like this could trigger it:

    // region 1:
    // x is allocated to register r1
    int x = 0; // store 0 to r1
    jmpbuf jb;
    if (setjmp(jb) == 0) {
        // region 2:
        // x is re-allocated to r2, so r1 is copied to r2
        x = 42; // store 42 to r2
        longjmp(jb, 1);
    }
    // region 3:
    // x is allocated to r1
    printf("%d\n", x); // load from r1
The compiler is allowed to allocate the same variable to different locations at different points in the program. At re-allocation boundaries, it will insert register moves or stack spills or whatever. The problem in the example is that the longjmp() crosses a re-allocation barrier, so the store to r2 won't be registered as a store to x in region 3.

Actually, this would still occur even if x was always allocated to r1. The reason is that before calling setjmp() the compiler spills x to the stack, so the callee can use the registers for its own use, and on returning (either via longjmp() or the first time through) it will restore x from the value on the stack.

I tried the example above with GCC. At the default optimization level it printed 42. But with -O2 it printed 0. Looking at the assembly code, it's actually even worse than I described. The compiler has treated the longjmp() akin to an exit() and so has determined that the x = 42 is actually a no-op that can be eliminated. This is similar to how in printf("1"); exit(0); printf("2"); the compiler will actually remove the second printf() as dead code from the executable.

So, in conclusion, there seems to be a whole bunch of ways in which the compiler could screw this up while staying within the bounds of standards-acceptable behavior.


That's cool example I didn't know about. Thanks!


I think memory-mapped IO is a pretty frequent topic for readers of "Embedded in Academia". If you find yourself looking at real embedded code that doesn't have volatile somewhere that's a really bad sign. Locks won't prevent a memory access driving a SPI loop or UART from being optimized out, since C doesn't consider memory access to be inherently side-effectful.


It would definitely be a good way to fuck with kernel code, though.


That it would. Lots of raw memory-mapped IO and dependencies on compiler-specific interpretations of C semantics!


I lost a couple hours of my life to a missing "volatile" writing code to talk to a network processor about 6 years ago, so this is kind of seared into my head.


Yeah, I've done low-level driver and microcontroller code in C as well. But these days I view everything through a multi-threading lens, so my default reaction when I see people bring up volatile is to assume they're talking about its use in threaded code, which is unfortunately commonplace. Yesterday I fixed a latent data race in some lock-free code where a should_kill_thread-style variable had been marked volatile and the author had assumed that was necessary and sufficient to dispel any issues. Incidentally, that variable also had type bool and was in a struct next to another variable of type bool. Ruh roh...


What's the proper fix here?

My knowledge of proper usage of volatile in C is weak. Does it have something to do with the fact that sizeof bool < sizeof int on most platforms?


The trick is to be careful about variable size and alignment and use wrappers around platform-specific load/store operations with the right memory ordering guarantees. There's no portable way to do this in C. But whatever you do, volatile won't be involved.

In that case I mentioned, yes, the issue is that sizeof(bool) = 1 by default on most compilers. You can force it to be 4 on MSVC with a compiler directive but that has its own problems (e.g. you are using a library which uses bools in its API and the library was compiled with sizeof(bool) = 1). The solution is to never use bool, int, etc, directly for variables that will be written to in lock-free code. Use typedefs (or even better for type safety, wrapper structs) that are explicit about size, make all lock-free reads and writes go through library functions/macros that provide the right atomic semantics.

Here's the specific data race with the bools. Assume that sizeof(bool) = 1 and that the two aforementioned bool bytes (call them A and B) wind up next to each other in the same 4-byte aligned word of memory. Thread 1 writes to A and thread 2 writes to B concurrently. It looks like there should be no data race because they're writing to separate variables. But unlike storing a full word to memory, storing a byte involves an implicit read-modify-write, so the only way for this situation to be safe is if thread 1 and 2 use CAS operations to write A and B.


Extremely informative...thanks!


sizeof(_Bool) is probably 1. Whether someone is using stdbool.h or some other bool define (int is common enough) is unassumable. Not all CPUs can do atomic byte operations, so they're going to read/modify/write a machine word at a time.

I'm not sure how much you'd have to mark volatile to get a compiler to behave the way you want, so you could use int and dodge the issue. Or better, the standard sigatomic_t type is provided and should work.


volatile is tied to a fairly specific usage and does not appear in that many projects.

I'd personally go with while -> if, that's very sneaky.


It will cause compiler errors, so it's not all that sneaky:

    int main(void)
    {
        while (1) break;
        return 0;
    }


It may rather than will. This ^ is a rather odd, syntax abuse case.


What do you mean? You will get a compiler error in any case where there is a break or continue in an outermost while block. There's nothing odd or uncommon about that. Here's something closer to a real program:

    int main(void)
    {
        while (1) {
            char c = getchar();
            if (c == 'q') break;
            process(c);
        }
        return 0;
    }


Ugh. You are right of course. I had a brain spasm.


I'm rusty on my C preprocessor, but how would this work out for do {} while blocks?


Obviously library dependent, but I think this would make me pull my hair out:

#define pthread_mutex_lock(x)

#define pthread_mutex_unlock(x)


Can anyone explain what effect

  #define goto
would have? It doesn't looks valid, because if I have

  myLabel:
  /* some code*/
  goto myLabel;
would produce

  myLabel;
Which, as far as I know shouldn't compile because it expects either a normal expression or a definition/declaration and myLabel; doesn't appears to be any of them. (Maybe I'm missing something)


    #define free(x)
Most tests still pass, but once the program's been running for a while you'll get a nice memory leak. Fails if your mortal enemy runs leak checks as part of their standard test suite or other QA, or has a high enough allocation rate to quickly turn up such issues.


Improvement: include some static counter, and only skip the free after a few million (billion?) calls. If possible, also make the thing random, and limit skipping free to a limited range of block sizes. The latter provides a nice false trail for investigators (hm, it is leaking only struct foo...)

Problem of course is to find a nice size range for the program at hand.


This is pretty much straight out of the daily wtf: http://thedailywtf.com/Articles/The-Disgruntled-Bomb.aspx


We have a winner:

#define if(x) if(rand()<0.0001 && (x))


C's rand() function returns an int >= 0, so rand() < 0.0001 will be true only when rand() returns zero. Assuming a pseudorandom number generator that uses all its possible values before repeating, you'll get a 0 approximately every 1 in RAND_MAX times you call rand().


Don't forget the #ifndef DEBUG around it - that would make it ten times harder to track down.


if(rand()>=0.0001 && (x))

that way it will fail extremely rarely and be even harder to detect!


You're right, they either need your change or a switch to ||


#define free(x) free(x + 1)

#define malloc(x) malloc(random())


The free one at least will be detected immediately with a good libc, but nice idea. It gives me another idea though:

    #define free(x) do { free(x); x = NULL; } while (0)
Assuming you can turn it off just before shipping, this will hide many forms of double free during development which then show up when the evil line is deleted.


That assumes 'x' is an lvalue, which it may very well not be (thus causing compilation errors, which would presumably get caught pretty quickly).


I'm having a very hard time thinking of an example where you'd free an rvalue. Function return, maybe, but without using the value? It (almost) had to be an lvalue in order to get the malloc into it. Ah, an unconst cast. free((void *)constx), but lots of compilers don't catch that. Is there something else you were thinking of?

There's also people doing stupid shit like free(&x), but those people don't need evil macros to dick up their code. :)


Function calls seem right - particularly if they're badly written. Also, this macro might give curious results when passed in ptr++ .

While this is C++, I've actually seen this particular construct: delete new SomeClass(...);

used as, basically, an eye-poppingly clever way of initializing something, doing work, and then cleaning up after.


    { SomeClass makeitso; }
:)

Also, free(ptr++) is broken by design, the post increment value is useless. But free(*ptr++) is a good case.


> free(*ptr++)

Yes, of course - that's what I thought I'd written. Clearly not :)


I can think of one case in a project where we allocate a 4K block, fill the first n bytes with header info, and then add n bytes to the pointer and return it to something that's told it's getting a 4K - n byte block.

And then later it passes that return value to some function that deallocates the block, by calling free(((char *)p) - n).


Some buddies and I were messing around with ways we could sneakily inject one hidden #define somewhere in a C project to make it look like a segfault. We ended up with #define printf to print out "segfault" but GCC always threw warnings, so it didn't have much practicality for use in a joke.


Pre C99? Cheap hack using variadic macros:

   #define printf(...) printf(__VA_ARGS__); puts("\n\n\n\nFAIL!")


Yes, pre-C99. I'm going to have to try that now.


Here's mine:

    __attribute__((constructor)) void __maiin(void)
    {
        if (rand() < 0.05) exit();
        //if (rand() < 0.05) close(1);
        //if (rand() < 0.05) main(); // and so on ..
    }

The program will randomly quit before main() is executed.


#define int char

I did this one to a friend (briefly) during school.


An interesting, albeit not that valuable, challenge. It's a fun little question, but it's not even valuable as a black hat exercise. It's just a non-sensical hypothetical.


Its supposed to be interesting from the perspective of people trying to write reliable C code, not do actual harm.


Perhaps it is the penetration tester in me that takes an interest in things like this.


I would prefer:

    #define assert(x) ((void)(x))
Most of these would cause an immediate flood of hints and / or warnings; but an assert that did nothing but evaluate its argument could lurk for some days, especially if the developers in question don't spelunk with a debugger very often.


I don't get it. In (non-wildly-buggy) code evaluating the argument to assert() is required to (a) succeed, and (b) have no side effects, so what would such a definition accomplish?


Assumptions:

* asserts are used in a defensive programming style to state invariants

* the enemy developers will be reasoning about the code assuming that asserts which didn't fire were true

* when debugging, they'll read the code and prematurely dismiss valid theories as to why the bug exists based on what they read in the assertions


Sure, but I don't think this matches the problem statement -- if the product is within a week or two of shipping, it should be long past the point where developers are tracking down bugs by using asserts to exclude possible code paths.

If the problem was to slow down early development I'd absolutely agree with you.


An easier way to define the same thing:

#define NDEBUG 1


No it isn't. That will discard the assert expression. What I wrote will still evaluate it.


You're right. Sorry.


    #define rand() (rand() % 100)


  #define memmove memcpy
  #define default default: break; case 0xfeceface


Reminds me of Sony... Perhaps they had this someplace: #define rand(x) 4


#define fopen(A,B) ((rand() % 1000 == 0) ? fopen(A,"w") : fopen(A,B))


I think redefining volatile wouldn't be harmful enough because so few people use volatile correctly anyway. By contrast, the break/continue or double-free suggestions would show up too quickly. I don't think I can come up with anything as insidious as some of the "one in a million" suggestions, but I like this one for its brevity.

#define do

You could even slip that into a makefile (-Ddo=) and it might not be noticed.


I think that would cause an infinite loop on most usages of do/while, thus making the problem (probably not the cause hehe) quickly noticeable.


Interesting header, but why would anyone want to cause someone else so much trouble trying to release a product?


It's just a throwaway scenerio to wrap around an interesting problem...




Consider applying for YC's W25 batch! Applications are open till Nov 12.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: