From 8a28aa8b1c9302422eab93e7565e85747ed2670e Mon Sep 17 00:00:00 2001
From: Maria <marianneliash@gmail.com>
Date: Thu, 12 Sep 2019 16:55:53 +0300
Subject: [PATCH] lua: keeping the pointer type in msgpackffi.decode()

Method decode_unchecked returns two values - the one that has
been decoded and a pointer to the new position within the buffer
given as a parameter. The type of returned pointer used to be
cdata<unsigned char *> and it was not possible to assign returned
value to buf.rpos due to the following error:

> cannot convert 'const unsigned char *' to 'char *'

The patch fixes this by making decode_unchecked method return either
cdata<char *> or cdata<const char *> depending on the given parameter.

Closes #3926

(cherry picked from commit 84bcba52a84852870726e1a4d19b3768c0bde7a7)
---
 src/lua/msgpackffi.lua           | 12 +++++----
 test/app-tap/msgpackffi.test.lua | 42 +++++++++++++++++++++++++++++++-
 2 files changed, 48 insertions(+), 6 deletions(-)

diff --git a/src/lua/msgpackffi.lua b/src/lua/msgpackffi.lua
index 63d9b33b7c..051011dd9f 100644
--- a/src/lua/msgpackffi.lua
+++ b/src/lua/msgpackffi.lua
@@ -9,7 +9,7 @@ local uint8_ptr_t = ffi.typeof('uint8_t *')
 local uint16_ptr_t = ffi.typeof('uint16_t *')
 local uint32_ptr_t = ffi.typeof('uint32_t *')
 local uint64_ptr_t = ffi.typeof('uint64_t *')
-local const_char_ptr_t = ffi.typeof('const char *')
+local char_ptr_t = ffi.typeof('char *')
 
 ffi.cdef([[
 char *
@@ -564,17 +564,19 @@ end
 local function decode_unchecked(str, offset)
     if type(str) == "string" then
         offset = check_offset(offset, #str)
-        local buf = ffi.cast(const_char_ptr_t, str)
+        local buf = ffi.cast(char_ptr_t, str)
         bufp[0] = buf + offset - 1
         local r = decode_r(bufp)
         return r, bufp[0] - buf + 1
-    elseif ffi.istype(const_char_ptr_t, str) then
+    elseif ffi.istype(char_ptr_t, str) then
+        -- Note: ffi.istype() ignores the const qualifier, so both
+        -- (char *) and (const char *) buffers are valid.
         bufp[0] = str
         local r = decode_r(bufp)
-        return r, bufp[0]
+        return r, ffi.cast(ffi.typeof(str), bufp[0])
     else
         error("msgpackffi.decode_unchecked(str, offset) -> res, new_offset | "..
-              "msgpackffi.decode_unchecked(const char *buf) -> res, new_buf")
+              "msgpackffi.decode_unchecked([const] char *buf) -> res, new_buf")
     end
 end
 
diff --git a/test/app-tap/msgpackffi.test.lua b/test/app-tap/msgpackffi.test.lua
index e82c7401ac..bb9826580a 100755
--- a/test/app-tap/msgpackffi.test.lua
+++ b/test/app-tap/msgpackffi.test.lua
@@ -4,6 +4,7 @@ package.path = "lua/?.lua;"..package.path
 
 local tap = require('tap')
 local common = require('serializer_test')
+local ffi = require('ffi')
 
 local function is_map(s)
     local b = string.byte(string.sub(s, 1, 1))
@@ -115,9 +116,48 @@ local function test_other(test, s)
                  encode_max_depth = max_depth})
 end
 
+-- gh-3926: Ensure that a returned pointer has the same cdata type
+-- as passed argument.
+local function test_decode_buffer(test, s)
+    local cases = {
+        {
+            'decode_unchecked(cdata<const char *>)',
+            data = ffi.cast('const char *', '\x93\x01\x02\x03'),
+            exp_res = {1, 2, 3},
+            exp_rewind = 4,
+        },
+        {
+            'decode_unchecked(cdata<char *>)',
+            data = ffi.cast('char *', '\x93\x01\x02\x03'),
+            exp_res = {1, 2, 3},
+            exp_rewind = 4,
+        },
+    }
+
+    test:plan(#cases)
+
+    for _, case in ipairs(cases) do
+        test:test(case[1], function(test)
+            test:plan(4)
+            local res, res_buf = s.decode_unchecked(case.data)
+            test:is_deeply(res, case.exp_res, 'verify result')
+            local rewind = res_buf - case.data
+            test:is(rewind, case.exp_rewind, 'verify resulting buffer')
+            -- test:iscdata() is not sufficient here, because it
+            -- ignores 'const' qualifier (because of using
+            -- ffi.istype()).
+            test:is(type(res_buf), 'cdata', 'verify resulting buffer type')
+            local data_ctype = tostring(ffi.typeof(case.data))
+            local res_buf_ctype = tostring(ffi.typeof(res_buf))
+            test:is(res_buf_ctype, data_ctype, 'verify resulting buffer ctype')
+        end)
+    end
+end
+
+
 tap.test("msgpackffi", function(test)
     local serializer = require('msgpackffi')
-    test:plan(9)
+    test:plan(10)
     test:test("unsigned", common.test_unsigned, serializer)
     test:test("signed", common.test_signed, serializer)
     test:test("double", common.test_double, serializer)
-- 
GitLab