~bigbes/tarantool

tarantool-protobuf

0c13fd735d0b7e3218d2b9d1f64c074e01c14234 — Eugene Blikh 3 months ago d855bcf
wire: truncate varints to 32 bits in int32/uint32/sint32/enum decode

decode_int32/uint32/sint32 returned full uint64 values when the wire input
carried bits above bit 31, corrupting re-encode on 51 conformance tests that
exercise overlong or over-range varints. Per the proto3 spec the decoder
must keep only the low 32 bits (and sign-extend for signed types).

Adds wire.varint_to_int32 / varint_to_uint32 and routes every enum-varint
decode site through them: wire.lua typed decoders, codec.lua (5 enum
sites), lazy.lua (3 sites), and the generated code via inline.go (3 sites).

Drops 51 entries from test/conformance/known_failures.txt.
M cmd/protoc-gen-tarantool/internal/gen/inline.go => cmd/protoc-gen-tarantool/internal/gen/inline.go +4 -4
@@ 268,7 268,7 @@ func emitInlineDecodeFieldBody(w *writer, f *protogen.Field, file *protogen.File
	case f.Enum != nil:
		w.line("            local u")
		w.line("            u, pos = wire.decode_varint(buf, pos)")
		w.line("            result.%s = tonumber(u)", fname)
		w.line("            result.%s = wire.varint_to_int32(u)", fname)
	default:
		st := scalarName(f.Desc.Kind())
		w.line("            local val")


@@ 306,12 306,12 @@ func emitInlineDecodeRepeated(w *writer, f *protogen.Field, fname string, file *
		w.line("                while p2 <= lim do")
		w.line("                    local u")
		w.line("                    u, p2 = wire.decode_varint(payload, p2)")
		w.line("                    list[#list + 1] = tonumber(u)")
		w.line("                    list[#list + 1] = wire.varint_to_int32(u)")
		w.line("                end")
		w.line("            else")
		w.line("                local u")
		w.line("                u, pos = wire.decode_varint(buf, pos)")
		w.line("                list[#list + 1] = tonumber(u)")
		w.line("                list[#list + 1] = wire.varint_to_int32(u)")
		w.line("            end")
	default:
		st := scalarName(f.Desc.Kind())


@@ 542,7 542,7 @@ func emitMapDecode(w *writer, dst string, f *protogen.Field, file *protogen.File
	case f.Enum != nil:
		w.line("                    local _u")
		w.line("                    _u, _ep = wire.decode_varint(payload, _ep)")
		w.line("                    %s = tonumber(_u)", dst)
		w.line("                    %s = wire.varint_to_int32(_u)", dst)
	default:
		st := scalarName(f.Desc.Kind())
		w.line("                    %s, _ep = wire.decode_%s(payload, _ep)", dst, st)

M examples/expected/full/conformance/conformance_pb.lua => examples/expected/full/conformance/conformance_pb.lua +2 -2
@@ 404,7 404,7 @@ function M.ConformanceRequest_decode(buf)
        elseif id == 3 then
            local u
            u, pos = wire.decode_varint(buf, pos)
            result.requested_output_format = tonumber(u)
            result.requested_output_format = wire.varint_to_int32(u)
        elseif id == 4 then
            local val
            val, pos = wire.decode_string(buf, pos)


@@ 412,7 412,7 @@ function M.ConformanceRequest_decode(buf)
        elseif id == 5 then
            local u
            u, pos = wire.decode_varint(buf, pos)
            result.test_category = tonumber(u)
            result.test_category = wire.varint_to_int32(u)
        elseif id == 6 then
            local payload
            payload, pos = wire.decode_len(buf, pos)

M examples/expected/full/hello/hello_pb.lua => examples/expected/full/hello/hello_pb.lua +1 -1
@@ 871,7 871,7 @@ function M.Person_decode(buf)
        elseif id == 4 then
            local u
            u, pos = wire.decode_varint(buf, pos)
            result.status = tonumber(u)
            result.status = wire.varint_to_int32(u)
        elseif id == 5 then
            local payload
            payload, pos = wire.decode_len(buf, pos)

M examples/expected/full/protobuf_test_messages/proto3/test_messages_proto3_pb.lua => examples/expected/full/protobuf_test_messages/proto3/test_messages_proto3_pb.lua +16 -16
@@ 1978,15 1978,15 @@ function M.TestAllTypesProto3_decode(buf)
        elseif id == 21 then
            local u
            u, pos = wire.decode_varint(buf, pos)
            result.optional_nested_enum = tonumber(u)
            result.optional_nested_enum = wire.varint_to_int32(u)
        elseif id == 22 then
            local u
            u, pos = wire.decode_varint(buf, pos)
            result.optional_foreign_enum = tonumber(u)
            result.optional_foreign_enum = wire.varint_to_int32(u)
        elseif id == 23 then
            local u
            u, pos = wire.decode_varint(buf, pos)
            result.optional_aliased_enum = tonumber(u)
            result.optional_aliased_enum = wire.varint_to_int32(u)
        elseif id == 24 then
            local val
            val, pos = wire.decode_string(buf, pos)


@@ 2260,12 2260,12 @@ function M.TestAllTypesProto3_decode(buf)
                while p2 <= lim do
                    local u
                    u, p2 = wire.decode_varint(payload, p2)
                    list[#list + 1] = tonumber(u)
                    list[#list + 1] = wire.varint_to_int32(u)
                end
            else
                local u
                u, pos = wire.decode_varint(buf, pos)
                list[#list + 1] = tonumber(u)
                list[#list + 1] = wire.varint_to_int32(u)
            end
        elseif id == 52 then
            local list = result.repeated_foreign_enum


@@ 2277,12 2277,12 @@ function M.TestAllTypesProto3_decode(buf)
                while p2 <= lim do
                    local u
                    u, p2 = wire.decode_varint(payload, p2)
                    list[#list + 1] = tonumber(u)
                    list[#list + 1] = wire.varint_to_int32(u)
                end
            else
                local u
                u, pos = wire.decode_varint(buf, pos)
                list[#list + 1] = tonumber(u)
                list[#list + 1] = wire.varint_to_int32(u)
            end
        elseif id == 54 then
            local list = result.repeated_string_piece


@@ 2527,12 2527,12 @@ function M.TestAllTypesProto3_decode(buf)
                while p2 <= lim do
                    local u
                    u, p2 = wire.decode_varint(payload, p2)
                    list[#list + 1] = tonumber(u)
                    list[#list + 1] = wire.varint_to_int32(u)
                end
            else
                local u
                u, pos = wire.decode_varint(buf, pos)
                list[#list + 1] = tonumber(u)
                list[#list + 1] = wire.varint_to_int32(u)
            end
        elseif id == 89 then
            local list = result.unpacked_int32


@@ 2765,12 2765,12 @@ function M.TestAllTypesProto3_decode(buf)
                while p2 <= lim do
                    local u
                    u, p2 = wire.decode_varint(payload, p2)
                    list[#list + 1] = tonumber(u)
                    list[#list + 1] = wire.varint_to_int32(u)
                end
            else
                local u
                u, pos = wire.decode_varint(buf, pos)
                list[#list + 1] = tonumber(u)
                list[#list + 1] = wire.varint_to_int32(u)
            end
        elseif id == 56 then
            local map = result.map_int32_int32


@@ 3129,7 3129,7 @@ function M.TestAllTypesProto3_decode(buf)
                elseif eid == 2 then
                    local _u
                    _u, _ep = wire.decode_varint(payload, _ep)
                    _val = tonumber(_u)
                    _val = wire.varint_to_int32(_u)
                else
                    _ep = wire.skip_field(payload, _ep, ewt)
                end


@@ 3150,7 3150,7 @@ function M.TestAllTypesProto3_decode(buf)
                elseif eid == 2 then
                    local _u
                    _u, _ep = wire.decode_varint(payload, _ep)
                    _val = tonumber(_u)
                    _val = wire.varint_to_int32(_u)
                else
                    _ep = wire.skip_field(payload, _ep, ewt)
                end


@@ 3263,7 3263,7 @@ function M.TestAllTypesProto3_decode(buf)
        elseif id == 119 then
            local u
            u, pos = wire.decode_varint(buf, pos)
            result.oneof_enum = tonumber(u)
            result.oneof_enum = wire.varint_to_int32(u)
            result.oneof_uint32 = nil
            result.oneof_nested_message = nil
            result.oneof_string = nil


@@ 3276,7 3276,7 @@ function M.TestAllTypesProto3_decode(buf)
        elseif id == 120 then
            local u
            u, pos = wire.decode_varint(buf, pos)
            result.oneof_null_value = tonumber(u)
            result.oneof_null_value = wire.varint_to_int32(u)
            result.oneof_uint32 = nil
            result.oneof_nested_message = nil
            result.oneof_string = nil


@@ 3493,7 3493,7 @@ function M.TestAllTypesProto3_decode(buf)
        elseif id == 307 then
            local u
            u, pos = wire.decode_varint(buf, pos)
            result.optional_null_value = tonumber(u)
            result.optional_null_value = wire.varint_to_int32(u)
        elseif id == 308 then
            local payload
            payload, pos = wire.decode_len(buf, pos)

M runtime/pb/codec.lua => runtime/pb/codec.lua +10 -8
@@ 475,6 475,7 @@ local function build_repeated_reader(f)
        local WIRE_LEN = wire.WIRE_LEN
        local decode_len_fn = wire.decode_len
        local decode_varint_fn = wire.decode_varint
        local varint_to_int32 = wire.varint_to_int32
        return function(buf, pos, wt, result)
            local list = result[fname]
            if list == nil then list = {}; result[fname] = list end


@@ 484,13 485,13 @@ local function build_repeated_reader(f)
                local n = #list
                while p <= lim do
                    local u, np2 = decode_varint_fn(payload, p)
                    n = n + 1; list[n] = tonumber(u)
                    n = n + 1; list[n] = varint_to_int32(u)
                    p = np2
                end
                return np
            end
            local u, np = decode_varint_fn(buf, pos)
            list[#list + 1] = tonumber(u)
            list[#list + 1] = varint_to_int32(u)
            return np
        end
    end


@@ 541,17 542,18 @@ local function build_reader(f)

    if kind == 'enum' then
        local decode_varint_fn = wire.decode_varint
        local varint_to_int32 = wire.varint_to_int32
        if siblings then
            return function(buf, pos, wt, result)
                local u, np = decode_varint_fn(buf, pos)
                result[fname] = tonumber(u)
                result[fname] = varint_to_int32(u)
                for i = 1, #siblings do result[siblings[i]] = nil end
                return np
            end
        end
        return function(buf, pos, wt, result)
            local u, np = decode_varint_fn(buf, pos)
            result[fname] = tonumber(u)
            result[fname] = varint_to_int32(u)
            return np
        end
    end


@@ 669,7 671,7 @@ local function decode_one(field, buf, pos)
        return scalar[field.proto_type].decode(buf, pos)
    elseif kind == 'enum' then
        local u, np = wire.decode_varint(buf, pos)
        return tonumber(u), np
        return wire.varint_to_int32(u), np
    elseif kind == 'message' then
        local payload, np = wire.decode_len(buf, pos)
        return decode_msg(field.message, payload), np


@@ 796,11 798,11 @@ decode_message = function(desc, buf)
                        while p2 <= lim do
                            local u, np2 = wire.decode_varint(payload, p2)
                            p2 = np2
                            list[#list + 1] = tonumber(u)
                            list[#list + 1] = wire.varint_to_int32(u)
                        end
                    else
                        local u, np = wire.decode_varint(buf, pos)
                        list[#list + 1] = tonumber(u)
                        list[#list + 1] = wire.varint_to_int32(u)
                        pos = np
                    end
                elseif kind == 'message' then


@@ 818,7 820,7 @@ decode_message = function(desc, buf)
                elseif kind == 'enum' then
                    local u, np = wire.decode_varint(buf, pos)
                    pos = np
                    result[f.name] = tonumber(u)
                    result[f.name] = wire.varint_to_int32(u)
                elseif kind == 'message' then
                    local payload, np = wire.decode_len(buf, pos)
                    pos = np

M runtime/pb/lazy.lua => runtime/pb/lazy.lua +3 -3
@@ 119,7 119,7 @@ local function read_singular(field, bytes, val_start)
        return v
    elseif kind == 'enum' then
        local u = wire.decode_varint(bytes, val_start)
        return tonumber(u)
        return wire.varint_to_int32(u)
    elseif kind == 'message' then
        local payload = wire.decode_len(bytes, val_start)
        if field.message.decode ~= nil then


@@ 282,13 282,13 @@ local function decode_map_entry(field, bytes, val_start)
            if key_field.kind == 'scalar' then
                key, p = wire.TYPE_INFO[key_field.proto_type].decode(bytes, p)
            elseif key_field.kind == 'enum' then
                local u; u, p = wire.decode_varint(bytes, p); key = tonumber(u)
                local u; u, p = wire.decode_varint(bytes, p); key = wire.varint_to_int32(u)
            end
        elseif eid == 2 then
            if val_field.kind == 'scalar' then
                val, p = wire.TYPE_INFO[val_field.proto_type].decode(bytes, p)
            elseif val_field.kind == 'enum' then
                local u; u, p = wire.decode_varint(bytes, p); val = tonumber(u)
                local u; u, p = wire.decode_varint(bytes, p); val = wire.varint_to_int32(u)
            elseif val_field.kind == 'message' then
                local payload; payload, p = wire.decode_len(bytes, p)
                if val_field.message.decode ~= nil then

M runtime/pb/wire.lua => runtime/pb/wire.lua +17 -5
@@ 39,6 39,20 @@ local function to_int64(v)
end
M.to_int64 = to_int64

-- Truncate a uint64 varint payload to proto3 int32/uint32 Lua numbers.
-- Per spec, int32/uint32/enum/sint32 fields must keep only the low 32 bits
-- of an over-range varint; int32/sint32 additionally sign-extend from bit 31.
local function varint_to_uint32(u)
    return tonumber(bit.band(u, 0xFFFFFFFF))
end
local function varint_to_int32(u)
    local n = tonumber(bit.band(u, 0xFFFFFFFF))
    if n >= 0x80000000 then n = n - 0x100000000 end
    return n
end
M.varint_to_uint32 = varint_to_uint32
M.varint_to_int32  = varint_to_int32

-- ---------------------------------------------------------------------------
-- Varint
-- ---------------------------------------------------------------------------


@@ 322,7 336,7 @@ local function decode_int32(buf, pos)
    if b == nil then error("truncated varint at offset " .. pos, 0) end
    if b < 0x80 then return b, pos + 1 end  -- 0..127 fits int32 directly
    local u, np = decode_varint(buf, pos)
    return tonumber(INT64(u)), np  -- truncated to int32 range via int64 sign-extension
    return varint_to_int32(u), np
end
local function decode_int64(buf, pos)
    local b = buf:byte(pos)


@@ 336,9 350,7 @@ local function decode_uint32(buf, pos)
    if b == nil then error("truncated varint at offset " .. pos, 0) end
    if b < 0x80 then return b, pos + 1 end
    local u, np = decode_varint(buf, pos)
    local n = tonumber(UINT64(u))
    if n < 0 then n = n + 0x100000000 end
    return n, np
    return varint_to_uint32(u), np
end
local function decode_uint64(buf, pos)
    local b = buf:byte(pos)


@@ 352,7 364,7 @@ local function decode_sint32(buf, pos)
    if b == nil then error("truncated varint at offset " .. pos, 0) end
    if b < 0x80 then return zigzag_decode32(b), pos + 1 end
    local u, np = decode_varint(buf, pos)
    return zigzag_decode32(tonumber(u)), np
    return zigzag_decode32(varint_to_uint32(u)), np
end
local function decode_sint64(buf, pos)
    local b = buf:byte(pos)

M test/conformance/known_failures.txt => test/conformance/known_failures.txt +0 -51
@@ 31,42 31,6 @@ Recommended.Proto3.ProtobufInput.RejectInvalidUtf8.String.Oneof
Recommended.Proto3.ProtobufInput.RejectInvalidUtf8.String.Repeated
Recommended.Proto3.ProtobufInput.RejectInvalidUtf8.String.Singular
Recommended.Proto3.ProtobufInput.ValidDataOneofBinary.MESSAGE.Merge.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.ENUM.PackedInput.DefaultOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.ENUM.PackedInput.PackedOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.ENUM.PackedInput.UnpackedOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.ENUM.UnpackedInput.DefaultOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.ENUM.UnpackedInput.PackedOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.ENUM.UnpackedInput.UnpackedOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.INT32.PackedInput.DefaultOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.INT32.PackedInput.PackedOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.INT32.PackedInput.UnpackedOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.INT32.UnpackedInput.DefaultOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.INT32.UnpackedInput.PackedOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.INT32.UnpackedInput.UnpackedOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.SINT32.PackedInput.DefaultOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.SINT32.PackedInput.PackedOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.SINT32.PackedInput.UnpackedOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.SINT32.UnpackedInput.DefaultOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.SINT32.UnpackedInput.PackedOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.SINT32.UnpackedInput.UnpackedOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.UINT32.PackedInput.DefaultOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.UINT32.PackedInput.PackedOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.UINT32.PackedInput.UnpackedOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.UINT32.UnpackedInput.DefaultOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.UINT32.UnpackedInput.PackedOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataRepeated.UINT32.UnpackedInput.UnpackedOutput.ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataScalarBinary.ENUM[4].ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataScalarBinary.ENUM[5].ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataScalarBinary.INT32[6].ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataScalarBinary.INT32[7].ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataScalarBinary.INT32[8].ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataScalarBinary.INT32[9].ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataScalarBinary.SINT32[4].ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataScalarBinary.UINT32[5].ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataScalarBinary.UINT32[6].ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataScalarBinary.UINT32[7].ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataScalarBinary.UINT32[8].ProtobufOutput
Recommended.Proto3.ProtobufInput.ValidDataScalarBinary.UINT32[9].ProtobufOutput
Required.Proto3.JsonInput.AllFieldAcceptNull.JsonOutput
Required.Proto3.JsonInput.AllFieldAcceptNull.ProtobufOutput
Required.Proto3.JsonInput.Any.ProtobufOutput


@@ 116,9 80,6 @@ Required.Proto3.ProtobufInput.PrematureEofInsideUnknownValue.FLOAT
Required.Proto3.ProtobufInput.PrematureEofInsideUnknownValue.SFIXED32
Required.Proto3.ProtobufInput.PrematureEofInsideUnknownValue.SFIXED64
Required.Proto3.ProtobufInput.RepeatedScalarMessageMerge.ProtobufOutput
Required.Proto3.ProtobufInput.RepeatedScalarSelectsLast.ENUM.ProtobufOutput
Required.Proto3.ProtobufInput.RepeatedScalarSelectsLast.INT32.ProtobufOutput
Required.Proto3.ProtobufInput.RepeatedScalarSelectsLast.UINT32.ProtobufOutput
Required.Proto3.ProtobufInput.UnknownWireType6_Field1_Version0
Required.Proto3.ProtobufInput.UnknownWireType6_Field1_Version1
Required.Proto3.ProtobufInput.UnknownWireType6_Field1_Version2


@@ 144,16 105,4 @@ Required.Proto3.ProtobufInput.UnknownWireType7_Field3_Version1
Required.Proto3.ProtobufInput.UnknownWireType7_Field3_Version2
Required.Proto3.ProtobufInput.UnknownWireType7_Field3_Version3
Required.Proto3.ProtobufInput.ValidDataOneof.MESSAGE.Merge.ProtobufOutput
Required.Proto3.ProtobufInput.ValidDataRepeated.ENUM.PackedInput.ProtobufOutput
Required.Proto3.ProtobufInput.ValidDataRepeated.ENUM.UnpackedInput.ProtobufOutput
Required.Proto3.ProtobufInput.ValidDataRepeated.INT32.PackedInput.ProtobufOutput
Required.Proto3.ProtobufInput.ValidDataRepeated.INT32.UnpackedInput.ProtobufOutput
Required.Proto3.ProtobufInput.ValidDataRepeated.UINT32.PackedInput.ProtobufOutput
Required.Proto3.ProtobufInput.ValidDataRepeated.UINT32.UnpackedInput.ProtobufOutput
Required.Proto3.ProtobufInput.ValidDataScalar.ENUM[4].ProtobufOutput
Required.Proto3.ProtobufInput.ValidDataScalar.ENUM[5].ProtobufOutput
Required.Proto3.ProtobufInput.ValidDataScalar.INT32[8].ProtobufOutput
Required.Proto3.ProtobufInput.ValidDataScalar.INT32[9].ProtobufOutput
Required.Proto3.ProtobufInput.ValidDataScalar.UINT32[8].ProtobufOutput
Required.Proto3.ProtobufInput.ValidDataScalar.UINT32[9].ProtobufOutput
Required.Proto3.TimestampProtoNegativeNanos.JsonOutput