/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 |