llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
27.86k stars 11.48k forks source link

operator new with `throw()` omits `NULL` check at `-O1` and higher #36457

Open llvmbot opened 6 years ago

llvmbot commented 6 years ago
Bugzilla Link 37109
Version 5.0
OS All
Reporter LLVM Bugzilla Contributor
CC @DougGregor,@efriedma-quic

Extended Description

I tested this example in various compilers on godbolt using -fno-exceptions and various -O levels.

https://godbolt.org/g/egWwC2

#include <stddef.h>

extern void * operator new(size_t sz) throw();

class Foo {
public:
   Foo(int i) : _i(i) {}
private:
   int _i;
};

void *
test_new() {
    Foo *f = new Foo(5);
    return f;
}

The expectation is if the operator new has a throw() keyword, there will be a NULL check before initialization is done and no NULL check if the throw() keyword is omitted.

This expectation is based on [expr.new]p13 that I find in this copy of the standard:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1905.pdf

[Note: unless an allocation function is declared with an empty exception-specification (15.4), throw(), it indicates failure to allocate storage by throwing a bad_alloc exception (clause 15, 18.4.2.1); it returns a non-null pointer otherwise. If the allocation function is declared with an empty exception-specification, throw(), it returns null to indicate failure to allocate storage and a non-null pointer otherwise. — end note ] If the allocation function returns null, initialization shall not be done, the deallocation function shall not be called, and the value of the new-expression shall be null.

The results I got for various permutations of compiler this code in godbolt are:

  1. gcc (any version/any -O/new declaration w/ throw()): NULL check before constructor init.

  2. gcc (any version/any -O/new declaration w/o throw()): no NULL check before constructor init.

  3. clang (any version/any -O/new declaration w/o throw()): no NULL check before constructor init.

  4. clang (3.3 and prior/any -O/new declaration w/ throw()): NULL check before constructor init.

  5. clang (3.4.1 and later/-O0/new declaration w/ throw()): NULL check before constructor init.

  6. clang (3.4.1 and later/-O1 and higher/new declaration w/ throw()): no NULL check before constructor init.

  7. clang (3.4.1 and later/-O0/new declaration w/ throw()): NULL check before constructor init.

E.g., below is the x86_64 assembly for clang 3.3 vs. 5.0.0 with -O3:

clang 3.3, -O3 -fno-exceptions

test_new(): # @test_new()
  push RAX
  mov EDI, 4
  call operator new(unsigned long)
  test RAX, RAX
  je .LBB0_1
  mov DWORD PTR [RAX], 5
  pop RDX
  ret
.LBB0_1:
  xor EAX, EAX
  pop RDX
  ret

clang 5.0.0, -O3 -fno-exceptions

test_new(): # @test_new()
  push rax
  mov edi, 4
  call operator new(unsigned long)
  mov dword ptr [rax], 5
  pop rcx
  ret

We believe there may be a bug here where clang should be doing the NULL check before initialization here regardless of the optimization level to be standard compliant.

efriedma-quic commented 6 years ago

Your program has undefined behavior; you're declaring a standard library function with a signature that's different from the one in the standard.

Leaving open to add a diagnostic to clang.