Coverage Report

Created: 2026-01-09 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/spotify-json/include/spotify/json/detail/decode_helpers.hpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2014-2019 Spotify AB
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5
 * use this file except in compliance with the License. You may obtain a copy of
6
 * the License at
7
 *
8
 * http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
 * License for the specific language governing permissions and limitations under
14
 * the License.
15
 */
16
17
#pragma once
18
19
#include <cstddef>
20
#include <cstdint>
21
#include <cstring>
22
#include <string>
23
#include <unordered_set>
24
#include <vector>
25
26
#include <spotify/json/decode_context.hpp>
27
#include <spotify/json/detail/macros.hpp>
28
#include <spotify/json/detail/skip_chars.hpp>
29
30
#if _MSC_VER
31
#pragma intrinsic (memcmp)
32
#endif
33
34
namespace spotify {
35
namespace json {
36
namespace detail {
37
38
json_noreturn void fail(const decode_context &context, const char *error, ptrdiff_t d = 0);
39
40
json_force_inline void fail_if(
41
    const decode_context &context,
42
    const bool condition,
43
    const char *error,
44
2.60M
    const ptrdiff_t d = 0) {
45
2.60M
  if (json_unlikely(condition)) {
46
1.71k
    fail(context, error, d);
47
1.71k
  }
48
2.60M
}
49
50
template <size_t num_required_bytes, typename string_type>
51
247k
json_force_inline void require_bytes(const decode_context &context, const string_type &error) {
52
247k
  fail_if(context, context.remaining() < num_required_bytes, error);
53
247k
}
void spotify::json::detail::require_bytes<1ul, char [24]>(spotify::json::decode_context const&, char const (&) [24])
Line
Count
Source
51
51.7k
json_force_inline void require_bytes(const decode_context &context, const string_type &error) {
52
51.7k
  fail_if(context, context.remaining() < num_required_bytes, error);
53
51.7k
}
void spotify::json::detail::require_bytes<4ul, char [24]>(spotify::json::decode_context const&, char const (&) [24])
Line
Count
Source
51
2.07k
json_force_inline void require_bytes(const decode_context &context, const string_type &error) {
52
2.07k
  fail_if(context, context.remaining() < num_required_bytes, error);
53
2.07k
}
void spotify::json::detail::require_bytes<1ul, char [20]>(spotify::json::decode_context const&, char const (&) [20])
Line
Count
Source
51
171k
json_force_inline void require_bytes(const decode_context &context, const string_type &error) {
52
171k
  fail_if(context, context.remaining() < num_required_bytes, error);
53
171k
}
void spotify::json::detail::require_bytes<4ul, char [36]>(spotify::json::decode_context const&, char const (&) [36])
Line
Count
Source
51
21.5k
json_force_inline void require_bytes(const decode_context &context, const string_type &error) {
52
21.5k
  fail_if(context, context.remaining() < num_required_bytes, error);
53
21.5k
}
54
55
template <size_t num_required_bytes>
56
2.07k
json_force_inline void require_bytes(const decode_context &context) {
57
2.07k
  require_bytes<num_required_bytes>(context, "Unexpected end of input");
58
2.07k
}
59
60
1.32M
json_force_inline char peek_unchecked(const decode_context &context) {
61
1.32M
  return *context.position;
62
1.32M
}
63
64
/**
65
 * Peek at the current character that a decode_context refers to. If the
66
 * decode_context has ended, '\0' is returned. This is a useful helper when
67
 * while decoding it is necessary to ensure that the current character is a
68
 * specific one, for example '['.
69
 */
70
134k
json_force_inline char peek(const decode_context &context) {
71
134k
  return (context.remaining() ? peek_unchecked(context) : 0);
72
134k
}
73
74
/**
75
 * Returns true if the next two characters are `first` and `second`.
76
 * Returns false if the characters do not match, or if there is less than 2
77
 * characters remaining.
78
 */
79
3.87k
json_force_inline bool peek_2(const decode_context &context, const char first, const char second) {
80
3.87k
  if (context.remaining() < 2) {
81
35
    return false;
82
35
  }
83
3.83k
  return first == *context.position && second == *(context.position + 1);
84
3.87k
}
85
86
222k
json_force_inline char next_unchecked(decode_context &context) {
87
222k
  return *(context.position++);
88
222k
}
89
90
template <typename string_type>
91
223k
json_force_inline char next(decode_context &context, const string_type &error) {
92
223k
  require_bytes<1>(context, error);
93
223k
  return next_unchecked(context);
94
223k
}
char spotify::json::detail::next<char [24]>(spotify::json::decode_context&, char const (&) [24])
Line
Count
Source
91
51.7k
json_force_inline char next(decode_context &context, const string_type &error) {
92
51.7k
  require_bytes<1>(context, error);
93
51.7k
  return next_unchecked(context);
94
51.7k
}
char spotify::json::detail::next<char [20]>(spotify::json::decode_context&, char const (&) [20])
Line
Count
Source
91
171k
json_force_inline char next(decode_context &context, const string_type &error) {
92
171k
  require_bytes<1>(context, error);
93
171k
  return next_unchecked(context);
94
171k
}
95
96
51.7k
json_force_inline char next(decode_context &context) {
97
51.7k
  return next(context, "Unexpected end of input");
98
51.7k
}
99
100
1.16M
json_force_inline void skip_unchecked_1(decode_context &context) {
101
1.16M
  context.position++;
102
1.16M
}
103
104
2.19k
json_force_inline void skip_unchecked_n(decode_context &context, const size_t num_bytes) {
105
2.19k
  context.position += num_bytes;
106
2.19k
}
107
108
0
json_force_inline void skip_any_n(decode_context &context, const size_t num_bytes) {
109
0
  fail_if(context, context.remaining() < num_bytes, "Unexpected end of input");
110
0
  skip_unchecked_n(context, num_bytes);
111
0
}
112
113
0
json_force_inline void skip_any_1(decode_context &context) {
114
0
  require_bytes<1>(context, "Unexpected end of input");
115
0
  context.position++;
116
0
}
117
118
/**
119
 * Skip past a specific character. If the context position does not point to a
120
 * matching character, a decode_exception is thrown.
121
 */
122
51.7k
json_force_inline void skip_1(decode_context &context, char character) {
123
51.7k
  fail_if(context, next(context) != character, "Unexpected input", -1);
124
51.7k
}
125
126
/**
127
 * Skip past four specific characters. If the context position does not point to
128
 * matching characters, a decode_exception is thrown. 'characters' must be a C
129
 * string of at least length 4. Only the first four characters will be read.
130
 */
131
2.07k
json_force_inline void skip_4(decode_context &context, const char characters[4]) {
132
2.07k
  require_bytes<4>(context);
133
2.07k
  fail_if(context, memcmp(characters, context.position, 4) != 0, "Unexpected input");
134
2.07k
  context.position += 4;
135
2.07k
}
136
137
/**
138
 * Helper function for parsing the comma separated entities in JSON: objects
139
 * and arrays. intro and outro are the characters before and after the entity:
140
 * {} and [], respectively. parse is a callback that is called for each
141
 * element in the comma separated list. It should advance the parse context to
142
 * after that element or mark the context as failed.
143
 *
144
 * The parse callback must mark the context as failed if it sees a premature end
145
 * of input, otherwise this function might enter an infinite loop!
146
 *
147
 * context.has_failed() must be false when this function is called.
148
 */
149
template <typename parse_function>
150
2.34k
json_never_inline void decode_comma_separated(decode_context &context, char intro, char outro, parse_function parse) {
151
2.34k
  skip_1(context, intro);
152
2.34k
  skip_any_whitespace(context);
153
154
2.34k
  if (json_likely(peek(context) != outro)) {
155
2.26k
    parse();
156
2.26k
    skip_any_whitespace(context);
157
158
12.4k
    while (json_likely(peek(context) != outro)) {
159
10.1k
      skip_1(context, ',');
160
10.1k
      skip_any_whitespace(context);
161
10.1k
      parse();
162
10.1k
      skip_any_whitespace(context);
163
10.1k
    }
164
2.26k
  }
165
166
2.34k
  context.position++;
167
2.34k
}
168
169
/**
170
 * Helper for parsing JSON objects. callback is called once for each key/value
171
 * pair. It is given the already parsed key and is expected to parse the value
172
 * and store it away as needed. The callback may be invoked a few times even if
173
 * parsing fails later on.
174
 */
175
template <typename key_codec_type, typename callback_function>
176
2.34k
json_force_inline void decode_object(decode_context &context, const callback_function &callback) {
177
2.34k
  auto codec = key_codec_type();
178
12.1k
  decode_comma_separated(context, '{', '}', [&]{
179
12.1k
    auto key = codec.decode(context);
180
12.1k
    skip_any_whitespace(context);
181
12.1k
    skip_1(context, ':');
182
12.1k
    skip_any_whitespace(context);
183
12.1k
    callback(std::move(key));
184
12.1k
  });
185
2.34k
}
186
187
787
json_force_inline void skip_true(decode_context &context) {
188
787
  skip_4(context, "true");
189
787
}
190
191
884
json_force_inline void skip_false(decode_context &context) {
192
884
  context.position++;  // skip past the 'f' in 'false', we know it is there
193
884
  skip_4(context, "alse");
194
884
}
195
196
402
json_force_inline void skip_null(decode_context &context) {
197
402
  skip_4(context, "null");
198
402
}
199
200
}  // namespace detail
201
}  // namespace json
202
}  // namespace spotify