aboutsummaryrefslogtreecommitdiff
path: root/test/unit/eval/decode_spec.lua
blob: 742b754d8ab623b022cfed885410e5306766fefe (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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
local helpers = require('test.unit.helpers')

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

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

describe('json_decode_string()', function()
  local saved_p_enc = nil

  before_each(function()
    saved_p_enc = decode.p_enc
  end)

  after_each(function()
    decode.emsg_silent = 0
    decode.p_enc = saved_p_enc
    while decode.delete_first_msg() == 1 do
      -- Delete all messages
    end
  end)

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

  it('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)

  it('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)

  it('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

  it('does not overflow in error messages', function()
    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: "')
    decode.p_enc = to_cstr('latin1')
    check_failure('"\\uABCD"test', 8,
                  'E474: Failed to convert string "ꯍ" from UTF-8')
    decode.p_enc = saved_p_enc
    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)

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

  it('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)