+ while (token.type == T_STRING_LITERAL) {
+ result = concat_strings(&result, &token.v.string);
+ next_token();
+ }
+
+ return result;
+}
+
+static const char *gnu_attribute_names[GNU_AK_LAST] = {
+ [GNU_AK_CONST] = "const",
+ [GNU_AK_VOLATILE] = "volatile",
+ [GNU_AK_CDECL] = "cdecl",
+ [GNU_AK_STDCALL] = "stdcall",
+ [GNU_AK_FASTCALL] = "fastcall",
+ [GNU_AK_DEPRECATED] = "deprecated",
+ [GNU_AK_NOINLINE] = "noinline",
+ [GNU_AK_NORETURN] = "noreturn",
+ [GNU_AK_NAKED] = "naked",
+ [GNU_AK_PURE] = "pure",
+ [GNU_AK_ALWAYS_INLINE] = "always_inline",
+ [GNU_AK_MALLOC] = "malloc",
+ [GNU_AK_WEAK] = "weak",
+ [GNU_AK_CONSTRUCTOR] = "constructor",
+ [GNU_AK_DESTRUCTOR] = "destructor",
+ [GNU_AK_NOTHROW] = "nothrow",
+ [GNU_AK_TRANSPARENT_UNION] = "transparent_union",
+ [GNU_AK_COMMON] = "coommon",
+ [GNU_AK_NOCOMMON] = "nocommon",
+ [GNU_AK_PACKED] = "packed",
+ [GNU_AK_SHARED] = "shared",
+ [GNU_AK_NOTSHARED] = "notshared",
+ [GNU_AK_USED] = "used",
+ [GNU_AK_UNUSED] = "unused",
+ [GNU_AK_NO_INSTRUMENT_FUNCTION] = "no_instrument_function",
+ [GNU_AK_WARN_UNUSED_RESULT] = "warn_unused_result",
+ [GNU_AK_LONGCALL] = "longcall",
+ [GNU_AK_SHORTCALL] = "shortcall",
+ [GNU_AK_LONG_CALL] = "long_call",
+ [GNU_AK_SHORT_CALL] = "short_call",
+ [GNU_AK_FUNCTION_VECTOR] = "function_vector",
+ [GNU_AK_INTERRUPT] = "interrupt",
+ [GNU_AK_INTERRUPT_HANDLER] = "interrupt_handler",
+ [GNU_AK_NMI_HANDLER] = "nmi_handler",
+ [GNU_AK_NESTING] = "nesting",
+ [GNU_AK_NEAR] = "near",
+ [GNU_AK_FAR] = "far",
+ [GNU_AK_SIGNAL] = "signal",
+ [GNU_AK_EIGTHBIT_DATA] = "eightbit_data",
+ [GNU_AK_TINY_DATA] = "tiny_data",
+ [GNU_AK_SAVEALL] = "saveall",
+ [GNU_AK_FLATTEN] = "flatten",
+ [GNU_AK_SSEREGPARM] = "sseregparm",
+ [GNU_AK_EXTERNALLY_VISIBLE] = "externally_visible",
+ [GNU_AK_RETURN_TWICE] = "return_twice",
+ [GNU_AK_MAY_ALIAS] = "may_alias",
+ [GNU_AK_MS_STRUCT] = "ms_struct",
+ [GNU_AK_GCC_STRUCT] = "gcc_struct",
+ [GNU_AK_DLLIMPORT] = "dllimport",
+ [GNU_AK_DLLEXPORT] = "dllexport",
+ [GNU_AK_ALIGNED] = "aligned",
+ [GNU_AK_ALIAS] = "alias",
+ [GNU_AK_SECTION] = "section",
+ [GNU_AK_FORMAT] = "format",
+ [GNU_AK_FORMAT_ARG] = "format_arg",
+ [GNU_AK_WEAKREF] = "weakref",
+ [GNU_AK_NONNULL] = "nonnull",
+ [GNU_AK_TLS_MODEL] = "tls_model",
+ [GNU_AK_VISIBILITY] = "visibility",
+ [GNU_AK_REGPARM] = "regparm",
+ [GNU_AK_MODE] = "mode",
+ [GNU_AK_MODEL] = "model",
+ [GNU_AK_TRAP_EXIT] = "trap_exit",
+ [GNU_AK_SP_SWITCH] = "sp_switch",
+ [GNU_AK_SENTINEL] = "sentinel"
+};
+
+/**
+ * compare two string, ignoring double underscores on the second.
+ */
+static int strcmp_underscore(const char *s1, const char *s2) {
+ if(s2[0] == '_' && s2[1] == '_') {
+ s2 += 2;
+ size_t l1 = strlen(s1);
+ if(l1 + 2 != strlen(s2)) {
+ /* not equal */
+ return 1;
+ }
+ return strncmp(s1, s2, l1);
+ }
+ return strcmp(s1, s2);
+}
+
+/**
+ * Allocate a new gnu temporal attribute.
+ */
+static gnu_attribute_t *allocate_gnu_attribute(gnu_attribute_kind_t kind) {
+ gnu_attribute_t *attribute = obstack_alloc(&temp_obst, sizeof(*attribute));
+ attribute->kind = kind;
+ attribute->next = NULL;
+ attribute->invalid = false;
+ attribute->have_arguments = false;
+
+ return attribute;
+}
+
+/**
+ * parse one constant expression argument.
+ */
+static void parse_gnu_attribute_const_arg(gnu_attribute_t *attribute) {
+ expression_t *expression;
+ add_anchor_token(')');
+ expression = parse_constant_expression();
+ rem_anchor_token(')');
+ expect(')');
+ (void)expression;
+ return;
+end_error:
+ attribute->invalid = true;
+}
+
+/**
+ * parse a list of constant expressions arguments.
+ */
+static void parse_gnu_attribute_const_arg_list(gnu_attribute_t *attribute) {
+ expression_t *expression;
+ add_anchor_token(')');
+ add_anchor_token(',');
+ while(true){
+ expression = parse_constant_expression();
+ if(token.type != ',')
+ break;
+ next_token();
+ }
+ rem_anchor_token(',');
+ rem_anchor_token(')');
+ expect(')');
+ (void)expression;
+ return;
+end_error:
+ attribute->invalid = true;
+}
+
+/**
+ * parse one string literal argument.
+ */
+static void parse_gnu_attribute_string_arg(gnu_attribute_t *attribute,
+ string_t *string)
+{
+ add_anchor_token('(');
+ if(token.type != T_STRING_LITERAL) {
+ parse_error_expected("while parsing attribute directive",
+ T_STRING_LITERAL, NULL);
+ goto end_error;
+ }
+ *string = parse_string_literals();
+ rem_anchor_token('(');
+ expect(')');
+ return;
+end_error:
+ attribute->invalid = true;
+}
+
+/**
+ * parse one tls model.
+ */
+static void parse_gnu_attribute_tls_model_arg(gnu_attribute_t *attribute) {
+ static const char *tls_models[] = {
+ "global-dynamic",
+ "local-dynamic",
+ "initial-exec",
+ "local-exec"
+ };
+ string_t string = { NULL, 0 };
+ parse_gnu_attribute_string_arg(attribute, &string);
+ if(string.begin != NULL) {
+ for(size_t i = 0; i < 4; ++i) {
+ if(strcmp(tls_models[i], string.begin) == 0) {
+ attribute->u.value = i;
+ return;
+ }
+ }
+ }
+ errorf(HERE, "'%s' is an unrecognized tls model", string.begin);
+ attribute->invalid = true;
+}
+
+/**
+ * parse one tls model.
+ */
+static void parse_gnu_attribute_visibility_arg(gnu_attribute_t *attribute) {
+ static const char *visibilities[] = {
+ "default",
+ "protected",
+ "hidden",
+ "internal"
+ };
+ string_t string = { NULL, 0 };
+ parse_gnu_attribute_string_arg(attribute, &string);
+ if(string.begin != NULL) {
+ for(size_t i = 0; i < 4; ++i) {
+ if(strcmp(visibilities[i], string.begin) == 0) {
+ attribute->u.value = i;
+ return;
+ }
+ }
+ }
+ errorf(HERE, "'%s' is an unrecognized visibility", string.begin);
+ attribute->invalid = true;
+}
+
+/**
+ * parse one (code) model.
+ */
+static void parse_gnu_attribute_model_arg(gnu_attribute_t *attribute) {
+ static const char *visibilities[] = {
+ "small",
+ "medium",
+ "large"
+ };
+ string_t string = { NULL, 0 };
+ parse_gnu_attribute_string_arg(attribute, &string);
+ if(string.begin != NULL) {
+ for(int i = 0; i < 3; ++i) {
+ if(strcmp(visibilities[i], string.begin) == 0) {
+ attribute->u.value = i;
+ return;
+ }
+ }
+ }
+ errorf(HERE, "'%s' is an unrecognized model", string.begin);
+ attribute->invalid = true;
+}
+
+static void parse_gnu_attribute_mode_arg(gnu_attribute_t *attribute)
+{
+ /* TODO: find out what is allowed here... */
+
+ /* at least: byte, word, pointer, list of machine modes
+ * __XXX___ is interpreted as XXX */
+ add_anchor_token(')');
+ expect(T_IDENTIFIER);
+ rem_anchor_token(')');
+ expect(')');
+ return;
+end_error:
+ attribute->invalid = true;
+}
+
+/**
+ * parse one interrupt argument.
+ */
+static void parse_gnu_attribute_interrupt_arg(gnu_attribute_t *attribute) {
+ static const char *interrupts[] = {
+ "IRQ",
+ "FIQ",
+ "SWI",
+ "ABORT",
+ "UNDEF"
+ };
+ string_t string = { NULL, 0 };
+ parse_gnu_attribute_string_arg(attribute, &string);
+ if(string.begin != NULL) {
+ for(size_t i = 0; i < 5; ++i) {
+ if(strcmp(interrupts[i], string.begin) == 0) {
+ attribute->u.value = i;
+ return;
+ }
+ }
+ }
+ errorf(HERE, "'%s' is an interrupt", string.begin);
+ attribute->invalid = true;
+}
+
+/**
+ * parse ( identifier, const expression, const expression )
+ */
+static void parse_gnu_attribute_format_args(gnu_attribute_t *attribute) {
+ static const char *format_names[] = {
+ "printf",
+ "scanf",
+ "strftime",
+ "strfmon"
+ };
+ int i;
+
+ if(token.type != T_IDENTIFIER) {
+ parse_error_expected("while parsing format attribute directive", T_IDENTIFIER, NULL);
+ goto end_error;
+ }
+ const char *name = token.v.symbol->string;
+ for(i = 0; i < 4; ++i) {
+ if(strcmp_underscore(format_names[i], name) == 0)
+ break;
+ }
+ if(i >= 4) {
+ if(warning.attribute)
+ warningf(HERE, "'%s' is an unrecognized format function type", name);
+ }
+ next_token();
+
+ expect(',');
+ add_anchor_token(')');
+ add_anchor_token(',');
+ parse_constant_expression();
+ rem_anchor_token(',');
+ rem_anchor_token('(');
+
+ expect(',');
+ add_anchor_token(')');
+ parse_constant_expression();
+ rem_anchor_token('(');
+ expect(')');
+ return;
+end_error:
+ attribute->u.value = true;
+}
+
+static void check_no_argument(gnu_attribute_t *attribute, const char *name)
+{
+ if(!attribute->have_arguments)
+ return;
+
+ /* should have no arguments */
+ errorf(HERE, "wrong number of arguments specified for '%s' attribute", name);
+ eat_until_matching_token('(');
+ /* we have already consumed '(', so we stop before ')', eat it */
+ eat(')');
+ attribute->invalid = true;
+}
+
+/**
+ * Parse one GNU attribute.
+ *
+ * Note that attribute names can be specified WITH or WITHOUT
+ * double underscores, ie const or __const__.
+ *
+ * The following attributes are parsed without arguments
+ * const
+ * volatile
+ * cdecl
+ * stdcall
+ * fastcall
+ * deprecated
+ * noinline
+ * noreturn
+ * naked
+ * pure
+ * always_inline
+ * malloc
+ * weak
+ * constructor
+ * destructor
+ * nothrow
+ * transparent_union
+ * common
+ * nocommon
+ * packed
+ * shared
+ * notshared
+ * used
+ * unused
+ * no_instrument_function
+ * warn_unused_result
+ * longcall
+ * shortcall
+ * long_call
+ * short_call
+ * function_vector
+ * interrupt_handler
+ * nmi_handler
+ * nesting
+ * near
+ * far
+ * signal
+ * eightbit_data
+ * tiny_data
+ * saveall
+ * flatten
+ * sseregparm
+ * externally_visible
+ * return_twice
+ * may_alias
+ * ms_struct
+ * gcc_struct
+ * dllimport
+ * dllexport
+ *
+ * The following attributes are parsed with arguments
+ * aligned( const expression )
+ * alias( string literal )
+ * section( string literal )
+ * format( identifier, const expression, const expression )
+ * format_arg( const expression )
+ * tls_model( string literal )
+ * visibility( string literal )
+ * regparm( const expression )
+ * model( string leteral )
+ * trap_exit( const expression )
+ * sp_switch( string literal )
+ *
+ * The following attributes might have arguments
+ * weak_ref( string literal )
+ * non_null( const expression // ',' )
+ * interrupt( string literal )
+ * sentinel( constant expression )
+ */
+static decl_modifiers_t parse_gnu_attribute(gnu_attribute_t **attributes)
+{
+ gnu_attribute_t *head = *attributes;
+ gnu_attribute_t *last = *attributes;
+ decl_modifiers_t modifiers = 0;
+ gnu_attribute_t *attribute;
+
+ eat(T___attribute__);
+ expect('(');
+ expect('(');
+
+ if(token.type != ')') {
+ /* find the end of the list */
+ if(last != NULL) {
+ while(last->next != NULL)
+ last = last->next;
+ }
+
+ /* non-empty attribute list */
+ while(true) {
+ const char *name;
+ if(token.type == T_const) {
+ name = "const";
+ } else if(token.type == T_volatile) {
+ name = "volatile";
+ } else if(token.type == T_cdecl) {
+ /* __attribute__((cdecl)), WITH ms mode */
+ name = "cdecl";
+ } else if(token.type != T_IDENTIFIER) {
+ parse_error_expected("while parsing GNU attribute", T_IDENTIFIER, NULL);
+ break;
+ }
+ const symbol_t *sym = token.v.symbol;
+ name = sym->string;
+ next_token();
+
+ int i;
+ for(i = 0; i < GNU_AK_LAST; ++i) {
+ if(strcmp_underscore(gnu_attribute_names[i], name) == 0)
+ break;
+ }
+ gnu_attribute_kind_t kind = (gnu_attribute_kind_t)i;
+
+ attribute = NULL;
+ if(kind == GNU_AK_LAST) {
+ if(warning.attribute)
+ warningf(HERE, "'%s' attribute directive ignored", name);
+
+ /* skip possible arguments */
+ if(token.type == '(') {
+ eat_until_matching_token(')');
+ }
+ } else {
+ /* check for arguments */
+ attribute = allocate_gnu_attribute(kind);
+ if(token.type == '(') {
+ next_token();
+ if(token.type == ')') {
+ /* empty args are allowed */
+ next_token();
+ } else
+ attribute->have_arguments = true;
+ }
+
+ switch(kind) {
+ case GNU_AK_CONST:
+ case GNU_AK_VOLATILE:
+ case GNU_AK_CDECL:
+ case GNU_AK_STDCALL:
+ case GNU_AK_FASTCALL:
+ case GNU_AK_DEPRECATED:
+ case GNU_AK_NAKED:
+ case GNU_AK_MALLOC:
+ case GNU_AK_WEAK:
+ case GNU_AK_COMMON:
+ case GNU_AK_NOCOMMON:
+ case GNU_AK_SHARED:
+ case GNU_AK_NOTSHARED:
+ case GNU_AK_USED:
+ case GNU_AK_UNUSED:
+ case GNU_AK_NO_INSTRUMENT_FUNCTION:
+ case GNU_AK_WARN_UNUSED_RESULT:
+ case GNU_AK_LONGCALL:
+ case GNU_AK_SHORTCALL:
+ case GNU_AK_LONG_CALL:
+ case GNU_AK_SHORT_CALL:
+ case GNU_AK_FUNCTION_VECTOR:
+ case GNU_AK_INTERRUPT_HANDLER:
+ case GNU_AK_NMI_HANDLER:
+ case GNU_AK_NESTING:
+ case GNU_AK_NEAR:
+ case GNU_AK_FAR:
+ case GNU_AK_SIGNAL:
+ case GNU_AK_EIGTHBIT_DATA:
+ case GNU_AK_TINY_DATA:
+ case GNU_AK_SAVEALL:
+ case GNU_AK_FLATTEN:
+ case GNU_AK_SSEREGPARM:
+ case GNU_AK_EXTERNALLY_VISIBLE:
+ case GNU_AK_RETURN_TWICE:
+ case GNU_AK_MAY_ALIAS:
+ case GNU_AK_MS_STRUCT:
+ case GNU_AK_GCC_STRUCT:
+ check_no_argument(attribute, name);
+ break;
+
+ case GNU_AK_PURE:
+ check_no_argument(attribute, name);
+ modifiers |= DM_PURE;
+ break;
+
+ case GNU_AK_ALWAYS_INLINE:
+ check_no_argument(attribute, name);
+ modifiers |= DM_FORCEINLINE;
+ break;
+
+ case GNU_AK_DLLIMPORT:
+ check_no_argument(attribute, name);
+ modifiers |= DM_DLLIMPORT;
+ break;
+
+ case GNU_AK_DLLEXPORT:
+ check_no_argument(attribute, name);
+ modifiers |= DM_DLLEXPORT;
+ break;
+
+ case GNU_AK_PACKED:
+ check_no_argument(attribute, name);
+ modifiers |= DM_PACKED;
+ break;
+
+ case GNU_AK_NOINLINE:
+ check_no_argument(attribute, name);
+ modifiers |= DM_NOINLINE;
+ break;
+
+ case GNU_AK_NORETURN:
+ check_no_argument(attribute, name);
+ modifiers |= DM_NORETURN;
+ break;
+
+ case GNU_AK_NOTHROW:
+ check_no_argument(attribute, name);
+ modifiers |= DM_NOTHROW;
+ break;
+
+ case GNU_AK_TRANSPARENT_UNION:
+ check_no_argument(attribute, name);
+ modifiers |= DM_TRANSPARENT_UNION;
+ break;
+
+ case GNU_AK_CONSTRUCTOR:
+ check_no_argument(attribute, name);
+ modifiers |= DM_CONSTRUCTOR;
+ break;
+
+ case GNU_AK_DESTRUCTOR:
+ check_no_argument(attribute, name);
+ modifiers |= DM_DESTRUCTOR;
+ break;
+
+ case GNU_AK_ALIGNED:
+ case GNU_AK_FORMAT_ARG:
+ case GNU_AK_REGPARM:
+ case GNU_AK_TRAP_EXIT:
+ if(!attribute->have_arguments) {
+ /* should have arguments */
+ errorf(HERE, "wrong number of arguments specified for '%s' attribute", name);
+ attribute->invalid = true;
+ } else
+ parse_gnu_attribute_const_arg(attribute);
+ break;
+ case GNU_AK_ALIAS:
+ case GNU_AK_SECTION:
+ case GNU_AK_SP_SWITCH:
+ if(!attribute->have_arguments) {
+ /* should have arguments */
+ errorf(HERE, "wrong number of arguments specified for '%s' attribute", name);
+ attribute->invalid = true;
+ } else
+ parse_gnu_attribute_string_arg(attribute, &attribute->u.string);
+ break;
+ case GNU_AK_FORMAT:
+ if(!attribute->have_arguments) {
+ /* should have arguments */
+ errorf(HERE, "wrong number of arguments specified for '%s' attribute", name);
+ attribute->invalid = true;
+ } else
+ parse_gnu_attribute_format_args(attribute);
+ break;
+ case GNU_AK_WEAKREF:
+ /* may have one string argument */
+ if(attribute->have_arguments)
+ parse_gnu_attribute_string_arg(attribute, &attribute->u.string);
+ break;
+ case GNU_AK_NONNULL:
+ if(attribute->have_arguments)
+ parse_gnu_attribute_const_arg_list(attribute);
+ break;
+ case GNU_AK_TLS_MODEL:
+ if(!attribute->have_arguments) {
+ /* should have arguments */
+ errorf(HERE, "wrong number of arguments specified for '%s' attribute", name);
+ } else
+ parse_gnu_attribute_tls_model_arg(attribute);
+ break;
+ case GNU_AK_VISIBILITY:
+ if(!attribute->have_arguments) {
+ /* should have arguments */
+ errorf(HERE, "wrong number of arguments specified for '%s' attribute", name);
+ } else
+ parse_gnu_attribute_visibility_arg(attribute);
+ break;
+ case GNU_AK_MODEL:
+ if(!attribute->have_arguments) {
+ /* should have arguments */
+ errorf(HERE, "wrong number of arguments specified for '%s' attribute", name);
+ } else {
+ parse_gnu_attribute_model_arg(attribute);
+ }
+ break;
+ case GNU_AK_MODE:
+ if(!attribute->have_arguments) {
+ /* should have arguments */
+ errorf(HERE, "wrong number of arguments specified for '%s' attribute", name);
+ } else {
+ parse_gnu_attribute_mode_arg(attribute);
+ }
+ break;
+ case GNU_AK_INTERRUPT:
+ /* may have one string argument */
+ if(attribute->have_arguments)
+ parse_gnu_attribute_interrupt_arg(attribute);
+ break;
+ case GNU_AK_SENTINEL:
+ /* may have one string argument */
+ if(attribute->have_arguments)
+ parse_gnu_attribute_const_arg(attribute);
+ break;
+ case GNU_AK_LAST:
+ /* already handled */
+ break;
+ }
+ }
+ if(attribute != NULL) {
+ if(last != NULL) {
+ last->next = attribute;
+ last = attribute;
+ } else {
+ head = last = attribute;
+ }
+ }
+
+ if(token.type != ',')
+ break;
+ next_token();
+ }