From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTPS id 5FDA33858D1E for ; Thu, 19 Oct 2023 14:03:19 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 5FDA33858D1E Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 5FDA33858D1E Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1697724203; cv=none; b=nmwYIcSHWpAhlelxw1lTJU5sinIbUq6HqF4A8ZxKHb1znNk8CxrL3BENTbMSr29sRvvxtr7xdMsvTU5iJDMPR2yTQdmInFgr50p1YJErFJ44c1F/GFuIsE0c7Itm5lAz9UExe96OAC8PxjiVs6cR5Hmo8+HN3AzVQshSSHq59Fw= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1697724203; c=relaxed/simple; bh=M0oS+ljvrbyPN/mMyk2zpRHGtRmg6/RAzlRnGohpHcY=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=lGgyGQZT8NiIELILJet3qoNIh9RJMcKaWJLuFq8hwkCKBXTkpTPG4MClMl6Oq3+8NEPSb0vEuLw5LCiOdhsFtjXPeXGLAE1wrXKiV9gFdbkKv2lV1VQbeMrbmGEAM6lQ1AY0rLFOLA9nmY5l7fr2tRGpI57+7oGiDpqi3XHGEb8= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1697724198; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=4k9Kc98wBqec9UAmkIpuIRNzfDSmuM+wg109iWxZsxE=; b=CmNn40vh8b1O6z1PBJ2oCow2d/4WtF71HbYd4oCYO09TyDlsBaLYsdagp0pHXz7SNHaSX5 yS0VwGyJm2FaKW7nmJoV2TQgztL2pWx6nFABikyoOCLfaQJqGdsycog6KKmXtEOEbt5IAw dfdUsSpeSl4JqyfmVAyaqIOzpMeMSO8= Received: from mimecast-mx02.redhat.com (mx-ext.redhat.com [66.187.233.73]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-327-8zWxJmhUM-ihjA_4q1O-Vg-1; Thu, 19 Oct 2023 10:03:01 -0400 X-MC-Unique: 8zWxJmhUM-ihjA_4q1O-Vg-1 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.rdu2.redhat.com [10.11.54.7]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 4A8193C108CB for ; Thu, 19 Oct 2023 14:02:59 +0000 (UTC) Received: from t14s.localdomain.com (unknown [10.22.10.128]) by smtp.corp.redhat.com (Postfix) with ESMTP id C9EC51C060AE; Thu, 19 Oct 2023 14:02:58 +0000 (UTC) From: David Malcolm To: gcc-patches@gcc.gnu.org Cc: David Malcolm Subject: [RFC] Add function attribute: null_terminated_string_arg(PARAM_IDX) Date: Thu, 19 Oct 2023 10:02:57 -0400 Message-Id: <20231019140257.360669-1-dmalcolm@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.11.54.7 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit Content-Type: text/plain; charset="US-ASCII"; x-default=true X-Spam-Status: No, score=-9.9 required=5.0 tests=BAYES_00,DKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,GIT_PATCH_0,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H4,RCVD_IN_MSPIKE_WL,SPF_HELO_NONE,SPF_NONE,TLD_CHINA,TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org List-Id: This patch adds a new function attribute to GCC for marking that an argument is expected to be a null-terminated string. For example, consider: void test_a (const char *p) __attribute__((null_terminated_string_arg (1))); which would indicate to humans and compilers that argument 1 of "test_a" is expected to be a null-terminated string, with the idea: - we should complain if it's not valid to read from *p up to the first '\0' character in the buffer - we should complain if *p is not terminated, or if it's uninitialized before the first '\0' character This is independent of the nonnull-ness of the pointer: if you also want to express that the argument must be non-null, we already have __attribute__((nonnull (N))), so the user can write e.g.: void test_b (const char *p) __attribute__((null_terminated_string_arg (1)) __attribute__((nonnull (1))); which can also be spelled as: void test_b (const char *p) __attribute__((null_terminated_string_arg (1), nonnull (1))); For a function similar to strncpy, we can use the "access" attribute to express a maximum size of the read: void test_c (const char *p, size_t sz) __attribute__((null_terminated_string_arg (1), nonnull (1), access (read_only, 1, 2))); The patch implements: (a) C/C++ frontends: recognition of this attribute (b) analyzer: usage of this attribute The name is rather long; a shorter name might be "c_string_arg". Does anything like this already exist in GCC, or in any other compilers or analysis tools? Thoughts? Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu. gcc/analyzer/ChangeLog: * region-model.cc (region_model::check_external_function_for_access_attr): Split out, replacing with... (region_model::check_function_attr_access): ...this new function and... (region_model::check_function_attrs): ...this new function. (region_model::check_one_function_attr_null_terminated_string_arg): New. (region_model::check_function_attr_null_terminated_string_arg): New. (region_model::handle_unrecognized_call): Update for renaming of check_external_function_for_access_attr to check_function_attrs. (region_model::check_for_null_terminated_string_arg): Add return value to one overload. Make both overloads const. * region-model.h: Include "stringpool.h" and "attribs.h". (region_model::check_for_null_terminated_string_arg): Add return value to one overload. Make both overloads const. (region_model::check_external_function_for_access_attr): Delete decl. (region_model::check_function_attr_access): New decl. (region_model::check_function_attr_null_terminated_string_arg): New decl. (region_model::check_one_function_attr_null_terminated_string_arg): New decl. (region_model::check_function_attrs): New decl. gcc/c-family/ChangeLog: * c-attribs.cc (c_common_attribute_table): Add "null_terminated_string_arg". (handle_null_terminated_string_arg_attribute): New. gcc/ChangeLog: * doc/extend.texi (Common Function Attributes): Add null_terminated_string_arg. gcc/testsuite/ChangeLog: * c-c++-common/analyzer/attr-null_terminated_string_arg-access-read_write.c: New test. * c-c++-common/analyzer/attr-null_terminated_string_arg-access-without-size.c: New test. * c-c++-common/analyzer/attr-null_terminated_string_arg-multiple.c: New test. * c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull-2.c: New test. * c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull-sized.c: New test. * c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull.c: New test. * c-c++-common/analyzer/attr-null_terminated_string_arg-nullable-sized.c: New test. * c-c++-common/analyzer/attr-null_terminated_string_arg-nullable.c: New test. * c-c++-common/attr-null_terminated_string_arg.c: New test. Signed-off-by: David Malcolm --- gcc/analyzer/region-model.cc | 180 +++++++++++++++--- gcc/analyzer/region-model.h | 27 ++- gcc/c-family/c-attribs.cc | 17 ++ gcc/doc/extend.texi | 57 ++++++ ..._terminated_string_arg-access-read_write.c | 15 ++ ...erminated_string_arg-access-without-size.c | 54 ++++++ ...attr-null_terminated_string_arg-multiple.c | 52 +++++ ...ttr-null_terminated_string_arg-nonnull-2.c | 33 ++++ ...null_terminated_string_arg-nonnull-sized.c | 69 +++++++ .../attr-null_terminated_string_arg-nonnull.c | 34 ++++ ...ull_terminated_string_arg-nullable-sized.c | 69 +++++++ ...attr-null_terminated_string_arg-nullable.c | 34 ++++ .../attr-null_terminated_string_arg.c | 16 ++ 13 files changed, 629 insertions(+), 28 deletions(-) create mode 100644 gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-access-read_write.c create mode 100644 gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-access-without-size.c create mode 100644 gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-multiple.c create mode 100644 gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull-2.c create mode 100644 gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull-sized.c create mode 100644 gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull.c create mode 100644 gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nullable-sized.c create mode 100644 gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nullable.c create mode 100644 gcc/testsuite/c-c++-common/attr-null_terminated_string_arg.c diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index c4e68661ef1e..fb84f1f5b009 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -1781,26 +1781,17 @@ private: attribute. */ void -region_model:: -check_external_function_for_access_attr (const gcall *call, - tree callee_fndecl, - region_model_context *ctxt) const +region_model::check_function_attr_access (const gcall *call, + tree callee_fndecl, + region_model_context *ctxt, + rdwr_map &rdwr_idx) const { gcc_assert (call); gcc_assert (callee_fndecl); gcc_assert (ctxt); tree fntype = TREE_TYPE (callee_fndecl); - if (!fntype) - return; - - if (!TYPE_ATTRIBUTES (fntype)) - return; - - /* Initialize a map of attribute access specifications for arguments - to the function call. */ - rdwr_map rdwr_idx; - init_attr_rdwr_indices (&rdwr_idx, TYPE_ATTRIBUTES (fntype)); + gcc_assert (fntype); unsigned argno = 0; @@ -1854,6 +1845,151 @@ check_external_function_for_access_attr (const gcall *call, } } +/* Subroutine of region_model::check_function_attr_null_terminated_string_arg, + checking one instance of __attribute__((null_terminated_string_arg)). */ + +void +region_model:: +check_one_function_attr_null_terminated_string_arg (const gcall *call, + tree callee_fndecl, + region_model_context *ctxt, + rdwr_map &rdwr_idx, + tree attr) +{ + gcc_assert (call); + gcc_assert (callee_fndecl); + gcc_assert (ctxt); + gcc_assert (attr); + + tree arg = TREE_VALUE (attr); + if (!arg) + return; + + /* Convert from 1-based to 0-based index. */ + unsigned int arg_idx = TREE_INT_CST_LOW (TREE_VALUE (arg)) - 1; + + /* If there's also an "access" attribute on the ptr param + for reading with a size param specified, then that size + limits the size of the possible read from the pointer. */ + if (const attr_access* access = rdwr_idx.get (arg_idx)) + if ((access->mode == access_read_only + || access->mode == access_read_write) + && access->sizarg != UINT_MAX) + { + /* First, check for a null-terminated string *without* + emitting emitting warnings (via a null context), to + get an svalue for the strlen of the buffer (possibly + nullptr if there would be an issue). */ + call_details cd_unchecked (call, this, nullptr); + const svalue *strlen_sval + = check_for_null_terminated_string_arg (cd_unchecked, + arg_idx); + + /* Get svalue for the size limit argument. */ + call_details cd_checked (call, this, ctxt); + const svalue *limit_sval + = cd_checked.get_arg_svalue (access->sizarg); + const svalue *ptr_sval + = cd_checked.get_arg_svalue (arg_idx); + /* Try reading all of the bytes expressed by the size param, + but without checking (via a null context). */ + const svalue *limited_sval + = read_bytes (deref_rvalue (ptr_sval, NULL_TREE, nullptr), + NULL_TREE, + limit_sval, + nullptr); + if (limited_sval->get_kind () == SK_POISONED) + { + /* Reading up to the truncation limit caused issues. + Assume that the string is meant to be terminated + before then, so perform a *checked* check for the + terminator. */ + check_for_null_terminated_string_arg (cd_checked, + arg_idx); + } + else + { + /* Reading up to the truncation limit seems OK; repeat + the read, but with checking enabled. */ + const svalue *limited_sval + = read_bytes (deref_rvalue (ptr_sval, NULL_TREE, ctxt), + NULL_TREE, + limit_sval, + ctxt); + } + return; + } + + /* Otherwise, we don't have an access-attribute limiting the read. + Simulate a read up to the null terminator (if any). */ + + call_details cd (call, this, ctxt); + check_for_null_terminated_string_arg (cd, arg_idx); +} + +/* Check CALL a call to external function CALLEE_FNDECL for any uses + of __attribute__ ((null_terminated_string_arg)), compaining + to CTXT about any issues. + + Use RDWR_IDX for tracking uses of __attribute__ ((access, ....). */ + +void +region_model:: +check_function_attr_null_terminated_string_arg (const gcall *call, + tree callee_fndecl, + region_model_context *ctxt, + rdwr_map &rdwr_idx) +{ + gcc_assert (call); + gcc_assert (callee_fndecl); + gcc_assert (ctxt); + + tree fntype = TREE_TYPE (callee_fndecl); + gcc_assert (fntype); + + /* A function declaration can specify multiple attribute + null_terminated_string_arg, each with one argument. */ + for (tree attr = TYPE_ATTRIBUTES (fntype); attr; attr = TREE_CHAIN (attr)) + { + attr = lookup_attribute ("null_terminated_string_arg", attr); + if (!attr) + return; + + check_one_function_attr_null_terminated_string_arg (call, callee_fndecl, + ctxt, rdwr_idx, + attr); + } +} + +/* Check CALL a call to external function CALLEE_FNDECL for any + function attributes, complaining to CTXT about any issues. */ + +void +region_model::check_function_attrs (const gcall *call, + tree callee_fndecl, + region_model_context *ctxt) +{ + gcc_assert (call); + gcc_assert (callee_fndecl); + gcc_assert (ctxt); + + tree fntype = TREE_TYPE (callee_fndecl); + if (!fntype) + return; + + if (!TYPE_ATTRIBUTES (fntype)) + return; + + /* Initialize a map of attribute access specifications for arguments + to the function call. */ + rdwr_map rdwr_idx; + init_attr_rdwr_indices (&rdwr_idx, TYPE_ATTRIBUTES (fntype)); + + check_function_attr_access (call, callee_fndecl, ctxt, rdwr_idx); + check_function_attr_null_terminated_string_arg (call, callee_fndecl, + ctxt, rdwr_idx); +} + /* Handle a call CALL to a function with unknown behavior. Traverse the regions in this model, determining what regions are @@ -1870,7 +2006,7 @@ region_model::handle_unrecognized_call (const gcall *call, tree fndecl = get_fndecl_for_call (call, ctxt); if (fndecl && ctxt) - check_external_function_for_access_attr (call, fndecl, ctxt); + check_function_attrs (call, fndecl, ctxt); reachable_regions reachable_regs (this); @@ -3768,14 +3904,14 @@ region_model::scan_for_null_terminator (const region *reg, TODO: we should also complain if: - the pointer is NULL (or could be). */ -void +const svalue * region_model::check_for_null_terminated_string_arg (const call_details &cd, - unsigned arg_idx) + unsigned arg_idx) const { - check_for_null_terminated_string_arg (cd, - arg_idx, - false, /* include_terminator */ - nullptr); // out_sval + return check_for_null_terminated_string_arg (cd, + arg_idx, + false, /* include_terminator */ + nullptr); // out_sval } @@ -3805,7 +3941,7 @@ const svalue * region_model::check_for_null_terminated_string_arg (const call_details &cd, unsigned arg_idx, bool include_terminator, - const svalue **out_sval) + const svalue **out_sval) const { class null_terminator_check_event : public custom_event { diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index 6946c688cbbc..8bfb06880ff4 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -27,6 +27,8 @@ along with GCC; see the file COPYING3. If not see http://lcs.ios.ac.cn/~xuzb/canalyze/memmodel.pdf */ #include "bitmap.h" +#include "stringpool.h" +#include "attribs.h" // for rdwr_map #include "selftest.h" #include "analyzer/svalue.h" #include "analyzer/region.h" @@ -527,14 +529,14 @@ class region_model const svalue *sval_hint, region_model_context *ctxt) const; - void + const svalue * check_for_null_terminated_string_arg (const call_details &cd, - unsigned idx); + unsigned idx) const; const svalue * check_for_null_terminated_string_arg (const call_details &cd, unsigned idx, bool include_terminator, - const svalue **out_sval); + const svalue **out_sval) const; const builtin_known_function * get_builtin_kf (const gcall *call, @@ -644,9 +646,22 @@ private: void check_call_args (const call_details &cd) const; void check_call_format_attr (const call_details &cd, tree format_attr) const; - void check_external_function_for_access_attr (const gcall *call, - tree callee_fndecl, - region_model_context *ctxt) const; + void check_function_attr_access (const gcall *call, + tree callee_fndecl, + region_model_context *ctxt, + rdwr_map &rdwr_idx) const; + void check_function_attr_null_terminated_string_arg (const gcall *call, + tree callee_fndecl, + region_model_context *ctxt, + rdwr_map &rdwr_idx); + void check_one_function_attr_null_terminated_string_arg (const gcall *call, + tree callee_fndecl, + region_model_context *ctxt, + rdwr_map &rdwr_idx, + tree attr); + void check_function_attrs (const gcall *call, + tree callee_fndecl, + region_model_context *ctxt); static auto_vec pop_frame_callbacks; /* Storing this here to avoid passing it around everywhere. */ diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc index dca7548b2c6a..44dd19a6c8cc 100644 --- a/gcc/c-family/c-attribs.cc +++ b/gcc/c-family/c-attribs.cc @@ -177,6 +177,7 @@ static tree handle_signed_bool_precision_attribute (tree *, tree, tree, int, bool *); static tree handle_retain_attribute (tree *, tree, tree, int, bool *); static tree handle_fd_arg_attribute (tree *, tree, tree, int, bool *); +static tree handle_null_terminated_string_arg_attribute (tree *, tree, tree, int, bool *); /* Helper to define attribute exclusions. */ #define ATTR_EXCL(name, function, type, variable) \ @@ -569,6 +570,8 @@ const struct attribute_spec c_common_attribute_table[] = handle_fd_arg_attribute, NULL}, { "fd_arg_write", 1, 1, false, true, true, false, handle_fd_arg_attribute, NULL}, + { "null_terminated_string_arg", 1, 1, false, true, true, false, + handle_null_terminated_string_arg_attribute, NULL}, { NULL, 0, 0, false, false, false, false, NULL, NULL } }; @@ -4657,6 +4660,20 @@ handle_fd_arg_attribute (tree *node, tree name, tree args, return NULL_TREE; } +/* Handle the "null_terminated_string_arg" attribute. */ + +static tree +handle_null_terminated_string_arg_attribute (tree *node, tree name, tree args, + int ARG_UNUSED (flags), + bool *no_add_attrs) +{ + if (positional_argument (*node, name, TREE_VALUE (args), POINTER_TYPE)) + return NULL_TREE; + + *no_add_attrs = true; + return NULL_TREE; +} + /* Handle the "nonstring" variable attribute. */ static tree diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index b82497f00e42..9152385e08aa 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -3744,6 +3744,63 @@ my_memcpy (void *dest, const void *src, size_t len) __attribute__((nonnull)); @end smallexample +@cindex @code{null_terminated_string_arg} function attribute +@item null_terminated_string_arg +@itemx null_terminated_string_arg (@var{N}) +The @code{null_terminated_string_arg} attribute may be applied to a +function that takes a @code{char *} or @code{const char *} at +referenced argument @var{N}. + +It indicates that the passed argument must be a C-style null-terminated +string. Specifically, the presence of the attribute implies that, if +the pointer is non-null, the function may scan through the referenced +buffer looking for the first zero byte. + +In particular, when the analyzer is enabled (via @option{-fanalyzer}), +if the pointer is non-null, it will simulate scanning for the first +zero byte in the referenced buffer, and potentially emit +@option{-Wanalyzer-use-of-uninitialized-value} +or @option{-Wanalyzer-out-of-bounds} on improperly terminated buffers. + +For example, given the following: + +@smallexample +char *example_1 (const char *p) + __attribute__((null_terminated_string_arg (1))); +@end smallexample + +the analyzer will check that any non-null pointers passed to the function +are validly terminated. + +If the parameter must be non-null, it is appropriate to use both this +attribute and the attribute @code{nonnull}, such as in: + +@smallexample +extern char *example_2 (const char *p) + __attribute__((null_terminated_string_arg (1), + nonnull (1))); +@end smallexample + +See the @code{nonnull} attribute for more information and +caveats. + +If the pointer argument is also referred to by an @code{access} attribute on the +function with @var{access-mode} either @code{read_only} or @code{read_write} +and the latter attribute has the optional @var{size-index} argument +referring to a size argument, this expressses the maximum size of the access. +For example, given: + +@smallexample +extern char *example_fn (const char *p, size_t n) + __attribute__((null_terminated_string_arg (1), + access (read_only, 1, 2), + nonnull (1))); +@end smallexample + +the analyzer will require the first parameter to be non-null, and either +be validly null-terminated, or validly readable up to the size specified by +the second parameter. + @cindex @code{noplt} function attribute @item noplt The @code{noplt} attribute is the counterpart to option @option{-fno-plt}. diff --git a/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-access-read_write.c b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-access-read_write.c new file mode 100644 index 000000000000..dc5da22a1bfd --- /dev/null +++ b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-access-read_write.c @@ -0,0 +1,15 @@ +/* { dg-additional-options "-Wno-stringop-overflow" } */ +/* { dg-additional-options "-fpermissive" { target c++ } } */ + +#include "../../gcc.dg/analyzer/analyzer-decls.h" + +char *example_fn (char *p, __SIZE_TYPE__ n) + __attribute__((null_terminated_string_arg (1))) + __attribute__((access (read_write, 1, 2))); + +char * +test_unterminated_str (void) +{ + char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is too long" "" { target c++ } } */ + return example_fn (str, 4); /* { dg-warning "stack-based buffer over-read" } */ +} diff --git a/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-access-without-size.c b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-access-without-size.c new file mode 100644 index 000000000000..83ec24a49ca2 --- /dev/null +++ b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-access-without-size.c @@ -0,0 +1,54 @@ +/* { dg-additional-options "-Wno-stringop-overread" } */ +/* { dg-additional-options "-fpermissive" { target c++ } } */ + +#include "../../gcc.dg/analyzer/analyzer-decls.h" + +char *example_fn (const char *p, __SIZE_TYPE__ n) /* { dg-message "argument 1 of '\[^\n\r\]*' must be a pointer to a null-terminated string" } */ + __attribute__((null_terminated_string_arg (1))) + __attribute__((access (read_only, 1))); // but doesn't identify an argument for a size limit + +char * +test_passthrough (const char* str, __SIZE_TYPE__ n) +{ + return example_fn (str, n); +} + +char * +test_NULL_str_a (void) +{ + return example_fn (NULL, 0); /* { dg-bogus "use of NULL where non-null expected" } */ +} + +char * +test_NULL_str_b (void) +{ + return example_fn (NULL, 4); /* { dg-bogus "use of NULL where non-null expected" } */ +} + +char * +test_unterminated_str (void) +{ + char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is too long" "" { target c++ } } */ + return example_fn (str, 4); /* { dg-warning "stack-based buffer over-read" } */ +} + +char * +test_uninitialized_str_a (void) +{ + char str[16]; + return example_fn (str, 1); /* { dg-warning "use of uninitialized value 'str\\\[0\\\]'" } */ +} + +char * +test_uninitialized_str_b (void) +{ + char str[16]; + return example_fn (str, 16); /* { dg-warning "use of uninitialized value 'str\\\[0\\\]'" } */ +} + +char * +test_uninitialized_str_c (void) +{ + char str[16]; + return example_fn (str, 17); /* { dg-warning "use of uninitialized value 'str\\\[0\\\]'" } */ +} diff --git a/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-multiple.c b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-multiple.c new file mode 100644 index 000000000000..a5418b4b5ace --- /dev/null +++ b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-multiple.c @@ -0,0 +1,52 @@ +#include "../../gcc.dg/analyzer/analyzer-decls.h" + +/* { dg-additional-options "-fpermissive" { target c++ } } */ + +/* Example with multiple params with attribute null_terminated_string_arg. */ + +char *example_fn (const char *p, const char *q) + __attribute__((null_terminated_string_arg (1))) + __attribute__((null_terminated_string_arg (2))); +// but can be NULL + +char * +test_passthrough (const char *a, const char *b) +{ + return example_fn (a, b); +} + +char * +test_NULL_str (void) +{ + return example_fn (NULL, NULL); /* { dg-bogus "use of NULL where non-null expected" } */ +} + +char * +test_unterminated_str_1 (void) +{ + char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is too long" "" { target c++ } } */ + return example_fn (str, NULL); /* { dg-warning "stack-based buffer over-read" } */ + /* { dg-message "while looking for null terminator for argument 1" "note" { target *-*-* } .-1 } */ +} + +char * +test_unterminated_str_2 (void) +{ + char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is too long" "" { target c++ } } */ + return example_fn (NULL, str); /* { dg-warning "stack-based buffer over-read" } */ + /* { dg-message "while looking for null terminator for argument 2" "note" { target *-*-* } .-1 } */ +} + +char * +test_uninitialized_str_1 (void) +{ + char str[16]; + return example_fn (str, NULL); /* { dg-warning "use of uninitialized value 'str\\\[0\\\]'" } */ +} + +char * +test_uninitialized_str_2 (void) +{ + char str[16]; + return example_fn (NULL, str); /* { dg-warning "use of uninitialized value 'str\\\[0\\\]'" } */ +} diff --git a/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull-2.c b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull-2.c new file mode 100644 index 000000000000..2614633318ec --- /dev/null +++ b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull-2.c @@ -0,0 +1,33 @@ +/* { dg-additional-options "-fpermissive" { target c++ } } */ + +#include "../../gcc.dg/analyzer/analyzer-decls.h" + +extern char *example_fn (const char *p) /* { dg-message "argument 1 of '\[^\n\r\]*' must be a pointer to a null-terminated string" } */ + __attribute__((null_terminated_string_arg (1), nonnull (1))); + +char * +test_passthrough (const char* str) +{ + return example_fn (str); +} + +char * +test_NULL_str (void) +{ + return example_fn (NULL); /* { dg-warning "use of NULL where non-null expected" } */ +} + +char * +test_unterminated_str (void) +{ + char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is too long" "" { target c++ } } */ + return example_fn (str); /* { dg-warning "stack-based buffer over-read" } */ + /* { dg-message "while looking for null terminator for argument 1" "note" { target *-*-* } .-1 } */ +} + +char * +test_uninitialized_str (void) +{ + char str[16]; + return example_fn (str); /* { dg-warning "use of uninitialized value 'str\\\[0\\\]'" } */ +} diff --git a/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull-sized.c b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull-sized.c new file mode 100644 index 000000000000..c539f29a5c7b --- /dev/null +++ b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull-sized.c @@ -0,0 +1,69 @@ +/* { dg-additional-options "-Wno-stringop-overread" } */ +/* { dg-additional-options "-fpermissive" { target c++ } } */ + +#include "../../gcc.dg/analyzer/analyzer-decls.h" + +char *example_fn (const char *p, __SIZE_TYPE__ n) /* { dg-message "argument 1 of '\[^\n\r\]*' must be a pointer to a null-terminated string" } */ + __attribute__((null_terminated_string_arg (1))) + __attribute__((access (read_only, 1, 2))) + __attribute__((nonnull)); + +char * +test_passthrough (const char* str, __SIZE_TYPE__ n) +{ + return example_fn (str, n); +} + +char * +test_NULL_str_a (void) +{ + return example_fn (NULL, 0); /* { dg-warning "use of NULL where non-null expected" } */ +} + +char * +test_NULL_str_b (void) +{ + return example_fn (NULL, 4); /* { dg-warning "use of NULL where non-null expected" } */ +} + +char * +test_unterminated_str (void) +{ + char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is too long" "" { target c++ } } */ + return example_fn (str, 4); /* { dg-warning "stack-based buffer over-read" } */ +} + +char * +test_unterminated_str_truncated (void) +{ + char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is too long" "" { target c++ } } */ + return example_fn (str, 3); /* { dg-bogus "stack-based buffer over-read" } */ +} + +char * +test_uninitialized_str_a (void) +{ + char str[16]; + return example_fn (str, 1); /* { dg-warning "use of uninitialized value 'str\\\[0\\\]'" } */ +} + +char * +test_uninitialized_str_b (void) +{ + char str[16]; + return example_fn (str, 16); /* { dg-warning "use of uninitialized value 'str\\\[0\\\]'" } */ +} + +char * +test_uninitialized_str_c (void) +{ + char str[16]; + return example_fn (str, 17); /* { dg-warning "use of uninitialized value 'str\\\[0\\\]'" } */ +} + +char * +test_uninitialized_str_truncated (void) +{ + char str[16]; + return example_fn (str, 0); /* { dg-bogus "use of uninitialized value" } */ +} diff --git a/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull.c b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull.c new file mode 100644 index 000000000000..6f805d5105ff --- /dev/null +++ b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nonnull.c @@ -0,0 +1,34 @@ +/* { dg-additional-options "-fpermissive" { target c++ } } */ + +#include "../../gcc.dg/analyzer/analyzer-decls.h" + +char *example_fn (const char *p) /* { dg-message "argument 1 of '\[^\n\r\]*' must be a pointer to a null-terminated string" } */ + __attribute__((null_terminated_string_arg (1))) + __attribute__((nonnull)); + +char * +test_passthrough (const char* str) +{ + return example_fn (str); +} + +char * +test_NULL_str (void) +{ + return example_fn (NULL); /* { dg-warning "use of NULL where non-null expected" } */ +} + +char * +test_unterminated_str (void) +{ + char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is too long" "" { target c++ } } */ + return example_fn (str); /* { dg-warning "stack-based buffer over-read" } */ + /* { dg-message "while looking for null terminator for argument 1" "note" { target *-*-* } .-1 } */ +} + +char * +test_uninitialized_str (void) +{ + char str[16]; + return example_fn (str); /* { dg-warning "use of uninitialized value 'str\\\[0\\\]'" } */ +} diff --git a/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nullable-sized.c b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nullable-sized.c new file mode 100644 index 000000000000..28df4b53f6f6 --- /dev/null +++ b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nullable-sized.c @@ -0,0 +1,69 @@ +/* { dg-additional-options "-Wno-stringop-overread" } */ +/* { dg-additional-options "-fpermissive" { target c++ } } */ + +#include "../../gcc.dg/analyzer/analyzer-decls.h" + +char *example_fn (const char *p, __SIZE_TYPE__ n) /* { dg-message "argument 1 of '\[^\n\r\]*' must be a pointer to a null-terminated string" } */ + __attribute__((null_terminated_string_arg (1))) + __attribute__((access (read_only, 1, 2))); +// can be NULL + +char * +test_passthrough (const char* str, __SIZE_TYPE__ n) +{ + return example_fn (str, n); +} + +char * +test_NULL_str_a (void) +{ + return example_fn (NULL, 0); /* { dg-bogus "use of NULL where non-null expected" } */ +} + +char * +test_NULL_str_b (void) +{ + return example_fn (NULL, 4); /* { dg-bogus "use of NULL where non-null expected" } */ +} + +char * +test_unterminated_str (void) +{ + char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is too long" "" { target c++ } } */ + return example_fn (str, 4); /* { dg-warning "stack-based buffer over-read" } */ +} + +char * +test_unterminated_str_truncated (void) +{ + char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is too long" "" { target c++ } } */ + return example_fn (str, 3); /* { dg-bogus "stack-based buffer over-read" } */ +} + +char * +test_uninitialized_str_a (void) +{ + char str[16]; + return example_fn (str, 1); /* { dg-warning "use of uninitialized value 'str\\\[0\\\]'" } */ +} + +char * +test_uninitialized_str_b (void) +{ + char str[16]; + return example_fn (str, 16); /* { dg-warning "use of uninitialized value 'str\\\[0\\\]'" } */ +} + +char * +test_uninitialized_str_c (void) +{ + char str[16]; + return example_fn (str, 17); /* { dg-warning "use of uninitialized value 'str\\\[0\\\]'" } */ +} + +char * +test_uninitialized_str_truncated (void) +{ + char str[16]; + return example_fn (str, 0); /* { dg-bogus "use of uninitialized value" } */ +} diff --git a/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nullable.c b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nullable.c new file mode 100644 index 000000000000..8bbe0b6a8130 --- /dev/null +++ b/gcc/testsuite/c-c++-common/analyzer/attr-null_terminated_string_arg-nullable.c @@ -0,0 +1,34 @@ +#include "../../gcc.dg/analyzer/analyzer-decls.h" + +/* { dg-additional-options "-fpermissive" { target c++ } } */ + +char *example_fn (const char *p) /* { dg-message "argument 1 of '\[^\n\r\]*' must be a pointer to a null-terminated string" } */ + __attribute__((null_terminated_string_arg (1))); +// but can be NULL + +char * +test_passthrough (const char* str) +{ + return example_fn (str); +} + +char * +test_NULL_str (void) +{ + return example_fn (NULL); /* { dg-bogus "use of NULL where non-null expected" } */ +} + +char * +test_unterminated_str (void) +{ + char str[3] = "abc"; /* { dg-warning "initializer-string for '\[^\n\]*' is too long" "" { target c++ } } */ + return example_fn (str); /* { dg-warning "stack-based buffer over-read" } */ + /* { dg-message "while looking for null terminator for argument 1" "note" { target *-*-* } .-1 } */ +} + +char * +test_uninitialized_str (void) +{ + char str[16]; + return example_fn (str); /* { dg-warning "use of uninitialized value 'str\\\[0\\\]'" } */ +} diff --git a/gcc/testsuite/c-c++-common/attr-null_terminated_string_arg.c b/gcc/testsuite/c-c++-common/attr-null_terminated_string_arg.c new file mode 100644 index 000000000000..f2f1d4895804 --- /dev/null +++ b/gcc/testsuite/c-c++-common/attr-null_terminated_string_arg.c @@ -0,0 +1,16 @@ +extern int not_a_function __attribute__((null_terminated_string_arg(1))); /* { dg-warning "'null_terminated_string_arg' attribute only applies to function types" } */ + +extern void no_arg (void) __attribute__((null_terminated_string_arg)); /* { dg-error "wrong number of arguments specified for 'null_terminated_string_arg' attribute" } */ + +extern void arg_idx_not_an_int (int) __attribute__((null_terminated_string_arg ("foo"))); /* { dg-warning "'null_terminated_string_arg' attribute argument has type" } */ + +extern void arg_not_a_pointer (int) __attribute__((null_terminated_string_arg (1))); /* { dg-warning "'null_terminated_string_arg' attribute argument value '1' refers to parameter type 'int'" } */ + +extern void arg_not_a_char_pointer (int) __attribute__((null_terminated_string_arg (1))); /* { dg-warning "'null_terminated_string_arg' attribute argument value '1' refers to parameter type 'int'" } */ + +extern void arg_idx_too_low (const char *) __attribute__((null_terminated_string_arg (0))); /* { dg-warning "'null_terminated_string_arg' attribute argument value '0' does not refer to a function parameter" } */ + +extern void arg_idx_too_high (const char *) __attribute__((null_terminated_string_arg (2))); /* { dg-warning "'null_terminated_string_arg' attribute argument value '2' exceeds the number of function parameters 1" } */ + +extern void valid_non_const (char *) __attribute__((null_terminated_string_arg (1))); +extern void valid_const (const char *) __attribute__((null_terminated_string_arg (1))); -- 2.26.3