fix(api/options): validate buf and win

Fixes #24398
This commit is contained in:
Lewis Russell 2023-07-22 09:52:13 +01:00 committed by GitHub
parent cfcda91827
commit 24e3ee9d07
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 33 additions and 37 deletions

View File

@ -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.")

View File

@ -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;
}

View File

@ -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()

View File

@ -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'))