From patchwork Mon May 16 19:41:13 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: David Malcolm X-Patchwork-Id: 54046 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 92FCF385624D for ; Mon, 16 May 2022 19:42:43 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 92FCF385624D DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gcc.gnu.org; s=default; t=1652730163; bh=4ZW2qS66XWKXunOsxkii+DMZ99dqoXUuLZdvDXSwXS4=; h=To:Subject:Date:List-Id:List-Unsubscribe:List-Archive:List-Post: List-Help:List-Subscribe:From:Reply-To:From; b=fOZbJ3RHhyZW48Voiivpcf0syOt1CzFI6qQpQzn/9QOi1aqMIBfhNUs59YUkMc2H2 cXMzC8lFVgUg3o0mmBeSmfuSxweFy1S03XZYwQkrszIVyihOuEUAnimz31sRFJmz+J mbV87YUQIIRHfZosQqUuxf0ib6Po78ihvk3ZW/Mw= 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 90886385624A for ; Mon, 16 May 2022 19:41:17 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org 90886385624A Received: from mimecast-mx02.redhat.com (mx3-rdu2.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-208-rA9bWnS9MfOh500rYSmWpw-1; Mon, 16 May 2022 15:41:15 -0400 X-MC-Unique: rA9bWnS9MfOh500rYSmWpw-1 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.rdu2.redhat.com [10.11.54.3]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 8B11B29AB45D for ; Mon, 16 May 2022 19:41:15 +0000 (UTC) Received: from t14s.localdomain.com (unknown [10.2.17.178]) by smtp.corp.redhat.com (Postfix) with ESMTP id 3C48F1121314; Mon, 16 May 2022 19:41:15 +0000 (UTC) To: gcc-patches@gcc.gnu.org Subject: [committed] analyzer: implement four new warnings for misuses [PR105103] Date: Mon, 16 May 2022 15:41:13 -0400 Message-Id: <20220516194113.3130440-1-dmalcolm@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.78 on 10.11.54.3 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-12.3 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_LOW, SPF_HELO_NONE, SPF_NONE, TXREP, T_SCC_BODY_TEXT_LINE 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.29 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+patchwork=sourceware.org@gcc.gnu.org Sender: "Gcc-patches" This patch adds support to the analyzer for checking usage of , with four new warnings. It adds: (a) a state-machine for tracking "started" and "ended" states on va_list instances, implementing two new warnings: -Wanalyzer-va-list-leak for complaining about missing va_end after a va_start or va_copy -Wanalyzer-va-list-use-after-va-end: for complaining about va_arg or va_copy used on a va_list that's had va_end called on it (b) interprocedural tracking of variadic parameters, tracking symbolic values, implementing two new warnings: -Wanalyzer-va-arg-type-mismatch for type-checking va_arg usage against the types of the parameters that were actually passed to the variadic call -Wanalyzer-va-list-exhausted for complaining if va_arg is used too many times on a va_list Here's an LTO example of a type mismatch in a variadic call that straddles two source files: stdarg-lto-1-a.c: In function 'called_by_test_type_mismatch_1': stdarg-lto-1-a.c:19:7: warning: 'va_arg' expected 'const char *' but received 'int' for variadic argument 1 of 'ap' [-Wanalyzer-va-arg-type-mismatch] 19 | str = va_arg (ap, const char *); | ^ 'test_type_mismatch_1': events 1-2 | |stdarg-lto-1-b.c:3:6: | 3 | void test_type_mismatch_1 (void) | | ^ | | | | | (1) entry to 'test_type_mismatch_1' | 4 | { | 5 | called_by_test_type_mismatch_1 (42, 1066); | | ~ | | | | | (2) calling 'called_by_test_type_mismatch_1' from 'test_type_mismatch_1' with 1 variadic argument | +--> 'called_by_test_type_mismatch_1': events 3-4 | |stdarg-lto-1-a.c:12:1: | 12 | called_by_test_type_mismatch_1 (int placeholder, ...) | | ^ | | | | | (3) entry to 'called_by_test_type_mismatch_1' |...... | 19 | str = va_arg (ap, const char *); | | ~ | | | | | (4) 'va_arg' expected 'const char *' but received 'int' for variadic argument 1 of 'ap' | Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu. Lightly tested with powerpc64le-linux-gnu and pdp11-aout. Pushed to trunk as r13-514-g2402dc6b982c4d. gcc/ChangeLog: PR analyzer/105103 * Makefile.in (ANALYZER_OBJS): Add analyzer/varargs.o. * doc/invoke.texi: Add -Wanalyzer-va-arg-type-mismatch, -Wanalyzer-va-list-exhausted, -Wanalyzer-va-list-leak, and -Wanalyzer-va-list-use-after-va-end. gcc/analyzer/ChangeLog: PR analyzer/105103 * analyzer.cc (make_label_text_n): New. * analyzer.h (class var_arg_region): New forward decl. (make_label_text_n): New decl. * analyzer.opt (Wanalyzer-va-arg-type-mismatch): New option. (Wanalyzer-va-list-exhausted): New option. (Wanalyzer-va-list-leak): New option. (Wanalyzer-va-list-use-after-va-end): New option. * checker-path.cc (call_event::get_desc): Split out decl access into.. (call_event::get_caller_fndecl): ...this new function and... (call_event::get_callee_fndecl): ...this new function. * checker-path.h (call_event::get_desc): Drop "FINAL". (call_event::get_caller_fndecl): New decl. (call_event::get_callee_fndecl): New decl. (class call_event): Make fields protected. * diagnostic-manager.cc (null_assignment_sm_context::warn): New overload. (null_assignment_sm_context::get_new_program_state): New. (diagnostic_manager::add_events_for_superedge): Move case SUPEREDGE_CALL to a new pending_diagnostic::add_call_event vfunc. * engine.cc (impl_sm_context::warn): Implement new override. (impl_sm_context::get_new_program_state): New. * pending-diagnostic.cc: Include "analyzer/diagnostic-manager.h", "cpplib.h", "digraph.h", "ordered-hash-map.h", "cfg.h", "basic-block.h", "gimple.h", "gimple-iterator.h", "cgraph.h" "analyzer/supergraph.h", "analyzer/program-state.h", "alloc-pool.h", "fibonacci_heap.h", "shortest-paths.h", "sbitmap.h", "analyzer/exploded-graph.h", "diagnostic-path.h", and "analyzer/checker-path.h". (ht_ident_eq): New. (fixup_location_in_macro_p): New. (pending_diagnostic::fixup_location): New. (pending_diagnostic::add_call_event): New. * pending-diagnostic.h (pending_diagnostic::fixup_location): Drop no-op inline implementation in favor of the more complex implementation above. (pending_diagnostic::add_call_event): New vfunc. * region-model-impl-calls.cc: Include "analyzer/sm.h", "diagnostic-path.h", and "analyzer/pending-diagnostic.h". * region-model-manager.cc (region_model_manager::get_var_arg_region): New. (region_model_manager::log_stats): Log m_var_arg_regions. * region-model.cc (region_model::on_call_pre): Handle IFN_VA_ARG, BUILT_IN_VA_START, and BUILT_IN_VA_COPY. (region_model::on_call_post): Handle BUILT_IN_VA_END. (region_model::get_representative_path_var_1): Handle RK_VAR_ARG. (region_model::push_frame): Push variadic arguments. * region-model.h (region_model_manager::get_var_arg_region): New decl. (region_model_manager::m_var_arg_regions): New field. (region_model::impl_call_va_start): New decl. (region_model::impl_call_va_copy): New decl. (region_model::impl_call_va_arg): New decl. (region_model::impl_call_va_end): New decl. * region.cc (alloca_region::dump_to_pp): Dump the id. (var_arg_region::dump_to_pp): New. (var_arg_region::get_frame_region): New. * region.h (enum region_kind): Add RK_VAR_ARG. (region::dyn_cast_var_arg_region): New. (class var_arg_region): New. (is_a_helper ::test): New. (struct default_hash_traits): New. * sm.cc (make_checkers): Call make_va_list_state_machine. * sm.h (sm_context::warn): New vfunc. (sm_context::get_old_svalue): Drop unused decl. (sm_context::get_new_program_state): New vfunc. (make_va_list_state_machine): New decl. * varargs.cc: New file. gcc/testsuite/ChangeLog: PR analyzer/105103 * gcc.dg/analyzer/stdarg-1.c: New test. * gcc.dg/analyzer/stdarg-2.c: New test. * gcc.dg/analyzer/stdarg-fmtstring-1.c: New test. * gcc.dg/analyzer/stdarg-lto-1-a.c: New test. * gcc.dg/analyzer/stdarg-lto-1-b.c: New test. * gcc.dg/analyzer/stdarg-lto-1.h: New test. * gcc.dg/analyzer/stdarg-sentinel-1.c: New test. * gcc.dg/analyzer/stdarg-types-1.c: New test. * gcc.dg/analyzer/stdarg-types-2.c: New test. Signed-off-by: David Malcolm --- gcc/Makefile.in | 3 +- gcc/analyzer/analyzer.cc | 38 + gcc/analyzer/analyzer.h | 4 + gcc/analyzer/analyzer.opt | 16 + gcc/analyzer/checker-path.cc | 16 +- gcc/analyzer/checker-path.h | 6 +- gcc/analyzer/diagnostic-manager.cc | 19 +- gcc/analyzer/engine.cc | 22 + gcc/analyzer/pending-diagnostic.cc | 82 ++ gcc/analyzer/pending-diagnostic.h | 11 +- gcc/analyzer/region-model-impl-calls.cc | 3 + gcc/analyzer/region-model-manager.cc | 20 + gcc/analyzer/region-model.cc | 26 + gcc/analyzer/region-model.h | 9 + gcc/analyzer/region.cc | 32 +- gcc/analyzer/region.h | 87 +- gcc/analyzer/sm.cc | 1 + gcc/analyzer/sm.h | 6 +- gcc/analyzer/varargs.cc | 1025 +++++++++++++++++ gcc/doc/invoke.texi | 55 + gcc/testsuite/gcc.dg/analyzer/stdarg-1.c | 433 +++++++ gcc/testsuite/gcc.dg/analyzer/stdarg-2.c | 436 +++++++ .../gcc.dg/analyzer/stdarg-fmtstring-1.c | 103 ++ .../gcc.dg/analyzer/stdarg-lto-1-a.c | 24 + .../gcc.dg/analyzer/stdarg-lto-1-b.c | 6 + gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1.h | 1 + .../gcc.dg/analyzer/stdarg-sentinel-1.c | 25 + .../gcc.dg/analyzer/stdarg-types-1.c | 25 + .../gcc.dg/analyzer/stdarg-types-2.c | 55 + 29 files changed, 2567 insertions(+), 22 deletions(-) create mode 100644 gcc/analyzer/varargs.cc create mode 100644 gcc/testsuite/gcc.dg/analyzer/stdarg-1.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/stdarg-2.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/stdarg-fmtstring-1.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1-a.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1-b.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1.h create mode 100644 gcc/testsuite/gcc.dg/analyzer/stdarg-sentinel-1.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/stdarg-types-1.c create mode 100644 gcc/testsuite/gcc.dg/analyzer/stdarg-types-2.c diff --git a/gcc/Makefile.in b/gcc/Makefile.in index 31ff95500c9..70f7d2191f1 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1278,7 +1278,8 @@ ANALYZER_OBJS = \ analyzer/store.o \ analyzer/supergraph.o \ analyzer/svalue.o \ - analyzer/trimmed-graph.o + analyzer/trimmed-graph.o \ + analyzer/varargs.o # Language-independent object files. # We put the *-match.o and insn-*.o files first so that a parallel make diff --git a/gcc/analyzer/analyzer.cc b/gcc/analyzer/analyzer.cc index 2c63a539fcb..c85dbf331d5 100644 --- a/gcc/analyzer/analyzer.cc +++ b/gcc/analyzer/analyzer.cc @@ -446,4 +446,42 @@ make_label_text (bool can_colorize, const char *fmt, ...) return result; } +/* As above, but with singular vs plural. */ + +label_text +make_label_text_n (bool can_colorize, int n, + const char *singular_fmt, + const char *plural_fmt, ...) +{ + pretty_printer *pp = global_dc->printer->clone (); + pp_clear_output_area (pp); + + if (!can_colorize) + pp_show_color (pp) = false; + + text_info ti; + rich_location rich_loc (line_table, UNKNOWN_LOCATION); + + va_list ap; + + va_start (ap, plural_fmt); + + const char *fmt = ngettext (singular_fmt, plural_fmt, n); + + ti.format_spec = fmt; + ti.args_ptr = ≈ + ti.err_no = 0; + ti.x_data = NULL; + ti.m_richloc = &rich_loc; + + pp_format (pp, &ti); + pp_output_formatted_text (pp); + + va_end (ap); + + label_text result = label_text::take (xstrdup (pp_formatted_text (pp))); + delete pp; + return result; +} + #endif /* #if ENABLE_ANALYZER */ diff --git a/gcc/analyzer/analyzer.h b/gcc/analyzer/analyzer.h index 39934a302cb..dcefc13a546 100644 --- a/gcc/analyzer/analyzer.h +++ b/gcc/analyzer/analyzer.h @@ -69,6 +69,7 @@ class region; class field_region; class string_region; class bit_range_region; + class var_arg_region; class region_model_manager; class conjured_purge; struct model_merger; @@ -296,6 +297,9 @@ extern const char *get_user_facing_name (const gcall *call); extern void register_analyzer_pass (); extern label_text make_label_text (bool can_colorize, const char *fmt, ...); +extern label_text make_label_text_n (bool can_colorize, int n, + const char *singular_fmt, + const char *plural_fmt, ...); extern bool fndecl_has_gimple_body_p (tree fndecl); diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt index a0ba2c94fb3..23dfc797cea 100644 --- a/gcc/analyzer/analyzer.opt +++ b/gcc/analyzer/analyzer.opt @@ -142,6 +142,22 @@ Wanalyzer-use-of-pointer-in-stale-stack-frame Common Var(warn_analyzer_use_of_pointer_in_stale_stack_frame) Init(1) Warning Warn about code paths in which a pointer to a stale stack frame is used. +Wanalyzer-va-arg-type-mismatch +Common Var(warn_analyzer_va_arg_type_mismatch) Init(1) Warning +Warn about code paths in which va_arg uses the wrong type. + +Wanalyzer-va-list-exhausted +Common Var(warn_analyzer_va_list_exhausted) Init(1) Warning +Warn about code paths in which va_arg is used too many times on a va_list. + +Wanalyzer-va-list-leak +Common Var(warn_analyzer_va_list_leak) Init(1) Warning +Warn about code paths in which va_start or va_copy is used without a corresponding va_end. + +Wanalyzer-va-list-use-after-va-end +Common Var(warn_analyzer_va_list_use_after_va_end) Init(1) Warning +Warn about code paths in which a va_list is used after va_end. + Wanalyzer-write-to-const Common Var(warn_analyzer_write_to_const) Init(1) Warning Warn about code paths which attempt to write to a const object. diff --git a/gcc/analyzer/checker-path.cc b/gcc/analyzer/checker-path.cc index a61b3ee0675..5fdbc388390 100644 --- a/gcc/analyzer/checker-path.cc +++ b/gcc/analyzer/checker-path.cc @@ -686,8 +686,8 @@ call_event::get_desc (bool can_colorize) const return make_label_text (can_colorize, "calling %qE from %qE", - m_dest_snode->m_fun->decl, - m_src_snode->m_fun->decl); + get_callee_fndecl (), + get_caller_fndecl ()); } /* Override of checker_event::is_call_p for calls. */ @@ -698,6 +698,18 @@ call_event::is_call_p () const return true; } +tree +call_event::get_caller_fndecl () const +{ + return m_src_snode->m_fun->decl; +} + +tree +call_event::get_callee_fndecl () const +{ + return m_dest_snode->m_fun->decl; +} + /* class return_event : public superedge_event. */ /* return_event's ctor. */ diff --git a/gcc/analyzer/checker-path.h b/gcc/analyzer/checker-path.h index d37c99989b5..545d7db06a2 100644 --- a/gcc/analyzer/checker-path.h +++ b/gcc/analyzer/checker-path.h @@ -352,10 +352,14 @@ public: call_event (const exploded_edge &eedge, location_t loc, tree fndecl, int depth); - label_text get_desc (bool can_colorize) const FINAL OVERRIDE; + label_text get_desc (bool can_colorize) const OVERRIDE; bool is_call_p () const FINAL OVERRIDE; +protected: + tree get_caller_fndecl () const; + tree get_callee_fndecl () const; + const supernode *m_src_snode; const supernode *m_dest_snode; }; diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc index 2d49a3bc6ad..e8a828d748d 100644 --- a/gcc/analyzer/diagnostic-manager.cc +++ b/gcc/analyzer/diagnostic-manager.cc @@ -1665,6 +1665,11 @@ struct null_assignment_sm_context : public sm_context { delete d; } + void warn (const supernode *, const gimple *, + const svalue *, pending_diagnostic *d) FINAL OVERRIDE + { + delete d; + } tree get_diagnostic_tree (tree expr) FINAL OVERRIDE { @@ -1707,6 +1712,10 @@ struct null_assignment_sm_context : public sm_context { return m_old_state; } + const program_state *get_new_program_state () const FINAL OVERRIDE + { + return m_new_state; + } const program_state *m_old_state; const program_state *m_new_state; @@ -2048,15 +2057,7 @@ diagnostic_manager::add_events_for_superedge (const path_builder &pb, break; case SUPEREDGE_CALL: - { - emission_path->add_event - (new call_event (eedge, - (last_stmt - ? last_stmt->location - : UNKNOWN_LOCATION), - src_point.get_fndecl (), - src_stack_depth)); - } + pd->add_call_event (eedge, emission_path); break; case SUPEREDGE_INTRAPROCEDURAL_CALL: diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index 03329324346..1b1c38f3116 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -435,6 +435,23 @@ public: var, var_old_sval, current, d); } + void warn (const supernode *snode, const gimple *stmt, + const svalue *sval, pending_diagnostic *d) FINAL OVERRIDE + { + LOG_FUNC (get_logger ()); + gcc_assert (d); // take ownership + impl_region_model_context old_ctxt + (m_eg, m_enode_for_diag, m_old_state, m_new_state, NULL, NULL, NULL); + + state_machine::state_t current + = (sval + ? m_old_smap->get_state (sval, m_eg.get_ext_state ()) + : m_old_smap->get_global_state ()); + m_eg.get_diagnostic_manager ().add_diagnostic + (&m_sm, m_enode_for_diag, snode, stmt, m_stmt_finder, + NULL_TREE, sval, current, d); + } + /* Hook for picking more readable trees for SSA names of temporaries, so that rather than e.g. "double-free of ''" @@ -512,6 +529,11 @@ public: return m_old_state; } + const program_state *get_new_program_state () const FINAL OVERRIDE + { + return m_new_state; + } + log_user m_logger; exploded_graph &m_eg; exploded_node *m_enode_for_diag; diff --git a/gcc/analyzer/pending-diagnostic.cc b/gcc/analyzer/pending-diagnostic.cc index 5e0ea4c31ac..eff050f6757 100644 --- a/gcc/analyzer/pending-diagnostic.cc +++ b/gcc/analyzer/pending-diagnostic.cc @@ -33,12 +33,30 @@ along with GCC; see the file COPYING3. If not see #include "diagnostic-event-id.h" #include "analyzer/sm.h" #include "analyzer/pending-diagnostic.h" +#include "analyzer/diagnostic-manager.h" #include "selftest.h" #include "tristate.h" #include "analyzer/call-string.h" #include "analyzer/program-point.h" #include "analyzer/store.h" #include "analyzer/region-model.h" +#include "cpplib.h" +#include "digraph.h" +#include "ordered-hash-map.h" +#include "cfg.h" +#include "basic-block.h" +#include "gimple.h" +#include "gimple-iterator.h" +#include "cgraph.h" +#include "analyzer/supergraph.h" +#include "analyzer/program-state.h" +#include "alloc-pool.h" +#include "fibonacci_heap.h" +#include "shortest-paths.h" +#include "sbitmap.h" +#include "analyzer/exploded-graph.h" +#include "diagnostic-path.h" +#include "analyzer/checker-path.h" #if ENABLE_ANALYZER @@ -111,6 +129,70 @@ pending_diagnostic::same_tree_p (tree t1, tree t2) return simple_cst_equal (t1, t2) == 1; } +/* Return true iff IDENT is STR. */ + +static bool +ht_ident_eq (ht_identifier ident, const char *str) +{ + return (strlen (str) == ident.len + && 0 == strcmp (str, (const char *)ident.str)); +} + +/* Return true if we should show the expansion location rather than unwind + within MACRO. */ + +static bool +fixup_location_in_macro_p (cpp_hashnode *macro) +{ + ht_identifier ident = macro->ident; + /* Don't unwind inside macros, so that we don't suppress warnings + from them (due to being in system headers). */ + if (ht_ident_eq (ident, "va_start") + || ht_ident_eq (ident, "va_copy") + || ht_ident_eq (ident, "va_arg") + || ht_ident_eq (ident, "va_end")) + return true; + return false; +} + +/* Base implementation of pending_diagnostic::fixup_location. + Don't unwind inside macros for which fixup_location_in_macro_p is true. */ + +location_t +pending_diagnostic::fixup_location (location_t loc) const +{ + if (linemap_location_from_macro_expansion_p (line_table, loc)) + { + line_map *map + = const_cast (linemap_lookup (line_table, loc)); + const line_map_macro *macro_map = linemap_check_macro (map); + if (fixup_location_in_macro_p (macro_map->macro)) + loc = linemap_resolve_location (line_table, loc, + LRK_MACRO_EXPANSION_POINT, NULL); + } + return loc; +} + +/* Base implementation of pending_diagnostic::add_call_event. + Add a call_event to EMISSION_PATH. */ + +void +pending_diagnostic::add_call_event (const exploded_edge &eedge, + checker_path *emission_path) +{ + const exploded_node *src_node = eedge.m_src; + const program_point &src_point = src_node->get_point (); + const int src_stack_depth = src_point.get_stack_depth (); + const gimple *last_stmt = src_point.get_supernode ()->get_last_stmt (); + emission_path->add_event + (new call_event (eedge, + (last_stmt + ? last_stmt->location + : UNKNOWN_LOCATION), + src_point.get_fndecl (), + src_stack_depth)); +} + } // namespace ana #endif /* #if ENABLE_ANALYZER */ diff --git a/gcc/analyzer/pending-diagnostic.h b/gcc/analyzer/pending-diagnostic.h index 51039ea1ba5..17db9fede95 100644 --- a/gcc/analyzer/pending-diagnostic.h +++ b/gcc/analyzer/pending-diagnostic.h @@ -203,10 +203,7 @@ class pending_diagnostic /* A vfunc for fixing up locations (both the primary location for the diagnostic, and for events in their paths), e.g. to avoid unwinding inside specific macros. */ - virtual location_t fixup_location (location_t loc) const - { - return loc; - } + virtual location_t fixup_location (location_t loc) const; /* For greatest precision-of-wording, the various following "describe_*" virtual functions give the pending diagnostic a way to describe events @@ -295,6 +292,12 @@ class pending_diagnostic return false; } + /* Vfunc for adding a call_event to a checker_path, so that e.g. + the varargs diagnostics can add a custom event subclass that annotates + the variadic arguments. */ + virtual void add_call_event (const exploded_edge &, + checker_path *); + /* Vfunc for determining that this pending_diagnostic supercedes OTHER, and that OTHER should therefore not be emitted. They have already been tested for being at the same stmt. */ diff --git a/gcc/analyzer/region-model-impl-calls.cc b/gcc/analyzer/region-model-impl-calls.cc index 621e7002ffb..a76caf73133 100644 --- a/gcc/analyzer/region-model-impl-calls.cc +++ b/gcc/analyzer/region-model-impl-calls.cc @@ -57,6 +57,9 @@ along with GCC; see the file COPYING3. If not see #include "analyzer/store.h" #include "analyzer/region-model.h" #include "analyzer/call-info.h" +#include "analyzer/sm.h" +#include "diagnostic-path.h" +#include "analyzer/pending-diagnostic.h" #include "gimple-pretty-print.h" #if ENABLE_ANALYZER diff --git a/gcc/analyzer/region-model-manager.cc b/gcc/analyzer/region-model-manager.cc index 6d248c98fcf..3377f15feac 100644 --- a/gcc/analyzer/region-model-manager.cc +++ b/gcc/analyzer/region-model-manager.cc @@ -1601,6 +1601,25 @@ region_model_manager::get_bit_range (const region *parent, tree type, return bit_range_reg; } +/* Return the region that describes accessing the IDX-th variadic argument + within PARENT_FRAME, creating it if necessary. */ + +const var_arg_region * +region_model_manager::get_var_arg_region (const frame_region *parent_frame, + unsigned idx) +{ + gcc_assert (parent_frame); + + var_arg_region::key_t key (parent_frame, idx); + if (var_arg_region *reg = m_var_arg_regions.get (key)) + return reg; + + var_arg_region *var_arg_reg + = new var_arg_region (alloc_region_id (), parent_frame, idx); + m_var_arg_regions.put (key, var_arg_reg); + return var_arg_reg; +} + /* If we see a tree code we don't know how to handle, rather than ICE or generate bogus results, create a dummy region, and notify CTXT so that it can mark the new state as being not properly @@ -1773,6 +1792,7 @@ region_model_manager::log_stats (logger *logger, bool show_objs) const log_uniq_map (logger, show_objs, "symbolic_region", m_symbolic_regions); log_uniq_map (logger, show_objs, "string_region", m_string_map); log_uniq_map (logger, show_objs, "bit_range_region", m_bit_range_regions); + log_uniq_map (logger, show_objs, "var_arg_region", m_var_arg_regions); logger->log (" # managed dynamic regions: %i", m_managed_dynamic_regions.length ()); m_store_mgr.log_stats (logger, show_objs); diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 816b4100f3a..de221c3014c 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -1342,6 +1342,9 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt, return false; case IFN_UBSAN_BOUNDS: return false; + case IFN_VA_ARG: + impl_call_va_arg (cd); + return false; } } @@ -1428,6 +1431,13 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt, on the return value. */ check_call_args (cd); break; + + case BUILT_IN_VA_START: + impl_call_va_start (cd); + return false; + case BUILT_IN_VA_COPY: + impl_call_va_copy (cd); + return false; } else if (is_named_call_p (callee_fndecl, "malloc", call, 1)) { @@ -1570,6 +1580,10 @@ region_model::on_call_post (const gcall *call, case BUILT_IN_REALLOC: impl_call_realloc (cd); return; + + case BUILT_IN_VA_END: + impl_call_va_end (cd); + return; } } @@ -3520,6 +3534,7 @@ region_model::get_representative_path_var_1 (const region *reg, return path_var (string_reg->get_string_cst (), 0); } + case RK_VAR_ARG: case RK_UNKNOWN: return path_var (NULL_TREE, 0); } @@ -3888,6 +3903,17 @@ region_model::push_frame (function *fun, const vec *arg_svals, const svalue *arg_sval = (*arg_svals)[idx]; set_value (parm_reg, arg_sval, ctxt); } + + /* Handle any variadic args. */ + unsigned va_arg_idx = 0; + for (; idx < arg_svals->length (); idx++, va_arg_idx++) + { + const svalue *arg_sval = (*arg_svals)[idx]; + const region *var_arg_reg + = m_mgr->get_var_arg_region (m_current_frame, + va_arg_idx); + set_value (var_arg_reg, arg_sval, ctxt); + } } else { diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index eff3d4930f9..4e5cb46a649 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -326,6 +326,8 @@ public: const string_region *get_region_for_string (tree string_cst); const region *get_bit_range (const region *parent, tree type, const bit_range &bits); + const var_arg_region *get_var_arg_region (const frame_region *parent, + unsigned idx); const region *get_unknown_symbolic_region (tree region_type); @@ -488,6 +490,7 @@ private: string_map_t m_string_map; consolidation_map m_bit_range_regions; + consolidation_map m_var_arg_regions; store_manager m_store_mgr; @@ -627,6 +630,12 @@ class region_model void impl_call_operator_delete (const call_details &cd); void impl_deallocation_call (const call_details &cd); + /* Implemented in varargs.cc. */ + void impl_call_va_start (const call_details &cd); + void impl_call_va_copy (const call_details &cd); + void impl_call_va_arg (const call_details &cd); + void impl_call_va_end (const call_details &cd); + void handle_unrecognized_call (const gcall *call, region_model_context *ctxt); void get_reachable_svalues (svalue_set *out, diff --git a/gcc/analyzer/region.cc b/gcc/analyzer/region.cc index 1a7949b3f09..a8286231d30 100644 --- a/gcc/analyzer/region.cc +++ b/gcc/analyzer/region.cc @@ -1541,9 +1541,9 @@ void alloca_region::dump_to_pp (pretty_printer *pp, bool simple) const { if (simple) - pp_string (pp, "ALLOCA_REGION"); + pp_printf (pp, "ALLOCA_REGION(%i)", get_id ()); else - pp_string (pp, "alloca_region()"); + pp_printf (pp, "alloca_region(%i)", get_id ()); } /* class string_region : public region. */ @@ -1637,6 +1637,34 @@ bit_range_region::get_relative_concrete_offset (bit_offset_t *out) const return true; } +/* class var_arg_region : public region. */ + +void +var_arg_region::dump_to_pp (pretty_printer *pp, bool simple) const +{ + if (simple) + { + pp_string (pp, "VAR_ARG_REG("); + get_parent_region ()->dump_to_pp (pp, simple); + pp_printf (pp, ", arg_idx: %d)", m_idx); + } + else + { + pp_string (pp, "var_arg_region("); + get_parent_region ()->dump_to_pp (pp, simple); + pp_printf (pp, ", arg_idx: %d)", m_idx); + } +} + +/* Get the frame_region for this var_arg_region. */ + +const frame_region * +var_arg_region::get_frame_region () const +{ + gcc_assert (get_parent_region ()); + return as_a (get_parent_region ()); +} + /* class unknown_region : public region. */ /* Implementation of region::dump_to_pp vfunc for unknown_region. */ diff --git a/gcc/analyzer/region.h b/gcc/analyzer/region.h index 5150be76d0b..d32110bf8e3 100644 --- a/gcc/analyzer/region.h +++ b/gcc/analyzer/region.h @@ -61,7 +61,8 @@ enum region_kind RK_ALLOCA, RK_STRING, RK_BIT_RANGE, - RK_UNKNOWN + RK_VAR_ARG, + RK_UNKNOWN, }; /* Region and its subclasses. @@ -90,6 +91,7 @@ enum region_kind alloca_region (RK_ALLOCA) string_region (RK_STRING) bit_range_region (RK_BIT_RANGE) + var_arg_region (RK_VAR_ARG) unknown_region (RK_UNKNOWN). */ /* Abstract base class for representing ways of accessing chunks of memory. @@ -131,6 +133,8 @@ public: dyn_cast_string_region () const { return NULL; } virtual const bit_range_region * dyn_cast_bit_range_region () const { return NULL; } + virtual const var_arg_region * + dyn_cast_var_arg_region () const { return NULL; } virtual void accept (visitor *v) const; @@ -1251,6 +1255,87 @@ template <> struct default_hash_traits namespace ana { +/* A region for the N-th vararg within a frame_region for a variadic call. */ + +class var_arg_region : public region +{ +public: + /* A support class for uniquifying instances of var_arg_region. */ + struct key_t + { + key_t (const frame_region *parent, unsigned idx) + : m_parent (parent), m_idx (idx) + { + gcc_assert (parent); + } + + hashval_t hash () const + { + inchash::hash hstate; + hstate.add_ptr (m_parent); + hstate.add_int (m_idx); + return hstate.end (); + } + + bool operator== (const key_t &other) const + { + return (m_parent == other.m_parent + && m_idx == other.m_idx); + } + + void mark_deleted () + { + m_parent = reinterpret_cast (1); + } + void mark_empty () { m_parent = NULL; } + bool is_deleted () const + { + return m_parent == reinterpret_cast (1); + } + bool is_empty () const { return m_parent == NULL; } + + const frame_region *m_parent; + unsigned m_idx; + }; + + var_arg_region (unsigned id, const frame_region *parent, + unsigned idx) + : region (complexity (parent), id, parent, NULL_TREE), + m_idx (idx) + {} + + const var_arg_region * + dyn_cast_var_arg_region () const FINAL OVERRIDE { return this; } + + enum region_kind get_kind () const FINAL OVERRIDE { return RK_VAR_ARG; } + + void dump_to_pp (pretty_printer *pp, bool simple) const FINAL OVERRIDE; + + const frame_region *get_frame_region () const; + unsigned get_index () const { return m_idx; } + +private: + unsigned m_idx; +}; + +} // namespace ana + +template <> +template <> +inline bool +is_a_helper ::test (const region *reg) +{ + return reg->get_kind () == RK_VAR_ARG; +} + +template <> struct default_hash_traits +: public member_function_hash_traits +{ + static const bool empty_zero_p = true; +}; + +namespace ana { + /* An unknown region, for handling unimplemented tree codes. */ class unknown_region : public region diff --git a/gcc/analyzer/sm.cc b/gcc/analyzer/sm.cc index 515f86da86a..622cb0b7ab3 100644 --- a/gcc/analyzer/sm.cc +++ b/gcc/analyzer/sm.cc @@ -173,6 +173,7 @@ make_checkers (auto_delete_vec &out, logger *logger) out.safe_push (make_taint_state_machine (logger)); out.safe_push (make_sensitive_state_machine (logger)); out.safe_push (make_signal_state_machine (logger)); + out.safe_push (make_va_list_state_machine (logger)); /* We only attempt to run the pattern tests if it might have been manually enabled (for DejaGnu purposes). */ diff --git a/gcc/analyzer/sm.h b/gcc/analyzer/sm.h index 7ce1c73e217..4cc54531c56 100644 --- a/gcc/analyzer/sm.h +++ b/gcc/analyzer/sm.h @@ -242,6 +242,8 @@ public: issue a diagnostic D using NODE and STMT for location information. */ virtual void warn (const supernode *node, const gimple *stmt, tree var, pending_diagnostic *d) = 0; + virtual void warn (const supernode *node, const gimple *stmt, + const svalue *var, pending_diagnostic *d) = 0; /* For use when generating trees when creating pending_diagnostics, so that rather than e.g. @@ -275,8 +277,7 @@ public: virtual bool unknown_side_effects_p () const { return false; } virtual const program_state *get_old_program_state () const = 0; - - const svalue *get_old_svalue (tree expr) const; + virtual const program_state *get_new_program_state () const = 0; protected: sm_context (int sm_idx, const state_machine &sm) @@ -299,6 +300,7 @@ extern state_machine *make_taint_state_machine (logger *logger); extern state_machine *make_sensitive_state_machine (logger *logger); extern state_machine *make_signal_state_machine (logger *logger); extern state_machine *make_pattern_test_state_machine (logger *logger); +extern state_machine *make_va_list_state_machine (logger *logger); } // namespace ana diff --git a/gcc/analyzer/varargs.cc b/gcc/analyzer/varargs.cc new file mode 100644 index 00000000000..de77fe5d3ed --- /dev/null +++ b/gcc/analyzer/varargs.cc @@ -0,0 +1,1025 @@ +/* Implementation of within analyzer. + Copyright (C) 2022 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" +#include "system.h" +#include "coretypes.h" +#include "tree.h" +#include "function.h" +#include "basic-block.h" +#include "gimple.h" +#include "diagnostic-path.h" +#include "json.h" +#include "analyzer/analyzer.h" +#include "analyzer/analyzer-logging.h" +#include "analyzer/sm.h" +#include "analyzer/pending-diagnostic.h" +#include "tristate.h" +#include "selftest.h" +#include "analyzer/call-string.h" +#include "analyzer/program-point.h" +#include "analyzer/store.h" +#include "analyzer/region-model.h" +#include "analyzer/program-state.h" +#include "analyzer/checker-path.h" +#include "digraph.h" +#include "ordered-hash-map.h" +#include "cfg.h" +#include "gimple-iterator.h" +#include "analyzer/supergraph.h" +#include "alloc-pool.h" +#include "fibonacci_heap.h" +#include "shortest-paths.h" +#include "sbitmap.h" +#include "analyzer/diagnostic-manager.h" +#include "analyzer/exploded-graph.h" + +#if ENABLE_ANALYZER + +namespace ana { + +/* Implementation of within analyzer. + + Objectives: + - detection of interprocedural type errors involving va_arg + - tracking of symbolic values interprocedurally from variadic call + through to va_arg unpacking + - detection of missing va_end + - detection of va_arg outside of a va_start/va_end pair + - detection of uses of a va_list after the frame in containing the + va_start has returned + + The analyzer runs *before* the "stdarg" and "lower_vaarg" gimple + passes, which have target-dependent effects. + + This file implements a state machine on svalues for tracking when + va_start has been called, so that we can detect missing va_end, + and misplaced va_arg, etc. + To do this requires an svalue that can have state, so we implement va_start + by creating a stack-allocated region, and use a pointer to that region + as the svalue that has state. + + We call this stack-allocated region the "impl_reg". Allocating it on + the stack ensures that it is invalidated when the frame containing + the va_start returns, leading to + -Wanalyzer-use-of-pointer-in-stale-stack-frame on attempts to use such + a va_list. + + To track svalues from variadic calls interprocedurally, we implement + variadic arguments via new child regions of the callee's frame_region, + var_arg_region, each one representing a storage slot for one of the + variadic arguments, accessed by index. + + We have: + + stack frame: + va_list: &impl_reg + 'impl_reg': pointer to next var_arg_region + var_arg_region for arg 0 + ... + var_arg_region for arg N-1 + + Hence given test_1 in stdarg-1.c, at the call to: + + __analyzer_called_by_test_1 (int placeholder, ...); + + here: + + __analyzer_called_by_test_1 (42, "foo", 1066, '@'); + + we push this frame for the called function: + clusters within frame: ‘__analyzer_called_by_test_1’@2 + cluster for: placeholder: (int)42 + cluster for: VAR_ARG_REG(frame: ‘__analyzer_called_by_test_1’@2, arg_idx: 0): &"foo" (TOUCHED) + cluster for: VAR_ARG_REG(frame: ‘__analyzer_called_by_test_1’@2, arg_idx: 1): (int)1066 (TOUCHED) + cluster for: VAR_ARG_REG(frame: ‘__analyzer_called_by_test_1’@2, arg_idx: 2): (int)64 (TOUCHED) + where the called function's frame has been populated with both the value + of the regular argument "placeholder", and with values for 3 variadic + arguments. + + At the call to + va_start (ap, placeholder); + we allocate a region ALLOCA_REGION for ap to point to, populate that + region with the address of variadic argument 0, and set sm-state of + &ALLOCA_REGION to "started": + clusters within frame: ‘__analyzer_called_by_test_1’@2 + cluster for: placeholder: (int)42 + cluster for: VAR_ARG_REG(frame: ‘__analyzer_called_by_test_1’@2, arg_idx: 0): &"foo" (TOUCHED) + cluster for: VAR_ARG_REG(frame: ‘__analyzer_called_by_test_1’@2, arg_idx: 1): (int)1066 (TOUCHED) + cluster for: VAR_ARG_REG(frame: ‘__analyzer_called_by_test_1’@2, arg_idx: 2): (int)64 (TOUCHED) + cluster for: ap: &ALLOCA_REGION + cluster for: ALLOCA_REGION: &VAR_ARG_REG(frame: ‘__analyzer_called_by_test_1’@2, arg_idx: 0) (TOUCHED) + va_list: + 0x4c83700: &ALLOCA_REGION: started + + At each call to + va_arg (ap, TYPE); + we can look within *ap, locate the region holding the next variadic + argument to be extracted, extract the svalue, and advance the index + by effectively updating *ap. + + At the va_end, we can set &ALLOCA_REGION's state to "ended". + + The various __builtin_va_* accept ap by pointer, so we have e.g.: + + __builtin_va_start (&ap, [...]); + + except for the 2nd param of __builtin_va_copy, where the type + is already target-dependent (see the discussion of BT_VALIST_ARG + below). */ + +/* Get a tree for diagnostics. + Typically we have "&ap", but it will make more sense to + the user as just "ap", so strip off the ADDR_EXPR. */ + +static tree +get_va_list_diag_arg (tree va_list_tree) +{ + if (TREE_CODE (va_list_tree) == ADDR_EXPR) + va_list_tree = TREE_OPERAND (va_list_tree, 0); + return va_list_tree; +} + +/* Get argument ARG_IDX of type BT_VALIST_ARG (for use by va_copy). + + builtin-types.def has: + DEF_PRIMITIVE_TYPE (BT_VALIST_ARG, va_list_arg_type_node) + + and c_common_nodes_and_builtins initializes va_list_arg_type_node + based on whether TREE_CODE (va_list_type_node) is of ARRAY_TYPE or + not, giving either one or zero levels of indirection. */ + +static const svalue * +get_BT_VALIST_ARG (const region_model *model, + region_model_context *ctxt, + const gcall *call, + unsigned arg_idx) +{ + tree arg = gimple_call_arg (call, arg_idx); + const svalue *arg_sval = model->get_rvalue (arg, ctxt); + if (const svalue *cast = arg_sval->maybe_undo_cast ()) + arg_sval = cast; + if (TREE_CODE (va_list_type_node) == ARRAY_TYPE) + { + /* va_list_arg_type_node is a pointer to a va_list; + return *ARG_SVAL. */ + const region *src_reg = model->deref_rvalue (arg_sval, arg, ctxt); + const svalue *src_reg_sval = model->get_store_value (src_reg, ctxt); + if (const svalue *cast = src_reg_sval->maybe_undo_cast ()) + src_reg_sval = cast; + return src_reg_sval; + } + else + { + /* va_list_arg_type_node is a va_list; return ARG_SVAL. */ + return arg_sval; + } +} + +namespace { + +/* A state machine for tracking the state of a va_list, so that + we can enforce that each va_start is paired with a va_end, + and va_arg only happens within a va_start/va_end pair. + Specifically, this tracks the state of the &ALLOCA_BUFFER + that va_start/va_copy allocate. */ + +class va_list_state_machine : public state_machine +{ +public: + va_list_state_machine (logger *logger); + + bool inherited_state_p () const FINAL OVERRIDE { return false; } + + bool on_stmt (sm_context *sm_ctxt, + const supernode *node, + const gimple *stmt) const FINAL OVERRIDE; + + bool can_purge_p (state_t s) const FINAL OVERRIDE + { + return s != m_started; + } + pending_diagnostic *on_leak (tree var) const FINAL OVERRIDE; + + /* State for a va_list that the result of a va_start or va_copy. */ + state_t m_started; + + /* State for a va_list that has had va_end called on it. */ + state_t m_ended; + +private: + void on_va_start (sm_context *sm_ctxt, const supernode *node, + const gcall *call) const; + void on_va_copy (sm_context *sm_ctxt, const supernode *node, + const gcall *call) const; + void on_va_arg (sm_context *sm_ctxt, const supernode *node, + const gcall *call) const; + void on_va_end (sm_context *sm_ctxt, const supernode *node, + const gcall *call) const; + void check_for_ended_va_list (sm_context *sm_ctxt, + const supernode *node, + const gcall *call, + const svalue *arg, + const char *usage_fnname) const; +}; + +/* va_list_state_machine's ctor. */ + +va_list_state_machine::va_list_state_machine (logger *logger) +: state_machine ("va_list", logger) +{ + m_started = add_state ("started"); + m_ended = add_state ("ended"); +} + +/* Implementation of the various "va_*" functions for + va_list_state_machine. */ + +bool +va_list_state_machine::on_stmt (sm_context *sm_ctxt, + const supernode *node, + const gimple *stmt) const +{ + if (const gcall *call = dyn_cast (stmt)) + { + if (gimple_call_internal_p (call) + && gimple_call_internal_fn (call) == IFN_VA_ARG) + { + on_va_arg (sm_ctxt, node, call); + return false; + } + + if (tree callee_fndecl = sm_ctxt->get_fndecl_for_call (call)) + if (fndecl_built_in_p (callee_fndecl, BUILT_IN_NORMAL) + && gimple_builtin_call_types_compatible_p (call, callee_fndecl)) + switch (DECL_UNCHECKED_FUNCTION_CODE (callee_fndecl)) + { + default: + break; + + case BUILT_IN_VA_START: + on_va_start (sm_ctxt, node, call); + break; + + case BUILT_IN_VA_COPY: + on_va_copy (sm_ctxt, node, call); + break; + + case BUILT_IN_VA_END: + on_va_end (sm_ctxt, node, call); + break; + } + } + return false; +} + +/* Get the svalue for which va_list_state_machine holds state on argument ARG_ + IDX to CALL. */ + +static const svalue * +get_stateful_arg (sm_context *sm_ctxt, const gcall *call, unsigned arg_idx) +{ + tree ap = gimple_call_arg (call, arg_idx); + if (ap + && POINTER_TYPE_P (TREE_TYPE (ap))) + { + if (const program_state *new_state = sm_ctxt->get_new_program_state ()) + { + const region_model *new_model = new_state->m_region_model; + const svalue *ptr_sval = new_model->get_rvalue (ap, NULL); + const region *reg = new_model->deref_rvalue (ptr_sval, ap, NULL); + const svalue *impl_sval = new_model->get_store_value (reg, NULL); + if (const svalue *cast = impl_sval->maybe_undo_cast ()) + impl_sval = cast; + return impl_sval; + } + } + return NULL; +} + +/* Abstract class for diagnostics relating to va_list_state_machine. */ + +class va_list_sm_diagnostic : public pending_diagnostic +{ +public: + bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE + { + const va_list_sm_diagnostic &other + = (const va_list_sm_diagnostic &)base_other; + return (m_ap_sval == other.m_ap_sval + && same_tree_p (m_ap_tree, other.m_ap_tree)); + } + + label_text describe_state_change (const evdesc::state_change &change) + OVERRIDE + { + if (const char *fnname = maybe_get_fnname (change)) + return change.formatted_print ("%qs called here", fnname); + return label_text (); + } + +protected: + va_list_sm_diagnostic (const va_list_state_machine &sm, + const svalue *ap_sval, tree ap_tree) + : m_sm (sm), m_ap_sval (ap_sval), m_ap_tree (ap_tree) + {} + + static const char *maybe_get_fnname (const evdesc::state_change &change) + { + if (change.m_event.m_stmt) + if (const gcall *call = as_a (change.m_event.m_stmt)) + if (tree callee_fndecl = gimple_call_fndecl (call)) + { + if (fndecl_built_in_p (callee_fndecl, BUILT_IN_NORMAL)) + switch (DECL_UNCHECKED_FUNCTION_CODE (callee_fndecl)) + { + case BUILT_IN_VA_START: + return "va_start"; + case BUILT_IN_VA_COPY: + return "va_copy"; + case BUILT_IN_VA_END: + return "va_end"; + } + } + return NULL; + } + + const va_list_state_machine &m_sm; + const svalue *m_ap_sval; + tree m_ap_tree; +}; + +/* Concrete class for -Wanalyzer-va-list-use-after-va-end: + complain about use of a va_list after va_end has been called on it. */ + +class va_list_use_after_va_end : public va_list_sm_diagnostic +{ +public: + va_list_use_after_va_end (const va_list_state_machine &sm, + const svalue *ap_sval, tree ap_tree, + const char *usage_fnname) + : va_list_sm_diagnostic (sm, ap_sval, ap_tree), + m_usage_fnname (usage_fnname) + { + } + + int get_controlling_option () const FINAL OVERRIDE + { + return OPT_Wanalyzer_va_list_use_after_va_end; + } + + bool operator== (const va_list_use_after_va_end &other) const + { + return (va_list_sm_diagnostic::subclass_equal_p (other) + && 0 == strcmp (m_usage_fnname, other.m_usage_fnname)); + } + + bool emit (rich_location *rich_loc) FINAL OVERRIDE + { + auto_diagnostic_group d; + return warning_at (rich_loc, get_controlling_option (), + "%qs after %qs", m_usage_fnname, "va_end"); + } + + const char *get_kind () const FINAL OVERRIDE + { + return "va_list_use_after_va_end"; + } + + label_text describe_state_change (const evdesc::state_change &change) + FINAL OVERRIDE + { + if (change.m_new_state == m_sm.m_ended) + m_va_end_event = change.m_event_id; + return va_list_sm_diagnostic::describe_state_change (change); + } + + label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE + { + if (ev.m_expr) + { + if (m_va_end_event.known_p ()) + return ev.formatted_print + ("%qs on %qE after %qs at %@", + m_usage_fnname, ev.m_expr, "va_end", &m_va_end_event); + else + return ev.formatted_print + ("%qs on %qE after %qs", + m_usage_fnname, ev.m_expr, "va_end"); + } + else + { + if (m_va_end_event.known_p ()) + return ev.formatted_print + ("%qs after %qs at %@", + m_usage_fnname, "va_end", &m_va_end_event); + else + return ev.formatted_print + ("%qs after %qs", + m_usage_fnname, "va_end"); + } + } + +private: + diagnostic_event_id_t m_va_end_event; + const char *m_usage_fnname; +}; + +/* Concrete class for -Wanalyzer-va-list-leak: + complain about a va_list in the "started" state that doesn't get after + va_end called on it. */ + +class va_list_leak : public va_list_sm_diagnostic +{ +public: + va_list_leak (const va_list_state_machine &sm, + const svalue *ap_sval, tree ap_tree) + : va_list_sm_diagnostic (sm, ap_sval, ap_tree), + m_start_event_fnname (NULL) + { + } + + int get_controlling_option () const FINAL OVERRIDE + { + return OPT_Wanalyzer_va_list_leak; + } + + bool operator== (const va_list_leak &other) const + { + return va_list_sm_diagnostic::subclass_equal_p (other); + } + + bool emit (rich_location *rich_loc) + { + auto_diagnostic_group d; + return warning_at (rich_loc, get_controlling_option (), + "missing call to %qs", "va_end"); + } + + const char *get_kind () const FINAL OVERRIDE { return "va_list_leak"; } + + label_text describe_state_change (const evdesc::state_change &change) + FINAL OVERRIDE + { + if (change.m_new_state == m_sm.m_started) + { + m_start_event = change.m_event_id; + m_start_event_fnname = maybe_get_fnname (change); + } + return va_list_sm_diagnostic::describe_state_change (change); + } + + label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE + { + if (ev.m_expr) + { + if (m_start_event.known_p () && m_start_event_fnname) + return ev.formatted_print + ("missing call to %qs on %qE to match %qs at %@", + "va_end", ev.m_expr, m_start_event_fnname, &m_start_event); + else + return ev.formatted_print + ("missing call to %qs on %qE", + "va_end", ev.m_expr); + } + else + { + if (m_start_event.known_p () && m_start_event_fnname) + return ev.formatted_print + ("missing call to %qs to match %qs at %@", + "va_end", m_start_event_fnname, &m_start_event); + else + return ev.formatted_print + ("missing call to %qs", + "va_end"); + } + } + +private: + diagnostic_event_id_t m_start_event; + const char *m_start_event_fnname; +}; + +/* Update state machine for a "va_start" call. */ + +void +va_list_state_machine::on_va_start (sm_context *sm_ctxt, + const supernode *, + const gcall *call) const +{ + const svalue *arg = get_stateful_arg (sm_ctxt, call, 0); + if (arg) + { + /* Transition from start state to "started". */ + if (sm_ctxt->get_state (call, arg) == m_start) + sm_ctxt->set_next_state (call, arg, m_started); + } +} + +/* Complain if ARG is in the "ended" state. */ + +void +va_list_state_machine::check_for_ended_va_list (sm_context *sm_ctxt, + const supernode *node, + const gcall *call, + const svalue *arg, + const char *usage_fnname) const +{ + if (sm_ctxt->get_state (call, arg) == m_ended) + sm_ctxt->warn (node, call, arg, + new va_list_use_after_va_end (*this, arg, NULL_TREE, + usage_fnname)); +} + +/* Get the svalue with associated va_list_state_machine state for a + BT_VALIST_ARG for ARG_IDX of CALL, if SM_CTXT supports this, + or NULL otherwise. */ + +static const svalue * +get_stateful_BT_VALIST_ARG (sm_context *sm_ctxt, + const gcall *call, + unsigned arg_idx) +{ + if (const program_state *new_state = sm_ctxt->get_new_program_state ()) + { + const region_model *new_model = new_state->m_region_model; + const svalue *arg = get_BT_VALIST_ARG (new_model, NULL, call, arg_idx); + return arg; + } + return NULL; +} + +/* Update state machine for a "va_copy" call. */ + +void +va_list_state_machine::on_va_copy (sm_context *sm_ctxt, + const supernode *node, + const gcall *call) const +{ + const svalue *src_arg = get_stateful_BT_VALIST_ARG (sm_ctxt, call, 1); + if (src_arg) + check_for_ended_va_list (sm_ctxt, node, call, src_arg, "va_copy"); + + const svalue *dst_arg = get_stateful_arg (sm_ctxt, call, 0); + if (dst_arg) + { + /* Transition from start state to "started". */ + if (sm_ctxt->get_state (call, dst_arg) == m_start) + sm_ctxt->set_next_state (call, dst_arg, m_started); + } +} + +/* Update state machine for a "va_arg" call. */ + +void +va_list_state_machine::on_va_arg (sm_context *sm_ctxt, + const supernode *node, + const gcall *call) const +{ + const svalue *arg = get_stateful_arg (sm_ctxt, call, 0); + if (arg) + check_for_ended_va_list (sm_ctxt, node, call, arg, "va_arg"); +} + +/* Update state machine for a "va_end" call. */ + +void +va_list_state_machine::on_va_end (sm_context *sm_ctxt, + const supernode *node, + const gcall *call) const +{ + const svalue *arg = get_stateful_arg (sm_ctxt, call, 0); + if (arg) + { + state_t s = sm_ctxt->get_state (call, arg); + /* Transition from "started" to "ended". */ + if (s == m_started) + sm_ctxt->set_next_state (call, arg, m_ended); + else if (s == m_ended) + check_for_ended_va_list (sm_ctxt, node, call, arg, "va_end"); + } +} + +/* Implementation of state_machine::on_leak vfunc for va_list_state_machine + (for complaining about leaks of values in state 'started'). */ + +pending_diagnostic * +va_list_state_machine::on_leak (tree var) const +{ + return new va_list_leak (*this, NULL, var); +} + +} // anonymous namespace + +/* Internal interface to this file. */ + +state_machine * +make_va_list_state_machine (logger *logger) +{ + return new va_list_state_machine (logger); +} + +/* Handle the on_call_pre part of "__builtin_va_start". */ + +void +region_model::impl_call_va_start (const call_details &cd) +{ + const svalue *out_ptr = cd.get_arg_svalue (0); + const region *out_reg + = deref_rvalue (out_ptr, cd.get_arg_tree (0), cd.get_ctxt ()); + + /* "*out_ptr = &IMPL_REGION;". */ + const region *impl_reg = m_mgr->create_region_for_alloca (m_current_frame); + + /* We abuse the types here, since va_list_type isn't + necessarily anything to do with a pointer. */ + const svalue *ptr_to_impl_reg = m_mgr->get_ptr_svalue (NULL_TREE, impl_reg); + set_value (out_reg, ptr_to_impl_reg, cd.get_ctxt ()); + + /* "*(&IMPL_REGION) = VA_LIST_VAL (0);". */ + const region *init_var_arg_reg + = m_mgr->get_var_arg_region (get_current_frame (), 0); + const svalue *ap_sval = m_mgr->get_ptr_svalue (NULL_TREE, init_var_arg_reg); + set_value (impl_reg, ap_sval, cd.get_ctxt ()); +} + +/* Handle the on_call_pre part of "__builtin_va_copy". */ + +void +region_model::impl_call_va_copy (const call_details &cd) +{ + const svalue *out_dst_ptr = cd.get_arg_svalue (0); + const svalue *in_va_list + = get_BT_VALIST_ARG (this, cd.get_ctxt (), cd.get_call_stmt (), 1); + in_va_list = check_for_poison (in_va_list, + get_va_list_diag_arg (cd.get_arg_tree (1)), + cd.get_ctxt ()); + + const region *out_dst_reg + = deref_rvalue (out_dst_ptr, cd.get_arg_tree (0), cd.get_ctxt ()); + + /* "*out_dst_ptr = &NEW_IMPL_REGION;". */ + const region *new_impl_reg + = m_mgr->create_region_for_alloca (m_current_frame); + const svalue *ptr_to_new_impl_reg + = m_mgr->get_ptr_svalue (NULL_TREE, new_impl_reg); + set_value (out_dst_reg, ptr_to_new_impl_reg, cd.get_ctxt ()); + + if (const region *old_impl_reg = in_va_list->maybe_get_region ()) + { + + /* "(NEW_IMPL_REGION) = (OLD_IMPL_REGION);". */ + const svalue *existing_sval + = get_store_value (old_impl_reg, cd.get_ctxt ()); + set_value (new_impl_reg, existing_sval, cd.get_ctxt ()); + } +} + +/* Get the number of variadic arguments to CALLEE_FNDECL at CALL_STMT. */ + +static int +get_num_variadic_arguments (tree callee_fndecl, + const gcall *call_stmt) +{ + int num_positional = 0; + for (tree iter_parm = DECL_ARGUMENTS (callee_fndecl); iter_parm; + iter_parm = DECL_CHAIN (iter_parm)) + num_positional++; + return gimple_call_num_args (call_stmt) - num_positional; +} + +/* An abstract subclass of pending_diagnostic for diagnostics relating + to bad va_arg invocations. + + This shows the number of variadic arguments at the call of interest. + Ideally we'd also be able to highlight individual arguments, but + that location information isn't generally available from the middle end. */ + +class va_arg_diagnostic : public pending_diagnostic +{ +public: + /* Override of pending_diagnostic::add_call_event, + adding a custom call_event subclass. */ + void add_call_event (const exploded_edge &eedge, + checker_path *emission_path) OVERRIDE + { + /* As per call_event, but show the number of variadic arguments + in the call. */ + class va_arg_call_event : public call_event + { + public: + va_arg_call_event (const exploded_edge &eedge, + location_t loc, tree fndecl, int depth, + int num_variadic_arguments) + : call_event (eedge, loc, fndecl, depth), + m_num_variadic_arguments (num_variadic_arguments) + { + } + + label_text get_desc (bool can_colorize) const OVERRIDE + { + return make_label_text_n + (can_colorize, m_num_variadic_arguments, + "calling %qE from %qE with %i variadic argument", + "calling %qE from %qE with %i variadic arguments", + get_callee_fndecl (), + get_caller_fndecl (), + m_num_variadic_arguments); + } + private: + int m_num_variadic_arguments; + }; + + const frame_region *frame_reg = m_var_arg_reg->get_frame_region (); + const exploded_node *dst_node = eedge.m_dest; + if (dst_node->get_state ().m_region_model->get_current_frame () + == frame_reg) + { + const exploded_node *src_node = eedge.m_src; + const program_point &src_point = src_node->get_point (); + const int src_stack_depth = src_point.get_stack_depth (); + const gimple *last_stmt = src_point.get_supernode ()->get_last_stmt (); + const gcall *call_stmt = as_a (last_stmt); + int num_variadic_arguments + = get_num_variadic_arguments (dst_node->get_function ()->decl, + call_stmt); + emission_path->add_event + (new va_arg_call_event (eedge, + (last_stmt + ? last_stmt->location + : UNKNOWN_LOCATION), + src_point.get_fndecl (), + src_stack_depth, + num_variadic_arguments)); + } + else + pending_diagnostic::add_call_event (eedge, emission_path); + } + +protected: + va_arg_diagnostic (tree va_list_tree, const var_arg_region *var_arg_reg) + : m_va_list_tree (va_list_tree), m_var_arg_reg (var_arg_reg) + {} + + bool subclass_equal_p (const pending_diagnostic &base_other) const OVERRIDE + { + const va_arg_diagnostic &other = (const va_arg_diagnostic &)base_other; + return (same_tree_p (m_va_list_tree, other.m_va_list_tree) + && m_var_arg_reg == other.m_var_arg_reg); + } + + /* Get the number of arguments consumed so far from the va_list + (*before* this va_arg call). */ + unsigned get_num_consumed () const + { + return m_var_arg_reg->get_index (); + } + + /* Get a 1-based index of which variadic argument is being consumed. */ + unsigned get_variadic_index_for_diagnostic () const + { + return get_num_consumed () + 1; + } + + /* User-readable expr for the va_list argument to va_arg. */ + tree m_va_list_tree; + + /* The region that the va_arg attempted to access. */ + const var_arg_region *m_var_arg_reg; +}; + +/* A subclass of pending_diagnostic for complaining about a type mismatch + between the result of: + va_arg (AP); + and the type of the argument that was passed to the variadic call. */ + +class va_arg_type_mismatch : public va_arg_diagnostic +{ +public: + va_arg_type_mismatch (tree va_list_tree, const var_arg_region *var_arg_reg, + tree expected_type, tree actual_type) + : va_arg_diagnostic (va_list_tree, var_arg_reg), + m_expected_type (expected_type), m_actual_type (actual_type) + {} + + const char *get_kind () const FINAL OVERRIDE + { + return "va_arg_type_mismatch"; + } + + bool subclass_equal_p (const pending_diagnostic &base_other) + const FINAL OVERRIDE + { + if (!va_arg_diagnostic::subclass_equal_p (base_other)) + return false; + const va_arg_type_mismatch &other + = (const va_arg_type_mismatch &)base_other; + return (same_tree_p (m_expected_type, other.m_expected_type) + && same_tree_p (m_actual_type, other.m_actual_type)); + } + + int get_controlling_option () const FINAL OVERRIDE + { + return OPT_Wanalyzer_va_arg_type_mismatch; + } + + bool emit (rich_location *rich_loc) FINAL OVERRIDE + { + auto_diagnostic_group d; + bool warned + = warning_at (rich_loc, get_controlling_option (), + "% expected %qT but received %qT" + " for variadic argument %i of %qE", + m_expected_type, m_actual_type, + get_variadic_index_for_diagnostic (), m_va_list_tree); + return warned; + } + + label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE + { + return ev.formatted_print ("% expected %qT but received %qT" + " for variadic argument %i of %qE", + m_expected_type, m_actual_type, + get_variadic_index_for_diagnostic (), + m_va_list_tree); + } + +private: + tree m_expected_type; + tree m_actual_type; +}; + +/* A subclass of pending_diagnostic for complaining about a + va_arg (AP); + after all of the args in AP have been consumed. */ + +class va_list_exhausted : public va_arg_diagnostic +{ +public: + va_list_exhausted (tree va_list_tree, const var_arg_region *var_arg_reg) + : va_arg_diagnostic (va_list_tree, var_arg_reg) + {} + + const char *get_kind () const FINAL OVERRIDE + { + return "va_list_exhausted"; + } + + int get_controlling_option () const FINAL OVERRIDE + { + return OPT_Wanalyzer_va_list_exhausted; + } + + bool emit (rich_location *rich_loc) FINAL OVERRIDE + { + auto_diagnostic_group d; + bool warned = warning_at (rich_loc, get_controlling_option (), + "%qE has no more arguments (%i consumed)", + m_va_list_tree, get_num_consumed ()); + return warned; + } + + label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE + { + return ev.formatted_print ("%qE has no more arguments (%i consumed)", + m_va_list_tree, get_num_consumed ()); + } +}; + +/* Return true if it's OK to copy a value from ARG_TYPE to LHS_TYPE via + va_arg (where argument promotion has already happened). */ + +static bool +va_arg_compatible_types_p (tree lhs_type, tree arg_type) +{ + return compat_types_p (arg_type, lhs_type); +} + +/* If AP_SVAL is a pointer to a var_arg_region, return that var_arg_region. + Otherwise return NULL. */ + +static const var_arg_region * +maybe_get_var_arg_region (const svalue *ap_sval) +{ + if (const region *reg = ap_sval->maybe_get_region ()) + return reg->dyn_cast_var_arg_region (); + return NULL; +} + +/* Handle the on_call_pre part of "__builtin_va_arg". */ + +void +region_model::impl_call_va_arg (const call_details &cd) +{ + region_model_context *ctxt = cd.get_ctxt (); + + const svalue *in_ptr = cd.get_arg_svalue (0); + const region *ap_reg = deref_rvalue (in_ptr, cd.get_arg_tree (0), ctxt); + + const svalue *ap_sval = get_store_value (ap_reg, ctxt); + if (const svalue *cast = ap_sval->maybe_undo_cast ()) + ap_sval = cast; + + tree va_list_tree = get_va_list_diag_arg (cd.get_arg_tree (0)); + ap_sval = check_for_poison (ap_sval, va_list_tree, ctxt); + + if (const region *impl_reg = ap_sval->maybe_get_region ()) + { + const svalue *old_impl_sval = get_store_value (impl_reg, ctxt); + if (const var_arg_region *arg_reg + = maybe_get_var_arg_region (old_impl_sval)) + { + bool saw_problem = false; + + const frame_region *frame_reg = arg_reg->get_frame_region (); + unsigned next_arg_idx = arg_reg->get_index (); + + if (get_stack_depth () > 1) + { + /* The interprocedural case: the called frame will have been + populated with any variadic aruguments. + Attempt to extract arg_reg to cd's return region (which already + has a conjured_svalue), or warn if there's a problem + (incompatible types, or if we've run out of args). */ + if (const svalue *arg_sval + = m_store.get_any_binding (m_mgr->get_store_manager (), + arg_reg)) + { + tree lhs_type = cd.get_lhs_type (); + tree arg_type = arg_sval->get_type (); + if (va_arg_compatible_types_p (lhs_type, arg_type)) + cd.maybe_set_lhs (arg_sval); + else + { + if (ctxt) + ctxt->warn (new va_arg_type_mismatch (va_list_tree, + arg_reg, + lhs_type, + arg_type)); + saw_problem = true; + } + } + else + { + if (ctxt) + ctxt->warn (new va_list_exhausted (va_list_tree, arg_reg)); + saw_problem = true; + } + } + else + { + /* This frame is an entry-point to the analysis, so there won't be + any specific var_arg_regions populated within it. + We already have a conjured_svalue for the result, so leave + it untouched. */ + gcc_assert (get_stack_depth () == 1); + } + + if (saw_problem) + { + /* Set impl_reg to UNKNOWN to suppress further warnings. */ + const svalue *new_ap_sval + = m_mgr->get_or_create_unknown_svalue (impl_reg->get_type ()); + set_value (impl_reg, new_ap_sval, ctxt); + } + else + { + /* Update impl_reg to advance to the next arg. */ + const region *next_var_arg_region + = m_mgr->get_var_arg_region (frame_reg, next_arg_idx + 1); + const svalue *new_ap_sval + = m_mgr->get_ptr_svalue (NULL_TREE, next_var_arg_region); + set_value (impl_reg, new_ap_sval, ctxt); + } + } + } +} + +/* Handle the on_call_post part of "__builtin_va_end". */ + +void +region_model::impl_call_va_end (const call_details &) +{ + /* No-op. */ +} + +} // namespace ana + +#endif /* #if ENABLE_ANALYZER */ diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 7a35d9613a4..e8e6d4e039b 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -464,6 +464,10 @@ Objective-C and Objective-C++ Dialects}. -Wno-analyzer-use-after-free @gol -Wno-analyzer-use-of-pointer-in-stale-stack-frame @gol -Wno-analyzer-use-of-uninitialized-value @gol +-Wno-analyzer-va-arg-type-mismatch @gol +-Wno-analyzer-va-list-exhausted @gol +-Wno-analyzer-va-list-leak @gol +-Wno-analyzer-va-list-use-after-va-end @gol -Wno-analyzer-write-to-const @gol -Wno-analyzer-write-to-string-literal @gol } @@ -9689,6 +9693,10 @@ Enabling this option effectively enables the following warnings: -Wanalyzer-use-after-free @gol -Wanalyzer-use-of-pointer-in-stale-stack-frame @gol -Wanalyzer-use-of-uninitialized-value @gol +-Wanalyzer-va-arg-type-mismatch @gol +-Wanalyzer-va-list-exhausted @gol +-Wanalyzer-va-list-leak @gol +-Wanalyzer-va-list-use-after-va-end @gol -Wanalyzer-write-to-const @gol -Wanalyzer-write-to-string-literal @gol } @@ -9971,6 +9979,53 @@ to disable it. This diagnostic warns for paths through the code in which a pointer is dereferenced that points to a variable in a stale stack frame. +@item -Wno-analyzer-va-arg-type-mismatch +@opindex Wanalyzer-va-arg-type-mismatch +@opindex Wno-analyzer-va-arg-type-mismatch +This warning requires @option{-fanalyzer}, which enables it; use +@option{-Wno-analyzer-va-arg-type-mismatch} +to disable it. + +This diagnostic warns for interprocedural paths through the code for which +the analyzer detects an attempt to use @code{va_arg} to extract a value +passed to a variadic call, but uses a type that does not match that of +the expression passed to the call. + +@item -Wno-analyzer-va-list-exhausted +@opindex Wanalyzer-va-list-exhausted +@opindex Wno-analyzer-va-list-exhausted +This warning requires @option{-fanalyzer}, which enables it; use +@option{-Wno-analyzer-va-list-exhausted} +to disable it. + +This diagnostic warns for interprocedural paths through the code for which +the analyzer detects an attempt to use @code{va_arg} to access the next +value passed to a variadic call, but all of the values in the +@code{va_list} have already been consumed. + +@item -Wno-analyzer-va-list-leak +@opindex Wanalyzer-va-list-leak +@opindex Wno-analyzer-va-list-leak +This warning requires @option{-fanalyzer}, which enables it; use +@option{-Wno-analyzer-va-list-leak} +to disable it. + +This diagnostic warns for interprocedural paths through the code for which +the analyzer detects that @code{va_start} or @code{va_copy} has been called +on a @code{va_list} without a corresponding call to @code{va_end}. + +@item -Wno-analyzer-va-list-use-after-va-end +@opindex Wanalyzer-va-list-use-after-va-end +@opindex Wno-analyzer-va-list-use-after-va-end +This warning requires @option{-fanalyzer}, which enables it; use +@option{-Wno-analyzer-va-list-use-after-va-end} +to disable it. + +This diagnostic warns for interprocedural paths through the code for which +the analyzer detects an attempt to use a @code{va_list} after +@code{va_end} has been called on it. +@code{va_list}. + @item -Wno-analyzer-write-to-const @opindex Wanalyzer-write-to-const @opindex Wno-analyzer-write-to-const diff --git a/gcc/testsuite/gcc.dg/analyzer/stdarg-1.c b/gcc/testsuite/gcc.dg/analyzer/stdarg-1.c new file mode 100644 index 00000000000..295f0efb74d --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/stdarg-1.c @@ -0,0 +1,433 @@ +#include "analyzer-decls.h" + +/* Unpacking a va_list. */ + +static void __attribute__((noinline)) +__analyzer_called_by_test_1 (int placeholder, ...) +{ + const char *s; + int i; + char c; + + __builtin_va_list ap; + __builtin_va_start (ap, placeholder); + + s = __builtin_va_arg (ap, char *); + __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */ + + i = __builtin_va_arg (ap, int); + __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */ + + c = (char)__builtin_va_arg (ap, int); + __analyzer_eval (c == '@'); /* { dg-warning "TRUE" } */ + + __builtin_va_end (ap); +} + +void test_1 (void) +{ + __analyzer_called_by_test_1 (42, "foo", 1066, '@'); +} + +/* Unpacking a va_list passed from an intermediate function. */ + +static void __attribute__((noinline)) +__analyzer_test_2_inner (__builtin_va_list ap) +{ + const char *s; + int i; + char c; + + s = __builtin_va_arg (ap, char *); + __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */ + + i = __builtin_va_arg (ap, int); + __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */ + + c = (char)__builtin_va_arg (ap, int); + __analyzer_eval (c == '@'); /* { dg-warning "TRUE" } */ +} + +static void __attribute__((noinline)) +__analyzer_test_2_middle (int placeholder, ...) +{ + __builtin_va_list ap; + __builtin_va_start (ap, placeholder); + __analyzer_test_2_inner (ap); + __builtin_va_end (ap); +} + +void test_2 (void) +{ + __analyzer_test_2_middle (42, "foo", 1066, '@'); +} + +/* Not enough args. */ + +static void __attribute__((noinline)) +__analyzer_called_by_test_not_enough_args (int placeholder, ...) +{ + const char *s; + int i; + + __builtin_va_list ap; + __builtin_va_start (ap, placeholder); + + s = __builtin_va_arg (ap, char *); + __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */ + + i = __builtin_va_arg (ap, int); /* { dg-warning "'ap' has no more arguments \\(1 consumed\\)" } */ + + __builtin_va_end (ap); +} + +void test_not_enough_args (void) +{ + __analyzer_called_by_test_not_enough_args (42, "foo"); +} + +/* Not enough args, with an intermediate function. */ + +static void __attribute__((noinline)) +__analyzer_test_not_enough_args_2_inner (__builtin_va_list ap) +{ + const char *s; + int i; + + s = __builtin_va_arg (ap, char *); + __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */ + + i = __builtin_va_arg (ap, int); /* { dg-warning "'ap' has no more arguments \\(1 consumed\\)" } */ +} + +static void __attribute__((noinline)) +__analyzer_test_not_enough_args_2_middle (int placeholder, ...) +{ + __builtin_va_list ap; + __builtin_va_start (ap, placeholder); + __analyzer_test_not_enough_args_2_inner (ap); + __builtin_va_end (ap); +} + +void test_not_enough_args_2 (void) +{ + __analyzer_test_not_enough_args_2_middle (42, "foo"); +} + +/* Excess args (not a problem). */ + +static void __attribute__((noinline)) +__analyzer_called_by_test_excess_args (int placeholder, ...) +{ + const char *s; + + __builtin_va_list ap; + __builtin_va_start (ap, placeholder); + + s = __builtin_va_arg (ap, char *); + __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */ + + __builtin_va_end (ap); +} + +void test_excess_args (void) +{ + __analyzer_called_by_test_excess_args (42, "foo", "bar"); +} + +/* Missing va_start. */ + +void test_missing_va_start (int placeholder, ...) +{ + __builtin_va_list ap; /* { dg-message "region created on stack here" } */ + int i = __builtin_va_arg (ap, int); /* { dg-warning "use of uninitialized value 'ap'" } */ +} + +/* Missing va_end. */ + +void test_missing_va_end (int placeholder, ...) +{ + int i; + __builtin_va_list ap; + __builtin_va_start (ap, placeholder); /* { dg-message "\\(1\\) 'va_start' called here" } */ + i = __builtin_va_arg (ap, int); +} /* { dg-warning "missing call to 'va_end'" "warning" } */ +/* { dg-message "\\(2\\) missing call to 'va_end' to match 'va_start' at \\(1\\)" "final event" { target *-*-* } .-1 } */ + +/* Missing va_end due to error-handling. */ + +int test_missing_va_end_2 (int placeholder, ...) +{ + int i, j; + __builtin_va_list ap; + __builtin_va_start (ap, placeholder); /* { dg-message "\\(1\\) 'va_start' called here" } */ + i = __builtin_va_arg (ap, int); + if (i == 42) + { + __builtin_va_end (ap); + return -1; + } + j = __builtin_va_arg (ap, int); + if (j == 1066) /* { dg-message "branch" } */ + return -1; /* { dg-message "here" } */ + __builtin_va_end (ap); + return 0; +} /* { dg-warning "missing call to 'va_end'" "warning" } */ + +/* va_arg after va_end. */ + +void test_va_arg_after_va_end (int placeholder, ...) +{ + int i; + __builtin_va_list ap; + __builtin_va_start (ap, placeholder); + __builtin_va_end (ap); /* { dg-message "'va_end' called here" } */ + i = __builtin_va_arg (ap, int); /* { dg-warning "'va_arg' after 'va_end'" } */ +} + +/* Type mismatch: expect int, but passed a char *. */ + +static void __attribute__((noinline)) +__analyzer_called_by_test_type_mismatch_1 (int placeholder, ...) +{ + int i; + + __builtin_va_list ap; + __builtin_va_start (ap, placeholder); + + i = __builtin_va_arg (ap, int); /* { dg-warning "'va_arg' expected 'int' but received '\[^\n\r\]*' for variadic argument 1 of 'ap'" } */ + + __builtin_va_end (ap); +} + +void test_type_mismatch_1 (void) +{ + __analyzer_called_by_test_type_mismatch_1 (42, "foo"); +} + +/* Type mismatch: expect char *, but passed an int. */ + +static void __attribute__((noinline)) +__analyzer_called_by_test_type_mismatch_2 (int placeholder, ...) +{ + const char *str; + + __builtin_va_list ap; + __builtin_va_start (ap, placeholder); + + str = __builtin_va_arg (ap, const char *); /* { dg-warning "'va_arg' expected 'const char \\*' but received 'int' for variadic argument 1" } */ + + __builtin_va_end (ap); +} + +void test_type_mismatch_2 (void) +{ + __analyzer_called_by_test_type_mismatch_2 (42, 1066); +} + +/* As above, but with an intermediate function. */ + +static void __attribute__((noinline)) +__analyzer_test_type_mismatch_3_inner (__builtin_va_list ap) +{ + const char *str; + + str = __builtin_va_arg (ap, const char *); /* { dg-warning "'va_arg' expected 'const char \\*' but received 'int' for variadic argument 1 of 'ap'" } */ +} + +static void __attribute__((noinline)) +__analyzer_test_type_mismatch_3_middle (int placeholder, ...) +{ + __builtin_va_list ap; + __builtin_va_start (ap, placeholder); + + __analyzer_test_type_mismatch_3_inner (ap); + + __builtin_va_end (ap); +} + +void test_type_mismatch_3 (void) +{ + __analyzer_test_type_mismatch_3_middle (42, 1066); +} + +/* Multiple traversals of the args. */ + +static void __attribute__((noinline)) +__analyzer_called_by_test_multiple_traversals (int placeholder, ...) +{ + __builtin_va_list ap; + + /* First traversal. */ + { + int i, j; + + __builtin_va_start (ap, placeholder); + + i = __builtin_va_arg (ap, int); + __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */ + + j = __builtin_va_arg (ap, int); + __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */ + + __builtin_va_end (ap); + } + + /* Second traversal. */ + { + int i, j; + + __builtin_va_start (ap, placeholder); + + i = __builtin_va_arg (ap, int); + __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */ + + j = __builtin_va_arg (ap, int); + __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */ + + __builtin_va_end (ap); + } +} + +void test_multiple_traversals (void) +{ + __analyzer_called_by_test_multiple_traversals (0, 1066, 42); +} + +/* Multiple traversals, using va_copy. */ + +static void __attribute__((noinline)) +__analyzer_called_by_test_multiple_traversals_2 (int placeholder, ...) +{ + int i, j; + __builtin_va_list args1; + __builtin_va_list args2; + + __builtin_va_start (args1, placeholder); + __builtin_va_copy (args2, args1); + + /* First traversal. */ + i = __builtin_va_arg (args1, int); + __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */ + j = __builtin_va_arg (args1, int); + __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */ + __builtin_va_end (args1); + + /* Traversal of copy. */ + i = __builtin_va_arg (args2, int); + __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */ + j = __builtin_va_arg (args2, int); + __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */ + __builtin_va_end (args2); +} + +void test_multiple_traversals_2 (void) +{ + __analyzer_called_by_test_multiple_traversals_2 (0, 1066, 42); +} + +/* Multiple traversals, using va_copy after a va_arg. */ + +static void __attribute__((noinline)) +__analyzer_called_by_test_multiple_traversals_3 (int placeholder, ...) +{ + int i, j; + __builtin_va_list args1; + __builtin_va_list args2; + + __builtin_va_start (args1, placeholder); + + /* First traversal. */ + i = __builtin_va_arg (args1, int); + __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */ + + /* va_copy after the first va_arg. */ + __builtin_va_copy (args2, args1); + + j = __builtin_va_arg (args1, int); + __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */ + __builtin_va_end (args1); + + /* Traversal of copy. */ + j = __builtin_va_arg (args2, int); + __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */ + __builtin_va_end (args2); +} + +void test_multiple_traversals_3 (void) +{ + __analyzer_called_by_test_multiple_traversals_3 (0, 1066, 42); +} + +/* va_copy after va_end. */ + +void test_va_copy_after_va_end (int placeholder, ...) +{ + __builtin_va_list ap1, ap2; + __builtin_va_start (ap1, placeholder); + __builtin_va_end (ap1); /* { dg-message "'va_end' called here" } */ + __builtin_va_copy (ap2, ap1); /* { dg-warning "'va_copy' after 'va_end'" } */ + __builtin_va_end (ap2); +} + +/* leak of va_copy. */ + +void test_leak_of_va_copy (int placeholder, ...) +{ + __builtin_va_list ap1, ap2; + __builtin_va_start (ap1, placeholder); + __builtin_va_copy (ap2, ap1); /* { dg-message "'va_copy' called here" } */ + __builtin_va_end (ap1); +} /* { dg-warning "missing call to 'va_end'" "warning" } */ + /* { dg-message "missing call to 'va_end' to match 'va_copy' at \\(1\\)" "final event" { target *-*-* } .-1 } */ + +/* double va_end. */ + +void test_double_va_end (int placeholder, ...) +{ + __builtin_va_list ap; + __builtin_va_start (ap, placeholder); + __builtin_va_end (ap); /* { dg-message "'va_end' called here" } */ + __builtin_va_end (ap); /* { dg-warning "'va_end' after 'va_end'" } */ +} + +/* double va_start. */ + +void test_double_va_start (int placeholder, ...) +{ + int i; + __builtin_va_list ap; + __builtin_va_start (ap, placeholder); /* { dg-message "'va_start' called here" } */ + __builtin_va_start (ap, placeholder); /* { dg-warning "missing call to 'va_end'" "warning" } */ + /* { dg-message "missing call to 'va_end' to match 'va_start' at \\(1\\)" "final event" { target *-*-* } .-1 } */ + __builtin_va_end (ap); +} + +/* va_copy before va_start. */ + +void test_va_copy_before_va_start (int placeholder, ...) +{ + __builtin_va_list ap1; /* { dg-message "region created on stack here" } */ + __builtin_va_list ap2; + __builtin_va_copy (ap2, ap1); /* { dg-warning "use of uninitialized value 'ap1'" } */ + __builtin_va_end (ap2); +} + +/* Verify that we complain about uses of a va_list after the function + in which va_start was called has returned. */ + +__builtin_va_list global_ap; + +static void __attribute__((noinline)) +__analyzer_called_by_test_va_arg_after_return (int placeholder, ...) +{ + __builtin_va_start (global_ap, placeholder); + __builtin_va_end (global_ap); +} + +void test_va_arg_after_return (void) +{ + int i; + __analyzer_called_by_test_va_arg_after_return (42, 1066); + i = __builtin_va_arg (global_ap, int); /* { dg-warning "dereferencing pointer 'global_ap' to within stale stack frame" } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/stdarg-2.c b/gcc/testsuite/gcc.dg/analyzer/stdarg-2.c new file mode 100644 index 00000000000..69a2acbc3fa --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/stdarg-2.c @@ -0,0 +1,436 @@ +/* As per stdarg-1.c, but using , rather than hardcoded builtins. */ + +#include +#include "analyzer-decls.h" + +/* Unpacking a va_list. */ + +static void __attribute__((noinline)) +__analyzer_called_by_test_1 (int placeholder, ...) +{ + const char *s; + int i; + char c; + + va_list ap; + va_start (ap, placeholder); + + s = va_arg (ap, char *); + __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */ + + i = va_arg (ap, int); + __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */ + + c = (char)va_arg (ap, int); + __analyzer_eval (c == '@'); /* { dg-warning "TRUE" } */ + + va_end (ap); +} + +void test_1 (void) +{ + __analyzer_called_by_test_1 (42, "foo", 1066, '@'); +} + +/* Unpacking a va_list passed from an intermediate function. */ + +static void __attribute__((noinline)) +__analyzer_test_2_inner (va_list ap) +{ + const char *s; + int i; + char c; + + s = va_arg (ap, char *); + __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */ + + i = va_arg (ap, int); + __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */ + + c = (char)va_arg (ap, int); + __analyzer_eval (c == '@'); /* { dg-warning "TRUE" } */ +} + +static void __attribute__((noinline)) +__analyzer_test_2_middle (int placeholder, ...) +{ + va_list ap; + va_start (ap, placeholder); + __analyzer_test_2_inner (ap); + va_end (ap); +} + +void test_2 (void) +{ + __analyzer_test_2_middle (42, "foo", 1066, '@'); +} + +/* Not enough args. */ + +static void __attribute__((noinline)) +__analyzer_called_by_test_not_enough_args (int placeholder, ...) +{ + const char *s; + int i; + + va_list ap; + va_start (ap, placeholder); + + s = va_arg (ap, char *); + __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */ + + i = va_arg (ap, int); /* { dg-warning "'ap' has no more arguments \\(1 consumed\\)" } */ + + va_end (ap); +} + +void test_not_enough_args (void) +{ + __analyzer_called_by_test_not_enough_args (42, "foo"); +} + +/* Not enough args, with an intermediate function. */ + +static void __attribute__((noinline)) +__analyzer_test_not_enough_args_2_inner (va_list ap) +{ + const char *s; + int i; + + s = va_arg (ap, char *); + __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */ + + i = va_arg (ap, int); /* { dg-warning "'ap' has no more arguments \\(1 consumed\\)" } */ +} + +static void __attribute__((noinline)) +__analyzer_test_not_enough_args_2_middle (int placeholder, ...) +{ + va_list ap; + va_start (ap, placeholder); + __analyzer_test_not_enough_args_2_inner (ap); + va_end (ap); +} + +void test_not_enough_args_2 (void) +{ + __analyzer_test_not_enough_args_2_middle (42, "foo"); +} + +/* Excess args (not a problem). */ + +static void __attribute__((noinline)) +__analyzer_called_by_test_excess_args (int placeholder, ...) +{ + const char *s; + + va_list ap; + va_start (ap, placeholder); + + s = va_arg (ap, char *); + __analyzer_eval (s[0] == 'f'); /* { dg-warning "TRUE" } */ + + va_end (ap); +} + +void test_excess_args (void) +{ + __analyzer_called_by_test_excess_args (42, "foo", "bar"); +} + +/* Missing va_start. */ + +void test_missing_va_start (int placeholder, ...) +{ + va_list ap; /* { dg-message "region created on stack here" } */ + int i = va_arg (ap, int); /* { dg-warning "use of uninitialized value 'ap'" } */ +} + +/* Missing va_end. */ + +void test_missing_va_end (int placeholder, ...) +{ + int i; + va_list ap; + va_start (ap, placeholder); /* { dg-message "\\(1\\) 'va_start' called here" } */ + i = va_arg (ap, int); +} /* { dg-warning "missing call to 'va_end'" "warning" } */ +/* { dg-message "\\(2\\) missing call to 'va_end' to match 'va_start' at \\(1\\)" "final event" { target *-*-* } .-1 } */ + +/* Missing va_end due to error-handling. */ + +int test_missing_va_end_2 (int placeholder, ...) +{ + int i, j; + va_list ap; + va_start (ap, placeholder); /* { dg-message "\\(1\\) 'va_start' called here" } */ + i = va_arg (ap, int); + if (i == 42) + { + va_end (ap); + return -1; + } + j = va_arg (ap, int); + if (j == 1066) /* { dg-message "branch" } */ + return -1; /* { dg-message "here" } */ + va_end (ap); + return 0; +} /* { dg-warning "missing call to 'va_end'" "warning" } */ + +/* va_arg after va_end. */ + +void test_va_arg_after_va_end (int placeholder, ...) +{ + int i; + va_list ap; + va_start (ap, placeholder); + va_end (ap); /* { dg-message "'va_end' called here" } */ + i = va_arg (ap, int); /* { dg-warning "'va_arg' after 'va_end'" } */ +} + +/* Type mismatch: expect int, but passed a char *. */ + +static void __attribute__((noinline)) +__analyzer_called_by_test_type_mismatch_1 (int placeholder, ...) +{ + int i; + + va_list ap; + va_start (ap, placeholder); + + i = va_arg (ap, int); /* { dg-warning "'va_arg' expected 'int' but received '\[^\n\r\]*' for variadic argument 1 of 'ap'" } */ + + va_end (ap); +} + +void test_type_mismatch_1 (void) +{ + __analyzer_called_by_test_type_mismatch_1 (42, "foo"); +} + +/* Type mismatch: expect char *, but passed an int. */ + +static void __attribute__((noinline)) +__analyzer_called_by_test_type_mismatch_2 (int placeholder, ...) +{ + const char *str; + + va_list ap; + va_start (ap, placeholder); + + str = va_arg (ap, const char *); /* { dg-warning "'va_arg' expected 'const char \\*' but received 'int' for variadic argument 1 of 'ap'" } */ + + va_end (ap); +} + +void test_type_mismatch_2 (void) +{ + __analyzer_called_by_test_type_mismatch_2 (42, 1066); +} + +/* As above, but with an intermediate function. */ + +static void __attribute__((noinline)) +__analyzer_test_type_mismatch_3_inner (va_list ap) +{ + const char *str; + + str = va_arg (ap, const char *); /* { dg-warning "'va_arg' expected 'const char \\*' but received 'int' for variadic argument 1 of 'ap'" } */ +} + +static void __attribute__((noinline)) +__analyzer_test_type_mismatch_3_middle (int placeholder, ...) +{ + va_list ap; + va_start (ap, placeholder); + + __analyzer_test_type_mismatch_3_inner (ap); + + va_end (ap); +} + +void test_type_mismatch_3 (void) +{ + __analyzer_test_type_mismatch_3_middle (42, 1066); +} + +/* Multiple traversals of the args. */ + +static void __attribute__((noinline)) +__analyzer_called_by_test_multiple_traversals (int placeholder, ...) +{ + va_list ap; + + /* First traversal. */ + { + int i, j; + + va_start (ap, placeholder); + + i = va_arg (ap, int); + __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */ + + j = va_arg (ap, int); + __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */ + + va_end (ap); + } + + /* Second traversal. */ + { + int i, j; + + va_start (ap, placeholder); + + i = va_arg (ap, int); + __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */ + + j = va_arg (ap, int); + __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */ + + va_end (ap); + } +} + +void test_multiple_traversals (void) +{ + __analyzer_called_by_test_multiple_traversals (0, 1066, 42); +} + +/* Multiple traversals, using va_copy. */ + +static void __attribute__((noinline)) +__analyzer_called_by_test_multiple_traversals_2 (int placeholder, ...) +{ + int i, j; + va_list args1; + va_list args2; + + va_start (args1, placeholder); + va_copy (args2, args1); + + /* First traversal. */ + i = va_arg (args1, int); + __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */ + j = va_arg (args1, int); + __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */ + va_end (args1); + + /* Traversal of copy. */ + i = va_arg (args2, int); + __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */ + j = va_arg (args2, int); + __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */ + va_end (args2); +} + +void test_multiple_traversals_2 (void) +{ + __analyzer_called_by_test_multiple_traversals_2 (0, 1066, 42); +} + +/* Multiple traversals, using va_copy after a va_arg. */ + +static void __attribute__((noinline)) +__analyzer_called_by_test_multiple_traversals_3 (int placeholder, ...) +{ + int i, j; + va_list args1; + va_list args2; + + va_start (args1, placeholder); + + /* First traversal. */ + i = va_arg (args1, int); + __analyzer_eval (i == 1066); /* { dg-warning "TRUE" } */ + + /* va_copy after the first va_arg. */ + va_copy (args2, args1); + + j = va_arg (args1, int); + __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */ + va_end (args1); + + /* Traversal of copy. */ + j = va_arg (args2, int); + __analyzer_eval (j == 42); /* { dg-warning "TRUE" } */ + va_end (args2); +} + +void test_multiple_traversals_3 (void) +{ + __analyzer_called_by_test_multiple_traversals_3 (0, 1066, 42); +} + +/* va_copy after va_end. */ + +void test_va_copy_after_va_end (int placeholder, ...) +{ + va_list ap1, ap2; + va_start (ap1, placeholder); + va_end (ap1); /* { dg-message "'va_end' called here" } */ + va_copy (ap2, ap1); /* { dg-warning "'va_copy' after 'va_end'" } */ + va_end (ap2); +} + +/* leak of va_copy. */ + +void test_leak_of_va_copy (int placeholder, ...) +{ + va_list ap1, ap2; + va_start (ap1, placeholder); + va_copy (ap2, ap1); /* { dg-message "'va_copy' called here" } */ + va_end (ap1); +} /* { dg-warning "missing call to 'va_end'" "warning" } */ + /* { dg-message "missing call to 'va_end' to match 'va_copy' at \\(1\\)" "final event" { target *-*-* } .-1 } */ + +/* double va_end. */ + +void test_double_va_end (int placeholder, ...) +{ + va_list ap; + va_start (ap, placeholder); + va_end (ap); /* { dg-message "'va_end' called here" } */ + va_end (ap); /* { dg-warning "'va_end' after 'va_end'" } */ +} + +/* double va_start. */ + +void test_double_va_start (int placeholder, ...) +{ + int i; + va_list ap; + va_start (ap, placeholder); /* { dg-message "'va_start' called here" } */ + va_start (ap, placeholder); /* { dg-warning "missing call to 'va_end'" "warning" } */ + /* { dg-message "missing call to 'va_end' to match 'va_start' at \\(1\\)" "final event" { target *-*-* } .-1 } */ + va_end (ap); +} + +/* va_copy before va_start. */ + +void test_va_copy_before_va_start (int placeholder, ...) +{ + va_list ap1; /* { dg-message "region created on stack here" } */ + va_list ap2; + va_copy (ap2, ap1); /* { dg-warning "use of uninitialized value 'ap1'" } */ + va_end (ap2); +} + +/* Verify that we complain about uses of a va_list after the function + in which va_start was called has returned. */ + +va_list global_ap; + +static void __attribute__((noinline)) +__analyzer_called_by_test_va_arg_after_return (int placeholder, ...) +{ + va_start (global_ap, placeholder); + va_end (global_ap); +} + +void test_va_arg_after_return (void) +{ + int i; + __analyzer_called_by_test_va_arg_after_return (42, 1066); + i = va_arg (global_ap, int); /* { dg-warning "dereferencing pointer 'global_ap' to within stale stack frame" } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/stdarg-fmtstring-1.c b/gcc/testsuite/gcc.dg/analyzer/stdarg-fmtstring-1.c new file mode 100644 index 00000000000..3892c3cdd1c --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/stdarg-fmtstring-1.c @@ -0,0 +1,103 @@ +/* { dg-additional-options "-fno-analyzer-call-summaries -fno-analyzer-state-merge -Wno-analyzer-too-complex" } */ + +void test_format_string (const char *fmt, ...) +{ + __builtin_va_list ap; + __builtin_va_start (ap, fmt); + while (*fmt) + switch (*fmt++) + { + case 's': + { + const char *s = __builtin_va_arg (ap, char *); /* { dg-warning "'va_arg' expected 'const char \\*' but received 'int' for variadic argument 1 of 'ap'" } */ + __builtin_printf ("string: %s\n", s); + } + break; + case 'd': + { + int i = __builtin_va_arg (ap, int); /* { dg-warning "'va_arg' expected 'int' but received '\[^\n\r\]*' for variadic argument 1 of 'ap'" "type mismatch from wrong_type_for_percent_d" } */ + /* { dg-warning "'ap' has no more arguments \\(1 consumed\\)" "not_enough_args" { target *-*-* } .-1 } */ + __builtin_printf ("int: %d\n", i); + } + break; + case 'c': + { + char c = (char)__builtin_va_arg (ap, int); + __builtin_printf ("char: %c\n", c); + } + break; + } + __builtin_va_end (ap); +} + +void test_missing_va_start (const char *fmt, ...) +{ + __builtin_va_list ap; + + while (*fmt) + switch (*fmt++) + { + case 's': + { + const char *s = __builtin_va_arg (ap, char *); /* { dg-warning "use of uninitialized value 'ap'" } */ + __builtin_printf ("string: %s\n", s); + } + break; + case 'd': + { + int i = __builtin_va_arg (ap, int); /* { dg-warning "use of uninitialized value 'ap'" } */ + __builtin_printf ("int: %d\n", i); + } + break; + case 'c': + { + char c = (char)__builtin_va_arg (ap, int); /* { dg-warning "use of uninitialized value 'ap'" } */ + __builtin_printf ("char: %c\n", c); + } + break; + } + __builtin_va_end (ap); +} + +void test_missing_va_end (const char *fmt, ...) +{ + __builtin_va_list ap; + __builtin_va_start (ap, fmt); + while (*fmt) + switch (*fmt++) + { + case 's': + { + const char *s = __builtin_va_arg (ap, char *); + __builtin_printf ("string: %s\n", s); + } + break; + case 'd': + { + int i = __builtin_va_arg (ap, int); + __builtin_printf ("int: %d\n", i); + } + break; + case 'c': + { + char c = (char)__builtin_va_arg (ap, int); + __builtin_printf ("char: %c\n", c); + } + break; + } +} /* { dg-warning "missing call to 'va_end'" } */ + +void wrong_type_for_percent_s (void) +{ + test_format_string ("%s", 42); +} + +void wrong_type_for_percent_d (void) +{ + test_format_string ("%d", "foo"); +} + +void not_enough_args (void) +{ + test_format_string ("%s%d", "foo"); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1-a.c b/gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1-a.c new file mode 100644 index 00000000000..f56ad88d990 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1-a.c @@ -0,0 +1,24 @@ +/* { dg-do link } */ +/* { dg-require-effective-target lto } */ +/* { dg-additional-options "-flto" } */ +/* { dg-additional-sources stdarg-lto-1-b.c } */ + +#include +#include "stdarg-lto-1.h" + +/* Type mismatch: expect const char *, but passed an int. */ + +void +called_by_test_type_mismatch_1 (int placeholder, ...) +{ + const char *str; + + va_list ap; + va_start (ap, placeholder); + + str = va_arg (ap, const char *); /* { dg-warning "'va_arg' expected '\[^\n\r\]*' but received 'int' for variadic argument 1 of 'ap'" } */ + + va_end (ap); +} + +int main() { return 0; } diff --git a/gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1-b.c b/gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1-b.c new file mode 100644 index 00000000000..edd51f02497 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1-b.c @@ -0,0 +1,6 @@ +#include "stdarg-lto-1.h" + +void test_type_mismatch_1 (void) +{ + called_by_test_type_mismatch_1 (42, 1066); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1.h b/gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1.h new file mode 100644 index 00000000000..69835747316 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/stdarg-lto-1.h @@ -0,0 +1 @@ +extern void called_by_test_type_mismatch_1 (int placeholder, ...); diff --git a/gcc/testsuite/gcc.dg/analyzer/stdarg-sentinel-1.c b/gcc/testsuite/gcc.dg/analyzer/stdarg-sentinel-1.c new file mode 100644 index 00000000000..f8c1f0eb0f8 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/stdarg-sentinel-1.c @@ -0,0 +1,25 @@ +/* { dg-additional-options "-Wno-analyzer-too-complex" } */ + +#define NULL ((void *)0) + +void test_sentinel (int arg, ...) +{ + const char *s; + __builtin_va_list ap; + __builtin_va_start (ap, arg); + while (s = __builtin_va_arg (ap, char *)) /* { dg-warning "'ap' has no more arguments \\(2 consumed\\)" } */ + { + (void)s; + } + __builtin_va_end (ap); +} + +void test_caller (void) +{ + test_sentinel (42, "foo", "bar", NULL); +} + +void missing_sentinel (void) +{ + test_sentinel (42, "foo", "bar"); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/stdarg-types-1.c b/gcc/testsuite/gcc.dg/analyzer/stdarg-types-1.c new file mode 100644 index 00000000000..dcea87e6050 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/stdarg-types-1.c @@ -0,0 +1,25 @@ +/* { dg-require-effective-target int32 } */ +/* { dg-require-effective-target lp64 } */ + +/* Type mismatch: expect long, but passed an int. */ + +static void __attribute__((noinline)) +__analyzer_consume_long (int placeholder, ...) +{ + long v; + __builtin_va_list ap; + __builtin_va_start (ap, placeholder); + v = __builtin_va_arg (ap, long); /* { dg-warning "'va_arg' expected 'long int' but received 'int' for variadic argument 1 of 'ap'" } */ + __builtin_va_end (ap); +} + +void test_int_to_long (void) +{ + __analyzer_consume_long (42, 1066); +} + +void test_char_to_long (void) +{ + /* char promoted to int. */ + __analyzer_consume_long (42, 'a'); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/stdarg-types-2.c b/gcc/testsuite/gcc.dg/analyzer/stdarg-types-2.c new file mode 100644 index 00000000000..39d5c6efb9e --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/stdarg-types-2.c @@ -0,0 +1,55 @@ +/* Should be OK to add a "const" qualifier to a ptr. */ + +static void __attribute__((noinline)) +__analyzer_consume_const_char_ptr (int placeholder, ...) +{ + const char *v; + __builtin_va_list ap; + __builtin_va_start (ap, placeholder); + v = __builtin_va_arg (ap, const char *); + __builtin_va_end (ap); +} + +void test_char_ptr_to_const_char_ptr (char *p) +{ + __analyzer_consume_const_char_ptr (42, p); /* { dg-bogus "" } */ +} + +/* What about casting away const-ness? + Currently we don't complain. */ + +static void __attribute__((noinline)) +__analyzer_consume_char_ptr (int placeholder, ...) +{ + char *v; + __builtin_va_list ap; + __builtin_va_start (ap, placeholder); + v = __builtin_va_arg (ap, char *); + __builtin_va_end (ap); +} + +void test_const_char_ptr_to_char_ptr (const char *p) +{ + __analyzer_consume_const_char_ptr (42, p); +} + +/* What about casting ptrs? + Currently we don't complain. */ + +struct foo; +struct bar; + +static void __attribute__((noinline)) +__analyzer_consume_bar_ptr (int placeholder, ...) +{ + struct bar *v; + __builtin_va_list ap; + __builtin_va_start (ap, placeholder); + v = __builtin_va_arg (ap, struct bar *); + __builtin_va_end (ap); +} + +void test_foo_ptr_to_bar_ptr (struct foo *p) +{ + __analyzer_consume_bar_ptr (42, p); +}