view src/testdir/test_filter_map.vim @ 34074:1629cc65d78d v9.1.0006

patch 9.1.0006: is*() and to*() function may be unsafe Commit: https://github.com/vim/vim/commit/184f71cc6868a240dc872ed2852542bbc1d43e28 Author: Keith Thompson <Keith.S.Thompson@gmail.com> Date: Thu Jan 4 21:19:04 2024 +0100 patch 9.1.0006: is*() and to*() function may be unsafe Problem: is*() and to*() function may be unsafe Solution: Add SAFE_* macros and start using those instead (Keith Thompson) Use SAFE_() macros for is*() and to*() functions The standard is*() and to*() functions declared in <ctype.h> have undefined behavior for negative arguments other than EOF. If plain char is signed, passing an unchecked value from argv for from user input to one of these functions has undefined behavior. Solution: Add SAFE_*() macros that cast the argument to unsigned char. Most implementations behave sanely for negative arguments, and most character values in practice are non-negative, but it's still best to avoid undefined behavior. The change from #13347 has been omitted, as this has already been separately fixed in commit ac709e2fc0db6d31abb7da96f743c40956b60c3a (v9.0.2054) fixes: #13332 closes: #13347 Signed-off-by: Keith Thompson <Keith.S.Thompson@gmail.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
author Christian Brabandt <cb@256bit.org>
date Thu, 04 Jan 2024 21:30:04 +0100
parents 5848a0867b26
children da670b1549b3
line wrap: on
line source

" Test filter() and map()

import './vim9.vim' as v9

" list with expression string
func Test_filter_map_list_expr_string()
  " filter()
  call assert_equal([2, 3, 4], filter([1, 2, 3, 4], 'v:val > 1'))
  call assert_equal([3, 4], filter([1, 2, 3, 4], 'v:key > 1'))
  call assert_equal([], filter([1, 2, 3, 4], 0))

  " map()
  call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], 'v:val * 2'))
  call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], 'v:key * 2'))
  call assert_equal([9, 9, 9, 9], map([1, 2, 3, 4], 9))
  call assert_equal([7, 7, 7], map([1, 2, 3], ' 7 '))
endfunc

" dict with expression string
func Test_filter_map_dict_expr_string()
  let dict = {"foo": 1, "bar": 2, "baz": 3}

  " filter()
  call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), 'v:val > 1'))
  call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), 'v:key > "bar"'))
  call assert_equal({}, filter(copy(dict), 0))

  " map()
  call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), 'v:val * 2'))
  call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), 'v:key[0]'))
  call assert_equal({"foo": 9, "bar": 9, "baz": 9}, map(copy(dict), 9))
endfunc

" list with funcref
func Test_filter_map_list_expr_funcref()
  " filter()
  func! s:filter1(index, val) abort
    return a:val > 1
  endfunc
  call assert_equal([2, 3, 4], filter([1, 2, 3, 4], function('s:filter1')))

  func! s:filter2(index, val) abort
    return a:index > 1
  endfunc
  call assert_equal([3, 4], filter([1, 2, 3, 4], function('s:filter2')))

  " map()
  func! s:filter3(index, val) abort
    return a:val * 2
  endfunc
  call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], function('s:filter3')))

  func! s:filter4(index, val) abort
    return a:index * 2
  endfunc
  call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], function('s:filter4')))
endfunc

func Test_filter_map_nested()
  let x = {"x":10}
  let r = map(range(2), 'filter(copy(x), "1")')
  call assert_equal([x, x], r)

  let r = map(copy(x), 'filter(copy(x), "1")')
  call assert_equal({"x": x}, r)
endfunc

" dict with funcref
func Test_filter_map_dict_expr_funcref()
  let dict = {"foo": 1, "bar": 2, "baz": 3}

  " filter()
  func! s:filter1(key, val) abort
    return a:val > 1
  endfunc
  call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), function('s:filter1')))

  func! s:filter2(key, val) abort
    return a:key > "bar"
  endfunc
  call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), function('s:filter2')))

  " map()
  func! s:filter3(key, val) abort
    return a:val * 2
  endfunc
  call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), function('s:filter3')))

  func! s:filter4(key, val) abort
    return a:key[0]
  endfunc
  call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), function('s:filter4')))
endfunc

func Test_map_filter_fails()
  call assert_fails('call map([1], "42 +")', 'E15:')
  call assert_fails('call filter([1], "42 +")', 'E15:')
  call assert_fails("let l = filter([1, 2, 3], '{}')", 'E728:')
  call assert_fails("let l = filter({'k' : 10}, '{}')", 'E728:')
  call assert_fails("let l = filter([1, 2], {})", 'E731:')
  call assert_equal(test_null_list(), filter(test_null_list(), 0))
  call assert_equal(test_null_dict(), filter(test_null_dict(), 0))
  call assert_equal(test_null_list(), map(test_null_list(), '"> " .. v:val'))
  call assert_equal(test_null_dict(), map(test_null_dict(), '"> " .. v:val'))
  call assert_equal([1, 2, 3], filter([1, 2, 3], test_null_function()))
  call assert_fails("let l = filter([1, 2], function('min'))", 'E118:')
  call assert_equal([1, 2, 3], filter([1, 2, 3], test_null_partial()))
  call assert_fails("let l = filter([1, 2], {a, b, c -> 1})", 'E119:')
