Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix undefined behaviour #646

Merged
merged 16 commits into from
Jun 15, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
if(POLICY CMP0025)
# detect Apple's Clang
cmake_policy(SET CMP0025 NEW)
endif()
if(POLICY CMP0054)
cmake_policy(SET CMP0054 NEW)
endif()
Expand Down Expand Up @@ -28,6 +32,9 @@ option(RAPIDJSON_BUILD_THIRDPARTY_GTEST

option(RAPIDJSON_BUILD_CXX11 "Build rapidjson with C++11 (gcc/clang)" ON)

option(RAPIDJSON_BUILD_ASAN "Build rapidjson with address sanitizer (gcc/clang)" OFF)
option(RAPIDJSON_BUILD_UBSAN "Build rapidjson with undefined behavior sanitizer (gcc/clang)" OFF)

option(RAPIDJSON_HAS_STDSTRING "" OFF)
if(RAPIDJSON_HAS_STDSTRING)
add_definitions(-DRAPIDJSON_HAS_STDSTRING)
Expand All @@ -51,11 +58,35 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
endif()
endif()
if (RAPIDJSON_BUILD_ASAN)
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.8.0")
message(FATAL_ERROR "GCC < 4.8 doesn't support the address sanitizer")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
endif()
endif()
if (RAPIDJSON_BUILD_UBSAN)
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.9.0")
message(FATAL_ERROR "GCC < 4.9 doesn't support the undefined behavior sanitizer")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined")
endif()
endif()
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native -Wall -Wextra -Werror -Wno-missing-field-initializers")
if (RAPIDJSON_BUILD_CXX11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
endif()
if (RAPIDJSON_BUILD_ASAN)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address")
endif()
if (RAPIDJSON_BUILD_UBSAN)
if (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined-trap -fsanitize-undefined-trap-on-error")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined")
endif()
endif()
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
add_definitions(-D_CRT_SECURE_NO_WARNINGS=1)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc")
Expand Down
12 changes: 10 additions & 2 deletions include/rapidjson/document.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "memorystream.h"
#include "encodedstream.h"
#include <new> // placement new
#include <limits>

#ifdef _MSC_VER
RAPIDJSON_DIAG_PUSH
Expand Down Expand Up @@ -952,12 +953,16 @@ class GenericValue {
if (IsUint64()) {
uint64_t u = GetUint64();
volatile double d = static_cast<double>(u);
return static_cast<uint64_t>(d) == u;
return (d >= 0.0)
&& (d < static_cast<double>(std::numeric_limits<uint64_t>::max()))
&& (u == static_cast<uint64_t>(d));
}
if (IsInt64()) {
int64_t i = GetInt64();
volatile double d = static_cast<double>(i);
return static_cast< int64_t>(d) == i;
return (d >= static_cast<double>(std::numeric_limits<int64_t>::min()))
&& (d < static_cast<double>(std::numeric_limits<int64_t>::max()))
&& (i == static_cast<int64_t>(d));
}
return true; // double, int, uint are always lossless
}
Expand All @@ -973,6 +978,9 @@ class GenericValue {
bool IsLosslessFloat() const {
if (!IsNumber()) return false;
double a = GetDouble();
if (a < static_cast<double>(-std::numeric_limits<float>::max())
|| a > static_cast<double>(std::numeric_limits<float>::max()))
return false;
double b = static_cast<double>(static_cast<float>(a));
return a >= b && a <= b; // Prevent -Wfloat-equal
}
Expand Down
6 changes: 5 additions & 1 deletion include/rapidjson/encodings.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,11 @@ struct UTF8 {
}

unsigned char type = GetRange(static_cast<unsigned char>(c));
*codepoint = (0xFF >> type) & static_cast<unsigned char>(c);
if (type >= 32) {
*codepoint = 0;
} else {
*codepoint = (0xFF >> type) & static_cast<unsigned char>(c);
}
bool result = true;
switch (type) {
case 2: TAIL(); return result;
Expand Down
3 changes: 2 additions & 1 deletion include/rapidjson/internal/dtoa.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ inline void DigitGen(const DiyFp& W, const DiyFp& Mp, uint64_t delta, char* buff
kappa--;
if (p2 < delta) {
*K += kappa;
GrisuRound(buffer, *len, delta, p2, one.f, wp_w.f * kPow10[-static_cast<int>(kappa)]);
int index = -static_cast<int>(kappa);
GrisuRound(buffer, *len, delta, p2, one.f, wp_w.f * (index < 9 ? kPow10[-static_cast<int>(kappa)] : 0));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this cause array out of bound?
Otherwise should change to size_t and assert(kappa <= 0)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a test case that causes index to be 13. It's an overflow, not an underflow.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to ask a question here. Since the array kPow10 is of length 10, why not check "index < 10" here? If index is 9, then it is still safe to access kPow10[9]. @efidler

return;
}
}
Expand Down
2 changes: 1 addition & 1 deletion include/rapidjson/internal/strtod.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ inline bool StrtodDiyFp(const char* decimals, size_t length, size_t decimalPosit
size_t remaining = length - i;
const unsigned kUlpShift = 3;
const unsigned kUlp = 1 << kUlpShift;
int error = (remaining == 0) ? 0 : kUlp / 2;
int64_t error = (remaining == 0) ? 0 : kUlp / 2;

DiyFp v(significand, 0);
v = v.Normalize();
Expand Down
8 changes: 6 additions & 2 deletions include/rapidjson/pointer.h
Original file line number Diff line number Diff line change
Expand Up @@ -767,8 +767,12 @@ class GenericPointer {
tokenCount_ = rhs.tokenCount_ + extraToken;
tokens_ = static_cast<Token *>(allocator_->Malloc(tokenCount_ * sizeof(Token) + (nameBufferSize + extraNameBufferSize) * sizeof(Ch)));
nameBuffer_ = reinterpret_cast<Ch *>(tokens_ + tokenCount_);
std::memcpy(tokens_, rhs.tokens_, rhs.tokenCount_ * sizeof(Token));
std::memcpy(nameBuffer_, rhs.nameBuffer_, nameBufferSize * sizeof(Ch));
if (rhs.tokenCount_ > 0) {
std::memcpy(tokens_, rhs.tokens_, rhs.tokenCount_ * sizeof(Token));
}
if (nameBufferSize > 0) {
std::memcpy(nameBuffer_, rhs.nameBuffer_, nameBufferSize * sizeof(Ch));
}

// Adjust pointers to name buffer
std::ptrdiff_t diff = nameBuffer_ - rhs.nameBuffer_;
Expand Down
1 change: 1 addition & 0 deletions include/rapidjson/reader.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ RAPIDJSON_DIAG_OFF(4702) // unreachable code

#ifdef __clang__
RAPIDJSON_DIAG_PUSH
RAPIDJSON_DIAG_OFF(old-style-cast)
RAPIDJSON_DIAG_OFF(padded)
RAPIDJSON_DIAG_OFF(switch-enum)
#endif
Expand Down
12 changes: 8 additions & 4 deletions include/rapidjson/schema.h
Original file line number Diff line number Diff line change
Expand Up @@ -413,9 +413,11 @@ class Schema {
}
}

AssignIfExist(allOf_, *schemaDocument, p, value, GetAllOfString(), document);
AssignIfExist(anyOf_, *schemaDocument, p, value, GetAnyOfString(), document);
AssignIfExist(oneOf_, *schemaDocument, p, value, GetOneOfString(), document);
if (schemaDocument) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems unncessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

schemaDocument is NULL for the Schema returned by Schema::GetTypeless().

AssignIfExist(allOf_, *schemaDocument, p, value, GetAllOfString(), document);
AssignIfExist(anyOf_, *schemaDocument, p, value, GetAnyOfString(), document);
AssignIfExist(oneOf_, *schemaDocument, p, value, GetOneOfString(), document);
}

if (const ValueType* v = GetMember(value, GetNotString())) {
schemaDocument->CreateSchema(&not_, p.Append(GetNotString(), allocator_), *v, document);
Expand Down Expand Up @@ -578,7 +580,9 @@ class Schema {
}

~Schema() {
allocator_->Free(enum_);
if (allocator_) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allocator_ must not null.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allocator_ is NULL for the Schema returned by Schema::GetTypeless(). The destructor is dereferencing the NULL when the global static is torn down during shutdown.

allocator_->Free(enum_);
}
if (properties_) {
for (SizeType i = 0; i < propertyCount_; i++)
properties_[i].~Property();
Expand Down
15 changes: 10 additions & 5 deletions test/unittest/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
include(CheckCXXCompilerFlag)

set(UNITTEST_SOURCES
allocatorstest.cpp
bigintegertest.cpp
Expand Down Expand Up @@ -38,11 +40,14 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror -Wall -Wextra -Weffc++ -Wswitch-default -Wfloat-equal")
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror -Wall -Wextra -Weffc++ -Wswitch-default -Wfloat-equal -Wimplicit-fallthrough -Weverything")
# If the user is running a newer version of Clang that includes the
# -Wdouble-promotion, we will ignore that warning.
# if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 3.7)
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-double-promotion")
# endif()
# If the user is running a newer version of Clang that includes the
# -Wdouble-promotion, we will ignore that warning.
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 3.7)
CHECK_CXX_COMPILER_FLAG("-Wno-double-promotion" HAS_NO_DOUBLE_PROMOTION)
if (HAS_NO_DOUBLE_PROMOTION)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-double-promotion")
endif()
endif()
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
# Force to always compile with /W4
if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]")
Expand Down
1 change: 1 addition & 0 deletions test/unittest/dtoatest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ TEST(dtoa, normal) {
TEST_DTOA(1.2345678, "1.2345678");
TEST_DTOA(0.123456789012, "0.123456789012");
TEST_DTOA(1234567.8, "1234567.8");
TEST_DTOA(-79.39773355813419, "-79.39773355813419");
TEST_DTOA(0.000001, "0.000001");
TEST_DTOA(0.0000001, "1e-7");
TEST_DTOA(1e30, "1e30");
Expand Down
2 changes: 2 additions & 0 deletions test/unittest/itoatest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ static void Verify(void(*f)(T, char*), char* (*g)(T, char*)) {
VerifyValue<T>(Traits<T>::Negate(i + 1), f, g);
}
last = i;
if (i > static_cast<T>(std::numeric_limits<T>::max() / static_cast<T>(power)))
break;
i *= power;
} while (last < i);
}
Expand Down
2 changes: 1 addition & 1 deletion test/unittest/schematest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ TEST(SchemaValidator, Hasher) {
EXPECT_FALSE(d.HasParseError());\
EXPECT_TRUE(expected == d.Accept(validator));\
EXPECT_TRUE(expected == validator.IsValid());\
if (expected && !validator.IsValid()) {\
if ((expected) && !validator.IsValid()) {\
StringBuffer sb;\
validator.GetInvalidSchemaPointer().StringifyUriFragment(sb);\
printf("Invalid schema: %s\n", sb.GetString());\
Expand Down
30 changes: 24 additions & 6 deletions test/unittest/valuetest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -545,8 +545,10 @@ TEST(Value, Int64) {
// Templated functions
EXPECT_TRUE(z.Is<int64_t>());
EXPECT_EQ(i, z.Get<int64_t>());
#if 0 // signed integer underflow is undefined behaviour
EXPECT_EQ(i - 1, z.Set(i - 1).Get<int64_t>());
EXPECT_EQ(i - 2, z.Set<int64_t>(i - 2).Get<int64_t>());
#endif
}

TEST(Value, Uint64) {
Expand Down Expand Up @@ -671,6 +673,7 @@ TEST(Value, Float) {
}

TEST(Value, IsLosslessDouble) {
EXPECT_TRUE(Value(0.0).IsLosslessDouble());
EXPECT_TRUE(Value(12.34).IsLosslessDouble());
EXPECT_TRUE(Value(-123).IsLosslessDouble());
EXPECT_TRUE(Value(2147483648u).IsLosslessDouble());
Expand All @@ -679,8 +682,19 @@ TEST(Value, IsLosslessDouble) {
EXPECT_TRUE(Value(RAPIDJSON_UINT64_C2(0xA0000000, 0x00000000)).IsLosslessDouble());
#endif

EXPECT_FALSE(Value(-static_cast<int64_t>(RAPIDJSON_UINT64_C2(0x7FFFFFFF, 0xFFFFFFFF))).IsLosslessDouble());
EXPECT_FALSE(Value(RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0xFFFFFFFF)).IsLosslessDouble());
EXPECT_FALSE(Value(static_cast<int64_t>(RAPIDJSON_UINT64_C2(0x7FFFFFFF, 0xFFFFFFFF))).IsLosslessDouble()); // INT64_MAX
EXPECT_FALSE(Value(-static_cast<int64_t>(RAPIDJSON_UINT64_C2(0x7FFFFFFF, 0xFFFFFFFF))).IsLosslessDouble()); // -INT64_MAX
EXPECT_TRUE(Value(-static_cast<int64_t>(RAPIDJSON_UINT64_C2(0x7FFFFFFF, 0xFFFFFFFF)) - 1).IsLosslessDouble()); // INT64_MIN
EXPECT_FALSE(Value(RAPIDJSON_UINT64_C2(0xFFFFFFFF, 0xFFFFFFFF)).IsLosslessDouble()); // UINT64_MAX

EXPECT_TRUE(Value(3.4028234e38f).IsLosslessDouble()); // FLT_MAX
EXPECT_TRUE(Value(-3.4028234e38f).IsLosslessDouble()); // -FLT_MAX
EXPECT_TRUE(Value(1.17549435e-38f).IsLosslessDouble()); // FLT_MIN
EXPECT_TRUE(Value(-1.17549435e-38f).IsLosslessDouble()); // -FLT_MIN
EXPECT_TRUE(Value(1.7976931348623157e+308).IsLosslessDouble()); // DBL_MAX
EXPECT_TRUE(Value(-1.7976931348623157e+308).IsLosslessDouble()); // -DBL_MAX
EXPECT_TRUE(Value(2.2250738585072014e-308).IsLosslessDouble()); // DBL_MIN
EXPECT_TRUE(Value(-2.2250738585072014e-308).IsLosslessDouble()); // -DBL_MIN
}

TEST(Value, IsLosslessFloat) {
Expand Down Expand Up @@ -1119,14 +1133,18 @@ TEST(Value, ArrayHelperRangeFor) {

{
int i = 0;
for (auto& v : x.GetArray())
EXPECT_EQ(i++, v.GetInt());
for (auto& v : x.GetArray()) {
EXPECT_EQ(i, v.GetInt());
i++;
}
EXPECT_EQ(i, 10);
}
{
int i = 0;
for (const auto& v : const_cast<const Value&>(x).GetArray())
EXPECT_EQ(i++, v.GetInt());
for (const auto& v : const_cast<const Value&>(x).GetArray()) {
EXPECT_EQ(i, v.GetInt());
i++;
}
EXPECT_EQ(i, 10);
}

Expand Down
9 changes: 4 additions & 5 deletions test/unittest/writertest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -439,11 +439,9 @@ TEST(Writer, InvalidEventSequence) {
}
}

extern double zero; // clang -Wmissing-variable-declarations
double zero = 0.0; // Use global variable to prevent compiler warning

TEST(Writer, NaN) {
double nan = zero / zero;
double nan = std::numeric_limits<double>::quiet_NaN();

EXPECT_TRUE(internal::Double(nan).IsNan());
StringBuffer buffer;
{
Expand All @@ -461,7 +459,8 @@ TEST(Writer, NaN) {
}

TEST(Writer, Inf) {
double inf = 1.0 / zero;
double inf = std::numeric_limits<double>::infinity();

EXPECT_TRUE(internal::Double(inf).IsInf());
StringBuffer buffer;
{
Expand Down