tcc-stupidos/libtcc/option.c
2025-02-11 13:07:04 +01:00

774 lines
16 KiB
C

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif /* HAVE_CONFIG_H */
#include <stdlib.h>
#include <string.h>
#include <tcc.h>
#include <tcc/option.h>
#include <tcc/memory.h>
#include "utils/string.h"
# define F_WD_ALL 0x0001 /* warning is activated when using -Wall */
# define F_FD_INVERT 0x0002 /* invert value before storing */
# define WARN_ON 1 /* warning is on (-Woption) */
# define WARN_ERR 2 /* warning is an error (-Werror=option) */
# define WARN_NOE 4 /* warning is not an error (-Wno-error=option) */
typedef struct Flag {
uint16_t offset;
uint16_t flags;
const char *name;
} Flag;
enum {
OPTION_ignored = 0,
OPTION_HELP,
OPTION_HELP_MORE,
OPTION_v,
OPTION_I,
OPTION_D,
OPTION_U,
OPTION_P,
OPTION_L,
OPTION_B,
OPTION_l,
OPTION_bench,
OPTION_c,
OPTION_dumpmachine,
OPTION_dumpversion,
OPTION_d,
OPTION_std,
OPTION_o,
OPTION_pthread,
OPTION_r,
OPTION_Wl,
OPTION_Wp,
OPTION_W,
OPTION_O,
OPTION_m,
OPTION_f,
OPTION_isystem,
OPTION_include,
OPTION_nostdinc,
OPTION_nostdlib,
OPTION_print_search_dirs,
OPTION_w,
OPTION_E,
OPTION_M,
OPTION_MF,
OPTION_MD,
OPTION_MM,
OPTION_MMD,
OPTION_MP,
OPTION_x,
OPTION_ar,
};
static const TCCOption options[] = {
{"h", OPTION_HELP, 0},
{"-help", OPTION_HELP, 0},
{"?", OPTION_HELP, 0},
{"hh", OPTION_HELP_MORE, 0},
{"v", OPTION_v, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP},
{"-version", OPTION_v, 0}, /* handle as verbose, also prints version*/
{"I", OPTION_I, TCC_OPTION_HAS_ARG},
{"D", OPTION_D, TCC_OPTION_HAS_ARG},
{"U", OPTION_U, TCC_OPTION_HAS_ARG},
{"P", OPTION_P, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP},
{"L", OPTION_L, TCC_OPTION_HAS_ARG},
{"B", OPTION_B, TCC_OPTION_HAS_ARG},
{"l", OPTION_l, TCC_OPTION_HAS_ARG},
{"bench", OPTION_bench, 0},
{"c", OPTION_c, 0},
{"dumpmachine", OPTION_dumpmachine, 0},
{"dumpversion", OPTION_dumpversion, 0},
{"d", OPTION_d, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP},
{"std", OPTION_std, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP},
{"o", OPTION_o, TCC_OPTION_HAS_ARG},
{"pthread", OPTION_pthread, 0},
{"r", OPTION_r, 0},
{"Wl", OPTION_Wl, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP},
{"Wp", OPTION_Wp, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP},
{"W", OPTION_W, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP},
{"O", OPTION_O, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP},
{"m", OPTION_m, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP},
{"f", OPTION_f, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP},
{"isystem", OPTION_isystem, TCC_OPTION_HAS_ARG},
{"include", OPTION_include, TCC_OPTION_HAS_ARG},
{"nostdinc", OPTION_nostdinc, 0},
{"nostdlib", OPTION_nostdlib, 0},
{"print-search-dirs", OPTION_print_search_dirs, 0},
{"w", OPTION_w, 0},
{"E", OPTION_E, 0},
{"M", OPTION_M, 0},
{"MD", OPTION_MD, 0},
{"MF", OPTION_MF, TCC_OPTION_HAS_ARG},
{"MM", OPTION_MM, 0},
{"MMD", OPTION_MMD, 0},
{"MP", OPTION_MP, 0},
{"x", OPTION_x, 0},
{"ar", OPTION_ar, 0},
/* ignored (silently, except after -Wunsupported) */
{"arch", 0, TCC_OPTION_HAS_ARG},
{"g", 0, TCC_OPTION_HAS_ARG | TCC_OPTION_NOSEP},
{"C", 0, 0},
{"-param", 0, 0},
{"pedantic", 0, 0},
{"pipe", 0, 0},
{"s", 0, 0},
{"traditional", 0, 0},
{"shared", 0, 0},
{"soname", 0, 0},
{"static", 0, 0},
{NULL, 0, 0}
};
static const Flag options_W[] = {
{offsetof(TCCState, warn_all), F_WD_ALL, "all"},
{offsetof(TCCState, warn_error), 0, "error"},
{offsetof(TCCState, warn_write_strings), 0, "write-strings"},
{offsetof(TCCState, warn_unsupported), 0, "unsupported"},
{offsetof(TCCState, warn_implicit_function_declaration), F_WD_ALL, "implicit-function-declaration"},
{offsetof(TCCState, warn_discarded_qualifiers), F_WD_ALL, "discarded-qualifiers"},
{0, 0, NULL}
};
static const Flag options_f[] = {
{offsetof(TCCState, char_is_unsigned), 0, "unsigned-char"},
{offsetof(TCCState, char_is_unsigned), F_FD_INVERT, "signed-char"},
{offsetof(TCCState, nocommon), F_FD_INVERT, "common"},
{offsetof(TCCState, leading_underscore), 0, "leading-underscore"},
{offsetof(TCCState, ms_extensions), 0, "ms-extensions"},
{offsetof(TCCState, dollars_in_identifiers), 0, "dollars-in-identifiers"},
{offsetof(TCCState, reverse_funcargs), 0, "reverse-funcargs"},
{offsetof(TCCState, gnu89_inline), 0, "gnu89-inline"},
{offsetof(TCCState, unwind_tables), 0, "asynchronous-unwind-tables"},
{0, 0, NULL}
};
static const Flag options_m[] = {
{offsetof(TCCState, ms_bitfields), 0, "ms-bitfields"},
{0, 0, NULL}
};
static const char dumpmachine_str[] = "i386-pc-stupidos";
static int
strstart(const char *val, const char **str)
{
const char *p;
const char *q;
p = *str;
q = val;
while (*q)
{
if (*p != *q) return (0);
p++;
q++;
}
*str = p;
return (1);
}
static int
set_flag(TCCState *s, const Flag *flags, const char *name)
{
int value, mask, ret;
const Flag *p;
const char *r;
unsigned char *f;
r = name;
value = !strstart("no-", &r);
mask = 0;
/* when called with options_W, look for -W[no-]error=<option> */
if ((flags->flags & F_WD_ALL) && strstart("error=", &r))
{
value = value ? WARN_ON|WARN_ERR : WARN_NOE;
mask = WARN_ON;
}
for (ret = -1, p = flags; p->name; ++p)
{
if ((ret && strcmp(r, p->name))
|| ((p->flags & F_WD_ALL) == 0))
{
continue;
}
f = (unsigned char *)s + p->offset;
*f = (*f & mask) | (value ^ !!(p->flags & F_FD_INVERT));
if (ret)
{
ret = 0;
if (strcmp(r, "all"))
{
break;
}
}
}
return ret;
}
/* Like strstart, but automatically takes into account that ld options can
*
* - start with double or single dash (e.g. '--soname' or '-soname')
* - arguments can be given as separate or after '=' (e.g. '-Wl,-soname,x.so'
* or '-Wl,-soname=x.so')
*
* you provide `val` always in 'option[=]' form (no leading -)
*/
static int
link_option(const char *str, const char *val, const char **ptr)
{
const char *p, *q;
int ret;
/* there should be 1 or 2 dashes */
if (*str++ != '-')
return 0;
if (*str == '-')
str++;
/* then str & val should match (potentially up to '=') */
p = str;
q = val;
ret = 1;
if (q[0] == '?')
{
++q;
if (strstart("no-", &p))
ret = -1;
}
while (*q != '\0' && *q != '=')
{
if (*p != *q)
return (0);
p++;
q++;
}
/* '=' near eos means ',' or '=' is ok */
if (*q == '=')
{
if (*p == 0)
*ptr = p;
if (*p != ',' && *p != '=')
return 0;
p++;
} else if (*p) {
return (0);
}
*ptr = p;
return (ret);
}
static int
link_arg(const char *opt, const char *str)
{
int l = strlen(opt);
return 0 == strncmp(opt, str, l) && (str[l] == '\0' || str[l] == ',');
}
static const char *
skip_linker_arg(const char **str)
{
const char *s1 = *str;
const char *s2 = strchr(s1, ',');
*str = s2 ? s2++ : (s2 = s1 + strlen(s1));
return s2;
}
static void
copy_linker_arg(char **pp, const char *s, int sep)
{
const char *q = s;
char *p = *pp;
int l = 0;
if (p && sep)
p[l = strlen(p)] = sep, ++l;
skip_linker_arg(&q);
pstrncpy(l + (*pp = tcc_realloc(p, q - s + l + 1)), s, q - s);
}
/* set linker options */
static int
set_linker(TCCState *s, const char *option)
{
const char *p;
char *end;
int ignoring;
int ret;
TCCState *s1 = s;
while (*option)
{
p = NULL;
end = NULL;
ignoring = 0;
if (link_option(option, "Bsymbolic", &p))
{
s->symbolic = 1;
} else if (link_option(option, "nostdlib", &p)) {
s->nostdlib = 1;
} else if (link_option(option, "e=", &p)
|| link_option(option, "entry=", &p)) {
copy_linker_arg(&s->entryname, p, 0);
} else if (link_option(option, "image-base=", &p)
|| link_option(option, "Ttext=", &p)) {
s->text_addr = strtoull(p, &end, 16);
s->has_text_addr = 1;
} else if (link_option(option, "oformat=", &p)) {
if (strstart("elf32-", &p)) {
s->output_format = TCC_OUTPUT_FORMAT_ELF;
} else if (link_arg("binary", p)) {
s->output_format = TCC_OUTPUT_FORMAT_BINARY;
#ifdef TCC_TARGET_COFF
} else if (link_arg("coff", p)) {
s->output_format = TCC_OUTPUT_FORMAT_COFF;
#endif
} else
goto err;
} else if (link_option(option, "as-needed", &p)) {
ignoring = 1;
} else if (link_option(option, "O", &p)) {
ignoring = 1;
} else if (link_option(option, "rpath=", &p)) {
copy_linker_arg(&s->rpath, p, ':');
} else if (link_option(option, "enable-new-dtags", &p)) {
s->enable_new_dtags = 1;
} else if (link_option(option, "section-alignment=", &p)) {
s->section_align = strtoul(p, &end, 16);
} else if (ret = link_option(option, "?whole-archive", &p), ret) {
if (ret > 0)
s->filetype |= AFF_WHOLE_ARCHIVE;
else
s->filetype &= ~AFF_WHOLE_ARCHIVE;
} else if (link_option(option, "z=", &p)) {
ignoring = 1;
} else if (p) {
return 0;
} else {
err:
return tcc_error_noabort("unsupported linker option '%s'", option);
}
if (ignoring)
tcc_warning_c(warn_unsupported)("unsupported linker option '%s'", option);
option = skip_linker_arg(&p);
}
return 1;
}
static int
args_parser_make_argv(const char *r, int *argc, char ***argv)
{
int ret = 0;
int q;
int c;
CString str;
for(;;)
{
while (c = (unsigned char)*r, c && c <= ' ')
{
++r;
}
if (c == 0)
{
break;
}
q = 0;
cstr_new(&str);
while (c = (unsigned char)*r, c)
{
++r;
if (c == '\\' && (*r == '"' || *r == '\\'))
{
c = *r++;
}
else if (c == '"')
{
q = !q;
continue;
}
else if (q == 0 && c <= ' ')
{
break;
}
cstr_ccat(&str, c);
}
cstr_ccat(&str, 0);
dynarray_add(argv, argc, tcc_strdup(str.data));
cstr_free(&str);
++ret;
}
return (ret);
}
static int
args_parser_listfile(TCCState *s, const char *fname, int optind, int *pargc,
char ***pargv)
{
TCCState *s1;
int fd, i;
char *p;
int argc = 0;
char **argv = NULL;
s1 = s;
fd = open(fname, O_RDONLY | O_BINARY);
if (fd < 0)
{
return (tcc_error_noabort("listfile '%s' not found", fname));
}
p = tcc_load_text(fd);
for (i = 0; i < *pargc; ++i)
{
if (i == optind)
{
args_parser_make_argv(p, &argc, &argv);
}
else
{
dynarray_add(&argv, &argc, tcc_strdup((*pargv)[i]));
}
}
tcc_free(p);
dynarray_reset(&s->argv, &s->argc);
*pargc = s->argc = argc, *pargv = s->argv = argv;
return (0);
}
static void
args_parser_add_file(TCCState *s, const char *fname, int ftype)
{
struct filespec *f;
f= tcc_malloc(sizeof *f + strlen(fname));
f->type = ftype;
strcpy(f->name, fname);
dynarray_add(&s->files, &s->nb_files, f);
}
int
tcc_parse_args(TCCState *s, int *pargc, char ***pargv, int optind)
{
TCCState *s1 = s;
const TCCOption *popt;
const char *optarg, *r;
const char *p1;
const char *r1;
int x;
int tool = 0;
int arg_start = 0;
int noaction;
char **argv;
int argc;
noaction = optind;
argv = *pargv;
argc = *pargc;
cstr_reset(&s->linker_arg);
while (optind < argc) {
r = argv[optind];
if (r[0] == '@' && r[1] != '\0')
{
if (args_parser_listfile(s, r + 1, optind, &argc, &argv))
{
return (-1);
}
continue;
}
optind++;
if (tool)
{
if (r[0] == '-' && r[1] == 'v' && r[2] == 0)
{
s->verbose++;
}
continue;
}
reparse:
if (r[0] != '-' || r[1] == '\0')
{
args_parser_add_file(s, r, s->filetype);
continue;
}
/* find option in table */
for (popt = options; ; ++popt)
{
p1 = popt->name;
r1 = r + 1;
if (p1 == NULL)
{
return tcc_error_noabort("invalid option -- '%s'", r);
}
if (!strstart(p1, &r1))
{
continue;
}
optarg = r1;
if (popt->flags & TCC_OPTION_HAS_ARG)
{
if (*r1 == '\0' && !(popt->flags & TCC_OPTION_NOSEP))
{
if (optind >= argc)
{
return (tcc_error_noabort("argument to '%s' is missing", r));
}
optarg = argv[optind++];
}
}
else if (*r1 != '\0')
{
continue;
}
break;
}
switch(popt->index)
{
case OPTION_HELP:
x = TCC_OPT_HELP;
goto extra_action;
case OPTION_HELP_MORE:
x = TCC_OPT_HELP_MORE;
goto extra_action;
case OPTION_I:
tcc_add_include_path(s, optarg);
break;
case OPTION_D:
tcc_define_symbol(s, optarg, NULL);
break;
case OPTION_U:
tcc_undefine_symbol(s, optarg);
break;
case OPTION_L:
tcc_add_library_path(s, optarg);
break;
case OPTION_B:
/* set tcc utilities path (mainly for tcc development) */
tcc_set_lib_path(s, optarg);
++noaction;
break;
case OPTION_l:
args_parser_add_file(s, optarg, AFF_TYPE_LIB | (s->filetype & ~AFF_TYPE_MASK));
s->nb_libraries++;
break;
case OPTION_pthread:
s->option_pthread = 1;
break;
case OPTION_bench:
s->do_bench = 1;
break;
case OPTION_c:
x = TCC_OUTPUT_OBJ;
set_output_type:
if (s->output_type)
tcc_warning("-%s: overriding compiler action already specified", popt->name);
s->output_type = x;
break;
case OPTION_d:
if (*optarg == 'D')
s->dflag = 3;
else if (*optarg == 'M')
s->dflag = 7;
else
goto unsupported_option;
break;
case OPTION_std:
if (strcmp(optarg, "=c11") == 0 || strcmp(optarg, "=gnu11") == 0)
s->cversion = 201112;
break;
case OPTION_o:
if (s->outfile)
{
tcc_warning("multiple -o option");
tcc_free(s->outfile);
}
s->outfile = tcc_strdup(optarg);
break;
case OPTION_r:
/* generate a .o merging several output files */
s->option_r = 1;
x = TCC_OUTPUT_OBJ;
goto set_output_type;
case OPTION_isystem:
tcc_add_sysinclude_path(s, optarg);
break;
case OPTION_include:
cstr_printf(&s->cmdline_incl, "#include \"%s\"\n", optarg);
break;
case OPTION_nostdinc:
s->nostdinc = 1;
break;
case OPTION_nostdlib:
s->nostdlib = 1;
break;
case OPTION_v:
do { ++s->verbose; } while (*optarg++ == 'v');
++noaction;
break;
case OPTION_f:
if (set_flag(s, options_f, optarg) < 0)
goto unsupported_option;
break;
case OPTION_m:
if (set_flag(s, options_m, optarg) < 0)
{
if (x = atoi(optarg), x != 32 && x != 64)
goto unsupported_option;
if (PTR_SIZE != x/8)
return x;
++noaction;
}
break;
case OPTION_W:
s->warn_none = 0;
if (optarg[0] && set_flag(s, options_W, optarg) < 0)
goto unsupported_option;
break;
case OPTION_w:
s->warn_none = 1;
break;
case OPTION_Wl:
if (s->linker_arg.size)
((char*)s->linker_arg.data)[s->linker_arg.size - 1] = ',';
cstr_cat(&s->linker_arg, optarg, 0);
x = set_linker(s, s->linker_arg.data);
if (x)
cstr_reset(&s->linker_arg);
if (x < 0)
return -1;
break;
case OPTION_Wp:
r = optarg;
goto reparse;
case OPTION_E:
x = TCC_OUTPUT_PREPROCESS;
goto set_output_type;
case OPTION_P:
s->Pflag = atoi(optarg) + 1;
break;
case OPTION_M:
s->include_sys_deps = 1;
// fall through
case OPTION_MM:
s->just_deps = 1;
if(!s->deps_outfile)
s->deps_outfile = tcc_strdup("-");
// fall through
case OPTION_MMD:
s->gen_deps = 1;
break;
case OPTION_MD:
s->gen_deps = 1;
s->include_sys_deps = 1;
break;
case OPTION_MF:
s->deps_outfile = tcc_strdup(optarg);
break;
case OPTION_MP:
s->gen_phony_deps = 1;
break;
case OPTION_dumpmachine:
printf("%s\n", dumpmachine_str);
exit(0);
case OPTION_dumpversion:
printf ("%s\n", PACKAGE_VERSION);
exit(0);
case OPTION_x:
x = 0;
if (*optarg == 'c')
x = AFF_TYPE_C;
else if (*optarg == 'a')
x = AFF_TYPE_ASMPP;
else if (*optarg == 'b')
x = AFF_TYPE_BIN;
else if (*optarg == 'n')
x = AFF_TYPE_NONE;
else
tcc_warning("unsupported language '%s'", optarg);
s->filetype = x | (s->filetype & ~AFF_TYPE_MASK);
break;
case OPTION_O:
s->optimize = atoi(optarg);
break;
case OPTION_print_search_dirs:
x = TCC_OPT_PRINT_DIRS;
goto extra_action;
case OPTION_ar:
x = TCC_OPT_ARCHIVE;
extra_action:
arg_start = optind - 1;
if (arg_start != noaction)
return tcc_error_noabort("cannot parse %s here", r);
tool = x;
break;
default:
unsupported_option:
tcc_warning_c(warn_unsupported)("unsupported option '%s'", r);
break;
}
}
if (s->linker_arg.size)
{
r = s->linker_arg.data;
return (tcc_error_noabort("argument to '%s' is missing", r));
}
*pargc = argc - arg_start;
*pargv = argv + arg_start;
if (tool)
return (tool);
if (optind != noaction)
return (0);
if (s->verbose == 2)
return (TCC_OPT_PRINT_DIRS);
if (s->verbose)
return (TCC_OPT_VERSION);
return (TCC_OPT_HELP);
}
int
tcc_set_options(TCCState *s, const char *str)
{
char **argv;
int argc;
int ret;
argv = NULL;
argc = 0;
args_parser_make_argv(str, &argc, &argv);
ret = tcc_parse_args(s, &argc, &argv, 0);
dynarray_reset(&argv, &argc);
if (ret < 0)
{
return (ret);
}
return (0);
}