From 24e3ee9d07e1433cb13b4d96ec20999f5f02b204 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sat, 22 Jul 2023 09:52:13 +0100 Subject: [PATCH] fix(api/options): validate buf and win Fixes #24398 --- runtime/lua/vim/_options.lua | 36 +++++++------------------------- src/nvim/api/options.c | 28 ++++++++++++++++++++----- test/functional/api/vim_spec.lua | 2 +- test/functional/lua/vim_spec.lua | 4 ++-- 4 files changed, 33 insertions(+), 37 deletions(-) diff --git a/runtime/lua/vim/_options.lua b/runtime/lua/vim/_options.lua index d54e8b447c..e1c125baf2 100644 --- a/runtime/lua/vim/_options.lua +++ b/runtime/lua/vim/_options.lua @@ -124,14 +124,12 @@ local function get_option_metatype(name, info) return info.type end -local options_info = setmetatable({}, { - __index = function(t, k) - local info = api.nvim_get_option_info(k) - info.metatype = get_option_metatype(k, info) - rawset(t, k, info) - return rawget(t, k) - end, -}) +--- @param name string +local function get_options_info(name) + local info = api.nvim_get_option_info(name) + info.metatype = get_option_metatype(name, info) + return info +end ---Environment variables defined in the editor session. ---See |expand-env| and |:let-environment| for the Vimscript behavior. @@ -155,34 +153,16 @@ vim.env = setmetatable({}, { end, }) -local function opt_validate(option_name, target_scope) - local scope = options_info[option_name].scope - if scope ~= target_scope then - local scope_to_string = { buf = 'buffer', win = 'window' } - error( - string.format( - [['%s' is a %s option, not a %s option. See ":help %s"]], - option_name, - scope_to_string[scope] or scope, - scope_to_string[target_scope] or target_scope, - option_name - ) - ) - end -end - local function new_buf_opt_accessor(bufnr) return setmetatable({}, { __index = function(_, k) if bufnr == nil and type(k) == 'number' then return new_buf_opt_accessor(k) end - opt_validate(k, 'buf') return api.nvim_get_option_value(k, { buf = bufnr or 0 }) end, __newindex = function(_, k, v) - opt_validate(k, 'buf') return api.nvim_set_option_value(k, v, { buf = bufnr or 0 }) end, }) @@ -203,7 +183,6 @@ local function new_win_opt_accessor(winid, bufnr) error('only bufnr=0 is supported') end - opt_validate(k, 'win') -- TODO(lewis6991): allow passing both buf and win to nvim_get_option_value return api.nvim_get_option_value(k, { scope = bufnr and 'local' or nil, @@ -212,7 +191,6 @@ local function new_win_opt_accessor(winid, bufnr) end, __newindex = function(_, k, v) - opt_validate(k, 'win') -- TODO(lewis6991): allow passing both buf and win to nvim_set_option_value return api.nvim_set_option_value(k, v, { scope = bufnr and 'local' or nil, @@ -680,7 +658,7 @@ local function create_option_accessor(scope) local option_mt local function make_option(name, value) - local info = assert(options_info[name], 'Not a valid option name: ' .. name) + local info = assert(get_options_info(name), 'Not a valid option name: ' .. name) if type(value) == 'table' and getmetatable(value) == option_mt then assert(name == value._name, "must be the same value, otherwise that's weird.") diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index b9a41adc3b..858a663b9f 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -23,8 +23,8 @@ # include "api/options.c.generated.h" #endif -static int validate_option_value_args(Dict(option) *opts, int *scope, int *opt_type, void **from, - char **filetype, Error *err) +static int validate_option_value_args(Dict(option) *opts, char *name, int *scope, int *opt_type, + void **from, char **filetype, Error *err) { if (HAS_KEY(opts->scope)) { VALIDATE_T("scope", kObjectTypeString, opts->scope.type, { @@ -92,6 +92,24 @@ static int validate_option_value_args(Dict(option) *opts, int *scope, int *opt_t return FAIL; }); + int flags = get_option_value_strict(name, NULL, NULL, 0, NULL); + if (flags == 0) { + // hidden or unknown option + api_set_error(err, kErrorTypeValidation, "Unknown option '%s'", name); + } else if (*opt_type & (SREQ_BUF | SREQ_WIN)) { + // if 'buf' or 'win' is passed, make sure the option supports it + int req_flags = *opt_type & SREQ_BUF ? SOPT_BUF : SOPT_WIN; + if (!(flags & req_flags)) { + char *tgt = *opt_type & SREQ_BUF ? "buf" : "win"; + char *global = flags & SOPT_GLOBAL ? "global ": ""; + char *req = flags & SOPT_BUF ? "buffer-local " : + flags & SOPT_WIN ? "window-local " : ""; + + api_set_error(err, kErrorTypeValidation, "'%s' cannot be passed for %s%soption '%s'", + tgt, global, req, name); + } + } + return OK; } @@ -197,7 +215,7 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) void *from = NULL; char *filetype = NULL; - if (!validate_option_value_args(opts, &scope, &opt_type, &from, &filetype, err)) { + if (!validate_option_value_args(opts, name.data, &scope, &opt_type, &from, &filetype, err)) { goto err; } @@ -259,7 +277,7 @@ void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict( int scope = 0; int opt_type = SREQ_GLOBAL; void *to = NULL; - if (!validate_option_value_args(opts, &scope, &opt_type, &to, NULL, err)) { + if (!validate_option_value_args(opts, name.data, &scope, &opt_type, &to, NULL, err)) { return; } @@ -343,7 +361,7 @@ Dictionary nvim_get_option_info2(String name, Dict(option) *opts, Error *err) int scope = 0; int opt_type = SREQ_GLOBAL; void *from = NULL; - if (!validate_option_value_args(opts, &scope, &opt_type, &from, NULL, err)) { + if (!validate_option_value_args(opts, name.data, &scope, &opt_type, &from, NULL, err)) { return (Dictionary)ARRAY_DICT_INIT; } diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 617d0f12ff..6aa9dcc8fa 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -2445,7 +2445,7 @@ describe('API', function() it('can throw exceptions', function() local status, err = pcall(nvim, 'get_option_value', 'invalid-option', {}) eq(false, status) - ok(err:match("Invalid 'option': 'invalid%-option'") ~= nil) + ok(err:match("Unknown option 'invalid%-option'") ~= nil) end) it('does not truncate error message <1 MB #5984', function() diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 77c724b8e6..f1a617fb70 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -1509,7 +1509,7 @@ describe('lua stdlib', function() ]] eq('', funcs.luaeval "vim.bo.filetype") eq(true, funcs.luaeval "vim.bo[BUF].modifiable") - matches("Invalid option %(not found%): 'nosuchopt'$", + matches("Unknown option 'nosuchopt'$", pcall_err(exec_lua, 'return vim.bo.nosuchopt')) matches("Expected lua string$", pcall_err(exec_lua, 'return vim.bo[0][0].autoread')) @@ -1530,7 +1530,7 @@ describe('lua stdlib', function() eq(0, funcs.luaeval "vim.wo.cole") eq(0, funcs.luaeval "vim.wo[0].cole") eq(0, funcs.luaeval "vim.wo[1001].cole") - matches("Invalid option %(not found%): 'notanopt'$", + matches("Unknown option 'notanopt'$", pcall_err(exec_lua, 'return vim.wo.notanopt')) matches("Invalid window id: %-1$", pcall_err(exec_lua, 'return vim.wo[-1].list'))