aboutsummaryrefslogtreecommitdiff
path: root/gcc/c-family/c-format.c
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/c-family/c-format.c')
-rw-r--r--gcc/c-family/c-format.c192
1 files changed, 178 insertions, 14 deletions
diff --git a/gcc/c-family/c-format.c b/gcc/c-family/c-format.c
index e7fd229..a64717a 100644
--- a/gcc/c-family/c-format.c
+++ b/gcc/c-family/c-format.c
@@ -32,6 +32,7 @@ along with GCC; see the file COPYING3. If not see
#include "langhooks.h"
#include "c-format.h"
#include "alloc-pool.h"
+#include "target.h"
/* Set format warning options according to a -Wformat=n option. */
@@ -63,6 +64,7 @@ enum format_type { printf_format_type, asm_fprintf_format_type,
gcc_diag_format_type, gcc_tdiag_format_type,
gcc_cdiag_format_type,
gcc_cxxdiag_format_type, gcc_gfc_format_type,
+ gcc_objc_string_format_type,
format_type_error = -1};
typedef struct function_format_info
@@ -77,12 +79,38 @@ static int decode_format_type (const char *);
static bool check_format_string (tree argument,
unsigned HOST_WIDE_INT format_num,
- int flags, bool *no_add_attrs);
+ int flags, bool *no_add_attrs,
+ int expected_format_type);
static bool get_constant (tree expr, unsigned HOST_WIDE_INT *value,
int validated_p);
static const char *convert_format_name_to_system_name (const char *attr_name);
static bool cmp_attribs (const char *tattr_name, const char *attr_name);
+static int first_target_format_type;
+static const char *format_name (int format_num);
+static int format_flags (int format_num);
+
+/* Check that we have a pointer to a string suitable for use as a format.
+ The default is to check for a char type.
+ For objective-c dialects, this is extended to include references to string
+ objects validated by objc_string_ref_type_p ().
+ Targets may also provide a string object type that can be used within c and
+ c++ and shared with their respective objective-c dialects. In this case the
+ reference to a format string is checked for validity via a hook.
+
+ The function returns true if strref points to any string type valid for the
+ language dialect and target. */
+
+static bool
+valid_stringptr_type_p (tree strref)
+{
+ return (strref != NULL
+ && TREE_CODE (strref) == POINTER_TYPE
+ && (TYPE_MAIN_VARIANT (TREE_TYPE (strref)) == char_type_node
+ || objc_string_ref_type_p (strref)
+ || (*targetcm.string_object_ref_type_p) ((const_tree) strref)));
+}
+
/* Handle a "format_arg" attribute; arguments as in
struct attribute_spec.handler. */
tree
@@ -104,13 +132,13 @@ handle_format_arg_attribute (tree *node, tree ARG_UNUSED (name),
argument = TYPE_ARG_TYPES (type);
if (argument)
{
- if (!check_format_string (argument, format_num, flags, no_add_attrs))
+ /* The format arg can be any string reference valid for the language and
+ target. We cannot be more specific in this case. */
+ if (!check_format_string (argument, format_num, flags, no_add_attrs, -1))
return NULL_TREE;
}
- if (TREE_CODE (TREE_TYPE (type)) != POINTER_TYPE
- || (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_TYPE (type)))
- != char_type_node))
+ if (!valid_stringptr_type_p (TREE_TYPE (type)))
{
if (!(flags & (int) ATTR_FLAG_BUILT_IN))
error ("function does not return string type");
@@ -121,13 +149,18 @@ handle_format_arg_attribute (tree *node, tree ARG_UNUSED (name),
return NULL_TREE;
}
-/* Verify that the format_num argument is actually a string, in case
- the format attribute is in error. */
+/* Verify that the format_num argument is actually a string reference suitable,
+ for the language dialect and target (in case the format attribute is in
+ error). When we know the specific reference type expected, this is also
+ checked. */
static bool
check_format_string (tree argument, unsigned HOST_WIDE_INT format_num,
- int flags, bool *no_add_attrs)
+ int flags, bool *no_add_attrs, int expected_format_type)
{
unsigned HOST_WIDE_INT i;
+ bool is_objc_sref, is_target_sref, is_char_ref;
+ tree ref;
+ int fmt_flags;
for (i = 1; i != format_num; i++)
{
@@ -137,17 +170,78 @@ check_format_string (tree argument, unsigned HOST_WIDE_INT format_num,
}
if (!argument
- || TREE_CODE (TREE_VALUE (argument)) != POINTER_TYPE
- || (TYPE_MAIN_VARIANT (TREE_TYPE (TREE_VALUE (argument)))
- != char_type_node))
+ || !(ref = TREE_VALUE (argument))
+ || !valid_stringptr_type_p (ref))
{
if (!(flags & (int) ATTR_FLAG_BUILT_IN))
- error ("format string argument not a string type");
+ error ("format string argument is not a string type");
*no_add_attrs = true;
return false;
}
- return true;
+ /* We only know that we want a suitable string reference. */
+ if (expected_format_type < 0)
+ return true;
+
+ /* Now check that the arg matches the expected type. */
+ is_char_ref =
+ (TYPE_MAIN_VARIANT (TREE_TYPE (ref)) == char_type_node);
+
+ fmt_flags = format_flags (expected_format_type);
+ is_objc_sref = is_target_sref = false;
+ if (!is_char_ref)
+ is_objc_sref = objc_string_ref_type_p (ref);
+
+ if (!(fmt_flags & FMT_FLAG_PARSE_ARG_CONVERT_EXTERNAL))
+ {
+ if (is_char_ref)
+ return true; /* OK, we expected a char and found one. */
+ else
+ {
+ /* We expected a char but found an extended string type. */
+ if (is_objc_sref)
+ error ("found a %<%s%> reference but the format argument should"
+ " be a string", format_name (gcc_objc_string_format_type));
+ else
+ error ("found a %qT but the format argument should be a string",
+ ref);
+ *no_add_attrs = true;
+ return false;
+ }
+ }
+
+ /* We expect a string object type as the format arg. */
+ if (is_char_ref)
+ {
+ error ("format argument should be a %<%s%> reference but"
+ " a string was found", format_name (expected_format_type));
+ *no_add_attrs = true;
+ return false;
+ }
+
+ /* We will assert that objective-c will support either its own string type
+ or the target-supplied variant. */
+ if (!is_objc_sref)
+ is_target_sref = (*targetcm.string_object_ref_type_p) ((const_tree) ref);
+
+ if (expected_format_type == (int) gcc_objc_string_format_type
+ && (is_objc_sref || is_target_sref))
+ return true;
+
+ /* We will allow a target string ref to match only itself. */
+ if (first_target_format_type
+ && expected_format_type >= first_target_format_type
+ && is_target_sref)
+ return true;
+ else
+ {
+ error ("format argument should be a %<%s%> reference",
+ format_name (expected_format_type));
+ *no_add_attrs = true;
+ return false;
+ }
+
+ gcc_unreachable ();
}
/* Verify EXPR is a constant, and store its value.
@@ -195,6 +289,16 @@ decode_format_attr (tree args, function_format_info *info, int validated_p)
p = convert_format_name_to_system_name (p);
info->format_type = decode_format_type (p);
+
+ if (!c_dialect_objc ()
+ && info->format_type == gcc_objc_string_format_type)
+ {
+ gcc_assert (!validated_p);
+ warning (OPT_Wformat, "%qE is only allowed in Objective-C dialects",
+ format_type_id);
+ info->format_type = format_type_error;
+ return false;
+ }
if (info->format_type == format_type_error)
{
@@ -750,6 +854,11 @@ static const format_kind_info format_types_orig[] =
0, 0, 0, 0, 0, 0,
NULL, NULL
},
+ { "NSString", NULL, NULL, NULL, NULL,
+ NULL, NULL,
+ FMT_FLAG_ARG_CONVERT|FMT_FLAG_PARSE_ARG_CONVERT_EXTERNAL, 0, 0, 0, 0, 0, 0,
+ NULL, NULL
+ },
{ "gnu_scanf", scanf_length_specs, scan_char_table, "*'I", NULL,
scanf_flag_specs, scanf_flag_pairs,
FMT_FLAG_ARG_CONVERT|FMT_FLAG_SCANF_A_KLUDGE|FMT_FLAG_USE_DOLLAR|FMT_FLAG_ZERO_WIDTH_BAD|FMT_FLAG_DOLLAR_GAP_POINTER_OK,
@@ -812,6 +921,26 @@ typedef struct
tree params;
} format_check_context;
+/* Return the format name (as specified in the original table) for the format
+ type indicated by format_num. */
+static const char *
+format_name (int format_num)
+{
+ if (format_num >= 0 && format_num < n_format_types)
+ return format_types[format_num].name;
+ gcc_unreachable ();
+}
+
+/* Return the format flags (as specified in the original table) for the format
+ type indicated by format_num. */
+static int
+format_flags (int format_num)
+{
+ if (format_num >= 0 && format_num < n_format_types)
+ return format_types[format_num].flags;
+ gcc_unreachable ();
+}
+
static void check_format_info (function_format_info *, tree);
static void check_format_arg (void *, tree, unsigned HOST_WIDE_INT);
static void check_format_info_main (format_check_results *,
@@ -1349,6 +1478,39 @@ check_format_arg (void *ctx, tree format_tree,
return;
}
format_tree = TREE_OPERAND (format_tree, 0);
+ if (format_types[info->format_type].flags
+ & (int) FMT_FLAG_PARSE_ARG_CONVERT_EXTERNAL)
+ {
+ bool objc_str = (info->format_type == gcc_objc_string_format_type);
+ /* We cannot examine this string here - but we can check that it is
+ a valid type. */
+ if (TREE_CODE (format_tree) != CONST_DECL
+ || !((objc_str && objc_string_ref_type_p (TREE_TYPE (format_tree)))
+ || (*targetcm.string_object_ref_type_p)
+ ((const_tree) TREE_TYPE (format_tree))))
+ {
+ res->number_non_literal++;
+ return;
+ }
+ /* Skip to first argument to check. */
+ while (arg_num + 1 < info->first_arg_num)
+ {
+ if (params == 0)
+ return;
+ params = TREE_CHAIN (params);
+ ++arg_num;
+ }
+ /* So, we have a valid literal string object and one or more params.
+ We need to use an external helper to parse the string into format
+ info. For Objective-C variants we provide the resource within the
+ objc tree, for target variants, via a hook. */
+ if (objc_str)
+ objc_check_format_arg (format_tree, params);
+ else if (targetcm.check_string_object_format_arg)
+ (*targetcm.check_string_object_format_arg) (format_tree, params);
+ /* Else we can't handle it and retire quietly. */
+ return;
+ }
if (TREE_CODE (format_tree) == ARRAY_REF
&& host_integerp (TREE_OPERAND (format_tree, 1), 0)
&& (offset += tree_low_cst (TREE_OPERAND (format_tree, 1), 0)) >= 0)
@@ -2785,6 +2947,8 @@ handle_format_attribute (tree *node, tree ARG_UNUSED (name), tree args,
TARGET_N_FORMAT_TYPES * sizeof (dynamic_format_types[0]));
format_types = dynamic_format_types;
+ /* Provide a reference for the first potential external type. */
+ first_target_format_type = n_format_types;
n_format_types += TARGET_N_FORMAT_TYPES;
}
#endif
@@ -2799,7 +2963,7 @@ handle_format_attribute (tree *node, tree ARG_UNUSED (name), tree args,
if (argument)
{
if (!check_format_string (argument, info.format_num, flags,
- no_add_attrs))
+ no_add_attrs, info.format_type))
return NULL_TREE;
if (info.first_arg_num != 0)