diff options
30 files changed, 336 insertions, 121 deletions
diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index bb7fbeb78b..d5809c42cf 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -28,7 +28,7 @@ tasks: gmake deps - build: | cd neovim - gmake CMAKE_BUILD_TYPE=Release CMAKE_EXTRA_FLAGS="${CMAKE_EXTRA_FLAGS}" nvim + gmake CMAKE_BUILD_TYPE=RelWithDebInfo CMAKE_EXTRA_FLAGS="${CMAKE_EXTRA_FLAGS}" nvim - functionaltest: | cd neovim gmake functionaltest diff --git a/.builds/openbsd.yml b/.builds/openbsd.yml index 2f0f970dcb..0ffc8aa786 100644 --- a/.builds/openbsd.yml +++ b/.builds/openbsd.yml @@ -28,16 +28,16 @@ tasks: mkdir neovim/.deps cd neovim/.deps cmake -G Ninja ../third-party/ - cmake --build . --config Debug + cmake --build . --config RelWithDebInfo - build: | mkdir neovim/build cd neovim/build cmake -G Ninja $CMAKE_EXTRA_FLAGS .. - cmake --build . --config Debug + cmake --build . --config RelWithDebInfo ./bin/nvim --version - functionaltest: | cd neovim/build - cmake --build . --config Debug --target functionaltest + cmake --build . --config RelWithDebInfo --target functionaltest - oldtest: | cd neovim gmake oldtest diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 0134ee838d..ec39de14f5 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -3270,7 +3270,7 @@ void maketitle(void) case 6: buf_p = strappend(buf_p, " -"); break; case 5: case 7: buf_p = strappend(buf_p, " -+"); break; - default: assert(false); + default: abort(); } if (curbuf->b_fname != NULL) { diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 37cbfb968b..5628ede2e6 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -304,7 +304,8 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, bool pty, bool rpc, bool overlapped, bool detach, const char *cwd, uint16_t pty_width, uint16_t pty_height, - char *term_name, char **env, varnumber_T *status_out) + char *term_name, dict_T *env, + varnumber_T *status_out) { assert(cwd == NULL || os_isdir_executable(cwd)); @@ -358,7 +359,9 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, if (status) { EMSG3(_(e_jobspawn), os_strerror(status), cmd); xfree(cmd); - os_free_fullenv(proc->env); + if (proc->env) { + tv_dict_free(proc->env); + } if (proc->type == kProcessTypePty) { xfree(chan->stream.pty.term_name); } @@ -367,8 +370,9 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, return NULL; } xfree(cmd); - os_free_fullenv(proc->env); - + if (proc->env) { + tv_dict_free(proc->env); + } wstream_init(&proc->in, 0); if (has_out) { diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 3e52b3e3ce..be265e3f27 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1747,7 +1747,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, goto vim_str2nr_dec; } default: { - assert(false); + abort(); } } } else if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) @@ -1788,7 +1788,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, } // Do the string-to-numeric conversion "manually" to avoid sscanf quirks. - assert(false); // Should’ve used goto earlier. + abort(); // Should’ve used goto earlier. #define PARSE_NUMBER(base, cond, conv) \ do { \ while (!STRING_ENDED(ptr) && (cond)) { \ diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 8a1556320c..a25b140b2f 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -365,7 +365,7 @@ void eval_init(void) eval_msgpack_type_lists[i] = type_list; if (tv_dict_add(msgpack_types_dict, di) == FAIL) { // There must not be duplicate items in this dictionary by definition. - assert(false); + abort(); } } msgpack_types_dict->dv_lock = VAR_FIXED; diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index 638fef331a..bd4dc87d31 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -147,7 +147,7 @@ static inline int json_decoder_pop(ValuesStackItem obj, tv_clear(&key.val); if (tv_dict_add(last_container.container.vval.v_dict, obj_di) == FAIL) { - assert(false); + abort(); } obj_di->di_tv = obj.val; } else { @@ -480,7 +480,7 @@ static inline int parse_json_string(const char *const buf, const size_t buf_len, break; } default: { - assert(false); + abort(); } } } else { diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 9a9f2e4287..a4d7af7971 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -174,7 +174,7 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, case kMPConvPartial: { switch (v.data.p.stage) { case kMPConvPartialArgs: { - assert(false); + abort(); break; } case kMPConvPartialSelf: { @@ -237,7 +237,7 @@ bool encode_vim_list_to_buf(const list_T *const list, size_t *const ret_len, char *const buf = xmalloc(len); size_t read_bytes; if (encode_read_from_list(&lrstate, buf, len, &read_bytes) != OK) { - assert(false); + abort(); } assert(len == read_bytes); *ret_buf = buf; diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c index da05ecda43..bbba9d12f2 100644 --- a/src/nvim/eval/executor.c +++ b/src/nvim/eval/executor.c @@ -118,7 +118,7 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, return OK; } case VAR_UNKNOWN: { - assert(false); + abort(); } } } diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 8235d74cbb..1a7bbf2d0e 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1798,7 +1798,7 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) os_copy_fullenv(env, env_size); - for (size_t i = 0; i < env_size; i++) { + for (ssize_t i = env_size - 1; i >= 0; i--) { const char * str = env[i]; const char * const end = strchr(str + (str[0] == '=' ? 1 : 0), '='); @@ -1806,6 +1806,12 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) ptrdiff_t len = end - str; assert(len > 0); const char * value = str + len + 1; + if (tv_dict_find(rettv->vval.v_dict, str, len) != NULL) { + // Since we're traversing from the end of the env block to the front, any + // duplicate names encountered should be ignored. This preserves the + // semantics of env vars defined later in the env block taking precedence. + continue; + } tv_dict_add_str(rettv->vval.v_dict, str, len, value); @@ -3301,7 +3307,7 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) } break; case kCdScopeInvalid: // We should never get here - assert(false); + abort(); } if (from) { @@ -4354,7 +4360,7 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr) break; case kCdScopeInvalid: // We should never get here - assert(false); + abort(); } } @@ -4875,6 +4881,95 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = 1; } +static const char *ignored_env_vars[] = { +#ifndef WIN32 + "COLUMNS", + "LINES", + "TERMCAP", + "COLORFGBG", +#endif + NULL +}; + +/// According to comments in src/win/process.c of libuv, Windows has a few +/// "essential" environment variables. +static const char *required_env_vars[] = { +#ifdef WIN32 + "HOMEDRIVE", + "HOMEPATH", + "LOGONSERVER", + "PATH", + "SYSTEMDRIVE", + "SYSTEMROOT", + "TEMP", + "USERDOMAIN", + "USERNAME", + "USERPROFILE", + "WINDIR", +#endif + NULL +}; + +static dict_T *create_environment(const dictitem_T *job_env, + const bool clear_env, + const bool pty) +{ + dict_T * env = tv_dict_alloc(); + + if (!clear_env) { + typval_T temp_env = TV_INITIAL_VALUE; + f_environ(NULL, &temp_env, NULL); + tv_dict_extend(env, temp_env.vval.v_dict, "force"); + tv_dict_free(temp_env.vval.v_dict); + + if (pty) { + // These environment variables generally shouldn't be propagated to the + // child process. We're removing them here so the user can still decide + // they want to explicitly set them. + for (size_t i = 0; + i < ARRAY_SIZE(ignored_env_vars) && ignored_env_vars[i]; + i++) { + dictitem_T *dv = tv_dict_find(env, ignored_env_vars[i], -1); + if (dv) { + tv_dict_item_remove(env, dv); + } + } +#ifndef WIN32 + // Set COLORTERM to "truecolor" if termguicolors is set and 256 + // otherwise, but only if it was set in the parent terminal at all + dictitem_T *dv = tv_dict_find(env, S_LEN("COLORTERM")); + if (dv) { + tv_dict_item_remove(env, dv); + tv_dict_add_str(env, S_LEN("COLORTERM"), p_tgc ? "truecolor" : "256"); + } +#endif + } + } + + if (job_env) { + tv_dict_extend(env, job_env->di_tv.vval.v_dict, "force"); + } + + if (pty) { + // Now that the custom environment is configured, we need to ensure certain + // environment variables are present. + for (size_t i = 0; + i < ARRAY_SIZE(required_env_vars) && required_env_vars[i]; + i++) { + size_t len = strlen(required_env_vars[i]); + dictitem_T *dv = tv_dict_find(env, required_env_vars[i], len); + if (!dv) { + const char *env_var = os_getenv(required_env_vars[i]); + if (env_var) { + tv_dict_add_str(env, required_env_vars[i], len, env_var); + } + } + } + } + + return env; +} + // "jobstart()" function static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -4887,7 +4982,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool executable = true; char **argv = tv_to_argv(&argvars[0], NULL, &executable); - char **env = NULL; + dict_T *env = NULL; if (!argv) { rettv->vval.v_number = executable ? 0 : -1; return; // Did error message in tv_to_argv. @@ -4911,6 +5006,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) on_stderr = CALLBACK_READER_INIT; Callback on_exit = CALLBACK_NONE; char *cwd = NULL; + dictitem_T *job_env = NULL; if (argvars[1].v_type == VAR_DICT) { job_opts = argvars[1].vval.v_dict; @@ -4936,7 +5032,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) #endif char *new_cwd = tv_dict_get_string(job_opts, "cwd", false); - if (new_cwd && strlen(new_cwd) > 0) { + if (new_cwd && *new_cwd != NUL) { cwd = new_cwd; // The new cwd must be a directory. if (!os_isdir_executable((const char *)cwd)) { @@ -4945,52 +5041,22 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } } - dictitem_T *job_env = tv_dict_find(job_opts, S_LEN("env")); - if (job_env) { - if (job_env->di_tv.v_type != VAR_DICT) { - EMSG2(_(e_invarg2), "env"); - shell_free_argv(argv); - return; - } - - size_t custom_env_size = (size_t)tv_dict_len(job_env->di_tv.vval.v_dict); - size_t i = 0; - size_t env_size = 0; - - if (clear_env) { - // + 1 for last null entry - env = xmalloc((custom_env_size + 1) * sizeof(*env)); - env_size = 0; - } else { - env_size = os_get_fullenv_size(); - env = xmalloc((custom_env_size + env_size + 1) * sizeof(*env)); - - os_copy_fullenv(env, env_size); - i = env_size; - } - assert(env); // env must be allocated at this point - - TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, { - const char *str = tv_get_string(&var->di_tv); - assert(str); - size_t len = STRLEN(var->di_key) + strlen(str) + strlen("=") + 1; - env[i] = xmalloc(len); - snprintf(env[i], len, "%s=%s", (char *)var->di_key, str); - i++; - }); - - // must be null terminated - env[env_size + custom_env_size] = NULL; + job_env = tv_dict_find(job_opts, S_LEN("env")); + if (job_env && job_env->di_tv.v_type != VAR_DICT) { + EMSG2(_(e_invarg2), "env"); + shell_free_argv(argv); + return; } - if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) { shell_free_argv(argv); return; } } + env = create_environment(job_env, clear_env, pty); + uint16_t width = 0, height = 0; char *term_name = NULL; @@ -10518,6 +10584,11 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) Callback on_exit = CALLBACK_NONE; dict_T *job_opts = NULL; const char *cwd = "."; + dict_T *env = NULL; + const bool pty = true; + bool clear_env = false; + dictitem_T *job_env = NULL; + if (argvars[1].v_type == VAR_DICT) { job_opts = argvars[1].vval.v_dict; @@ -10532,17 +10603,31 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } + job_env = tv_dict_find(job_opts, S_LEN("env")); + if (job_env && job_env->di_tv.v_type != VAR_DICT) { + EMSG2(_(e_invarg2), "env"); + shell_free_argv(argv); + return; + } + + clear_env = tv_dict_get_number(job_opts, "clear_env") != 0; + if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) { shell_free_argv(argv); return; } } + env = create_environment(job_env, clear_env, pty); + + const bool rpc = false; + const bool overlapped = false; + const bool detach = false; uint16_t term_width = MAX(0, curwin->w_width_inner - win_col_off(curwin)); Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, - true, false, false, false, cwd, + pty, rpc, overlapped, detach, cwd, term_width, curwin->w_height_inner, - xstrdup("xterm-256color"), NULL, + xstrdup("xterm-256color"), env, &rettv->vval.v_number); if (rettv->vval.v_number <= 0) { return; diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 02d32a4f86..9be487f4fd 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1523,6 +1523,33 @@ varnumber_T tv_dict_get_number(const dict_T *const d, const char *const key) return tv_get_number(&di->di_tv); } +/// Converts a dict to an environment +/// +/// +char **tv_dict_to_env(dict_T *denv) +{ + size_t env_size = (size_t)tv_dict_len(denv); + + size_t i = 0; + char **env = NULL; + + // + 1 for NULL + env = xmalloc((env_size + 1) * sizeof(*env)); + + TV_DICT_ITER(denv, var, { + const char *str = tv_get_string(&var->di_tv); + assert(str); + size_t len = STRLEN(var->di_key) + strlen(str) + strlen("=") + 1; + env[i] = xmalloc(len); + snprintf(env[i], len, "%s=%s", (char *)var->di_key, str); + i++; + }); + + // must be null terminated + env[env_size] = NULL; + return env; +} + /// Get a string item from a dictionary /// /// @param[in] d Dictionary to get item from. @@ -2494,7 +2521,7 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock) break; } case VAR_UNKNOWN: { - assert(false); + abort(); } } #undef CHANGE_LOCK @@ -2666,7 +2693,7 @@ bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic, } } - assert(false); + abort(); return false; } @@ -2719,7 +2746,7 @@ bool tv_check_str_or_nr(const typval_T *const tv) return false; } } - assert(false); + abort(); return false; } @@ -2764,7 +2791,7 @@ bool tv_check_num(const typval_T *const tv) return false; } } - assert(false); + abort(); return false; } @@ -2809,7 +2836,7 @@ bool tv_check_str(const typval_T *const tv) return false; } } - assert(false); + abort(); return false; } diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c index 13517d3df1..0b1ecb12e2 100644 --- a/src/nvim/event/libuv_process.c +++ b/src/nvim/event/libuv_process.c @@ -41,7 +41,6 @@ int libuv_process_spawn(LibuvProcess *uvproc) #endif uvproc->uvopts.exit_cb = exit_cb; uvproc->uvopts.cwd = proc->cwd; - uvproc->uvopts.env = proc->env; uvproc->uvopts.stdio = uvproc->uvstdio; uvproc->uvopts.stdio_count = 3; uvproc->uvstdio[0].flags = UV_IGNORE; @@ -49,6 +48,12 @@ int libuv_process_spawn(LibuvProcess *uvproc) uvproc->uvstdio[2].flags = UV_IGNORE; uvproc->uv.data = proc; + if (proc->env) { + uvproc->uvopts.env = tv_dict_to_env(proc->env); + } else { + uvproc->uvopts.env = NULL; + } + if (!proc->in.closed) { uvproc->uvstdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; #ifdef WIN32 @@ -78,6 +83,9 @@ int libuv_process_spawn(LibuvProcess *uvproc) int status; if ((status = uv_spawn(&proc->loop->uv, &uvproc->uv, &uvproc->uvopts))) { ELOG("uv_spawn failed: %s", uv_strerror(status)); + if (uvproc->uvopts.env) { + os_free_fullenv(uvproc->uvopts.env); + } return status; } @@ -97,6 +105,10 @@ static void close_cb(uv_handle_t *handle) if (proc->internal_close_cb) { proc->internal_close_cb(proc); } + LibuvProcess *uvproc = (LibuvProcess *)proc; + if (uvproc->uvopts.env) { + os_free_fullenv(uvproc->uvopts.env); + } } static void exit_cb(uv_process_t *handle, int64_t status, int term_signal) diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h index 84e81238e9..24debdb276 100644 --- a/src/nvim/event/process.h +++ b/src/nvim/event/process.h @@ -4,6 +4,7 @@ #include "nvim/event/loop.h" #include "nvim/event/rstream.h" #include "nvim/event/wstream.h" +#include "nvim/eval/typval.h" typedef enum { kProcessTypeUv, @@ -23,7 +24,7 @@ struct process { uint64_t stopped_time; // process_stop() timestamp const char *cwd; char **argv; - char **env; + dict_T *env; Stream in, out, err; process_exit_cb cb; internal_process_cb internal_exit_cb, internal_close_cb; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 8b00417ce3..52da7fe4f6 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7591,7 +7591,7 @@ void post_chdir(CdScope scope, bool trigger_dirchanged) curwin->w_localdir = (char_u *)xstrdup(cwd); break; case kCdScopeInvalid: - assert(false); + abort(); } shorten_fnames(true); diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 8d10e98259..cf6bb6d492 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -6383,7 +6383,7 @@ int hist_type2char(int type) return '>'; } default: { - assert(false); + abort(); } } return NUL; diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index b1fa0b6779..8beba38509 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -1595,7 +1595,7 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, bool changed_window) } case kCdScopeInvalid: { // Should never happen. - assert(false); + abort(); } } diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 030df69caa..83b3729ad3 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -245,7 +245,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) } else { dictitem_T *const di = tv_dict_item_alloc_len(s, len); if (tv_dict_add(cur.tv->vval.v_dict, di) == FAIL) { - assert(false); + abort(); } kv_push(stack, cur); cur = (TVPopStackItem) { &di->di_tv, false, false, 0 }; @@ -391,7 +391,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) break; } default: { - assert(false); + abort(); } } nlua_pop_typval_table_processing_end: @@ -1200,7 +1200,7 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err) break; } default: { - assert(false); + abort(); } } break; diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 052b07ed44..87d092281a 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -2623,7 +2623,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) } // NOTREACHED case kMTUnknown: - assert(false); + abort(); } } @@ -6092,7 +6092,7 @@ static void set_clipboard(int name, yankreg_T *reg) break; } case kMTUnknown: { - assert(false); + abort(); } } diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index 4d7d9a45df..348a139e79 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -20,6 +20,10 @@ # include <pty.h> #endif +#ifdef __APPLE__ +# include <crt_externs.h> +#endif + #include <uv.h> #include "nvim/lib/klist.h" @@ -154,28 +158,14 @@ void pty_process_teardown(Loop *loop) static void init_child(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL { +#if defined(HAVE__NSGETENVIRON) +#define environ (*_NSGetEnviron()) +#else + extern char **environ; +#endif // New session/process-group. #6530 setsid(); - os_unsetenv("COLUMNS"); - os_unsetenv("LINES"); - os_unsetenv("TERMCAP"); - os_unsetenv("COLORFGBG"); - // setting COLORTERM to "truecolor" if termguicolors is set and 256 - // otherwise, but only if it was set in the parent terminal at all - if (os_env_exists("COLORTERM")) { - const char *colorterm = os_getenv("COLORTERM"); - if (colorterm != NULL) { - if (p_tgc) { - os_setenv("COLORTERM", "truecolor", 1); - } else { - os_setenv("COLORTERM", "256", 1); - } - } else { - os_unsetenv("COLORTERM"); - } - } - signal(SIGCHLD, SIG_DFL); signal(SIGHUP, SIG_DFL); signal(SIGINT, SIG_DFL); @@ -190,9 +180,14 @@ static void init_child(PtyProcess *ptyproc) } char *prog = ptyproc->process.argv[0]; - os_setenv("TERM", ptyproc->term_name ? ptyproc->term_name : "ansi", 1); - execvp(prog, ptyproc->process.argv); + + assert(proc->env); + tv_dict_add_str(proc->env, S_LEN("TERM"), + ptyproc->term_name ? ptyproc->term_name : "ansi"); + environ = tv_dict_to_env(proc->env); + execvp(prog, proc->argv); ELOG("execvp failed: %s: %s", strerror(errno), prog); + _exit(122); // 122 is EXEC_FAILED in the Vim source. } diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index 6f7100e846..52d2f84ace 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -52,6 +52,7 @@ int pty_process_spawn(PtyProcess *ptyproc) uv_connect_t *out_req = NULL; wchar_t *cmd_line = NULL; wchar_t *cwd = NULL; + wchar_t *env = NULL; const char *emsg = NULL; assert(proc->err.closed); @@ -124,13 +125,22 @@ int pty_process_spawn(PtyProcess *ptyproc) goto cleanup; } + if (proc->env != NULL) { + status = build_env_block(proc->env, &env); + } + + if (status != 0) { + emsg = "build_env_block failed"; + goto cleanup; + } + if (ptyproc->type == kConpty) { if (!os_conpty_spawn(conpty_object, &process_handle, NULL, cmd_line, cwd, - NULL)) { + env)) { emsg = "os_conpty_spawn failed"; status = (int)GetLastError(); goto cleanup; @@ -141,7 +151,7 @@ int pty_process_spawn(PtyProcess *ptyproc) NULL, // Optional application name cmd_line, cwd, - NULL, // Optional environment variables + env, &err); if (spawncfg == NULL) { emsg = "winpty_spawn_config_new failed"; @@ -213,6 +223,7 @@ cleanup: xfree(in_req); xfree(out_req); xfree(cmd_line); + xfree(env); xfree(cwd); return status; } @@ -454,3 +465,66 @@ int translate_winpty_error(int winpty_errno) default: return UV_UNKNOWN; } } + +typedef struct EnvNode { + wchar_t *str; + size_t len; + QUEUE node; +} EnvNode; + +/// Build the environment block to pass to CreateProcessW. +/// +/// @param[in] denv Dict of environment name/value pairs +/// @param[out] env Allocated environment block +/// +/// @returns zero on success or error code of MultiByteToWideChar function. +static int build_env_block(dict_T *denv, wchar_t **env_block) +{ + const size_t denv_size = (size_t)tv_dict_len(denv); + size_t env_block_len = 0; + int rc; + char **env = tv_dict_to_env(denv); + + QUEUE *q; + QUEUE env_q; + QUEUE_INIT(&env_q); + // Convert env vars to wchar_t and calculate how big the final env block + // needs to be + for (size_t i = 0; i < denv_size; i++) { + EnvNode *env_node = xmalloc(sizeof(*env_node)); + rc = utf8_to_utf16(env[i], -1, &env_node->str); + if (rc != 0) { + goto cleanup; + } + env_node->len = wcslen(env_node->str) + 1; + env_block_len += env_node->len; + QUEUE_INSERT_TAIL(&env_q, &env_node->node); + } + + // Additional '\0' after the final entry + env_block_len++; + + *env_block = xmalloc(sizeof(**env_block) * env_block_len); + wchar_t *pos = *env_block; + + QUEUE_FOREACH(q, &env_q) { + EnvNode *env_node = QUEUE_DATA(q, EnvNode, node); + memcpy(pos, env_node->str, env_node->len * sizeof(*pos)); + pos += env_node->len; + } + + *pos = L'\0'; + +cleanup: + q = QUEUE_HEAD(&env_q); + while (q != &env_q) { + QUEUE *next = q->next; + EnvNode *env_node = QUEUE_DATA(q, EnvNode, node); + XFREE_CLEAR(env_node->str); + QUEUE_REMOVE(q); + xfree(env_node); + q = next; + } + + return rc; +} diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index 4b6533cd0c..5cf628935f 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -97,7 +97,7 @@ void os_microdelay(uint64_t us, bool ignoreinput) const int rv = uv_cond_timedwait(&delay_cond, &delay_mutex, ns_delta); if (0 != rv && UV_ETIMEDOUT != rv) { - assert(false); + abort(); break; } // Else: Timeout proceeded normally. diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 3d639d7db5..e074f73d04 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -5648,7 +5648,7 @@ static int get_qfline_items(qfline_T *qfp, list_T *list) == FAIL)) { // tv_dict_add* fail only if key already exist, but this is a newly // allocated dictionary which is thus guaranteed to have no existing keys. - assert(false); + abort(); } return OK; diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 2444910bb3..c0e787380f 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -765,7 +765,7 @@ static ShaDaReadResult sd_reader_skip(ShaDaReadDef *const sd_reader, (uint64_t) offset); return kSDReadStatusNotShaDa; } - assert(false); + abort(); } return kSDReadStatusSuccess; } @@ -1224,7 +1224,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) } case kSDReadStatusFinished: { // Should be handled by the while condition. - assert(false); + abort(); } case kSDReadStatusNotShaDa: case kSDReadStatusReadError: { @@ -1236,7 +1236,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) } switch (cur_entry.type) { case kSDItemMissing: { - assert(false); + abort(); } case kSDItemUnknown: { break; @@ -1628,7 +1628,7 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ((size_t) (!CHECK_DEFAULT(entry, attr))) switch (entry.type) { case kSDItemMissing: { - assert(false); + abort(); } case kSDItemUnknown: { if (spacker->callback(spacker->data, entry.data.unknown_item.contents, @@ -1850,7 +1850,7 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, break; } default: { - assert(false); + abort(); } } } @@ -2147,7 +2147,7 @@ static inline ShaDaWriteResult shada_read_when_writing( } case kSDReadStatusFinished: { // Should be handled by the while condition. - assert(false); + abort(); } case kSDReadStatusNotShaDa: { ret = kSDWriteReadNotShada; @@ -2184,7 +2184,7 @@ static inline ShaDaWriteResult shada_read_when_writing( } case kSDItemHeader: case kSDItemBufferList: { - assert(false); + abort(); } case kSDItemUnknown: { ret = shada_pack_entry(packer, entry, 0); @@ -4044,7 +4044,7 @@ shada_read_next_item_start: } case kSDItemMissing: case kSDItemUnknown: { - assert(false); + abort(); } } entry->type = (ShadaEntryType) type_u64; diff --git a/src/nvim/tag.c b/src/nvim/tag.c index c6b1a0d04c..84ca240734 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -1461,7 +1461,7 @@ find_tags( p_ic = ignorecase_opt(pat, true, true); break; default: - assert(false); + abort(); } help_save = curbuf->b_help; diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 124f96e039..3e683a4926 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -297,7 +297,7 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key) len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Release"); break; case TERMKEY_MOUSE_UNKNOWN: - assert(false); + abort(); } len += (size_t)snprintf(buf + len, sizeof(buf) - len, "><%d,%d>", col, row); diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index 9d3ec21949..06efc9fa99 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -157,7 +157,7 @@ bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width, #ifndef NDEBUG for (size_t i = 0; i < kv_size(layers); i++) { if (kv_A(layers, i) == grid) { - assert(false); + abort(); } } #endif diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 44b6ab5f5a..e9d82ca87d 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -2078,7 +2078,7 @@ viml_pexpr_parse_process_token: case kExprLexMissing: case kExprLexSpacing: case kExprLexEOC: { - assert(false); + abort(); } case kExprLexInvalid: { ERROR_FROM_TOKEN(cur_token); @@ -3028,7 +3028,7 @@ viml_pexpr_parse_end: // Until trailing "}" it is impossible to distinguish curly braces // identifier and dictionary, so it must not appear in the stack like // this. - assert(false); + abort(); } case kExprNodeInteger: case kExprNodeFloat: @@ -3042,7 +3042,7 @@ viml_pexpr_parse_end: // These are plain values and not containers, for them it should only // be possible to show up in the topmost stack element, but it was // unconditionally popped at the start. - assert(false); + abort(); } case kExprNodeComma: case kExprNodeColon: diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index 6d1182478a..b59d87eb12 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -31,9 +31,9 @@ describe('jobs', function() nvim('set_var', 'channel', channel) source([[ function! Normalize(data) abort - " Windows: remove ^M + " Windows: remove ^M and term escape sequences return type([]) == type(a:data) - \ ? map(a:data, 'substitute(v:val, "\r", "", "g")') + \ ? map(a:data, 'substitute(substitute(v:val, "\r", "", "g"), "\x1b\\%(\\]\\d\\+;.\\{-}\x07\\|\\[.\\{-}[\x40-\x7E]\\)", "", "g")') \ : a:data endfunction function! OnEvent(id, data, event) dict @@ -63,6 +63,7 @@ describe('jobs', function() it('append environment #env', function() nvim('command', "let $VAR = 'abc'") + nvim('command', "let $TOTO = 'goodbye world'") nvim('command', "let g:job_opts.env = {'TOTO': 'hello world'}") if iswin() then nvim('command', [[call jobstart('echo %TOTO% %VAR%', g:job_opts)]]) @@ -75,8 +76,24 @@ describe('jobs', function() }) end) + it('append environment with pty #env', function() + nvim('command', "let $VAR = 'abc'") + nvim('command', "let $TOTO = 'goodbye world'") + nvim('command', "let g:job_opts.pty = v:true") + nvim('command', "let g:job_opts.env = {'TOTO': 'hello world'}") + if iswin() then + nvim('command', [[call jobstart('echo %TOTO% %VAR%', g:job_opts)]]) + else + nvim('command', [[call jobstart('echo $TOTO $VAR', g:job_opts)]]) + end + expect_msg_seq({ + {'notification', 'stdout', {0, {'hello world abc', ''}}}, + }) + end) + it('replace environment #env', function() nvim('command', "let $VAR = 'abc'") + nvim('command', "let $TOTO = 'goodbye world'") nvim('command', "let g:job_opts.env = {'TOTO': 'hello world'}") nvim('command', "let g:job_opts.clear_env = 1") diff --git a/test/symbolic/klee/nvim/charset.c b/test/symbolic/klee/nvim/charset.c index f9bc3fabc4..fd1c5ee4b9 100644 --- a/test/symbolic/klee/nvim/charset.c +++ b/test/symbolic/klee/nvim/charset.c @@ -62,7 +62,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, goto vim_str2nr_dec; } default: { - assert(false); + abort(); } } } else if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) @@ -102,7 +102,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, } // Do the string-to-numeric conversion "manually" to avoid sscanf quirks. - assert(false); // Should’ve used goto earlier. + abort(); // Should’ve used goto earlier. #define PARSE_NUMBER(base, cond, conv) \ do { \ while (!STRING_ENDED(ptr) && (cond)) { \ diff --git a/test/symbolic/klee/nvim/memory.c b/test/symbolic/klee/nvim/memory.c index df422cea3e..1614f813d7 100644 --- a/test/symbolic/klee/nvim/memory.c +++ b/test/symbolic/klee/nvim/memory.c @@ -45,7 +45,7 @@ void xfree(void *const p) return; } } - assert(false); + abort(); } void *xrealloc(void *const p, size_t new_size) @@ -63,7 +63,7 @@ void *xrealloc(void *const p, size_t new_size) return ret; } } - assert(false); + abort(); return (void *)(intptr_t)1; } |