Coverage Report

Created: 2025-08-03 06:22

/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
3.28M
    const ptrdiff_t d = 0) {
45
3.28M
  if (json_unlikely(condition)) {
46
1.79k
    fail(context, error, d);
47
1.79k
  }
48
3.28M
}
49
50
template <size_t num_required_bytes, typename string_type>
51
320k
json_force_inline void require_bytes(const decode_context &context, const string_type &error) {
52
320k
  fail_if(context, context.remaining() < num_required_bytes, error);
53
320k
}
void spotify::json::detail::require_bytes<1ul, char [24]>(spotify::json::decode_context const&, char const (&) [24])
Line
Count
Source
51
69.4k
json_force_inline void require_bytes(const decode_context &context, const string_type &error) {
52
69.4k
  fail_if(context, context.remaining() < num_required_bytes, error);
53
69.4k
}
void spotify::json::detail::require_bytes<4ul, char [24]>(spotify::json::decode_context const&, char const (&) [24])
Line
Count
Source
51
1.81k
json_force_inline void require_bytes(const decode_context &context, const string_type &error) {
52
1.81k
  fail_if(context, context.remaining() < num_required_bytes, error);
53
1.81k
}
void spotify::json::detail::require_bytes<1ul, char [20]>(spotify::json::decode_context const&, char const (&) [20])
Line
Count
Source
51
216k
json_force_inline void require_bytes(const decode_context &context, const string_type &error) {
52
216k
  fail_if(context, context.remaining() < num_required_bytes, error);
53
216k
}
void spotify::json::detail::require_bytes<4ul, char [36]>(spotify::json::decode_context const&, char const (&) [36])
Line
Count
Source
51
32.5k
json_force_inline void require_bytes(const decode_context &context, const string_type &error) {
52
32.5k
  fail_if(context, context.remaining() < num_required_bytes, error);
53
32.5k
}
54
55
template <size_t num_required_bytes>
56
1.81k
json_force_inline void require_bytes(const decode_context &context) {
57
1.81k
  require_bytes<num_required_bytes>(context, "Unexpected end of input");
58
1.81k
}
59
60
1.66M
json_force_inline char peek_unchecked(const decode_context &context) {
61
1.66M
  return *context.position;
62
1.66M
}
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
181k
json_force_inline char peek(const decode_context &context) {
71
181k
  return (context.remaining() ? peek_unchecked(context) : 0);
72
181k
}
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
7.53k
json_force_inline bool peek_2(const decode_context &context, const char first, const char second) {
80
7.53k
  if (context.remaining() < 2) {
81
36
    return false;
82
36
  }
83
7.49k
  return first == *context.position && second == *(context.position + 1);
84
7.53k
}
85
86
285k
json_force_inline char next_unchecked(decode_context &context) {
87
285k
  return *(context.position++);
88
285k
}
89
90
template <typename string_type>
91
285k
json_force_inline char next(decode_context &context, const string_type &error) {
92
285k
  require_bytes<1>(context, error);
93
285k
  return next_unchecked(context);
94
285k
}
char spotify::json::detail::next<char [24]>(spotify::json::decode_context&, char const (&) [24])
Line
Count
Source
91
69.4k
json_force_inline char next(decode_context &context, const string_type &error) {
92
69.4k
  require_bytes<1>(context, error);
93
69.4k
  return next_unchecked(context);
94
69.4k
}
char spotify::json::detail::next<char [20]>(spotify::json::decode_context&, char const (&) [20])
Line
Count
Source
91
216k
json_force_inline char next(decode_context &context, const string_type &error) {
92
216k
  require_bytes<1>(context, error);
93
216k
  return next_unchecked(context);
94
216k
}
95
96
69.4k
json_force_inline char next(decode_context &context) {
97
69.4k
  return next(context, "Unexpected end of input");
98
69.4k
}
99
100
1.45M
json_force_inline void skip_unchecked_1(decode_context &context) {
101
1.45M
  context.position++;
102
1.45M
}
103
104
2.73k
json_force_inline void skip_unchecked_n(decode_context &context, const size_t num_bytes) {
105
2.73k
  context.position += num_bytes;
106
2.73k
}
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
69.4k
json_force_inline void skip_1(decode_context &context, char character) {
123
69.4k
  fail_if(context, next(context) != character, "Unexpected input", -1);
124
69.4k
}
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
1.81k
json_force_inline void skip_4(decode_context &context, const char characters[4]) {
132
1.81k
  require_bytes<4>(context);
133
1.81k
  fail_if(context, memcmp(characters, context.position, 4) != 0, "Unexpected input");
134
1.81k
  context.position += 4;
135
1.81k
}
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
18.9k
    while (json_likely(peek(context) != outro)) {
159
16.6k
      skip_1(context, ',');
160
16.6k
      skip_any_whitespace(context);
161
16.6k
      parse();
162
16.6k
      skip_any_whitespace(context);
163
16.6k
    }
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
18.7k
  decode_comma_separated(context, '{', '}', [&]{
179
18.7k
    auto key = codec.decode(context);
180
18.7k
    skip_any_whitespace(context);
181
18.7k
    skip_1(context, ':');
182
18.7k
    skip_any_whitespace(context);
183
18.7k
    callback(std::move(key));
184
18.7k
  });
185
2.41k
}
186
187
979
json_force_inline void skip_true(decode_context &context) {
188
979
  skip_4(context, "true");
189
979
}
190
191
408
json_force_inline void skip_false(decode_context &context) {
192
408
  context.position++;  // skip past the 'f' in 'false', we know it is there
193
408
  skip_4(context, "alse");
194
408
}
195
196
428
json_force_inline void skip_null(decode_context &context) {
197
428
  skip_4(context, "null");
198
428
}
199
200
}  // namespace detail
201
}  // namespace json
202
}  // namespace spotify