diff --git a/Makefile.in b/Makefile.in index 2503db9c7..5da73df61 100644 --- a/Makefile.in +++ b/Makefile.in @@ -59,6 +59,8 @@ SUBDIRS += driver-vpi # rebuild the lexor_keyword.cc file. If we do, then we want to use the # local version instead of the one is $(srcdir). vpath lexor_keyword.cc . +vpath %.c $(srcdir)/libmisc +vpath %.c $(srcdir) vpath %.cc $(srcdir)/libmisc vpath %.cc $(srcdir) @@ -109,7 +111,8 @@ M = LineInfo.o StringHeap.o TT = t-dll.o t-dll-api.o t-dll-expr.o t-dll-proc.o t-dll-analog.o FF = cprop.o exposenodes.o nodangle.o synth.o synth2.o syn-rules.o -O = main.o async.o design_dump.o discipline.o dup_expr.o elaborate.o \ +C_OBJ = CmdExec.o +CXX_OBJ = main.o async.o design_dump.o discipline.o dup_expr.o elaborate.o \ elab_expr.o elaborate_analog.o elab_lval.o elab_net.o \ elab_scope.o elab_sig.o elab_sig_analog.o elab_type.o \ emit.o eval_attrib.o \ @@ -128,6 +131,7 @@ O = main.o async.o design_dump.o discipline.o dup_expr.o elaborate.o \ Attrib.o HName.o Module.o PClass.o PDelays.o PEvent.o PExpr.o PFunction.o \ PGate.o PGenerate.o PModport.o PNamedItem.o PPackage.o PScope.o PSpec.o PTimingCheck.o \ PTask.o PUdp.o PWire.o Statement.o AStatement.o $M $(FF) $(TT) +O = $(C_OBJ) $(CXX_OBJ) all: dep config.h _pli_types.h version_tag.h version_base.h ivl@EXEEXT@ $(foreach dir,$(SUBDIRS),$(MAKE) -C $(dir) $@ && ) true @@ -174,7 +178,7 @@ ifneq (@srcdir@,.) endif rm -rf autom4te.cache -cppcheck: $(O:.o=.cc) $(srcdir)/dosify.c +cppcheck: $(C_OBJ:.o=.c) $(CXX_OBJ:.o=.cc) $(srcdir)/dosify.c cppcheck --enable=all --std=c99 --std=c++11 -f \ --check-level=exhaustive \ --suppressions-list=$(srcdir)/cppcheck-global.sup \ @@ -232,6 +236,10 @@ endif $(CXX) $(CPPFLAGS) $(CXXFLAGS) @DEPENDENCY_FLAG@ -c $< -o $*.o mv $*.d dep/$*.d +%.o: %.c config.h | dep + $(CC) $(CPPFLAGS) $(CFLAGS) @DEPENDENCY_FLAG@ -c $< -o $*.o + mv $*.d dep/$*.d + # Here are some explicit dependencies needed to get things going. main.o: main.cc version_tag.h diff --git a/cppcheck.sup b/cppcheck.sup index f4e70d72d..52f232c17 100644 --- a/cppcheck.sup +++ b/cppcheck.sup @@ -93,6 +93,10 @@ unusedFunction:vpi_modules.cc:90 // vpi_sim_control() unusedFunction:vpi_modules.cc:110 +// CmdExec.c is shared by multiple executables, so each cppcheck target sees +// some exported helpers that are used only by another target. +unusedFunction:libmisc/CmdExec.c + // These are the functions that the compiler exports to the targets. //ivl_branch_island() unusedFunction:t-dll-api.cc:39 diff --git a/driver/Makefile.in b/driver/Makefile.in index 91e472816..e41fdc99f 100644 --- a/driver/Makefile.in +++ b/driver/Makefile.in @@ -31,6 +31,7 @@ builddir=@builddir@ top_builddir=@top_builddir@ VPATH = $(srcdir) +vpath %.c $(srcdir)/../libmisc bindir = $(exec_prefix)/bin libdir = $(exec_prefix)/lib @@ -50,16 +51,16 @@ MAN = @MAN@ PS2PDF = @PS2PDF@ ifeq (@srcdir@,.) -INCLUDE_PATH = -I. -I.. +INCLUDE_PATH = -I. -I.. -I../libmisc else -INCLUDE_PATH = -I. -I.. -I$(srcdir) -I$(srcdir)/.. +INCLUDE_PATH = -I. -I.. -I$(srcdir) -I$(srcdir)/.. -I$(srcdir)/../libmisc endif CPPFLAGS = $(INCLUDE_PATH) @CPPFLAGS@ @DEFS@ CFLAGS = @WARNING_FLAGS@ @WARNING_FLAGS_CC@ @CFLAGS@ LDFLAGS = @LDFLAGS@ -O = main.o substit.o cflexor.o cfparse.o +O = main.o substit.o cflexor.o cfparse.o CmdExec.o all: dep iverilog@EXEEXT@ iverilog.man diff --git a/driver/cppcheck.sup b/driver/cppcheck.sup index 82ce9a74e..13dd860cf 100644 --- a/driver/cppcheck.sup +++ b/driver/cppcheck.sup @@ -11,6 +11,10 @@ memleakOnRealloc nullPointerArithmeticOutOfMemory nullPointerOutOfMemory +// CmdExec.c is shared by multiple executables, so this target sees helpers +// that are used only by another target. +unusedFunction:../libmisc/CmdExec.c + // Errors/limitations in the generated yacc and lex files duplicateBreak:cflexor.lex constVariablePointer:cfparse.y diff --git a/driver/main.c b/driver/main.c index 95d02e91b..5ce3a5e6c 100644 --- a/driver/main.c +++ b/driver/main.c @@ -97,6 +97,7 @@ extern const char*optarg; #endif # include "globals.h" +# include "CmdExec.h" #include "cfparse_misc.h" /* cfparse() */ #include "ivl_alloc.h" @@ -335,16 +336,16 @@ static int t_version_only(void) free(source_path); fflush(0); - snprintf(tmp, sizeof tmp, "%s%civlpp -V", ivlpp_dir, sep); - rc = system(tmp); + snprintf(tmp, sizeof tmp, "\"%s%civlpp\" -V", ivlpp_dir, sep); + rc = ivl_run_cmd(tmp, verbose_flag); if (rc != 0) { fprintf(stderr, "Unable to get version from \"%s\"\n", tmp); } fflush(0); - snprintf(tmp, sizeof tmp, "%s%civl -V -C\"%s\" -C\"%s\"", ivl_dir, sep, + snprintf(tmp, sizeof tmp, "\"%s%civl\" -V -C\"%s\" -C\"%s\"", ivl_dir, sep, iconfig_path, iconfig_common_path); - rc = system(tmp); + rc = ivl_run_cmd(tmp, verbose_flag); if (rc != 0) { fprintf(stderr, "Unable to get version from \"%s\"\n", tmp); } @@ -363,7 +364,7 @@ static int t_version_only(void) static void build_preprocess_command(int e_flag) { - snprintf(tmp, sizeof tmp, "%s%civlpp%s%s%s -F\"%s\" -f\"%s\" -p\"%s\"%s", + snprintf(tmp, sizeof tmp, "\"%s%civlpp\"%s%s%s -F\"%s\" -f\"%s\" -p\"%s\"%s", ivlpp_dir, sep, verbose_flag ? " -v" : "", e_flag ? "" : " -L", @@ -395,7 +396,7 @@ static int t_preprocess_only(void) if (verbose_flag) printf("preprocess: %s\n", cmd); - rc = system(cmd); + rc = ivl_run_cmd(cmd, verbose_flag); remove(source_path); free(source_path); @@ -449,7 +450,7 @@ static int t_compile(void) #endif /* Build the ivl command. */ - snprintf(tmp, sizeof tmp, "%s%civl", ivl_dir, sep); + snprintf(tmp, sizeof tmp, "\"%s%civl\"", ivl_dir, sep); rc = strlen(tmp); cmd = realloc(cmd, ncmd+rc+1); strcpy(cmd+ncmd, tmp); @@ -496,7 +497,7 @@ static int t_compile(void) printf("translate: %s\n", cmd); - rc = system(cmd); + rc = ivl_run_cmd(cmd, verbose_flag); if ( ! getenv("IVERILOG_ICONFIG")) { remove(source_path); free(source_path); @@ -1484,7 +1485,7 @@ int main(int argc, char **argv) if (vhdlpp_work == 0) vhdlpp_work = "ivl_vhdl_work"; - fprintf(defines_file, "vhdlpp:%s%cvhdlpp\n", vhdlpp_dir, sep); + fprintf(defines_file, "vhdlpp:\"%s%cvhdlpp\"\n", vhdlpp_dir, sep); fprintf(defines_file, "vhdlpp-work:%s\n", vhdlpp_work); for (unsigned idx = 0 ; idx < vhdlpp_libdir_cnt ; idx += 1) fprintf(defines_file, "vhdlpp-libdir:%s\n", vhdlpp_libdir[idx]); @@ -1547,7 +1548,7 @@ int main(int argc, char **argv) /* Write the preprocessor command needed to preprocess a single file. This may be used to preprocess library files. */ - fprintf(iconfig_file, "ivlpp:%s%civlpp %s -L -F\"%s\" -P\"%s\"\n", + fprintf(iconfig_file, "ivlpp:\"%s%civlpp\" %s -L -F\"%s\" -P\"%s\"\n", ivlpp_dir, sep, strchr(warning_flags, 'r') ? "-Wredef-all" : strchr(warning_flags, 'R') ? "-Wredef-chg" : "", diff --git a/ivlpp/Makefile.in b/ivlpp/Makefile.in index 7fdd9e2fb..5811f53b1 100644 --- a/ivlpp/Makefile.in +++ b/ivlpp/Makefile.in @@ -26,6 +26,7 @@ exec_prefix = @exec_prefix@ srcdir = @srcdir@ VPATH = $(srcdir) +vpath %.c $(srcdir)/../libmisc bindir = @bindir@ libdir = @libdir@ @@ -38,16 +39,16 @@ INSTALL_DATA = @INSTALL_DATA@ LEX = @LEX@ ifeq (@srcdir@,.) -INCLUDE_PATH = -I. -I.. +INCLUDE_PATH = -I. -I.. -I../libmisc else -INCLUDE_PATH = -I. -I.. -I$(srcdir) -I$(srcdir)/.. +INCLUDE_PATH = -I. -I.. -I$(srcdir) -I$(srcdir)/.. -I$(srcdir)/../libmisc endif CPPFLAGS = $(INCLUDE_PATH) @CPPFLAGS@ @DEFS@ CFLAGS = @WARNING_FLAGS@ @WARNING_FLAGS_CC@ @CFLAGS@ LDFLAGS = @LDFLAGS@ -O = main.o lexor.o +O = main.o lexor.o CmdExec.o all: ivlpp@EXEEXT@ diff --git a/ivlpp/cppcheck.sup b/ivlpp/cppcheck.sup index e380f98ce..0aa1bbfb5 100644 --- a/ivlpp/cppcheck.sup +++ b/ivlpp/cppcheck.sup @@ -6,6 +6,10 @@ memleakOnRealloc nullPointerArithmeticOutOfMemory nullPointerOutOfMemory +// CmdExec.c is shared by multiple executables, so this target sees helpers +// that are used only by another target. +unusedFunction:../libmisc/CmdExec.c + // Errors/limitations in the generated yacc and lex files ctunullpointerOutOfMemory:lexor.lex memleakOnRealloc:lexor.lex diff --git a/ivlpp/lexor.lex b/ivlpp/lexor.lex index 6da2aaafe..dc354784f 100644 --- a/ivlpp/lexor.lex +++ b/ivlpp/lexor.lex @@ -27,6 +27,7 @@ # include # include +# include "CmdExec.h" # include "globals.h" # include "ivl_alloc.h" @@ -2128,12 +2129,12 @@ static void open_input_file(struct include_stack_t*isp) cmdlen += liblen; char*cmd = malloc(cmdlen); - snprintf(cmd, cmdlen, "%s -w\"%s\"%s %s", vhdlpp_path, vhdlpp_work, libs, isp->path); + snprintf(cmd, cmdlen, "%s -w\"%s\"%s \"%s\"", vhdlpp_path, vhdlpp_work, libs, isp->path); if (verbose_flag) fprintf(stderr, "Invoke vhdlpp: %s\n", cmd); - isp->file = popen(cmd, "r"); - isp->file_close = pclose; + isp->file = ivl_run_cmd_pipe(cmd); + isp->file_close = ivl_close_cmd_pipe; free(libs); free(cmd); diff --git a/libmisc/CmdExec.c b/libmisc/CmdExec.c new file mode 100644 index 000000000..5aefb0abc --- /dev/null +++ b/libmisc/CmdExec.c @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2026 Lars-Peter Clausen + * + * This source code is free software; you can redistribute it + * and/or modify it in source code form under the terms of the GNU + * General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +# include "CmdExec.h" + +# include +# include + +#ifdef __MINGW32__ +# if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0600 +# undef _WIN32_WINNT +# define _WIN32_WINNT 0x0600 +# endif +# if !defined(WINVER) || WINVER < 0x0600 +# undef WINVER +# define WINVER 0x0600 +# endif +# include +# include +# include +# include + +static char *get_cmd_exe(void) +{ + DWORD n = GetEnvironmentVariableA("COMSPEC", NULL, 0); + char *buf; + + if (n > 0) { + buf = (char *)malloc(n); + if (buf == NULL) + return NULL; + + DWORD len = GetEnvironmentVariableA("COMSPEC", buf, n); + + if (len > 0 && len < n) + return buf; + + free(buf); + } + + /* Fallback. This avoids searching PATH for cmd.exe. */ + { + char sysdir[MAX_PATH]; + UINT len = GetSystemDirectoryA(sysdir, sizeof(sysdir)); + + if (len > 0 && len < sizeof(sysdir)) { + static const char suffix[] = "\\cmd.exe"; + size_t need = strlen(sysdir) + sizeof(suffix); + + buf = (char *)malloc(need); + if (buf == NULL) + return NULL; + + strcpy(buf, sysdir); + strcat(buf, suffix); + return buf; + } + } + + return _strdup("cmd.exe"); +} + +/* + * On Windows `system()` runs the string as cmd.exe /C . + * `popen()` similarly runs the string through CMD.exe and attaches one side of + * a pipe. + * + * cmd.exe has special quote handling for /C. Quote characters are preserved + * only if all of the following are true: + * - there is no /S switch; + * - there are exactly two quote characters; + * - there are no special characters between them, where special is one + * of &<>()@^|; + * - there is whitespace between them; + * - the string between them is the name of an executable file. + * + * For an input like "C:\Program Files\...\ivlpp" -F"...\defs" "input file.v" + * the quotes at the beginning and the end get stripped, which results in + * incorrect behavior. + * + * Below are versions of `system()` and `popen()` that call cmd.exe with /C /S, + * which will preserve the quotes. Select cmd.exe from COMSPEC, with a fallback + * to the system directory, and pass it as the application name so PATH is not + * searched. + * + * Also pass /D to suppress AutoRun commands so a registry setting cannot + * modify this parsing path. + */ +static int start_cmd_process(const char *cmd, HANDLE stdout_handle, + PROCESS_INFORMATION *pi, + DWORD *last_error) +{ + STARTUPINFOEX siex; + STARTUPINFO si; + STARTUPINFO *startup_info; + HANDLE child_stdout = NULL; + HANDLE inherit_handles[3]; + DWORD creation_flags = 0; + BOOL inherit = FALSE; + SIZE_T attr_size = 0; + unsigned inherit_count = 0; + char *cmd_exe = get_cmd_exe(); + size_t cmd2len; + char *cmd2; + LPPROC_THREAD_ATTRIBUTE_LIST attr_list = NULL; + int attr_list_initialized = 0; + int rc = -1; + + if (last_error != NULL) + *last_error = 0; + + if (cmd_exe == NULL) + return -1; + + cmd2len = strlen(cmd_exe) + strlen(cmd) + 16; + cmd2 = (char *)malloc(cmd2len); + if (cmd2 == NULL) + goto out; + + snprintf(cmd2, cmd2len, "\"%s\" /D /S /C \"%s\"", cmd_exe, cmd); + + memset(&si, 0x00, sizeof(si)); + si.cb = sizeof(si); + startup_info = &si; + + if (stdout_handle != NULL) { + HANDLE std_input = GetStdHandle(STD_INPUT_HANDLE); + HANDLE std_error = GetStdHandle(STD_ERROR_HANDLE); + + memset(&siex, 0x00, sizeof(siex)); + siex.StartupInfo.cb = sizeof(siex); + siex.StartupInfo.dwFlags = STARTF_USESTDHANDLES; + + if (!DuplicateHandle(GetCurrentProcess(), stdout_handle, GetCurrentProcess(), + &child_stdout, 0, TRUE, DUPLICATE_SAME_ACCESS)) + goto out; + + /* The normal system()/popen() path already relies on standard + * handles being inheritable. Only the pipe writer is duplicated. + */ + if (std_input != NULL && std_input != INVALID_HANDLE_VALUE) + inherit_handles[inherit_count++] = std_input; + inherit_handles[inherit_count++] = child_stdout; + if (std_error != NULL && std_error != INVALID_HANDLE_VALUE) + inherit_handles[inherit_count++] = std_error; + + siex.StartupInfo.hStdInput = std_input; + siex.StartupInfo.hStdOutput = child_stdout; + siex.StartupInfo.hStdError = std_error; + + InitializeProcThreadAttributeList(NULL, 1, 0, &attr_size); + attr_list = (LPPROC_THREAD_ATTRIBUTE_LIST)malloc(attr_size); + if (attr_list == NULL) + goto out; + if (!InitializeProcThreadAttributeList(attr_list, 1, 0, &attr_size)) + goto out; + attr_list_initialized = 1; + if (!UpdateProcThreadAttribute(attr_list, 0, + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, + inherit_handles, + inherit_count * sizeof(inherit_handles[0]), + NULL, NULL)) + goto out; + + siex.lpAttributeList = attr_list; + startup_info = &siex.StartupInfo; + creation_flags = EXTENDED_STARTUPINFO_PRESENT; + inherit = TRUE; + } + memset(pi, 0x00, sizeof(*pi)); + + if (!CreateProcess(cmd_exe, cmd2, NULL, NULL, inherit, + creation_flags, NULL, NULL, startup_info, pi)) { + if (last_error != NULL) + *last_error = GetLastError(); + goto out; + } + + rc = 0; + +out: + if (attr_list_initialized) + DeleteProcThreadAttributeList(attr_list); + if (attr_list != NULL) + free(attr_list); + if (child_stdout != NULL) + CloseHandle(child_stdout); + free(cmd2); + free(cmd_exe); + return rc; +} +#endif + +int ivl_run_cmd(const char *cmd, int verbose) +{ + if (verbose) + fprintf(stderr, "Executing: %s", cmd); + +#ifdef __MINGW32__ + DWORD exit_code = 1; + DWORD last_error; + PROCESS_INFORMATION pi; + + if (start_cmd_process(cmd, NULL, &pi, &last_error) != 0) { + if (last_error != 0) + fprintf(stderr, "CreateProcess failed (%lu).\n", last_error); + return -1; + } + + WaitForSingleObject(pi.hProcess, INFINITE); + GetExitCodeProcess(pi.hProcess, &exit_code); + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + return exit_code; +#else + return system(cmd); +#endif +} + +FILE *ivl_run_cmd_pipe(const char *cmd) +{ +#ifdef __MINGW32__ + SECURITY_ATTRIBUTES sa; + HANDLE read_pipe = NULL; + HANDLE write_pipe = NULL; + PROCESS_INFORMATION pi; + int fd; + FILE *fp; + + memset(&sa, 0x00, sizeof(sa)); + sa.nLength = sizeof(sa); + sa.bInheritHandle = FALSE; + + if (!CreatePipe(&read_pipe, &write_pipe, &sa, 0)) + return NULL; + + if (start_cmd_process(cmd, write_pipe, &pi, NULL) != 0) { + CloseHandle(read_pipe); + CloseHandle(write_pipe); + return NULL; + } + + CloseHandle(write_pipe); + CloseHandle(pi.hThread); + + /* Match popen("r") text-mode semantics. The lexer expects CRLF from + * Windows command output to be normalized before it sees `line directives. + */ + fd = _open_osfhandle((intptr_t)read_pipe, _O_RDONLY | _O_TEXT); + if (fd < 0) { + CloseHandle(read_pipe); + TerminateProcess(pi.hProcess, 1); + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hProcess); + return NULL; + } + + fp = _fdopen(fd, "rt"); + if (fp == NULL) { + _close(fd); + TerminateProcess(pi.hProcess, 1); + WaitForSingleObject(pi.hProcess, INFINITE); + CloseHandle(pi.hProcess); + return NULL; + } + + /* The child owns the pipe writer. We do not need the process handle + * unless we want pclose() style exit status. + */ + CloseHandle(pi.hProcess); + return fp; +#else + return popen(cmd, "r"); +#endif +} + +int ivl_close_cmd_pipe(FILE *fp) +{ +#ifdef __MINGW32__ + return fclose(fp); +#else + return pclose(fp); +#endif +} diff --git a/libmisc/CmdExec.h b/libmisc/CmdExec.h new file mode 100644 index 000000000..7da9fb8b5 --- /dev/null +++ b/libmisc/CmdExec.h @@ -0,0 +1,36 @@ +#ifndef IVL_CmdExec_H +#define IVL_CmdExec_H +/* + * Copyright (c) 2026 Lars-Peter Clausen + * + * This source code is free software; you can redistribute it + * and/or modify it in source code form under the terms of the GNU + * General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +# include + +#ifdef __cplusplus +extern "C" { +#endif + +int ivl_run_cmd(const char *cmd, int verbose); +FILE *ivl_run_cmd_pipe(const char *cmd); +int ivl_close_cmd_pipe(FILE *fp); + +#ifdef __cplusplus +} +#endif + +#endif /* IVL_CmdExec_H */ diff --git a/pform.cc b/pform.cc index 0a62724e8..f35effd21 100644 --- a/pform.cc +++ b/pform.cc @@ -44,6 +44,7 @@ # include # include +# include "CmdExec.h" # include "ivl_assert.h" # include "ivl_alloc.h" @@ -3466,17 +3467,14 @@ int pform_parse(const char*path) if (strcmp(path, "-") == 0) { vl_input = stdin; } else if (ivlpp_string) { - char*cmdline = static_cast(malloc(strlen(ivlpp_string) + - strlen(path) + 4)); - strcpy(cmdline, ivlpp_string); - strcat(cmdline, " \""); - strcat(cmdline, path); - strcat(cmdline, "\""); + size_t cmdlen = strlen(ivlpp_string) + strlen(path) + 4; + char*cmdline = static_cast(malloc(cmdlen)); + snprintf(cmdline, cmdlen, "%s \"%s\"", ivlpp_string, path); if (verbose_flag) cerr << "Executing: " << cmdline << endl<< flush; - vl_input = popen(cmdline, "r"); + vl_input = ivl_run_cmd_pipe(cmdline); if (vl_input == 0) { cerr << "Unable to preprocess " << path << "." << endl; return 1; @@ -3526,7 +3524,7 @@ int pform_parse(const char*path) if (vl_input != stdin) { if (ivlpp_string) - pclose(vl_input); + ivl_close_cmd_pipe(vl_input); else fclose(vl_input); }