THRIFT-2026: Eliminate some undefined behavior in C/C++

Clients: glib, C++
Patch: Jim Apple <jbapple-impala@apache.org>

This closes #1214

This patch fixes some undefined behavior were found using Clang's
UndefinedBehaviorSanitizer (UBSan). To check for undefined behavior,
run /build/docker/scripts/ubsan.sh. This is run during CI builds, as
well.

The examples of the types of undefined behavior fixed in this commit
are:

1. Enumerations exhibit undefined behavior when they have values
   outside of a range dependent on the values of their enumerators, as
   specified in C++14's chapter 7.2 ("Enumeration declarations"),
   paragraph 8.

2. Left shift of negative values, used in zigzag encoding, is
   undefined behavior. See 5.8 ("Shift operators"), paragraph 2 for
   C++ and 6.5.7 ("Bitwise shift operators"), paragraph 4 for C99 and
   C11.
This commit is contained in:
Jim Apple 2017-03-18 12:56:50 -07:00 committed by James E. King, III
parent 6c08ac72c6
commit 147c2849af
7 changed files with 51 additions and 10 deletions

View File

@ -152,6 +152,12 @@ env:
BUILD_ENV="-e CC=gcc -e CXX=g++"
DISTRO=debian
# C and C++ undefined behavior. This wraps autotools.sh, but each binary crashes if
# undefined behavior occurs. Skips the known flaky tests.
- TEST_NAME="UBSan"
SCRIPT="ubsan.sh"
BUILD_ARG="--without-haskell --without-nodejs --without-perl --without-python"
matrix:
include:
# QA jobs for code analytics and metrics

35
build/docker/scripts/ubsan.sh Executable file
View File

@ -0,0 +1,35 @@
#!/bin/sh
set -ex
# Wraps autotools.sh, but each binary crashes if it exhibits undefined behavior. See
# http://releases.llvm.org/3.8.0/tools/clang/docs/UndefinedBehaviorSanitizer.html
# Install a more recent clang than default:
sudo apt-get update
sudo apt-get install -y --no-install-recommends clang-3.8 llvm-3.8-dev
export CC=clang-3.8
export CXX=clang++-3.8
# Set the undefined behavior flags. This crashes on all undefined behavior except for
# undefined casting, aka "vptr".
#
# TODO: fix undefined vptr behavior and turn this option back on.
export CFLAGS="-fsanitize=undefined -fno-sanitize-recover=undefined -fno-sanitize=vptr"
# Builds without optimization and with debugging symbols for making crash reports more
# readable.
export CFLAGS="${CFLAGS} -O0 -ggdb3"
export CXXFLAGS="${CFLAGS}"
export UBSAN_OPTIONS=print_stacktrace=1
# llvm-symbolizer must be on PATH, but the above installation instals a binary called
# "llvm-symbolizer-3.8", not "llvm-symbolizer". This fixes that with a softlink in a new
# directory.
CLANG_PATH="$(mktemp -d)"
trap "rm -rf ${CLANG_PATH}" EXIT
ln -s "$(whereis llvm-symbolizer-3.8 | rev | cut -d ' ' -f 1 | rev)" \
"${CLANG_PATH}/llvm-symbolizer"
export PATH="${CLANG_PATH}:${PATH}"
llvm-symbolizer -version
build/docker/scripts/autotools.sh $*

View File

@ -4061,9 +4061,9 @@ void t_c_glib_generator::generate_deserialize_field(ofstream& out,
// if the type is not required and this is a thrift struct (no prefix),
// set the isset variable. if the type is required, then set the
// local variable indicating the value was set, so that we can do // validation later.
if (tfield->get_req() != t_field::T_REQUIRED && prefix != "") {
if (prefix != "" && tfield->get_req() != t_field::T_REQUIRED) {
indent(out) << prefix << "__isset_" << tfield->get_name() << suffix << " = TRUE;" << endl;
} else if (tfield->get_req() == t_field::T_REQUIRED && prefix != "") {
} else if (prefix != "" && tfield->get_req() == t_field::T_REQUIRED) {
indent(out) << "isset_" << tfield->get_name() << " = TRUE;" << endl;
}
}

View File

@ -382,7 +382,7 @@ private:
<< (*f_iter)->get_name() << " called\");" << endl;
t_base_type* rt = (t_base_type*)(*f_iter)->get_returntype();
if (rt->get_base() != t_base_type::TYPE_VOID) {
if (!rt->is_void()) {
indent(out) << "return typeof(return).init;" << endl;
}

View File

@ -57,15 +57,15 @@ public:
void set_string_list(bool val) { string_list_ = val; }
bool is_string_list() const { return (base_ == TYPE_STRING) && string_list_; }
bool is_string_list() const { return string_list_ && (base_ == TYPE_STRING); }
void set_binary(bool val) { binary_ = val; }
bool is_binary() const { return (base_ == TYPE_STRING) && binary_; }
bool is_binary() const { return binary_ && (base_ == TYPE_STRING); }
void set_string_enum(bool val) { string_enum_ = val; }
bool is_string_enum() const { return base_ == TYPE_STRING && string_enum_; }
bool is_string_enum() const { return string_enum_ && base_ == TYPE_STRING; }
void add_string_enum_val(std::string val) { string_enum_vals_.push_back(val); }

View File

@ -120,7 +120,7 @@ thrift_bitwise_cast_gdouble (const guint64 v)
static guint64
i64_to_zigzag (const gint64 l)
{
return (l << 1) ^ (l >> 63);
return (((guint64)l) << 1) ^ (l >> 63);
}
/**
@ -130,7 +130,7 @@ i64_to_zigzag (const gint64 l)
static guint32
i32_to_zigzag (const gint32 n)
{
return (n << 1) ^ (n >> 31);
return (((guint32)n) << 1) ^ (n >> 31);
}
/**

View File

@ -387,7 +387,7 @@ uint32_t TCompactProtocolT<Transport_>::writeVarint64(uint64_t n) {
*/
template <class Transport_>
uint64_t TCompactProtocolT<Transport_>::i64ToZigzag(const int64_t l) {
return (l << 1) ^ (l >> 63);
return (static_cast<uint64_t>(l) << 1) ^ (l >> 63);
}
/**
@ -396,7 +396,7 @@ uint64_t TCompactProtocolT<Transport_>::i64ToZigzag(const int64_t l) {
*/
template <class Transport_>
uint32_t TCompactProtocolT<Transport_>::i32ToZigzag(const int32_t n) {
return (n << 1) ^ (n >> 31);
return (static_cast<uint32_t>(n) << 1) ^ (n >> 31);
}
/**