From b5338fb359ea3480d6ed37bbc52fe2df49b82fb9 Mon Sep 17 00:00:00 2001 From: Martin Sebor Date: Fri, 22 Nov 2019 16:47:22 +0000 Subject: [PATCH] PR middle-end/88226 - missing warning on fprintf, fputs, and puts with an unterminated array gcc/ChangeLog: PR middle-end/88226 * builtins.c (check_nul_terminated_array): New function. (fold_builtin_0): Remove declaration. (fold_builtin_1): Same. (fold_builtin_2): Same. (fold_builtin_3): Same. (fold_builtin_strpbrk): Add argument. (fold_builtin_strspn): Same. (fold_builtin_strcspn): Same. (expand_builtin_strcat): Call it. Remove unused argument. (expand_builtin_stpncpy): Same. (expand_builtin_strncat): Same. (expand_builtin_strncpy): Same. Adjust indentation. (expand_builtin_strcmp): Same. (expand_builtin_strncmp): Same. (expand_builtin_fork_or_exec): Same. (expand_builtin): Handle more built-ins. (fold_builtin_2): Add argument. (fold_builtin_n): Make static. Add argument. (fold_call_expr): Pass new argument to fold_builtin_n and fold_builtin_2. (fold_builtin_call_array): Pass new argument to fold_builtin_n. (fold_builtin_strpbrk): Add argument. Call check_nul_terminated_array. (fold_call_stmt): Pass new argument to fold_builtin_n. * builtins.h: Correct a comment. * gimple-fold.c (gimple_fold_builtin_strchr): Call check_nul_terminated_array. * tree-ssa-strlen.c (handle_builtin_strlen): Call check_nul_terminated_array. (handle_builtin_strchr): Same. (handle_builtin_string_cmp): Same. gcc/testsuite/ChangeLog: PR middle-end/88226 * gcc.dg/Wstringop-overflow-22.c: New test. * gcc.dg/tree-ssa/builtin-fprintf-warn-1.c: Remove xfails. From-SVN: r278623 --- gcc/ChangeLog | 33 ++ gcc/builtins.c | 420 ++++++++++++------ gcc/builtins.h | 4 +- gcc/gimple-fold.c | 18 +- gcc/testsuite/ChangeLog | 6 + gcc/testsuite/gcc.dg/Wstringop-overflow-22.c | 263 +++++++++++ .../gcc.dg/tree-ssa/builtin-fprintf-warn-1.c | 6 +- gcc/tree-ssa-strlen.c | 22 +- 8 files changed, 612 insertions(+), 160 deletions(-) create mode 100644 gcc/testsuite/gcc.dg/Wstringop-overflow-22.c diff --git a/gcc/ChangeLog b/gcc/ChangeLog index 443d64d59f3..f042116fa4d 100644 --- a/gcc/ChangeLog +++ b/gcc/ChangeLog @@ -6,6 +6,39 @@ (LDS_SIZE): Redefine using above. (gcn_expand_prologue): Initialize m0 with LDS_SIZE-1. +2019-11-22 Martin Sebor + + PR middle-end/88226 + * builtins.c (check_nul_terminated_array): New function. + (fold_builtin_0): Remove declaration. + (fold_builtin_1): Same. + (fold_builtin_2): Same. + (fold_builtin_3): Same. + (fold_builtin_strpbrk): Add argument. + (fold_builtin_strspn): Same. + (fold_builtin_strcspn): Same. + (expand_builtin_strcat): Call it. Remove unused argument. + (expand_builtin_stpncpy): Same. + (expand_builtin_strncat): Same. + (expand_builtin_strncpy): Same. Adjust indentation. + (expand_builtin_strcmp): Same. + (expand_builtin_strncmp): Same. + (expand_builtin_fork_or_exec): Same. + (expand_builtin): Handle more built-ins. + (fold_builtin_2): Add argument. + (fold_builtin_n): Make static. Add argument. + (fold_call_expr): Pass new argument to fold_builtin_n and fold_builtin_2. + (fold_builtin_call_array): Pass new argument to fold_builtin_n. + (fold_builtin_strpbrk): Add argument. Call check_nul_terminated_array. + (fold_call_stmt): Pass new argument to fold_builtin_n. + * builtins.h: Correct a comment. + * gimple-fold.c (gimple_fold_builtin_strchr): Call + check_nul_terminated_array. + * tree-ssa-strlen.c (handle_builtin_strlen): Call + check_nul_terminated_array. + (handle_builtin_strchr): Same. + (handle_builtin_string_cmp): Same. + 2019-11-22 Martin Sebor PR tree-optimization/92501 diff --git a/gcc/builtins.c b/gcc/builtins.c index 50909af91f9..f8cd4b44fa8 100644 --- a/gcc/builtins.c +++ b/gcc/builtins.c @@ -131,7 +131,7 @@ static rtx expand_builtin_memory_copy_args (tree dest, tree src, tree len, static rtx expand_builtin_memmove (tree, rtx); static rtx expand_builtin_mempcpy (tree, rtx); static rtx expand_builtin_mempcpy_args (tree, tree, tree, rtx, tree, memop_ret); -static rtx expand_builtin_strcat (tree, rtx); +static rtx expand_builtin_strcat (tree); static rtx expand_builtin_strcpy (tree, rtx); static rtx expand_builtin_strcpy_args (tree, tree, tree, rtx); static rtx expand_builtin_stpcpy (tree, rtx, machine_mode); @@ -166,15 +166,11 @@ static tree fold_builtin_fabs (location_t, tree, tree); static tree fold_builtin_abs (location_t, tree, tree); static tree fold_builtin_unordered_cmp (location_t, tree, tree, tree, enum tree_code, enum tree_code); -static tree fold_builtin_0 (location_t, tree); -static tree fold_builtin_1 (location_t, tree, tree); -static tree fold_builtin_2 (location_t, tree, tree, tree); -static tree fold_builtin_3 (location_t, tree, tree, tree, tree); static tree fold_builtin_varargs (location_t, tree, tree*, int); -static tree fold_builtin_strpbrk (location_t, tree, tree, tree); -static tree fold_builtin_strspn (location_t, tree, tree); -static tree fold_builtin_strcspn (location_t, tree, tree); +static tree fold_builtin_strpbrk (location_t, tree, tree, tree, tree); +static tree fold_builtin_strspn (location_t, tree, tree, tree); +static tree fold_builtin_strcspn (location_t, tree, tree, tree); static rtx expand_builtin_object_size (tree); static rtx expand_builtin_memory_chk (tree, rtx, machine_mode, @@ -564,6 +560,51 @@ warn_string_no_nul (location_t loc, const char *fn, tree arg, tree decl) } } +/* For a call EXPR (which may be null) that expects a string argument + and SRC as the argument, returns false if SRC is a character array + with no terminating NUL. When nonnull, BOUND is the number of + characters in which to expect the terminating NUL. + When EXPR is nonnull also issues a warning. */ + +bool +check_nul_terminated_array (tree expr, tree src, tree bound /* = NULL_TREE */) +{ + tree size; + bool exact; + tree nonstr = unterminated_array (src, &size, &exact); + if (!nonstr) + return true; + + /* NONSTR refers to the non-nul terminated constant array and SIZE + is the constant size of the array in bytes. EXACT is true when + SIZE is exact. */ + + if (bound) + { + wide_int min, max; + if (TREE_CODE (bound) == INTEGER_CST) + min = max = wi::to_wide (bound); + else + { + value_range_kind rng = get_range_info (bound, &min, &max); + if (rng != VR_RANGE) + return true; + } + + if (wi::leu_p (min, wi::to_wide (size))) + return true; + } + + if (expr && !TREE_NO_WARNING (expr)) + { + tree fndecl = get_callee_fndecl (expr); + const char *fname = IDENTIFIER_POINTER (DECL_NAME (fndecl)); + warn_string_no_nul (EXPR_LOCATION (expr), fname, src, nonstr); + } + + return false; +} + /* If EXP refers to an unterminated constant character array return the declaration of the object of which the array is a member or element and if SIZE is not null, set *SIZE to the size of @@ -4024,7 +4065,7 @@ expand_movstr (tree dest, tree src, rtx target, memop_ret retmode) to the library function. */ static rtx -expand_builtin_strcat (tree exp, rtx) +expand_builtin_strcat (tree exp) { if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE) || !warn_stringop_overflow) @@ -4033,6 +4074,10 @@ expand_builtin_strcat (tree exp, rtx) tree dest = CALL_EXPR_ARG (exp, 0); tree src = CALL_EXPR_ARG (exp, 1); + /* Detect unterminated source (only). */ + if (!check_nul_terminated_array (exp, src)) + return NULL_RTX; + /* There is no way here to determine the length of the string in the destination to which the SRC string is being appended so just diagnose cases when the souce string is longer than @@ -4228,6 +4273,8 @@ expand_builtin_stpncpy (tree exp, rtx) /* The exact number of bytes to write (not the maximum). */ tree len = CALL_EXPR_ARG (exp, 2); + if (!check_nul_terminated_array (exp, src, len)) + return NULL_RTX; /* The size of the destination object. */ tree destsize = compute_objsize (dest, warn_stringop_overflow - 1); @@ -4330,6 +4377,11 @@ expand_builtin_strncat (tree exp, rtx) tree src = CALL_EXPR_ARG (exp, 1); /* The upper bound on the number of bytes to write. */ tree maxread = CALL_EXPR_ARG (exp, 2); + + /* Detect unterminated source (only). */ + if (!check_nul_terminated_array (exp, src, maxread)) + return NULL_RTX; + /* The length of the source sequence. */ tree slen = c_strlen (src, 1); @@ -4391,59 +4443,63 @@ expand_builtin_strncpy (tree exp, rtx target) { location_t loc = EXPR_LOCATION (exp); - if (validate_arglist (exp, - POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) - { - tree dest = CALL_EXPR_ARG (exp, 0); - tree src = CALL_EXPR_ARG (exp, 1); - /* The number of bytes to write (not the maximum). */ - tree len = CALL_EXPR_ARG (exp, 2); - /* The length of the source sequence. */ - tree slen = c_strlen (src, 1); + if (!validate_arglist (exp, + POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) + return NULL_RTX; + tree dest = CALL_EXPR_ARG (exp, 0); + tree src = CALL_EXPR_ARG (exp, 1); + /* The number of bytes to write (not the maximum). */ + tree len = CALL_EXPR_ARG (exp, 2); - if (warn_stringop_overflow) - { - tree destsize = compute_objsize (dest, - warn_stringop_overflow - 1); + if (!check_nul_terminated_array (exp, src, len)) + return NULL_RTX; - /* The number of bytes to write is LEN but check_access will also - check SLEN if LEN's value isn't known. */ - check_access (exp, dest, src, len, /*maxread=*/NULL_TREE, src, - destsize); - } + /* The length of the source sequence. */ + tree slen = c_strlen (src, 1); - /* We must be passed a constant len and src parameter. */ - if (!tree_fits_uhwi_p (len) || !slen || !tree_fits_uhwi_p (slen)) - return NULL_RTX; + if (warn_stringop_overflow) + { + tree destsize = compute_objsize (dest, + warn_stringop_overflow - 1); - slen = size_binop_loc (loc, PLUS_EXPR, slen, ssize_int (1)); + /* The number of bytes to write is LEN but check_access will also + check SLEN if LEN's value isn't known. */ + check_access (exp, dest, src, len, /*maxread=*/NULL_TREE, src, + destsize); + } - /* We're required to pad with trailing zeros if the requested - len is greater than strlen(s2)+1. In that case try to - use store_by_pieces, if it fails, punt. */ - if (tree_int_cst_lt (slen, len)) - { - unsigned int dest_align = get_pointer_alignment (dest); - const char *p = c_getstr (src); - rtx dest_mem; - - if (!p || dest_align == 0 || !tree_fits_uhwi_p (len) - || !can_store_by_pieces (tree_to_uhwi (len), - builtin_strncpy_read_str, - CONST_CAST (char *, p), - dest_align, false)) - return NULL_RTX; + /* We must be passed a constant len and src parameter. */ + if (!tree_fits_uhwi_p (len) || !slen || !tree_fits_uhwi_p (slen)) + return NULL_RTX; - dest_mem = get_memory_rtx (dest, len); - store_by_pieces (dest_mem, tree_to_uhwi (len), - builtin_strncpy_read_str, - CONST_CAST (char *, p), dest_align, false, - RETURN_BEGIN); - dest_mem = force_operand (XEXP (dest_mem, 0), target); - dest_mem = convert_memory_address (ptr_mode, dest_mem); - return dest_mem; - } + slen = size_binop_loc (loc, PLUS_EXPR, slen, ssize_int (1)); + + /* We're required to pad with trailing zeros if the requested + len is greater than strlen(s2)+1. In that case try to + use store_by_pieces, if it fails, punt. */ + if (tree_int_cst_lt (slen, len)) + { + unsigned int dest_align = get_pointer_alignment (dest); + const char *p = c_getstr (src); + rtx dest_mem; + + if (!p || dest_align == 0 || !tree_fits_uhwi_p (len) + || !can_store_by_pieces (tree_to_uhwi (len), + builtin_strncpy_read_str, + CONST_CAST (char *, p), + dest_align, false)) + return NULL_RTX; + + dest_mem = get_memory_rtx (dest, len); + store_by_pieces (dest_mem, tree_to_uhwi (len), + builtin_strncpy_read_str, + CONST_CAST (char *, p), dest_align, false, + RETURN_BEGIN); + dest_mem = force_operand (XEXP (dest_mem, 0), target); + dest_mem = convert_memory_address (ptr_mode, dest_mem); + return dest_mem; } + return NULL_RTX; } @@ -4822,6 +4878,13 @@ expand_builtin_strcmp (tree exp, ATTRIBUTE_UNUSED rtx target) if (!validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE)) return NULL_RTX; + tree arg1 = CALL_EXPR_ARG (exp, 0); + tree arg2 = CALL_EXPR_ARG (exp, 1); + + if (!check_nul_terminated_array (exp, arg1) + || !check_nul_terminated_array (exp, arg2)) + return NULL_RTX; + /* Due to the performance benefit, always inline the calls first. */ rtx result = NULL_RTX; result = inline_expand_builtin_string_cmp (exp, target); @@ -4833,9 +4896,6 @@ expand_builtin_strcmp (tree exp, ATTRIBUTE_UNUSED rtx target) if (cmpstr_icode == CODE_FOR_nothing && cmpstrn_icode == CODE_FOR_nothing) return NULL_RTX; - tree arg1 = CALL_EXPR_ARG (exp, 0); - tree arg2 = CALL_EXPR_ARG (exp, 1); - unsigned int arg1_align = get_pointer_alignment (arg1) / BITS_PER_UNIT; unsigned int arg2_align = get_pointer_alignment (arg2) / BITS_PER_UNIT; @@ -4941,6 +5001,14 @@ expand_builtin_strncmp (tree exp, ATTRIBUTE_UNUSED rtx target, POINTER_TYPE, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) return NULL_RTX; + tree arg1 = CALL_EXPR_ARG (exp, 0); + tree arg2 = CALL_EXPR_ARG (exp, 1); + tree arg3 = CALL_EXPR_ARG (exp, 2); + + if (!check_nul_terminated_array (exp, arg1, arg3) + || !check_nul_terminated_array (exp, arg2, arg3)) + return NULL_RTX; + /* Due to the performance benefit, always inline the calls first. */ rtx result = NULL_RTX; result = inline_expand_builtin_string_cmp (exp, target); @@ -4956,10 +5024,6 @@ expand_builtin_strncmp (tree exp, ATTRIBUTE_UNUSED rtx target, tree len; - tree arg1 = CALL_EXPR_ARG (exp, 0); - tree arg2 = CALL_EXPR_ARG (exp, 1); - tree arg3 = CALL_EXPR_ARG (exp, 2); - unsigned int arg1_align = get_pointer_alignment (arg1) / BITS_PER_UNIT; unsigned int arg2_align = get_pointer_alignment (arg2) / BITS_PER_UNIT; @@ -5954,6 +6018,26 @@ expand_builtin_fork_or_exec (tree fn, tree exp, rtx target, int ignore) tree id, decl; tree call; + if (DECL_FUNCTION_CODE (fn) != BUILT_IN_FORK) + { + /* Detect unterminated path. */ + if (!check_nul_terminated_array (exp, CALL_EXPR_ARG (exp, 0))) + return NULL_RTX; + + /* Also detect unterminated first argument. */ + switch (DECL_FUNCTION_CODE (fn)) + { + case BUILT_IN_EXECL: + case BUILT_IN_EXECLE: + case BUILT_IN_EXECLP: + if (!check_nul_terminated_array (exp, CALL_EXPR_ARG (exp, 0))) + return NULL_RTX; + default: + break; + } + } + + /* If we are not profiling, just call the function. */ if (!profile_arc_flag) return NULL_RTX; @@ -7604,11 +7688,49 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode, break; case BUILT_IN_STRCAT: - target = expand_builtin_strcat (exp, target); + target = expand_builtin_strcat (exp); if (target) return target; break; + case BUILT_IN_GETTEXT: + case BUILT_IN_PUTS: + case BUILT_IN_PUTS_UNLOCKED: + case BUILT_IN_STRDUP: + if (validate_arglist (exp, POINTER_TYPE, VOID_TYPE)) + check_nul_terminated_array (exp, CALL_EXPR_ARG (exp, 0)); + break; + + case BUILT_IN_INDEX: + case BUILT_IN_RINDEX: + case BUILT_IN_STRCHR: + case BUILT_IN_STRRCHR: + if (validate_arglist (exp, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) + check_nul_terminated_array (exp, CALL_EXPR_ARG (exp, 0)); + break; + + case BUILT_IN_FPUTS: + case BUILT_IN_FPUTS_UNLOCKED: + if (validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE)) + check_nul_terminated_array (exp, CALL_EXPR_ARG (exp, 0)); + break; + + case BUILT_IN_STRNDUP: + if (validate_arglist (exp, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE)) + check_nul_terminated_array (exp, + CALL_EXPR_ARG (exp, 0), + CALL_EXPR_ARG (exp, 1)); + break; + + case BUILT_IN_STRCASECMP: + case BUILT_IN_STRSTR: + if (validate_arglist (exp, POINTER_TYPE, POINTER_TYPE, VOID_TYPE)) + { + check_nul_terminated_array (exp, CALL_EXPR_ARG (exp, 0)); + check_nul_terminated_array (exp, CALL_EXPR_ARG (exp, 1)); + } + break; + case BUILT_IN_STRCPY: target = expand_builtin_strcpy (exp, target); if (target) @@ -9671,11 +9793,12 @@ fold_builtin_1 (location_t loc, tree fndecl, tree arg0) } -/* Fold a call to built-in function FNDECL with 2 arguments, ARG0 and ARG1. - This function returns NULL_TREE if no simplification was possible. */ +/* Folds a call EXPR (which may be null) to built-in function FNDECL + with 2 arguments, ARG0 and ARG1. This function returns NULL_TREE + if no simplification was possible. */ static tree -fold_builtin_2 (location_t loc, tree fndecl, tree arg0, tree arg1) +fold_builtin_2 (location_t loc, tree expr, tree fndecl, tree arg0, tree arg1) { tree type = TREE_TYPE (TREE_TYPE (fndecl)); enum built_in_function fcode = DECL_FUNCTION_CODE (fndecl); @@ -9703,13 +9826,13 @@ fold_builtin_2 (location_t loc, tree fndecl, tree arg0, tree arg1) return fold_builtin_modf (loc, arg0, arg1, type); case BUILT_IN_STRSPN: - return fold_builtin_strspn (loc, arg0, arg1); + return fold_builtin_strspn (loc, expr, arg0, arg1); case BUILT_IN_STRCSPN: - return fold_builtin_strcspn (loc, arg0, arg1); + return fold_builtin_strcspn (loc, expr, arg0, arg1); case BUILT_IN_STRPBRK: - return fold_builtin_strpbrk (loc, arg0, arg1, type); + return fold_builtin_strpbrk (loc, expr, arg0, arg1, type); case BUILT_IN_EXPECT: return fold_builtin_expect (loc, arg0, arg1, NULL_TREE, NULL_TREE); @@ -9827,13 +9950,14 @@ fold_builtin_3 (location_t loc, tree fndecl, return NULL_TREE; } -/* Fold a call to built-in function FNDECL. ARGS is an array of NARGS - arguments. IGNORE is true if the result of the - function call is ignored. This function returns NULL_TREE if no - simplification was possible. */ +/* Folds a call EXPR (which may be null) to built-in function FNDECL. + ARGS is an array of NARGS arguments. IGNORE is true if the result + of the function call is ignored. This function returns NULL_TREE + if no simplification was possible. */ -tree -fold_builtin_n (location_t loc, tree fndecl, tree *args, int nargs, bool) +static tree +fold_builtin_n (location_t loc, tree expr, tree fndecl, tree *args, + int nargs, bool) { tree ret = NULL_TREE; @@ -9846,7 +9970,7 @@ fold_builtin_n (location_t loc, tree fndecl, tree *args, int nargs, bool) ret = fold_builtin_1 (loc, fndecl, args[0]); break; case 2: - ret = fold_builtin_2 (loc, fndecl, args[0], args[1]); + ret = fold_builtin_2 (loc, expr, fndecl, args[0], args[1]); break; case 3: ret = fold_builtin_3 (loc, fndecl, args[0], args[1], args[2]); @@ -9944,7 +10068,7 @@ fold_call_expr (location_t loc, tree exp, bool ignore) else { tree *args = CALL_EXPR_ARGP (exp); - ret = fold_builtin_n (loc, fndecl, args, nargs, ignore); + ret = fold_builtin_n (loc, exp, fndecl, args, nargs, ignore); if (ret) return ret; } @@ -9982,7 +10106,7 @@ fold_builtin_call_array (location_t loc, tree, if (DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_MD) return targetm.fold_builtin (fndecl, n, argarray, false); else - return fold_builtin_n (loc, fndecl, argarray, n, false); + return fold_builtin_n (loc, NULL_TREE, fndecl, argarray, n, false); } return NULL_TREE; @@ -10134,51 +10258,53 @@ readonly_data_expr (tree exp) form of the builtin function call. */ static tree -fold_builtin_strpbrk (location_t loc, tree s1, tree s2, tree type) +fold_builtin_strpbrk (location_t loc, tree expr, tree s1, tree s2, tree type) { if (!validate_arg (s1, POINTER_TYPE) || !validate_arg (s2, POINTER_TYPE)) return NULL_TREE; - else - { - tree fn; - const char *p1, *p2; - p2 = c_getstr (s2); - if (p2 == NULL) - return NULL_TREE; + if (!check_nul_terminated_array (expr, s1) + || !check_nul_terminated_array (expr, s2)) + return NULL_TREE; - p1 = c_getstr (s1); - if (p1 != NULL) - { - const char *r = strpbrk (p1, p2); - tree tem; + tree fn; + const char *p1, *p2; - if (r == NULL) - return build_int_cst (TREE_TYPE (s1), 0); + p2 = c_getstr (s2); + if (p2 == NULL) + return NULL_TREE; - /* Return an offset into the constant string argument. */ - tem = fold_build_pointer_plus_hwi_loc (loc, s1, r - p1); - return fold_convert_loc (loc, type, tem); - } + p1 = c_getstr (s1); + if (p1 != NULL) + { + const char *r = strpbrk (p1, p2); + tree tem; - if (p2[0] == '\0') - /* strpbrk(x, "") == NULL. - Evaluate and ignore s1 in case it had side-effects. */ - return omit_one_operand_loc (loc, type, integer_zero_node, s1); + if (r == NULL) + return build_int_cst (TREE_TYPE (s1), 0); - if (p2[1] != '\0') - return NULL_TREE; /* Really call strpbrk. */ + /* Return an offset into the constant string argument. */ + tem = fold_build_pointer_plus_hwi_loc (loc, s1, r - p1); + return fold_convert_loc (loc, type, tem); + } - fn = builtin_decl_implicit (BUILT_IN_STRCHR); - if (!fn) - return NULL_TREE; + if (p2[0] == '\0') + /* strpbrk(x, "") == NULL. + Evaluate and ignore s1 in case it had side-effects. */ + return omit_one_operand_loc (loc, type, integer_zero_node, s1); - /* New argument list transforming strpbrk(s1, s2) to - strchr(s1, s2[0]). */ - return build_call_expr_loc (loc, fn, 2, s1, - build_int_cst (integer_type_node, p2[0])); - } + if (p2[1] != '\0') + return NULL_TREE; /* Really call strpbrk. */ + + fn = builtin_decl_implicit (BUILT_IN_STRCHR); + if (!fn) + return NULL_TREE; + + /* New argument list transforming strpbrk(s1, s2) to + strchr(s1, s2[0]). */ + return build_call_expr_loc (loc, fn, 2, s1, + build_int_cst (integer_type_node, p2[0])); } /* Simplify a call to the strspn builtin. S1 and S2 are the arguments @@ -10200,23 +10326,25 @@ fold_builtin_strpbrk (location_t loc, tree s1, tree s2, tree type) form of the builtin function call. */ static tree -fold_builtin_strspn (location_t loc, tree s1, tree s2) +fold_builtin_strspn (location_t loc, tree expr, tree s1, tree s2) { if (!validate_arg (s1, POINTER_TYPE) || !validate_arg (s2, POINTER_TYPE)) return NULL_TREE; - else - { - const char *p1 = c_getstr (s1), *p2 = c_getstr (s2); - /* If either argument is "", return NULL_TREE. */ - if ((p1 && *p1 == '\0') || (p2 && *p2 == '\0')) - /* Evaluate and ignore both arguments in case either one has - side-effects. */ - return omit_two_operands_loc (loc, size_type_node, size_zero_node, + if (!check_nul_terminated_array (expr, s1) + || !check_nul_terminated_array (expr, s2)) + return NULL_TREE; + + const char *p1 = c_getstr (s1), *p2 = c_getstr (s2); + + /* If either argument is "", return NULL_TREE. */ + if ((p1 && *p1 == '\0') || (p2 && *p2 == '\0')) + /* Evaluate and ignore both arguments in case either one has + side-effects. */ + return omit_two_operands_loc (loc, size_type_node, size_zero_node, s1, s2); - return NULL_TREE; - } + return NULL_TREE; } /* Simplify a call to the strcspn builtin. S1 and S2 are the arguments @@ -10238,38 +10366,40 @@ fold_builtin_strspn (location_t loc, tree s1, tree s2) form of the builtin function call. */ static tree -fold_builtin_strcspn (location_t loc, tree s1, tree s2) +fold_builtin_strcspn (location_t loc, tree expr, tree s1, tree s2) { if (!validate_arg (s1, POINTER_TYPE) || !validate_arg (s2, POINTER_TYPE)) return NULL_TREE; - else + + if (!check_nul_terminated_array (expr, s1) + || !check_nul_terminated_array (expr, s2)) + return NULL_TREE; + + /* If the first argument is "", return NULL_TREE. */ + const char *p1 = c_getstr (s1); + if (p1 && *p1 == '\0') { - /* If the first argument is "", return NULL_TREE. */ - const char *p1 = c_getstr (s1); - if (p1 && *p1 == '\0') - { - /* Evaluate and ignore argument s2 in case it has - side-effects. */ - return omit_one_operand_loc (loc, size_type_node, + /* Evaluate and ignore argument s2 in case it has + side-effects. */ + return omit_one_operand_loc (loc, size_type_node, size_zero_node, s2); - } + } - /* If the second argument is "", return __builtin_strlen(s1). */ - const char *p2 = c_getstr (s2); - if (p2 && *p2 == '\0') - { - tree fn = builtin_decl_implicit (BUILT_IN_STRLEN); + /* If the second argument is "", return __builtin_strlen(s1). */ + const char *p2 = c_getstr (s2); + if (p2 && *p2 == '\0') + { + tree fn = builtin_decl_implicit (BUILT_IN_STRLEN); - /* If the replacement _DECL isn't initialized, don't do the - transformation. */ - if (!fn) - return NULL_TREE; + /* If the replacement _DECL isn't initialized, don't do the + transformation. */ + if (!fn) + return NULL_TREE; - return build_call_expr_loc (loc, fn, 1, s1); - } - return NULL_TREE; + return build_call_expr_loc (loc, fn, 1, s1); } + return NULL_TREE; } /* Fold the next_arg or va_start call EXP. Returns true if there was an error @@ -11112,7 +11242,7 @@ fold_call_stmt (gcall *stmt, bool ignore) } else { - ret = fold_builtin_n (loc, fndecl, args, nargs, ignore); + ret = fold_builtin_n (loc, NULL_TREE, fndecl, args, nargs, ignore); if (ret) { /* Propagate location information from original call to diff --git a/gcc/builtins.h b/gcc/builtins.h index 1ad82e86963..d9e27ca16aa 100644 --- a/gcc/builtins.h +++ b/gcc/builtins.h @@ -91,7 +91,7 @@ struct c_strlen_data tree minlen; tree maxlen; tree maxbound; - /* When non-null, NONSTR refers to the declaration known to store + /* When non-null, DECL refers to the declaration known to store an unterminated constant character array, as in: const char s[] = { 'a', 'b', 'c' }; It is used to diagnose uses of such arrays in functions such as @@ -125,7 +125,6 @@ extern tree fold_builtin_expect (location_t, tree, tree, tree, tree); extern bool avoid_folding_inline_builtin (tree); extern tree fold_call_expr (location_t, tree, bool); extern tree fold_builtin_call_array (location_t, tree, tree, int, tree *); -extern tree fold_builtin_n (location_t, tree, tree *, int, bool); extern bool validate_gimple_arglist (const gcall *, ...); extern rtx default_expand_builtin (tree, rtx, rtx, machine_mode, int); extern bool fold_builtin_next_arg (tree, bool); @@ -148,6 +147,7 @@ extern bool target_char_cst_p (tree t, char *p); extern internal_fn associated_internal_fn (tree); extern internal_fn replacement_internal_fn (gcall *); +bool check_nul_terminated_array (tree, tree, tree = NULL_TREE); extern void warn_string_no_nul (location_t, const char *, tree, tree); extern tree unterminated_array (tree, tree * = NULL, bool * = NULL); extern bool builtin_with_linkage_p (tree); diff --git a/gcc/gimple-fold.c b/gcc/gimple-fold.c index cfae9db6043..fadc422aed8 100644 --- a/gcc/gimple-fold.c +++ b/gcc/gimple-fold.c @@ -1899,6 +1899,11 @@ gimple_fold_builtin_strchr (gimple_stmt_iterator *gsi, bool is_strrchr) if (!gimple_call_lhs (stmt)) return false; + /* Avoid folding if the first argument is not a nul-terminated array. + Defer warning until later. */ + if (!check_nul_terminated_array (NULL_TREE, str)) + return false; + if ((p = c_getstr (str)) && target_char_cst_p (c, &ch)) { const char *p1 = is_strrchr ? strrchr (p, ch) : strchr (p, ch); @@ -1973,18 +1978,23 @@ static bool gimple_fold_builtin_strstr (gimple_stmt_iterator *gsi) { gimple *stmt = gsi_stmt (*gsi); + if (!gimple_call_lhs (stmt)) + return false; + tree haystack = gimple_call_arg (stmt, 0); tree needle = gimple_call_arg (stmt, 1); - const char *p, *q; - if (!gimple_call_lhs (stmt)) + /* Avoid folding if either argument is not a nul-terminated array. + Defer warning until later. */ + if (!check_nul_terminated_array (NULL_TREE, haystack) + || !check_nul_terminated_array (NULL_TREE, needle)) return false; - q = c_getstr (needle); + const char *q = c_getstr (needle); if (q == NULL) return false; - if ((p = c_getstr (haystack))) + if (const char *p = c_getstr (haystack)) { const char *r = strstr (p, q); diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index f602c9b27ad..96c0f899bce 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,3 +1,9 @@ +2019-11-22 Martin Sebor + + PR middle-end/88226 + * gcc.dg/Wstringop-overflow-22.c: New test. + * gcc.dg/tree-ssa/builtin-fprintf-warn-1.c: Remove xfails. + 2019-11-22 Martin Sebor PR tree-optimization/92501 diff --git a/gcc/testsuite/gcc.dg/Wstringop-overflow-22.c b/gcc/testsuite/gcc.dg/Wstringop-overflow-22.c new file mode 100644 index 00000000000..a81ab99d724 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wstringop-overflow-22.c @@ -0,0 +1,263 @@ +/* { dg-do compile } + { dg-options "-O2 -Wall -Wno-stringop-truncation -ftrack-macro-expansion=0" } */ + +#define NULL (void*)0 + +const char a[] = { 'a', 'b', 'c', 'd' }; +const char b[] = { 'a', '\0', 'c', '\0', 'e' }; + +#define CONCAT(a, b) a ## b +#define CAT(a, b) CONCAT (a, b) + +typedef struct FILE FILE; +extern FILE *fp; + +extern char *d; +extern const char *s; +extern int n; + +#define T(func, ...) \ + __attribute__ ((noipa)) void \ + CAT (test_ ## func, __LINE__) (void) \ + { \ + sink (0, __builtin_ ## func (__VA_ARGS__), d, s, n); \ + } typedef void dummy_type + +void sink (void*, ...); + + +// Exercise string functions. +T (index, a, 'x'); // { dg-warning "missing terminating nul" "index" } +T (index, a, *s); // { dg-warning "missing terminating nul" "index" } + +T (index, b, '0'); +T (index, b + 1, '1'); +T (index, b + 2, '2'); +T (index, b + 3, '3'); +T (index, b + 4, '4'); // { dg-warning "missing terminating nul" "index" } + +T (rindex, a, 'x'); // { dg-warning "missing terminating nul" "rindex" } +T (rindex, a, *s); // { dg-warning "missing terminating nul" "rindex" } + +T (rindex, b, '0'); +T (rindex, b + 1, '1'); +T (rindex, b + 2, '2'); +T (rindex, b + 3, '3'); +T (rindex, b + 4, '4'); // { dg-warning "missing terminating nul" "rindex" } + +T (stpcpy, d, a); // { dg-warning "missing terminating nul" "stpcpy" } + +T (stpncpy, d, a, 4); +T (stpncpy, d, a, 5); // { dg-warning "missing terminating nul" "stpncpy" } +T (stpncpy, d, a, n); + +T (stpncpy, d, a + n, 4); +T (stpncpy, d, a + n, 5); // { dg-warning "missing terminating nul" "stpncpy" } + +T (stpncpy, d, b, 4); +T (stpncpy, d, b, 5); +T (stpncpy, d, b, n); + +T (stpncpy, d, b + 1, 4); +T (stpncpy, d, b + 1, 5); +T (stpncpy, d, b + 1, n); + +T (stpncpy, d, b + 3, 4); +T (stpncpy, d, b + 3, 5); +T (stpncpy, d, b + 3, n); + +T (stpncpy, d, b + 4, 1); +T (stpncpy, d, b + 4, 2); // { dg-warning "missing terminating nul" "stpncpy" } +T (stpncpy, d, b + 4, n); +/* The following might be worth warning about since it's only safe with + n < 4. */ +T (stpncpy, d, b + n, 5); + +T (strcasecmp, a, "ab"); // { dg-warning "missing terminating nul" "strcasecmp" } +T (strcasecmp, a, s); // { dg-warning "missing terminating nul" "strcasecmp" } +T (strcasecmp, a, b); // { dg-warning "missing terminating nul" "strcasecmp" } +T (strcasecmp, b, b + 1); +T (strcasecmp, b, b + 2); +T (strcasecmp, b, b + 3); +T (strcasecmp, b, b + 4); // { dg-warning "missing terminating nul" "strcasecmp" } + +T (strcat, d, a); // { dg-warning "missing terminating nul" "strcat" } + +T (strncat, d, a, 4); +T (strncat, d, a, 5); // { dg-warning "missing terminating nul" "strncat" } +T (strncat, d, a, n); + +T (strncat, d, b, n); +T (strncat, d, b + 1, n); +T (strncat, d, b + 2, n); +T (strncat, d, b + 3, n); +T (strncat, d, b + 4, 0); +T (strncat, d, b + 4, 1); +T (strncat, d, b + 4, 2); // { dg-warning "missing terminating nul" "strncat" } +/* The following should probably trigger a warning since it's only safe + when n < 2, makes little sense with n == 0, and not much more with + n == 1. */ +T (strncat, d, b + 4, n); // { dg-warning "missing terminating nul" "strncat" { xfail *-*-* } } + +T (strchr, a, 'x'); // { dg-warning "missing terminating nul" "strchr" } +T (strchr, a, *s); // { dg-warning "missing terminating nul" "strchr" } + +T (strcmp, a, "ab"); // { dg-warning "missing terminating nul" "strcmp" } +T (strcmp, "bc", a); // { dg-warning "missing terminating nul" "strcmp" } +T (strcmp, a, s); // { dg-warning "missing terminating nul" "strcmp" } +T (strcmp, s, a); // { dg-warning "missing terminating nul" "strcmp" } + +T (strcmp, a, b); // { dg-warning "missing terminating nul" "strcmp" } +/* Even though most likely safe in reality because b[1] is nul, + the following is strictly undefined because a is not a string. + The warning is not issued because GCC folds the call to (int)*a. */ +T (strcmp, a, b + 1); // { dg-warning "missing terminating nul" "bug" { xfail *-*-* } } + +T (strncmp, a, "ab", 4); +T (strncmp, "bc", a, 4); +T (strncmp, a, a, 4); +T (strncmp, a, s, 4); +T (strncmp, s, a, 4); + +/* The warning below is not issued because GCC folds strncmp calls with + the same arguments to zero before it checks for the missing nul. */ +T (strncmp, a, a, 5); // { dg-warning "missing terminating nul" "pr92624" { xfail *-*-*} } +T (strncmp, a, s, 5); // { dg-warning "missing terminating nul" "strcmp" } +T (strncmp, s, a, 5); // { dg-warning "missing terminating nul" "strcmp" } + +T (strcpy, d, a); // { dg-warning "missing terminating nul" "strcpy" } + +T (strcspn, a, s); // { dg-warning "missing terminating nul" "strcspn" } +T (strcspn, s, a); // { dg-warning "missing terminating nul" "strcspn" } + +T (strspn, a, s); // { dg-warning "missing terminating nul" "strcspn" } +T (strspn, s, a); // { dg-warning "missing terminating nul" "strcspn" } + +T (strdup, a); // { dg-warning "missing terminating nul" "strdup" } + +T (strndup, a, 4); +T (strndup, a, 5); // { dg-warning "missing terminating nul" "strndup" } +T (strndup, b + 3, 2); +T (strndup, b + 4, 1); +T (strndup, b + 4, 2); // { dg-warning "missing terminating nul" "strndup" } + +T (strlen, a); // { dg-warning "missing terminating nul" "strlen" } + +T (strnlen, a, 4); +T (strnlen, a, 5); // { dg-warning "specified bound 5 exceeds the size 4 of unterminated array" "strnlen" } +T (strnlen, a, n); + +T (strpbrk, s, a); // { dg-warning "missing terminating nul" "strpbrk" } + +T (strrchr, a, 'x'); // { dg-warning "missing terminating nul" "strrchr" } +T (strrchr, a, *s); // { dg-warning "missing terminating nul" "strrchr" } + +T (strstr, a, "cde"); // { dg-warning "missing terminating nul" "strstr" } +T (strstr, a, s); // { dg-warning "missing terminating nul" "strstr" } + + +// Exercise a few string checking functions. +T (__stpcpy_chk, d, a, -1); // { dg-warning "missing terminating nul" "stpcpy" } + + +T (__stpncpy_chk, d, a, 4, -1); +T (__stpncpy_chk, d, a, 5, -1); // { dg-warning "missing terminating nul" "stpncpy_chk" } +T (__stpncpy_chk, d, a, n, -1); + +T (__stpncpy_chk, d, a + n, 4, -1); +T (__stpncpy_chk, d, a + n, 5, -1); // { dg-warning "missing terminating nul" "stpncpy_chk" } + +T (__stpncpy_chk, d, b, 4, -1); +T (__stpncpy_chk, d, b, 5, -1); +T (__stpncpy_chk, d, b, n, -1); + +T (__stpncpy_chk, d, b + 1, 4, -1); +T (__stpncpy_chk, d, b + 1, 5, -1); +T (__stpncpy_chk, d, b + 1, n, -1); + +T (__stpncpy_chk, d, b + 3, 4, -1); +T (__stpncpy_chk, d, b + 3, 5, -1); +T (__stpncpy_chk, d, b + 3, n, -1); + +T (__stpncpy_chk, d, b + 4, 1, -1); +T (__stpncpy_chk, d, b + 4, 2, -1); // { dg-warning "missing terminating nul" "stpncpy_chk" } +T (__stpncpy_chk, d, b + 4, n, -1); + + +T (__strncat_chk, d, a, 4, -1); +T (__strncat_chk, d, a, 5, -1); // { dg-warning "missing terminating nul" "strncat_chk" } +T (__strncat_chk, d, a, n, -1); + +T (__strncat_chk, d, a + n, 4, -1); +T (__strncat_chk, d, a + n, 5, -1); // { dg-warning "missing terminating nul" "strncat_chk" } + +T (__strncat_chk, d, b, 4, -1); +T (__strncat_chk, d, b, 5, -1); +T (__strncat_chk, d, b, n, -1); + +T (__strncat_chk, d, b + 1, 4, -1); +T (__strncat_chk, d, b + 1, 5, -1); +T (__strncat_chk, d, b + 1, n, -1); + +T (__strncat_chk, d, b + 3, 4, -1); +T (__strncat_chk, d, b + 3, 5, -1); +T (__strncat_chk, d, b + 3, n, -1); + +T (__strncat_chk, d, b + 4, 1, -1); +T (__strncat_chk, d, b + 4, 2, -1); // { dg-warning "missing terminating nul" "strncat_chk" } +T (__strncat_chk, d, b + 4, n, -1); + + +T (__strncpy_chk, d, a, 4, -1); +T (__strncpy_chk, d, a, 5, -1); // { dg-warning "missing terminating nul" "strncpy_chk" } +T (__strncpy_chk, d, a, n, -1); + +T (__strncpy_chk, d, a + n, 4, -1); +T (__strncpy_chk, d, a + n, 5, -1); // { dg-warning "missing terminating nul" "strncpy_chk" } + +T (__strncpy_chk, d, b, 4, -1); +T (__strncpy_chk, d, b, 5, -1); +T (__strncpy_chk, d, b, n, -1); + +T (__strncpy_chk, d, b + 1, 4, -1); +T (__strncpy_chk, d, b + 1, 5, -1); +T (__strncpy_chk, d, b + 1, n, -1); + +T (__strncpy_chk, d, b + 3, 4, -1); +T (__strncpy_chk, d, b + 3, 5, -1); +T (__strncpy_chk, d, b + 3, n, -1); + +T (__strncpy_chk, d, b + 4, 1, -1); +T (__strncpy_chk, d, b + 4, 2, -1); // { dg-warning "missing terminating nul" "strncpy" } +T (__strncpy_chk, d, b + 4, n, -1); + + +// Exercise some stdio functions. +T (printf, a); // { dg-warning "unterminated format string" "printf" } +T (printf, "%s", a); // { dg-warning "not a nul-terminated string" "printf" } +T (sprintf, d, "%s", a); // { dg-warning "not a nul-terminated string" "sprintf" } +T (snprintf, d, n, "%s", a); // { dg-warning "not a nul-terminated string" "sprintf" } + +T (__sprintf_chk, d, 0, -1, "%s", a); // { dg-warning "not a nul-terminated string" "sprintf" } +T (__snprintf_chk, d, n, 0, -1, "%s", a); // { dg-warning "not a nul-terminated string" "sprintf" } + +T (fputs, a, fp); // { dg-warning "missing terminating nul" "fputs" } +T (fputs_unlocked, a, fp); // { dg-warning "missing terminating nul" "fputs_unlocked" } +T (puts, a); // { dg-warning "missing terminating nul" "puts" } +T (puts_unlocked, a); // { dg-warning "missing terminating nul" "puts_unlocked" } + + + +// Exerise exec functions. +T (execl, a, s, NULL); // { dg-warning "missing terminating nul" "execl" } +T (execl, a, s, NULL); // { dg-warning "missing terminating nul" "execl" } +T (execle, a, s, NULL, NULL); // { dg-warning "missing terminating nul" "execl" } +T (execlp, a, s, NULL); // { dg-warning "missing terminating nul" "execl" } + +T (execv, a, &d); // { dg-warning "missing terminating nul" "execl" } +T (execve, a, &d, &d); // { dg-warning "missing terminating nul" "execl" } +T (execvp, a, &d); // { dg-warning "missing terminating nul" "execl" } + +T (gettext, a); // { dg-warning "missing terminating nul" "gettext" } + +T (strfmon, d, n, a); // { dg-warning "unterminated format string" "strfmon" } diff --git a/gcc/testsuite/gcc.dg/tree-ssa/builtin-fprintf-warn-1.c b/gcc/testsuite/gcc.dg/tree-ssa/builtin-fprintf-warn-1.c index 690404ade99..0b06c0da00d 100644 --- a/gcc/testsuite/gcc.dg/tree-ssa/builtin-fprintf-warn-1.c +++ b/gcc/testsuite/gcc.dg/tree-ssa/builtin-fprintf-warn-1.c @@ -84,8 +84,8 @@ void test_fprintf_s_const (int width) if (nulptr) T ("%s", nulptr); - T ("%s", &chr_no_nul); /* { dg-warning ".%s. directive argument is not a nul-terminated string" "pr88226" { xfail *-*-* } } */ - T ("%s", arr_no_nul); /* { dg-warning ".%s. directive argument is not a nul-terminated string" "pr88226" { xfail *-*-* } } */ + T ("%s", &chr_no_nul); /* { dg-warning ".%s. directive argument is not a nul-terminated string|argument missing terminating nul" } */ + T ("%s", arr_no_nul); /* { dg-warning ".%s. directive argument is not a nul-terminated string|argument missing terminating nul" } */ /* Verify that output in excess of INT_MAX bytes is diagnosed even when the size of the destination object is unknown. */ @@ -117,7 +117,7 @@ void test_fprintf_ls_const (int width) T ("%ls", nulptr); T ("%ls", &wchr_no_nul); /* { dg-warning ".%ls. directive argument is not a nul-terminated string" } */ - T ("%ls", warr_no_nul); /* { dg-warning ".%ls. directive argument is not a nul-terminated string" "pr88211" { xfail *-*-* } } */ + T ("%ls", warr_no_nul); /* { dg-warning ".%ls. directive argument is not a nul-terminated string" "pr88226" { xfail *-*-* } } */ /* Verify that output in excess of INT_MAX bytes is diagnosed even when the size of the destination object is unknown. */ diff --git a/gcc/tree-ssa-strlen.c b/gcc/tree-ssa-strlen.c index be0f37cc4d1..06fa99eb5ac 100644 --- a/gcc/tree-ssa-strlen.c +++ b/gcc/tree-ssa-strlen.c @@ -1946,8 +1946,6 @@ handle_builtin_strlen (gimple_stmt_iterator *gsi) static void handle_builtin_strchr (gimple_stmt_iterator *gsi) { - int idx; - tree src; gimple *stmt = gsi_stmt (*gsi); tree lhs = gimple_call_lhs (stmt); @@ -1957,8 +1955,14 @@ handle_builtin_strchr (gimple_stmt_iterator *gsi) if (!integer_zerop (gimple_call_arg (stmt, 1))) return; - src = gimple_call_arg (stmt, 0); - idx = get_stridx (src); + tree src = gimple_call_arg (stmt, 0); + + /* Avoid folding if the first argument is not a nul-terminated array. + Defer warning until later. */ + if (!check_nul_terminated_array (NULL_TREE, src)) + return; + + int idx = get_stridx (src); if (idx) { strinfo *si = NULL; @@ -3794,11 +3798,11 @@ handle_builtin_string_cmp (gimple_stmt_iterator *gsi) /* For strncmp set to the the value of the third argument if known. */ HOST_WIDE_INT bound = -1; - + tree len = NULL_TREE; /* Extract the strncmp bound. */ if (gimple_call_num_args (stmt) == 3) { - tree len = gimple_call_arg (stmt, 2); + len = gimple_call_arg (stmt, 2); if (tree_fits_shwi_p (len)) bound = tree_to_shwi (len); @@ -3807,6 +3811,12 @@ handle_builtin_string_cmp (gimple_stmt_iterator *gsi) return false; } + /* Avoid folding if either argument is not a nul-terminated array. + Defer warning until later. */ + if (!check_nul_terminated_array (NULL_TREE, arg1, len) + || !check_nul_terminated_array (NULL_TREE, arg2, len)) + return false; + { /* Set to the length of one argument (or its complement if it's the lower bound of a range) and the size of the array storing -- 2.30.2