From 234e2dd2bf920e1fdb2deaa4ab549ba85e11e9d7 Mon Sep 17 00:00:00 2001 From: "Avi Halachmi (:avih)" Date: Mon, 27 Oct 2025 13:02:14 +0200 Subject: [PATCH] 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 . --- libtcc.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ optclash | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100755 optclash diff --git a/libtcc.c b/libtcc.c index 6ff551eb..70d5e6a4 100644 --- a/libtcc.c +++ b/libtcc.c @@ -1571,6 +1571,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 }, @@ -1808,6 +1832,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) { diff --git a/optclash b/optclash new file mode 100755 index 00000000..92015fbc --- /dev/null +++ b/optclash @@ -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 | +# " { " 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"} +'