Skip to content

Commit c4c7812

Browse files
authored
test msgpack encoder error cases (#15)
* cover more error cases in msgpack encoder * speed up coverage report generation * don't execute a readme file
1 parent 893bd55 commit c4c7812

File tree

7 files changed

+182
-32
lines changed

7 files changed

+182
-32
lines changed

bin/README.md

100755100644
File mode changed.

bin/install-lcov

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,10 @@ cd lcov-1.16/
1010
make install
1111
cd ../
1212
rm -r lcov-1.16 lcov-1.16.tar.gz
13+
14+
# Install a faster JSON library for Perl using Perl's package manager, cpan.
15+
# Newer versions of lcov use JSON instead of an old binary format, so if the
16+
# JSON library is slow, lcov is slow.
17+
# The PERL_MM_USE_DEFAULT=1 environment variable prevents cpan from prompting
18+
# us.
19+
PERL_MM_USE_DEFAULT=1 cpan JSON:XS

bin/test

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ if [ "$coverage_flags" != '' ]; then
5555
rm -rf .coverage
5656
mkdir -p .coverage
5757
# Compile the .gcdo files into an intermediate format.
58-
lcov --quiet --capture --directory .build --output-file .coverage/raw.info # -rc geninfo_json_module=JSON::XS
58+
# Use the faster JSON Perl module, JSON:XS, to speed things up.
59+
lcov --quiet --capture --directory .build --output-file .coverage/raw.info --rc geninfo_json_module=JSON::XS
5960
# Filter out system headers and test drivers.
6061
lcov --quiet --remove .coverage/raw.info -o .coverage/filtered.info '/usr/*' "$(pwd)/test/*" '*json.hpp'
6162
# Generate an HTML coverage report at ".coverage/report/index.html".

src/datadog/msgpack.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "msgpack.h"
22

33
#include <cassert>
4+
#include <climits>
45
#include <limits>
56
#include <type_traits>
67

@@ -50,7 +51,7 @@ void push_number_big_endian(std::string& buffer, Integer integer) {
5051
// effectively copies the bytes of `value` backwards.
5152
const int size = sizeof value;
5253
for (int i = 0; i < size; ++i) {
53-
const char byte = (value >> (8 * ((size - 1) - i))) & 0xFF;
54+
const char byte = (value >> (CHAR_BIT * ((size - 1) - i))) & 0xFF;
5455
buf[i] = byte;
5556
}
5657

@@ -93,20 +94,20 @@ void pack_double(std::string& buffer, double value) {
9394
push_number_big_endian(buffer, memory.as_integer);
9495
}
9596

96-
Expected<void> pack_string(std::string& buffer, StringView value) {
97-
const auto size = value.size();
97+
Expected<void> pack_string(std::string& buffer, const char* begin,
98+
std::size_t size) {
9899
const auto max = std::numeric_limits<std::uint32_t>::max();
99100
if (size > max) {
100101
return Error{Error::MESSAGEPACK_ENCODE_FAILURE,
101102
make_overflow_message("string", size, max)};
102103
}
103104
buffer.push_back(static_cast<char>(types::STR32));
104105
push_number_big_endian(buffer, static_cast<std::uint32_t>(size));
105-
buffer.append(value.begin(), value.end());
106+
buffer.append(begin, size);
106107
return {};
107108
}
108109

109-
Expected<void> pack_array(std::string& buffer, size_t size) {
110+
Expected<void> pack_array(std::string& buffer, std::size_t size) {
110111
const auto max = std::numeric_limits<std::uint32_t>::max();
111112
if (size > max) {
112113
return Error{Error::MESSAGEPACK_ENCODE_FAILURE,
@@ -117,7 +118,7 @@ Expected<void> pack_array(std::string& buffer, size_t size) {
117118
return {};
118119
}
119120

120-
Expected<void> pack_map(std::string& buffer, size_t size) {
121+
Expected<void> pack_map(std::string& buffer, std::size_t size) {
121122
const auto max = std::numeric_limits<std::uint32_t>::max();
122123
if (size > max) {
123124
return Error{Error::MESSAGEPACK_ENCODE_FAILURE,

src/datadog/msgpack.h

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//
1212
// [1]: https://msgpack.org/index.html
1313

14+
#include <climits>
1415
#include <cstddef>
1516
#include <cstdint>
1617
#include <string>
@@ -30,6 +31,8 @@ void pack_integer(std::string& buffer, std::int32_t value);
3031
void pack_double(std::string& buffer, double value);
3132

3233
Expected<void> pack_string(std::string& buffer, StringView value);
34+
Expected<void> pack_string(std::string& buffer, const char* begin,
35+
std::size_t size);
3336

3437
Expected<void> pack_array(std::string& buffer, std::size_t size);
3538

@@ -65,17 +68,16 @@ Expected<void> pack_map(std::string& buffer, const PairIterable& pairs,
6568
// an even number of arguments. First in each pair of arguments is `key`, the
6669
// key name of the corresponding map item. Second in each pair of arguments is
6770
// `pack_value`, a function that encodes the corresponding value. `pack_value`
68-
// is invoked with two arguments: the first is a reference to `buffer`, and the
69-
// second is a reference to the current value. `pack_value` returns an
70-
// `Expected<void>`. If the return value is an error, then iteration is halted
71-
// and the error is returned. If some other error occurs, then an error is
72-
// returned. Otherwise, the non-error value is returned.
73-
template <typename Key, typename PackValue, typename... Rest>
74-
Expected<void> pack_map(std::string& buffer, Key&& key, PackValue&& pack_value,
75-
Rest&&... rest);
76-
77-
template <typename Key, typename PackValue, typename... Rest>
78-
Expected<void> pack_map_suffix(std::string& buffer, Key&& key,
71+
// is invoked with one argument: a reference to `buffer`. `pack_value` returns
72+
// an `Expected<void>`. If the return value is an error, then iteration is
73+
// halted and the error is returned. If some other error occurs, then an error
74+
// is returned. Otherwise, the non-error value is returned.
75+
template <typename PackValue, typename... Rest>
76+
Expected<void> pack_map(std::string& buffer, StringView key,
77+
PackValue&& pack_value, Rest&&... rest);
78+
79+
template <typename PackValue, typename... Rest>
80+
Expected<void> pack_map_suffix(std::string& buffer, StringView key,
7981
PackValue&& pack_value, Rest&&... rest);
8082
Expected<void> pack_map_suffix(std::string& buffer);
8183

@@ -117,25 +119,23 @@ Expected<void> pack_map(std::string& buffer, const PairIterable& pairs,
117119
return result;
118120
}
119121

120-
template <typename Key, typename PackValue, typename... Rest>
121-
Expected<void> pack_map(std::string& buffer, Key&& key, PackValue&& pack_value,
122-
Rest&&... rest) {
123-
Expected<void> result;
122+
template <typename PackValue, typename... Rest>
123+
Expected<void> pack_map(std::string& buffer, StringView key,
124+
PackValue&& pack_value, Rest&&... rest) {
124125
static_assert(
125126
sizeof...(rest) % 2 == 0,
126127
"pack_map must receive an even number of arguments after the first.");
127-
result = pack_map(buffer, 1 + sizeof...(rest) / 2);
128-
if (!result) {
129-
return result;
130-
}
131-
result = pack_map_suffix(buffer, std::forward<Key>(key),
132-
std::forward<PackValue>(pack_value),
133-
std::forward<Rest>(rest)...);
134-
return result;
128+
static_assert(
129+
sizeof...(rest) / 2 <= UINT32_MAX,
130+
"You're passing more than eight billion arguments to a function.");
131+
(void)pack_map(buffer, 1 + sizeof...(rest) / 2);
132+
133+
return pack_map_suffix(buffer, key, std::forward<PackValue>(pack_value),
134+
std::forward<Rest>(rest)...);
135135
}
136136

137-
template <typename Key, typename PackValue, typename... Rest>
138-
Expected<void> pack_map_suffix(std::string& buffer, Key&& key,
137+
template <typename PackValue, typename... Rest>
138+
Expected<void> pack_map_suffix(std::string& buffer, StringView key,
139139
PackValue&& pack_value, Rest&&... rest) {
140140
Expected<void> result;
141141
result = pack_string(buffer, key);
@@ -159,6 +159,10 @@ inline void pack_integer(std::string& buffer, std::int32_t value) {
159159
pack_integer(buffer, std::int64_t(value));
160160
}
161161

162+
inline Expected<void> pack_string(std::string& buffer, StringView value) {
163+
return pack_string(buffer, value.begin(), value.size());
164+
}
165+
162166
} // namespace msgpack
163167
} // namespace tracing
164168
} // namespace datadog

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ add_executable(tests
2121
test_datadog_agent.cpp
2222
test_glob.cpp
2323
test_limiter.cpp
24+
test_msgpack.cpp
2425
test_smoke.cpp
2526
test_span.cpp
2627
test_span_sampler.cpp

test/test_msgpack.cpp

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#include <datadog/error.h>
2+
#include <datadog/msgpack.h>
3+
4+
#include <cstdint>
5+
#include <string>
6+
#include <utility>
7+
8+
#include "test.h"
9+
10+
namespace datadog {
11+
namespace tracing {
12+
13+
bool operator==(const Error& left, const Error& right) {
14+
return left.code == right.code && left.message == right.message;
15+
}
16+
17+
} // namespace tracing
18+
} // namespace datadog
19+
20+
using namespace datadog::tracing;
21+
22+
TEST_CASE("array element fails to encode") {
23+
std::string destination;
24+
const int dummy[] = {42};
25+
const Error error{Error::OTHER, "any error will do"};
26+
const auto result = msgpack::pack_array(
27+
destination, dummy, [&](std::string&, int) { return error; });
28+
29+
REQUIRE_FALSE(result);
30+
REQUIRE(result.error() == error);
31+
}
32+
33+
TEST_CASE("map element fails to encode") {
34+
std::string destination;
35+
const Error error{Error::OTHER, "any error will do"};
36+
37+
SECTION("sequence of pairs") {
38+
const std::pair<std::string, int> pairs[] = {
39+
std::pair<std::string, int>{"dummy", 42}};
40+
const auto result = msgpack::pack_map(
41+
destination, pairs, [&](std::string&, int) { return error; });
42+
43+
REQUIRE_FALSE(result);
44+
REQUIRE(result.error() == error);
45+
}
46+
47+
SECTION("key/value arguments") {
48+
const auto succeed = [](std::string&) { return nullopt; };
49+
const auto fail = [&](std::string&) { return error; };
50+
const auto result =
51+
msgpack::pack_map(destination, "foo", succeed, "bar", fail);
52+
53+
REQUIRE_FALSE(result);
54+
REQUIRE(result.error() == error);
55+
}
56+
}
57+
58+
// The following group of tests verify that encoding routines return an error
59+
// if the size of their input cannot fit in 32 bits.
60+
// This is impossible to do on a 32-bit system, so these tests are excluded by
61+
// the preprocessor when the pointer size is not greater than four.
62+
#if UINTPTR_MAX > UINT32_MAX
63+
64+
// `OversizedSequence` can be used to test oversized inputs for both the
65+
// sequence overloads of both the array and map encoding functions.
66+
struct OversizedSequence {
67+
std::size_t size() const {
68+
return std::size_t(std::numeric_limits<std::uint32_t>::max()) + 1;
69+
}
70+
71+
const std::pair<std::string, int>* begin() const { return nullptr; }
72+
const std::pair<std::string, int>* end() const { return nullptr; }
73+
};
74+
75+
TEST_CASE("oversized string") {
76+
const char* const dummy = "doesn't matter";
77+
const auto oversized =
78+
std::size_t(std::numeric_limits<std::uint32_t>::max()) + 1;
79+
std::string destination;
80+
const auto result = msgpack::pack_string(destination, dummy, oversized);
81+
82+
REQUIRE_FALSE(result);
83+
REQUIRE(result.error().code == Error::MESSAGEPACK_ENCODE_FAILURE);
84+
REQUIRE(destination == "");
85+
}
86+
87+
TEST_CASE("oversized array") {
88+
std::string destination;
89+
90+
SECTION("just the header") {
91+
const auto oversized =
92+
std::size_t(std::numeric_limits<std::uint32_t>::max()) + 1;
93+
const auto result = msgpack::pack_array(destination, oversized);
94+
95+
REQUIRE_FALSE(result);
96+
REQUIRE(result.error().code == Error::MESSAGEPACK_ENCODE_FAILURE);
97+
}
98+
99+
SECTION("sequence") {
100+
const auto result =
101+
msgpack::pack_array(destination, OversizedSequence{},
102+
[](const auto&, const auto&) { return nullopt; });
103+
104+
REQUIRE_FALSE(result);
105+
REQUIRE(result.error().code == Error::MESSAGEPACK_ENCODE_FAILURE);
106+
}
107+
108+
REQUIRE(destination == "");
109+
}
110+
111+
TEST_CASE("oversized map") {
112+
SECTION("just the header") {
113+
const auto oversized =
114+
std::size_t(std::numeric_limits<std::uint32_t>::max()) + 1;
115+
std::string destination;
116+
const auto result = msgpack::pack_map(destination, oversized);
117+
118+
REQUIRE_FALSE(result);
119+
REQUIRE(result.error().code == Error::MESSAGEPACK_ENCODE_FAILURE);
120+
REQUIRE(destination == "");
121+
}
122+
123+
SECTION("sequence of pairs") {
124+
std::string destination;
125+
const OversizedSequence oversized;
126+
const auto result =
127+
msgpack::pack_map(destination, oversized,
128+
[](const auto&, const auto&) { return nullopt; });
129+
130+
REQUIRE_FALSE(result);
131+
REQUIRE(result.error().code == Error::MESSAGEPACK_ENCODE_FAILURE);
132+
REQUIRE(destination == "");
133+
}
134+
}
135+
136+
#endif

0 commit comments

Comments
 (0)