PR tree-optimization/82945 - add warning for passing non-strings to functions that expect string arguments

gcc/ChangeLog:

	PR tree-optimization/82945
	* builtins.c (expand_builtin_strlen): Call maybe_warn_nonstring_arg.
	* calls.h (maybe_warn_nonstring_arg): Declare new function.
	* calls.c (get_attr_nonstring_decl, maybe_warn_nonstring_arg): New
	functions.
	(initialize_argument_information): Call maybe_warn_nonstring_arg.
	* calls.h (get_attr_nonstring_decl): Declare new function.
	* doc/extend.texi (attribute nonstring): Update.
	* gimple-fold.c (gimple_fold_builtin_strncpy): Call
	get_attr_nonstring_decl and handle it.
	* tree-ssa-strlen.c (maybe_diag_stxncpy_trunc): Same.  Improve
	detection of nul-termination.
	(strlen_to_stridx): Change to a pointer.
	(handle_builtin_strlen, handle_builtin_stxncpy): Adjust.
	(pass_strlen::execute): Same.

gcc/testsuite/ChangeLog:

	PR tree-optimization/82945
	* c-c++-common/Wstringop-truncation-2.c: New test.
	* c-c++-common/Wstringop-truncation.c: Adjust.
	* c-c++-common/attr-nonstring-2.c: Adjust.
	* c-c++-common/attr-nonstring-3.c: New test.

From-SVN: r255031
This commit is contained in:
Martin Sebor 2017-11-21 20:01:58 +00:00 committed by Martin Sebor
parent ab2c4ec8dc
commit 6a33d0ff21
12 changed files with 970 additions and 124 deletions

View File

@ -1,3 +1,21 @@
2017-11-21 Martin Sebor <msebor@redhat.com>
PR tree-optimization/82945
* builtins.c (expand_builtin_strlen): Call maybe_warn_nonstring_arg.
* calls.h (maybe_warn_nonstring_arg): Declare new function.
* calls.c (get_attr_nonstring_decl, maybe_warn_nonstring_arg): New
functions.
(initialize_argument_information): Call maybe_warn_nonstring_arg.
* calls.h (get_attr_nonstring_decl): Declare new function.
* doc/extend.texi (attribute nonstring): Update.
* gimple-fold.c (gimple_fold_builtin_strncpy): Call
get_attr_nonstring_decl and handle it.
* tree-ssa-strlen.c (maybe_diag_stxncpy_trunc): Same. Improve
detection of nul-termination.
(strlen_to_stridx): Change to a pointer.
(handle_builtin_strlen, handle_builtin_stxncpy): Adjust.
(pass_strlen::execute): Same.
2017-11-21 Sergey Shalnov <Sergey.Shalnov@intel.com>
* config/i386/i386-opts.h (enum prefer_vector_width): Added new enum

View File

