Mercurial > vim
view src/typval.c @ 33471:baa62f464436 v9.0.1988
patch 9.0.1988: Vim9: potential use-after-free for class members
Commit: https://github.com/vim/vim/commit/d2f4800099733216e28d59e1a5710f624b0d9ec1
Author: Yegappan Lakshmanan <yegappan@yahoo.com>
Date: Thu Oct 5 20:24:18 2023 +0200
patch 9.0.1988: Vim9: potential use-after-free for class members
Problem: Vim9: potential use-after-free for class members
Solution: Use the class-related grow array for storing the
member type instead of using a temporary type
list grow array
Use the type list grow array associated with the class than using a
temporary type list grow array to allocate the class member type.
For simple types, a predefined type is used. For complex types, the type
is dynamically allocated from a grow array. For class variables, the
type grow array in the class should be used. So that the lifetime of the
type is same as the lifetime of the class.
closes: #13279
Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
author | Christian Brabandt <cb@256bit.org> |
---|---|
date | Thu, 05 Oct 2023 20:30:11 +0200 |
parents | def9fc5c92d1 |
children | 288da62613ba |
line wrap: on
line source
/* vi:set ts=8 sts=4 sw=4 noet: * * VIM - Vi IMproved by Bram Moolenaar * * Do ":help uganda" in Vim to read copying and usage conditions. * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ /* * typval.c: functions that deal with a typval */ #include "vim.h" #if defined(FEAT_EVAL) || defined(PROTO) /* * Allocate memory for a variable type-value, and make it empty (0 or NULL * value). */ typval_T * alloc_tv(void) { return ALLOC_CLEAR_ONE(typval_T); } /* * Allocate memory for a variable type-value, and assign a string to it. * The string "s" must have been allocated, it is consumed. * Return NULL for out of memory, the variable otherwise. */ typval_T * alloc_string_tv(char_u *s) { typval_T *rettv; rettv = alloc_tv(); if (rettv != NULL) { rettv->v_type = VAR_STRING; rettv->vval.v_string = s; } else vim_free(s); return rettv; } /* * Free the memory for a variable type-value. */ void free_tv(typval_T *varp) { if (varp == NULL) return; switch (varp->v_type) { case VAR_FUNC: func_unref(varp->vval.v_string); // FALLTHROUGH case VAR_STRING: vim_free(varp->vval.v_string); break; case VAR_PARTIAL: partial_unref(varp->vval.v_partial); break; case VAR_BLOB: blob_unref(varp->vval.v_blob); break; case VAR_LIST: list_unref(varp->vval.v_list); break; case VAR_DICT: dict_unref(varp->vval.v_dict); break; case VAR_JOB: #ifdef FEAT_JOB_CHANNEL job_unref(varp->vval.v_job); break; #endif case VAR_CHANNEL: #ifdef FEAT_JOB_CHANNEL channel_unref(varp->vval.v_channel); break; #endif case VAR_CLASS: class_unref(varp->vval.v_class); break; case VAR_OBJECT: object_unref(varp->vval.v_object); break; case VAR_NUMBER: case VAR_FLOAT: case VAR_ANY: case VAR_UNKNOWN: case VAR_VOID: case VAR_BOOL: case VAR_SPECIAL: case VAR_INSTR: break; } vim_free(varp); } /* * Free the memory for a variable value and set the value to NULL or 0. */ void clear_tv(typval_T *varp) { if (varp == NULL) return; switch (varp->v_type) { case VAR_FUNC: func_unref(varp->vval.v_string); // FALLTHROUGH case VAR_STRING: VIM_CLEAR(varp->vval.v_string); break; case VAR_PARTIAL: partial_unref(varp->vval.v_partial); varp->vval.v_partial = NULL; break; case VAR_BLOB: blob_unref(varp->vval.v_blob); varp->vval.v_blob = NULL; break; case VAR_LIST: list_unref(varp->vval.v_list); varp->vval.v_list = NULL; break; case VAR_DICT: dict_unref(varp->vval.v_dict); varp->vval.v_dict = NULL; break; case VAR_NUMBER: case VAR_BOOL: case VAR_SPECIAL: varp->vval.v_number = 0; break; case VAR_FLOAT: varp->vval.v_float = 0.0; break; case VAR_JOB: #ifdef FEAT_JOB_CHANNEL job_unref(varp->vval.v_job); varp->vval.v_job = NULL; #endif break; case VAR_CHANNEL: #ifdef FEAT_JOB_CHANNEL channel_unref(varp->vval.v_channel); varp->vval.v_channel = NULL; #endif break; case VAR_INSTR: VIM_CLEAR(varp->vval.v_instr); break; case VAR_CLASS: class_unref(varp->vval.v_class); varp->vval.v_class = NULL; break; case VAR_OBJECT: object_unref(varp->vval.v_object); varp->vval.v_object = NULL; break; case VAR_UNKNOWN: case VAR_ANY: case VAR_VOID: break; } varp->v_lock = 0; } /* * Set the value of a variable to NULL without freeing items. */ void init_tv(typval_T *varp) { if (varp != NULL) CLEAR_POINTER(varp); } static varnumber_T tv_get_bool_or_number_chk( typval_T *varp, int *denote, int want_bool, int vim9_string_error) // in Vim9 using a string is an error { varnumber_T n = 0L; switch (varp->v_type) { case VAR_NUMBER: if (in_vim9script() && want_bool && varp->vval.v_number != 0 && varp->vval.v_number != 1) { semsg(_(e_using_number_as_bool_nr), varp->vval.v_number); break; } return varp->vval.v_number; case VAR_FLOAT: emsg(_(e_using_float_as_number)); break; case VAR_FUNC: case VAR_PARTIAL: emsg(_(e_using_funcref_as_number)); break; case VAR_STRING: if (vim9_string_error && in_vim9script()) { emsg_using_string_as(varp, !want_bool); break; } if (varp->vval.v_string != NULL) vim_str2nr(varp->vval.v_string, NULL, NULL, STR2NR_ALL, &n, NULL, 0, FALSE, NULL); return n; case VAR_LIST: emsg(_(e_using_list_as_number)); break; case VAR_DICT: emsg(_(e_using_dictionary_as_number)); break; case VAR_BOOL: case VAR_SPECIAL: if (!want_bool && in_vim9script()) { if (varp->v_type == VAR_BOOL) emsg(_(e_using_bool_as_number)); else emsg(_(e_using_special_as_number)); break; } return varp->vval.v_number == VVAL_TRUE ? 1 : 0; case VAR_JOB: #ifdef FEAT_JOB_CHANNEL emsg(_(e_using_job_as_number)); break; #endif case VAR_CHANNEL: #ifdef FEAT_JOB_CHANNEL emsg(_(e_using_channel_as_number)); break; #endif case VAR_BLOB: emsg(_(e_using_blob_as_number)); break; case VAR_CLASS: emsg(_(e_using_class_as_number)); break; case VAR_OBJECT: emsg(_(e_using_object_as_number)); break; case VAR_VOID: emsg(_(e_cannot_use_void_value)); break; case VAR_UNKNOWN: case VAR_ANY: case VAR_INSTR: internal_error_no_abort("tv_get_number(UNKNOWN)"); break; } if (denote == NULL) // useful for values that must be unsigned n = -1; else *denote = TRUE; return n; } /* * Get the number value of a variable. * If it is a String variable, uses vim_str2nr(). * For incompatible types, return 0. * tv_get_number_chk() is similar to tv_get_number(), but informs the * caller of incompatible types: it sets *denote to TRUE if "denote" * is not NULL or returns -1 otherwise. */ varnumber_T tv_get_number(typval_T *varp) { int error = FALSE; return tv_get_number_chk(varp, &error); // return 0L on error } /* * Like tv_get_number() but in Vim9 script do convert a number in a string to a * number without giving an error. */ varnumber_T tv_to_number(typval_T *varp) { int error = FALSE; return tv_get_bool_or_number_chk(varp, &error, FALSE, FALSE); } varnumber_T tv_get_number_chk(typval_T *varp, int *denote) { return tv_get_bool_or_number_chk(varp, denote, FALSE, TRUE); } /* * Get the boolean value of "varp". This is like tv_get_number_chk(), * but in Vim9 script accepts Number (0 and 1) and Bool/Special. */ varnumber_T tv_get_bool(typval_T *varp) { return tv_get_bool_or_number_chk(varp, NULL, TRUE, TRUE); } /* * Get the boolean value of "varp". This is like tv_get_number_chk(), * but in Vim9 script accepts Number and Bool. */ varnumber_T tv_get_bool_chk(typval_T *varp, int *denote) { return tv_get_bool_or_number_chk(varp, denote, TRUE, TRUE); } static float_T tv_get_float_chk(typval_T *varp, int *error) { switch (varp->v_type) { case VAR_NUMBER: return (float_T)(varp->vval.v_number); case VAR_FLOAT: return varp->vval.v_float; case VAR_FUNC: case VAR_PARTIAL: emsg(_(e_using_funcref_as_float)); break; case VAR_STRING: emsg(_(e_using_string_as_float)); break; case VAR_LIST: emsg(_(e_using_list_as_float)); break; case VAR_DICT: emsg(_(e_using_dictionary_as_float)); break; case VAR_BOOL: emsg(_(e_using_boolean_value_as_float)); break; case VAR_SPECIAL: emsg(_(e_using_special_value_as_float)); break; case VAR_JOB: # ifdef FEAT_JOB_CHANNEL emsg(_(e_using_job_as_float)); break; # endif case VAR_CHANNEL: # ifdef FEAT_JOB_CHANNEL emsg(_(e_using_channel_as_float)); break; # endif case VAR_BLOB: emsg(_(e_using_blob_as_float)); break; case VAR_CLASS: emsg(_(e_using_class_as_float)); break; case VAR_OBJECT: emsg(_(e_using_object_as_float)); break; case VAR_VOID: emsg(_(e_cannot_use_void_value)); break; case VAR_UNKNOWN: case VAR_ANY: case VAR_INSTR: internal_error_no_abort("tv_get_float(UNKNOWN)"); break; } if (error != NULL) *error = TRUE; return 0; } float_T tv_get_float(typval_T *varp) { return tv_get_float_chk(varp, NULL); } /* * Give an error and return FAIL unless "args[idx]" is unknown */ int check_for_unknown_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_UNKNOWN) { semsg(_(e_too_many_arguments), idx + 1); return FAIL; } return OK; } /* * Give an error and return FAIL unless "args[idx]" is a string. */ int check_for_string_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_STRING) { semsg(_(e_string_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Give an error and return FAIL unless "args[idx]" is a non-empty string. */ int check_for_nonempty_string_arg(typval_T *args, int idx) { if (check_for_string_arg(args, idx) == FAIL) return FAIL; if (args[idx].vval.v_string == NULL || *args[idx].vval.v_string == NUL) { semsg(_(e_non_empty_string_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Check for an optional string argument at 'idx' */ int check_for_opt_string_arg(typval_T *args, int idx) { return (args[idx].v_type == VAR_UNKNOWN || check_for_string_arg(args, idx) != FAIL) ? OK : FAIL; } /* * Give an error and return FAIL unless "args[idx]" is a number. */ int check_for_number_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_NUMBER) { semsg(_(e_number_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Check for an optional number argument at 'idx' */ int check_for_opt_number_arg(typval_T *args, int idx) { return (args[idx].v_type == VAR_UNKNOWN || check_for_number_arg(args, idx) != FAIL) ? OK : FAIL; } /* * Give an error and return FAIL unless "args[idx]" is a float or a number. */ int check_for_float_or_nr_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_FLOAT && args[idx].v_type != VAR_NUMBER) { semsg(_(e_float_or_number_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Give an error and return FAIL unless "args[idx]" is a bool. */ int check_for_bool_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_BOOL && !(args[idx].v_type == VAR_NUMBER && (args[idx].vval.v_number == 0 || args[idx].vval.v_number == 1))) { semsg(_(e_bool_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Check for an optional bool argument at 'idx'. * Return FAIL if the type is wrong. */ int check_for_opt_bool_arg(typval_T *args, int idx) { if (args[idx].v_type == VAR_UNKNOWN) return OK; return check_for_bool_arg(args, idx); } /* * Give an error and return FAIL unless "args[idx]" is a blob. */ int check_for_blob_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_BLOB) { semsg(_(e_blob_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Give an error and return FAIL unless "args[idx]" is a list. */ int check_for_list_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_LIST) { semsg(_(e_list_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Give an error and return FAIL unless "args[idx]" is a non-NULL list. */ int check_for_nonnull_list_arg(typval_T *args, int idx) { if (check_for_list_arg(args, idx) == FAIL) return FAIL; if (args[idx].vval.v_list == NULL) { semsg(_(e_non_null_list_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Check for an optional list argument at 'idx' */ int check_for_opt_list_arg(typval_T *args, int idx) { return (args[idx].v_type == VAR_UNKNOWN || check_for_list_arg(args, idx) != FAIL) ? OK : FAIL; } /* * Give an error and return FAIL unless "args[idx]" is a dict. */ int check_for_dict_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_DICT) { semsg(_(e_dict_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Give an error and return FAIL unless "args[idx]" is a non-NULL dict. */ int check_for_nonnull_dict_arg(typval_T *args, int idx) { if (check_for_dict_arg(args, idx) == FAIL) return FAIL; if (args[idx].vval.v_dict == NULL) { semsg(_(e_non_null_dict_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Check for an optional dict argument at 'idx' */ int check_for_opt_dict_arg(typval_T *args, int idx) { return (args[idx].v_type == VAR_UNKNOWN || check_for_dict_arg(args, idx) != FAIL) ? OK : FAIL; } #if defined(FEAT_JOB_CHANNEL) || defined(PROTO) /* * Give an error and return FAIL unless "args[idx]" is a channel or a job. */ int check_for_chan_or_job_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_CHANNEL && args[idx].v_type != VAR_JOB) { semsg(_(e_chan_or_job_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Give an error and return FAIL unless "args[idx]" is an optional channel or a * job. */ int check_for_opt_chan_or_job_arg(typval_T *args, int idx) { return (args[idx].v_type == VAR_UNKNOWN || check_for_chan_or_job_arg(args, idx) != FAIL) ? OK : FAIL; } /* * Give an error and return FAIL unless "args[idx]" is a job. */ int check_for_job_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_JOB) { semsg(_(e_job_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Check for an optional job argument at 'idx'. */ int check_for_opt_job_arg(typval_T *args, int idx) { return (args[idx].v_type == VAR_UNKNOWN || check_for_job_arg(args, idx) != FAIL) ? OK : FAIL; } #else /* * Give an error and return FAIL unless "args[idx]" is an optional channel or a * job. Used without the +channel feature, thus only VAR_UNKNOWN is accepted. */ int check_for_opt_chan_or_job_arg(typval_T *args, int idx) { return args[idx].v_type == VAR_UNKNOWN ? OK : FAIL; } #endif /* * Give an error and return FAIL unless "args[idx]" is a string or * a number. */ int check_for_string_or_number_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_STRING && args[idx].v_type != VAR_NUMBER) { semsg(_(e_string_or_number_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Check for an optional string or number argument at 'idx'. */ int check_for_opt_string_or_number_arg(typval_T *args, int idx) { return (args[idx].v_type == VAR_UNKNOWN || check_for_string_or_number_arg(args, idx) != FAIL) ? OK : FAIL; } /* * Give an error and return FAIL unless "args[idx]" is a buffer number. * Buffer number can be a number or a string. */ int check_for_buffer_arg(typval_T *args, int idx) { return check_for_string_or_number_arg(args, idx); } /* * Check for an optional buffer argument at 'idx' */ int check_for_opt_buffer_arg(typval_T *args, int idx) { return (args[idx].v_type == VAR_UNKNOWN || check_for_buffer_arg(args, idx) != FAIL) ? OK : FAIL; } /* * Give an error and return FAIL unless "args[idx]" is a line number. * Line number can be a number or a string. */ int check_for_lnum_arg(typval_T *args, int idx) { return check_for_string_or_number_arg(args, idx); } /* * Check for an optional line number argument at 'idx' */ int check_for_opt_lnum_arg(typval_T *args, int idx) { return (args[idx].v_type == VAR_UNKNOWN || check_for_lnum_arg(args, idx) != FAIL) ? OK : FAIL; } #if defined(FEAT_JOB_CHANNEL) || defined(PROTO) /* * Give an error and return FAIL unless "args[idx]" is a string or a blob. */ int check_for_string_or_blob_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_STRING && args[idx].v_type != VAR_BLOB) { semsg(_(e_string_or_blob_required_for_argument_nr), idx + 1); return FAIL; } return OK; } #endif /* * Give an error and return FAIL unless "args[idx]" is a string or a list. */ int check_for_string_or_list_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_STRING && args[idx].v_type != VAR_LIST) { semsg(_(e_string_or_list_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Give an error and return FAIL unless "args[idx]" is a string, a list or a * blob. */ int check_for_string_or_list_or_blob_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_STRING && args[idx].v_type != VAR_LIST && args[idx].v_type != VAR_BLOB) { semsg(_(e_string_list_or_blob_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Check for an optional string or list argument at 'idx' */ int check_for_opt_string_or_list_arg(typval_T *args, int idx) { return (args[idx].v_type == VAR_UNKNOWN || check_for_string_or_list_arg(args, idx) != FAIL) ? OK : FAIL; } /* * Give an error and return FAIL unless "args[idx]" is a string or a dict. */ int check_for_string_or_dict_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_STRING && args[idx].v_type != VAR_DICT) { semsg(_(e_string_or_dict_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Give an error and return FAIL unless "args[idx]" is a string or a number * or a list. */ int check_for_string_or_number_or_list_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_STRING && args[idx].v_type != VAR_NUMBER && args[idx].v_type != VAR_LIST) { semsg(_(e_string_number_or_list_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Give an error and return FAIL unless "args[idx]" is an optional string * or number or a list */ int check_for_opt_string_or_number_or_list_arg(typval_T *args, int idx) { return (args[idx].v_type == VAR_UNKNOWN || check_for_string_or_number_or_list_arg(args, idx) != FAIL) ? OK : FAIL; } /* * Give an error and return FAIL unless "args[idx]" is a string or a number * or a list or a blob. */ int check_for_string_or_number_or_list_or_blob_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_STRING && args[idx].v_type != VAR_NUMBER && args[idx].v_type != VAR_LIST && args[idx].v_type != VAR_BLOB) { semsg(_(e_string_number_list_or_blob_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Give an error and return FAIL unless "args[idx]" is a string or a list * or a dict. */ int check_for_string_or_list_or_dict_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_STRING && args[idx].v_type != VAR_LIST && args[idx].v_type != VAR_DICT) { semsg(_(e_string_list_or_dict_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Give an error and return FAIL unless "args[idx]" is a string * or a function reference. */ int check_for_string_or_func_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_PARTIAL && args[idx].v_type != VAR_FUNC && args[idx].v_type != VAR_STRING) { semsg(_(e_string_or_function_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Give an error and return FAIL unless "args[idx]" is a list or a blob. */ int check_for_list_or_blob_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_LIST && args[idx].v_type != VAR_BLOB) { semsg(_(e_list_or_blob_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Give an error and return FAIL unless "args[idx]" is a list or dict */ int check_for_list_or_dict_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_LIST && args[idx].v_type != VAR_DICT) { semsg(_(e_list_or_dict_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Give an error and return FAIL unless "args[idx]" is a list or dict or a * blob. */ int check_for_list_or_dict_or_blob_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_LIST && args[idx].v_type != VAR_DICT && args[idx].v_type != VAR_BLOB) { semsg(_(e_list_dict_or_blob_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Give an error and return FAIL unless "args[idx]" is a list or dict or a * blob or a string. */ int check_for_list_or_dict_or_blob_or_string_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_LIST && args[idx].v_type != VAR_DICT && args[idx].v_type != VAR_BLOB && args[idx].v_type != VAR_STRING) { semsg(_(e_list_dict_blob_or_string_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Give an error and return FAIL unless "args[idx]" is an optional buffer * number or a dict. */ int check_for_opt_buffer_or_dict_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_UNKNOWN && args[idx].v_type != VAR_STRING && args[idx].v_type != VAR_NUMBER && args[idx].v_type != VAR_DICT) { semsg(_(e_string_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Give an error and return FAIL unless "args[idx]" is an object. */ int check_for_object_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_OBJECT) { semsg(_(e_object_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Give an error and return FAIL unless "args[idx]" is a class or a list. */ int check_for_class_or_list_arg(typval_T *args, int idx) { if (args[idx].v_type != VAR_CLASS && args[idx].v_type != VAR_LIST) { semsg(_(e_list_or_class_required_for_argument_nr), idx + 1); return FAIL; } return OK; } /* * Get the string value of a variable. * If it is a Number variable, the number is converted into a string. * tv_get_string() uses a single, static buffer. YOU CAN ONLY USE IT ONCE! * tv_get_string_buf() uses a given buffer. * If the String variable has never been set, return an empty string. * Never returns NULL; * tv_get_string_chk() and tv_get_string_buf_chk() are similar, but return * NULL on error. */ char_u * tv_get_string(typval_T *varp) { static char_u mybuf[NUMBUFLEN]; return tv_get_string_buf(varp, mybuf); } /* * Like tv_get_string() but don't allow number to string conversion for Vim9. */ char_u * tv_get_string_strict(typval_T *varp) { static char_u mybuf[NUMBUFLEN]; char_u *res = tv_get_string_buf_chk_strict( varp, mybuf, in_vim9script()); return res != NULL ? res : (char_u *)""; } char_u * tv_get_string_buf(typval_T *varp, char_u *buf) { char_u *res = tv_get_string_buf_chk(varp, buf); return res != NULL ? res : (char_u *)""; } /* * Careful: This uses a single, static buffer. YOU CAN ONLY USE IT ONCE! */ char_u * tv_get_string_chk(typval_T *varp) { static char_u mybuf[NUMBUFLEN]; return tv_get_string_buf_chk(varp, mybuf); } char_u * tv_get_string_buf_chk(typval_T *varp, char_u *buf) { return tv_get_string_buf_chk_strict(varp, buf, FALSE); } char_u * tv_get_string_buf_chk_strict(typval_T *varp, char_u *buf, int strict) { switch (varp->v_type) { case VAR_NUMBER: if (strict) { emsg(_(e_using_number_as_string)); break; } vim_snprintf((char *)buf, NUMBUFLEN, "%lld", (varnumber_T)varp->vval.v_number); return buf; case VAR_FUNC: case VAR_PARTIAL: emsg(_(e_using_funcref_as_string)); break; case VAR_LIST: emsg(_(e_using_list_as_string)); break; case VAR_DICT: emsg(_(e_using_dictionary_as_string)); break; case VAR_FLOAT: if (strict) { emsg(_(e_using_float_as_string)); break; } vim_snprintf((char *)buf, NUMBUFLEN, "%g", varp->vval.v_float); return buf; case VAR_STRING: if (varp->vval.v_string != NULL) return varp->vval.v_string; return (char_u *)""; case VAR_BOOL: case VAR_SPECIAL: STRCPY(buf, get_var_special_name(varp->vval.v_number)); return buf; case VAR_BLOB: emsg(_(e_using_blob_as_string)); break; case VAR_CLASS: emsg(_(e_using_class_as_string)); break; case VAR_OBJECT: emsg(_(e_using_object_as_string)); break; case VAR_JOB: #ifdef FEAT_JOB_CHANNEL if (in_vim9script()) { semsg(_(e_using_invalid_value_as_string_str), "job"); break; } return job_to_string_buf(varp, buf); #endif break; case VAR_CHANNEL: #ifdef FEAT_JOB_CHANNEL if (in_vim9script()) { semsg(_(e_using_invalid_value_as_string_str), "channel"); break; } return channel_to_string_buf(varp, buf); #endif break; case VAR_VOID: emsg(_(e_cannot_use_void_value)); break; case VAR_UNKNOWN: case VAR_ANY: case VAR_INSTR: semsg(_(e_using_invalid_value_as_string_str), vartype_name(varp->v_type)); break; } return NULL; } /* * Turn a typeval into a string. Similar to tv_get_string_buf() but uses * string() on Dict, List, etc. */ char_u * tv_stringify(typval_T *varp, char_u *buf) { if (varp->v_type == VAR_LIST || varp->v_type == VAR_DICT || varp->v_type == VAR_BLOB || varp->v_type == VAR_FUNC || varp->v_type == VAR_PARTIAL || varp->v_type == VAR_FLOAT) { typval_T tmp; f_string(varp, &tmp); tv_get_string_buf(&tmp, buf); clear_tv(varp); *varp = tmp; return tmp.vval.v_string; } return tv_get_string_buf(varp, buf); } /* * Return TRUE if typeval "tv" and its value are set to be locked (immutable). * Also give an error message, using "name" or _("name") when use_gettext is * TRUE. */ int tv_check_lock(typval_T *tv, char_u *name, int use_gettext) { int lock = 0; switch (tv->v_type) { case VAR_BLOB: if (tv->vval.v_blob != NULL) lock = tv->vval.v_blob->bv_lock; break; case VAR_LIST: if (tv->vval.v_list != NULL) lock = tv->vval.v_list->lv_lock; break; case VAR_DICT: if (tv->vval.v_dict != NULL) lock = tv->vval.v_dict->dv_lock; break; default: break; } return value_check_lock(tv->v_lock, name, use_gettext) || (lock != 0 && value_check_lock(lock, name, use_gettext)); } /* * Copy the values from typval_T "from" to typval_T "to". * When needed allocates string or increases reference count. * Does not make a copy of a list, blob or dict but copies the reference! * It is OK for "from" and "to" to point to the same item. This is used to * make a copy later. */ void copy_tv(typval_T *from, typval_T *to) { to->v_type = from->v_type; to->v_lock = 0; switch (from->v_type) { case VAR_NUMBER: case VAR_BOOL: case VAR_SPECIAL: to->vval.v_number = from->vval.v_number; break; case VAR_FLOAT: to->vval.v_float = from->vval.v_float; break; case VAR_JOB: #ifdef FEAT_JOB_CHANNEL to->vval.v_job = from->vval.v_job; if (to->vval.v_job != NULL) ++to->vval.v_job->jv_refcount; break; #endif case VAR_CHANNEL: #ifdef FEAT_JOB_CHANNEL to->vval.v_channel = from->vval.v_channel; if (to->vval.v_channel != NULL) ++to->vval.v_channel->ch_refcount; break; #endif case VAR_INSTR: to->vval.v_instr = from->vval.v_instr; break; case VAR_CLASS: copy_class(from, to); break; case VAR_OBJECT: copy_object(from, to); break; case VAR_STRING: case VAR_FUNC: if (from->vval.v_string == NULL) to->vval.v_string = NULL; else { to->vval.v_string = vim_strsave(from->vval.v_string); if (from->v_type == VAR_FUNC) func_ref(to->vval.v_string); } break; case VAR_PARTIAL: if (from->vval.v_partial == NULL) to->vval.v_partial = NULL; else { to->vval.v_partial = from->vval.v_partial; ++to->vval.v_partial->pt_refcount; } break; case VAR_BLOB: if (from->vval.v_blob == NULL) to->vval.v_blob = NULL; else { to->vval.v_blob = from->vval.v_blob; ++to->vval.v_blob->bv_refcount; } break; case VAR_LIST: if (from->vval.v_list == NULL) to->vval.v_list = NULL; else { to->vval.v_list = from->vval.v_list; ++to->vval.v_list->lv_refcount; } break; case VAR_DICT: if (from->vval.v_dict == NULL) to->vval.v_dict = NULL; else { to->vval.v_dict = from->vval.v_dict; ++to->vval.v_dict->dv_refcount; } break; case VAR_VOID: emsg(_(e_cannot_use_void_value)); break; case VAR_UNKNOWN: case VAR_ANY: internal_error_no_abort("copy_tv(UNKNOWN)"); break; } } /* * Compare "tv1" and "tv2". * Put the result in "tv1". Caller should clear "tv2". */ int typval_compare( typval_T *tv1, // first operand typval_T *tv2, // second operand exprtype_T type, // operator int ic) // ignore case { varnumber_T n1, n2; int res = 0; int type_is = type == EXPR_IS || type == EXPR_ISNOT; if (type_is && tv1->v_type != tv2->v_type) { // For "is" a different type always means FALSE, for "isnot" // it means TRUE. n1 = (type == EXPR_ISNOT); } else if (((tv1->v_type == VAR_SPECIAL && tv1->vval.v_number == VVAL_NULL) || (tv2->v_type == VAR_SPECIAL && tv2->vval.v_number == VVAL_NULL)) && tv1->v_type != tv2->v_type && (type == EXPR_EQUAL || type == EXPR_NEQUAL)) { n1 = typval_compare_null(tv1, tv2); if (n1 == MAYBE) { clear_tv(tv1); return FAIL; } if (type == EXPR_NEQUAL) n1 = !n1; } else if (tv1->v_type == VAR_BLOB || tv2->v_type == VAR_BLOB) { if (typval_compare_blob(tv1, tv2, type, &res) == FAIL) { clear_tv(tv1); return FAIL; } n1 = res; } else if (tv1->v_type == VAR_LIST || tv2->v_type == VAR_LIST) { if (typval_compare_list(tv1, tv2, type, ic, &res) == FAIL) { clear_tv(tv1); return FAIL; } n1 = res; } else if (tv1->v_type == VAR_CLASS || tv2->v_type == VAR_CLASS) { if (typval_compare_class(tv1, tv2, type, ic, &res) == FAIL) { clear_tv(tv1); return FAIL; } n1 = res; } else if (tv1->v_type == VAR_OBJECT || tv2->v_type == VAR_OBJECT) { if (typval_compare_object(tv1, tv2, type, ic, &res) == FAIL) { clear_tv(tv1); return FAIL; } n1 = res; } else if (tv1->v_type == VAR_DICT || tv2->v_type == VAR_DICT) { if (typval_compare_dict(tv1, tv2, type, ic, &res) == FAIL) { clear_tv(tv1); return FAIL; } n1 = res; } else if (tv1->v_type == VAR_FUNC || tv2->v_type == VAR_FUNC || tv1->v_type == VAR_PARTIAL || tv2->v_type == VAR_PARTIAL) { if (typval_compare_func(tv1, tv2, type, ic, &res) == FAIL) { clear_tv(tv1); return FAIL; } n1 = res; } // If one of the two variables is a float, compare as a float. // When using "=~" or "!~", always compare as string. else if ((tv1->v_type == VAR_FLOAT || tv2->v_type == VAR_FLOAT) && type != EXPR_MATCH && type != EXPR_NOMATCH) { float_T f1, f2; int error = FALSE; f1 = tv_get_float_chk(tv1, &error); if (!error) f2 = tv_get_float_chk(tv2, &error); if (error) { clear_tv(tv1); return FAIL; } n1 = FALSE; switch (type) { case EXPR_IS: case EXPR_EQUAL: n1 = (f1 == f2); break; case EXPR_ISNOT: case EXPR_NEQUAL: n1 = (f1 != f2); break; case EXPR_GREATER: n1 = (f1 > f2); break; case EXPR_GEQUAL: n1 = (f1 >= f2); break; case EXPR_SMALLER: n1 = (f1 < f2); break; case EXPR_SEQUAL: n1 = (f1 <= f2); break; case EXPR_UNKNOWN: case EXPR_MATCH: default: break; // avoid gcc warning } } // If one of the two variables is a number, compare as a number. // When using "=~" or "!~", always compare as string. else if ((tv1->v_type == VAR_NUMBER || tv2->v_type == VAR_NUMBER) && type != EXPR_MATCH && type != EXPR_NOMATCH) { int error = FALSE; n1 = tv_get_number_chk(tv1, &error); if (!error) n2 = tv_get_number_chk(tv2, &error); if (error) { clear_tv(tv1); return FAIL; } switch (type) { case EXPR_IS: case EXPR_EQUAL: n1 = (n1 == n2); break; case EXPR_ISNOT: case EXPR_NEQUAL: n1 = (n1 != n2); break; case EXPR_GREATER: n1 = (n1 > n2); break; case EXPR_GEQUAL: n1 = (n1 >= n2); break; case EXPR_SMALLER: n1 = (n1 < n2); break; case EXPR_SEQUAL: n1 = (n1 <= n2); break; case EXPR_UNKNOWN: case EXPR_MATCH: default: break; // avoid gcc warning } } else if (in_vim9script() && (tv1->v_type == VAR_BOOL || tv2->v_type == VAR_BOOL || (tv1->v_type == VAR_SPECIAL && tv2->v_type == VAR_SPECIAL))) { if (tv1->v_type != tv2->v_type) { semsg(_(e_cannot_compare_str_with_str), vartype_name(tv1->v_type), vartype_name(tv2->v_type)); clear_tv(tv1); return FAIL; } n1 = tv1->vval.v_number; n2 = tv2->vval.v_number; switch (type) { case EXPR_IS: case EXPR_EQUAL: n1 = (n1 == n2); break; case EXPR_ISNOT: case EXPR_NEQUAL: n1 = (n1 != n2); break; default: semsg(_(e_invalid_operation_for_str), vartype_name(tv1->v_type)); clear_tv(tv1); return FAIL; } } #ifdef FEAT_JOB_CHANNEL else if (tv1->v_type == tv2->v_type && (tv1->v_type == VAR_CHANNEL || tv1->v_type == VAR_JOB) && (type == EXPR_NEQUAL || type == EXPR_EQUAL)) { if (tv1->v_type == VAR_CHANNEL) n1 = tv1->vval.v_channel == tv2->vval.v_channel; else n1 = tv1->vval.v_job == tv2->vval.v_job; if (type == EXPR_NEQUAL) n1 = !n1; } #endif else { if (typval_compare_string(tv1, tv2, type, ic, &res) == FAIL) { clear_tv(tv1); return FAIL; } n1 = res; } clear_tv(tv1); if (in_vim9script()) { tv1->v_type = VAR_BOOL; tv1->vval.v_number = n1 ? VVAL_TRUE : VVAL_FALSE; } else { tv1->v_type = VAR_NUMBER; tv1->vval.v_number = n1; } return OK; } /* * Compare "tv1" to "tv2" as lists according to "type" and "ic". * Put the result, false or true, in "res". * Return FAIL and give an error message when the comparison can't be done. */ int typval_compare_list( typval_T *tv1, typval_T *tv2, exprtype_T type, int ic, int *res) { int val = 0; if (type == EXPR_IS || type == EXPR_ISNOT) { val = (tv1->v_type == tv2->v_type && tv1->vval.v_list == tv2->vval.v_list); if (type == EXPR_ISNOT) val = !val; } else if (tv1->v_type != tv2->v_type || (type != EXPR_EQUAL && type != EXPR_NEQUAL)) { if (tv1->v_type != tv2->v_type) emsg(_(e_can_only_compare_list_with_list)); else emsg(_(e_invalid_operation_for_list)); return FAIL; } else { val = list_equal(tv1->vval.v_list, tv2->vval.v_list, ic, FALSE); if (type == EXPR_NEQUAL) val = !val; } *res = val; return OK; } /* * Compare v:null with another type. Return TRUE if the value is NULL. */ int typval_compare_null(typval_T *tv1, typval_T *tv2) { if ((tv1->v_type == VAR_SPECIAL && tv1->vval.v_number == VVAL_NULL) || (tv2->v_type == VAR_SPECIAL && tv2->vval.v_number == VVAL_NULL)) { typval_T *tv = tv1->v_type == VAR_SPECIAL ? tv2 : tv1; switch (tv->v_type) { case VAR_BLOB: return tv->vval.v_blob == NULL; #ifdef FEAT_JOB_CHANNEL case VAR_CHANNEL: return tv->vval.v_channel == NULL; #endif // TODO: null_class handling // case VAR_CLASS: return tv->vval.v_class == NULL; case VAR_DICT: return tv->vval.v_dict == NULL; case VAR_FUNC: return tv->vval.v_string == NULL; #ifdef FEAT_JOB_CHANNEL case VAR_JOB: return tv->vval.v_job == NULL; #endif case VAR_LIST: return tv->vval.v_list == NULL; case VAR_OBJECT: return tv->vval.v_object == NULL; case VAR_PARTIAL: return tv->vval.v_partial == NULL; case VAR_STRING: return tv->vval.v_string == NULL; case VAR_NUMBER: if (!in_vim9script()) return tv->vval.v_number == 0; break; case VAR_FLOAT: if (!in_vim9script()) return tv->vval.v_float == 0.0; break; default: break; } } // although comparing null with number, float or bool is not very useful // we won't give an error return FALSE; } /* * Compare "tv1" to "tv2" as blobs according to "type". * Put the result, false or true, in "res". * Return FAIL and give an error message when the comparison can't be done. */ int typval_compare_blob( typval_T *tv1, typval_T *tv2, exprtype_T type, int *res) { int val = 0; if (type == EXPR_IS || type == EXPR_ISNOT) { val = (tv1->v_type == tv2->v_type && tv1->vval.v_blob == tv2->vval.v_blob); if (type == EXPR_ISNOT) val = !val; } else if (tv1->v_type != tv2->v_type || (type != EXPR_EQUAL && type != EXPR_NEQUAL)) { if (tv1->v_type != tv2->v_type) emsg(_(e_can_only_compare_blob_with_blob)); else emsg(_(e_invalid_operation_for_blob)); return FAIL; } else { val = blob_equal(tv1->vval.v_blob, tv2->vval.v_blob); if (type == EXPR_NEQUAL) val = !val; } *res = val; return OK; } /* * Compare "tv1" to "tv2" as classes according to "type". * Put the result, false or true, in "res". * Return FAIL and give an error message when the comparison can't be done. */ int typval_compare_class( typval_T *tv1, typval_T *tv2, exprtype_T type UNUSED, int ic UNUSED, int *res) { // TODO: use "type" *res = tv1->vval.v_class == tv2->vval.v_class; return OK; } /* * Compare "tv1" to "tv2" as objects according to "type". * Put the result, false or true, in "res". * Return FAIL and give an error message when the comparison can't be done. */ int typval_compare_object( typval_T *tv1, typval_T *tv2, exprtype_T type, int ic, int *res) { int res_match = type == EXPR_EQUAL || type == EXPR_IS ? TRUE : FALSE; if (tv1->vval.v_object == NULL && tv2->vval.v_object == NULL) { *res = res_match; return OK; } if (tv1->vval.v_object == NULL || tv2->vval.v_object == NULL) { *res = !res_match; return OK; } class_T *cl1 = tv1->vval.v_object->obj_class; class_T *cl2 = tv2->vval.v_object->obj_class; if (cl1 != cl2 || cl1 == NULL || cl2 == NULL) { *res = !res_match; return OK; } object_T *obj1 = tv1->vval.v_object; object_T *obj2 = tv2->vval.v_object; if (type == EXPR_IS || type == EXPR_ISNOT) { *res = obj1 == obj2 ? res_match : !res_match; return OK; } for (int i = 0; i < cl1->class_obj_member_count; ++i) if (!tv_equal((typval_T *)(obj1 + 1) + i, (typval_T *)(obj2 + 1) + i, ic, TRUE)) { *res = !res_match; return OK; } *res = res_match; return OK; } /* * Compare "tv1" to "tv2" as dictionaries according to "type" and "ic". * Put the result, false or true, in "res". * Return FAIL and give an error message when the comparison can't be done. */ int typval_compare_dict( typval_T *tv1, typval_T *tv2, exprtype_T type, int ic, int *res) { int val; if (type == EXPR_IS || type == EXPR_ISNOT) { val = (tv1->v_type == tv2->v_type && tv1->vval.v_dict == tv2->vval.v_dict); if (type == EXPR_ISNOT) val = !val; } else if (tv1->v_type != tv2->v_type || (type != EXPR_EQUAL && type != EXPR_NEQUAL)) { if (tv1->v_type != tv2->v_type) emsg(_(e_can_only_compare_dictionary_with_dictionary)); else emsg(_(e_invalid_operation_for_dictionary)); return FAIL; } else { val = dict_equal(tv1->vval.v_dict, tv2->vval.v_dict, ic, FALSE); if (type == EXPR_NEQUAL) val = !val; } *res = val; return OK; } /* * Compare "tv1" to "tv2" as funcrefs according to "type" and "ic". * Put the result, false or true, in "res". * Return FAIL and give an error message when the comparison can't be done. */ int typval_compare_func( typval_T *tv1, typval_T *tv2, exprtype_T type, int ic, int *res) { int val = 0; if (type != EXPR_EQUAL && type != EXPR_NEQUAL && type != EXPR_IS && type != EXPR_ISNOT) { emsg(_(e_invalid_operation_for_funcrefs)); return FAIL; } if ((tv1->v_type == VAR_PARTIAL && tv1->vval.v_partial == NULL) || (tv2->v_type == VAR_PARTIAL && tv2->vval.v_partial == NULL)) // When both partials are NULL, then they are equal. // Otherwise they are not equal. val = (tv1->vval.v_partial == tv2->vval.v_partial); else if (type == EXPR_IS || type == EXPR_ISNOT) { if (tv1->v_type == VAR_FUNC && tv2->v_type == VAR_FUNC) // strings are considered the same if their value is // the same val = tv_equal(tv1, tv2, ic, FALSE); else if (tv1->v_type == VAR_PARTIAL && tv2->v_type == VAR_PARTIAL) val = (tv1->vval.v_partial == tv2->vval.v_partial); else val = FALSE; } else val = tv_equal(tv1, tv2, ic, FALSE); if (type == EXPR_NEQUAL || type == EXPR_ISNOT) val = !val; *res = val; return OK; } /* * Compare "tv1" to "tv2" as strings according to "type" and "ic". * Put the result, false or true, in "res". * Return FAIL and give an error message when the comparison can't be done. */ int typval_compare_string( typval_T *tv1, typval_T *tv2, exprtype_T type, int ic, int *res) { int i = 0; int val = FALSE; char_u *s1, *s2; char_u buf1[NUMBUFLEN], buf2[NUMBUFLEN]; if (in_vim9script() && ((tv1->v_type != VAR_STRING && tv1->v_type != VAR_SPECIAL) || (tv2->v_type != VAR_STRING && tv2->v_type != VAR_SPECIAL))) { semsg(_(e_cannot_compare_str_with_str), vartype_name(tv1->v_type), vartype_name(tv2->v_type)); return FAIL; } s1 = tv_get_string_buf(tv1, buf1); s2 = tv_get_string_buf(tv2, buf2); if (type != EXPR_MATCH && type != EXPR_NOMATCH) i = ic ? MB_STRICMP(s1, s2) : STRCMP(s1, s2); switch (type) { case EXPR_IS: if (in_vim9script()) { // Really check it is the same string, not just // the same value. val = tv1->vval.v_string == tv2->vval.v_string; break; } // FALLTHROUGH case EXPR_EQUAL: val = (i == 0); break; case EXPR_ISNOT: if (in_vim9script()) { // Really check it is not the same string, not // just a different value. val = tv1->vval.v_string != tv2->vval.v_string; break; } // FALLTHROUGH case EXPR_NEQUAL: val = (i != 0); break; case EXPR_GREATER: val = (i > 0); break; case EXPR_GEQUAL: val = (i >= 0); break; case EXPR_SMALLER: val = (i < 0); break; case EXPR_SEQUAL: val = (i <= 0); break; case EXPR_MATCH: case EXPR_NOMATCH: val = pattern_match(s2, s1, ic); if (type == EXPR_NOMATCH) val = !val; break; default: break; // avoid gcc warning } *res = val; return OK; } /* * Convert any type to a string, never give an error. * When "quotes" is TRUE add quotes to a string. * Returns an allocated string. */ char_u * typval_tostring(typval_T *arg, int quotes) { char_u *tofree; char_u numbuf[NUMBUFLEN]; char_u *ret = NULL; if (arg == NULL) return vim_strsave((char_u *)"(does not exist)"); if (!quotes && arg->v_type == VAR_STRING) { ret = vim_strsave(arg->vval.v_string == NULL ? (char_u *)"" : arg->vval.v_string); } else { ret = tv2string(arg, &tofree, numbuf, 0); // Make a copy if we have a value but it's not in allocated memory. if (ret != NULL && tofree == NULL) ret = vim_strsave(ret); } return ret; } /* * Return TRUE if typeval "tv" is locked: Either that value is locked itself * or it refers to a List or Dictionary that is locked. */ int tv_islocked(typval_T *tv) { return (tv->v_lock & VAR_LOCKED) || (tv->v_type == VAR_LIST && tv->vval.v_list != NULL && (tv->vval.v_list->lv_lock & VAR_LOCKED)) || (tv->v_type == VAR_DICT && tv->vval.v_dict != NULL && (tv->vval.v_dict->dv_lock & VAR_LOCKED)); } static int func_equal( typval_T *tv1, typval_T *tv2, int ic) // ignore case { char_u *s1, *s2; dict_T *d1, *d2; int a1, a2; int i; // empty and NULL function name considered the same s1 = tv1->v_type == VAR_FUNC ? tv1->vval.v_string : partial_name(tv1->vval.v_partial); if (s1 != NULL && *s1 == NUL) s1 = NULL; s2 = tv2->v_type == VAR_FUNC ? tv2->vval.v_string : partial_name(tv2->vval.v_partial); if (s2 != NULL && *s2 == NUL) s2 = NULL; if (s1 == NULL || s2 == NULL) { if (s1 != s2) return FALSE; } else if (STRCMP(s1, s2) != 0) return FALSE; // empty dict and NULL dict is different d1 = tv1->v_type == VAR_FUNC ? NULL : tv1->vval.v_partial->pt_dict; d2 = tv2->v_type == VAR_FUNC ? NULL : tv2->vval.v_partial->pt_dict; if (d1 == NULL || d2 == NULL) { if (d1 != d2) return FALSE; } else if (!dict_equal(d1, d2, ic, TRUE)) return FALSE; // empty list and no list considered the same a1 = tv1->v_type == VAR_FUNC ? 0 : tv1->vval.v_partial->pt_argc; a2 = tv2->v_type == VAR_FUNC ? 0 : tv2->vval.v_partial->pt_argc; if (a1 != a2) return FALSE; for (i = 0; i < a1; ++i) if (!tv_equal(tv1->vval.v_partial->pt_argv + i, tv2->vval.v_partial->pt_argv + i, ic, TRUE)) return FALSE; return TRUE; } /* * Return TRUE if "tv1" and "tv2" have the same value. * Compares the items just like "==" would compare them, but strings and * numbers are different. Floats and numbers are also different. */ int tv_equal( typval_T *tv1, typval_T *tv2, int ic, // ignore case int recursive) // TRUE when used recursively { char_u buf1[NUMBUFLEN], buf2[NUMBUFLEN]; char_u *s1, *s2; static int recursive_cnt = 0; // catch recursive loops int r; static int tv_equal_recurse_limit; // Catch lists and dicts that have an endless loop by limiting // recursiveness to a limit. We guess they are equal then. // A fixed limit has the problem of still taking an awful long time. // Reduce the limit every time running into it. That should work fine for // deeply linked structures that are not recursively linked and catch // recursiveness quickly. if (!recursive) tv_equal_recurse_limit = 1000; if (recursive_cnt >= tv_equal_recurse_limit) { --tv_equal_recurse_limit; return TRUE; } // For VAR_FUNC and VAR_PARTIAL compare the function name, bound dict and // arguments. if ((tv1->v_type == VAR_FUNC || (tv1->v_type == VAR_PARTIAL && tv1->vval.v_partial != NULL)) && (tv2->v_type == VAR_FUNC || (tv2->v_type == VAR_PARTIAL && tv2->vval.v_partial != NULL))) { ++recursive_cnt; r = func_equal(tv1, tv2, ic); --recursive_cnt; return r; } if (tv1->v_type != tv2->v_type && ((tv1->v_type != VAR_BOOL && tv1->v_type != VAR_SPECIAL) || (tv2->v_type != VAR_BOOL && tv2->v_type != VAR_SPECIAL))) return FALSE; switch (tv1->v_type) { case VAR_LIST: ++recursive_cnt; r = list_equal(tv1->vval.v_list, tv2->vval.v_list, ic, TRUE); --recursive_cnt; return r; case VAR_DICT: ++recursive_cnt; r = dict_equal(tv1->vval.v_dict, tv2->vval.v_dict, ic, TRUE); --recursive_cnt; return r; case VAR_BLOB: return blob_equal(tv1->vval.v_blob, tv2->vval.v_blob); case VAR_NUMBER: case VAR_BOOL: case VAR_SPECIAL: return tv1->vval.v_number == tv2->vval.v_number; case VAR_STRING: s1 = tv_get_string_buf(tv1, buf1); s2 = tv_get_string_buf(tv2, buf2); return ((ic ? MB_STRICMP(s1, s2) : STRCMP(s1, s2)) == 0); case VAR_FLOAT: return tv1->vval.v_float == tv2->vval.v_float; case VAR_JOB: #ifdef FEAT_JOB_CHANNEL return tv1->vval.v_job == tv2->vval.v_job; #endif case VAR_CHANNEL: #ifdef FEAT_JOB_CHANNEL return tv1->vval.v_channel == tv2->vval.v_channel; #endif case VAR_INSTR: return tv1->vval.v_instr == tv2->vval.v_instr; case VAR_CLASS: // A class only exists once, equality is identity. return tv1->vval.v_class == tv2->vval.v_class; case VAR_OBJECT: (void)typval_compare_object(tv1, tv2, EXPR_EQUAL, ic, &r); return r; case VAR_PARTIAL: return tv1->vval.v_partial == tv2->vval.v_partial; case VAR_FUNC: return tv1->vval.v_string == tv2->vval.v_string; case VAR_UNKNOWN: case VAR_ANY: case VAR_VOID: break; } // VAR_UNKNOWN can be the result of a invalid expression, let's say it // does not equal anything, not even itself. return FALSE; } /* * Get an option value. * "arg" points to the '&' or '+' before the option name. * "arg" is advanced to character after the option name. * Return OK or FAIL. */ int eval_option( char_u **arg, typval_T *rettv, // when NULL, only check if option exists int evaluate) { char_u *option_end; long numval; char_u *stringval; getoption_T opt_type; int c; int working = (**arg == '+'); // has("+option") int ret = OK; int scope; // Isolate the option name and find its value. option_end = find_option_end(arg, &scope); if (option_end == NULL) { if (rettv != NULL) semsg(_(e_option_name_missing_str), *arg); return FAIL; } if (!evaluate) { *arg = option_end; return OK; } c = *option_end; *option_end = NUL; opt_type = get_option_value(*arg, &numval, rettv == NULL ? NULL : &stringval, NULL, scope); if (opt_type == gov_unknown) { if (rettv != NULL) semsg(_(e_unknown_option_str), *arg); ret = FAIL; } else if (rettv != NULL) { rettv->v_lock = 0; if (opt_type == gov_hidden_string) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; } else if (opt_type == gov_hidden_bool || opt_type == gov_hidden_number) { rettv->v_type = in_vim9script() && opt_type == gov_hidden_bool ? VAR_BOOL : VAR_NUMBER; rettv->vval.v_number = 0; } else if (opt_type == gov_bool || opt_type == gov_number) { if (in_vim9script() && opt_type == gov_bool) { rettv->v_type = VAR_BOOL; rettv->vval.v_number = numval ? VVAL_TRUE : VVAL_FALSE; } else { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = numval; } } else // string option { rettv->v_type = VAR_STRING; rettv->vval.v_string = stringval; } } else if (working && (opt_type == gov_hidden_bool || opt_type == gov_hidden_number || opt_type == gov_hidden_string)) ret = FAIL; *option_end = c; // put back for error messages *arg = option_end; return ret; } /* * Allocate a variable for a number constant. Also deals with "0z" for blob. * Return OK or FAIL. */ int eval_number( char_u **arg, typval_T *rettv, int evaluate, int want_string UNUSED) { int len; int skip_quotes = !in_old_script(4); char_u *p; int get_float = FALSE; // We accept a float when the format matches // "[0-9]\+\.[0-9]\+\([eE][+-]\?[0-9]\+\)\?". This is very // strict to avoid backwards compatibility problems. // With script version 2 and later the leading digit can be // omitted. // Don't look for a float after the "." operator, so that // ":let vers = 1.2.3" doesn't fail. if (**arg == '.') p = *arg; else { p = *arg + 1; if (skip_quotes) for (;;) { if (*p == '\'') ++p; if (!vim_isdigit(*p)) break; p = skipdigits(p); } else p = skipdigits(p); } if (!want_string && p[0] == '.' && vim_isdigit(p[1])) { get_float = TRUE; p = skipdigits(p + 2); if (*p == 'e' || *p == 'E') { ++p; if (*p == '-' || *p == '+') ++p; if (!vim_isdigit(*p)) get_float = FALSE; else p = skipdigits(p + 1); } if (ASCII_ISALPHA(*p) || *p == '.') get_float = FALSE; } if (get_float) { float_T f; *arg += string2float(*arg, &f, skip_quotes); if (evaluate) { rettv->v_type = VAR_FLOAT; rettv->vval.v_float = f; } } else if (**arg == '0' && ((*arg)[1] == 'z' || (*arg)[1] == 'Z')) { char_u *bp; blob_T *blob = NULL; // init for gcc // Blob constant: 0z0123456789abcdef if (evaluate) blob = blob_alloc(); for (bp = *arg + 2; vim_isxdigit(bp[0]); bp += 2) { if (!vim_isxdigit(bp[1])) { if (blob != NULL) { emsg(_(e_blob_literal_should_have_an_even_number_of_hex_characters)); ga_clear(&blob->bv_ga); VIM_CLEAR(blob); } return FAIL; } if (blob != NULL) ga_append(&blob->bv_ga, (hex2nr(*bp) << 4) + hex2nr(*(bp+1))); if (bp[2] == '.' && vim_isxdigit(bp[3])) ++bp; } if (blob != NULL) rettv_blob_set(rettv, blob); *arg = bp; } else { varnumber_T n; // decimal, hex or octal number vim_str2nr(*arg, NULL, &len, skip_quotes ? STR2NR_NO_OCT + STR2NR_QUOTE : STR2NR_ALL, &n, NULL, 0, TRUE, NULL); if (len == 0) { if (evaluate) semsg(_(e_invalid_expression_str), *arg); return FAIL; } *arg += len; if (evaluate) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = n; } } return OK; } /* * Evaluate a string constant and put the result in "rettv". * "*arg" points to the double quote or to after it when "interpolate" is TRUE. * When "interpolate" is TRUE reduce "{{" to "{", reduce "}}" to "}" and stop * at a single "{". * Return OK or FAIL. */ int eval_string(char_u **arg, typval_T *rettv, int evaluate, int interpolate) { char_u *p; char_u *end; int extra = interpolate ? 1 : 0; int off = interpolate ? 0 : 1; int len; // Find the end of the string, skipping backslashed characters. for (p = *arg + off; *p != NUL && *p != '"'; MB_PTR_ADV(p)) { if (*p == '\\' && p[1] != NUL) { ++p; // A "\<x>" form occupies at least 4 characters, and produces up // to 9 characters (6 for the char and 3 for a modifier): // reserve space for 5 extra. if (*p == '<') { int modifiers = 0; int flags = FSK_KEYCODE | FSK_IN_STRING; extra += 5; // Skip to the '>' to avoid using '{' inside for string // interpolation. if (p[1] != '*') flags |= FSK_SIMPLIFY; if (find_special_key(&p, &modifiers, flags, NULL) != 0) --p; // leave "p" on the ">" } } else if (interpolate && (*p == '{' || *p == '}')) { if (*p == '{' && p[1] != '{') // start of expression break; ++p; if (p[-1] == '}' && *p != '}') // single '}' is an error { semsg(_(e_stray_closing_curly_str), *arg); return FAIL; } --extra; // "{{" becomes "{", "}}" becomes "}" } } if (*p != '"' && !(interpolate && *p == '{')) { semsg(_(e_missing_double_quote_str), *arg); return FAIL; } // If only parsing, set *arg and return here if (!evaluate) { *arg = p + off; return OK; } // Copy the string into allocated memory, handling backslashed // characters. rettv->v_type = VAR_STRING; len = (int)(p - *arg + extra); rettv->vval.v_string = alloc(len); if (rettv->vval.v_string == NULL) return FAIL; end = rettv->vval.v_string; for (p = *arg + off; *p != NUL && *p != '"'; ) { if (*p == '\\') { switch (*++p) { case 'b': *end++ = BS; ++p; break; case 'e': *end++ = ESC; ++p; break; case 'f': *end++ = FF; ++p; break; case 'n': *end++ = NL; ++p; break; case 'r': *end++ = CAR; ++p; break; case 't': *end++ = TAB; ++p; break; case 'X': // hex: "\x1", "\x12" case 'x': case 'u': // Unicode: "\u0023" case 'U': if (vim_isxdigit(p[1])) { int n, nr; int c = toupper(*p); if (c == 'X') n = 2; else if (*p == 'u') n = 4; else n = 8; nr = 0; while (--n >= 0 && vim_isxdigit(p[1])) { ++p; nr = (nr << 4) + hex2nr(*p); } ++p; // For "\u" store the number according to // 'encoding'. if (c != 'X') end += (*mb_char2bytes)(nr, end); else *end++ = nr; } break; // octal: "\1", "\12", "\123" case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': *end = *p++ - '0'; if (*p >= '0' && *p <= '7') { *end = (*end << 3) + *p++ - '0'; if (*p >= '0' && *p <= '7') *end = (*end << 3) + *p++ - '0'; } ++end; break; // Special key, e.g.: "\<C-W>" case '<': { int flags = FSK_KEYCODE | FSK_IN_STRING; if (p[1] != '*') flags |= FSK_SIMPLIFY; extra = trans_special(&p, end, flags, FALSE, NULL); if (extra != 0) { end += extra; if (end >= rettv->vval.v_string + len) iemsg("eval_string() used more space than allocated"); break; } } // FALLTHROUGH default: MB_COPY_CHAR(p, end); break; } } else { if (interpolate && (*p == '{' || *p == '}')) { if (*p == '{' && p[1] != '{') // start of expression break; ++p; // reduce "{{" to "{" and "}}" to "}" } MB_COPY_CHAR(p, end); } } *end = NUL; if (*p == '"' && !interpolate) ++p; *arg = p; return OK; } /* * Allocate a variable for a 'str''ing' constant. * When "interpolate" is TRUE reduce "{{" to "{" and stop at a single "{". * Return OK when a "rettv" was set to the string. * Return FAIL on error, "rettv" is not set. */ int eval_lit_string(char_u **arg, typval_T *rettv, int evaluate, int interpolate) { char_u *p; char_u *str; int reduce = interpolate ? -1 : 0; int off = interpolate ? 0 : 1; // Find the end of the string, skipping ''. for (p = *arg + off; *p != NUL; MB_PTR_ADV(p)) { if (*p == '\'') { if (p[1] != '\'') break; ++reduce; ++p; } else if (interpolate) { if (*p == '{') { if (p[1] != '{') break; ++p; ++reduce; } else if (*p == '}') { ++p; if (*p != '}') { semsg(_(e_stray_closing_curly_str), *arg); return FAIL; } ++reduce; } } } if (*p != '\'' && !(interpolate && *p == '{')) { semsg(_(e_missing_single_quote_str), *arg); return FAIL; } // If only parsing return after setting "*arg" if (!evaluate) { *arg = p + off; return OK; } // Copy the string into allocated memory, handling '' to ' reduction and // any expressions. str = alloc((p - *arg) - reduce); if (str == NULL) return FAIL; rettv->v_type = VAR_STRING; rettv->vval.v_string = str; for (p = *arg + off; *p != NUL; ) { if (*p == '\'') { if (p[1] != '\'') break; ++p; } else if (interpolate && (*p == '{' || *p == '}')) { if (*p == '{' && p[1] != '{') break; ++p; } MB_COPY_CHAR(p, str); } *str = NUL; *arg = p + off; return OK; } /* * Evaluate a single or double quoted string possibly containing expressions. * "arg" points to the '$'. The result is put in "rettv". * Returns OK or FAIL. */ int eval_interp_string(char_u **arg, typval_T *rettv, int evaluate) { typval_T tv; int ret = OK; int quote; garray_T ga; char_u *p; ga_init2(&ga, 1, 80); // *arg is on the '$' character, move it to the first string character. ++*arg; quote = **arg; ++*arg; for (;;) { // Get the string up to the matching quote or to a single '{'. // "arg" is advanced to either the quote or the '{'. if (quote == '"') ret = eval_string(arg, &tv, evaluate, TRUE); else ret = eval_lit_string(arg, &tv, evaluate, TRUE); if (ret == FAIL) break; if (evaluate) { ga_concat(&ga, tv.vval.v_string); clear_tv(&tv); } if (**arg != '{') { // found terminating quote ++*arg; break; } p = eval_one_expr_in_str(*arg, &ga, evaluate); if (p == NULL) { ret = FAIL; break; } *arg = p; } rettv->v_type = VAR_STRING; if (ret == FAIL || !evaluate || ga_append(&ga, NUL) == FAIL) { ga_clear(&ga); rettv->vval.v_string = NULL; return ret; } rettv->vval.v_string = ga.ga_data; return OK; } /* * Return a string with the string representation of a variable. * If the memory is allocated "tofree" is set to it, otherwise NULL. * "numbuf" is used for a number. * Puts quotes around strings, so that they can be parsed back by eval(). * May return NULL. */ char_u * tv2string( typval_T *tv, char_u **tofree, char_u *numbuf, int copyID) { return echo_string_core(tv, tofree, numbuf, copyID, FALSE, TRUE, FALSE); } /* * Get the value of an environment variable. * "arg" is pointing to the '$'. It is advanced to after the name. * If the environment variable was not set, silently assume it is empty. * Return FAIL if the name is invalid. */ int eval_env_var(char_u **arg, typval_T *rettv, int evaluate) { char_u *string = NULL; int len; int cc; char_u *name; int mustfree = FALSE; ++*arg; name = *arg; len = get_env_len(arg); if (evaluate) { if (len == 0) return FAIL; // invalid empty name cc = name[len]; name[len] = NUL; // first try vim_getenv(), fast for normal environment vars string = vim_getenv(name, &mustfree); if (string != NULL && *string != NUL) { if (!mustfree) string = vim_strsave(string); } else { if (mustfree) vim_free(string); // next try expanding things like $VIM and ${HOME} string = expand_env_save(name - 1); if (string != NULL && *string == '$') VIM_CLEAR(string); } name[len] = cc; rettv->v_type = VAR_STRING; rettv->vval.v_string = string; rettv->v_lock = 0; } return OK; } /* * Get the lnum from the first argument. * Also accepts ".", "$", etc., but that only works for the current buffer. * Returns -1 on error. */ linenr_T tv_get_lnum(typval_T *argvars) { linenr_T lnum = -1; int did_emsg_before = did_emsg; if (argvars[0].v_type != VAR_STRING || !in_vim9script()) lnum = (linenr_T)tv_get_number_chk(&argvars[0], NULL); if (lnum <= 0 && did_emsg_before == did_emsg && argvars[0].v_type != VAR_NUMBER) { int fnum; pos_T *fp; // no valid number, try using arg like line() fp = var2fpos(&argvars[0], TRUE, &fnum, FALSE); if (fp != NULL) lnum = fp->lnum; } return lnum; } /* * Get the lnum from the first argument. * Also accepts "$", then "buf" is used. * Returns 0 on error. */ linenr_T tv_get_lnum_buf(typval_T *argvars, buf_T *buf) { if (argvars[0].v_type == VAR_STRING && argvars[0].vval.v_string != NULL && argvars[0].vval.v_string[0] == '$' && argvars[0].vval.v_string[1] == NUL && buf != NULL) return buf->b_ml.ml_line_count; return (linenr_T)tv_get_number_chk(&argvars[0], NULL); } /* * Get buffer by number or pattern. */ buf_T * tv_get_buf(typval_T *tv, int curtab_only) { char_u *name = tv->vval.v_string; buf_T *buf; if (tv->v_type == VAR_NUMBER) return buflist_findnr((int)tv->vval.v_number); if (tv->v_type != VAR_STRING) return NULL; if (name == NULL || *name == NUL) return curbuf; if (name[0] == '$' && name[1] == NUL) return lastbuf; buf = buflist_find_by_name(name, curtab_only); // If not found, try expanding the name, like done for bufexists(). if (buf == NULL) buf = find_buffer(tv); return buf; } /* * Like tv_get_buf() but give an error message is the type is wrong. */ buf_T * tv_get_buf_from_arg(typval_T *tv) { buf_T *buf; ++emsg_off; buf = tv_get_buf(tv, FALSE); --emsg_off; if (buf == NULL && tv->v_type != VAR_NUMBER && tv->v_type != VAR_STRING) // issue errmsg for type error (void)tv_get_number(tv); return buf; } #endif // FEAT_EVAL