~bigbes/tarantool

tarantool-protobuf

ref: 53840866a6ef3349d42bf589a6646aaad6869386 tarantool-protobuf/test/decode_unsafe_test.lua -rw-r--r-- 4.1 KiB
53840866 — Eugene Blikh codegen: emit <Msg>_decode_unsafe for trusted-source decoding (6bb) 2 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
-- decode_unsafe: codegen emits a sister <Msg>_decode_unsafe alongside
-- <Msg>_decode that skips the per-string utf8_len validation. Intended
-- for re-decoding bytes from a trusted producer (own encoder, JSON/text
-- round-trip, in-process typed RPC). Emitted in full mode only; runtime
-- mode does not currently expose the unsafe path (compiled f._reader
-- closures capture handler.decode by value, so a swap-on-call would not
-- reach them — a proper runtime-mode unsafe path would need parallel
-- _reader_unsafe closures and is intentionally deferred). (6bb)
local t = require('luatest')
local hello = require('full.hello.hello_pb')

local g = t.group('decode_unsafe.full')

-- Hand-rolled wire bytes for hello.Address{street=<s>}. Tag for field 1
-- (wire 2, LEN) is 0x0A; length-prefix is one varint byte for len<128.
local function address_with_street(s)
    return string.char(0x0A, #s) .. s
end

-- Hand-rolled wire bytes for hello.Person{name=<s>}. Tag for field 1
-- is identical (0x0A).
local function person_with_name(s)
    return string.char(0x0A, #s) .. s
end

g.test_valid_string_matches_safe_decode = function()
    local addr = {street = 'Pushkina 1', city = 'Moscow', zip = 123456}
    local bytes = hello.Address_encode(addr)
    t.assert_equals(hello.Address_decode_unsafe(bytes),
                    hello.Address_decode(bytes))
end

g.test_safe_decode_rejects_invalid_utf8 = function()
    -- 0xC0 0x80 is the classic overlong NUL — rejected by RFC 3629
    -- (also banned in proto3 strings).
    local bytes = address_with_street('\xC0\x80')
    t.assert_error_msg_contains(
        'invalid UTF-8',
        function() hello.Address_decode(bytes) end)
end

g.test_unsafe_decode_accepts_invalid_utf8 = function()
    local bytes = address_with_street('\xC0\x80')
    local dec = hello.Address_decode_unsafe(bytes)
    t.assert_equals(dec.street, '\xC0\x80')
end

g.test_unsafe_decode_repeated_string = function()
    -- Person.emails is a repeated string; two entries, second is invalid.
    -- Tag 0x1A = field 3 (emails), wire 2.
    local good = 'alice@example.com'
    local bad  = '\xFF\xFE'
    local bytes = string.char(0x1A, #good) .. good
                .. string.char(0x1A, #bad)  .. bad
    t.assert_error_msg_contains(
        'invalid UTF-8',
        function() hello.Person_decode(bytes) end)
    local dec = hello.Person_decode_unsafe(bytes)
    t.assert_equals(dec.emails, {good, bad})
end

g.test_unsafe_decode_recurses_into_sub_messages = function()
    -- Person{address = Address{street = '\xC0\x80'}}.
    -- Tag 0x2A = field 5 (address), wire 2; payload is the Address bytes.
    local inner = address_with_street('\xC0\x80')
    local bytes = string.char(0x2A, #inner) .. inner

    -- Safe path: nested string rejected (proves nested validation runs by
    -- default).
    t.assert_error_msg_contains(
        'invalid UTF-8',
        function() hello.Person_decode(bytes) end)

    -- Unsafe path: nested call must also be the unsafe variant. If
    -- Person_decode_unsafe were to call Address_decode (the safe variant)
    -- for sub-messages, this would still error. The recursive dispatch is
    -- emitted by inline.go and pinned by this assertion.
    local dec = hello.Person_decode_unsafe(bytes)
    t.assert_equals(dec.address.street, '\xC0\x80')
end

g.test_unsafe_decode_handles_long_string_fallback = function()
    -- >=128 byte payload exits the 1-byte LEN inline fast path and falls
    -- through to wire.decode_bytes (instead of wire.decode_string) on
    -- the unsafe path. Exercises the fallback branch in the emitted code.
    local big = string.rep('x', 200) .. '\xFF'  -- 201 bytes, trailing bad
    local bytes = string.char(0x0A) .. string.char(0xC9, 0x01) .. big
    -- 201 in varint = 0xC9 0x01.
    t.assert_error_msg_contains(
        'invalid UTF-8',
        function() hello.Address_decode(bytes) end)
    local dec = hello.Address_decode_unsafe(bytes)
    t.assert_equals(#dec.street, 201)
    t.assert_equals(dec.street:byte(201), 0xFF)
end

-- Person.name omitted from the above explicitly to keep tests focused;
-- the singular-string scalar path is already covered by Address.street.
_ = person_with_name -- silence unused-local under future trimming