13

In this paper is the following example of a piece of code that can trigger a division-by-zero:

if (arg2 == 0)
    ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO),
                    errmsg("division by zero")));
/* No overflow is possible */
PG_RETURN_INT32((int32) arg1 / arg2);

ereport here is a macro that expands to a call to a bool-returning function errstart that may or may not return and, conditional (using a ?:) on its return value, a call to another function. In this case, I believe ereport with level ERROR unconditionally causes a longjmp() someplace else.

Consequently, a naive interpretation of the above code is that, if arg2 is nonzero, the division will happen and the result will be returned, while, if arg2 is zero, an error will be reported and the division will not happen. However, the linked paper claims that a C compiler may legitimately hoist the division before the zero check, then infer that the zero check is never triggered. Their only reasoning, which seems incorrect to me, is that

[T]he programmer failed to inform the compiler that the call to ereport(ERROR, : : :) does not return. This implies that the division will always execute.

John Regehr has a simpler example:

void bar (void);
int a;
void foo3 (unsigned y, unsigned z)
{
  bar();
  a = y%z;
}

According to this blog post, clang hoists the modulo operation above the call to bar, and he shows some assembly code to prove it.

My understanding of C as it applies to these snippets was that

  1. Functions that do not, or may not, return are well-formed in standard C, and declarations of such require no particular attributes, bells, or whistles.

  2. The semantics of a call to a function that do not, or may not, return are well-defined, in particular by 6.5.2.2 "Function calls" in C99.

  3. Since the ereport invocation is a full expression, there is a sequence point at the ;. Similarly, since the bar call in John Regehr's code is a full expression, there is a sequence point at the ;.

  4. There is consequently a sequence point between the ereport invocation or bar call and the division or modulo.

  5. C compilers may not introduce undefined behaviour to programs that do not elicit undefined behaviour on their own.

These five points seem to be enough to conclude that the above division-by-zero test is correctly-written and that hoisting the modulo above the call to bar is incorrect. Two compilers and a host of experts disagree. What is wrong with my reasoning?

tmyklebu
  • 13,915
  • 3
  • 28
  • 57
  • 1
    The assertion 'no overflow is possible' is wrong on 2's complement machines (most machines). If you divide INT32_MIN by -1 and you get INT32_MAX+1, which is an overflow on the `int32` type. – Jonathan Leffler Aug 19 '13 at 01:32
  • @JonathanLeffler: I don't know the range of input values for that first snippet. That may be another issue, but I believe it's independent of the divide-by-zero case. – tmyklebu Aug 19 '13 at 01:36
  • This looks like a close duplicate with better answers: [Are all functions in C/C++ assumed to return?](http://stackoverflow.com/q/20059532/1708801) – Shafik Yaghmour Aug 05 '15 at 11:42
  • @ShafikYaghmour: R's answer here is excellent. I see longer answers at the other question, but no better answers. – tmyklebu Aug 05 '15 at 17:25
  • normally when we have duplicates we want someone looking at one of them to be able to easily find the other one. Normally the criteria is [views, votes and completeness of answers](http://meta.stackoverflow.com/a/277129/1708801) I personally believe the question linked above has a more diverse set of answers but I am happy to let someone else decide perhaps @MattMcNabb has a strong opinion here. – Shafik Yaghmour Aug 05 '15 at 17:35

2 Answers2

8

The paper is wrong, and as for the clang example, this is a compiler bug (a rather common occurrence with clang...). I wish I could give you better reasons, but you already provided all the correct reasoning in the question.

Actually, for the clang issue, as far as I can tell, no bug has been demonstrated yet. Since bar does return in the example on the blog you linked to, the compiler is free to reorder the division across the call. This is trivial to do if bar is defined in the same translation unit, but it's also possible with LTO. To actually test for this bug, you need a function bar that never returns.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • How do you know that `bar` returns? I see a declaration but no definition. – tmyklebu Aug 19 '13 at 01:39
  • Well, it reproduces without a definition of `bar` here :) – tmyklebu Aug 19 '13 at 01:42
  • OK, then you've confirmed it. I suspected the bug exists, but the blog does not clearly show it, and the inclusion of a function `bar` that *does* return muddles the issue. – R.. GitHub STOP HELPING ICE Aug 19 '13 at 01:45
  • 2
    As an aside, it is trivial to provide a definition of `bar()` that doesn't return in standard C: `void bar(void) { exit(0); }`. – caf Aug 19 '13 at 03:32
  • 2
    @caf: I think I'd prefer `volatile int foo; void bar(void) { if (foo) exit(0); }`, since even a compiler which could see all code paths from `main()` would not be entitled to make any assumption about whether it would or would not return [other code in the system could find out where `foo` was located and modify it asynchronously without the compiler having any way of knowing about it, and the code would be required to honor such effect] – supercat Apr 24 '15 at 02:28
2

Using the "as if" rule...

The division can be done wherever the compiler feels like it; as long as the resulting code behaves as if the division was done in the correct place.

This means that:

a) if division by zero (or division resulting in an overflow) causes an exception (e.g. typical for integer division on 80x86) then the division can't be done before the function call (unless the compiler can prove that the division is always safe).

b) if division by zero (or division resulting in an overflow) does not cause an exception (e.g. typical for floating point on 80x86) the compiler may do the division before the function call; as long as nothing in the function being called can modify the values used in the division.

Brendan
  • 35,656
  • 2
  • 39
  • 66
  • The "as if" rule does not permit the compiler to generate code that crashes when the behaviour of the abstract machine is not undefined, unspecified, or implementation-defined. – tmyklebu Nov 11 '14 at 18:27
  • 1
    @tmyklebu: It does, however, allow a compiler to generate code that performs operations (like division by zero) which would crash on *some platforms* in cases when the abstract machine operation is fully defined, *provided that the operations don't crash the platform on which the code will run*. – supercat Jul 24 '15 at 20:00
  • @tmyklebu we're off topic for this question, but AFAIK, it doesn't permit the compiler to generate code that crashes even in the case of unspecified behaviour, or implementation-defined (except for the case where raising a signal is a permitted form of implementation-definition). – M.M Aug 05 '15 at 00:06
  • @MattMcNabb I removed my answer since I found a close duplicate see [my comment above](http://stackoverflow.com/questions/18305296/division-by-zero-and-undefined-behaviour-in-c#comment51585033_18305296) and it has much better answers. I even commented on that one, it is amazing how many SO question you completely forget about. Seems like the answer can depend on whether we are talking about C or C++. – Shafik Yaghmour Aug 05 '15 at 11:43
  • @ShafikYaghmour Well they are different languages – M.M Aug 05 '15 at 21:21