aboutsummaryrefslogtreecommitdiff
path: root/test/unit/eval/decode_spec.lua
blob: 0c444b33f28ac83edf6929cb47fa7fa374021111 (plain) (blame)
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
local helpers = require('test.unit.helpers')(after_each)
local itp = helpers.gen_itp(it)

local cimport = helpers.cimport
local eq = helpers.eq
local neq = helpers.neq
local ffi = helpers.ffi

local decode = cimport('./src/nvim/eval/decode.h', './src/nvim/eval/typval.h',
                       './src/nvim/globals.h', './src/nvim/memory.h',
                       './src/nvim/message.h')

describe('json_decode_string()', function()
  local char = function(c)
    return ffi.gc(decode.xmemdup(c, 1), decode.xfree)
  end

  itp('does not overflow when running with `n…`, `t…`, `f…`', function()
    local rettv = ffi.new('typval_T', {v_type=decode.VAR_UNKNOWN})
    decode.emsg_silent = 1
    -- This will not crash, but if `len` argument will be ignored it will parse
    -- `null` as `null` and if not it will parse `null` as `n`.
    eq(0, decode.json_decode_string('null', 1, rettv))
    eq(decode.VAR_UNKNOWN, rettv.v_type)
    eq(0, decode.json_decode_string('true', 1, rettv))
    eq(decode.VAR_UNKNOWN, rettv.v_type)
    eq(0, decode.json_decode_string('false', 1, rettv))
    eq(decode.VAR_UNKNOWN, rettv.v_type)
    eq(0, decode.json_decode_string('null', 2, rettv))
    eq(decode.VAR_UNKNOWN, rettv.v_type)
    eq(0, decode.json_decode_string('true', 2, rettv))
    eq(decode.VAR_UNKNOWN, rettv.v_type)
    eq(0, decode.json_decode_string('false', 2, rettv))
    eq(decode.VAR_UNKNOWN, rettv.v_type)
    eq(0, decode.json_decode_string('null', 3, rettv))
    eq(decode.VAR_UNKNOWN, rettv.v_type)
    eq(0, decode.json_decode_string('true', 3, rettv))
    eq(decode.VAR_UNKNOWN, rettv.v_type)
    eq(0, decode.json_decode_string('false', 3, rettv))
    eq(decode.VAR_UNKNOWN, rettv.v_type)
    eq(0, decode.json_decode_string('false', 4, rettv))
    eq(decode.VAR_UNKNOWN, rettv.v_type)
  end)

  itp('does not overflow and crash when running with `n`, `t`, `f`', function()
    local rettv = ffi.new('typval_T', {v_type=decode.VAR_UNKNOWN})
    decode.emsg_silent = 1
    eq(0, decode.json_decode_string(char('n'), 1, rettv))
    eq(decode.VAR_UNKNOWN, rettv.v_type)
    eq(0, decode.json_decode_string(char('t'), 1, rettv))
    eq(decode.VAR_UNKNOWN, rettv.v_type)
    eq(0, decode.json_decode_string(char('f'), 1, rettv))
    eq(decode.VAR_UNKNOWN, rettv.v_type)
  end)

  itp('does not overflow when running with `"…`', function()
    local rettv = ffi.new('typval_T', {v_type=decode.VAR_UNKNOWN})
    decode.emsg_silent = 1
    eq(0, decode.json_decode_string('"t"', 2, rettv))
    eq(decode.VAR_UNKNOWN, rettv.v_type)
    eq(0, decode.json_decode_string('""', 1, rettv))
    eq(decode.VAR_UNKNOWN, rettv.v_type)
  end)

  local check_failure = function(s, len, msg)
    local rettv = ffi.new('typval_T', {v_type=decode.VAR_UNKNOWN})
    eq(0, decode.json_decode_string(s, len, rettv))
    eq(decode.VAR_UNKNOWN, rettv.v_type)
    neq(nil, decode.last_msg_hist)
    eq(msg, ffi.string(decode.last_msg_hist.msg))
  end

  itp('does not overflow in error messages', function()
    collectgarbage('restart')
    check_failure(']test', 1, 'E474: No container to close: ]')
    check_failure('[}test', 2, 'E474: Closing list with curly bracket: }')
    check_failure('{]test', 2,
                  'E474: Closing dictionary with square bracket: ]')
    check_failure('[1,]test', 4, 'E474: Trailing comma: ]')
    check_failure('{"1":}test', 6, 'E474: Expected value after colon: }')
    check_failure('{"1"}test', 5, 'E474: Expected value: }')
    check_failure(',test', 1, 'E474: Comma not inside container: ,')
    check_failure('[1,,1]test', 6, 'E474: Duplicate comma: ,1]')
    check_failure('{"1":,}test', 7, 'E474: Comma after colon: ,}')
    check_failure('{"1",}test', 6, 'E474: Using comma in place of colon: ,}')
    check_failure('{,}test', 3, 'E474: Leading comma: ,}')
    check_failure('[,]test', 3, 'E474: Leading comma: ,]')
    check_failure(':test', 1, 'E474: Colon not inside container: :')
    check_failure('[:]test', 3, 'E474: Using colon not in dictionary: :]')
    check_failure('{:}test', 3, 'E474: Unexpected colon: :}')
    check_failure('{"1"::1}test', 8, 'E474: Duplicate colon: :1}')
    check_failure('ntest', 1, 'E474: Expected null: n')
    check_failure('ttest', 1, 'E474: Expected true: t')
    check_failure('ftest', 1, 'E474: Expected false: f')
    check_failure('"\\test', 2, 'E474: Unfinished escape sequence: "\\')
    check_failure('"\\u"test', 4,
                  'E474: Unfinished unicode escape sequence: "\\u"')
    check_failure('"\\uXXXX"est', 8,
                  'E474: Expected four hex digits after \\u: \\uXXXX"')
    check_failure('"\\?"test', 4, 'E474: Unknown escape sequence: \\?"')
    check_failure(
        '"\t"test', 3,
        'E474: ASCII control characters cannot be present inside string: \t"')
    check_failure('"\194"test', 3, 'E474: Only UTF-8 strings allowed: \194"')
    check_failure('"\252\144\128\128\128\128"test', 8, 'E474: Only UTF-8 code points up to U+10FFFF are allowed to appear unescaped: \252\144\128\128\128\128"')
    check_failure('"test', 1, 'E474: Expected string end: "')
    check_failure('-test', 1, 'E474: Missing number after minus sign: -')
    check_failure('-1.test', 3, 'E474: Missing number after decimal dot: -1.')
    check_failure('-1.0etest', 5, 'E474: Missing exponent: -1.0e')
    check_failure('?test', 1, 'E474: Unidentified byte: ?')
    check_failure('1?test', 2, 'E474: Trailing characters: ?')
    check_failure('[1test', 2, 'E474: Unexpected end of input: [1')
  end)

  itp('does not overflow with `-`', function()
    check_failure('-0', 1, 'E474: Missing number after minus sign: -')
  end)

  itp('does not overflow and crash when running with `"`', function()
    local rettv = ffi.new('typval_T', {v_type=decode.VAR_UNKNOWN})
    decode.emsg_silent = 1
    eq(0, decode.json_decode_string(char('"'), 1, rettv))
    eq(decode.VAR_UNKNOWN, rettv.v_type)
  end)
end)