Coverage Report

Created: 2025-07-23 06:53

/src/spotify-json/include/spotify/json/detail/decode_helpers.hpp
Line
Count
Source (jump to first uncovered line)
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.83M
    const ptrdiff_t d = 0) {
45
2.83M
  if (json_unlikely(condition)) {
46
1.82k
    fail(context, error, d);
47
1.82k
  }
48
2.83M
}
49
50
template <size_t num_required_bytes, typename string_type>
51
350k
json_force_inline void require_bytes(const decode_context &context, const string_type &error) {
52
350k
  fail_if(context, context.remaining() < num_required_bytes, error);
53
350k
}
void spotify::json::detail::require_bytes<1ul, char [24]>(spotify::json::decode_context const&, char const (&) [24])
Line
Count
Source
51
76.2k
json_force_inline void require_bytes(const decode_context &context, const string_type &error) {
52
76.2k
  fail_if(context, context.remaining() < num_required_bytes, error);
53
76.2k
}
void spotify::json::detail::require_bytes<4ul, char [24]>(spotify::json::decode_context const&, char const (&) [24])
Line
Count
Source
51
3.26k
json_force_inline void require_bytes(const decode_context &context, const string_type &error) {
52
3.26k
  fail_if(context, context.remaining() < num_required_bytes, error);
53
3.26k
}
void spotify::json::detail::require_bytes<1ul, char [20]>(spotify::json::decode_context const&, char const (&) [20])
Line
Count
Source
51
238k
json_force_inline void require_bytes(const decode_context &context, const string_type &error) {
52
238k
  fail_if(context, context.remaining() < num_required_bytes, error);
53
238k
}
void spotify::json::detail::require_bytes<4ul, char [36]>(spotify::json::decode_context const&, char const (&) [36])
Line
Count
Source
51
31.9k
json_force_inline void require_bytes(const decode_context &context, const string_type &error) {
52
31.9k
  fail_if(context, context.remaining() < num_required_bytes, error);
53
31.9k
}
54
55
template <size_t num_required_bytes>
56
3.26k
json_force_inline void require_bytes(const decode_context &context) {
57
3.26k
  require_bytes<num_required_bytes>(context, "Unexpected end of input");
58
3.26k
}
59
60
1.44M
json_force_inline char peek_unchecked(const decode_context &context) {
61
1.44M
  return *context.position;
62
1.44M
}
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
192k
json_force_inline char peek(const decode_context &context) {
71
192k
  return (context.remaining() ? peek_unchecked(context) : 0);
72
192k
}
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
5.33k
json_force_inline bool peek_2(const decode_context &context, const char first, const char second) {
80
5.33k
  if (context.remaining() < 2) {
81
37
    return false;
82
37
  }
83
5.30k
  return first == *context.position && second == *(context.position + 1);
84
5.33k
}
85
86
314k
json_force_inline char next_unchecked(decode_context &context) {
87
314k
  return *(context.position++);
88
314k
}
89
90
template <typename string_type>
91
314k
json_force_inline char next(decode_context &context, const string_type &error) {
92
314k
  require_bytes<1>(context, error);
93
314k
  return next_unchecked(context);
94
314k
}
char spotify::json::detail::next<char [24]>(spotify::json::decode_context&, char const (&) [24])
Line
Count
Source
91
76.2k
json_force_inline char next(decode_context &context, const string_type &error) {
92
76.2k
  require_bytes<1>(context, error);
93
76.2k
  return next_unchecked(context);
94
76.2k
}
char spotify::json::detail::next<char [20]>(spotify::json::decode_context&, char const (&) [20])
Line
Count
Source
91
238k
json_force_inline char next(decode_context &context, const string_type &error) {
92
238k
  require_bytes<1>(context, error);
93
238k
  return next_unchecked(context);
94
238k
}
95
96
76.2k
json_force_inline char next(decode_context &context) {
97
76.2k
  return next(context, "Unexpected end of input");
98
76.2k
}
99
100
1.21M
json_force_inline void skip_unchecked_1(decode_context &context) {
101
1.21M
  context.position++;
102
1.21M
}
103
104
2.30k
json_force_inline void skip_unchecked_n(decode_context &context, const size_t num_bytes) {
105
2.30k
  context.position += num_bytes;
106
2.30k
}
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
76.2k
json_force_inline void skip_1(decode_context &context, char character) {
123
76.2k
  fail_if(context, next(context) != character, "Unexpected input", -1);
124
76.2k
}
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
3.26k
json_force_inline void skip_4(decode_context &context, const char characters[4]) {
132
3.26k
  require_bytes<4>(context);
133
3.26k
  fail_if(context, memcmp(characters, context.position, 4) != 0, "Unexpected input");
134
3.26k
  context.position += 4;
135
3.26k
}
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.41k
json_never_inline void decode_comma_separated(decode_context &context, char intro, char outro, parse_function parse) {
151
2.41k
  skip_1(context, intro);
152
2.41k
  skip_any_whitespace(context);
153
154
2.41k
  if (json_likely(peek(context) != outro)) {
155
2.33k
    parse();
156
2.33k
    skip_any_whitespace(context);
157
158
16.7k
    while (json_likely(peek(context) != outro)) {
159
14.3k
      skip_1(context, ',');
160
14.3k
      skip_any_whitespace(context);
161
14.3k
      parse();
162
14.3k
      skip_any_whitespace(context);
163
14.3k
    }
164
2.33k
  }
165
166
2.41k
  context.position++;
167
2.41k
}
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.41k
json_force_inline void decode_object(decode_context &context, const callback_function &callback) {
177
2.41k
  auto codec = key_codec_type();
178
16.4k
  decode_comma_separated(context, '{', '}', [&]{
179
16.4k
    auto key = codec.decode(context);
180
16.4k
    skip_any_whitespace(context);
181
16.4k
    skip_1(context, ':');
182
16.4k
    skip_any_whitespace(context);
183
16.4k
    callback(std::move(key));
184
16.4k
  });
185
2.41k
}
186
187
1.43k
json_force_inline void skip_true(decode_context &context) {
188
1.43k
  skip_4(context, "true");
189
1.43k
}
190
191
627
json_force_inline void skip_false(decode_context &context) {
192
627
  context.position++;  // skip past the 'f' in 'false', we know it is there
193
627
  skip_4(context, "alse");
194
627
}
195
196
1.20k
json_force_inline void skip_null(decode_context &context) {
197
1.20k
  skip_4(context, "null");
198
1.20k
}
199
200
}  // namespace detail
201
}  // namespace json
202
}  // namespace spotify