@ -2885,6 +2885,11 @@ expand_builtin_strlen (tree exp, rtx target,
if (!maybe_expand_insn (icode, 4, ops))
return NULL_RTX;
/* Check to see if the argument was declared attribute nonstring
and if so, issue a warning since at this point it's not known
to be nul-terminated. */
maybe_warn_nonstring_arg (TREE_OPERAND (CALL_EXPR_FN (exp), 0), exp);
/* Now that we are assured of success, expand the source. */
start_sequence ();
pat = expand_expr (src, src_reg, Pmode, EXPAND_NORMAL);

View File

@ -1494,6 +1494,210 @@ maybe_warn_alloc_args_overflow (tree fn, tree exp, tree args[2], int idx[2])
}
}
/* If EXPR refers to a character array or pointer declared attribute
nonstring return a decl for that array or pointer and set *REF to
the referenced enclosing object or pointer. Otherwise returns
null. */
tree
get_attr_nonstring_decl (tree expr, tree *ref)
{
tree decl = expr;
if (TREE_CODE (decl) == SSA_NAME)
{
gimple *def = SSA_NAME_DEF_STMT (decl);
if (is_gimple_assign (def))
{
tree_code code = gimple_assign_rhs_code (def);
if (code == ADDR_EXPR
|| code == COMPONENT_REF
|| code == VAR_DECL)
decl = gimple_assign_rhs1 (def);
}
else if (tree var = SSA_NAME_VAR (decl))
decl = var;
}
if (TREE_CODE (decl) == ADDR_EXPR)
decl = TREE_OPERAND (decl, 0);
if (ref)
*ref = decl;
if (TREE_CODE (decl) == COMPONENT_REF)
decl = TREE_OPERAND (decl, 1);
if (DECL_P (decl)
&& lookup_attribute ("nonstring", DECL_ATTRIBUTES (decl)))
return decl;
return NULL_TREE;
}
/* Check the size argument to the strncmp built-in to see if it's within
the bounds of the arguments and if not, issue a warning. */
static void
warn_nonstring_bound (tree fndecl, tree call)
{
bool with_bounds = CALL_WITH_BOUNDS_P (call);
tree cnt = CALL_EXPR_ARG (call, with_bounds ? 4 : 2);
tree cntrange[2];
if (!get_size_range (cnt, cntrange))
return;
location_t callloc = EXPR_LOCATION (call);
for (unsigned i = 0; i != 2; ++i)
{
tree str = CALL_EXPR_ARG (call, i + 2 * with_bounds);
tree sref;
tree decl = get_attr_nonstring_decl (str, &sref);
if (!decl)
continue;
tree type = TREE_TYPE (decl);
if (TREE_CODE (type) != ARRAY_TYPE)
continue;
tree dom = TYPE_DOMAIN (type);
if (!dom)
continue;
tree bound = TYPE_MAX_VALUE (dom);
if (!bound)
continue;
bool warned = false;
if (tree_int_cst_le (bound, cntrange[0]))
warned = warning_at (callloc, OPT_Wstringop_truncation,
"%qD argument %i declared attribute %<nonstring%> "
"is smaller than the specified bound %E",
fndecl, i, cntrange[0]);
if (warned)
{
location_t loc = DECL_SOURCE_LOCATION (decl);
if (loc != UNKNOWN_LOCATION)
inform (loc, "argument %qD declared here", decl);
}
}
}
/* Warn about passing a non-string array/pointer to a function that
expects a nul-terminated string argument. */
void
maybe_warn_nonstring_arg (tree fndecl, tree exp)
{
if (!fndecl || DECL_BUILT_IN_CLASS (fndecl) != BUILT_IN_NORMAL)
return;
bool with_bounds = CALL_WITH_BOUNDS_P (exp);
/* The bound argument to a bounded string function like strncpy. */
tree bound = NULL_TREE;
/* It's safe to call "bounded" string functions with a non-string
argument since the functions provide an explicit bound for this
purpose. */
switch (DECL_FUNCTION_CODE (fndecl))
{
case BUILT_IN_STPNCPY:
case BUILT_IN_STPNCPY_CHK:
case BUILT_IN_STRNCMP:
case BUILT_IN_STRNCASECMP:
case BUILT_IN_STRNCPY:
case BUILT_IN_STRNCPY_CHK:
bound = CALL_EXPR_ARG (exp, with_bounds ? 4 : 2);
break;
case BUILT_IN_STRNDUP:
bound = CALL_EXPR_ARG (exp, with_bounds ? 2 : 1);
break;
default:
break;
}
/* Determine the range of the bound argument (if specified). */
tree bndrng[2] = { NULL_TREE, NULL_TREE };
if (bound)
get_size_range (bound, bndrng);
/* Iterate over the built-in function's formal arguments and check
each const char* against the actual argument. If the actual
argument is declared attribute non-string issue a warning unless
the argument's maximum length is bounded. */
function_args_iterator it;
function_args_iter_init (&it, TREE_TYPE (fndecl));
for (unsigned argno = 0; ; ++argno, function_args_iter_next (&it))
{
tree argtype = function_args_iter_cond (&it);
if (!argtype)
break;
if (TREE_CODE (argtype) != POINTER_TYPE)
continue;
argtype = TREE_TYPE (argtype);
if (TREE_CODE (argtype) != INTEGER_TYPE
|| !TYPE_READONLY (argtype))
continue;
argtype = TYPE_MAIN_VARIANT (argtype);
if (argtype != char_type_node)
continue;
tree callarg = CALL_EXPR_ARG (exp, argno);
if (TREE_CODE (callarg) == ADDR_EXPR)
callarg = TREE_OPERAND (callarg, 0);
/* See if the destination is declared with attribute "nonstring". */
tree decl = get_attr_nonstring_decl (callarg);
if (!decl)
continue;
tree type = TREE_TYPE (decl);
offset_int wibnd = 0;
if (bndrng[0])
wibnd = wi::to_offset (bndrng[0]);
offset_int asize = wibnd;
if (TREE_CODE (type) == ARRAY_TYPE)
if (tree arrbnd = TYPE_DOMAIN (type))
{
if ((arrbnd = TYPE_MAX_VALUE (arrbnd)))
asize = wi::to_offset (arrbnd) + 1;
}
location_t loc = EXPR_LOCATION (exp);
bool warned = false;
if (wi::ltu_p (asize, wibnd))
warned = warning_at (loc, OPT_Wstringop_overflow_,
"%qD argument %i declared attribute %<nonstring%> "
"is smaller than the specified bound %E",
fndecl, argno + 1, bndrng[0]);
else if (!bound)
warned = warning_at (loc, OPT_Wstringop_overflow_,
"%qD argument %i declared attribute %<nonstring%>",
fndecl, argno + 1);
if (warned)
inform (DECL_SOURCE_LOCATION (decl),
"argument %qD declared here", decl);
}
}
/* Issue an error if CALL_EXPR was flagged as requiring
tall-call optimization. */
@ -1943,6 +2147,10 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
alloc_size. */
maybe_warn_alloc_args_overflow (fndecl, exp, alloc_args, alloc_idx);
}
/* Detect passing non-string arguments to functions expecting
nul-terminated strings. */
maybe_warn_nonstring_arg (fndecl, exp);
}
/* Update ARGS_SIZE to contain the total size for the argument block.

View File

@ -39,5 +39,7 @@ extern bool reference_callee_copied (CUMULATIVE_ARGS *, machine_mode,
tree, bool);
extern void maybe_warn_alloc_args_overflow (tree, tree, tree[2], int[2]);
extern bool get_size_range (tree, tree[2]);
extern tree get_attr_nonstring_decl (tree, tree * = NULL);
extern void maybe_warn_nonstring_arg (tree, tree);
#endif // GCC_CALLS_H

View File

@ -6008,22 +6008,33 @@ types (@pxref{Common Function Attributes},
The @code{nonstring} variable attribute specifies that an object or member
declaration with type array of @code{char} or pointer to @code{char} is
intended to store character arrays that do not necessarily contain
a terminating @code{NUL} character. This is useful to avoid warnings
when such an array or pointer is used as an argument to a bounded string
manipulation function such as @code{strncpy}. For example, without the
attribute, GCC will issue a warning for the call below because it may
truncate the copy without appending the terminating NUL character. Using
the attribute makes it possible to suppress the warning.
a terminating @code{NUL} character. This is useful in detecting uses
of such arrays or pointers with functions that expect NUL-terminated
strings, and to avoid warnings when such an array or pointer is used
as an argument to a bounded string manipulation function such as
@code{strncpy}. For example, without the attribute, GCC will issue
a warning for the @code{strncpy} call below because it may truncate
the copy without appending the terminating @code{NUL} character. Using
the attribute makes it possible to suppress the warning. However, when
the array is declared with the attribute the call to @code{strlen} is
diagnosed because when the array doesn't contain a @code{NUL}-terminated
string the call is undefined. To copy, compare, of search non-string
character arrays use the @code{memcpy}, @code{memcmp}, @code{memchr},
and other functions that operate on arrays of bytes. In addition,
calling @code{strnlen} and @code{strndup} with such arrays is safe
provided a suitable bound is specified, and not diagnosed.
@smallexample
struct Data
@{
char name [32] __attribute__ ((nonstring));
@};
void f (struct Data *pd, const char *s)
int f (struct Data *pd, const char *s)
@{
strncpy (pd->name, s, sizeof pd->name);
@dots{}
return strlen (pd->name); // unsafe, gets a warning
@}
@end smallexample

View File

@ -62,6 +62,7 @@ along with GCC; see the file COPYING3. If not see
#include "asan.h"
#include "diagnostic-core.h"
#include "intl.h"
#include "calls.h"
/* Return true when DECL can be referenced from current unit.
FROM_DECL (if non-null) specify constructor of variable DECL was taken from.
@ -1558,25 +1559,31 @@ gimple_fold_builtin_strncpy (gimple_stmt_iterator *gsi,
{
gimple *stmt = gsi_stmt (*gsi);
location_t loc = gimple_location (stmt);
bool nonstring = get_attr_nonstring_decl (dest) != NULL_TREE;
/* If the LEN parameter is zero, return DEST. */
if (integer_zerop (len))
{
tree fndecl = gimple_call_fndecl (stmt);
gcall *call = as_a <gcall *> (stmt);
/* Avoid warning if the destination refers to a an array/pointer
decorate with attribute nonstring. */
if (!nonstring)
{
tree fndecl = gimple_call_fndecl (stmt);
gcall *call = as_a <gcall *> (stmt);
/* Warn about the lack of nul termination: the result is not
a (nul-terminated) string. */
tree slen = get_maxval_strlen (src, 0);
if (slen && !integer_zerop (slen))
warning_at (loc, OPT_Wstringop_truncation,
"%G%qD destination unchanged after copying no bytes "
"from a string of length %E",
call, fndecl, slen);
else
warning_at (loc, OPT_Wstringop_truncation,
"%G%qD destination unchanged after copying no bytes",
call, fndecl);
/* Warn about the lack of nul termination: the result is not
a (nul-terminated) string. */
tree slen = get_maxval_strlen (src, 0);
if (slen && !integer_zerop (slen))
warning_at (loc, OPT_Wstringop_truncation,
"%G%qD destination unchanged after copying no bytes "
"from a string of length %E",
call, fndecl, slen);
else
warning_at (loc, OPT_Wstringop_truncation,
"%G%qD destination unchanged after copying no bytes",
call, fndecl);
}
replace_call_with_value (gsi, dest);
return true;
@ -1601,53 +1608,36 @@ gimple_fold_builtin_strncpy (gimple_stmt_iterator *gsi,
if (tree_int_cst_lt (ssize, len))
return false;
if (tree_int_cst_lt (len, slen))
if (!nonstring)
{
tree fndecl = gimple_call_fndecl (stmt);
gcall *call = as_a <gcall *> (stmt);
warning_at (loc, OPT_Wstringop_truncation,
(tree_int_cst_equal (size_one_node, len)
? G_("%G%qD output truncated copying %E byte "
"from a string of length %E")
: G_("%G%qD output truncated copying %E bytes "
"from a string of length %E")),
call, fndecl, len, slen);
}
else if (tree_int_cst_equal (len, slen))
{
tree decl = dest;
if (TREE_CODE (decl) == SSA_NAME)
if (tree_int_cst_lt (len, slen))
{
gimple *def_stmt = SSA_NAME_DEF_STMT (decl);
if (is_gimple_assign (def_stmt))
{
tree_code code = gimple_assign_rhs_code (def_stmt);
if (code == ADDR_EXPR || code == VAR_DECL)
decl = gimple_assign_rhs1 (def_stmt);
}
tree fndecl = gimple_call_fndecl (stmt);
gcall *call = as_a <gcall *> (stmt);
warning_at (loc, OPT_Wstringop_truncation,
(tree_int_cst_equal (size_one_node, len)
? G_("%G%qD output truncated copying %E byte "
"from a string of length %E")
: G_("%G%qD output truncated copying %E bytes "
"from a string of length %E")),
call, fndecl, len, slen);
}
else if (tree_int_cst_equal (len, slen))
{
tree fndecl = gimple_call_fndecl (stmt);
gcall *call = as_a <gcall *> (stmt);
if (TREE_CODE (decl) == ADDR_EXPR)
decl = TREE_OPERAND (decl, 0);
if (TREE_CODE (decl) == COMPONENT_REF)
decl = TREE_OPERAND (decl, 1);
tree fndecl = gimple_call_fndecl (stmt);
gcall *call = as_a <gcall *> (stmt);
if (!DECL_P (decl)
|| !lookup_attribute ("nonstring", DECL_ATTRIBUTES (decl)))
warning_at (loc, OPT_Wstringop_truncation,
(tree_int_cst_equal (size_one_node, len)
? G_("%G%qD output truncated before terminating nul "
"copying %E byte from a string of the same "
"length")
: G_("%G%qD output truncated before terminating nul "
"copying %E bytes from a string of the same "
"length")),
call, fndecl, len);
warning_at (loc, OPT_Wstringop_truncation,
(tree_int_cst_equal (size_one_node, len)
? G_("%G%qD output truncated before terminating nul "
"copying %E byte from a string of the same "
"length")
: G_("%G%qD output truncated before terminating nul "
"copying %E bytes from a string of the same "
"length")),
call, fndecl, len);
}
}
/* OK transform into builtin memcpy. */

View File

@ -1,3 +1,11 @@
2017-11-21 Martin Sebor <msebor@redhat.com>
PR tree-optimization/82945
* c-c++-common/Wstringop-truncation-2.c: New test.
* c-c++-common/Wstringop-truncation.c: Adjust.
* c-c++-common/attr-nonstring-2.c: Adjust.
* c-c++-common/attr-nonstring-3.c: New test.
2017-11-21 Sergey Shalnov <Sergey.Shalnov@intel.com>
* g++.dg/ext/pr57362.C (__attribute__): Test

View File

@ -0,0 +1,105 @@
/* Verify that
{ dg-do compile }
{ dg-options "-O2 -Wstringop-truncation -Wno-stringop-overflow -ftrack-macro-expansion=0" } */
typedef __SIZE_TYPE__ size_t;
#define stpncpy(d, s, n) __builtin_stpncpy ((d), (s), (n))
#define strncpy(d, s, n) __builtin_stpncpy ((d), (s), (n))
void sink (void*);
struct A {
char arr[3] __attribute__ ((nonstring));
char str[3];
};
struct B { struct A a[3]; int i; };
struct C { struct B b[3]; int i; };
void stpncpy_arr_1 (struct C *pc, const char *s)
{
stpncpy (pc->b[0].a[0].arr, s, sizeof pc->b[0].a[0].arr);
sink (pc->b[0].a[0].arr);
stpncpy (pc->b[0].a[1].arr, s, sizeof pc->b[0].a[1].arr);
sink (pc->b[0].a[1].arr);
stpncpy (pc->b[0].a[2].arr, s, sizeof pc->b[0].a[2].arr);
sink (pc->b[0].a[2].arr);
stpncpy (pc->b[1].a[0].arr, s, sizeof pc->b[1].a[0].arr);
sink (pc->b[1].a[0].arr);
stpncpy (pc->b[1].a[1].arr, s, sizeof pc->b[1].a[1].arr);
sink (pc->b[1].a[1].arr);
stpncpy (pc->b[1].a[2].arr, s, sizeof pc->b[1].a[2].arr);
sink (pc->b[1].a[2].arr);
stpncpy (pc->b[2].a[0].arr, s, sizeof pc->b[2].a[0].arr);
sink (pc->b[2].a[0].arr);
stpncpy (pc->b[2].a[1].arr, s, sizeof pc->b[2].a[1].arr);
sink (pc->b[2].a[1].arr);
stpncpy (pc->b[2].a[2].arr, s, sizeof pc->b[2].a[2].arr);
sink (pc->b[2].a[2].arr);
}
void stpncpy_str_nowarn_1 (struct C *pc, const char *s)
{
stpncpy (pc->b[0].a[0].str, s, sizeof pc->b[0].a[0].str)[-1] = 0; /* { dg-bogus "\\\[-Wstringop-truncation" } */
}
void stpncpy_str_nowarn_2 (struct C *pc, const char *s)
{
*stpncpy (pc->b[0].a[0].str, s, sizeof pc->b[0].a[0].str - 1) = 0; /* { dg-bogus "\\\[-Wstringop-truncation" } */
}
void stpncpy_str_nowarn_3 (struct C *pc, const char *s)
{
char *d = stpncpy (pc->b[0].a[0].str, s, sizeof pc->b[0].a[0].str); /* { dg-bogus "\\\[-Wstringop-truncation" } */
d[-1] = 0;
}
void stpncpy_str_nowarn_4 (struct C *pc, const char *s)
{
char *d = stpncpy (pc->b[0].a[0].str, s, sizeof pc->b[0].a[0].str - 1); /* { dg-bogus "\\\[-Wstringop-truncation" } */
*d = 0;
}
void strncpy_arr_1 (struct C *pc, const char *s)
{
strncpy (pc->b[0].a[0].arr, s, sizeof pc->b[0].a[0].arr);
sink (pc->b[0].a[0].arr);
strncpy (pc->b[0].a[1].arr, s, sizeof pc->b[0].a[1].arr);
sink (pc->b[0].a[1].arr);
strncpy (pc->b[0].a[2].arr, s, sizeof pc->b[0].a[2].arr);
sink (pc->b[0].a[2].arr);
}
void strncpy_str_nowarn_1 (struct C *pc, const char *s)
{
strncpy (pc->b[0].a[0].str, s, sizeof pc->b[0].a[0].str); /* { dg-bogus "\\\[-Wstringop-truncation" } */
pc->b[0].a[0].str[sizeof pc->b[0].a[0].str - 1] = 0;
}
void strncpy_str_warn_1 (struct C *pc, const char *s)
{
strncpy (pc->b[0].a[0].str, s, sizeof pc->b[0].a[0].str); /* { dg-warning "specified bound 3 equals destination size" } */
pc->b[1].a[0].str[sizeof pc->b[0].a[0].str - 1] = 0;
}
void strncpy_str_warn_2 (struct C *pc, const char *s)
{
strncpy (pc->b[0].a[0].str, s, sizeof pc->b[0].a[0].str); /* { dg-warning "specified bound 3 equals destination size" } */
pc->b[0].a[1].str[sizeof pc->b[0].a[0].str - 1] = 0;
}

View File

@ -193,35 +193,35 @@ void test_strncpy_ptr (char *d, const char* s, const char *t, int i)
CPY (d, CHOOSE ("123", "12"), 1); /* { dg-warning ".strncpy\[^\n\r\]* output truncated copying 1 byte from a string of length 2" } */
{
signed char n = strlen (s); /* { dg-message "length computed here" } */
signed char n = strlen (s); /* { dg-message "length computed here" } */
CPY (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
}
{
short n = strlen (s); /* { dg-message "length computed here" } */
short n = strlen (s); /* { dg-message "length computed here" } */
CPY (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
}
{
int n = strlen (s); /* { dg-message "length computed here" } */
int n = strlen (s); /* { dg-message "length computed here" } */
CPY (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
}
{
unsigned n = strlen (s); /* { dg-message "length computed here" } */
unsigned n = strlen (s); /* { dg-message "length computed here" } */
CPY (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
}
{
size_t n;
n = strlen (s); /* { dg-message "length computed here" } */
n = strlen (s); /* { dg-message "length computed here" } */
CPY (d, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
}
{
size_t n;
char *dp2 = d + 1;
n = strlen (s); /* { dg-message "length computed here" } */
n = strlen (s); /* { dg-message "length computed here" } */
CPY (dp2, s, n); /* { dg-warning ".strncpy\[^\n\r\]* output truncated before terminating nul copying as many bytes from a string as its length" } */
}
@ -312,9 +312,11 @@ void test_strncpy_array (Dest *pd, int i, const char* s)
/* Exercise destination with attribute "nonstring". */
CPY (pd->c3ns, "", 3);
CPY (pd->c3ns, "", 1);
/* Truncation is still diagnosed -- using strncpy in this case is
pointless and should be replaced with memcpy. */
CPY (pd->c3ns, "12", 1); /* { dg-warning "output truncated copying 1 byte from a string of length 2" } */
/* It could be argued that truncation in the literal case should be
diagnosed even for non-strings. Using strncpy in this case is
pointless and should be replaced with memcpy. But it would likely
be viewed as a false positive. */
CPY (pd->c3ns, "12", 1);
CPY (pd->c3ns, "12", 2);
CPY (pd->c3ns, "12", 3);
CPY (pd->c3ns, "123", 3);

View File

@ -89,7 +89,7 @@ void test_pointer (const char *s, unsigned n)
strncpy (pns_1, "a", 1);
strncpy (pns_2, "ab", 2);
strncpy (pns_3, "abc", 3);
strncpy (pns_3, s7, 3); /* { dg-warning "output truncated copying 3 bytes from a string of length 7" } */
strncpy (pns_3, s7, 3);
strncpy (pns_1, s, 1);
strncpy (pns_2, s, 1);
@ -105,6 +105,7 @@ void test_member_array (struct MemArrays *p, const char *s, unsigned n)
{
const char s7[] = "1234567";
strncpy (p->ma3, "", 0);
strncpy (p->ma3, "a", 1);
strncpy (p->ma4, "ab", 2);
strncpy (p->ma5, "abc", 3);

View File

@ -0,0 +1,474 @@
/* Test to exercise warnings when an array declared with attribute "nonstring"
is passed to a function that expects a nul-terminated string as an argument.
{ dg-do compile }
{ dg-options "-O2 -Wattributes -Wstringop-overflow -ftrack-macro-expansion=0" } */
typedef __SIZE_TYPE__ size_t;
typedef __builtin_va_list va_list;
#if __cplusplus
extern "C" {
#endif
void* memchr (const void*, int, size_t);
int memcmp (const void*, const void*, size_t);
void* memcpy (void*, const void*, size_t);
void* memmove (void*, const void*, size_t);
int printf (const char*, ...);
int puts (const char*);
int puts_unlocked (const char*);
int sprintf (char*, const char*, ...);
int snprintf (char*, size_t, const char*, ...);
int vsprintf (char*, const char*, va_list);
int vsnprintf (char*, size_t, const char*, va_list);
int strcmp (const char*, const char*);
int strncmp (const char*, const char*, size_t);
char* stpcpy (char*, const char*);
char* stpncpy (char*, const char*, size_t);
char* strcat (char*, const char*);
char* strncat (char*, const char*, size_t);
char* strcpy (char*, const char*);
char* strncpy (char*, const char*, size_t);
char* strchr (const char*, int);
char* strdup (const char*);
size_t strlen (const char*);
size_t strnlen (const char*, size_t);
char* strndup (const char*, size_t);
#if __cplusplus
} /* extern "C" */
#endif
#define NONSTRING __attribute__ ((nonstring))
char str[4];
char arr[4] NONSTRING;
char *ptr;
char *parr NONSTRING;
struct MemArrays
{
char str[4];
char arr[4] NONSTRING;
char *parr NONSTRING;
};
void sink (int, ...);
#define T(call) sink (0, (call))
void test_printf (struct MemArrays *p)
{
T (printf (str));
T (printf (arr)); /* { dg-warning "argument 1 declared attribute .nonstring." } */
T (printf (ptr));
T (printf (parr)); /* { dg-warning "argument 1 declared attribute .nonstring." } */
T (printf (p->str));
T (printf (p->arr)); /* { dg-warning "argument 1 declared attribute .nonstring." } */
}
void test_puts (struct MemArrays *p)
{
T (puts (str));
T (puts (arr)); /* { dg-warning "argument 1 declared attribute .nonstring." } */
T (puts (ptr));
T (puts (parr)); /* { dg-warning "argument 1 declared attribute .nonstring." } */
T (puts (p->str));
T (puts (p->arr)); /* { dg-warning "argument 1 declared attribute .nonstring." } */
}
void test_snprintf (char *d, size_t n, struct MemArrays *p)
{
T (snprintf (d, n, str));
T (snprintf (d, n, arr)); /* { dg-warning "argument 3 declared attribute .nonstring." } */
T (snprintf (d, n, ptr));
T (snprintf (d, n, parr)); /* { dg-warning "argument 3 declared attribute .nonstring." } */
T (snprintf (d, n, p->str));
T (snprintf (d, n, p->arr)); /* { dg-warning "argument 3 declared attribute .nonstring." } */
}
void test_sprintf (char *d, struct MemArrays *p)
{
T (sprintf (d, str));
T (sprintf (d, arr)); /* { dg-warning "argument 2 declared attribute .nonstring." } */
T (sprintf (d, ptr));
T (sprintf (d, parr)); /* { dg-warning "argument 2 declared attribute .nonstring." } */
T (sprintf (d, p->str));
T (sprintf (d, p->arr)); /* { dg-warning "argument 2 declared attribute .nonstring." } */
}
void test_vsnprintf (char *d, size_t n, struct MemArrays *p, va_list va)
{
T (vsnprintf (d, n, str, va));
T (vsnprintf (d, n, arr, va)); /* { dg-warning "argument 3 declared attribute .nonstring." } */
T (vsnprintf (d, n, ptr, va));
T (vsnprintf (d, n, parr, va)); /* { dg-warning "argument 3 declared attribute .nonstring." } */
T (vsnprintf (d, n, p->str, va));
T (vsnprintf (d, n, p->arr, va)); /* { dg-warning "argument 3 declared attribute .nonstring." } */
}
void test_vsprintf (char *d, struct MemArrays *p, va_list va)
{
T (vsprintf (d, str, va));
T (vsprintf (d, arr, va)); /* { dg-warning "argument 2 declared attribute .nonstring." } */
T (vsprintf (d, ptr, va));
T (vsprintf (d, parr, va)); /* { dg-warning "argument 2 declared attribute .nonstring." } */
T (vsprintf (d, p->str, va));
T (vsprintf (d, p->arr, va)); /* { dg-warning "argument 2 declared attribute .nonstring." } */
}
void test_strcmp (struct MemArrays *p)
{
T (strcmp (str, str));
T (strcmp (str, arr)); /* { dg-warning "argument 2 declared attribute .nonstring." } */
T (strcmp (arr, str)); /* { dg-warning "argument 1 declared attribute .nonstring." } */
T (strcmp (str, ptr));
T (strcmp (str, parr)); /* { dg-warning "argument 2 declared attribute .nonstring." } */
T (strcmp (parr, str)); /* { dg-warning "argument 1 declared attribute .nonstring." } */
T (strcmp (p->str, p->arr)); /* { dg-warning "argument 2 declared attribute .nonstring." } */
T (strcmp (p->arr, p->str)); /* { dg-warning "argument 1 declared attribute .nonstring." } */
T (strcmp (p->parr, p->str)); /* { dg-warning "argument 1 declared attribute .nonstring." } */
}
void test_strncmp_warn (struct MemArrays *p)
{
enum { N = sizeof arr };
T (strncmp (str, arr, N));
T (strncmp (arr, str, N));
T (strncmp (str, arr, N + 1)); /* { dg-warning "argument 2 declared attribute .nonstring. is smaller than the specified bound 5" } */
T (strncmp (arr, str, N + 1)); /* { dg-warning "argument 1 declared attribute .nonstring. is smaller than the specified bound 5" } */
T (strncmp (str, parr, N + 1));
T (strncmp (parr, str, N + 1));
T (strncmp (p->str, p->arr, N));
T (strncmp (p->arr, p->str, N));
T (strncmp (p->parr, p->str, N));
T (strncmp (p->str, p->arr, N));
T (strncmp (p->arr, p->str, N));
T (strncmp (p->parr, p->str, N));
}
void test_strncmp_nowarn (struct MemArrays *p, size_t n)
{
T (strncmp (str, str, n));
T (strncmp (str, arr, n));
T (strncmp (arr, str, n));
T (strncmp (str, ptr, n));
T (strncmp (str, parr, n));
T (strncmp (parr, str, n));
T (strncmp (p->str, p->arr, n));
T (strncmp (p->arr, p->str, n));
T (strncmp (p->parr, p->str, n));
}
void test_stpcpy (struct MemArrays *p)
{
T (stpcpy (str, str));
T (stpcpy (str, arr)); /* { dg-warning "argument 2 declared attribute .nonstring." } */
T (stpcpy (arr, str));
T (stpcpy (str, ptr));
T (stpcpy (str, parr)); /* { dg-warning "argument 2 declared attribute .nonstring." } */
T (stpcpy (parr, str));
T (stpcpy (p->str, p->arr)); /* { dg-warning "argument 2 declared attribute .nonstring." } */
T (stpcpy (p->arr, p->str));
T (stpcpy (p->parr, p->str));
}
void test_stpncpy_nowarn (struct MemArrays *p, unsigned n)
{
T (stpncpy (str, str, n));
T (stpncpy (str, arr, n));
T (stpncpy (arr, str, n));
T (stpncpy (str, ptr, n));
T (stpncpy (str, parr, n));
T (stpncpy (parr, str, n));
T (stpncpy (p->str, p->arr, n));
T (stpncpy (p->arr, p->str, n));
T (stpncpy (p->parr, p->str, n));
}
void test_stpncpy_warn (struct MemArrays *p, unsigned n)
{
enum { N = sizeof arr };
T (stpncpy (str, str, N));
T (stpncpy (str, arr, N));
T (stpncpy (arr, str, N));
T (stpncpy (str, ptr, N));
T (stpncpy (str, parr, N));
T (stpncpy (parr, str, N));
T (stpncpy (p->str, p->arr, N));
T (stpncpy (p->arr, p->str, N));
T (stpncpy (p->parr, p->str, N));
T (stpncpy (ptr, str, N + 1));
T (stpncpy (ptr, arr, N + 1)); /* { dg-warning "argument 2 declared attribute .nonstring. is smaller than the specified bound 5" } */
T (stpncpy (arr, str, N + 1)); /* { dg-warning "writing 5 bytes into a region of size 4 overflows " } */
T (stpncpy (ptr, ptr, N + 1));
T (stpncpy (ptr, parr, N + 1));
T (stpncpy (parr, str, N + 1));
T (stpncpy (ptr, p->arr, N + 1)); /* { dg-warning "argument 2 declared attribute .nonstring. is smaller" } */
T (stpncpy (p->arr, p->str, N + 1)); /* { dg-warning "writing 5 bytes into a region of size 4 overflows" } */
T (stpncpy (p->parr, p->str, N + 1));
}
void test_strcat (struct MemArrays *p)
{
T (strcat (str, str));
T (strcat (str, arr)); /* { dg-warning "argument 2 declared attribute .nonstring." } */
T (strcat (arr, str));
T (strcat (str, ptr));
T (strcat (str, parr)); /* { dg-warning "argument 2 declared attribute .nonstring." } */
T (strcat (parr, str));
T (strcat (p->str, p->arr)); /* { dg-warning "argument 2 declared attribute .nonstring." } */
T (strcat (p->arr, p->str));
T (strcat (p->parr, p->str));
}
void test_strncat (struct MemArrays *p, unsigned n)
{
T (strncat (str, str, n));
T (strncat (str, arr, n)); /* { dg-warning "argument 2 declared attribute .nonstring." } */
T (strncat (arr, str, n));
T (strncat (str, ptr, n));
T (strncat (str, parr, n)); /* { dg-warning "argument 2 declared attribute .nonstring." } */
T (strncat (parr, str, n));
T (strncat (p->str, p->arr, n)); /* { dg-warning "argument 2 declared attribute .nonstring." } */
T (strncat (p->arr, p->str, n));
T (strncat (p->parr, p->str, n));
}
void test_strcpy (struct MemArrays *p)
{
T (strcpy (str, str));
T (strcpy (str, arr)); /* { dg-warning "argument 2 declared attribute .nonstring." } */
T (strcpy (arr, str));
T (strcpy (str, ptr));
T (strcpy (str, parr)); /* { dg-warning "argument 2 declared attribute .nonstring." } */
T (strcpy (parr, str));
T (strcpy (p->str, p->arr)); /* { dg-warning "argument 2 declared attribute .nonstring." } */
T (strcpy (p->arr, p->str));
T (strcpy (p->parr, p->str));
}
void test_strncpy (struct MemArrays *p, unsigned n)
{
T (strncpy (str, str, n));
T (strncpy (str, arr, n));
T (strncpy (arr, str, n));
T (strncpy (str, ptr, n));
T (strncpy (str, parr, n));
T (strncpy (parr, str, n));
T (strncpy (p->str, p->arr, n));
T (strncpy (p->arr, p->str, n));
T (strncpy (p->parr, p->str, n));
}
void test_strchr (struct MemArrays *p, int c)
{
T (strchr (str, c));
T (strchr (arr, c)); /* { dg-warning "argument 1 declared attribute .nonstring." } */
T (strchr (ptr, c));
T (strchr (parr, c)); /* { dg-warning "argument 1 declared attribute .nonstring." } */
T (strchr (p->str, c));
T (strchr (p->arr, c)); /* { dg-warning "argument 1 declared attribute .nonstring." } */
}
void test_strdup (struct MemArrays *p)
{
T (strdup (str));
T (strdup (arr)); /* { dg-warning "argument 1 declared attribute .nonstring." } */
T (strdup (ptr));
T (strdup (parr)); /* { dg-warning "argument 1 declared attribute .nonstring." } */
T (strdup (p->str));
T (strdup (p->arr)); /* { dg-warning "argument 1 declared attribute .nonstring." } */
}
void test_stnrdup_nowarn (struct MemArrays *p, size_t n)
{
T (strndup (str, n));
T (strndup (arr, n));
T (strndup (ptr, n));
T (strndup (parr, n));
T (strndup (p->str, n));
T (strndup (p->arr, n));
}
void test_stnrdup_warn (struct MemArrays *p)
{
enum { N = sizeof arr };
T (strndup (str, N));
T (strndup (arr, N));
T (strndup (ptr, N));
T (strndup (parr, N));
T (strndup (p->str, N));
T (strndup (p->arr, N));
T (strndup (arr, N + 1)); /* { dg-warning "argument 1 declared attribute .nonstring. is smaller than the specified bound 5" } */
T (strndup (parr, N + 1));
T (strndup (p->arr, N + 1)); /* { dg-warning "argument 1 declared attribute .nonstring. is smaller than the specified bound 5" } */
T (strndup (p->parr, N + 1));
}
void test_strlen (struct MemArrays *p, char *s NONSTRING, size_t n)
{
T (strlen (str));
T (strlen (arr)); /* { dg-warning "argument 1 declared attribute .nonstring." } */
T (strlen (ptr));
T (strlen (parr)); /* { dg-warning "argument 1 declared attribute .nonstring." } */
T (strlen (p->str));
T (strlen (p->arr)); /* { dg-warning "argument 1 declared attribute .nonstring." } */
T (strlen (s)); /* { dg-warning "argument 1 declared attribute .nonstring." } */
{
strcpy (s, "123");
T (strlen (s));
}
{
char a[] __attribute__ ((nonstring)) = { 1, 2, 3 };
T (strlen (a)); /* { dg-warning "argument 1 declared attribute .nonstring." } */
}
{
char a[] __attribute__ ((nonstring)) = { 1, 2, 3, 4 };
strcpy (a, "12");
T (strlen (a));
}
{
char *p __attribute__ ((nonstring));
p = (char *)__builtin_malloc (n);
__builtin_memset (p, '*', n);
T (strlen (p)); /* { dg-warning "argument 1 declared attribute .nonstring." } */
strcpy (p, "12345");
T (strlen (p));
}
}
void test_strnlen (struct MemArrays *p, size_t n)
{
T (strnlen (str, n));
T (strnlen (arr, n));
T (strnlen (ptr, n));
T (strnlen (parr, n));
T (strnlen (p->str, n));
T (strnlen (p->arr, n));
}
/* Verify no warnings are issued for raw mempory functions. */
void test_mem_functions (struct MemArrays *p, int c, size_t n)
{
T (memchr (arr, c, n));
T (memchr (parr, c, n));
T (memchr (p->arr, c, n));
T (memchr (p->parr, c, n));
T (memcmp (str, arr, n));
T (memcmp (arr, str, n));
T (memcmp (str, parr, n));
T (memcmp (parr, str, n));
T (memcmp (p->str, p->arr, n));
T (memcmp (p->arr, p->str, n));
T (memcmp (p->parr, p->str, n));
T (memcpy (str, arr, n));
T (memcpy (arr, str, n));
T (memcpy (str, parr, n));
T (memcpy (parr, str, n));
T (memcpy (p->str, p->arr, n));
T (memcpy (p->arr, p->str, n));
T (memcpy (p->parr, p->str, n));
T (memmove (str, arr, n));
T (memmove (arr, str, n));
T (memmove (str, parr, n));
T (memmove (parr, str, n));
T (memmove (p->str, p->arr, n));
T (memmove (p->arr, p->str, n));
T (memmove (p->parr, p->str, n));
}

View File

@ -51,6 +51,7 @@ along with GCC; see the file COPYING3. If not see
#include "diagnostic.h"
#include "intl.h"
#include "attribs.h"
#include "calls.h"
/* A vector indexed by SSA_NAME_VERSION. 0 means unknown, positive value
is an index into strinfo vector, negative value stands for
@ -152,8 +153,11 @@ struct decl_stridxlist_map
mappings. */
static hash_map<tree_decl_hash, stridxlist> *decl_to_stridxlist_htab;
/* Hash table mapping strlen calls to stridx instances describing
the calls' arguments. Non-null only when warn_stringop_truncation
is non-zero. */
typedef std::pair<int, location_t> stridx_strlenloc;
static hash_map<tree, stridx_strlenloc> strlen_to_stridx;
static hash_map<tree, stridx_strlenloc> *strlen_to_stridx;
/* Obstack for struct stridxlist and struct decl_stridxlist_map. */
static struct obstack stridx_obstack;
@ -1207,8 +1211,11 @@ handle_builtin_strlen (gimple_stmt_iterator *gsi)
gcc_assert (si->full_string_p);
}
location_t loc = gimple_location (stmt);
strlen_to_stridx.put (lhs, stridx_strlenloc (idx, loc));
if (strlen_to_stridx)
{
location_t loc = gimple_location (stmt);
strlen_to_stridx->put (lhs, stridx_strlenloc (idx, loc));
}
return;
}
}
@ -1253,8 +1260,11 @@ handle_builtin_strlen (gimple_stmt_iterator *gsi)
set_strinfo (idx, si);
find_equal_ptrs (src, idx);
location_t loc = gimple_location (stmt);
strlen_to_stridx.put (lhs, stridx_strlenloc (idx, loc));
if (strlen_to_stridx)
{
location_t loc = gimple_location (stmt);
strlen_to_stridx->put (lhs, stridx_strlenloc (idx, loc));
}
}
}
@ -1691,9 +1701,6 @@ is_strlen_related_p (tree src, tree len)
static bool
maybe_diag_stxncpy_trunc (gimple_stmt_iterator gsi, tree src, tree cnt)
{
if (!warn_stringop_truncation)
return false;
gimple *stmt = gsi_stmt (gsi);
wide_int cntrange[2];
@ -1733,35 +1740,15 @@ maybe_diag_stxncpy_trunc (gimple_stmt_iterator gsi, tree src, tree cnt)
return false;
tree dst = gimple_call_arg (stmt, 0);
/* See if the destination is declared with attribute "nonstring"
and if so, avoid the truncation warning. */
if (TREE_CODE (dst) == SSA_NAME)
{
if (SSA_NAME_IS_DEFAULT_DEF (dst))
dst = SSA_NAME_VAR (dst);
else
{
gimple *def = SSA_NAME_DEF_STMT (dst);
if (is_gimple_assign (def)
&& gimple_assign_rhs_code (def) == ADDR_EXPR)
dst = gimple_assign_rhs1 (def);
}
}
tree dstdecl = dst;
if (TREE_CODE (dstdecl) == ADDR_EXPR)
dstdecl = TREE_OPERAND (dstdecl, 0);
{
tree d = dstdecl;
if (TREE_CODE (d) == COMPONENT_REF)
d = TREE_OPERAND (d, 1);
if (DECL_P (d) && lookup_attribute ("nonstring", DECL_ATTRIBUTES (d)))
return false;
}
/* If the destination refers to a an array/pointer declared nonstring
return early. */
tree ref = NULL_TREE;
if (get_attr_nonstring_decl (dstdecl, &ref))
return false;
/* Look for dst[i] = '\0'; after the stxncpy() call and if found
avoid the truncation warning. */
@ -1770,12 +1757,32 @@ maybe_diag_stxncpy_trunc (gimple_stmt_iterator gsi, tree src, tree cnt)
if (!gsi_end_p (gsi) && is_gimple_assign (next_stmt))
{
HOST_WIDE_INT off;
dstdecl = get_addr_base_and_unit_offset (dstdecl, &off);
tree lhs = gimple_assign_lhs (next_stmt);
tree lhsbase = get_addr_base_and_unit_offset (lhs, &off);
if (lhsbase && operand_equal_p (dstdecl, lhsbase, 0))
tree_code code = TREE_CODE (lhs);
if (code == ARRAY_REF || code == MEM_REF)
lhs = TREE_OPERAND (lhs, 0);
tree func = gimple_call_fndecl (stmt);
if (DECL_FUNCTION_CODE (func) == BUILT_IN_STPNCPY)
{
tree ret = gimple_call_lhs (stmt);
if (ret && operand_equal_p (ret, lhs, 0))
return false;
}
/* Determine the base address and offset of the reference,
ignoring the innermost array index. */
if (TREE_CODE (ref) == ARRAY_REF)
ref = TREE_OPERAND (ref, 0);
HOST_WIDE_INT dstoff;
tree dstbase = get_addr_base_and_unit_offset (ref, &dstoff);
HOST_WIDE_INT lhsoff;
tree lhsbase = get_addr_base_and_unit_offset (lhs, &lhsoff);
if (lhsbase
&& dstoff == lhsoff
&& operand_equal_p (dstbase, lhsbase, 0))
return false;
}
@ -1909,6 +1916,9 @@ maybe_diag_stxncpy_trunc (gimple_stmt_iterator gsi, tree src, tree cnt)
static void
handle_builtin_stxncpy (built_in_function, gimple_stmt_iterator *gsi)
{
if (!strlen_to_stridx)
return;
gimple *stmt = gsi_stmt (*gsi);
bool with_bounds = gimple_call_with_bounds_p (stmt);
@ -1919,7 +1929,7 @@ handle_builtin_stxncpy (built_in_function, gimple_stmt_iterator *gsi)
/* If the length argument was computed from strlen(S) for some string
S retrieve the strinfo index for the string (PSS->FIRST) alonng with
the location of the strlen() call (PSS->SECOND). */
stridx_strlenloc *pss = strlen_to_stridx.get (len);
stridx_strlenloc *pss = strlen_to_stridx->get (len);
if (!pss || pss->first <= 0)
{
if (maybe_diag_stxncpy_trunc (*gsi, src, len))
@ -1953,13 +1963,13 @@ handle_builtin_stxncpy (built_in_function, gimple_stmt_iterator *gsi)
whether its value is known. Otherwise, issue the more generic
-Wstringop-overflow which triggers for LEN arguments that in
any meaningful way depend on strlen(SRC). */
if (warn_stringop_truncation
&& sisrc == silen
&& is_strlen_related_p (src, len))
warned = warning_at (callloc, OPT_Wstringop_truncation,
"%qD output truncated before terminating nul "
"copying as many bytes from a string as its length",
func);
if (sisrc == silen
&& is_strlen_related_p (src, len)
&& warning_at (callloc, OPT_Wstringop_truncation,
"%qD output truncated before terminating nul "
"copying as many bytes from a string as its length",
func))
warned = true;
else if (silen && is_strlen_related_p (src, silen->ptr))
warned = warning_at (callloc, OPT_Wstringop_overflow_,
"%qD specified bound depends on the length "
@ -2966,9 +2976,12 @@ strlen_optimize_stmt (gimple_stmt_iterator *gsi)
fold_strstr_to_strncmp (gimple_assign_rhs1 (stmt),
gimple_assign_rhs2 (stmt), stmt);
tree rhs1 = gimple_assign_rhs1 (stmt);
if (stridx_strlenloc *ps = strlen_to_stridx.get (rhs1))
strlen_to_stridx.put (lhs, stridx_strlenloc (*ps));
if (strlen_to_stridx)
{
tree rhs1 = gimple_assign_rhs1 (stmt);
if (stridx_strlenloc *ps = strlen_to_stridx->get (rhs1))
strlen_to_stridx->put (lhs, stridx_strlenloc (*ps));
}
}
else if (TREE_CODE (lhs) != SSA_NAME && !TREE_SIDE_EFFECTS (lhs))
{
@ -3199,6 +3212,10 @@ public:
unsigned int
pass_strlen::execute (function *fun)
{
gcc_assert (!strlen_to_stridx);
if (warn_stringop_overflow || warn_stringop_truncation)
strlen_to_stridx = new hash_map<tree, stridx_strlenloc> ();
ssa_ver_to_stridx.safe_grow_cleared (num_ssa_names);
max_stridx = 1;
@ -3220,7 +3237,12 @@ pass_strlen::execute (function *fun)
laststmt.len = NULL_TREE;
laststmt.stridx = 0;
strlen_to_stridx.empty ();
if (strlen_to_stridx)
{
strlen_to_stridx->empty ();
delete strlen_to_stridx;
strlen_to_stridx = NULL;
}
return 0;
}