• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

realm / realm-core / github_pull_request_281922

31 Oct 2023 09:13AM UTC coverage: 90.445% (-0.08%) from 90.528%
github_pull_request_281922

Pull #7039

Evergreen

jedelbo
Merge branch 'next-major' into je/global-key
Pull Request #7039: Remove ability to synchronize objects without primary key

95324 of 175822 branches covered (0.0%)

101 of 105 new or added lines in 13 files covered. (96.19%)

238 existing lines in 19 files now uncovered.

232657 of 257235 relevant lines covered (90.45%)

6351359.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.96
/src/realm/sync/changeset_parser.cpp
1

2
#include <realm/sync/changeset_parser.hpp>
3

4
#include <realm/global_key.hpp>
5
#include <realm/mixed.hpp>
6
#include <realm/sync/changeset.hpp>
7
#include <realm/sync/instructions.hpp>
8
#include <realm/sync/noinst/integer_codec.hpp>
9
#include <realm/table.hpp>
10
#include <realm/util/base64.hpp>
11

12
#include <unordered_set>
13

14
using namespace realm;
15
using namespace realm::sync;
16

17
namespace {
18

19
struct State {
20
    util::InputStream& m_input;
21
    InstructionHandler& m_handler;
22

23
    explicit State(util::InputStream& input, InstructionHandler& handler)
24
        : m_input(input)
25
        , m_handler(handler)
26
    {
10,594,978✔
27
    }
10,594,978✔
28

29
    // pointer into transaction log, each instruction is parsed from m_input_begin and onwards.
30
    // Each instruction are assumed to be contiguous in memory.
31
    const char* m_input_begin = nullptr;
32
    // pointer to one past current instruction log chunk. If m_input_begin reaches m_input_end,
33
    // a call to next_input_buffer will move m_input_begin and m_input_end to a new chunk of
34
    // memory. Setting m_input_end to 0 disables this check, and is used if it is already known
35
    // that all of the instructions are in memory.
36
    const char* m_input_end = nullptr;
37

38
    std::string m_buffer;
39
    // Cannot use StringData as key type since m_input_begin may start pointing
40
    // to a new chunk of memory.
41
    std::unordered_set<std::string> m_intern_strings;
42

43

44
    void parse_one(); // Throws
45
    bool has_next() noexcept;
46

47
    // Advance m_input_begin and m_input_end to reflect the next block of
48
    // instructions.
49
    // Returns false if no more input was available
50
    bool next_input_buffer() noexcept;
51

52
    template <class T = int64_t>
53
    T read_int(); // Throws
54

55
    util::Optional<Instruction::Payload::Type> read_optional_payload_type();
56
    Instruction::Payload::Type read_payload_type();
57
    Instruction::AddColumn::CollectionType read_collection_type();
58
    Instruction::Payload read_payload();
59
    Instruction::Payload::Link read_link();
60
    Instruction::PrimaryKey read_object_key();
61
    Instruction::Path read_path();
62
    bool read_char(char& c) noexcept;
63
    void read_bytes(char* data, size_t size); // Throws
64
    bool read_bool();                         // Throws
65
    float read_float();                       // Throws
66
    double read_double();                     // Throws
67
    InternString read_intern_string();        // Throws
68
    GlobalKey read_global_key();              // Throws
69
    Timestamp read_timestamp();               // Throws
70
    ObjectId read_object_id();                // Throws
71
    Decimal128 read_decimal();                // Throws
72
    UUID read_uuid();                         // Throws
73

74
    void read_path_instr(Instruction::PathInstruction& instr);
75

76
    // Reads a string value from the stream. The returned value is only valid
77
    // until the next call to `read_string()` or `read_binary()`.
78
    StringData read_string(); // Throws
79

80
    // Reads a binary blob value from the stream. The returned value is only
81
    // valid until the next call to `read_string()` or `read_binary()`.
82
    BinaryData read_binary(); // Throws
83

84
    BinaryData read_buffer(size_t size);
85

86
    REALM_NORETURN void parser_error(std::string_view complaint); // Throws
87
    REALM_NORETURN void parser_error()
88
    {
×
89
        parser_error("Bad input");
×
90
    } // Throws
×
91
};
92

93
struct UnreachableInstructionHandler : public InstructionHandler {
94
    void set_intern_string(uint32_t, StringBufferRange) override
95
    {
×
96
        REALM_UNREACHABLE();
×
97
    }
×
98

99
    StringBufferRange add_string_range(StringData) override
100
    {
×
101
        REALM_UNREACHABLE();
×
102
    }
×
103

104
    void operator()(const Instruction&) override
105
    {
×
106
        REALM_UNREACHABLE();
×
107
    }
×
108
};
109

110
struct InstructionBuilder : InstructionHandler {
111
    explicit InstructionBuilder(Changeset& log)
112
        : m_log(log)
113
    {
10,594,940✔
114
        log.interned_strings().clear();
10,594,940✔
115
    }
10,594,940✔
116
    Changeset& m_log;
117

118
    void operator()(const Instruction& instr) final
119
    {
9,865,046✔
120
        m_log.push_back(instr);
9,865,046✔
121
    }
9,865,046✔
122

123
    StringBufferRange add_string_range(StringData string) final
124
    {
15,166,084✔
125
        return m_log.append_string(string);
15,166,084✔
126
    }
15,166,084✔
127

128
    void set_intern_string(uint32_t index, StringBufferRange range) final
129
    {
14,100,796✔
130
        InternStrings& strings = m_log.interned_strings();
14,100,796✔
131
        REALM_ASSERT(index == strings.size());
14,100,796✔
132
        strings.push_back(range);
14,100,796✔
133
    }
14,100,796✔
134
};
135

136
Instruction::Payload::Type State::read_payload_type()
137
{
12,470,396✔
138
    using Type = Instruction::Payload::Type;
12,470,396✔
139
    auto type = Instruction::Payload::Type(read_int());
12,470,396✔
140
    // Validate the type.
6,131,528✔
141
    switch (type) {
12,470,396✔
UNCOV
142
        case Type::GlobalKey:
✔
UNCOV
143
            [[fallthrough]];
×
144
        case Type::Erased:
332✔
145
            [[fallthrough]];
332✔
146
        case Type::Set:
332✔
147
            [[fallthrough]];
332✔
148
        case Type::List:
332✔
149
            [[fallthrough]];
332✔
150
        case Type::Dictionary:
332✔
151
            [[fallthrough]];
332✔
152
        case Type::ObjectValue:
4,994✔
153
            [[fallthrough]];
4,994✔
154
        case Type::Null:
38,350✔
155
            [[fallthrough]];
38,350✔
156
        case Type::Int:
9,471,592✔
157
            [[fallthrough]];
9,471,592✔
158
        case Type::Bool:
9,472,120✔
159
            [[fallthrough]];
9,472,120✔
160
        case Type::String:
12,229,496✔
161
            [[fallthrough]];
12,229,496✔
162
        case Type::Binary:
12,249,844✔
163
            [[fallthrough]];
12,249,844✔
164
        case Type::Timestamp:
12,254,312✔
165
            [[fallthrough]];
12,254,312✔
166
        case Type::Float:
12,255,036✔
167
            [[fallthrough]];
12,255,036✔
168
        case Type::Double:
12,256,500✔
169
            [[fallthrough]];
12,256,500✔
170
        case Type::Decimal:
12,256,924✔
171
            [[fallthrough]];
12,256,924✔
172
        case Type::Link:
12,265,284✔
173
            [[fallthrough]];
12,265,284✔
174
        case Type::ObjectId:
12,469,372✔
175
            [[fallthrough]];
12,469,372✔
176
        case Type::UUID:
12,470,468✔
177
            return type;
12,470,468✔
178
    }
×
179
    parser_error("Unsupported data type");
×
180
}
×
181

182
Instruction::AddColumn::CollectionType State::read_collection_type()
183
{
597,964✔
184
    using CollectionType = Instruction::AddColumn::CollectionType;
597,964✔
185
    auto type = Instruction::AddColumn::CollectionType(read_int<uint8_t>());
597,964✔
186
    // Validate the type.
295,038✔
187
    switch (type) {
597,964✔
188
        case CollectionType::Single:
397,104✔
189
            [[fallthrough]];
397,104✔
190
        case CollectionType::List:
597,190✔
191
            [[fallthrough]];
597,190✔
192
        case CollectionType::Dictionary:
597,678✔
193
            [[fallthrough]];
597,678✔
194
        case CollectionType::Set:
597,962✔
195
            return type;
597,962✔
196
    }
×
197
    parser_error("Unsupported collection type");
×
198
}
×
199

200
Instruction::Payload State::read_payload()
201
{
2,747,460✔
202
    using Type = Instruction::Payload::Type;
2,747,460✔
203

1,361,014✔
204
    Instruction::Payload payload;
2,747,460✔
205
    payload.type = read_payload_type();
2,747,460✔
206
    auto& data = payload.data;
2,747,460✔
207
    switch (payload.type) {
2,747,460✔
208
        case Type::GlobalKey: {
✔
209
            parser_error("Unsupported payload data type");
×
210
        }
×
211
        case Type::Int: {
1,632,862✔
212
            data.integer = read_int();
1,632,862✔
213
            return payload;
1,632,862✔
214
        }
×
215
        case Type::Bool: {
328✔
216
            data.boolean = read_bool();
328✔
217
            return payload;
328✔
218
        }
×
219
        case Type::Float: {
484✔
220
            data.fnum = read_float();
484✔
221
            return payload;
484✔
222
        }
×
223
        case Type::Double: {
1,236✔
224
            data.dnum = read_double();
1,236✔
225
            return payload;
1,236✔
226
        }
×
227
        case Type::String: {
1,045,290✔
228
            StringData value = read_string();
1,045,290✔
229
            data.str = m_handler.add_string_range(value);
1,045,290✔
230
            return payload;
1,045,290✔
231
        }
×
232
        case Type::Binary: {
20,120✔
233
            BinaryData value = read_binary();
20,120✔
234
            data.binary = m_handler.add_string_range(StringData{value.data(), value.size()});
20,120✔
235
            return payload;
20,120✔
236
        }
×
237
        case Type::Timestamp: {
4,064✔
238
            data.timestamp = read_timestamp();
4,064✔
239
            return payload;
4,064✔
240
        }
×
241
        case Type::ObjectId: {
1,356✔
242
            data.object_id = read_object_id();
1,356✔
243
            return payload;
1,356✔
244
        }
×
245
        case Type::Decimal: {
276✔
246
            data.decimal = read_decimal();
276✔
247
            return payload;
276✔
248
        }
×
249
        case Type::UUID: {
472✔
250
            data.uuid = read_uuid();
472✔
251
            return payload;
472✔
252
        }
×
253
        case Type::Link: {
2,772✔
254
            data.link = read_link();
2,772✔
255
            return payload;
2,772✔
256
        }
×
257

258
        case Type::Null:
32,964✔
259
            [[fallthrough]];
32,964✔
260
        case Type::Set:
32,964✔
261
            [[fallthrough]];
32,964✔
262
        case Type::List:
32,964✔
263
            [[fallthrough]];
32,964✔
264
        case Type::Dictionary:
32,964✔
265
            [[fallthrough]];
32,964✔
266
        case Type::Erased:
33,296✔
267
            [[fallthrough]];
33,296✔
268
        case Type::ObjectValue:
37,958✔
269
            return payload;
37,958✔
270
    }
×
271

272
    parser_error("Unsupported payload type");
×
273
}
×
274

275
Instruction::PrimaryKey State::read_object_key()
276
{
8,877,180✔
277
    using Type = Instruction::Payload::Type;
8,877,180✔
278
    Type type = read_payload_type();
8,877,180✔
279
    switch (type) {
8,877,180✔
280
        case Type::Null:
88✔
281
            return mpark::monostate{};
88✔
282
        case Type::Int:
7,304,818✔
283
            return read_int();
7,304,818✔
284
        case Type::String:
1,375,428✔
285
            return read_intern_string();
1,375,428✔
UNCOV
286
        case Type::GlobalKey:
✔
UNCOV
287
            return read_global_key();
×
288
        case Type::ObjectId:
196,558✔
289
            return read_object_id();
196,558✔
290
        case Type::UUID:
176✔
291
            return read_uuid();
176✔
292
        default:
✔
293
            break;
×
294
    }
×
295
    parser_error("Unsupported object key type");
×
296
}
×
297

298
Instruction::Payload::Link State::read_link()
299
{
2,772✔
300
    auto target_class = read_intern_string();
2,772✔
301
    auto key = read_object_key();
2,772✔
302
    return Instruction::Payload::Link{target_class, key};
2,772✔
303
}
2,772✔
304

305
Instruction::Path State::read_path()
306
{
3,019,478✔
307
    Instruction::Path path;
3,019,478✔
308
    size_t path_len = read_int<uint32_t>();
3,019,478✔
309

1,484,986✔
310
    // Note: Not reserving `path_len`, because a corrupt changeset could cause std::bad_alloc to be thrown.
1,484,986✔
311
    if (path_len != 0)
3,019,478✔
312
        path.reserve(16);
1,664,792✔
313

1,484,986✔
314
    for (size_t i = 0; i < path_len; ++i) {
4,711,026✔
315
        int64_t element = read_int();
1,691,548✔
316
        if (element >= 0) {
1,691,548✔
317
            // Integer path element
827,462✔
318
            path.push_back(uint32_t(element));
1,667,174✔
319
        }
1,667,174✔
320
        else {
24,374✔
321
            // String path element
12,184✔
322
            path.push_back(read_intern_string());
24,374✔
323
        }
24,374✔
324
    }
1,691,548✔
325

1,484,986✔
326
    return path;
3,019,478✔
327
}
3,019,478✔
328

329
void State::read_path_instr(Instruction::PathInstruction& instr)
330
{
3,019,552✔
331
    instr.table = read_intern_string();
3,019,552✔
332
    instr.object = read_object_key();
3,019,552✔
333
    instr.field = read_intern_string();
3,019,552✔
334
    instr.path = read_path();
3,019,552✔
335
}
3,019,552✔
336

337
void State::parse_one()
338
{
23,964,460✔
339
    uint64_t t = read_int<uint64_t>();
23,964,460✔
340

11,738,756✔
341
    if (t == InstrTypeInternString) {
23,964,460✔
342
        uint32_t index = read_int<uint32_t>();
14,100,236✔
343
        if (index != m_intern_strings.size()) {
14,100,236✔
344
            parser_error(util::format("Unexpected intern index: %1", index));
12✔
345
        }
12✔
346
        StringData str = read_string();
14,100,236✔
347
        if (!m_intern_strings.insert(str).second) {
14,100,236✔
348
            parser_error(util::format("Unexpected intern string: %1", str));
4✔
349
        }
4✔
350
        StringBufferRange range = m_handler.add_string_range(str);
14,100,236✔
351
        m_handler.set_intern_string(index, range);
14,100,236✔
352
        return;
14,100,236✔
353
    }
14,100,236✔
354

4,842,986✔
355
    switch (Instruction::Type(t)) {
9,864,224✔
356
        case Instruction::Type::AddTable: {
249,512✔
357
            Instruction::AddTable instr;
249,512✔
358
            instr.table = read_intern_string();
249,512✔
359
            auto table_type = Table::Type(read_int<uint8_t>());
249,512✔
360
            switch (table_type) {
249,512✔
361
                case Table::Type::TopLevel:
248,744✔
362
                case Table::Type::TopLevelAsymmetric: {
248,818✔
363
                    Instruction::AddTable::TopLevelTable spec;
248,818✔
364
                    spec.pk_field = read_intern_string();
248,818✔
365
                    spec.pk_type = read_payload_type();
248,818✔
366
                    if (!is_valid_key_type(spec.pk_type)) {
248,818✔
367
                        parser_error(util::format("Invalid primary key type in AddTable: %1",
×
368
                                                  static_cast<uint8_t>(spec.pk_type)));
×
369
                    }
×
370
                    spec.pk_nullable = read_bool();
248,818✔
371
                    spec.is_asymmetric = (table_type == Table::Type::TopLevelAsymmetric);
248,818✔
372
                    instr.type = spec;
248,818✔
373
                    break;
248,818✔
374
                }
248,744✔
375
                case Table::Type::Embedded: {
127,008✔
376
                    instr.type = Instruction::AddTable::EmbeddedTable{};
692✔
377
                    break;
692✔
378
                }
248,744✔
379
                default:
126,662✔
380
                    parser_error(util::format("AddTable: unknown table type: %1", table_type));
×
381
            }
249,512✔
382
            m_handler(instr);
249,512✔
383
            return;
249,512✔
384
        }
249,512✔
385
        case Instruction::Type::EraseTable: {
196,580✔
386
            Instruction::EraseTable instr;
142,410✔
387
            instr.table = read_intern_string();
142,410✔
388
            m_handler(instr);
142,410✔
389
            return;
142,410✔
390
        }
249,512✔
391
        case Instruction::Type::CreateObject: {
3,554,090✔
392
            Instruction::CreateObject instr;
3,554,090✔
393
            instr.table = read_intern_string();
3,554,090✔
394
            instr.object = read_object_key();
3,554,090✔
395
            m_handler(instr);
3,554,090✔
396
            return;
3,554,090✔
397
        }
249,512✔
398
        case Instruction::Type::EraseObject: {
2,301,258✔
399
            Instruction::EraseObject instr;
2,301,258✔
400
            instr.table = read_intern_string();
2,301,258✔
401
            instr.object = read_object_key();
2,301,258✔
402
            m_handler(instr);
2,301,258✔
403
            return;
2,301,258✔
404
        }
249,512✔
405
        case Instruction::Type::Update: {
1,543,050✔
406
            Instruction::Update instr;
1,543,050✔
407
            read_path_instr(instr);
1,543,050✔
408
            instr.value = read_payload();
1,543,050✔
409

765,846✔
410
            // If the last path element is a string, we are setting a field. Otherwise, we are setting an array
765,846✔
411
            // element.
765,846✔
412
            if (!instr.is_array_update()) {
1,543,050✔
413
                instr.is_default = read_bool();
1,301,302✔
414
            }
1,301,302✔
415
            else {
241,748✔
416
                instr.prior_size = read_int<uint32_t>();
241,748✔
417
            }
241,748✔
418
            m_handler(instr);
1,543,050✔
419
            return;
1,543,050✔
420
        }
249,512✔
421
        case Instruction::Type::AddInteger: {
160,436✔
422
            Instruction::AddInteger instr;
56,908✔
423
            read_path_instr(instr);
56,908✔
424
            instr.value = read_int();
56,908✔
425
            m_handler(instr);
56,908✔
426
            return;
56,908✔
427
        }
249,512✔
428
        case Instruction::Type::AddColumn: {
597,968✔
429
            Instruction::AddColumn instr;
597,968✔
430
            instr.table = read_intern_string();
597,968✔
431
            instr.field = read_intern_string();
597,968✔
432
            instr.type = read_payload_type();
597,968✔
433
            instr.nullable = read_bool();
597,968✔
434
            instr.collection_type = read_collection_type();
597,968✔
435
            if (instr.type == Instruction::Payload::Type::Link) {
597,968✔
436
                instr.link_target_table = read_intern_string();
5,588✔
437
            }
5,588✔
438
            if (instr.collection_type == Instruction::AddColumn::CollectionType::Dictionary) {
597,968✔
439
                instr.key_type = read_payload_type();
488✔
440
            }
488✔
441
            else {
597,480✔
442
                instr.key_type = Instruction::Payload::Type::Null;
597,480✔
443
            }
597,480✔
444
            m_handler(instr);
597,968✔
445
            return;
597,968✔
446
        }
249,512✔
447
        case Instruction::Type::EraseColumn: {
127,016✔
448
            Instruction::EraseColumn instr;
12✔
449
            instr.table = read_intern_string();
12✔
450
            instr.field = read_intern_string();
12✔
451
            m_handler(instr);
12✔
452
            return;
12✔
453
        }
249,512✔
454
        case Instruction::Type::ArrayInsert: {
1,201,536✔
455
            Instruction::ArrayInsert instr;
1,201,536✔
456
            read_path_instr(instr);
1,201,536✔
457
            if (!instr.path.is_array_index()) {
1,201,536✔
458
                parser_error("ArrayInsert without an index");
×
459
            }
×
460
            instr.value = read_payload();
1,201,536✔
461
            instr.prior_size = read_int<uint32_t>();
1,201,536✔
462
            m_handler(instr);
1,201,536✔
463
            return;
1,201,536✔
464
        }
249,512✔
465
        case Instruction::Type::ArrayMove: {
127,102✔
466
            Instruction::ArrayMove instr;
184✔
467
            read_path_instr(instr);
184✔
468
            if (!instr.path.is_array_index()) {
184✔
469
                parser_error("ArrayMove without an index");
×
470
            }
×
471
            instr.ndx_2 = read_int<uint32_t>();
184✔
472
            instr.prior_size = read_int<uint32_t>();
184✔
473
            m_handler(instr);
184✔
474
            return;
184✔
475
        }
249,512✔
476
        case Instruction::Type::ArrayErase: {
240,144✔
477
            Instruction::ArrayErase instr;
211,250✔
478
            read_path_instr(instr);
211,250✔
479
            if (!instr.path.is_array_index()) {
211,250✔
480
                parser_error("ArrayErase without an index");
×
481
            }
×
482
            instr.prior_size = read_int<uint32_t>();
211,250✔
483
            m_handler(instr);
211,250✔
484
            return;
211,250✔
485
        }
249,512✔
486
        case Instruction::Type::Clear: {
128,456✔
487
            Instruction::Clear instr;
3,834✔
488
            read_path_instr(instr);
3,834✔
489
            uint32_t prior_size = read_int<uint32_t>();
3,834✔
490
            static_cast<void>(prior_size); // Ignored
3,834✔
491
            m_handler(instr);
3,834✔
492
            return;
3,834✔
493
        }
249,512✔
494
        case Instruction::Type::SetInsert: {
128,110✔
495
            Instruction::SetInsert instr;
2,200✔
496
            read_path_instr(instr);
2,200✔
497
            instr.value = read_payload();
2,200✔
498
            m_handler(instr);
2,200✔
499
            return;
2,200✔
500
        }
249,512✔
501
        case Instruction::Type::SetErase: {
127,292✔
502
            Instruction::SetErase instr;
564✔
503
            read_path_instr(instr);
564✔
504
            instr.value = read_payload();
564✔
505
            m_handler(instr);
564✔
506
            return;
564✔
507
        }
8✔
508
    }
8✔
509

4✔
510
    parser_error(util::format("Unknown instruction type: %1", t));
8✔
511
}
8✔
512

513

514
bool State::has_next() noexcept
515
{
34,558,692✔
516
    return m_input_begin != m_input_end || next_input_buffer();
34,558,692✔
517
}
34,558,692✔
518

519
bool State::next_input_buffer() noexcept
520
{
19,579,490✔
521
    auto next = m_input.next_block();
19,579,490✔
522
    m_input_begin = next.begin();
19,579,490✔
523
    m_input_end = next.end();
19,579,490✔
524
    return m_input_begin != m_input_end;
19,579,490✔
525
}
19,579,490✔
526

527
template <class T>
528
T State::read_int()
529
{
99,192,836✔
530
    T value = 0;
99,192,836✔
531
    if (REALM_LIKELY(_impl::decode_int(*this, value)))
99,192,836✔
532
        return value;
99,191,230✔
533
    parser_error("bad changeset - integer decoding failure");
1,964✔
534
}
1,964✔
535

536
bool State::read_char(char& c) noexcept
537
{
100,874,396✔
538
    if (m_input_begin == m_input_end && !next_input_buffer())
100,874,396✔
539
        return false;
8✔
540
    c = *m_input_begin++;
100,874,388✔
541
    return true;
100,874,388✔
542
}
100,874,388✔
543

544
void State::read_bytes(char* data, size_t size)
545
{
200,818✔
546
    for (;;) {
201,546✔
547
        const size_t avail = m_input_end - m_input_begin;
201,546✔
548
        if (size <= avail)
201,546✔
549
            break;
200,814✔
550
        std::copy_n(m_input_begin, avail, data);
732✔
551
        if (!next_input_buffer())
732✔
552
            parser_error("truncated input");
4✔
553
        data += avail;
732✔
554
        size -= avail;
732✔
555
    }
732✔
556
    const char* to = m_input_begin + size;
200,818✔
557
    std::copy_n(m_input_begin, size, data);
200,818✔
558
    m_input_begin = to;
200,818✔
559
}
200,818✔
560

561
bool State::read_bool()
562
{
2,148,428✔
563
    return read_int<uint8_t>(); // Throws
2,148,428✔
564
}
2,148,428✔
565

566
float State::read_float()
567
{
484✔
568
    static_assert(std::numeric_limits<float>::is_iec559 &&
484✔
569
                      sizeof(float) * std::numeric_limits<unsigned char>::digits == 32,
484✔
570
                  "Unsupported 'float' representation");
484✔
571
    float value;
484✔
572
    read_bytes(reinterpret_cast<char*>(&value), sizeof value); // Throws
484✔
573
    return value;
484✔
574
}
484✔
575

576
double State::read_double()
577
{
1,236✔
578
    static_assert(std::numeric_limits<double>::is_iec559 &&
1,236✔
579
                      sizeof(double) * std::numeric_limits<unsigned char>::digits == 64,
1,236✔
580
                  "Unsupported 'double' representation");
1,236✔
581
    double value;
1,236✔
582
    read_bytes(reinterpret_cast<char*>(&value), sizeof value); // Throws
1,236✔
583
    return value;
1,236✔
584
}
1,236✔
585

586
InternString State::read_intern_string()
587
{
15,138,670✔
588
    uint32_t index = read_int<uint32_t>(); // Throws
15,138,670✔
589
    if (index >= m_intern_strings.size())
15,138,670✔
590
        parser_error("Invalid interned string");
8✔
591
    return InternString{index};
15,138,670✔
592
}
15,138,670✔
593

594
GlobalKey State::read_global_key()
UNCOV
595
{
×
UNCOV
596
    uint64_t hi = read_int<uint64_t>(); // Throws
×
UNCOV
597
    uint64_t lo = read_int<uint64_t>(); // Throws
×
UNCOV
598
    return GlobalKey{hi, lo};
×
UNCOV
599
}
×
600

601
Timestamp State::read_timestamp()
602
{
4,064✔
603
    int64_t seconds = read_int<int64_t>();     // Throws
4,064✔
604
    int64_t nanoseconds = read_int<int64_t>(); // Throws
4,064✔
605
    if (nanoseconds > std::numeric_limits<int32_t>::max())
4,064✔
606
        parser_error("timestamp out of range");
×
607
    return Timestamp{seconds, int32_t(nanoseconds)};
4,064✔
608
}
4,064✔
609

610
ObjectId State::read_object_id()
611
{
197,950✔
612
    // FIXME: This is completely wrong and unsafe.
91,650✔
613
    ObjectId id;
197,950✔
614
    read_bytes(reinterpret_cast<char*>(&id), sizeof(id));
197,950✔
615
    return id;
197,950✔
616
}
197,950✔
617

618
UUID State::read_uuid()
619
{
652✔
620
    UUID::UUIDBytes bytes{};
652✔
621
    read_bytes(reinterpret_cast<char*>(bytes.data()), bytes.size());
652✔
622
    return UUID(bytes);
652✔
623
}
652✔
624

625
Decimal128 State::read_decimal()
626
{
276✔
627
    _impl::Bid128 cx;
276✔
628
    if (!_impl::decode_int(*this, cx))
276✔
629
        parser_error("bad changeset - decimal decoding failure");
×
630

138✔
631
    int exp = read_int<int>();
276✔
632
    bool sign = read_int<int>() != 0;
276✔
633
    Decimal128::Bid128 tmp;
276✔
634
    memcpy(&tmp, &cx, sizeof(Decimal128::Bid128));
276✔
635
    return Decimal128(tmp, exp, sign);
276✔
636
}
276✔
637

638
StringData State::read_string()
639
{
15,145,382✔
640
    uint64_t size = read_int<uint64_t>(); // Throws
15,145,382✔
641

7,430,776✔
642
    if (size > realm::Table::max_string_size)
15,145,382✔
643
        parser_error("string too long"); // Throws
4✔
644
    if (size > std::numeric_limits<size_t>::max())
15,145,382✔
645
        parser_error("invalid length"); // Throws
×
646

7,430,776✔
647
    BinaryData buffer = read_buffer(size_t(size));
15,145,382✔
648
    return StringData{buffer.data(), size_t(size)};
15,145,382✔
649
}
15,145,382✔
650

651
BinaryData State::read_binary()
652
{
20,120✔
653
    uint64_t size = read_int<uint64_t>(); // Throws
20,120✔
654

10,058✔
655
    if (size > std::numeric_limits<size_t>::max())
20,120✔
656
        parser_error("invalid binary length"); // Throws
×
657

10,058✔
658
    return read_buffer(size_t(size));
20,120✔
659
}
20,120✔
660

661
BinaryData State::read_buffer(size_t size)
662
{
15,165,534✔
663
    const size_t avail = m_input_end - m_input_begin;
15,165,534✔
664
    if (avail >= size) {
15,165,534✔
665
        m_input_begin += size;
15,165,160✔
666
        return BinaryData(m_input_begin - size, size);
15,165,160✔
667
    }
15,165,160✔
668

204✔
669
    m_buffer.clear();
374✔
670
    m_buffer.resize(size); // Throws
374✔
671
    read_bytes(m_buffer.data(), size);
374✔
672
    return BinaryData(m_buffer.data(), size);
374✔
673
}
374✔
674

675
void State::parser_error(std::string_view complaints)
676
{
56✔
677
    throw BadChangesetError{std::string(complaints)};
56✔
678
}
56✔
679

680
} // anonymous namespace
681