endfunc

func Test_map_and_modify()
  let l = ["abc"]
  " cannot change the list halfway a map()
  call assert_fails('call map(l, "remove(l, 0)[0]")', 'E741:')

  let d = #{a: 1, b: 2, c: 3}
  call assert_fails('call map(d, "remove(d, v:key)[0]")', 'E741:')
  call assert_fails('echo map(d, {k,v -> remove(d, k)})', 'E741:')

  let b = 0z1234
  call assert_fails('call filter(b, "remove(b, 0)")', 'E741:')
endfunc

func Test_filter_and_modify()
  let l = [0]
  " cannot change the list halfway a map()
  call assert_fails('call filter(l, "remove(l, 0)")', 'E741:')

  let d = #{a: 0, b: 0, c: 0}
  call assert_fails('call filter(d, "remove(d, v:key)")', 'E741:')

  let b = 0z1234
  call assert_fails('call filter(b, "remove(b, 0)")', 'E741:')
endfunc

func Test_mapnew_dict()
  let din = #{one: 1, two: 2}
  let dout = mapnew(din, {k, v -> string(v)})
  call assert_equal(#{one: 1, two: 2}, din)
  call assert_equal(#{one: '1', two: '2'}, dout)

  const dconst = #{one: 1, two: 2, three: 3}
  call assert_equal(#{one: 2, two: 3, three: 4}, mapnew(dconst, {_, v -> v + 1}))
endfunc

func Test_mapnew_list()
  let lin = [1, 2, 3]
  let lout = mapnew(lin, {k, v -> string(v)})
  call assert_equal([1, 2, 3], lin)
  call assert_equal(['1', '2', '3'], lout)

  const lconst = [1, 2, 3]
  call assert_equal([2, 3, 4], mapnew(lconst, {_, v -> v + 1}))
endfunc

func Test_mapnew_blob()
  let bin = 0z123456
  let bout = mapnew(bin, {k, v -> k == 1 ? 0x99 : v})
  call assert_equal(0z123456, bin)
  call assert_equal(0z129956, bout)
endfunc

" Test for using map(), filter() and mapnew() with a string
func Test_filter_map_string()
  " filter()
  let lines =<< trim END
    VAR s = "abc"
    call filter(s, '"b" != v:val')
    call assert_equal('abc', s)
    call assert_equal('ac', filter('abc', '"b" != v:val'))
    call assert_equal('あいうえお', filter('あxいxうxえxお', '"x" != v:val'))
    call assert_equal('あa😊💕💕b💕', filter('あxax😊x💕💕b💕x', '"x" != v:val'))
    call assert_equal('xxxx', filter('あxax😊x💕💕b💕x', '"x" == v:val'))
    VAR t = "%),:;>?]}’”†‡…‰,‱‼⁇⁈⁉℃℉,、。〉》」,』】〕〗〙〛,!),.:,;?,]}"
    VAR u = "%):;>?]}’”†‡…‰‱‼⁇⁈⁉℃℉、。〉》」』】〕〗〙〛!),.:;?]}"
    call assert_equal(u, filter(t, '"," != v:val'))
    call assert_equal('', filter('abc', '0'))
    call assert_equal('ac', filter('abc', LSTART i, x LMIDDLE "b" != x LEND))
    call assert_equal('あいうえお', filter('あxいxうxえxお', LSTART i, x LMIDDLE "x" != x LEND))
    call assert_equal('', filter('abc', LSTART i, x LMIDDLE v:false LEND))
    call assert_equal('', filter('', "v:val == 'a'"))
    call assert_equal('', filter(test_null_string(), "v:val == 'a'"))
  END
  call v9.CheckLegacyAndVim9Success(lines)

  " map()
  let lines =<< trim END
    VAR s = "abc"
    call map(s, 'nr2char(char2nr(v:val) + 2)')
    call assert_equal('abc', s)
    call assert_equal('cde', map('abc', 'nr2char(char2nr(v:val) + 2)'))
    call assert_equal('[あ][i][う][え][お]', map('あiうえお', '"[" .. v:val .. "]"'))
    call assert_equal('[あ][a][😊][,][‱][‼][⁇][⁈][⁉][💕][b][💕][c][💕]', map('あa😊,‱‼⁇⁈⁉💕b💕c💕', '"[" .. v:val .. "]"'))
    call assert_equal('', map('abc', '""'))
    call assert_equal('cde', map('abc', LSTART i, x LMIDDLE nr2char(char2nr(x) + 2) LEND))
    call assert_equal('[あ][i][う][え][お]', map('あiうえお', LSTART i, x LMIDDLE '[' .. x .. ']' LEND))
    call assert_equal('', map('abc', LSTART i, x LMIDDLE '' LEND))
    call assert_equal('', map('', "v:val == 'a'"))
    call assert_equal('', map(test_null_string(), "v:val == 'a'"))
    call assert_fails('echo map("abc", "10")', 'E928:')
    call assert_fails('echo map("abc", "a10")', 'E121:')
  END
  call v9.CheckLegacyAndVim9Success(lines)

  " mapnew()
  let lines =<< trim END
    VAR s = "abc"
    call mapnew(s, 'nr2char(char2nr(v:val) + 2)')
    call assert_equal('abc', s)
    call assert_equal('cde', mapnew('abc', 'nr2char(char2nr(v:val) + 2)'))
    call assert_equal('[あ][i][う][え][お]', mapnew('あiうえお', '"[" .. v:val .. "]"'))
    call assert_equal('[あ][a][😊][,][‱][‼][⁇][⁈][⁉][💕][b][💕][c][💕]', mapnew('あa😊,‱‼⁇⁈⁉💕b💕c💕', '"[" .. v:val .. "]"'))
    call assert_equal('', mapnew('abc', '""'))
    call assert_equal('cde', mapnew('abc', LSTART i, x LMIDDLE nr2char(char2nr(x) + 2) LEND))
    call assert_equal('[あ][i][う][え][お]', mapnew('あiうえお', LSTART i, x LMIDDLE '[' .. x .. ']' LEND))
    call assert_equal('', mapnew('abc', LSTART i, x LMIDDLE '' LEND))
    call assert_equal('', mapnew('', "v:val == 'a'"))
    call assert_equal('', mapnew(test_null_string(), "v:val == 'a'"))
  END
  call v9.CheckLegacyAndVim9Success(lines)

  let lines =<< trim END
    #" map() and filter()
    call assert_equal('[あ][⁈][a][😊][⁉][💕][💕][b][💕]', map(filter('あx⁈ax😊x⁉💕💕b💕x', '"x" != v:val'), '"[" .. v:val .. "]"'))

    #" patterns-composing(\Z)
    call assert_equal('ॠॠ', filter('ऊॠॡ,ऊॠॡ', LSTART i, x LMIDDLE x =~ '\Z' .. nr2char(0x0960) LEND))
    call assert_equal('àà', filter('càt,càt', LSTART i, x LMIDDLE x =~ '\Za' LEND))
    call assert_equal('ÅÅ', filter('Åström,Åström', LSTART i, x LMIDDLE x =~ '\Z' .. nr2char(0xc5) LEND))
    call assert_equal('öö', filter('Åström,Åström', LSTART i, x LMIDDLE x =~ '\Z' .. nr2char(0xf6) LEND))
    call assert_equal('ऊ@ॡ', map('ऊॠॡ', LSTART i, x LMIDDLE x =~ '\Z' .. nr2char(0x0960) ? '@' : x LEND))
    call assert_equal('c@t', map('càt', LSTART i, x LMIDDLE x =~ '\Za' ? '@' : x LEND))
    call assert_equal('@ström', map('Åström', LSTART i, x LMIDDLE x =~ '\Z' .. nr2char(0xc5) ? '@' : x LEND))
    call assert_equal('Åstr@m', map('Åström', LSTART i, x LMIDDLE x =~ '\Z' .. nr2char(0xf6) ? '@' : x LEND))

    #" patterns-composing(\%C)
    call assert_equal('ॠॠ', filter('ऊॠॡ,ऊॠॡ', LSTART i, x LMIDDLE x =~ nr2char(0x0960) .. '\%C' LEND))
    call assert_equal('àà', filter('càt,càt', LSTART i, x LMIDDLE x =~ 'a' .. '\%C' LEND))
    call assert_equal('ÅÅ', filter('Åström,Åström', LSTART i, x LMIDDLE x =~ nr2char(0xc5) .. '\%C' LEND))
    call assert_equal('öö', filter('Åström,Åström', LSTART i, x LMIDDLE x =~ nr2char(0xf6) .. '\%C' LEND))
    call assert_equal('ऊ@ॡ', map('ऊॠॡ', LSTART i, x LMIDDLE x =~ nr2char(0x0960) .. '\%C' ? '@' : x LEND))
    call assert_equal('c@t', map('càt', LSTART i, x LMIDDLE x =~ 'a' .. '\%C' ? '@' : x LEND))
    call assert_equal('@ström', map('Åström', LSTART i, x LMIDDLE x =~ nr2char(0xc5) .. '\%C' ? '@' : x LEND))
    call assert_equal('Åstr@m', map('Åström', LSTART i, x LMIDDLE x =~ nr2char(0xf6) .. '\%C' ? '@' : x LEND))
  END
  call v9.CheckLegacyAndVim9Success(lines)
endfunc

" vim: shiftwidth=2 sts=2 expandtab