From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: by sourceware.org (Postfix, from userid 2209) id 4C5CF3858000; Fri, 4 Aug 2023 20:20:01 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 4C5CF3858000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gcc.gnu.org; s=default; t=1691180401; bh=kVtP1yUA1mdvOVBzurSBd8Tp7Ukkj4xljCa0tfl+lXk=; h=From:To:Subject:Date:From; b=D3CqzMLKoKQe70tHAxQbZ0bi1/tyb3ifehqqEFV4++tRZzrs5nEBSwHd40aqkmK+K suKqMWeSqDZr7xxgIOYHl0ISJfCsmwi0XwdBY1hbdGuXPH+hhJt88FgdUGNZm2g4Hm OG8dyZzd5RtXEd2BdG5AW9+fxhR6wAKil6+sfybQ= MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Type: text/plain; charset="utf-8" From: David Malcolm To: gcc-cvs@gcc.gnu.org Subject: [gcc r14-3001] analyzer: handle function attribute "alloc_size" [PR110426] X-Act-Checkin: gcc X-Git-Author: David Malcolm X-Git-Refname: refs/heads/master X-Git-Oldrev: 187b213ddbe7ea7a3180f6ca3188732999729623 X-Git-Newrev: 021077b94741c9300dfff3a24e95b3ffa3f508a7 Message-Id: <20230804202001.4C5CF3858000@sourceware.org> Date: Fri, 4 Aug 2023 20:20:01 +0000 (GMT) List-Id: https://gcc.gnu.org/g:021077b94741c9300dfff3a24e95b3ffa3f508a7 commit r14-3001-g021077b94741c9300dfff3a24e95b3ffa3f508a7 Author: David Malcolm Date: Fri Aug 4 16:18:40 2023 -0400 analyzer: handle function attribute "alloc_size" [PR110426] This patch makes -fanalyzer make use of the function attribute "alloc_size", allowing -fanalyzer to emit -Wanalyzer-allocation-size, -Wanalyzer-out-of-bounds, and -Wanalyzer-tainted-allocation-size on execution paths involving allocations using such functions. gcc/analyzer/ChangeLog: PR analyzer/110426 * bounds-checking.cc (region_model::check_region_bounds): Handle symbolic base regions. * call-details.cc: Include "stringpool.h" and "attribs.h". (call_details::lookup_function_attribute): New function. * call-details.h (call_details::lookup_function_attribute): New function decl. * region-model-manager.cc (region_model_manager::maybe_fold_binop): Add reference to PR analyzer/110902. * region-model-reachability.cc (reachable_regions::handle_sval): Add symbolic regions for pointers that are conjured svalues for the LHS of a stmt. * region-model.cc (region_model::canonicalize): Purge dynamic extents for regions that aren't referenced. (get_result_size_in_bytes): New function. (region_model::on_call_pre): Use get_result_size_in_bytes and potentially set the dynamic extents of the region pointed to by the return value. (region_model::deref_rvalue): Add param "add_nonnull_constraint" and use it to conditionalize adding the constraint. (pending_diagnostic_subclass::dubious_allocation_size): Add "stmt" param to both ctors and use it to initialize new "m_stmt" field. (pending_diagnostic_subclass::operator==): Use m_stmt; don't use m_lhs or m_rhs. (pending_diagnostic_subclass::m_stmt): New field. (region_model::check_region_size): Generalize to any kind of pointer svalue by using deref_rvalue rather than checking for region_svalue. Pass stmt to dubious_allocation_size ctor. * region-model.h (region_model::deref_rvalue): Add param "add_nonnull_constraint". * svalue.cc (conjured_svalue::lhs_value_p): New function. * svalue.h (conjured_svalue::lhs_value_p): New decl. gcc/testsuite/ChangeLog: PR analyzer/110426 * gcc.dg/analyzer/allocation-size-1.c: Update expected message to reflect consolidation of size and assignment into a single event. * gcc.dg/analyzer/allocation-size-2.c: Likewise. * gcc.dg/analyzer/allocation-size-3.c: Likewise. * gcc.dg/analyzer/allocation-size-4.c: Likewise. * gcc.dg/analyzer/allocation-size-multiline-1.c: Likewise. * gcc.dg/analyzer/allocation-size-multiline-2.c: Likewise. * gcc.dg/analyzer/allocation-size-multiline-3.c: Likewise. * gcc.dg/analyzer/attr-alloc_size-1.c: New test. * gcc.dg/analyzer/attr-alloc_size-2.c: New test. * gcc.dg/analyzer/attr-alloc_size-3.c: New test. * gcc.dg/analyzer/explode-4.c: New test. * gcc.dg/analyzer/taint-size-1.c: Add test coverage for __attribute__ alloc_size. Signed-off-by: David Malcolm Diff: --- gcc/analyzer/bounds-checking.cc | 12 +- gcc/analyzer/call-details.cc | 21 +++ gcc/analyzer/call-details.h | 2 + gcc/analyzer/region-model-manager.cc | 2 + gcc/analyzer/region-model-reachability.cc | 21 +++ gcc/analyzer/region-model.cc | 109 +++++++++++--- gcc/analyzer/region-model.h | 3 +- gcc/analyzer/svalue.cc | 11 ++ gcc/analyzer/svalue.h | 1 + gcc/testsuite/gcc.dg/analyzer/allocation-size-1.c | 3 +- gcc/testsuite/gcc.dg/analyzer/allocation-size-2.c | 3 +- gcc/testsuite/gcc.dg/analyzer/allocation-size-3.c | 9 +- gcc/testsuite/gcc.dg/analyzer/allocation-size-4.c | 6 +- .../gcc.dg/analyzer/allocation-size-multiline-1.c | 12 +- .../gcc.dg/analyzer/allocation-size-multiline-2.c | 15 +- .../gcc.dg/analyzer/allocation-size-multiline-3.c | 10 +- gcc/testsuite/gcc.dg/analyzer/attr-alloc_size-1.c | 81 +++++++++++ gcc/testsuite/gcc.dg/analyzer/attr-alloc_size-2.c | 19 +++ gcc/testsuite/gcc.dg/analyzer/attr-alloc_size-3.c | 14 ++ gcc/testsuite/gcc.dg/analyzer/explode-4.c | 157 +++++++++++++++++++++ gcc/testsuite/gcc.dg/analyzer/taint-size-1.c | 10 ++ 21 files changed, 458 insertions(+), 63 deletions(-) diff --git a/gcc/analyzer/bounds-checking.cc b/gcc/analyzer/bounds-checking.cc index 5e8de9a7aa5..f49cf7cf2af 100644 --- a/gcc/analyzer/bounds-checking.cc +++ b/gcc/analyzer/bounds-checking.cc @@ -981,12 +981,6 @@ region_model::check_region_bounds (const region *reg, region_offset reg_offset = reg->get_offset (m_mgr); const region *base_reg = reg_offset.get_base_region (); - /* Bail out on symbolic regions. - (e.g. because the analyzer did not see previous offsets on the latter, - it might think that a negative access is before the buffer). */ - if (base_reg->symbolic_p ()) - return true; - /* Find out how many bytes were accessed. */ const svalue *num_bytes_sval = reg->get_byte_size_sval (m_mgr); tree num_bytes_tree = maybe_get_integer_cst_tree (num_bytes_sval); @@ -1010,9 +1004,9 @@ region_model::check_region_bounds (const region *reg, offset = wi::sext (reg_offset.get_bit_offset () >> LOG2_BITS_PER_UNIT, TYPE_PRECISION (size_type_node)); - /* If either the offset or the number of bytes accessed are symbolic, - we have to reason about symbolic values. */ - if (reg_offset.symbolic_p () || !num_bytes_tree) + /* If any of the base region, the offset, or the number of bytes accessed + are symbolic, we have to reason about symbolic values. */ + if (base_reg->symbolic_p () || reg_offset.symbolic_p () || !num_bytes_tree) { const svalue* byte_offset_sval; if (!reg_offset.symbolic_p ()) diff --git a/gcc/analyzer/call-details.cc b/gcc/analyzer/call-details.cc index 17edaf26276..793317eaa02 100644 --- a/gcc/analyzer/call-details.cc +++ b/gcc/analyzer/call-details.cc @@ -34,6 +34,8 @@ along with GCC; see the file COPYING3. If not see #include "gimple-pretty-print.h" #include "analyzer/region-model.h" #include "analyzer/call-details.h" +#include "stringpool.h" +#include "attribs.h" #if ENABLE_ANALYZER @@ -226,6 +228,25 @@ call_details::get_or_create_conjured_svalue (const region *reg) const conjured_purge (m_model, m_ctxt)); } +/* Look for a function attribute with name ATTR_NAME on the called + function (or on its type). + Return the attribute if one is found, otherwise return NULL_TREE. */ + +tree +call_details::lookup_function_attribute (const char *attr_name) const +{ + tree allocfntype; + if (tree fndecl = get_fndecl_for_call ()) + allocfntype = TREE_TYPE (fndecl); + else + allocfntype = gimple_call_fntype (m_call); + + if (!allocfntype) + return NULL_TREE; + + return lookup_attribute (attr_name, TYPE_ATTRIBUTES (allocfntype)); +} + } // namespace ana #endif /* #if ENABLE_ANALYZER */ diff --git a/gcc/analyzer/call-details.h b/gcc/analyzer/call-details.h index 14a206ff5d6..25ea5546182 100644 --- a/gcc/analyzer/call-details.h +++ b/gcc/analyzer/call-details.h @@ -64,6 +64,8 @@ public: const svalue *get_or_create_conjured_svalue (const region *) const; + tree lookup_function_attribute (const char *attr_name) const; + private: const gcall *m_call; region_model *m_model; diff --git a/gcc/analyzer/region-model-manager.cc b/gcc/analyzer/region-model-manager.cc index 46d271a295c..65b719056c8 100644 --- a/gcc/analyzer/region-model-manager.cc +++ b/gcc/analyzer/region-model-manager.cc @@ -654,6 +654,8 @@ region_model_manager::maybe_fold_binop (tree type, enum tree_code op, return get_or_create_constant_svalue (build_int_cst (type, 0)); /* (VAL * 1) -> VAL. */ if (cst1 && integer_onep (cst1)) + /* TODO: we ought to have a cast to TYPE here, but doing so introduces + regressions; see PR analyzer/110902. */ return arg0; break; case BIT_AND_EXPR: diff --git a/gcc/analyzer/region-model-reachability.cc b/gcc/analyzer/region-model-reachability.cc index a5c12f49346..1c747e14eab 100644 --- a/gcc/analyzer/region-model-reachability.cc +++ b/gcc/analyzer/region-model-reachability.cc @@ -184,6 +184,27 @@ reachable_regions::handle_sval (const svalue *sval) } add (pointee, ptr_is_mutable); } + else if (sval->get_type () + && TREE_CODE (sval->get_type ()) == POINTER_TYPE + && sval->get_kind () == SK_CONJURED) + { + /* Also add symbolic regions for pointers, but only for conjured svalues + for the LHS of a stmt. Doing it for more leads to state explosions + on chains of calls to external functions, due to each conjured svalue + potentially being modified at each successive call, recursively. */ + const conjured_svalue *conjured_sval = (const conjured_svalue *)sval; + if (conjured_sval->lhs_value_p ()) + { + const region *pointee + = m_model->get_manager ()->get_symbolic_region (sval); + /* Use const-ness of pointer type to affect mutability. */ + bool ptr_is_mutable = true; + if (TYPE_READONLY (TREE_TYPE (sval->get_type ()))) + ptr_is_mutable = false; + add (pointee, ptr_is_mutable); + } + } + /* Treat all svalues within a compound_svalue as reachable. */ if (const compound_svalue *compound_sval = sval->dyn_cast_compound_svalue ()) diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc index 5ed735dc2a2..e92b3f7b074 100644 --- a/gcc/analyzer/region-model.cc +++ b/gcc/analyzer/region-model.cc @@ -441,6 +441,29 @@ region_model::canonicalize () { m_store.canonicalize (m_mgr->get_store_manager ()); m_constraints->canonicalize (); + + if (!m_dynamic_extents.is_empty ()) + { + /* Purge any dynamic extents for regions that aren't referenced. + Normally these are eliminated when leaks are detected, but we + can also gain stray heap_allocated_regions that aren't seen + by the leak-detection code. This happens when + region_model::on_call_pre provides a default result for a + function with both attributes "malloc" and "alloc_size" that + also has a known_function implementation. + Purge dynamic extent information for such regions. */ + auto_bitmap referenced_base_region_ids; + get_referenced_base_regions (referenced_base_region_ids); + auto_vec purgable_dyn_extents; + for (auto iter : m_dynamic_extents) + { + const region *reg = iter.first; + if (!bitmap_bit_p (referenced_base_region_ids, reg->get_id ())) + purgable_dyn_extents.safe_push (reg); + } + for (auto reg : purgable_dyn_extents) + m_dynamic_extents.remove (reg); + } } /* Return true if this region_model is in canonical form. */ @@ -1462,6 +1485,48 @@ region_model::get_known_function (enum internal_fn ifn) const return known_fn_mgr->get_internal_fn (ifn); } +/* Look for attribute "alloc_size" on the called function and, if found, + return a symbolic value of type size_type_node for the allocation size + based on the call's parameters. + Otherwise, return null. */ + +static const svalue * +get_result_size_in_bytes (const call_details &cd) +{ + const tree attr = cd.lookup_function_attribute ("alloc_size"); + if (!attr) + return nullptr; + + const tree atval_1 = TREE_VALUE (attr); + if (!atval_1) + return nullptr; + + unsigned argidx1 = TREE_INT_CST_LOW (TREE_VALUE (atval_1)) - 1; + if (cd.num_args () <= argidx1) + return nullptr; + + const svalue *sval_arg1 = cd.get_arg_svalue (argidx1); + + if (const tree atval_2 = TREE_CHAIN (atval_1)) + { + /* Two arguments. */ + unsigned argidx2 = TREE_INT_CST_LOW (TREE_VALUE (atval_2)) - 1; + if (cd.num_args () <= argidx2) + return nullptr; + const svalue *sval_arg2 = cd.get_arg_svalue (argidx2); + /* TODO: ideally we shouldn't need this cast here; + see PR analyzer/110902. */ + return cd.get_manager ()->get_or_create_cast + (size_type_node, + cd.get_manager ()->get_or_create_binop (size_type_node, + MULT_EXPR, + sval_arg1, sval_arg2)); + } + else + /* Single argument. */ + return cd.get_manager ()->get_or_create_cast (size_type_node, sval_arg1); +} + /* Update this model for the CALL stmt, using CTXT to report any diagnostics - the first half. @@ -1522,6 +1587,11 @@ region_model::on_call_pre (const gcall *call, region_model_context *ctxt) lhs_region, conjured_purge (this, ctxt)); + if (const svalue *size_in_bytes = get_result_size_in_bytes (cd)) + { + const region *reg = deref_rvalue (sval, NULL_TREE, ctxt, false); + set_dynamic_extents (reg, size_in_bytes, ctxt); + } } set_value (lhs_region, sval, ctxt); } @@ -2461,7 +2531,8 @@ region_model::region_exists_p (const region *reg) const const region * region_model::deref_rvalue (const svalue *ptr_sval, tree ptr_tree, - region_model_context *ctxt) const + region_model_context *ctxt, + bool add_nonnull_constraint) const { gcc_assert (ptr_sval); gcc_assert (POINTER_TYPE_P (ptr_sval->get_type ())); @@ -2471,9 +2542,13 @@ region_model::deref_rvalue (const svalue *ptr_sval, tree ptr_tree, -Wanalyzer-null-dereference for the case where we later have an if (PTR_SVAL) that would occur if we considered the false branch and transitioned the malloc state machine from start->null. */ - tree null_ptr_cst = build_int_cst (ptr_sval->get_type (), 0); - const svalue *null_ptr = m_mgr->get_or_create_constant_svalue (null_ptr_cst); - m_constraints->add_constraint (ptr_sval, NE_EXPR, null_ptr); + if (add_nonnull_constraint) + { + tree null_ptr_cst = build_int_cst (ptr_sval->get_type (), 0); + const svalue *null_ptr + = m_mgr->get_or_create_constant_svalue (null_ptr_cst); + m_constraints->add_constraint (ptr_sval, NE_EXPR, null_ptr); + } switch (ptr_sval->get_kind ()) { @@ -2851,14 +2926,15 @@ class dubious_allocation_size : public pending_diagnostic_subclass { public: - dubious_allocation_size (const region *lhs, const region *rhs) - : m_lhs (lhs), m_rhs (rhs), m_expr (NULL_TREE), + dubious_allocation_size (const region *lhs, const region *rhs, + const gimple *stmt) + : m_lhs (lhs), m_rhs (rhs), m_expr (NULL_TREE), m_stmt (stmt), m_has_allocation_event (false) {} dubious_allocation_size (const region *lhs, const region *rhs, - tree expr) - : m_lhs (lhs), m_rhs (rhs), m_expr (expr), + tree expr, const gimple *stmt) + : m_lhs (lhs), m_rhs (rhs), m_expr (expr), m_stmt (stmt), m_has_allocation_event (false) {} @@ -2869,8 +2945,8 @@ public: bool operator== (const dubious_allocation_size &other) const { - return m_lhs == other.m_lhs && m_rhs == other.m_rhs - && pending_diagnostic::same_tree_p (m_expr, other.m_expr); + return (m_stmt == other.m_stmt + && pending_diagnostic::same_tree_p (m_expr, other.m_expr)); } int get_controlling_option () const final override @@ -2940,6 +3016,7 @@ private: const region *m_lhs; const region *m_rhs; const tree m_expr; + const gimple *m_stmt; bool m_has_allocation_event; }; @@ -3139,10 +3216,6 @@ region_model::check_region_size (const region *lhs_reg, const svalue *rhs_sval, if (!is_any_cast_p (ctxt->get_stmt ())) return; - const region_svalue *reg_sval = dyn_cast (rhs_sval); - if (!reg_sval) - return; - tree pointer_type = lhs_reg->get_type (); if (pointer_type == NULL_TREE || !POINTER_TYPE_P (pointer_type)) return; @@ -3167,7 +3240,7 @@ region_model::check_region_size (const region *lhs_reg, const svalue *rhs_sval, || integer_onep (pointee_size_tree)) return; - const region *rhs_reg = reg_sval->get_pointee (); + const region *rhs_reg = deref_rvalue (rhs_sval, NULL_TREE, ctxt, false); const svalue *capacity = get_capacity (rhs_reg); switch (capacity->get_kind ()) { @@ -3180,7 +3253,8 @@ region_model::check_region_size (const region *lhs_reg, const svalue *rhs_sval, && !capacity_compatible_with_type (cst_cap, pointee_size_tree, is_struct)) ctxt->warn (make_unique (lhs_reg, rhs_reg, - cst_cap)); + cst_cap, + ctxt->get_stmt ())); } break; default: @@ -3193,7 +3267,8 @@ region_model::check_region_size (const region *lhs_reg, const svalue *rhs_sval, tree expr = get_representative_tree (capacity); ctxt->warn (make_unique (lhs_reg, rhs_reg, - expr)); + expr, + ctxt->get_stmt ())); } } break; diff --git a/gcc/analyzer/region-model.h b/gcc/analyzer/region-model.h index d6d9615490c..0cf38714c96 100644 --- a/gcc/analyzer/region-model.h +++ b/gcc/analyzer/region-model.h @@ -352,7 +352,8 @@ class region_model const svalue *get_rvalue (tree expr, region_model_context *ctxt) const; const region *deref_rvalue (const svalue *ptr_sval, tree ptr_tree, - region_model_context *ctxt) const; + region_model_context *ctxt, + bool add_nonnull_constraint = true) const; const svalue *get_rvalue_for_bits (tree type, const region *reg, diff --git a/gcc/analyzer/svalue.cc b/gcc/analyzer/svalue.cc index 5d5c80f88c6..064627f3dcc 100644 --- a/gcc/analyzer/svalue.cc +++ b/gcc/analyzer/svalue.cc @@ -1940,6 +1940,17 @@ conjured_svalue::accept (visitor *v) const v->visit_conjured_svalue (this); } +/* Return true iff this conjured_svalue is for the LHS of the + stmt that conjured it. */ + +bool +conjured_svalue::lhs_value_p () const +{ + if (tree decl = m_id_reg->maybe_get_decl ()) + return decl == gimple_get_lhs (m_stmt); + return false; +} + /* class asm_output_svalue : public svalue. */ /* Implementation of svalue::dump_to_pp vfunc for asm_output_svalue. */ diff --git a/gcc/analyzer/svalue.h b/gcc/analyzer/svalue.h index fbb10187677..5492b1e0b7c 100644 --- a/gcc/analyzer/svalue.h +++ b/gcc/analyzer/svalue.h @@ -1411,6 +1411,7 @@ public: const gimple *get_stmt () const { return m_stmt; } const region *get_id_region () const { return m_id_reg; } + bool lhs_value_p () const; private: const gimple *m_stmt; diff --git a/gcc/testsuite/gcc.dg/analyzer/allocation-size-1.c b/gcc/testsuite/gcc.dg/analyzer/allocation-size-1.c index 003914ed96c..dcffc1175cf 100644 --- a/gcc/testsuite/gcc.dg/analyzer/allocation-size-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/allocation-size-1.c @@ -16,8 +16,7 @@ void test_2 (void) free (ptr); /* { dg-warning "allocated buffer size is not a multiple of the pointee's size \\\[CWE-131\\\]" "warning" { target *-*-* } malloc2 } */ - /* { dg-message "42 bytes" "note" { target *-*-* } malloc2 } */ - /* { dg-message "'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4'" "note" { target *-*-* } malloc2 } */ + /* { dg-message "allocated 42 bytes and assigned to 'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4'" "note" { target *-*-* } malloc2 } */ } void test_3 (void) diff --git a/gcc/testsuite/gcc.dg/analyzer/allocation-size-2.c b/gcc/testsuite/gcc.dg/analyzer/allocation-size-2.c index eb770f73d4a..d26c2672531 100644 --- a/gcc/testsuite/gcc.dg/analyzer/allocation-size-2.c +++ b/gcc/testsuite/gcc.dg/analyzer/allocation-size-2.c @@ -19,8 +19,7 @@ void test_2 (int32_t n) free (ptr); /* { dg-warning "allocated buffer size is not a multiple of the pointee's size \\\[CWE-131\\\]" "warning" { target *-*-* } malloc2 } */ - /* { dg-message "'\[a-z0-9\\*\\(\\)\\s\]*' bytes" "note" { target *-*-* } malloc2 } */ - /* { dg-message "'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4" "note" { target *-*-* } malloc2 } */ + /* { dg-message "allocated '\[a-z0-9\\*\\(\\)\\s\]*' bytes and assigned to 'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4" "note" { target *-*-* } malloc2 } */ } void test_3 (int32_t n) diff --git a/gcc/testsuite/gcc.dg/analyzer/allocation-size-3.c b/gcc/testsuite/gcc.dg/analyzer/allocation-size-3.c index 6751441dd18..6b753073008 100644 --- a/gcc/testsuite/gcc.dg/analyzer/allocation-size-3.c +++ b/gcc/testsuite/gcc.dg/analyzer/allocation-size-3.c @@ -20,8 +20,7 @@ void test_1 (void) free (id_sequence); /* { dg-warning "allocated buffer size is not a multiple of the pointee's size \\\[CWE-131\\\]" "warning" { target *-*-* } malloc1 } */ - /* { dg-message "3 bytes" "note" { target *-*-* } malloc1 } */ - /* { dg-message "'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4'" "note" { target *-*-* } malloc1 } */ + /* { dg-message "allocated 3 bytes and assigned to 'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4'" "note" { target *-*-* } malloc1 } */ } void test_2 (void) @@ -30,8 +29,7 @@ void test_2 (void) free (ptr); /* { dg-warning "allocated buffer size is not a multiple of the pointee's size \\\[CWE-131\\\]" "warning" { target *-*-* } malloc2 } */ - /* { dg-message "14 bytes" "note" { target *-*-* } malloc2 } */ - /* { dg-message "'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4'" "note" { target *-*-* } malloc2 } */ + /* { dg-message "allocated 14 bytes and assigned to 'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4'" "note" { target *-*-* } malloc2 } */ } void test_3 (int32_t n) @@ -40,8 +38,7 @@ void test_3 (int32_t n) free (ptr); /* { dg-warning "allocated buffer size is not a multiple of the pointee's size \\\[CWE-131\\\]" "warning" { target *-*-* } malloc3 } */ - /* { dg-message "'\[a-z0-9\\+\\(\\)\\s\]*' bytes" "note" { target *-*-* } malloc3 } */ - /* { dg-message "'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4'" "note" { target *-*-* } malloc3 } */ + /* { dg-message "allocated '\[a-z0-9\\+\\(\\)\\s\]*' bytes and assigned to 'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4'" "note" { target *-*-* } malloc3 } */ } void test_4 (int32_t n, int32_t m) diff --git a/gcc/testsuite/gcc.dg/analyzer/allocation-size-4.c b/gcc/testsuite/gcc.dg/analyzer/allocation-size-4.c index a56b25b4374..642e8f5f496 100644 --- a/gcc/testsuite/gcc.dg/analyzer/allocation-size-4.c +++ b/gcc/testsuite/gcc.dg/analyzer/allocation-size-4.c @@ -30,8 +30,7 @@ void test_2 (void) free (ptr); /* { dg-warning "allocated buffer size is not a multiple of the pointee's size \\\[CWE-131\\\]" "warning" { target *-*-* } malloc2 } */ - /* { dg-message "\\d+ bytes" "note" { target *-*-* } malloc2 } */ - /* { dg-message "'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4'" "note" { target *-*-* } malloc2 } */ + /* { dg-message "allocated \\d+ bytes and assigned to 'int32_t \\*' (\\\{aka '(long )?int \\*'\\\})? here; 'sizeof \\(int32_t (\\\{aka (long )?int\\\})?\\)' is '4'" "note" { target *-*-* } malloc2 } */ } void test_3 (void) @@ -56,6 +55,5 @@ void test_5 (void) free (ptr); /* { dg-warning "allocated buffer size is not a multiple of the pointee's size \\\[CWE-131\\\]" "warning" { target *-*-* } malloc5 } */ - /* { dg-message "allocated 1 byte here" "note" { target *-*-* } malloc5 } */ - /* { dg-message "'struct base \\*' here; 'sizeof \\(struct base\\)' is '\\d+'" "note" { target *-*-* } malloc5 } */ + /* { dg-message "allocated 1 bytes and assigned to 'struct base \\*' here; 'sizeof \\(struct base\\)' is '\\d+'" "note" { target *-*-* } malloc5 } */ } diff --git a/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-1.c b/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-1.c index 7251665105d..9938ba237a0 100644 --- a/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-1.c @@ -11,13 +11,12 @@ void test_constant_1 (void) /* { dg-begin-multiline-output "" } int32_t *ptr = __builtin_malloc (1); ^~~~~~~~~~~~~~~~~~~~ - 'test_constant_1': events 1-2 + 'test_constant_1': event 1 | | int32_t *ptr = __builtin_malloc (1); | ^~~~~~~~~~~~~~~~~~~~ | | - | (1) allocated 1 byte here - | (2) assigned to 'int32_t *' + | (1) allocated 1 bytes and assigned to 'int32_t *' {aka 'int *'} here; 'sizeof (int32_t {aka int})' is '4' | { dg-end-multiline-output "" } */ @@ -30,13 +29,12 @@ void test_constant_2 (void) /* { dg-begin-multiline-output "" } int32_t *ptr = __builtin_malloc (2); ^~~~~~~~~~~~~~~~~~~~ - 'test_constant_2': events 1-2 + 'test_constant_2': event 1 | | int32_t *ptr = __builtin_malloc (2); | ^~~~~~~~~~~~~~~~~~~~ | | - | (1) allocated 2 bytes here - | (2) assigned to 'int32_t *' + | (1) allocated 2 bytes and assigned to 'int32_t *' {aka 'int *'} here; 'sizeof (int32_t {aka int})' is '4' | { dg-end-multiline-output "" } */ @@ -54,6 +52,6 @@ void test_symbolic (int n) | int32_t *ptr = __builtin_malloc (n * 2); | ^~~~~~~~~~~~~~~~~~~~~~~~ | | - | (1) allocated 'n * 2' bytes and assigned to 'int32_t *' + | (1) allocated 'n * 2' bytes and assigned to 'int32_t *' {aka 'int *'} here; 'sizeof (int32_t {aka int})' is '4' | { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-2.c b/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-2.c index 7cadbb74751..9e1269cbb7a 100644 --- a/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-2.c +++ b/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-2.c @@ -11,13 +11,12 @@ void test_constant_1 (void) /* { dg-begin-multiline-output "" } int32_t *ptr = __builtin_alloca (1); ^~~~~~~~~~~~~~~~~~~~ - 'test_constant_1': events 1-2 + 'test_constant_1': event 1 | | int32_t *ptr = __builtin_alloca (1); | ^~~~~~~~~~~~~~~~~~~~ | | - | (1) allocated 1 byte here - | (2) assigned to 'int32_t *' + | (1) allocated 1 bytes and assigned to 'int32_t *' {aka 'int *'} here; 'sizeof (int32_t {aka int})' is '4' | { dg-end-multiline-output "" } */ @@ -29,13 +28,12 @@ void test_constant_2 (void) /* { dg-begin-multiline-output "" } int32_t *ptr = __builtin_alloca (2); ^~~~~~~~~~~~~~~~~~~~ - 'test_constant_2': events 1-2 + 'test_constant_2': event 1 | | int32_t *ptr = __builtin_alloca (2); | ^~~~~~~~~~~~~~~~~~~~ | | - | (1) allocated 2 bytes here - | (2) assigned to 'int32_t *' + | (1) allocated 2 bytes and assigned to 'int32_t *' {aka 'int *'} here; 'sizeof (int32_t {aka int})' is '4' | { dg-end-multiline-output "" } */ @@ -47,13 +45,12 @@ void test_symbolic (int n) /* { dg-begin-multiline-output "" } int32_t *ptr = __builtin_alloca (n * 2); ^~~~~~~~~~~~~~~~~~~~~~~~ - 'test_symbolic': events 1-2 + 'test_symbolic': event 1 | | int32_t *ptr = __builtin_alloca (n * 2); | ^~~~~~~~~~~~~~~~~~~~~~~~ | | - | (1) allocated 'n * 2' bytes here - | (2) assigned to 'int32_t *' + | (1) allocated 'n * 2' bytes and assigned to 'int32_t *' {aka 'int *'} here; 'sizeof (int32_t {aka int})' is '4' | { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-3.c b/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-3.c index b3de582368f..71790d91753 100644 --- a/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-3.c +++ b/gcc/testsuite/gcc.dg/analyzer/allocation-size-multiline-3.c @@ -15,13 +15,12 @@ void test_constant_99 (void) /* { dg-begin-multiline-output "" } int32_t *ptr = alloca (99); ^~~~~~ - 'test_constant_99': events 1-2 + 'test_constant_99': event 1 | | int32_t *ptr = alloca (99); | ^~~~~~ | | - | (1) allocated 99 bytes here - | (2) assigned to 'int32_t *' {aka '{re:long :re?}int *'} here; 'sizeof (int32_t {aka {re:long :re?}int})' is '4' + | (1) allocated 99 bytes and assigned to 'int32_t *' {aka 'int *'} here; 'sizeof (int32_t {aka int})' is '4' | { dg-end-multiline-output "" } */ @@ -33,12 +32,11 @@ void test_symbolic (int n) /* { dg-begin-multiline-output "" } int32_t *ptr = alloca (n * 2); ^~~~~~ - 'test_symbolic': events 1-2 + 'test_symbolic': event 1 | | int32_t *ptr = alloca (n * 2); | ^~~~~~ | | - | (1) allocated 'n * 2' bytes here - | (2) assigned to 'int32_t *' {aka '{re:long :re?}int *'} here; 'sizeof (int32_t {aka {re:long :re?}int})' is '4' + | (1) allocated 'n * 2' bytes and assigned to 'int32_t *' {aka 'int *'} here; 'sizeof (int32_t {aka int})' is '4' | { dg-end-multiline-output "" } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-alloc_size-1.c b/gcc/testsuite/gcc.dg/analyzer/attr-alloc_size-1.c new file mode 100644 index 00000000000..eb3c6955e11 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/attr-alloc_size-1.c @@ -0,0 +1,81 @@ +#include "analyzer-decls.h" + +typedef __SIZE_TYPE__ size_t; + +extern void* (*my_alloc)(size_t) __attribute__ ((alloc_size (1))); +extern void* (*my_alloc_2)(size_t, size_t) __attribute__ ((alloc_size (1, 2))); + +int test_one_arg_concrete_int_ptr (void) +{ + int *x = my_alloc (1); /* { dg-warning "allocated buffer size is not a multiple of the pointee's size" } */ + __analyzer_dump_capacity (x); /* { dg-warning "capacity: '\\(\[^\n\r\]*\\)1'" } */ + x[0] = 0; /* { dg-warning "buffer overflow" } */ + return 0; +} + +void test_one_arg_concrete (void) +{ + char *p = my_alloc (10); + __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(\[^\n\r\]*\\)10'" } */ + p[0] = 'a'; + p[9] = 'b'; + p[10] = 'c'; /* { dg-warning "buffer overflow" } */ + p[11] = 'c'; /* { dg-warning "buffer overflow" } */ +} + +void test_one_arg_symbolic (size_t sz) +{ + char *p = my_alloc (sz); + __analyzer_dump_capacity (p); /* { dg-warning "capacity: 'INIT_VAL\\(sz" } */ +} + +void test_two_args_concrete (void) +{ + char *p = my_alloc_2 (2, 5); + __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(\[^\n\r\]*\\)10'" } */ + p[0] = 'a'; + p[9] = 'b'; + p[10] = 'c'; /* { dg-warning "buffer overflow" } */ + p[11] = 'c'; /* { dg-warning "buffer overflow" } */ +} + +void test_two_args_symbolic_first (size_t sz) +{ + char *p = my_alloc_2 (sz, 5); + __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(INIT_VAL\\(sz\[^\n\r\]*\\*\\(size_t\\)5\\)'" } */ +} + +void test_two_args_symbolic_second (size_t sz) +{ + char *p = my_alloc_2 (5, sz); + __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(INIT_VAL\\(sz\[^\n\r\]*\\*\\(size_t\\)5\\)'" } */ +} + +void test_two_args_symbolic_both (size_t a, size_t b) +{ + char *p = my_alloc_2 (a, b); + __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(INIT_VAL\\(a\[^\n\r\]*\\*INIT_VAL\\(b" } */ +} + +typedef void* (*my_alloc_t)(size_t) __attribute__ ((alloc_size (1))); +typedef void* (*my_alloc_2_t)(size_t, size_t) __attribute__ ((alloc_size (1, 2))); + +void test_one_arg_concrete_fnptr (my_alloc_t fnptr) +{ + char *p = fnptr (10); + __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(\[^\n\r\]*\\)10'" } */ + p[0] = 'a'; + p[9] = 'b'; + p[10] = 'c'; /* { dg-warning "buffer overflow" } */ + p[11] = 'c'; /* { dg-warning "buffer overflow" } */ +} + +void test_two_args_concrete_fnptr (my_alloc_2_t fnptr) +{ + char *p = fnptr (2, 5); + __analyzer_dump_capacity (p); /* { dg-warning "capacity: '\\(\[^\n\r\]*\\)10'" } */ + p[0] = 'a'; + p[9] = 'b'; + p[10] = 'c'; /* { dg-warning "buffer overflow" } */ + p[11] = 'c'; /* { dg-warning "buffer overflow" } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-alloc_size-2.c b/gcc/testsuite/gcc.dg/analyzer/attr-alloc_size-2.c new file mode 100644 index 00000000000..7b787f959dc --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/attr-alloc_size-2.c @@ -0,0 +1,19 @@ +typedef signed long idx_t; + +void *xirealloc (void *p, idx_t s) + __attribute__ ((__alloc_size__ (2))) __attribute__ ((__returns_nonnull__)); + +char * +test_cast_1 (char *buf, idx_t buf_count) +{ + return xirealloc (buf, buf_count + 1); +} + +void *alloc_cast_2 (signed char x, signed char y) + __attribute__ ((__alloc_size__ (1, 2))); + +void * +test_cast_2 (signed char a, signed char b) +{ + return alloc_cast_2 (a + 1, b + 1); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/attr-alloc_size-3.c b/gcc/testsuite/gcc.dg/analyzer/attr-alloc_size-3.c new file mode 100644 index 00000000000..9a33922fdd0 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/attr-alloc_size-3.c @@ -0,0 +1,14 @@ +typedef long int idx_t; +extern void rpl_free (void *ptr); + +void *xicalloc (idx_t n, idx_t s) + __attribute__ ((__malloc__)) + __attribute__ ((__malloc__ (rpl_free, 1))) + __attribute__ ((__alloc_size__ (1, 2))) + __attribute__ ((__returns_nonnull__)); + +void * +xizalloc (idx_t s) +{ + return xicalloc (s, 1); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/explode-4.c b/gcc/testsuite/gcc.dg/analyzer/explode-4.c new file mode 100644 index 00000000000..874b1e9c300 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/explode-4.c @@ -0,0 +1,157 @@ +/* Regression test for state explosion seen whilst implementing + PR analyzer/110426, involving an explosion in the number of + conjured_svalues whilst handling a long chain of external + function calls. */ + +/* { dg-additional-options "-Wno-implicit-function-declaration -Wno-int-conversion -Wno-analyzer-too-complex" } */ + +#define NULL ((void *)0) +typedef unsigned char uint8_t; +typedef unsigned short int uint16_t; +typedef unsigned int uint32_t; +typedef struct Error Error; +typedef struct QEMUTimer QEMUTimer; +enum libusb_class_code { + LIBUSB_CLASS_PER_INTERFACE = 0x00, +}; +struct libusb_device_descriptor {}; +struct libusb_endpoint_descriptor { + uint8_t bEndpointAddress; + uint8_t bmAttributes; +}; +struct libusb_interface_descriptor { + uint8_t bNumEndpoints; + const struct libusb_endpoint_descriptor *endpoint; +}; +struct libusb_interface { + const struct libusb_interface_descriptor *altsetting; +}; +struct libusb_config_descriptor { + uint8_t bNumInterfaces; + const struct libusb_interface *interface; +}; +typedef struct libusb_context libusb_context; +typedef struct libusb_device libusb_device; +typedef struct libusb_device_handle libusb_device_handle; +enum libusb_speed { + LIBUSB_SUCCESS = 0, + LIBUSB_TRANSFER_TYPE_BULK = 2U, +}; +typedef struct USBDevice USBDevice; +typedef struct USBHostDevice USBHostDevice; +struct USBAutoFilter { + uint32_t bus_num; +}; +struct USBHostDevice { + struct USBAutoFilter match; + struct USBHostDevice *tqe_next; + int bus_num; + int addr; + char port[16]; + libusb_device *dev; + libusb_device_handle *dh; + struct libusb_device_descriptor ddesc; +}; +static union { struct USBHostDevice *tqh_first; } hostdevs = {}; +static libusb_context *ctx; +static void usb_host_ep_update(USBHostDevice *s) { + static const char *tname[] = {}; + USBDevice *udev = USB_DEVICE(s); + struct libusb_config_descriptor *conf; + const struct libusb_interface_descriptor *intf; + const struct libusb_endpoint_descriptor *endp; + struct libusb_ss_endpoint_companion_descriptor *endp_ss_comp; + uint8_t devep, type; + int ep; + int rc, i, e; + usb_ep_reset(udev); + rc = libusb_get_active_config_descriptor(s->dev, &conf); + for (i = 0; i < conf->bNumInterfaces; i++) { + intf = &conf->interface[i].altsetting[0]; + trace_usb_host_parse_interface(); + for (e = 0; e < intf->bNumEndpoints; e++) { + endp = &intf->endpoint[e]; + devep = endp->bEndpointAddress; + ep = devep & 0xf; + type = endp->bmAttributes & 0x3; + if (usb_ep_get_type(udev) != 255) { + trace_usb_host_parse_error(); + } + trace_usb_host_parse_endpoint(s->bus_num, s->addr); + usb_ep_set_max_packet_size(udev); + usb_ep_set_type(udev); + usb_ep_set_ifnum(udev); + usb_ep_set_halted(udev); + if (type == LIBUSB_TRANSFER_TYPE_BULK && + libusb_get_ss_endpoint_companion_descriptor( + ctx, endp, &endp_ss_comp) == LIBUSB_SUCCESS) { + usb_ep_set_max_streams(udev); + libusb_free_ss_endpoint_companion_descriptor(endp_ss_comp); + } + } + } +} +static int usb_host_open(USBHostDevice *s, libusb_device *dev, int hostfd) { + USBDevice *udev = USB_DEVICE(s); + int bus_num = 0; + int addr = 0; + Error *local_err = NULL; + if (dev) { + bus_num = libusb_get_bus_number(dev); + addr = libusb_get_device_address(dev); + trace_usb_host_open_started(bus_num, addr); + libusb_open(dev, &s->dh); + trace_usb_host_open_hostfd(hostfd); + libusb_wrap_sys_device(ctx, &s->dh); + dev = libusb_get_device(s->dh); + bus_num = libusb_get_bus_number(dev); + addr = libusb_get_device_address(dev); + } + usb_host_detach_kernel(s); + libusb_get_device_descriptor(dev, &s->ddesc); + usb_host_get_port(s->dev, s->port, sizeof(s->port)); + usb_ep_init(udev); + usb_host_ep_update(s); + libusb_get_device_speed(dev); + usb_device_attach(udev, &local_err); + if (local_err) { + error_report_err(local_err); + goto fail; + } + return 0; +fail: + trace_usb_host_open_failure(); + if (s->dh != NULL) { + usb_host_release_interfaces(s); + libusb_reset_device(s->dh); + usb_host_attach_kernel(s); + } +} +static QEMUTimer *usb_auto_timer; +static void usb_host_vm_state(void *unused, _Bool running) {} +static void usb_host_auto_check(void *unused) { + struct USBHostDevice *s; + struct USBAutoFilter *f; + libusb_device **devs = NULL; + struct libusb_device_descriptor ddesc; + int i, n; + if (usb_host_init() != 0) { + n = libusb_get_device_list(ctx, &devs); + for (i = 0; i < n; i++) { + if (libusb_get_device_descriptor(devs[i], &ddesc) != 0) { + } + for ((s) = ((&hostdevs)->tqh_first); (s); (s) = ((s)->tqe_next)) { + f = &s->match; + if (f->bus_num > 0 && f->bus_num != libusb_get_bus_number(devs[i])) { + } + if (usb_host_open(s, devs[i], 0) < 0) { + } + } + } + libusb_free_device_list(devs, 1); + qemu_add_vm_change_state_handler(usb_host_vm_state, NULL); + timer_new_ms(usb_host_auto_check, NULL); + trace_usb_host_auto_scan_enabled(); + } + timer_mod(usb_auto_timer, qemu_clock_get_ms() + 2000); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/taint-size-1.c b/gcc/testsuite/gcc.dg/analyzer/taint-size-1.c index 0b166f7a86a..1fd5fd486c0 100644 --- a/gcc/testsuite/gcc.dg/analyzer/taint-size-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/taint-size-1.c @@ -39,3 +39,13 @@ test_2 (size_t sz) { memcpy (buf, buf2, sz); /* { dg-warning "use of attacker-controlled value 'sz' as size without upper-bounds checking" } */ } + +/* __attribute__ alloc_size with tainted size. */ + +extern void* (*my_alloc)(size_t) __attribute__ ((alloc_size (1))); + +void * __attribute__((tainted_args)) +test_attr_alloc_size (size_t sz) +{ + return my_alloc (sz); /* { dg-warning "use of attacker-controlled value 'sz' as allocation size without upper-bounds checking" } */ +}