From patchwork Tue Aug 29 22:17:57 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: David Malcolm X-Patchwork-Id: 137126 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a59:a7d1:0:b0:3f2:4152:657d with SMTP id p17csp4195165vqm; Tue, 29 Aug 2023 15:18:53 -0700 (PDT) X-Google-Smtp-Source: AGHT+IGQqbjFgOzjGkY+tABWvK7bDXr8njtnykRrEQHB69syh57ADfam6Zr4ySwgNBzwstBo8ZEl X-Received: by 2002:aa7:df0d:0:b0:526:9626:e37d with SMTP id c13-20020aa7df0d000000b005269626e37dmr397896edy.37.1693347533621; Tue, 29 Aug 2023 15:18:53 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1693347533; cv=none; d=google.com; s=arc-20160816; b=uMulHbwJ0tlvKF6lo2XCMqS1/qNnV4Is/if+FtgMrfNr6W/N84vSNIRfrac0RPik+E LTT2EwrybnPfYbdmUFtaPU7pfsgXjXXCfiFrF+B6vxxSjJ9mUfQB2Se23WoIiUtAwexf kEhrN0KXdbXNHwdwdZQpTTkR2XP18v99aIs4NzMBmHh3siPs2R+ir1eZtSRj2ORgwTaQ jH/bCYmqbpbsju9zv/lhgdl/cc43RlOkEpWN3yCTjhmEgVen62TaT6XUXJv/qPuhLJXF dkdu8w8TMRBrxqgxLUrehSreJqjJahEcg09ucKwpt0CrQseqaTJzaFAiAQdZIZAO3dCq 4C8g== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:reply-to:from:list-subscribe:list-help:list-post :list-archive:list-unsubscribe:list-id:precedence :content-transfer-encoding:mime-version:message-id:date:subject:to :dmarc-filter:delivered-to:dkim-signature:dkim-filter; bh=l/Y2HgihoEnpCFAFEsYmByTeborpnDBEsIrVOIrlV1U=; fh=hPrbWPhweUx4V0GV9uXJqbyAzg2ABmTz7kczrAQqMmM=; b=bMNbLtnF8QgpRn42tOtf0enVLwkwtpDsrU9F0FCv+1W3UjOGud2OhTHJfT3cHgkJaB FDWS6jGznPeWQoT22ZePbqTzndGBfoPHJJxH9m2wsaOfOwG8LQ1UMUrekCYKxZFGc3Z1 kBnuG1SzT0iUaSuj1m8XBMgZ5kt33t9QBzAKCn2kZzFWjWwRp3I8S8245E7haZowG2XI 6TFlaATEDKtk5jWAHNejb/TR+9X2Z9CgjTkF2R+scHN3W23VUz92PmS5zitNDdQpVFcy 6fyiX+yYuVqXin5GZB+Ma4ZIdHNRE492ek2KIcgMbBszxT/Rp1nJu3WYrNOvfzMm/kph Z9eQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@gcc.gnu.org header.s=default header.b=g6JoL3wH; spf=pass (google.com: domain of gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org designates 2620:52:3:1:0:246e:9693:128c as permitted sender) smtp.mailfrom="gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org"; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=gnu.org Received: from server2.sourceware.org (server2.sourceware.org. [2620:52:3:1:0:246e:9693:128c]) by mx.google.com with ESMTPS id n19-20020a05640206d300b0052a07f8efafsi6518103edy.565.2023.08.29.15.18.53 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 29 Aug 2023 15:18:53 -0700 (PDT) Received-SPF: pass (google.com: domain of gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org designates 2620:52:3:1:0:246e:9693:128c as permitted sender) client-ip=2620:52:3:1:0:246e:9693:128c; Authentication-Results: mx.google.com; dkim=pass header.i=@gcc.gnu.org header.s=default header.b=g6JoL3wH; spf=pass (google.com: domain of gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org designates 2620:52:3:1:0:246e:9693:128c as permitted sender) smtp.mailfrom="gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org"; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=gnu.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 2D532385840F for ; Tue, 29 Aug 2023 22:18:52 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 2D532385840F DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gcc.gnu.org; s=default; t=1693347532; bh=l/Y2HgihoEnpCFAFEsYmByTeborpnDBEsIrVOIrlV1U=; h=To:Subject:Date:List-Id:List-Unsubscribe:List-Archive:List-Post: List-Help:List-Subscribe:From:Reply-To:From; b=g6JoL3wHj0ALI15zh72NZGpM65ltRksuC/b27hWdm/dpxoBNutuQbo1uI8lQkMLbW hS12XDWXrrSMECtlmOIr0JuG3scb6co4bbR7obAWLg2BBx0a7EpAnWW4IszxzbnW3A TB7C3LE003g6oh+mZJ8pmEHF4y7ZbPPpUwza5gu8= X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTPS id DAFB73858D35 for ; Tue, 29 Aug 2023 22:18:00 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org DAFB73858D35 Received: from mimecast-mx02.redhat.com (66.187.233.73 [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-478-s40-KVZ3Mv2tO8_e9n1POw-1; Tue, 29 Aug 2023 18:17:59 -0400 X-MC-Unique: s40-KVZ3Mv2tO8_e9n1POw-1 Received: from smtp.corp.redhat.com (int-mx10.intmail.prod.int.rdu2.redhat.com [10.11.54.10]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id CE48129A9CA0 for ; Tue, 29 Aug 2023 22:17:58 +0000 (UTC) Received: from t14s.localdomain.com (unknown [10.22.34.135]) by smtp.corp.redhat.com (Postfix) with ESMTP id 8D6A84021C8; Tue, 29 Aug 2023 22:17:58 +0000 (UTC) To: gcc-patches@gcc.gnu.org Subject: [pushed] analyzer: new warning: -Wanalyzer-overlapping-buffers [PR99860] Date: Tue, 29 Aug 2023 18:17:57 -0400 Message-Id: <20230829221757.3870381-1-dmalcolm@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.1 on 10.11.54.10 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-11.7 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, 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 X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: David Malcolm via Gcc-patches From: David Malcolm Reply-To: David Malcolm Errors-To: gcc-patches-bounces+ouuuleilei=gmail.com@gcc.gnu.org Sender: "Gcc-patches" X-getmail-retrieved-from-mailbox: INBOX X-GMAIL-THRID: 1775603583195273284 X-GMAIL-MSGID: 1775603583195273284 Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu. Pushed to trunk as r14-3556-g034d99e81484fb. gcc/ChangeLog: PR analyzer/99860 * Makefile.in (ANALYZER_OBJS): Add analyzer/ranges.o. gcc/analyzer/ChangeLog: PR analyzer/99860 * analyzer-selftests.cc (selftest::run_analyzer_selftests): Call selftest::analyzer_ranges_cc_tests. * analyzer-selftests.h (selftest::run_analyzer_selftests): New decl. * analyzer.opt (Wanalyzer-overlapping-buffers): New option. * call-details.cc: Include "analyzer/ranges.h" and "make-unique.h". (class overlapping_buffers): New. (call_details::complain_about_overlap): New. * call-details.h (call_details::complain_about_overlap): New decl. * kf.cc (kf_memcpy_memmove::impl_call_pre): Call cd.complain_about_overlap for memcpy and memcpy_chk. (kf_strcat::impl_call_pre): Call cd.complain_about_overlap. (kf_strcpy::impl_call_pre): Likewise. * ranges.cc: New file. * ranges.h: New file. gcc/ChangeLog: PR analyzer/99860 * doc/invoke.texi: Add -Wanalyzer-overlapping-buffers. gcc/testsuite/ChangeLog: PR analyzer/99860 * c-c++-common/analyzer/overlapping-buffers.c: New test. --- gcc/Makefile.in | 1 + gcc/analyzer/analyzer-selftests.cc | 1 + gcc/analyzer/analyzer-selftests.h | 1 + gcc/analyzer/analyzer.opt | 4 + gcc/analyzer/call-details.cc | 106 ++++++ gcc/analyzer/call-details.h | 5 + gcc/analyzer/kf.cc | 19 +- gcc/analyzer/ranges.cc | 324 ++++++++++++++++++ gcc/analyzer/ranges.h | 96 ++++++ gcc/doc/invoke.texi | 20 ++ .../analyzer/overlapping-buffers.c | 147 ++++++++ 11 files changed, 722 insertions(+), 2 deletions(-) create mode 100644 gcc/analyzer/ranges.cc create mode 100644 gcc/analyzer/ranges.h create mode 100644 gcc/testsuite/c-c++-common/analyzer/overlapping-buffers.c diff --git a/gcc/Makefile.in b/gcc/Makefile.in index 78779546459f..5930b52462aa 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1313,6 +1313,7 @@ ANALYZER_OBJS = \ analyzer/pending-diagnostic.o \ analyzer/program-point.o \ analyzer/program-state.o \ + analyzer/ranges.o \ analyzer/region.o \ analyzer/region-model.o \ analyzer/region-model-asm.o \ diff --git a/gcc/analyzer/analyzer-selftests.cc b/gcc/analyzer/analyzer-selftests.cc index 63b8cdfa1369..d06b4c374430 100644 --- a/gcc/analyzer/analyzer-selftests.cc +++ b/gcc/analyzer/analyzer-selftests.cc @@ -55,6 +55,7 @@ run_analyzer_selftests () analyzer_function_set_cc_tests (); analyzer_program_point_cc_tests (); analyzer_program_state_cc_tests (); + analyzer_ranges_cc_tests (); analyzer_region_model_cc_tests (); analyzer_sm_file_cc_tests (); analyzer_sm_signal_cc_tests (); diff --git a/gcc/analyzer/analyzer-selftests.h b/gcc/analyzer/analyzer-selftests.h index d848ed9bc941..de494bfceae8 100644 --- a/gcc/analyzer/analyzer-selftests.h +++ b/gcc/analyzer/analyzer-selftests.h @@ -38,6 +38,7 @@ extern void analyzer_constraint_manager_cc_tests (); extern void analyzer_function_set_cc_tests (); extern void analyzer_program_point_cc_tests (); extern void analyzer_program_state_cc_tests (); +extern void analyzer_ranges_cc_tests (); extern void analyzer_region_model_cc_tests (); extern void analyzer_sm_file_cc_tests (); extern void analyzer_sm_signal_cc_tests (); diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt index 7917473d1223..25df89d9c06b 100644 --- a/gcc/analyzer/analyzer.opt +++ b/gcc/analyzer/analyzer.opt @@ -154,6 +154,10 @@ Wanalyzer-out-of-bounds Common Var(warn_analyzer_out_of_bounds) Init(1) Warning Warn about code paths in which a write or read to a buffer is out-of-bounds. +Wanalyzer-overlapping-buffers +Common Var(warn_analyzer_overlapping_buffers) Init(1) Warning +Warn about code paths in which undefined behavior would occur due to overlapping buffers. + Wanalyzer-possible-null-argument Common Var(warn_analyzer_possible_null_argument) Init(1) Warning Warn about code paths in which a possibly-NULL value is passed to a must-not-be-NULL function argument. diff --git a/gcc/analyzer/call-details.cc b/gcc/analyzer/call-details.cc index ce1f859c9996..66fb0fe871e2 100644 --- a/gcc/analyzer/call-details.cc +++ b/gcc/analyzer/call-details.cc @@ -34,8 +34,10 @@ along with GCC; see the file COPYING3. If not see #include "gimple-pretty-print.h" #include "analyzer/region-model.h" #include "analyzer/call-details.h" +#include "analyzer/ranges.h" #include "stringpool.h" #include "attribs.h" +#include "make-unique.h" #if ENABLE_ANALYZER @@ -405,6 +407,110 @@ check_for_null_terminated_string_arg (unsigned arg_idx, out_sval); } +/* A subclass of pending_diagnostic for complaining about overlapping + buffers. */ + +class overlapping_buffers +: public pending_diagnostic_subclass +{ +public: + overlapping_buffers (tree fndecl) + : m_fndecl (fndecl) + { + } + + const char *get_kind () const final override + { + return "overlapping_buffers"; + } + + bool operator== (const overlapping_buffers &other) const + { + return m_fndecl == other.m_fndecl; + } + + int get_controlling_option () const final override + { + return OPT_Wanalyzer_overlapping_buffers; + } + + bool emit (rich_location *rich_loc, logger *) final override + { + auto_diagnostic_group d; + + bool warned; + warned = warning_at (rich_loc, get_controlling_option (), + "overlapping buffers passed as arguments to %qD", + m_fndecl); + + // TODO: draw a picture? + + if (warned) + inform (DECL_SOURCE_LOCATION (m_fndecl), + "the behavior of %qD is undefined for overlapping buffers", + m_fndecl); + + return warned; + } + + label_text describe_final_event (const evdesc::final_event &ev) final override + { + return ev.formatted_print + ("overlapping buffers passed as arguments to %qD", + m_fndecl); + } + +private: + tree m_fndecl; +}; + + +/* Check if the buffers pointed to by arguments ARG_IDX_A and ARG_IDX_B + (zero-based) overlap, when considering them both to be of size + NUM_BYTES_READ_SVAL. + + If they do overlap, complain to the context. */ + +void +call_details::complain_about_overlap (unsigned arg_idx_a, + unsigned arg_idx_b, + const svalue *num_bytes_read_sval) const +{ + region_model_context *ctxt = get_ctxt (); + if (!ctxt) + return; + + region_model *model = get_model (); + region_model_manager *mgr = model->get_manager (); + + const svalue *arg_a_ptr_sval = get_arg_svalue (arg_idx_a); + if (arg_a_ptr_sval->get_kind () == SK_UNKNOWN) + return; + const region *arg_a_reg = model->deref_rvalue (arg_a_ptr_sval, + get_arg_tree (arg_idx_a), + ctxt); + const svalue *arg_b_ptr_sval = get_arg_svalue (arg_idx_b); + if (arg_b_ptr_sval->get_kind () == SK_UNKNOWN) + return; + const region *arg_b_reg = model->deref_rvalue (arg_b_ptr_sval, + get_arg_tree (arg_idx_b), + ctxt); + if (arg_a_reg->get_base_region () != arg_b_reg->get_base_region ()) + return; + + /* Are they within NUM_BYTES_READ_SVAL of each other? */ + symbolic_byte_range byte_range_a (arg_a_reg->get_offset (mgr), + num_bytes_read_sval, + *mgr); + symbolic_byte_range byte_range_b (arg_b_reg->get_offset (mgr), + num_bytes_read_sval, + *mgr); + if (!byte_range_a.intersection (byte_range_b, *model).is_true ()) + return; + + ctxt->warn (make_unique (get_fndecl_for_call ())); +} + } // namespace ana #endif /* #if ENABLE_ANALYZER */ diff --git a/gcc/analyzer/call-details.h b/gcc/analyzer/call-details.h index ae528e4ab116..57b9d6e40ab1 100644 --- a/gcc/analyzer/call-details.h +++ b/gcc/analyzer/call-details.h @@ -79,6 +79,11 @@ public: bool include_terminator, const svalue **out_sval) const; + void + complain_about_overlap (unsigned arg_idx_a, + unsigned arg_idx_b, + const svalue *num_bytes_read_sval) const; + private: const gcall *m_call; region_model *m_model; diff --git a/gcc/analyzer/kf.cc b/gcc/analyzer/kf.cc index 37792aed909c..219421005c16 100644 --- a/gcc/analyzer/kf.cc +++ b/gcc/analyzer/kf.cc @@ -534,8 +534,6 @@ kf_malloc::impl_call_pre (const call_details &cd) const /* Handler for "memcpy" and "__builtin_memcpy", "memmove", and "__builtin_memmove". */ -/* TODO: complain about overlapping src and dest for the memcpy - variants. */ class kf_memcpy_memmove : public builtin_known_function { @@ -592,7 +590,22 @@ kf_memcpy_memmove::impl_call_pre (const call_details &cd) const = model->deref_rvalue (src_ptr_sval, cd.get_arg_tree (1), cd.get_ctxt ()); cd.maybe_set_lhs (dest_ptr_sval); + /* Check for overlap. */ + switch (m_variant) + { + case KF_MEMCPY: + case KF_MEMCPY_CHK: + cd.complain_about_overlap (0, 1, num_bytes_sval); + break; + + case KF_MEMMOVE: + case KF_MEMMOVE_CHK: + /* It's OK for memmove's arguments to overlap. */ + break; + default: + gcc_unreachable (); + } model->copy_bytes (dest_reg, src_reg, cd.get_arg_tree (1), num_bytes_sval, @@ -1221,6 +1234,7 @@ public: } cd.maybe_set_lhs (dest_sval); + cd.complain_about_overlap (0, 1, num_src_bytes_read_sval); const region *offset_reg = mgr->get_offset_region (dest_reg, NULL_TREE, dst_strlen_sval); @@ -1276,6 +1290,7 @@ kf_strcpy::impl_call_pre (const call_details &cd) const if (const svalue *num_bytes_read_sval = cd.check_for_null_terminated_string_arg (1, true, &bytes_to_copy)) { + cd.complain_about_overlap (0, 1, num_bytes_read_sval); model->write_bytes (dest_reg, num_bytes_read_sval, bytes_to_copy, ctxt); } else diff --git a/gcc/analyzer/ranges.cc b/gcc/analyzer/ranges.cc new file mode 100644 index 000000000000..8b1613e6b204 --- /dev/null +++ b/gcc/analyzer/ranges.cc @@ -0,0 +1,324 @@ +/* Symbolic offsets and ranges. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm . + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +. */ + +#include "config.h" +#define INCLUDE_MEMORY +#include "system.h" +#include "coretypes.h" +#include "tree.h" +#include "diagnostic-core.h" +#include "gimple-pretty-print.h" +#include "function.h" +#include "basic-block.h" +#include "gimple.h" +#include "gimple-iterator.h" +#include "diagnostic-core.h" +#include "graphviz.h" +#include "options.h" +#include "cgraph.h" +#include "tree-dfa.h" +#include "stringpool.h" +#include "convert.h" +#include "target.h" +#include "fold-const.h" +#include "tree-pretty-print.h" +#include "bitmap.h" +#include "analyzer/analyzer.h" +#include "analyzer/analyzer-logging.h" +#include "ordered-hash-map.h" +#include "options.h" +#include "analyzer/supergraph.h" +#include "sbitmap.h" +#include "analyzer/call-string.h" +#include "analyzer/program-point.h" +#include "analyzer/store.h" +#include "analyzer/region-model.h" +#include "analyzer/constraint-manager.h" +#include "analyzer/analyzer-selftests.h" +#include "analyzer/ranges.h" + +#if ENABLE_ANALYZER + +namespace ana { + +/* class symbolic_byte_offset. */ + +symbolic_byte_offset::symbolic_byte_offset (int i, region_model_manager &mgr) +: m_num_bytes_sval (mgr.get_or_create_int_cst (size_type_node, i)) +{ +} + +symbolic_byte_offset::symbolic_byte_offset (const svalue *num_bytes_sval) +: m_num_bytes_sval (num_bytes_sval) +{ +} + +symbolic_byte_offset::symbolic_byte_offset (region_offset offset, + region_model_manager &mgr) +{ + if (offset.concrete_p ()) + { + bit_offset_t num_bits = offset.get_bit_offset (); + gcc_assert (num_bits % BITS_PER_UNIT == 0); + byte_offset_t num_bytes = num_bits / BITS_PER_UNIT; + m_num_bytes_sval = mgr.get_or_create_int_cst (size_type_node, num_bytes); + } + else + m_num_bytes_sval = offset.get_symbolic_byte_offset (); +} + +void +symbolic_byte_offset::dump_to_pp (pretty_printer *pp, bool simple) const +{ + pp_string (pp, "byte "); + m_num_bytes_sval->dump_to_pp (pp, simple); +} + +void +symbolic_byte_offset::dump (bool simple) const +{ + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + pp_show_color (&pp) = pp_show_color (global_dc->printer); + pp.buffer->stream = stderr; + dump_to_pp (&pp, simple); + pp_newline (&pp); + pp_flush (&pp); +} + +tree +symbolic_byte_offset::maybe_get_constant () const +{ + return m_num_bytes_sval->maybe_get_constant (); +} + +/* class symbolic_byte_range. */ + +symbolic_byte_range::symbolic_byte_range (region_offset start, + const svalue *num_bytes, + region_model_manager &mgr) +: m_start (start, mgr), + m_size (num_bytes) +{ +} + +void +symbolic_byte_range::dump_to_pp (pretty_printer *pp, + bool simple, + region_model_manager &mgr) const +{ + if (empty_p ()) + { + pp_string (pp, "empty"); + return; + } + + if (tree size_cst = m_size.maybe_get_constant ()) + if (integer_onep (size_cst)) + { + pp_string (pp, "byte "); + m_start.get_svalue ()->dump_to_pp (pp, simple); + return; + } + + pp_string (pp, "bytes "); + m_start.get_svalue ()->dump_to_pp (pp, simple); + pp_string (pp, " to "); + get_last_byte_offset (mgr).get_svalue ()->dump_to_pp (pp, simple); +} + +void +symbolic_byte_range::dump (bool simple, region_model_manager &mgr) const +{ + pretty_printer pp; + pp_format_decoder (&pp) = default_tree_printer; + pp_show_color (&pp) = pp_show_color (global_dc->printer); + pp.buffer->stream = stderr; + dump_to_pp (&pp, simple, mgr); + pp_newline (&pp); + pp_flush (&pp); +} + +bool +symbolic_byte_range::empty_p () const +{ + tree cst = m_size.maybe_get_constant (); + if (!cst) + return false; + return zerop (cst); +} + +symbolic_byte_offset +symbolic_byte_range::get_last_byte_offset (region_model_manager &mgr) const +{ + gcc_assert (!empty_p ()); + const symbolic_byte_offset one (1, mgr); + return symbolic_byte_offset + (mgr.get_or_create_binop (size_type_node, + MINUS_EXPR, + get_next_byte_offset (mgr).get_svalue (), + one.get_svalue ())); +} + +symbolic_byte_offset +symbolic_byte_range::get_next_byte_offset (region_model_manager &mgr) const +{ + return symbolic_byte_offset (mgr.get_or_create_binop (size_type_node, + PLUS_EXPR, + m_start.get_svalue (), + m_size.get_svalue ())); +} + +/* Attempt to determine if THIS range intersects OTHER, + using constraints from MODEL. */ + +tristate +symbolic_byte_range::intersection (const symbolic_byte_range &other, + const region_model &model) const +{ + /* For brevity, consider THIS to be "range A", and OTHER to be "range B". */ + + region_model_manager *mgr = model.get_manager (); + + const svalue *first_sval_a = m_start.get_svalue (); + const svalue *first_sval_b = other.m_start.get_svalue (); + const svalue *last_sval_a = get_last_byte_offset (*mgr).get_svalue (); + const svalue *last_sval_b = other.get_last_byte_offset (*mgr).get_svalue (); + + if (m_size.get_svalue ()->get_kind () == SK_UNKNOWN + || other.m_size.get_svalue ()->get_kind () == SK_UNKNOWN) + { + if (first_sval_a == first_sval_b) + return tristate::TS_TRUE; + else + return tristate::TS_UNKNOWN; + } + + if (first_sval_a == first_sval_b) + return tristate::TS_TRUE; + + /* Is B fully before A? */ + tristate b_fully_before_a = model.eval_condition (last_sval_b, + LT_EXPR, + first_sval_a); + /* Is B fully after A? */ + tristate b_fully_after_a = model.eval_condition (first_sval_b, + GT_EXPR, + last_sval_a); + + if (b_fully_before_a.is_true () + || b_fully_after_a.is_true ()) + return tristate::TS_FALSE; + + if (b_fully_before_a.is_unknown () + || b_fully_after_a.is_unknown ()) + return tristate::TS_UNKNOWN; + + return tristate::TS_TRUE; +} + +#if CHECKING_P + +namespace selftest { + +static void test_intersects (void) +{ + region_model_manager mgr; + region_model m (&mgr); + + /* Test various concrete ranges. */ + symbolic_byte_offset zero (0, mgr); + symbolic_byte_offset one (1, mgr); + symbolic_byte_offset five (5, mgr); + symbolic_byte_offset nine (9, mgr); + symbolic_byte_offset ten (10, mgr); + + symbolic_byte_range r0_9 (zero, ten); + symbolic_byte_range r0 (zero, one); + symbolic_byte_range r5_9 (five, five); + symbolic_byte_range r9 (nine, one); + symbolic_byte_range r10 (ten, one); + symbolic_byte_range r10_19 (ten, ten); + + ASSERT_EQ (r0_9.get_start_byte_offset (), zero); + ASSERT_EQ (r0_9.get_size_in_bytes (), ten); + ASSERT_EQ (r0_9.get_next_byte_offset (mgr), ten); + ASSERT_EQ (r0_9.get_last_byte_offset (mgr), nine); + + ASSERT_EQ (r0_9.intersection (r0, m), tristate::TS_TRUE); + ASSERT_EQ (r0.intersection (r0_9, m), tristate::TS_TRUE); + ASSERT_EQ (r0_9.intersection (r9, m), tristate::TS_TRUE); + ASSERT_EQ (r9.intersection (r0_9, m), tristate::TS_TRUE); + ASSERT_EQ (r0_9.intersection (r10, m), tristate::TS_FALSE); + ASSERT_EQ (r10.intersection (r0_9, m), tristate::TS_FALSE); + + ASSERT_EQ (r5_9.intersection (r0, m), tristate::TS_FALSE); + ASSERT_EQ (r0.intersection (r5_9, m), tristate::TS_FALSE); + ASSERT_EQ (r9.intersection (r5_9, m), tristate::TS_TRUE); + ASSERT_EQ (r10.intersection (r5_9, m), tristate::TS_FALSE); + + /* Test various symbolic ranges. */ + tree x = build_global_decl ("x", size_type_node); + const svalue *x_init_sval = m.get_rvalue (x, nullptr); + tree y = build_global_decl ("y", size_type_node); + const svalue *y_init_sval = m.get_rvalue (y, nullptr); + + symbolic_byte_range r0_x_minus_1 (zero, x_init_sval); + symbolic_byte_range rx (x_init_sval, one); + symbolic_byte_range r0_y_minus_1 (zero, y_init_sval); + symbolic_byte_range ry (y_init_sval, one); + symbolic_byte_range rx_x_plus_y_minus_1 (x_init_sval, y_init_sval); + + ASSERT_EQ (rx_x_plus_y_minus_1.get_start_byte_offset (), x_init_sval); + ASSERT_EQ (rx_x_plus_y_minus_1.get_size_in_bytes (), y_init_sval); + ASSERT_EQ + (rx_x_plus_y_minus_1.get_next_byte_offset (mgr).get_svalue ()->get_kind (), + SK_BINOP); + ASSERT_EQ + (rx_x_plus_y_minus_1.get_last_byte_offset (mgr).get_svalue ()->get_kind (), + SK_BINOP); + + ASSERT_EQ (rx.intersection (ry, m), tristate::TS_UNKNOWN); + ASSERT_EQ (r0_x_minus_1.intersection (r0, m), tristate::TS_TRUE); +#if 0 + ASSERT_EQ (r0_x_minus_1.intersection (rx, m), tristate::TS_FALSE); + /* Fails (with UNKNOWN): b_fully_after_a is UNKNOWN, when it could + be TRUE: last of A is (x - 1), but it's not necessarily true that + X > (x - 1), for the case where x is (unsigned)0. */ +#endif + ASSERT_EQ (r0_x_minus_1.intersection (r0_y_minus_1, m), tristate::TS_TRUE); + // TODO: etc +} + +/* Run all of the selftests within this file. */ + +void +analyzer_ranges_cc_tests () +{ + test_intersects (); +} + +} // namespace selftest + +#endif /* CHECKING_P */ + +} // namespace ana + +#endif /* #if ENABLE_ANALYZER */ diff --git a/gcc/analyzer/ranges.h b/gcc/analyzer/ranges.h new file mode 100644 index 000000000000..cc72469a9023 --- /dev/null +++ b/gcc/analyzer/ranges.h @@ -0,0 +1,96 @@ +/* Symbolic offsets and ranges. + Copyright (C) 2023 Free Software Foundation, Inc. + Contributed by David Malcolm . + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GCC; see the file COPYING3. If not see +. */ + +#ifndef GCC_ANALYZER_RANGES_H +#define GCC_ANALYZER_RANGES_H + +namespace ana { + +/* Wrapper around an svalue for a value measured in bytes. */ + +class symbolic_byte_offset +{ +public: + explicit symbolic_byte_offset (int i, region_model_manager &mgr); + symbolic_byte_offset (const svalue *num_bytes_sval); + explicit symbolic_byte_offset (region_offset offset, + region_model_manager &mgr); + + const svalue *get_svalue () const { return m_num_bytes_sval; } + tree maybe_get_constant () const; + + void dump_to_pp (pretty_printer *pp, bool) const; + void dump (bool) const; + + bool operator== (const symbolic_byte_offset &other) const + { + return m_num_bytes_sval == other.m_num_bytes_sval; + } + +private: + const svalue *m_num_bytes_sval; +}; + +/* A range of byte offsets, where both the start and size of the + range can be symbolic. */ + +class symbolic_byte_range +{ +public: + symbolic_byte_range (symbolic_byte_offset start, + symbolic_byte_offset size) + : m_start (start), + m_size (size) + { + } + + symbolic_byte_range (region_offset start, + const svalue *num_bytes, + region_model_manager &mgr); + + void dump_to_pp (pretty_printer *pp, + bool simple, + region_model_manager &mgr) const; + void dump (bool, region_model_manager &mgr) const; + + bool empty_p () const; + + symbolic_byte_offset get_start_byte_offset () const + { + return m_start; + } + symbolic_byte_offset get_last_byte_offset (region_model_manager &mgr) const; + symbolic_byte_offset get_size_in_bytes () const + { + return m_size; + } + symbolic_byte_offset get_next_byte_offset (region_model_manager &mgr) const; + + tristate intersection (const symbolic_byte_range &other, + const region_model &model) const; + +private: + symbolic_byte_offset m_start; + symbolic_byte_offset m_size; +}; + +} // namespace ana + +#endif /* GCC_ANALYZER_RANGES_H */ diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index a32dabf0405f..703aaaed13c6 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -474,6 +474,7 @@ Objective-C and Objective-C++ Dialects}. -Wno-analyzer-null-argument -Wno-analyzer-null-dereference -Wno-analyzer-out-of-bounds +-Wno-analyzer-overlapping-buffers -Wno-analyzer-possible-null-argument -Wno-analyzer-possible-null-dereference -Wno-analyzer-putenv-of-auto-var @@ -10319,6 +10320,7 @@ Enabling this option effectively enables the following warnings: -Wanalyzer-null-argument -Wanalyzer-null-dereference -Wanalyzer-out-of-bounds +-Wanalyzer-overlapping-buffers -Wanalyzer-possible-null-argument -Wanalyzer-possible-null-dereference -Wanalyzer-putenv-of-auto-var @@ -10664,6 +10666,24 @@ involved, the direction of the access (read vs write), and, in some cases, the values of data involved. This diagram can be suppressed using @option{-fdiagnostics-text-art-charset=none}. +@opindex Wanalyzer-overlapping-buffers +@opindex Wno-analyzer-overlapping-buffers +@item -Wno-analyzer-overlapping-buffers +This warning requires @option{-fanalyzer}, which enables it; use +@option{-Wno-analyzer-overlapping-buffers} to disable it. + +This diagnostic warns for paths through the code in which overlapping +buffers are passed to an API for which the behavior on such buffers +is undefined. + +Specifically, the diagnostic occurs on calls to the following functions +@itemize @bullet +@item @code{memcpy} +@item @code{strcat} +@item @code{strcpy} +@end itemize +for cases where the buffers are known to overlap. + @opindex Wanalyzer-possible-null-argument @opindex Wno-analyzer-possible-null-argument @item -Wno-analyzer-possible-null-argument diff --git a/gcc/testsuite/c-c++-common/analyzer/overlapping-buffers.c b/gcc/testsuite/c-c++-common/analyzer/overlapping-buffers.c new file mode 100644 index 000000000000..5808b3304cfc --- /dev/null +++ b/gcc/testsuite/c-c++-common/analyzer/overlapping-buffers.c @@ -0,0 +1,147 @@ +/* Test of -Wanalyzer-overlapping-buffers. */ + +#include + +/* Use noinline functions to hide these calls from the optimizer, to avoid + undefined behavior being optimized away to GIMPLE_NOP before the analyzer + sees it. */ + +char * __attribute__((noinline)) +call_strcat_symbolic_1 (char *dest, const char *src) +{ + return strcat (dest, src); /* { dg-warning "overlapping buffers" } */ +} + +void test_strcat_symbolic_1 (char *p) +{ + call_strcat_symbolic_1 (p, p); +} + +char * __attribute__((noinline)) +call_strcpy_symbolic_1 (char *dest, const char *src) +{ + return strcpy (dest, src); /* { dg-warning "overlapping buffers" } */ +} + +void test_strcpy_symbolic_1 (char *p) +{ + call_strcpy_symbolic_1 (p, p); +} + +void * __attribute__((noinline)) +call_memcpy_concrete_1 (void *dest, const void *src, size_t n) +{ + return memcpy (dest, src, n); /* { dg-warning "overlapping buffers" } */ +} + +void test_memcpy_concrete_1 (void *p) +{ + call_memcpy_concrete_1 (p, p, 10); +} + +void * __attribute__((noinline)) +call_memcpy_symbolic_1 (void *dest, const void *src, size_t n) +{ + return memcpy (dest, src, n); /* { dg-warning "overlapping buffers" } */ +} + +void test_memcpy_symbolic_1 (void *p, size_t n) +{ + call_memcpy_symbolic_1 (p, p, n); +} + +/* Intersecting vs non-intersecting parts of the same buffer. */ + +void * __attribute__((noinline)) +call_memcpy_nonintersecting_concrete_1 (void *dest, const void *src, size_t n) +{ + return memcpy (dest, src, n); /* { dg-bogus "overlapping buffers" } */ +} + +void test_memcpy_nonintersecting_concrete_1 (char *p) +{ + call_memcpy_nonintersecting_concrete_1 (p, p + 10, 10); +} + +void * __attribute__((noinline)) +call_memcpy_nonintersecting_concrete_2 (void *dest, const void *src, size_t n) +{ + return memcpy (dest, src, n); /* { dg-bogus "overlapping buffers" } */ +} + +void test_memcpy_nonintersecting_concrete_2 (char *p) +{ + call_memcpy_nonintersecting_concrete_2 (p + 10, p, 10); +} + +void * __attribute__((noinline)) +call_memcpy_intersecting_concrete_1 (void *dest, const void *src, size_t n) +{ + return memcpy (dest, src, n); /* { dg-warning "overlapping buffers" } */ +} + +void test_memcpy_intersecting_concrete_1 (char *p) +{ + call_memcpy_intersecting_concrete_1 (p, p + 9, 10); +} + +void * __attribute__((noinline)) +call_memcpy_intersecting_concrete_2 (void *dest, const void *src, size_t n) +{ + return memcpy (dest, src, n); /* { dg-warning "overlapping buffers" } */ +} + +void test_memcpy_intersecting_concrete_2 (char *p) +{ + call_memcpy_intersecting_concrete_2 (p + 9, p, 10); +} + +void * __attribute__((noinline)) +call_memcpy_intersecting_symbolic_1 (void *dest, const void *src, size_t n) +{ + return memcpy (dest, src, n); /* { dg-warning "overlapping buffers" "" { xfail *-*-* } } */ + // TODO(xfail) +} + +void test_memcpy_intersecting_symbolic_1 (char *p, size_t n) +{ + call_memcpy_intersecting_symbolic_1 (p, p + 1, n); +} + +void * __attribute__((noinline)) +call_memcpy_nonintersecting_symbolic_1 (void *dest, const void *src, size_t n) +{ + return memcpy (dest, src, n); /* { dg-bogus "overlapping buffers" } */ +} + +void test_memcpy_nonintersecting_symbolic_1 (char *p, size_t n) +{ + call_memcpy_nonintersecting_symbolic_1 (p, p + n, n); +} + +void * __attribute__((noinline)) +call_memcpy_nonintersecting_symbolic_2 (void *dest, const void *src, size_t n) +{ + return memcpy (dest, src, n); /* { dg-bogus "overlapping buffers" } */ +} + +void test_memcpy_nonintersecting_symbolic_2 (char *p, size_t n) +{ + call_memcpy_nonintersecting_symbolic_2 (p + n, p, n); +} +/* It's OK for memmove's arguments to overlap. */ + +void * __attribute__((noinline)) +call_memmove_symbolic_1 (void *dest, const void *src, size_t n) +{ + return memmove (dest, src, n); /* { dg-bogus "overlapping buffers" } */ +} + +void test_memmove_symbolic_1 (void *p, size_t n) +{ + call_memmove_symbolic_1 (p, p, n); +} + +/* TODO: + - strncpy + */