Unapiedra
Unapiedra

Reputation: 16228

Clang fails to detect undefined behavior

How do I detect undefined behavior in the example below?

#include <iostream>
#include <stream>
int main() {
    std::cout << "Undefined: " << std::string().front() << std::endl;
    return 0;
}

Compiled with clang++ -fsanitize=address,undefined -g -Wall -Wextra main.cc.

My expected output at either compile time or run time would be an error. Calling front() on an empty string is undefined behavior according to cplusplus.com. The actual output is Undefined:.

Questions

  1. Can I produce an error at compile or run time? If not, why can the compiler not detect this?
  2. If not, is there any tool that can detect this? For example, static analysis.

Versions used:

$ clang++ --version
Apple LLVM version 9.0.0 (clang-900.0.37)
Target: x86_64-apple-darwin17.0.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin

$ /usr/local/Cellar/gcc/7.2.0/bin/g++-7 --version
g++-7 (Homebrew GCC 7.2.0) 7.2.0
[Copyright Notice]

Related, relevant questions:

Upvotes: 0

Views: 576

Answers (2)

Richard Hodges
Richard Hodges

Reputation: 69912

A Compile time warning isn't always possible with standard containers, but implementing a free function would allow extra checks which could optionally be turned off with compiler flags.

#define NDEBUG

#include <iostream>
#include <string>
#include <cassert>
#include <stdexcept>


struct assert_on_failure
{
    template<class ErrorMessage>
    void check(bool condition, ErrorMessage&& msg) const
    {
        if (!condition)
            assert(!msg);
    }
};

struct throw_on_failure
{
    template<class ErrorMessage>
    void check(bool condition, ErrorMessage&& msg) const
    {
        if (!condition)
            throw std::runtime_error(msg);
    }
};

#ifdef NDEBUG
constexpr auto default_failure_policy = throw_on_failure{};
#else
constexpr auto default_failure_policy = assert_on_failure{};
#endif



template
<
    class Container,
    class FailurePolicy = std::add_lvalue_reference_t<decltype(default_failure_policy)>
>
decltype(auto) get_front(Container&& cont, 
                         FailurePolicy&& policy = default_failure_policy)
{
    policy.check(cont.size() > 0, "container is empty");
    return cont.front();
}

int main() {
    std::cout << "Undefined: " << get_front(std::string()) << std::endl;
    return 0;
}

Upvotes: 1

Unapiedra
Unapiedra

Reputation: 16228

Turning Baum mit Augen's link into an answer.

Adding the flags -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC when compiling with GCC works. The same does not work with Clang.

This enables a runtime check inside std::basic_string.

Results (line breaks added for clarity):

$ /usr/local/Cellar/gcc/7.2.0/bin/g++-7 main.cc -g  -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC
$ ./a.out
Undefined:
/usr/local/Cellar/gcc/7.2.0/include/c++/7.2.0/bits/basic_string.h:1077:
 std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::reference 
 std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::front() 
     [with _CharT = char; _Traits = std::char_traits<char>;
      _Alloc = std::allocator<char>;
      std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::reference = char&]: 
         Assertion '!empty()' failed.
[1]    24143 abort      ./a.out

Upvotes: 1

Related Questions