2 Commits

Author SHA1 Message Date
Avi Halachmi (:avih)
f4e01bfcab tcc -run: support custom stdin with -rstdin FILE
When tcc -run is used with input file from stdin, then run_main's
stdin (which is tcc's stdin) is already EOF after we've read it.
If the program operates exclusively on stdin, then it cannot be used.

This commit adds an option -rstdin FILE which sets run_main's stdin.

Can also be used in such case to set run_main's stdin to the tty,
using -rstdin /dev/tty (posix) or -rstdin con (windows).
2025-10-27 14:50:56 +02:00
Avi Halachmi (:avih)
234e2dd2bf tcc options: document behavior and clashes (no-op)
The tcc options behavior is non trivial, and it's also susceptible
to ambiguities - which do exist (but currently are not painful).

- Add a script 'optclash' which detects clashes/ambiguities.
- Document the parsing behaviour and current clashes/resolutions
  as comments in libtcc.c .
2025-10-27 14:50:54 +02:00
5 changed files with 104 additions and 1 deletions

View File

@@ -1547,6 +1547,7 @@ enum {
TCC_OPTION_rdynamic,
TCC_OPTION_pthread,
TCC_OPTION_run,
TCC_OPTION_rstdin,
TCC_OPTION_w,
TCC_OPTION_E,
TCC_OPTION_M,
@@ -1571,6 +1572,30 @@ enum {
#define TCC_OPTION_HAS_ARG 0x0001
#define TCC_OPTION_NOSEP 0x0002 /* cannot have space before option and arg */
/*
* in tcc_options, if opt-string A is a prefix of opt-string B,
* it's un-ambiguous if and only if option A is without TCC_OPTION_HAS_ARG.
* otherwise (A with HAS_ARG), if, for instance, A is FOO and B is FOOBAR,
* then "-FOOBAR" is either A with arg BAR, or B (-FOOBARX too, if B HAS_ARG).
*
* tcc_parse_args searches tcc_options in order, so if ambiguous:
* - if the shorter (A) is earlier: the longer (B) is completely unreachable.
* - else B wins, and A can't be used with adjacent arg if it also matches B.
*
* there are few clashes currently, and the longer is always earlier/reachable.
* when it's ambiguous, shorter-concat-arg is not useful currently.
* the sh(1) script 'optclash' can identifiy clashes (tcc root dir, try "-h").
* at the time of writing, running './optclash' prints this:
-Wl,... (1642) overrides -W... (1644)
-Wp,... (1643) overrides -W... (1644)
-dumpmachine (1630) overrides -d... (1632)
-dumpversion (1631) overrides -d... (1632)
-dynamiclib (1623) overrides -d... (1632)
-flat_namespace (1624) overrides -f... (1650)
-mfloat-abi... (1647) overrides -m... (1649)
*/
static const TCCOption tcc_options[] = {
{ "h", TCC_OPTION_HELP, 0 },
{ "-help", TCC_OPTION_HELP, 0 },
@@ -1613,6 +1638,7 @@ static const TCCOption tcc_options[] = {
{ "o", TCC_OPTION_o, TCC_OPTION_HAS_ARG },
{ "pthread", TCC_OPTION_pthread, 0},
{ "run", TCC_OPTION_run, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP },
{ "rstdin", TCC_OPTION_rstdin, TCC_OPTION_HAS_ARG },
{ "rdynamic", TCC_OPTION_rdynamic, 0 },
{ "r", TCC_OPTION_r, 0 },
{ "Wl,", TCC_OPTION_Wl, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP },
@@ -1808,6 +1834,27 @@ static void args_parser_add_file(TCCState *s, const char* filename, int filetype
++s->nb_libraries;
}
/* parsing is between getopt(3) and getopt_long(3), and permuting-like:
* - an option is 1 or more chars.
* - at most 1 option per arg in argv.
* - an option in argv is "-OPT[...]" (few are --OPT, if OPT is "-...").
* - optarg is next arg, or adjacent non-empty (no '='. -std=.. is arg "=..").
* - supports also adjacent-only optarg (typically optional).
* - supports mixed options and operands ("--" is ignored, except with -run).
* - -OPT[...] can be ambiguous, which is resolved using tcc_options's order.
* (see tcc_options for details)
*
* specifically, per arg of argv, in order:
* - if arg begins with '@' and is not exactly "@": process as @listfile.
* - elif arg is exactly "-" or doesn't begin with '-': process as input file.
* - if -run... is already set: also stop, arg... become argv of run_main.
* - elif arg is "--":
* - if -run... is already set: stop, arg... become argv of run_main.
* - else ignore it.
* - else ("-STRING") try to apply it as option, maybe with next (opt)arg.
*
* after all args, if -run... but no "stop": run_main gets our argv (tcc ...)
*/
/* using * to argc/argv to let "tcc -ar" benefit from @listfile expansion */
PUB_FUNC int tcc_parse_args(TCCState *s, int *pargc, char ***pargv)
{
@@ -1996,6 +2043,12 @@ PUB_FUNC int tcc_parse_args(TCCState *s, int *pargc, char ***pargv)
goto set_output_type;
#else
return tcc_error_noabort("-run is not available in a cross compiler");
#endif
#ifdef TCC_IS_NATIVE
case TCC_OPTION_rstdin:
/* custom stdin for run_main */
s->run_stdin = optarg;
break;
#endif
case TCC_OPTION_v:
do ++s->verbose; while (*optarg++ == 'v');

38
optclash Executable file
View File

@@ -0,0 +1,38 @@
#!/bin/sh
export LC_ALL=C
defname=libtcc.c
# $1 is the line number, $2... is the actual source line
extract_opts() { awk '/tcc_options\[\]/ {x=1}; x; x && $2~/^}/ {exit}'; }
case $1 in -h|--help)
echo "Usage: $0 [INFILE]"
echo "Detect tcc_options[] clashes in $defname-like INFILE."
echo "If INFILE is missing, use $defname at the same dir as this script."
echo "Clashes are reported as longer-overrides, or longer-unreachable."
exit
esac
f=${1-$(dirname "$0")/$defname}
[ -r "$f" ] || { >&2 echo "$0: can't read -- $f"; exit 1; }
nl -b a <"$f" | extract_opts | tr \" ' ' | awk '$2=="{"' | sort -b -k 3 |
# "<line-num> { <unquoted-opt> <rest-of-line>" sorted-up by opt
# unavoidable O(N^2). the sort simplifies the awk code - only test prior opts.
awk '
{
n=$1; opt=$3; h=/HAS_ARG/
for (pn in prevs) { # pn: line-num
po = prevs[pn] # po: opt-with-has-arg
if (index(opt,po) == 1) {
clash=1
printf("-%s%s (%d) %s -%s... (%s)\n", opt,h?"...":"",n,
n>pn? "is not reachable! by":"overrides", po,pn)
}
}
}
h {prevs[n] = opt}
END {if (clash) exit 1; print "no clashes"}
'

2
tcc.c
View File

@@ -35,7 +35,7 @@ static const char help[] =
"General options:\n"
" -c compile only - generate an object file\n"
" -o outfile set output filename\n"
" -run run compiled source\n"
" -run run compiled source [with custom stdin: -rstdin FILE]\n"
" -fflag set or reset (with 'no-' prefix) 'flag' (see tcc -hh)\n"
" -Wwarning set or reset (with 'no-' prefix) 'warning' (see tcc -hh)\n"
" -w disable all warnings\n"

1
tcc.h
View File

@@ -976,6 +976,7 @@ struct TCCState {
const char *run_main; /* entry for tcc_run() */
void *run_ptr; /* runtime_memory */
unsigned run_size; /* size of runtime_memory */
const char *run_stdin; /* custom stdin file for run_main */
#ifdef _WIN64
void *run_function_table; /* unwind data */
#endif

View File

@@ -233,6 +233,17 @@ LIBTCCAPI int tcc_run(TCCState *s1, int argc, char **argv)
prog_main = (void*)get_sym_addr(s1, s1->run_main, 1, 1);
if ((addr_t)-1 == (addr_t)prog_main)
return -1;
/* custom stdin for run_main, mainly if stdin is/was an input file.
* fileno(stdin) should remain 0, as posix mandates to use the smallest
* free fd, which is 0 after the initial fclose in freopen. windows too.
* to set stdin to the tty, use /dev/tty (posix) or con (windows).
*/
if (s1->run_stdin && !freopen(s1->run_stdin, "r", stdin)) {
tcc_error_noabort("failed to reopen stdin from '%s'", s1->run_stdin);
return -1;
}
errno = 0; /* clean errno value */
fflush(stdout);
fflush(stderr);