682
namespace realm::sync {
683

684
void parse_changeset(util::InputStream& input, Changeset& out_log)
685
{
10,594,952✔
686
    InstructionBuilder builder{out_log};
10,594,952✔
687
    State state{input, builder};
10,594,952✔
688

5,223,496✔
689
    while (state.has_next())
34,559,458✔
690
        state.parse_one();
23,964,506✔
691
}
10,594,952✔
692

693
OwnedMixed parse_base64_encoded_primary_key(std::string_view str)
694
{
52✔
695
    auto bin_encoded = util::base64_decode_to_vector(str);
52✔
696
    if (!bin_encoded) {
52✔
697
        throw BadChangesetError("invalid base64 in base64-encoded primary key");
×
698
    }
×
699
    util::SimpleInputStream stream(*bin_encoded);
52✔
700
    UnreachableInstructionHandler fake_encoder;
52✔
701
    State state{stream, fake_encoder};
52✔
702
    using Type = Instruction::Payload::Type;
52✔
703
    Type type = state.read_payload_type();
52✔
704
    switch (type) {
52✔
705
        case Type::Null:
✔
706
            return OwnedMixed{};
×
707
        case Type::Int:
4✔
708
            return OwnedMixed{state.read_int()};
4✔
709
        case Type::String: {
8✔
710
            auto str = state.read_string();
8✔
711
            return OwnedMixed{std::string{str.data(), str.size()}};
8✔
712
        }
×
713
        case Type::GlobalKey:
✔
714
            // GlobalKey's are not actually used as primary keys in sync. We currently have wire protocol support
715
            // for them, but we've never sent them to the sync server.
716
            REALM_UNREACHABLE();
×
717
        case Type::ObjectId:
36✔
718
            return OwnedMixed{state.read_object_id()};
36✔
719
        case Type::UUID:
4✔
720
            return OwnedMixed{state.read_uuid()};
4✔
721
        default:
✔
722
            throw BadChangesetError(util::format("invalid primary key type %1", static_cast<int>(type)));
×
723
    }
52✔
724
}
52✔
725

726
} // namespace realm::sync
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc