analyzer: rewrite of region and value-handling
This large patch reimplements how the analyzer tracks regions and values. Elimination of region_id and svalue_id ************************************** The patch eliminates region_id and svalue_id in favor of simply using pointers. I'd hoped that the ID classes would make it easier to compare states, avoiding having to compare long hexadecimal addresses in favor of small integers. Unfortunately it added lots of complexity, with the need to remap IDs when comparing or purging states, and the need to "canonicalize" when comparing states. Various "state explosion" bugs in the old implementation were due to failures in canonicalization, where two states that ought to be equal were non-equal due to differences in ID ordering. I spent a lot of time trying to fix canonicalization bugs, and there always seemed to be one more bug. By eliminating IDs in this new implementation, lots of tricky canonicalization goes away and no ID remapping should be needed; almost all of the old validation code becomes redundant. There's still some canonicalization in the new implementation, mostly in constraint_manager, but much less than before. Ownership of regions and svalues ******************************** In the old implementation, each region_model had its own copies of regions and svalues, so there was heap bloat and churn as lots of little objects were cloned when copying program_state instances. In the new implementation the regions and svalues are immutable and are shared thoughout the analysis, rather than being per region_model. They are owned by a manager class, and are effectively singletons. Region and svalue instances can now be compared by pointer rather than by comparing their fields (the manager class takes care of uniqueness). This is a huge simplification, and (I hope) will avoid lots of heap churn as states are copied; all mutable state from regions and svalues is now stored in a "store" class in the region_model. Changes to the meaning of a "region" ************************************ Region subclasses no longer represent internal structure, but instead represent how the regions are reached. So e.g. a global "struct coord c;" is now a decl_region, rather than a struct_region. In the old implementation, the values for each region were stored in the region instances, but in the new implementation the regions are immutable. Memory is now modeled in a new "store" class: a mapping from keys to svalues, where the keys are both concrete bit-offsets from the start of a "base region", and "symbolic" keys (thus hopefully making unions, casts, aliasing etc easier to deal with). So e.g. for assignments to the fields of a struct, it records the mapping from bit-offsets of e.g. field to the values; if that memory is cast to another type and written to, the appropriate clobbering of the bound values can happen. The concept of "what the current stack is" moves from the regions to being a field within the region_model ("m_current_frame"). Bugs fixed by this patch ************************ PR analyzer/93032 (missing leak diagnostic for zlib/contrib/minizip/mztools.c) PR analyzer/93938 (ICE in analyzer) PR analyzer/94011 (ICE in analyzer) PR analyzer/94099 (ICE in analyzer) PR analyzer/94399 (leak false positive with __attribute__((cleanup()))) PR analyzer/94458 (leak false positive) PR analyzer/94503 (ICE on C++ return-value-optimization) PR analyzer/94640 (leak false positive) PR analyzer/94688 (ICE in analyzer) PR analyzer/94689 ("arrays of functions are not meaningful" error) PR analyzer/94839 (leak false positive) PR analyzer/95026 (leak false positive) PR analyzer/95042 (ICE merging const and non-const C++ object instances) PR analyzer/95240 (leak false positive) gcc/ChangeLog: * Makefile.in (ANALYZER_OBJS): Add analyzer/region.o, analyzer/region-model-impl-calls.o, analyzer/region-model-manager.o, analyzer/region-model-reachability.o, analyzer/store.o, and analyzer/svalue.o. * doc/analyzer.texi: Update for changes to analyzer implementation. * tristate.h (tristate::get_value): New accessor. gcc/analyzer/ChangeLog: * analyzer-logging.cc: Ignore "-Wformat-diag". (logger::enter_scope): Use inc_indent in both overloads. (logger::exit_scope): Use dec_indent. * analyzer-logging.h (logger::inc_indent): New. (logger::dec_indent): New. * analyzer-selftests.cc (run_analyzer_selftests): Call analyzer_store_cc_tests. * analyzer-selftests.h (analyzer_store_cc_tests): New decl. * analyzer.cc (get_stmt_location): New function. * analyzer.h (class initial_svalue): New forward decl. (class unaryop_svalue): New forward decl. (class binop_svalue): New forward decl. (class sub_svalue): New forward decl. (class unmergeable_svalue): New forward decl. (class placeholder_svalue): New forward decl. (class widening_svalue): New forward decl. (class compound_svalue): New forward decl. (class conjured_svalue): New forward decl. (svalue_set): New typedef. (class map_region): Delete. (class array_region): Delete. (class frame_region): New forward decl. (class function_region): New forward decl. (class label_region): New forward decl. (class decl_region): New forward decl. (class element_region): New forward decl. (class offset_region): New forward decl. (class cast_region): New forward decl. (class field_region): New forward decl. (class string_region): New forward decl. (class region_model_manager): New forward decl. (class store_manager): New forward decl. (class store): New forward decl. (class call_details): New forward decl. (struct svalue_id_merger_mapping): Delete. (struct canonicalization): Delete. (class function_point): New forward decl. (class engine): New forward decl. (dump_tree): New function decl. (print_quoted_type): New function decl. (readability_comparator): New function decl. (tree_cmp): New function decl. (class path_var): Move here from region-model.h (bit_offset_t, bit_size_t, byte_size_t): New typedefs. (class region_offset): New class. (get_stmt_location): New decl. (struct member_function_hash_traits): New struct. (class consolidation_map): New class. Ignore "-Wformat-diag". * analyzer.opt (-param=analyzer-max-svalue-depth=): New param. (-param=analyzer-max-enodes-for-full-dump=): New param. * call-string.cc: Ignore -Wformat-diag. * checker-path.cc: Move includes of "analyzer/call-string.h" and "analyzer/program-point.h" to before "analyzer/region-model.h", and also include "analyzer/store.h" before it. (state_change_event::state_change_event): Replace "tree var" param with "const svalue *sval". Convert "origin" param from tree to "const svalue *". (state_change_event::get_desc): Call get_representative_tree to convert the var and origin from const svalue * to tree. Use svalue::get_desc rather than %qE when describing state changes. (checker_path::add_final_event): Use get_stmt_location. * checker-path.h (state_change_event::state_change_event): Port from tree to const svalue *. (state_change_event::get_lvalue): Delete. (state_change_event::get_dest_function): New. (state_change_event::m_var): Replace with... (state_change_event::m_sval): ...this. (state_change_event::m_origin): Convert from tree to const svalue *. * constraint-manager.cc: Include "analyzer/call-string.h", "analyzer/program-point.h", and "analyzer/store.h" before "analyzer/region-model.h". (struct bound, struct range): Move to constraint-manager.h. (compare_constants): New function. (range::dump): Rename to... (range::dump_to_pp): ...this. Support NULL constants. (range::dump): Reintroduce for dumping to stderr. (range::constrained_to_single_element): Return result, rather than writing to *OUT. (range::eval_condition): New. (range::below_lower_bound): New. (range::above_upper_bound): New. (equiv_class::equiv_class): Port from svalue_id to const svalue *. (equiv_class::print): Likewise. (equiv_class::hash): Likewise. (equiv_class::operator==): Port from svalue_id to const svalue *. (equiv_class::add): Port from svalue_id to const svalue *. Drop "cm" param. (equiv_class::del): Port from svalue_id to const svalue *. (equiv_class::get_representative): Likewise. (equiv_class::remap_svalue_ids): Delete. (svalue_id_cmp_by_id): Rename to... (svalue_cmp_by_ptr): ...this, porting from svalue_id to const svalue *. (equiv_class::canonicalize): Update qsort comparator. (constraint::implied_by): New. (constraint_manager::constraint_manager): Copy m_mgr in copy ctor. (constraint_manager::dump_to_pp): Add "multiline" param (constraint_manager::dump): Pass "true" for "multiline". (constraint_manager::add_constraint): Port from svalue_id to const svalue *. Split out second part into... (constraint_manager::add_unknown_constraint): ...this new function. Remove self-constraints when merging equivalence classes. (constraint_manager::add_constraint_internal): Remove constraints that would be implied by the new constraint. Port from svalue_id to const svalue *. (constraint_manager::get_equiv_class_by_sid): Rename to... (constraint_manager::get_equiv_class_by_svalue): ...this, porting from svalue_id to const svalue *. (constraint_manager::get_or_add_equiv_class): Port from svalue_id to const svalue *. (constraint_manager::eval_condition): Make const. Call compare_constants and return early if it provides a known result. (constraint_manager::get_ec_bounds): New. (constraint_manager::eval_condition): New overloads. Make existing one const, and use compare_constants. (constraint_manager::purge): Convert "p" param to a template rather that an abstract base class. Port from svalue_id to const svalue *. (class dead_svalue_purger): New class. (constraint_manager::remap_svalue_ids): Delete. (constraint_manager::on_liveness_change): New. (equiv_class_cmp): Port from svalue_id to const svalue *. (constraint_manager::canonicalize): Likewise. Combine with purging of redundant equivalence classes and constraints. (class cleaned_constraint_manager): Delete. (class merger_fact_visitor): Make "m_cm_b" const. Add "m_merger" field. (merger_fact_visitor::fact): Port from svalue_id to const svalue *. Add special case for widening. (constraint_manager::merge): Port from svalue_id to const svalue *. (constraint_manager::clean_merger_input): Delete. (constraint_manager::for_each_fact): Port from svalue_id to const svalue *. (constraint_manager::validate): Likewise. (selftest::test_constraint_conditions): Provide a region_model_manager when creating region_model instances. Add test for self-equality not creating equivalence classes. (selftest::test_transitivity): Provide a region_model_manager when creating region_model instances. Verify that EC-merging happens when constraints are implied. (selftest::test_constant_comparisons): Provide a region_model_manager when creating region_model instances. (selftest::test_constraint_impl): Likewise. Remove over-specified assertions. (selftest::test_equality): Provide a region_model_manager when creating region_model instances. (selftest::test_many_constants): Likewise. Provide a program_point when testing merging. (selftest::run_constraint_manager_tests): Move call to test_constant_comparisons to outside the transitivity guard. * constraint-manager.h (struct bound): Move here from constraint-manager.cc. (struct range): Likewise. (struct::eval_condition): New decl. (struct::below_lower_bound): New decl. (struct::above_upper_bound): New decl. (equiv_class::add): Port from svalue_id to const svalue *. (equiv_class::del): Likewise. (equiv_class::get_representative): Likewise. (equiv_class::remap_svalue_ids): Drop. (equiv_class::m_cst_sid): Convert to.. (equiv_class::m_cst_sval): ...this. (equiv_class::m_vars): Port from svalue_id to const svalue *. (constraint::bool implied_by): New decl. (fact_visitor::on_fact): Port from svalue_id to const svalue *. (constraint_manager::constraint_manager): Add mgr param. (constraint_manager::clone): Delete. (constraint_manager::maybe_get_constant): Delete. (constraint_manager::get_sid_for_constant): Delete. (constraint_manager::get_num_svalues): Delete. (constraint_manager::dump_to_pp): Add "multiline" param. (constraint_manager::get_equiv_class): Port from svalue_id to const svalue *. (constraint_manager::add_constraint): Likewise. (constraint_manager::get_equiv_class_by_sid): Rename to... (constraint_manager::get_equiv_class_by_svalue): ...this, porting from svalue_id to const svalue *. (constraint_manager::add_unknown_constraint): New decl. (constraint_manager::get_or_add_equiv_class): Port from svalue_id to const svalue *. (constraint_manager::eval_condition): Likewise. Add overloads. (constraint_manager::get_ec_bounds): New decl. (constraint_manager::purge): Convert to template. (constraint_manager::remap_svalue_ids): Delete. (constraint_manager::on_liveness_change): New decl. (constraint_manager::canonicalize): Drop param. (constraint_manager::clean_merger_input): Delete. (constraint_manager::m_mgr): New field. * diagnostic-manager.cc: Move includes of "analyzer/call-string.h" and "analyzer/program-point.h" to before "analyzer/region-model.h", and also include "analyzer/store.h" before it. (saved_diagnostic::saved_diagnostic): Add "sval" param. (diagnostic_manager::diagnostic_manager): Add engine param. (diagnostic_manager::add_diagnostic): Add "sval" param, passing it to saved_diagnostic ctor. Update overload to pass NULL for it. (dedupe_winners::dedupe_winners): Add engine param. (dedupe_winners::add): Add "eg" param. Pass m_engine to feasible_p. (dedupe_winner::m_engine): New field. (diagnostic_manager::emit_saved_diagnostics): Pass engine to dedupe_winners. Pass &eg when adding candidates. Pass svalue rather than tree to prune_path. Use get_stmt_location to get primary location of diagnostic. (diagnostic_manager::emit_saved_diagnostic): Likewise. (get_any_origin): Drop. (state_change_event_creator::on_global_state_change): Pass NULL const svalue * rather than NULL_TREE trees to state_change_event ctor. (state_change_event_creator::on_state_change): Port from tree and svalue_id to const svalue *. (for_each_state_change): Port from svalue_id to const svalue *. (struct null_assignment_sm_context): New. (diagnostic_manager::add_events_for_eedge): Add state change events for assignment to NULL. (diagnostic_manager::prune_path): Update param from tree to const svalue *. (diagnostic_manager::prune_for_sm_diagnostic): Port from tracking by tree to by const svalue *. * diagnostic-manager.h (saved_diagnostic::saved_diagnostic): Add sval param. (saved_diagnostic::m_sval): New field. (diagnostic_manager::diagnostic_manager): Add engine param. (diagnostic_manager::get_engine): New. (diagnostic_manager::add_diagnostic): Add "sval" param. (diagnostic_manager::prune_path): Likewise. (diagnostic_manager::prune_for_sm_diagnostic): New overload. (diagnostic_manager::m_eng): New field. * engine.cc: Move includes of "analyzer/call-string.h" and "analyzer/program-point.h" to before "analyzer/region-model.h", and also include "analyzer/store.h" before it. (impl_region_model_context::impl_region_model_context): Update for removal of m_change field. (impl_region_model_context::remap_svalue_ids): Delete. (impl_region_model_context::on_svalue_leak): New. (impl_region_model_context::on_svalue_purge): Delete. (impl_region_model_context::on_liveness_change): New. (impl_region_model_context::on_unknown_change): Update param from svalue_id to const svalue *. Add is_mutable param. (setjmp_svalue::compare_fields): Delete. (setjmp_svalue::accept): New. (setjmp_svalue::add_to_hash): Delete. (setjmp_svalue::dump_to_pp): New. (setjmp_svalue::print_details): Delete. (impl_sm_context::impl_sm_context): Drop "change" param. (impl_sm_context::get_fndecl_for_call): Drop "m_change". (impl_sm_context::on_transition): Drop ATTRIBUTE_UNUSED from "stmt" param. Drop m_change. Port from svalue_id to const svalue *. (impl_sm_context::warn_for_state): Drop m_change. Port from svalue_id to const svalue *. (impl_sm_context::get_readable_tree): Rename to... (impl_sm_context::get_diagnostic_tree): ...this. Port from svalue_id to const svalue *. (impl_sm_context::is_zero_assignment): New. (impl_sm_context::m_change): Delete field. (leak_stmt_finder::find_stmt): Handle m_var being NULL. (readability): Increase penalty for MEM_REF. For SSA_NAMEs, slightly favor the underlying var over the SSA name. Heavily penalize temporaries. Handle RESULT_DECL. (readability_comparator): Make non-static. Consider stack depths. (impl_region_model_context::on_state_leak): Convert from svalue_id to const svalue *, updating for region_model changes. Use id_equal. (impl_region_model_context::on_inherited_svalue): Delete. (impl_region_model_context::on_cast): Delete. (impl_region_model_context::on_condition): Drop m_change. (impl_region_model_context::on_phi): Likewise. (impl_region_model_context::on_unexpected_tree_code): Handle t being NULL. (point_and_state::validate): Update stack checking for region_model changes. (eg_traits::dump_args_t::show_enode_details_p): New. (exploded_node::exploded_node): Initialize m_num_processed_stmts. (exploded_node::get_processed_stmt): New function. (exploded_node::get_dot_fillcolor): Add more colors. (exploded_node::dump_dot): Guard the printing of the point and state with show_enode_details_p. Print the processed stmts for this enode after the initial state. (exploded_node::dump_to_pp): Pass true for new multiline param of program_state::dump_to_pp. (exploded_node::on_stmt): Drop "change" param. Log the stmt. Set input_location. Implement __analyzer_describe. Update implementation of __analyzer_dump and __analyzer_eval. Remove purging of sm-state for unknown fncalls from here. (exploded_node::on_edge): Drop "change" param. (exploded_node::on_longjmp): Port from region_id/svalue_id to const region */const svalue *. Call program_state::detect_leaks. Drop state_change. (exploded_node::detect_leaks): Update for changes to region_model. Call program_state::detect_leaks. (exploded_edge::exploded_edge): Drop ext_state and change params. (exploded_edge::dump_dot): "args" is no longer used. Drop dumping of m_change. (exploded_graph::exploded_graph): Pass engine to m_diagnostic_manager ctor. Use program_point::origin. (exploded_graph::add_function_entry): Drop ctxt. Use program_state::push_frame. Drop state_change. (exploded_graph::get_or_create_node): Drop "change" param. Add "enode_for_diag" param. Update dumping calls for API changes. Pass point to can_merge_with_p. Show enode indices within -Wanalyzer-too-complex diagnostic for hitting the per-point limit. (exploded_graph::add_edge): Drop "change" param. Log which nodes are being connected. Update for changes to exploded_edge ctor. (exploded_graph::get_per_program_point_data): New. (exploded_graph::process_worklist): Pass point to can_merge_with_p. Drop state_change. Update dumping call for API change. (exploded_graph::process_node): Drop state_change. Split the node in-place if an sm-state-change occurs. Update m_num_processed_stmts. Update dumping calls for API change. (exploded_graph::log_stats): Call engine::log_stats. (exploded_graph::dump_states_for_supernode): Update dumping call. (exploded_path::feasible_p): Add "eng" and "eg" params. Rename "i" to "end_idx". Pass the manager to the region_model ctor. Update for every processed stmt in the enode, not just the first. Keep track of which snodes have been visited, and call loop_replay_fixup when revisiting one. (enode_label::get_text): Update dump call for new param. (exploded_graph::dump_exploded_nodes): Likewise. (exploded_graph::get_node_by_index): New. (impl_run_checkers): Create engine instance and pass its address to extrinsic_state ctor. * exploded-graph.h (impl_region_model_context::impl_region_model_context): Drop "change" params. (impl_region_model_context::void remap_svalue_ids): Delete. (impl_region_model_context::on_svalue_purge): Delete. (impl_region_model_context::on_svalue_leak): New. (impl_region_model_context::on_liveness_change): New. (impl_region_model_context::on_state_leak): Update signature. (impl_region_model_context::on_inherited_svalue): Delete. (impl_region_model_context::on_cast): Delete. (impl_region_model_context::on_unknown_change): Update signature. (impl_region_model_context::m_change): Delete. (eg_traits::dump_args_t::show_enode_details_p): New. (exploded_node::on_stmt): Drop "change" param. (exploded_node::on_edge): Likewise. (exploded_node::get_processed_stmt): New decl. (exploded_node::m_num_processed_stmts): New field. (exploded_edge::exploded_edge): Drop ext_state and change params. (exploded_edge::m_change): Delete. (exploded_graph::get_engine): New accessor. (exploded_graph::get_or_create_node): Drop "change" param. Add "enode_for_diag" param. (exploded_graph::add_edge): Drop "change" param. (exploded_graph::get_per_program_point_data): New decl. (exploded_graph::get_node_by_index): New decl. (exploded_path::feasible_p): Add "eng" and "eg" params. * program-point.cc: Include "analyzer/store.h" before including "analyzer/region-model.h". (function_point::function_point): Move here from program-point.h. (function_point::get_function): Likewise. (function_point::from_function_entry): Likewise. (function_point::before_supernode): Likewise. (function_point::next_stmt): New function. * program-point.h (function_point::function_point): Move implementation from here to program-point.cc. (function_point::get_function): Likewise. (function_point::from_function_entry): Likewise. (function_point::before_supernode): Likewise. (function_point::next_stmt): New decl. (program_point::operator!=): New. (program_point::origin): New. (program_point::next_stmt): New. (program_point::m_function_point): Make non-const. * program-state.cc: Move includes of "analyzer/call-string.h" and "analyzer/program-point.h" to before "analyzer/region-model.h", and also include "analyzer/store.h" before it. (extrinsic_state::get_model_manager): New. (sm_state_map::sm_state_map): Pass in sm and sm_idx to ctor, rather than pass the around. (sm_state_map::clone_with_remapping): Delete. (sm_state_map::print): Remove "sm" param in favor of "m_sm". Add "simple" and "multiline" params and support multiline vs single line dumping. (sm_state_map::dump): Remove "sm" param in favor of "m_sm". Add "simple" param. (sm_state_map::hash): Port from svalue_id to const svalue *. (sm_state_map::operator==): Likewise. (sm_state_map::get_state): Likewise. Call canonicalize_svalue on input. Handle inheritance of sm-state. Call get_default_state. (sm_state_map::get_origin): Port from svalue_id to const svalue *. (sm_state_map::set_state): Likewise. Pass in ext_state. Reject attempts to set state on UNKNOWN. (sm_state_map::impl_set_state): Port from svalue_id to const svalue *. Pass in ext_state. Call canonicalize_svalue on input. (sm_state_map::purge_for_unknown_fncall): Delete. (sm_state_map::on_svalue_leak): New. (sm_state_map::remap_svalue_ids): Delete. (sm_state_map::on_liveness_change): New. (sm_state_map::on_unknown_change): Reimplement. (sm_state_map::on_svalue_purge): Delete. (sm_state_map::on_inherited_svalue): Delete. (sm_state_map::on_cast): Delete. (sm_state_map::validate): Delete. (sm_state_map::canonicalize_svalue): New. (program_state::program_state): Update to pass manager to region_model's ctor. Constify num_states and pass state machine and index to sm_state_map ctor. (program_state::print): Update for changes to dump API. (program_state::dump_to_pp): Ignore the summarize param. Add "multiline" param. (program_state::dump_to_file): Add "multiline" param. (program_state::dump): Pass "true" for new "multiline" param. (program_state::push_frame): New. (program_state::on_edge): Drop "change" param. Call program_state::detect_leaks. (program_state::prune_for_point): Add enode_for_diag param. Reimplement based on store class. Call detect_leaks (program_state::remap_svalue_ids): Delete. (program_state::get_representative_tree): Port from svalue_id to const svalue *. (program_state::can_merge_with_p): Add "point" param. Add early reject for sm-differences. Drop id remapping. (program_state::validate): Drop region model and sm_state_map validation. (state_change::sm_change::dump): Delete. (state_change::sm_change::remap_svalue_ids): Delete. (state_change::sm_change::on_svalue_purge): Delete. (log_set_of_svalues): New. (state_change::sm_change::validate): Delete. (state_change::state_change): Delete. (state_change::add_sm_change): Delete. (state_change::affects_p): Delete. (state_change::dump): Delete. (state_change::remap_svalue_ids): Delete. (state_change::on_svalue_purge): Delete. (state_change::validate): Delete. (selftest::assert_dump_eq): Delete. (ASSERT_DUMP_EQ): Delete. (selftest::test_sm_state_map): Update for changes to region_model and sm_state_map, porting from svalue_id to const svalue *. (selftest::test_program_state_dumping): Likewise. Drop test of dumping, renaming to... (selftest::test_program_state_1): ...this. (selftest::test_program_state_dumping_2): Likewise, renaming to... (selftest::test_program_state_2): ...this. (selftest::test_program_state_merging): Update for changes to region_model. (selftest::test_program_state_merging_2): Likewise. (selftest::analyzer_program_state_cc_tests): Update for renamed tests. * program-state.h (extrinsic_state::extrinsic_state): Add logger and engine params. (extrinsic_state::get_logger): New accessor. (extrinsic_state::get_engine): New accessor. (extrinsic_state::get_model_manager): New accessor. (extrinsic_state::m_logger): New field. (extrinsic_state::m_engine): New field. (struct default_hash_traits<svalue_id>): Delete. (pod_hash_traits<svalue_id>::hash): Delete. (pod_hash_traits<svalue_id>::equal): Delete. (pod_hash_traits<svalue_id>::mark_deleted): Delete. (pod_hash_traits<svalue_id>::mark_empty): Delete. (pod_hash_traits<svalue_id>::is_deleted): Delete. (pod_hash_traits<svalue_id>::is_empty): Delete. (sm_state_map::entry_t::entry_t): Port from svalue_id to const svalue *. (sm_state_map::entry_t::m_origin): Likewise. (sm_state_map::map_t): Likewise. (sm_state_map::sm_state_map): Add state_machine and index params. (sm_state_map::clone_with_remapping): Delete. (sm_state_map::print): Drop sm param; add simple and multiline params. (sm_state_map::dump): Drop sm param; add simple param. (sm_state_map::get_state): Port from svalue_id to const svalue *. Add ext_state param. (sm_state_map::get_origin): Likewise. (sm_state_map::set_state): Likewise. (sm_state_map::impl_set_state): Likewise. (sm_state_map::purge_for_unknown_fncall): Delete. (sm_state_map::remap_svalue_ids): Delete. (sm_state_map::on_svalue_purge): Delete. (sm_state_map::on_svalue_leak): New. (sm_state_map::on_liveness_change): New. (sm_state_map::on_inherited_svalue): Delete. (sm_state_map::on_cast): Delete. (sm_state_map::validate): Delete. (sm_state_map::on_unknown_change): Port from svalue_id to const svalue *. Add is_mutable and ext_state params. (sm_state_map::canonicalize_svalue): New. (sm_state_map::m_sm): New field. (sm_state_map::m_sm_idx): New field. (program_state::operator=): Delete. (program_state::dump_to_pp): Drop "summarize" param, adding "simple" and "multiline". (program_state::dump_to_file): Likewise. (program_state::dump): Rename "summarize" to "simple". (program_state::push_frame): New. (program_state::get_current_function): New. (program_state::on_edge): Drop "change" param. (program_state::prune_for_point): Likewise. Add enode_for_diag param. (program_state::remap_svalue_ids): Delete. (program_state::get_representative_tree): Port from svalue_id to const svalue *. (program_state::can_purge_p): Likewise. Pass ext_state to get_state. (program_state::can_merge_with_p): Add point param. (program_state::detect_leaks): New. (state_change_visitor::on_state_change): Port from tree and svalue_id to a pair of const svalue *. (class state_change): Delete. * region.cc: New file. * region-model-impl-calls.cc: New file. * region-model-manager.cc: New file. * region-model-reachability.cc: New file. * region-model-reachability.h: New file. * region-model.cc: Include "analyzer/call-string.h", "analyzer/program-point.h", and "analyzer/store.h" before "analyzer/region-model.h". Include "analyzer/region-model-reachability.h". (dump_tree): Make non-static. (dump_quoted_tree): Make non-static. (print_quoted_type): Make non-static. (path_var::dump): Delete. (dump_separator): Delete. (class impl_constraint_manager): Delete. (svalue_id::print): Delete. (svalue_id::dump_node_name_to_pp): Delete. (svalue_id::validate): Delete. (region_id::print): Delete. (region_id::dump_node_name_to_pp): Delete. (region_id::validate): Delete. (region_id_set::region_id_set): Delete. (svalue_id_set::svalue_id_set): Delete. (svalue::operator==): Delete. (svalue::hash): Delete. (svalue::print): Delete. (svalue::dump_dot_to_pp): Delete. (svalue::remap_region_ids): Delete. (svalue::walk_for_canonicalization): Delete. (svalue::get_child_sid): Delete. (svalue::maybe_get_constant): Delete. (region_svalue::compare_fields): Delete. (region_svalue::add_to_hash): Delete. (region_svalue::print_details): Delete. (region_svalue::dump_dot_to_pp): Delete. (region_svalue::remap_region_ids): Delete. (region_svalue::merge_values): Delete. (region_svalue::walk_for_canonicalization): Delete. (region_svalue::eval_condition): Delete. (constant_svalue::compare_fields): Delete. (constant_svalue::add_to_hash): Delete. (constant_svalue::merge_values): Delete. (constant_svalue::eval_condition): Move to svalue.cc. (constant_svalue::print_details): Delete. (constant_svalue::get_child_sid): Delete. (unknown_svalue::compare_fields): Delete. (unknown_svalue::add_to_hash): Delete. (unknown_svalue::print_details): Delete. (poison_kind_to_str): Move to svalue.cc. (poisoned_svalue::compare_fields): Delete. (poisoned_svalue::add_to_hash): Delete. (poisoned_svalue::print_details): Delete. (region_kind_to_str): Move to region.cc and reimplement. (region::operator==): Delete. (region::get_parent_region): Delete. (region::set_value): Delete. (region::become_active_view): Delete. (region::deactivate_any_active_view): Delete. (region::deactivate_view): Delete. (region::get_value): Delete. (region::get_inherited_child_sid): Delete. (region_model::copy_region): Delete. (region_model::copy_struct_region): Delete. (region_model::copy_union_region): Delete. (region_model::copy_array_region): Delete. (region::hash): Delete. (region::print): Delete. (region::dump_dot_to_pp): Delete. (region::dump_to_pp): Delete. (region::dump_child_label): Delete. (region::validate): Delete. (region::remap_svalue_ids): Delete. (region::remap_region_ids): Delete. (region::add_view): Delete. (region::get_view): Delete. (region::region): Move to region.cc. (region::add_to_hash): Delete. (region::print_fields): Delete. (region::non_null_p): Delete. (primitive_region::clone): Delete. (primitive_region::walk_for_canonicalization): Delete. (map_region::map_region): Delete. (map_region::compare_fields): Delete. (map_region::print_fields): Delete. (map_region::validate): Delete. (map_region::dump_dot_to_pp): Delete. (map_region::dump_child_label): Delete. (map_region::get_or_create): Delete. (map_region::get): Delete. (map_region::add_to_hash): Delete. (map_region::remap_region_ids): Delete. (map_region::unbind): Delete. (map_region::get_tree_for_child_region): Delete. (map_region::get_tree_for_child_region): Delete. (tree_cmp): Move to region.cc. (map_region::can_merge_p): Delete. (map_region::walk_for_canonicalization): Delete. (map_region::get_value_by_name): Delete. (struct_or_union_region::valid_key_p): Delete. (struct_or_union_region::compare_fields): Delete. (struct_region::clone): Delete. (struct_region::compare_fields): Delete. (union_region::clone): Delete. (union_region::compare_fields): Delete. (frame_region::compare_fields): Delete. (frame_region::clone): Delete. (frame_region::valid_key_p): Delete. (frame_region::print_fields): Delete. (frame_region::add_to_hash): Delete. (globals_region::compare_fields): Delete. (globals_region::clone): Delete. (globals_region::valid_key_p): Delete. (code_region::compare_fields): Delete. (code_region::clone): Delete. (code_region::valid_key_p): Delete. (array_region::array_region): Delete. (array_region::get_element): Delete. (array_region::clone): Delete. (array_region::compare_fields): Delete. (array_region::print_fields): Delete. (array_region::validate): Delete. (array_region::dump_dot_to_pp): Delete. (array_region::dump_child_label): Delete. (array_region::get_or_create): Delete. (array_region::get): Delete. (array_region::add_to_hash): Delete. (array_region::remap_region_ids): Delete. (array_region::get_key_for_child_region): Delete. (array_region::key_cmp): Delete. (array_region::walk_for_canonicalization): Delete. (array_region::key_from_constant): Delete. (array_region::constant_from_key): Delete. (function_region::compare_fields): Delete. (function_region::clone): Delete. (function_region::valid_key_p): Delete. (stack_region::stack_region): Delete. (stack_region::compare_fields): Delete. (stack_region::clone): Delete. (stack_region::print_fields): Delete. (stack_region::dump_child_label): Delete. (stack_region::validate): Delete. (stack_region::push_frame): Delete. (stack_region::get_current_frame_id): Delete. (stack_region::pop_frame): Delete. (stack_region::add_to_hash): Delete. (stack_region::remap_region_ids): Delete. (stack_region::can_merge_p): Delete. (stack_region::walk_for_canonicalization): Delete. (stack_region::get_value_by_name): Delete. (heap_region::heap_region): Delete. (heap_region::compare_fields): Delete. (heap_region::clone): Delete. (heap_region::walk_for_canonicalization): Delete. (root_region::root_region): Delete. (root_region::compare_fields): Delete. (root_region::clone): Delete. (root_region::print_fields): Delete. (root_region::validate): Delete. (root_region::dump_child_label): Delete. (root_region::push_frame): Delete. (root_region::get_current_frame_id): Delete. (root_region::pop_frame): Delete. (root_region::ensure_stack_region): Delete. (root_region::get_stack_region): Delete. (root_region::ensure_globals_region): Delete. (root_region::get_code_region): Delete. (root_region::ensure_code_region): Delete. (root_region::get_globals_region): Delete. (root_region::ensure_heap_region): Delete. (root_region::get_heap_region): Delete. (root_region::remap_region_ids): Delete. (root_region::can_merge_p): Delete. (root_region::add_to_hash): Delete. (root_region::walk_for_canonicalization): Delete. (root_region::get_value_by_name): Delete. (symbolic_region::symbolic_region): Delete. (symbolic_region::compare_fields): Delete. (symbolic_region::clone): Delete. (symbolic_region::walk_for_canonicalization): Delete. (symbolic_region::print_fields): Delete. (region_model::region_model): Add region_model_manager * param. Reimplement in terms of store, dropping impl_constraint_manager subclass. (region_model::operator=): Reimplement in terms of store (region_model::operator==): Likewise. (region_model::hash): Likewise. (region_model::print): Delete. (region_model::print_svalue): Delete. (region_model::dump_dot_to_pp): Delete. (region_model::dump_dot_to_file): Delete. (region_model::dump_dot): Delete. (region_model::dump_to_pp): Replace "summarize" param with "simple" and "multiline". Port to store-based implementation. (region_model::dump): Replace "summarize" param with "simple" and "multiline". (dump_vec_of_tree): Delete. (region_model::dump_summary_of_rep_path_vars): Delete. (region_model::validate): Delete. (svalue_id_cmp_by_constant_svalue_model): Delete. (svalue_id_cmp_by_constant_svalue): Delete. (region_model::canonicalize): Drop "ctxt" param. Reimplement in terms of store and constraints. (region_model::canonicalized_p): Remove NULL arg to canonicalize. (region_model::loop_replay_fixup): New. (poisoned_value_diagnostic::emit): Tweak wording of warnings. (region_model::check_for_poison): Delete. (region_model::get_gassign_result): New. (region_model::on_assignment): Port to store-based implementation. (region_model::on_call_pre): Delete calls to check_for_poison. Move implementations to region-model-impl-calls.c and port to store-based implementation. (region_model::on_call_post): Likewise. (class reachable_regions): Move to region-model-reachability.h/cc and port to store-based implementation. (region_model::handle_unrecognized_call): Port to store-based implementation. (region_model::get_reachable_svalues): New. (region_model::on_setjmp): Port to store-based implementation. (region_model::on_longjmp): Likewise. (region_model::handle_phi): Drop is_back_edge param and the logic using it. (region_model::get_lvalue_1): Port from region_id to const region *. (region_model::make_region_for_unexpected_tree_code): Delete. (assert_compat_types): If the check fails, use internal_error to show the types. (region_model::get_lvalue): Port from region_id to const region *. (region_model::get_rvalue_1): Port from svalue_id to const svalue *. (region_model::get_rvalue): Likewise. (region_model::get_or_create_ptr_svalue): Delete. (region_model::get_or_create_constant_svalue): Delete. (region_model::get_svalue_for_fndecl): Delete. (region_model::get_region_for_fndecl): Delete. (region_model::get_svalue_for_label): Delete. (region_model::get_region_for_label): Delete. (build_cast): Delete. (region_model::maybe_cast_1): Delete. (region_model::maybe_cast): Delete. (region_model::get_field_region): Delete. (region_model::get_store_value): New. (region_model::region_exists_p): New. (region_model::deref_rvalue): Port from svalue_id to const svalue *. (region_model::set_value): Likewise. (region_model::clobber_region): New. (region_model::purge_region): New. (region_model::zero_fill_region): New. (region_model::mark_region_as_unknown): New. (region_model::eval_condition): Port from svalue_id to const svalue *. (region_model::eval_condition_without_cm): Likewise. (region_model::compare_initial_and_pointer): New. (region_model::add_constraint): Port from svalue_id to const svalue *. (region_model::maybe_get_constant): Delete. (region_model::get_representative_path_var): New. (region_model::add_new_malloc_region): Delete. (region_model::get_representative_tree): Port to const svalue *. (region_model::get_representative_path_var): Port to const region *. (region_model::get_path_vars_for_svalue): Delete. (region_model::set_to_new_unknown_value): Delete. (region_model::update_for_phis): Don't pass is_back_edge to handle_phi. (region_model::update_for_call_superedge): Port from svalue_id to const svalue *. (region_model::update_for_return_superedge): Port to store-based implementation. (region_model::update_for_call_summary): Replace set_to_new_unknown_value with mark_region_as_unknown. (region_model::get_root_region): Delete. (region_model::get_stack_region_id): Delete. (region_model::push_frame): Delete. (region_model::get_current_frame_id): Delete. (region_model::get_current_function): Delete. (region_model::pop_frame): Delete. (region_model::on_top_level_param): New. (region_model::get_stack_depth): Delete. (region_model::get_function_at_depth): Delete. (region_model::get_globals_region_id): Delete. (region_model::add_svalue): Delete. (region_model::replace_svalue): Delete. (region_model::add_region): Delete. (region_model::get_svalue): Delete. (region_model::get_region): Delete. (make_region_for_type): Delete. (region_model::add_region_for_type): Delete. (region_model::on_top_level_param): New. (class restrict_to_used_svalues): Delete. (region_model::purge_unused_svalues): Delete. (region_model::push_frame): New. (region_model::remap_svalue_ids): Delete. (region_model::remap_region_ids): Delete. (region_model::purge_regions): Delete. (region_model::get_descendents): Delete. (region_model::delete_region_and_descendents): Delete. (region_model::poison_any_pointers_to_bad_regions): Delete. (region_model::can_merge_with_p): Delete. (region_model::get_current_function): New. (region_model::get_value_by_name): Delete. (region_model::convert_byte_offset_to_array_index): Delete. (region_model::pop_frame): New. (region_model::get_or_create_mem_ref): Delete. (region_model::get_stack_depth): New. (region_model::get_frame_at_index): New. (region_model::unbind_region_and_descendents): New. (struct bad_pointer_finder): New. (region_model::get_or_create_pointer_plus_expr): Delete. (region_model::poison_any_pointers_to_descendents): New. (region_model::get_or_create_view): Delete. (region_model::can_merge_with_p): New. (region_model::get_fndecl_for_call): Port from svalue_id to const svalue *. (struct append_ssa_names_cb_data): New. (get_ssa_name_regions_for_current_frame): New. (region_model::append_ssa_names_cb): New. (model_merger::dump_to_pp): Add "simple" param. Drop dumping of remappings. (model_merger::dump): Add "simple" param to both overloads. (model_merger::can_merge_values_p): Delete. (model_merger::record_regions): Delete. (model_merger::record_svalues): Delete. (svalue_id_merger_mapping::svalue_id_merger_mapping): Delete. (svalue_id_merger_mapping::dump_to_pp): Delete. (svalue_id_merger_mapping::dump): Delete. (region_model::create_region_for_heap_alloc): New. (region_model::create_region_for_alloca): New. (region_model::record_dynamic_extents): New. (canonicalization::canonicalization): Delete. (canonicalization::walk_rid): Delete. (canonicalization::walk_sid): Delete. (canonicalization::dump_to_pp): Delete. (canonicalization::dump): Delete. (inchash::add): Delete overloads for svalue_id and region_id. (engine::log_stats): New. (assert_condition): Add overload comparing svalues. (assert_dump_eq): Pass "true" for multiline. (selftest::test_dump): Update for rewrite of region_model. (selftest::test_dump_2): Rename to... (selftest::test_struct): ...this. Provide a region_model_manager when creating region_model instance. Remove dump test. Add checks for get_offset. (selftest::test_dump_3): Rename to... (selftest::test_array_1): ...this. Provide a region_model_manager when creating region_model instance. Remove dump test. (selftest::test_get_representative_tree): Port from svalue_id to new API. Add test coverage for various expressions. (selftest::test_unique_constants): Provide a region_model_manager for the region_model. Add test coverage for comparing const vs non-const. (selftest::test_svalue_equality): Delete. (selftest::test_region_equality): Delete. (selftest::test_unique_unknowns): New. (class purge_all_svalue_ids): Delete. (class purge_one_svalue_id): Delete. (selftest::test_purging_by_criteria): Delete. (selftest::test_initial_svalue_folding): New. (selftest::test_unaryop_svalue_folding): New. (selftest::test_binop_svalue_folding): New. (selftest::test_sub_svalue_folding): New. (selftest::test_purge_unused_svalues): Delete. (selftest::test_descendent_of_p): New. (selftest::test_assignment): Provide a region_model_manager for the region_model. Drop the dump test. (selftest::test_compound_assignment): Likewise. (selftest::test_stack_frames): Port to new implementation. (selftest::test_get_representative_path_var): Likewise. (selftest::test_canonicalization_1): Rename to... (selftest::test_equality_1): ...this. Port to new API, and add (selftest::test_canonicalization_2): Provide a region_model_manager when creating region_model instances. Remove redundant canicalization. (selftest::test_canonicalization_3): Provide a region_model_manager when creating region_model instances. Remove param from calls to region_model::canonicalize. (selftest::test_canonicalization_4): Likewise. (selftest::assert_region_models_merge): Constify out_merged_svalue. Port to new API. (selftest::test_state_merging): Provide a region_model_manager when creating region_model instances. Provide a program_point point when merging them. Replace set_to_new_unknown_value with usage of placeholder_svalues. Drop get_value_by_name. Port from svalue_id to const svalue *. Add test of heap allocation. (selftest::test_constraint_merging): Provide a region_model_manager when creating region_model instances. Provide a program_point point when merging them. Eliminate use of set_to_new_unknown_value. (selftest::test_widening_constraints): New. (selftest::test_iteration_1): New. (selftest::test_malloc_constraints): Port to store-based implementation. (selftest::test_var): New test. (selftest::test_array_2): New test. (selftest::test_mem_ref): New test. (selftest::test_POINTER_PLUS_EXPR_then_MEM_REF): New. (selftest::test_malloc): New. (selftest::test_alloca): New. (selftest::analyzer_region_model_cc_tests): Update for renamings. Call new functions. * region-model.h (class path_var): Move to analyzer.h. (class svalue_id): Delete. (class region_id): Delete. (class id_map): Delete. (svalue_id_map): Delete. (region_id_map): Delete. (id_map<T>::id_map): Delete. (id_map<T>::put): Delete. (id_map<T>::get_dst_for_src): Delete. (id_map<T>::get_src_for_dst): Delete. (id_map<T>::dump_to_pp): Delete. (id_map<T>::dump): Delete. (id_map<T>::update): Delete. (one_way_svalue_id_map): Delete. (one_way_region_id_map): Delete. (class region_id_set): Delete. (class svalue_id_set): Delete. (struct complexity): New. (class visitor): New. (enum svalue_kind): Add SK_SETJMP, SK_INITIAL, SK_UNARYOP, SK_BINOP, SK_SUB,SK_UNMERGEABLE, SK_PLACEHOLDER, SK_WIDENING, SK_COMPOUND, and SK_CONJURED. (svalue::operator==): Delete. (svalue::operator!=): Delete. (svalue::clone): Delete. (svalue::hash): Delete. (svalue::dump_dot_to_pp): Delete. (svalue::dump_to_pp): New. (svalue::dump): New. (svalue::get_desc): New. (svalue::dyn_cast_initial_svalue): New. (svalue::dyn_cast_unaryop_svalue): New. (svalue::dyn_cast_binop_svalue): New. (svalue::dyn_cast_sub_svalue): New. (svalue::dyn_cast_unmergeable_svalue): New. (svalue::dyn_cast_widening_svalue): New. (svalue::dyn_cast_compound_svalue): New. (svalue::dyn_cast_conjured_svalue): New. (svalue::maybe_undo_cast): New. (svalue::unwrap_any_unmergeable): New. (svalue::remap_region_ids): Delete (svalue::can_merge_p): New. (svalue::walk_for_canonicalization): Delete (svalue::get_complexity): New. (svalue::get_child_sid): Delete (svalue::accept): New. (svalue::live_p): New. (svalue::implicitly_live_p): New. (svalue::svalue): Add complexity param. (svalue::add_to_hash): Delete (svalue::print_details): Delete (svalue::m_complexity): New field. (region_svalue::key_t): New struct. (region_svalue::region_svalue): Port from region_id to const region_id *. Add complexity. (region_svalue::compare_fields): Delete. (region_svalue::clone): Delete. (region_svalue::dump_dot_to_pp): Delete. (region_svalue::get_pointee): Port from region_id to const region_id *. (region_svalue::remap_region_ids): Delete. (region_svalue::merge_values): Delete. (region_svalue::dump_to_pp): New. (region_svalue::accept): New. (region_svalue::walk_for_canonicalization): Delete. (region_svalue::eval_condition): Make params const. (region_svalue::add_to_hash): Delete. (region_svalue::print_details): Delete. (region_svalue::m_rid): Replace with... (region_svalue::m_reg): ...this. (is_a_helper <region_svalue *>::test): Convert to... (is_a_helper <const region_svalue *>::test): ...this. (template <> struct default_hash_traits<region_svalue::key_t>): New. (constant_svalue::constant_svalue): Add complexity. (constant_svalue::compare_fields): Delete. (constant_svalue::clone): Delete. (constant_svalue::add_to_hash): Delete. (constant_svalue::dump_to_pp): New. (constant_svalue::accept): New. (constant_svalue::implicitly_live_p): New. (constant_svalue::merge_values): Delete. (constant_svalue::eval_condition): Make params const. (constant_svalue::get_child_sid): Delete. (constant_svalue::print_details): Delete. (is_a_helper <constant_svalue *>::test): Convert to... (is_a_helper <const constant_svalue *>::test): ...this. (class unknown_svalue): Update leading comment. (unknown_svalue::unknown_svalue): Add complexity. (unknown_svalue::compare_fields): Delete. (unknown_svalue::add_to_hash): Delete. (unknown_svalue::dyn_cast_unknown_svalue): Delete. (unknown_svalue::print_details): Delete. (unknown_svalue::dump_to_pp): New. (unknown_svalue::accept): New. (poisoned_svalue::key_t): New struct. (poisoned_svalue::poisoned_svalue): Add complexity. (poisoned_svalue::compare_fields): Delete. (poisoned_svalue::clone): Delete. (poisoned_svalue::add_to_hash): Delete. (poisoned_svalue::dump_to_pp): New. (poisoned_svalue::accept): New. (poisoned_svalue::print_details): Delete. (is_a_helper <poisoned_svalue *>::test): Convert to... (is_a_helper <const poisoned_svalue *>::test): ...this. (template <> struct default_hash_traits<poisoned_svalue::key_t>): New. (setjmp_record::add_to_hash): New. (setjmp_svalue::key_t): New struct. (setjmp_svalue::compare_fields): Delete. (setjmp_svalue::clone): Delete. (setjmp_svalue::add_to_hash): Delete. (setjmp_svalue::setjmp_svalue): Add complexity. (setjmp_svalue::dump_to_pp): New. (setjmp_svalue::accept): New. (setjmp_svalue::void print_details): Delete. (is_a_helper <const setjmp_svalue *>::test): New. (template <> struct default_hash_traits<setjmp_svalue::key_t>): New. (class initial_svalue : public svalue): New. (is_a_helper <const initial_svalue *>::test): New. (class unaryop_svalue): New. (is_a_helper <const unaryop_svalue *>::test): New. (template <> struct default_hash_traits<unaryop_svalue::key_t>): New. (class binop_svalue): New. (is_a_helper <const binop_svalue *>::test): New. (template <> struct default_hash_traits<binop_svalue::key_t>): New. (class sub_svalue): New. (is_a_helper <const sub_svalue *>::test): New. (template <> struct default_hash_traits<sub_svalue::key_t>): New. (class unmergeable_svalue): New. (is_a_helper <const unmergeable_svalue *>::test): New. (class placeholder_svalue): New. (is_a_helper <placeholder_svalue *>::test): New. (class widening_svalue): New. (is_a_helper <widening_svalue *>::test): New. (template <> struct default_hash_traits<widening_svalue::key_t>): New. (class compound_svalue): New. (is_a_helper <compound_svalue *>::test): New. (template <> struct default_hash_traits<compound_svalue::key_t>): New. (class conjured_svalue): New. (is_a_helper <conjured_svalue *>::test): New. (template <> struct default_hash_traits<conjured_svalue::key_t>): New. (enum region_kind): Delete RK_PRIMITIVE, RK_STRUCT, RK_UNION, and RK_ARRAY. Add RK_LABEL, RK_DECL, RK_FIELD, RK_ELEMENT, RK_OFFSET, RK_CAST, RK_HEAP_ALLOCATED, RK_ALLOCA, RK_STRING, and RK_UNKNOWN. (region_kind_to_str): Delete. (region::~region): Move implementation to region.cc. (region::operator==): Delete. (region::operator!=): Delete. (region::clone): Delete. (region::get_id): New. (region::cmp_ids): New. (region::dyn_cast_map_region): Delete. (region::dyn_cast_array_region): Delete. (region::region_id get_parent): Delete. (region::get_parent_region): Convert to a simple accessor. (region::void set_value): Delete. (region::svalue_id get_value): Delete. (region::svalue_id get_value_direct): Delete. (region::svalue_id get_inherited_child_sid): Delete. (region::dyn_cast_frame_region): New. (region::dyn_cast_function_region): New. (region::dyn_cast_decl_region): New. (region::dyn_cast_field_region): New. (region::dyn_cast_element_region): New. (region::dyn_cast_offset_region): New. (region::dyn_cast_cast_region): New. (region::dyn_cast_string_region): New. (region::accept): New. (region::get_base_region): New. (region::base_region_p): New. (region::descendent_of_p): New. (region::maybe_get_frame_region): New. (region::maybe_get_decl): New. (region::hash): Delete. (region::rint): Delete. (region::dump_dot_to_pp): Delete. (region::get_desc): New. (region::dump_to_pp): Convert to vfunc, changing signature. (region::dump_child_label): Delete. (region::remap_svalue_ids): Delete. (region::remap_region_ids): Delete. (region::dump): New. (region::walk_for_canonicalization): Delete. (region::non_null_p): Drop region_model param. (region::add_view): Delete. (region::get_view): Delete. (region::get_active_view): Delete. (region::is_view_p): Delete. (region::cmp_ptrs): New. (region::validate): Delete. (region::get_offset): New. (region::get_byte_size): New. (region::get_bit_size): New. (region::get_subregions_for_binding): New. (region::region): Add complexity param. Convert parent from region_id to const region *. Drop svalue_id. Drop copy ctor. (region::symbolic_for_unknown_ptr_p): New. (region::add_to_hash): Delete. (region::print_fields): Delete. (region::get_complexity): New accessor. (region::become_active_view): Delete. (region::deactivate_any_active_view): Delete. (region::deactivate_view): Delete. (region::calc_offset): New. (region::m_parent_rid): Delete. (region::m_sval_id): Delete. (region::m_complexity): New. (region::m_id): New. (region::m_parent): New. (region::m_view_rids): Delete. (region::m_is_view): Delete. (region::m_active_view_rid): Delete. (region::m_cached_offset): New. (is_a_helper <region *>::test): Convert to... (is_a_helper <const region *>::test): ... this. (class primitive_region): Delete. (class space_region): New. (class map_region): Delete. (is_a_helper <map_region *>::test): Delete. (class frame_region): Reimplement. (template <> struct default_hash_traits<frame_region::key_t>): New. (class globals_region): Reimplement. (is_a_helper <globals_region *>::test): Convert to... (is_a_helper <const globals_region *>::test): ...this. (class struct_or_union_region): Delete. (is_a_helper <struct_or_union_region *>::test): Delete. (class code_region): Reimplement. (is_a_helper <const code_region *>::test): New. (class struct_region): Delete. (is_a_helper <struct_region *>::test): Delete. (class function_region): Reimplement. (is_a_helper <function_region *>::test): Convert to... (is_a_helper <const function_region *>::test): ...this. (class union_region): Delete. (is_a_helper <union_region *>::test): Delete. (class label_region): New. (is_a_helper <const label_region *>::test): New. (class scope_region): Delete. (class stack_region): Reimplement. (is_a_helper <stack_region *>::test): Convert to... (is_a_helper <const stack_region *>::test): ...this. (class heap_region): Reimplement. (is_a_helper <heap_region *>::test): Convert to... (is_a_helper <const heap_region *>::test): ...this. (class root_region): Reimplement. (is_a_helper <root_region *>::test): Convert to... (is_a_helper <const root_region *>::test): ...this. (class symbolic_region): Reimplement. (is_a_helper <const symbolic_region *>::test): New. (template <> struct default_hash_traits<symbolic_region::key_t>): New. (class decl_region): New. (is_a_helper <const decl_region *>::test): New. (class field_region): New. (template <> struct default_hash_traits<field_region::key_t>): New. (class array_region): Delete. (class element_region): New. (is_a_helper <array_region *>::test): Delete. (is_a_helper <const element_region *>::test): New. (template <> struct default_hash_traits<element_region::key_t>): New. (class offset_region): New. (is_a_helper <const offset_region *>::test): New. (template <> struct default_hash_traits<offset_region::key_t>): New. (class cast_region): New. (is_a_helper <const cast_region *>::test): New. (template <> struct default_hash_traits<cast_region::key_t>): New. (class heap_allocated_region): New. (class alloca_region): New. (class string_region): New. (is_a_helper <const string_region *>::test): New. (class unknown_region): New. (class region_model_manager): New. (struct append_ssa_names_cb_data): New. (class call_details): New. (region_model::region_model): Add region_model_manager param. (region_model::print_svalue): Delete. (region_model::dump_dot_to_pp): Delete. (region_model::dump_dot_to_file): Delete. (region_model::dump_dot): Delete. (region_model::dump_to_pp): Drop summarize param in favor of simple and multiline. (region_model::dump): Likewise. (region_model::summarize_to_pp): Delete. (region_model::summarize): Delete. (region_model::void canonicalize): Drop ctxt param. (region_model::void check_for_poison): Delete. (region_model::get_gassign_result): New. (region_model::impl_call_alloca): New. (region_model::impl_call_analyzer_describe): New. (region_model::impl_call_analyzer_eval): New. (region_model::impl_call_builtin_expect): New. (region_model::impl_call_calloc): New. (region_model::impl_call_free): New. (region_model::impl_call_malloc): New. (region_model::impl_call_memset): New. (region_model::impl_call_strlen): New. (region_model::get_reachable_svalues): New. (region_model::handle_phi): Drop is_back_edge param. (region_model::region_id get_root_rid): Delete. (region_model::root_region *get_root_region): Delete. (region_model::region_id get_stack_region_id): Delete. (region_model::push_frame): Convert from region_id and svalue_id to const region * and const svalue *. (region_model::get_current_frame_id): Replace with... (region_model::get_current_frame): ...this. (region_model::pop_frame): Convert from region_id to const region *. Drop purge and stats param. Add out_result. (region_model::function *get_function_at_depth): Delete. (region_model::get_globals_region_id): Delete. (region_model::add_svalue): Delete. (region_model::replace_svalue): Delete. (region_model::add_region): Delete. (region_model::add_region_for_type): Delete. (region_model::get_svalue): Delete. (region_model::get_region): Delete. (region_model::get_lvalue): Convert from region_id to const region *. (region_model::get_rvalue): Convert from svalue_id to const svalue *. (region_model::get_or_create_ptr_svalue): Delete. (region_model::get_or_create_constant_svalue): Delete. (region_model::get_svalue_for_fndecl): Delete. (region_model::get_svalue_for_label): Delete. (region_model::get_region_for_fndecl): Delete. (region_model::get_region_for_label): Delete. (region_model::get_frame_at_index (int index) const;): New. (region_model::maybe_cast): Delete. (region_model::maybe_cast_1): Delete. (region_model::get_field_region): Delete. (region_model::id deref_rvalue): Convert from region_id and svalue_id to const region * and const svalue *. Drop overload, passing in both a tree and an svalue. (region_model::set_value): Convert from region_id and svalue_id to const region * and const svalue *. (region_model::set_to_new_unknown_value): Delete. (region_model::clobber_region (const region *reg);): New. (region_model::purge_region (const region *reg);): New. (region_model::zero_fill_region (const region *reg);): New. (region_model::mark_region_as_unknown (const region *reg);): New. (region_model::copy_region): Convert from region_id to const region *. (region_model::eval_condition): Convert from svalue_id to const svalue *. (region_model::eval_condition_without_cm): Likewise. (region_model::compare_initial_and_pointer): New. (region_model:maybe_get_constant): Delete. (region_model::add_new_malloc_region): Delete. (region_model::get_representative_tree): Convert from svalue_id to const svalue *. (region_model::get_representative_path_var): Delete decl taking a region_id in favor of two decls, for svalue vs region, with an svalue_set to ensure termination. (region_model::get_path_vars_for_svalue): Delete. (region_model::create_region_for_heap_alloc): New. (region_model::create_region_for_alloca): New. (region_model::purge_unused_svalues): Delete. (region_model::remap_svalue_ids): Delete. (region_model::remap_region_ids): Delete. (region_model::purge_regions): Delete. (region_model::get_num_svalues): Delete. (region_model::get_num_regions): Delete. (region_model::get_descendents): Delete. (region_model::get_store): New. (region_model::delete_region_and_descendents): Delete. (region_model::get_manager): New. (region_model::unbind_region_and_descendents): New. (region_model::can_merge_with_p): Add point param. Drop svalue_id_merger_mapping. (region_model::get_value_by_name): Delete. (region_model::convert_byte_offset_to_array_index): Delete. (region_model::get_or_create_mem_ref): Delete. (region_model::get_or_create_pointer_plus_expr): Delete. (region_model::get_or_create_view): Delete. (region_model::get_lvalue_1): Convert from region_id to const region *. (region_model::get_rvalue_1): Convert from svalue_id to const svalue *. (region_model::get_ssa_name_regions_for_current_frame): New. (region_model::append_ssa_names_cb): New. (region_model::get_store_value): New. (region_model::copy_struct_region): Delete. (region_model::copy_union_region): Delete. (region_model::copy_array_region): Delete. (region_model::region_exists_p): New. (region_model::make_region_for_unexpected_tree_code): Delete. (region_model::loop_replay_fixup): New. (region_model::poison_any_pointers_to_bad_regions): Delete. (region_model::poison_any_pointers_to_descendents): New. (region_model::dump_summary_of_rep_path_vars): Delete. (region_model::on_top_level_param): New. (region_model::record_dynamic_extents): New. (region_model::m_mgr;): New. (region_model::m_store;): New. (region_model::m_svalues;): Delete. (region_model::m_regions;): Delete. (region_model::m_root_rid;): Delete. (region_model::m_current_frame;): New. (region_model_context::remap_svalue_ids): Delete. (region_model_context::can_purge_p): Delete. (region_model_context::on_svalue_leak): New. (region_model_context::on_svalue_purge): Delete. (region_model_context::on_liveness_change): New. (region_model_context::on_inherited_svalue): Delete. (region_model_context::on_cast): Delete. (region_model_context::on_unknown_change): Convert from svalue_id to const svalue * and add is_mutable. (class noop_region_model_context): Update for region_model_context changes. (model_merger::model_merger): Add program_point. Drop svalue_id_merger_mapping. (model_merger::dump_to_pp): Add "simple" param. (model_merger::dump): Likewise. (model_merger::get_region_a): Delete. (model_merger::get_region_b): Delete. (model_merger::can_merge_values_p): Delete. (model_merger::record_regions): Delete. (model_merger::record_svalues): Delete. (model_merger::m_point): New field. (model_merger::m_map_regions_from_a_to_m): Delete. (model_merger::m_map_regions_from_b_to_m): Delete. (model_merger::m_sid_mapping): Delete. (struct svalue_id_merger_mapping): Delete. (class engine): New. (struct canonicalization): Delete. (inchash::add): Delete decls for hashing svalue_id and region_id. (test_region_model_context::on_unexpected_tree_code): Require t to be non-NULL. (selftest::assert_condition): Add overload comparing a pair of const svalue *. * sm-file.cc: Include "tristate.h", "selftest.h", "analyzer/call-string.h", "analyzer/program-point.h", "analyzer/store.h", and "analyzer/region-model.h". (fileptr_state_machine::get_default_state): New. (fileptr_state_machine::on_stmt): Remove calls to get_readable_tree in favor of get_diagnostic_tree. * sm-malloc.cc: Include "tristate.h", "selftest.h", "analyzer/call-string.h", "analyzer/program-point.h", "analyzer/store.h", and "analyzer/region-model.h". (malloc_state_machine::get_default_state): New. (malloc_state_machine::reset_when_passed_to_unknown_fn_p): New. (malloc_diagnostic::describe_state_change): Handle change.m_expr being NULL. (null_arg::emit): Avoid printing "NULL '0'". (null_arg::describe_final_event): Avoid printing "(0) NULL". (malloc_leak::emit): Handle m_arg being NULL. (malloc_leak::describe_final_event): Handle ev.m_expr being NULL. (malloc_state_machine::on_stmt): Don't call get_readable_tree. Call get_diagnostic_tree when creating pending diagnostics. Update for is_zero_assignment becoming a member function of sm_ctxt. Don't transition to m_non_heap for ADDR_EXPR(MEM_REF()). (malloc_state_machine::reset_when_passed_to_unknown_fn_p): New vfunc implementation. * sm-sensitive.cc (sensitive_state_machine::warn_for_any_exposure): Call get_diagnostic_tree and pass the result to warn_for_state. * sm-signal.cc: Move includes of "analyzer/call-string.h" and "analyzer/program-point.h" to before "analyzer/region-model.h", and also include "analyzer/store.h" before it. (signal_unsafe_call::describe_state_change): Use get_dest_function to get handler. (update_model_for_signal_handler): Pass manager to region_model ctor. (register_signal_handler::impl_transition): Update for changes to get_or_create_node and add_edge. * sm-taint.cc (taint_state_machine::on_stmt): Remove calls to get_readable_tree, replacing them when calling warn_for_state with calls to get_diagnostic_tree. * sm.cc (is_zero_assignment): Delete. (any_pointer_p): Move to within namespace ana. * sm.h (is_zero_assignment): Remove decl. (any_pointer_p): Move decl to within namespace ana. (state_machine::get_default_state): New vfunc. (state_machine::reset_when_passed_to_unknown_fn_p): New vfunc. (sm_context::get_readable_tree): Rename to... (sm_context::get_diagnostic_tree): ...this. (sm_context::is_zero_assignment): New vfunc. * store.cc: New file. * store.h: New file. * svalue.cc: New file. gcc/testsuite/ChangeLog: PR analyzer/93032 PR analyzer/93938 PR analyzer/94011 PR analyzer/94099 PR analyzer/94399 PR analyzer/94458 PR analyzer/94503 PR analyzer/94640 PR analyzer/94688 PR analyzer/94689 PR analyzer/94839 PR analyzer/95026 PR analyzer/95042 PR analyzer/95240 * g++.dg/analyzer/pr93212.C: Add dg-warning for dangling reference. * g++.dg/analyzer/pr93950.C: Remove xfail. * g++.dg/analyzer/pr94011.C: New test. * g++.dg/analyzer/pr94028.C: Remove leak false positives; mark as failing on C++98. * g++.dg/analyzer/pr94503.C: New test. * g++.dg/analyzer/pr95042.C: New test. * gcc.dg/analyzer/CVE-2005-1689-dedupe-issue-2.c: New test. * gcc.dg/analyzer/CVE-2005-1689-dedupe-issue.c: Add xfail. * gcc.dg/analyzer/CVE-2005-1689-minimal.c: Include "analyzer-decls.h". (test_4, test_5, test_6, test_7, test_8): New tests. * gcc.dg/analyzer/abs-1.c: New test. * gcc.dg/analyzer/aliasing-1.c: New test. * gcc.dg/analyzer/aliasing-2.c: New test. * gcc.dg/analyzer/analyzer-decls.h (__analyzer_describe): New decl. (__analyzer_dump_num_heap_regions): Remove. * gcc.dg/analyzer/attribute-nonnull.c: Add dg-warnings for cases where NULL is directly used as an argument. * gcc.dg/analyzer/bzero-1.c: New test. * gcc.dg/analyzer/casts-1.c: New test. * gcc.dg/analyzer/casts-2.c: New test. * gcc.dg/analyzer/compound-assignment-1.c (test_4): Remove xfail from leak false positive. (called_by_test_5a): Add "allocated here" expected message. (called_by_test_5b): Make expected leak message more precise. * gcc.dg/analyzer/compound-assignment-3.c: Update expected leak message. * gcc.dg/analyzer/compound-assignment-4.c: New test. * gcc.dg/analyzer/compound-assignment-5.c: New test. * gcc.dg/analyzer/conditionals-notrans.c: Remove xfails. * gcc.dg/analyzer/data-model-1.c (test_12d): Update expected results. (test_13): Remove xfail. (test_14): Remove xfail. (test_15): Remove xfail. (test_16): Remove xfails. Add out-of-bounds access. (test_16_alt): Remove xfails. (test_23): Remove xfail. (test_24): Remove xfail. (test_25): Remove xfail. (test_26): Update expected result. Remove xfail. Add xfail. (test_27): Remove xfails. (test_29): Add __analyzer_eval pointer comparisons. (test_41): Generalize expected output for u.ptr comparison with NULL for targets where this could be known to be false. (test_42): Remove xfail. (test_51): Remove xfails. * gcc.dg/analyzer/data-model-13.c: Update for improvements to source location and wording of leak message. * gcc.dg/analyzer/data-model-14.c: Remove -fanalyzer-fine-grained. (test_1): Update for improvement to expected message. (test_2): Remove xfail. * gcc.dg/analyzer/data-model-18.c: Remove xfail. * gcc.dg/analyzer/data-model-20.c: New test. * gcc.dg/analyzer/data-model-5.c: Add dg-warning for deref of NULL. Add xfailing false leak. * gcc.dg/analyzer/data-model-5b.c: Add xfailing false leak. * gcc.dg/analyzer/data-model-5c.c: Update xfailing false leak. * gcc.dg/analyzer/data-model-5d.c: Reimplement. * gcc.dg/analyzer/data-model-6.c: Delete test. * gcc.dg/analyzer/data-model-8.c: Remove xfail. * gcc.dg/analyzer/describe-1.c: New test. * gcc.dg/analyzer/dot-output.c: Remove xfail. * gcc.dg/analyzer/explode-1.c: Add expected leak warning. * gcc.dg/analyzer/explode-2.c: Add expected leak warnings. Mark double-free warnings as xfail for now. * gcc.dg/analyzer/feasibility-1.c: New test. * gcc.dg/analyzer/first-field-1.c: New test. * gcc.dg/analyzer/first-field-2.c: New test. * gcc.dg/analyzer/init.c: New test. * gcc.dg/analyzer/leak-2.c: New test. * gcc.dg/analyzer/loop-0-up-to-n-by-1-with-iter-obj.c: New test. * gcc.dg/analyzer/loop-0-up-to-n-by-1.c: New test. * gcc.dg/analyzer/loop-2a.c: Update expected behavior. * gcc.dg/analyzer/loop-3.c: Mark use-after-free as xfail. Add expected warning about deref of unchecked pointer. * gcc.dg/analyzer/loop-4.c: Remove -fno-analyzer-state-purge. Update expected behavior. * gcc.dg/analyzer/loop-n-down-to-1-by-1.c: New test. * gcc.dg/analyzer/loop-start-down-to-end-by-1.c: New test. * gcc.dg/analyzer/loop-start-down-to-end-by-step.c: New test. * gcc.dg/analyzer/loop-start-to-end-by-step.c: New test. * gcc.dg/analyzer/loop-start-up-to-end-by-1.c: New test. * gcc.dg/analyzer/loop.c: Remove -fno-analyzer-state-purge. Update expected behavior. * gcc.dg/analyzer/malloc-1.c: Remove xfails from leak false positives. Update expected wording of global_link.m_ptr leak. (test_49): New test. * gcc.dg/analyzer/malloc-4.c: Remove leak false positive. Update expected wording of leak warning. * gcc.dg/analyzer/malloc-in-loop.c: New test. * gcc.dg/analyzer/malloc-ipa-8-double-free.c: Update expected path to show call to wrapped_malloc. * gcc.dg/analyzer/malloc-ipa-8-unchecked.c: Remove -fanalyzer-verbose-state-changes. * gcc.dg/analyzer/malloc-paths-9.c: Remove comment about duplicate warnings. Remove duplicate use-after-free paths. * gcc.dg/analyzer/malloc-vs-local-1a.c: Add dg-warning for deref of unchecked pointer. Update expected number of enodes. * gcc.dg/analyzer/malloc-vs-local-2.c: Likewise. * gcc.dg/analyzer/malloc-vs-local-3.c: Add dg-warning for deref of unchecked pointer. Update expected number of enodes. Avoid overspecifying the leak message. * gcc.dg/analyzer/memset-1.c: New test. * gcc.dg/analyzer/paths-3.c: Update expected number of enodes. * gcc.dg/analyzer/paths-4.c: Likewise. * gcc.dg/analyzer/paths-6.c: Likewise. * gcc.dg/analyzer/paths-7.c: Likewise. * gcc.dg/analyzer/pr93032-mztools-simplified.c: New test. * gcc.dg/analyzer/pr93032-mztools.c: New test. * gcc.dg/analyzer/pr93382.c: Mark taint tests as failing. * gcc.dg/analyzer/pr93938.c: New test. * gcc.dg/analyzer/pr94099.c: Replace uninit dg-warning with dg-warning for NULL dereference. * gcc.dg/analyzer/pr94399.c: New test. * gcc.dg/analyzer/pr94447.c: Add dg-warning for NULL dereference. * gcc.dg/analyzer/pr94458.c: New test. * gcc.dg/analyzer/pr94640.c: New test. * gcc.dg/analyzer/pr94688.c: New test. * gcc.dg/analyzer/pr94689.c: New test. * gcc.dg/analyzer/pr94839.c: New test. * gcc.dg/analyzer/pr95026.c: New test. * gcc.dg/analyzer/pr95240.c: New test. * gcc.dg/analyzer/refcounting-1.c: New test. * gcc.dg/analyzer/single-field.c: New test. * gcc.dg/analyzer/stale-frame-1.c: New test. * gcc.dg/analyzer/symbolic-1.c: New test. * gcc.dg/analyzer/symbolic-2.c: New test. * gcc.dg/analyzer/symbolic-3.c: New test. * gcc.dg/analyzer/symbolic-4.c: New test. * gcc.dg/analyzer/symbolic-5.c: New test. * gcc.dg/analyzer/symbolic-6.c: New test. * gcc.dg/analyzer/taint-1.c: Mark the "gets unchecked value" events as failing for now. Update dg-message directives to avoid relying on numbering. * gcc.dg/analyzer/torture/loop-inc-ptr-1.c: New test. * gcc.dg/analyzer/torture/loop-inc-ptr-2.c: New test. * gcc.dg/analyzer/torture/loop-inc-ptr-3.c: New test. * gcc.dg/analyzer/unknown-fns-2.c: New test. * gcc.dg/analyzer/unknown-fns-3.c: New test. * gcc.dg/analyzer/unknown-fns-4.c: New test. * gcc.dg/analyzer/unknown-fns.c: Update dg-warning to reflect fixed source location for leak diagnostic. * gcc.dg/analyzer/use-after-free.c: New test. * gcc.dg/analyzer/vla-1.c: New test. * gcc.dg/analyzer/zlib-4.c: Rewrite to avoid "exit" calls. Add expected leak warnings. * gfortran.dg/analyzer/pr93993.f90: Remove leak of tm warning, which seems to have been a false positive.
This commit is contained in:
parent
b5034abbaa
commit
808f4dfeb3
142 changed files with 16310 additions and 10559 deletions
|
@ -1237,7 +1237,11 @@ ANALYZER_OBJS = \
|
|||
analyzer/pending-diagnostic.o \
|
||||
analyzer/program-point.o \
|
||||
analyzer/program-state.o \
|
||||
analyzer/region.o \
|
||||
analyzer/region-model.o \
|
||||
analyzer/region-model-impl-calls.o \
|
||||
analyzer/region-model-manager.o \
|
||||
analyzer/region-model-reachability.o \
|
||||
analyzer/sm.o \
|
||||
analyzer/sm-file.o \
|
||||
analyzer/sm-malloc.o \
|
||||
|
@ -1246,7 +1250,9 @@ ANALYZER_OBJS = \
|
|||
analyzer/sm-signal.o \
|
||||
analyzer/sm-taint.o \
|
||||
analyzer/state-purge.o \
|
||||
analyzer/supergraph.o
|
||||
analyzer/store.o \
|
||||
analyzer/supergraph.o \
|
||||
analyzer/svalue.o
|
||||
|
||||
# Language-independent object files.
|
||||
# We put the *-match.o and insn-*.o files first so that a parallel make
|
||||
|
|
|
@ -30,6 +30,8 @@ along with GCC; see the file COPYING3. If not see
|
|||
|
||||
#if ENABLE_ANALYZER
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wformat-diag"
|
||||
|
||||
namespace ana {
|
||||
|
||||
/* Implementation of class logger. */
|
||||
|
@ -164,7 +166,7 @@ void
|
|||
logger::enter_scope (const char *scope_name)
|
||||
{
|
||||
log ("entering: %s", scope_name);
|
||||
m_indent_level += 1;
|
||||
inc_indent ();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -175,7 +177,7 @@ logger::enter_scope (const char *scope_name, const char *fmt, va_list *ap)
|
|||
log_va_partial (fmt, ap);
|
||||
end_log_line ();
|
||||
|
||||
m_indent_level += 1;
|
||||
inc_indent ();
|
||||
}
|
||||
|
||||
|
||||
|
@ -186,7 +188,7 @@ void
|
|||
logger::exit_scope (const char *scope_name)
|
||||
{
|
||||
if (m_indent_level)
|
||||
m_indent_level -= 1;
|
||||
dec_indent ();
|
||||
else
|
||||
log ("(mismatching indentation)");
|
||||
log ("exiting: %s", scope_name);
|
||||
|
|
|
@ -52,6 +52,8 @@ class logger
|
|||
void enter_scope (const char *scope_name, const char *fmt, va_list *ap)
|
||||
ATTRIBUTE_GCC_DIAG(3, 0);
|
||||
void exit_scope (const char *scope_name);
|
||||
void inc_indent () { m_indent_level++; }
|
||||
void dec_indent () { m_indent_level--; }
|
||||
|
||||
pretty_printer *get_printer () const { return m_pp; }
|
||||
FILE *get_file () const { return m_f_out; }
|
||||
|
|
|
@ -58,6 +58,7 @@ run_analyzer_selftests ()
|
|||
analyzer_region_model_cc_tests ();
|
||||
analyzer_sm_file_cc_tests ();
|
||||
analyzer_sm_signal_cc_tests ();
|
||||
analyzer_store_cc_tests ();
|
||||
#endif /* #if ENABLE_ANALYZER */
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ extern void analyzer_program_state_cc_tests ();
|
|||
extern void analyzer_region_model_cc_tests ();
|
||||
extern void analyzer_sm_file_cc_tests ();
|
||||
extern void analyzer_sm_signal_cc_tests ();
|
||||
extern void analyzer_store_cc_tests ();
|
||||
|
||||
} /* end of namespace ana::selftest. */
|
||||
|
||||
|
|
|
@ -32,6 +32,36 @@ along with GCC; see the file COPYING3. If not see
|
|||
|
||||
#if ENABLE_ANALYZER
|
||||
|
||||
namespace ana {
|
||||
|
||||
/* Workaround for missing location information for some stmts,
|
||||
which ultimately should be solved by fixing the frontends
|
||||
to provide the locations (TODO). */
|
||||
|
||||
location_t
|
||||
get_stmt_location (const gimple *stmt, function *fun)
|
||||
{
|
||||
if (get_pure_location (stmt->location) == UNKNOWN_LOCATION)
|
||||
{
|
||||
/* Workaround for missing location information for clobber
|
||||
stmts, which seem to lack location information in the C frontend
|
||||
at least. Created by gimplify_bind_expr, which uses the
|
||||
BLOCK_SOURCE_END_LOCATION (BIND_EXPR_BLOCK (bind_expr))
|
||||
but this is never set up when the block is created in
|
||||
c_end_compound_stmt's pop_scope.
|
||||
TODO: fix this missing location information.
|
||||
|
||||
For now, as a hackish workaround, use the location of the end of
|
||||
the function. */
|
||||
if (gimple_clobber_p (stmt) && fun)
|
||||
return fun->function_end_locus;
|
||||
}
|
||||
|
||||
return stmt->location;
|
||||
}
|
||||
|
||||
} // namespace ana
|
||||
|
||||
/* Helper function for checkers. Is the CALL to the given function name,
|
||||
and with the given number of arguments?
|
||||
|
||||
|
|
|
@ -35,24 +35,45 @@ class superedge;
|
|||
class callgraph_superedge;
|
||||
class call_superedge;
|
||||
class return_superedge;
|
||||
|
||||
class svalue;
|
||||
class region_svalue;
|
||||
class constant_svalue;
|
||||
class poisoned_svalue;
|
||||
class unknown_svalue;
|
||||
class poisoned_svalue;
|
||||
class setjmp_svalue;
|
||||
class initial_svalue;
|
||||
class unaryop_svalue;
|
||||
class binop_svalue;
|
||||
class sub_svalue;
|
||||
class unmergeable_svalue;
|
||||
class placeholder_svalue;
|
||||
class widening_svalue;
|
||||
class compound_svalue;
|
||||
class conjured_svalue;
|
||||
typedef hash_set<const svalue *> svalue_set;
|
||||
class region;
|
||||
class map_region;
|
||||
class array_region;
|
||||
class frame_region;
|
||||
class function_region;
|
||||
class label_region;
|
||||
class decl_region;
|
||||
class symbolic_region;
|
||||
class element_region;
|
||||
class offset_region;
|
||||
class cast_region;
|
||||
class field_region;
|
||||
class string_region;
|
||||
class region_model_manager;
|
||||
struct model_merger;
|
||||
class store_manager;
|
||||
class store;
|
||||
class region_model;
|
||||
class region_model_context;
|
||||
class impl_region_model_context;
|
||||
class call_details;
|
||||
class constraint_manager;
|
||||
class equiv_class;
|
||||
struct model_merger;
|
||||
struct svalue_id_merger_mapping;
|
||||
struct canonicalization;
|
||||
|
||||
class pending_diagnostic;
|
||||
class state_change_event;
|
||||
class checker_path;
|
||||
|
@ -60,6 +81,7 @@ class extrinsic_state;
|
|||
class sm_state_map;
|
||||
class stmt_finder;
|
||||
class program_point;
|
||||
class function_point;
|
||||
class program_state;
|
||||
class exploded_graph;
|
||||
class exploded_node;
|
||||
|
@ -73,9 +95,95 @@ class state_purge_per_ssa_name;
|
|||
class state_change;
|
||||
class rewind_info_t;
|
||||
|
||||
class engine;
|
||||
|
||||
/* Forward decls of functions. */
|
||||
|
||||
extern void dump_tree (pretty_printer *pp, tree t);
|
||||
extern void dump_quoted_tree (pretty_printer *pp, tree t);
|
||||
extern void print_quoted_type (pretty_printer *pp, tree t);
|
||||
extern int readability_comparator (const void *p1, const void *p2);
|
||||
extern int tree_cmp (const void *p1, const void *p2);
|
||||
|
||||
/* A tree, extended with stack frame information for locals, so that
|
||||
we can distinguish between different values of locals within a potentially
|
||||
recursive callstack. */
|
||||
|
||||
class path_var
|
||||
{
|
||||
public:
|
||||
path_var (tree t, int stack_depth)
|
||||
: m_tree (t), m_stack_depth (stack_depth)
|
||||
{
|
||||
// TODO: ignore stack depth for globals and constants
|
||||
}
|
||||
|
||||
bool operator== (const path_var &other) const
|
||||
{
|
||||
return (m_tree == other.m_tree
|
||||
&& m_stack_depth == other.m_stack_depth);
|
||||
}
|
||||
|
||||
operator bool () const
|
||||
{
|
||||
return m_tree != NULL_TREE;
|
||||
}
|
||||
|
||||
void dump (pretty_printer *pp) const;
|
||||
|
||||
tree m_tree;
|
||||
int m_stack_depth; // or -1 for globals?
|
||||
};
|
||||
|
||||
typedef offset_int bit_offset_t;
|
||||
typedef offset_int bit_size_t;
|
||||
typedef offset_int byte_size_t;
|
||||
|
||||
/* The location of a region expressesd as an offset relative to a
|
||||
base region. */
|
||||
|
||||
class region_offset
|
||||
{
|
||||
public:
|
||||
static region_offset make_concrete (const region *base_region,
|
||||
bit_offset_t offset)
|
||||
{
|
||||
return region_offset (base_region, offset, false);
|
||||
}
|
||||
static region_offset make_symbolic (const region *base_region)
|
||||
{
|
||||
return region_offset (base_region, 0, true);
|
||||
}
|
||||
|
||||
const region *get_base_region () const { return m_base_region; }
|
||||
|
||||
bool symbolic_p () const { return m_is_symbolic; }
|
||||
|
||||
bit_offset_t get_bit_offset () const
|
||||
{
|
||||
gcc_assert (!symbolic_p ());
|
||||
return m_offset;
|
||||
}
|
||||
|
||||
bool operator== (const region_offset &other)
|
||||
{
|
||||
return (m_base_region == other.m_base_region
|
||||
&& m_offset == other.m_offset
|
||||
&& m_is_symbolic == other.m_is_symbolic);
|
||||
}
|
||||
|
||||
private:
|
||||
region_offset (const region *base_region, bit_offset_t offset,
|
||||
bool is_symbolic)
|
||||
: m_base_region (base_region), m_offset (offset), m_is_symbolic (is_symbolic)
|
||||
{}
|
||||
|
||||
const region *m_base_region;
|
||||
bit_offset_t m_offset;
|
||||
bool m_is_symbolic;
|
||||
};
|
||||
|
||||
extern location_t get_stmt_location (const gimple *stmt, function *fun);
|
||||
|
||||
} // namespace ana
|
||||
|
||||
|
@ -124,4 +232,76 @@ struct pod_hash_traits : typed_noop_remove<Type>
|
|||
static inline bool is_empty (Type);
|
||||
};
|
||||
|
||||
/* A hash traits class that uses member functions to implement
|
||||
the various required ops. */
|
||||
|
||||
template <typename Type>
|
||||
struct member_function_hash_traits : public typed_noop_remove<Type>
|
||||
{
|
||||
typedef Type value_type;
|
||||
typedef Type compare_type;
|
||||
static inline hashval_t hash (value_type v) { return v.hash (); }
|
||||
static inline bool equal (const value_type &existing,
|
||||
const value_type &candidate)
|
||||
{
|
||||
return existing == candidate;
|
||||
}
|
||||
static inline void mark_deleted (Type &t) { t.mark_deleted (); }
|
||||
static inline void mark_empty (Type &t) { t.mark_empty (); }
|
||||
static inline bool is_deleted (Type t) { return t.is_deleted (); }
|
||||
static inline bool is_empty (Type t) { return t.is_empty (); }
|
||||
};
|
||||
|
||||
/* A map from T::key_t to T* for use in consolidating instances of T.
|
||||
Owns all instances of T.
|
||||
T::key_t should have operator== and be hashable. */
|
||||
|
||||
template <typename T>
|
||||
class consolidation_map
|
||||
{
|
||||
public:
|
||||
typedef typename T::key_t key_t;
|
||||
typedef T instance_t;
|
||||
typedef hash_map<key_t, instance_t *> inner_map_t;
|
||||
typedef typename inner_map_t::iterator iterator;
|
||||
|
||||
/* Delete all instances of T. */
|
||||
|
||||
~consolidation_map ()
|
||||
{
|
||||
for (typename inner_map_t::iterator iter = m_inner_map.begin ();
|
||||
iter != m_inner_map.end (); ++iter)
|
||||
delete (*iter).second;
|
||||
}
|
||||
|
||||
/* Get the instance of T for K if one exists, or NULL. */
|
||||
|
||||
T *get (const key_t &k) const
|
||||
{
|
||||
if (instance_t **slot = const_cast<inner_map_t &> (m_inner_map).get (k))
|
||||
return *slot;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Take ownership of INSTANCE. */
|
||||
|
||||
void put (const key_t &k, T *instance)
|
||||
{
|
||||
m_inner_map.put (k, instance);
|
||||
}
|
||||
|
||||
size_t elements () const { return m_inner_map.elements (); }
|
||||
|
||||
iterator begin () const { return m_inner_map.begin (); }
|
||||
iterator end () const { return m_inner_map.end (); }
|
||||
|
||||
private:
|
||||
inner_map_t m_inner_map;
|
||||
};
|
||||
|
||||
/* Disable -Wformat-diag; we want to be able to use pp_printf
|
||||
for logging/dumping without complying with the rules for diagnostics. */
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wformat-diag"
|
||||
|
||||
#endif /* GCC_ANALYZER_ANALYZER_H */
|
||||
|
|
|
@ -34,10 +34,18 @@ The maximum number of exploded nodes per program point within the analyzer, befo
|
|||
Common Joined UInteger Var(param_analyzer_max_recursion_depth) Init(2) Param
|
||||
The maximum number of times a callsite can appear in a call stack within the analyzer, before terminating analysis of a call that would recurse deeper.
|
||||
|
||||
-param=analyzer-max-svalue-depth=
|
||||
Common Joined UInteger Var(param_analyzer_max_svalue_depth) Init(13) Param
|
||||
The maximum depth of a symbolic value, before approximating the value as unknown.
|
||||
|
||||
-param=analyzer-min-snodes-for-call-summary=
|
||||
Common Joined UInteger Var(param_analyzer_min_snodes_for_call_summary) Init(10) Param
|
||||
The minimum number of supernodes within a function for the analyzer to consider summarizing its effects at call sites.
|
||||
|
||||
-param=analyzer-max-enodes-for-full-dump=
|
||||
Common Joined UInteger Var(param_analyzer_max_enodes_for_full_dump) Init(200) Param
|
||||
The maximum depth of exploded nodes that should appear in a dot dump before switching to a less verbose format.
|
||||
|
||||
Wanalyzer-double-fclose
|
||||
Common Var(warn_analyzer_double_fclose) Init(1) Warning
|
||||
Warn about code paths in which a stdio FILE can be closed more than once.
|
||||
|
|
|
@ -38,6 +38,8 @@ along with GCC; see the file COPYING3. If not see
|
|||
|
||||
#if ENABLE_ANALYZER
|
||||
|
||||
#pragma GCC diagnostic ignored "-Wformat-diag"
|
||||
|
||||
/* class call_string. */
|
||||
|
||||
/* call_string's copy ctor. */
|
||||
|
|
|
@ -46,6 +46,9 @@ along with GCC; see the file COPYING3. If not see
|
|||
#include "tristate.h"
|
||||
#include "ordered-hash-map.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"
|
||||
|
@ -56,8 +59,6 @@ along with GCC; see the file COPYING3. If not see
|
|||
#include "analyzer/constraint-manager.h"
|
||||
#include "analyzer/diagnostic-manager.h"
|
||||
#include "analyzer/checker-path.h"
|
||||
#include "analyzer/call-string.h"
|
||||
#include "analyzer/program-point.h"
|
||||
#include "analyzer/exploded-graph.h"
|
||||
|
||||
#if ENABLE_ANALYZER
|
||||
|
@ -214,16 +215,16 @@ state_change_event::state_change_event (const supernode *node,
|
|||
const gimple *stmt,
|
||||
int stack_depth,
|
||||
const state_machine &sm,
|
||||
tree var,
|
||||
const svalue *sval,
|
||||
state_machine::state_t from,
|
||||
state_machine::state_t to,
|
||||
tree origin,
|
||||
const svalue *origin,
|
||||
const program_state &dst_state)
|
||||
: checker_event (EK_STATE_CHANGE,
|
||||
stmt->location, node->m_fun->decl,
|
||||
stack_depth),
|
||||
m_node (node), m_stmt (stmt), m_sm (sm),
|
||||
m_var (var), m_from (from), m_to (to),
|
||||
m_sval (sval), m_from (from), m_to (to),
|
||||
m_origin (origin),
|
||||
m_dst_state (dst_state)
|
||||
{
|
||||
|
@ -245,9 +246,12 @@ state_change_event::get_desc (bool can_colorize) const
|
|||
{
|
||||
if (m_pending_diagnostic)
|
||||
{
|
||||
region_model *model = m_dst_state.m_region_model;
|
||||
tree var = model->get_representative_tree (m_sval);
|
||||
tree origin = model->get_representative_tree (m_origin);
|
||||
label_text custom_desc
|
||||
= m_pending_diagnostic->describe_state_change
|
||||
(evdesc::state_change (can_colorize, m_var, m_origin,
|
||||
(evdesc::state_change (can_colorize, var, origin,
|
||||
m_from, m_to, m_emission_id, *this));
|
||||
if (custom_desc.m_buffer)
|
||||
{
|
||||
|
@ -260,16 +264,16 @@ state_change_event::get_desc (bool can_colorize) const
|
|||
(can_colorize,
|
||||
"%s (state of %qE: %qs -> %qs, origin: %qE)",
|
||||
custom_desc.m_buffer,
|
||||
m_var,
|
||||
var,
|
||||
m_sm.get_state_name (m_from),
|
||||
m_sm.get_state_name (m_to),
|
||||
m_origin);
|
||||
origin);
|
||||
else
|
||||
result = make_label_text
|
||||
(can_colorize,
|
||||
"%s (state of %qE: %qs -> %qs, origin: NULL)",
|
||||
"%s (state of %qE: %qs -> %qs, NULL origin)",
|
||||
custom_desc.m_buffer,
|
||||
m_var,
|
||||
var,
|
||||
m_sm.get_state_name (m_from),
|
||||
m_sm.get_state_name (m_to));
|
||||
custom_desc.maybe_free ();
|
||||
|
@ -281,27 +285,31 @@ state_change_event::get_desc (bool can_colorize) const
|
|||
}
|
||||
|
||||
/* Fallback description. */
|
||||
if (m_var)
|
||||
if (m_sval)
|
||||
{
|
||||
label_text sval_desc = m_sval->get_desc ();
|
||||
if (m_origin)
|
||||
return make_label_text
|
||||
(can_colorize,
|
||||
"state of %qE: %qs -> %qs (origin: %qE)",
|
||||
m_var,
|
||||
m_sm.get_state_name (m_from),
|
||||
m_sm.get_state_name (m_to),
|
||||
m_origin);
|
||||
{
|
||||
label_text origin_desc = m_origin->get_desc ();
|
||||
return make_label_text
|
||||
(can_colorize,
|
||||
"state of %qs: %qs -> %qs (origin: %qs)",
|
||||
sval_desc.m_buffer,
|
||||
m_sm.get_state_name (m_from),
|
||||
m_sm.get_state_name (m_to),
|
||||
origin_desc.m_buffer);
|
||||
}
|
||||
else
|
||||
return make_label_text
|
||||
(can_colorize,
|
||||
"state of %qE: %qs -> %qs (origin: NULL)",
|
||||
m_var,
|
||||
"state of %qs: %qs -> %qs (NULL origin)",
|
||||
sval_desc.m_buffer,
|
||||
m_sm.get_state_name (m_from),
|
||||
m_sm.get_state_name (m_to));
|
||||
}
|
||||
else
|
||||
{
|
||||
gcc_assert (m_origin == NULL_TREE);
|
||||
gcc_assert (m_origin == NULL);
|
||||
return make_label_text
|
||||
(can_colorize,
|
||||
"global state: %qs -> %qs",
|
||||
|
@ -954,7 +962,7 @@ checker_path::add_final_event (const state_machine *sm,
|
|||
tree var, state_machine::state_t state)
|
||||
{
|
||||
checker_event *end_of_path
|
||||
= new warning_event (stmt->location,
|
||||
= new warning_event (get_stmt_location (stmt, enode->get_function ()),
|
||||
enode->get_function ()->decl,
|
||||
enode->get_stack_depth (),
|
||||
sm, var, state);
|
||||
|
|
|
@ -193,26 +193,26 @@ public:
|
|||
state_change_event (const supernode *node, const gimple *stmt,
|
||||
int stack_depth,
|
||||
const state_machine &sm,
|
||||
tree var,
|
||||
const svalue *sval,
|
||||
state_machine::state_t from,
|
||||
state_machine::state_t to,
|
||||
tree origin,
|
||||
const svalue *origin,
|
||||
const program_state &dst_state);
|
||||
|
||||
label_text get_desc (bool can_colorize) const FINAL OVERRIDE;
|
||||
|
||||
region_id get_lvalue (tree expr, region_model_context *ctxt) const
|
||||
function *get_dest_function () const
|
||||
{
|
||||
return m_dst_state.m_region_model->get_lvalue (expr, ctxt);
|
||||
return m_dst_state.get_current_function ();
|
||||
}
|
||||
|
||||
const supernode *m_node;
|
||||
const gimple *m_stmt;
|
||||
const state_machine &m_sm;
|
||||
tree m_var;
|
||||
const svalue *m_sval;
|
||||
state_machine::state_t m_from;
|
||||
state_machine::state_t m_to;
|
||||
tree m_origin;
|
||||
const svalue *m_origin;
|
||||
program_state m_dst_state;
|
||||
};
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -25,17 +25,47 @@ namespace ana {
|
|||
|
||||
class constraint_manager;
|
||||
|
||||
/* Abstract base class for specifying how state should be purged. */
|
||||
/* One of the end-points of a range. */
|
||||
|
||||
class purge_criteria
|
||||
struct bound
|
||||
{
|
||||
public:
|
||||
virtual ~purge_criteria () {}
|
||||
virtual bool should_purge_p (svalue_id sid) const = 0;
|
||||
bound () : m_constant (NULL_TREE), m_closed (false) {}
|
||||
bound (tree constant, bool closed)
|
||||
: m_constant (constant), m_closed (closed) {}
|
||||
|
||||
void ensure_closed (bool is_upper);
|
||||
|
||||
const char * get_relation_as_str () const;
|
||||
|
||||
tree m_constant;
|
||||
bool m_closed;
|
||||
};
|
||||
|
||||
/* A range of values, used for determining if a value has been
|
||||
constrained to just one possible constant value. */
|
||||
|
||||
struct range
|
||||
{
|
||||
range () : m_lower_bound (), m_upper_bound () {}
|
||||
range (const bound &lower, const bound &upper)
|
||||
: m_lower_bound (lower), m_upper_bound (upper) {}
|
||||
|
||||
void dump_to_pp (pretty_printer *pp) const;
|
||||
void dump () const;
|
||||
|
||||
tree constrained_to_single_element ();
|
||||
|
||||
tristate eval_condition (enum tree_code op,
|
||||
tree rhs_const) const;
|
||||
bool below_lower_bound (tree rhs_const) const;
|
||||
bool above_upper_bound (tree rhs_const) const;
|
||||
|
||||
bound m_lower_bound;
|
||||
bound m_upper_bound;
|
||||
};
|
||||
|
||||
/* An equivalence class within a constraint manager: a set of
|
||||
svalue_ids that are known to all be equal to each other,
|
||||
svalues that are known to all be equal to each other,
|
||||
together with an optional tree constant that they are equal to. */
|
||||
|
||||
class equiv_class
|
||||
|
@ -47,14 +77,12 @@ public:
|
|||
hashval_t hash () const;
|
||||
bool operator== (const equiv_class &other);
|
||||
|
||||
void add (svalue_id sid, const constraint_manager &cm);
|
||||
bool del (svalue_id sid);
|
||||
void add (const svalue *sval);
|
||||
bool del (const svalue *sval);
|
||||
|
||||
tree get_any_constant () const { return m_constant; }
|
||||
|
||||
svalue_id get_representative () const;
|
||||
|
||||
void remap_svalue_ids (const svalue_id_map &map);
|
||||
const svalue *get_representative () const;
|
||||
|
||||
void canonicalize ();
|
||||
|
||||
|
@ -64,10 +92,10 @@ public:
|
|||
different zeroes, for different types); these are just for the last
|
||||
constant added. */
|
||||
tree m_constant;
|
||||
svalue_id m_cst_sid;
|
||||
const svalue *m_cst_sval;
|
||||
|
||||
// TODO: should this be a set rather than a vec?
|
||||
auto_vec<svalue_id> m_vars;
|
||||
auto_vec<const svalue *> m_vars;
|
||||
};
|
||||
|
||||
/* The various kinds of constraint. */
|
||||
|
@ -141,6 +169,9 @@ class constraint
|
|||
return m_op != CONSTRAINT_NE;
|
||||
}
|
||||
|
||||
bool implied_by (const constraint &other,
|
||||
const constraint_manager &cm) const;
|
||||
|
||||
equiv_class_id m_lhs;
|
||||
enum constraint_op m_op;
|
||||
equiv_class_id m_rhs;
|
||||
|
@ -152,7 +183,9 @@ class fact_visitor
|
|||
{
|
||||
public:
|
||||
virtual ~fact_visitor () {}
|
||||
virtual void on_fact (svalue_id lhs, enum tree_code, svalue_id rhs) = 0;
|
||||
virtual void on_fact (const svalue *lhs,
|
||||
enum tree_code,
|
||||
const svalue *rhs) = 0;
|
||||
};
|
||||
|
||||
/* A collection of equivalence classes and constraints on them.
|
||||
|
@ -164,15 +197,10 @@ class fact_visitor
|
|||
class constraint_manager
|
||||
{
|
||||
public:
|
||||
constraint_manager () {}
|
||||
constraint_manager (region_model_manager *mgr) : m_mgr (mgr) {}
|
||||
constraint_manager (const constraint_manager &other);
|
||||
virtual ~constraint_manager () {}
|
||||
|
||||
virtual constraint_manager *clone (region_model *) const = 0;
|
||||
virtual tree maybe_get_constant (svalue_id sid) const = 0;
|
||||
virtual svalue_id get_sid_for_constant (tree cst) const = 0;
|
||||
virtual int get_num_svalues () const = 0;
|
||||
|
||||
constraint_manager& operator= (const constraint_manager &other);
|
||||
|
||||
hashval_t hash () const;
|
||||
|
@ -183,7 +211,7 @@ public:
|
|||
}
|
||||
|
||||
void print (pretty_printer *pp) const;
|
||||
void dump_to_pp (pretty_printer *pp) const;
|
||||
void dump_to_pp (pretty_printer *pp, bool multiline) const;
|
||||
void dump (FILE *fp) const;
|
||||
void dump () const;
|
||||
|
||||
|
@ -196,32 +224,47 @@ public:
|
|||
return *m_equiv_classes[idx];
|
||||
}
|
||||
|
||||
equiv_class &get_equiv_class (svalue_id sid)
|
||||
equiv_class &get_equiv_class (const svalue *sval)
|
||||
{
|
||||
equiv_class_id ec_id = get_or_add_equiv_class (sid);
|
||||
equiv_class_id ec_id = get_or_add_equiv_class (sval);
|
||||
return ec_id.get_obj (*this);
|
||||
}
|
||||
|
||||
bool add_constraint (svalue_id lhs, enum tree_code op, svalue_id rhs);
|
||||
bool add_constraint (const svalue *lhs,
|
||||
enum tree_code op,
|
||||
const svalue *rhs);
|
||||
|
||||
bool add_constraint (equiv_class_id lhs_ec_id,
|
||||
enum tree_code op,
|
||||
equiv_class_id rhs_ec_id);
|
||||
|
||||
bool get_equiv_class_by_sid (svalue_id sid, equiv_class_id *out) const;
|
||||
equiv_class_id get_or_add_equiv_class (svalue_id sid);
|
||||
void add_unknown_constraint (equiv_class_id lhs_ec_id,
|
||||
enum tree_code op,
|
||||
equiv_class_id rhs_ec_id);
|
||||
|
||||
bool get_equiv_class_by_svalue (const svalue *sval,
|
||||
equiv_class_id *out) const;
|
||||
equiv_class_id get_or_add_equiv_class (const svalue *sval);
|
||||
tristate eval_condition (equiv_class_id lhs,
|
||||
enum tree_code op,
|
||||
equiv_class_id rhs);
|
||||
tristate eval_condition (svalue_id lhs,
|
||||
equiv_class_id rhs) const;
|
||||
tristate eval_condition (equiv_class_id lhs_ec,
|
||||
enum tree_code op,
|
||||
svalue_id rhs);
|
||||
tree rhs_const) const;
|
||||
tristate eval_condition (const svalue *lhs,
|
||||
enum tree_code op,
|
||||
const svalue *rhs) const;
|
||||
range get_ec_bounds (equiv_class_id ec_id) const;
|
||||
|
||||
void purge (const purge_criteria &p, purge_stats *stats);
|
||||
/* PurgeCriteria should have:
|
||||
bool should_purge_p (const svalue *sval) const. */
|
||||
template <typename PurgeCriteria>
|
||||
void purge (const PurgeCriteria &p, purge_stats *stats);
|
||||
|
||||
void remap_svalue_ids (const svalue_id_map &map);
|
||||
void on_liveness_change (const svalue_set &live_svalues,
|
||||
const region_model *model);
|
||||
|
||||
void canonicalize (unsigned num_svalue_ids);
|
||||
void canonicalize ();
|
||||
|
||||
static void merge (const constraint_manager &cm_a,
|
||||
const constraint_manager &cm_b,
|
||||
|
@ -236,13 +279,11 @@ public:
|
|||
auto_vec<constraint> m_constraints;
|
||||
|
||||
private:
|
||||
static void clean_merger_input (const constraint_manager &cm_in,
|
||||
const one_way_svalue_id_map &map_sid_to_m,
|
||||
constraint_manager *out);
|
||||
|
||||
void add_constraint_internal (equiv_class_id lhs_id,
|
||||
enum constraint_op c_op,
|
||||
equiv_class_id rhs_id);
|
||||
|
||||
region_model_manager *m_mgr;
|
||||
};
|
||||
|
||||
} // namespace ana
|
||||
|
|
|
@ -42,6 +42,9 @@ along with GCC; see the file COPYING3. If not see
|
|||
#include "analyzer/sm.h"
|
||||
#include "analyzer/pending-diagnostic.h"
|
||||
#include "analyzer/diagnostic-manager.h"
|
||||
#include "analyzer/call-string.h"
|
||||
#include "analyzer/program-point.h"
|
||||
#include "analyzer/store.h"
|
||||
#include "analyzer/region-model.h"
|
||||
#include "analyzer/constraint-manager.h"
|
||||
#include "cfg.h"
|
||||
|
@ -51,8 +54,6 @@ along with GCC; see the file COPYING3. If not see
|
|||
#include "cgraph.h"
|
||||
#include "digraph.h"
|
||||
#include "analyzer/supergraph.h"
|
||||
#include "analyzer/call-string.h"
|
||||
#include "analyzer/program-point.h"
|
||||
#include "analyzer/program-state.h"
|
||||
#include "analyzer/exploded-graph.h"
|
||||
#include "analyzer/checker-path.h"
|
||||
|
@ -71,13 +72,15 @@ saved_diagnostic::saved_diagnostic (const state_machine *sm,
|
|||
const exploded_node *enode,
|
||||
const supernode *snode, const gimple *stmt,
|
||||
stmt_finder *stmt_finder,
|
||||
tree var, state_machine::state_t state,
|
||||
tree var,
|
||||
const svalue *sval,
|
||||
state_machine::state_t state,
|
||||
pending_diagnostic *d)
|
||||
: m_sm (sm), m_enode (enode), m_snode (snode), m_stmt (stmt),
|
||||
/* stmt_finder could be on-stack; we want our own copy that can
|
||||
outlive that. */
|
||||
m_stmt_finder (stmt_finder ? stmt_finder->clone () : NULL),
|
||||
m_var (var), m_state (state),
|
||||
m_var (var), m_sval (sval), m_state (state),
|
||||
m_d (d), m_trailing_eedge (NULL),
|
||||
m_status (STATUS_NEW), m_epath_length (0), m_problem (NULL)
|
||||
{
|
||||
|
@ -150,8 +153,9 @@ private:
|
|||
|
||||
/* diagnostic_manager's ctor. */
|
||||
|
||||
diagnostic_manager::diagnostic_manager (logger *logger, int verbosity)
|
||||
: log_user (logger), m_verbosity (verbosity)
|
||||
diagnostic_manager::diagnostic_manager (logger *logger, engine *eng,
|
||||
int verbosity)
|
||||
: log_user (logger), m_eng (eng), m_verbosity (verbosity)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -162,7 +166,9 @@ diagnostic_manager::add_diagnostic (const state_machine *sm,
|
|||
const exploded_node *enode,
|
||||
const supernode *snode, const gimple *stmt,
|
||||
stmt_finder *finder,
|
||||
tree var, state_machine::state_t state,
|
||||
tree var,
|
||||
const svalue *sval,
|
||||
state_machine::state_t state,
|
||||
pending_diagnostic *d)
|
||||
{
|
||||
LOG_FUNC (get_logger ());
|
||||
|
@ -172,7 +178,8 @@ diagnostic_manager::add_diagnostic (const state_machine *sm,
|
|||
gcc_assert (enode);
|
||||
|
||||
saved_diagnostic *sd
|
||||
= new saved_diagnostic (sm, enode, snode, stmt, finder, var, state, d);
|
||||
= new saved_diagnostic (sm, enode, snode, stmt, finder, var, sval,
|
||||
state, d);
|
||||
m_saved_diagnostics.safe_push (sd);
|
||||
if (get_logger ())
|
||||
log ("adding saved diagnostic %i at SN %i: %qs",
|
||||
|
@ -189,7 +196,7 @@ diagnostic_manager::add_diagnostic (const exploded_node *enode,
|
|||
pending_diagnostic *d)
|
||||
{
|
||||
gcc_assert (enode);
|
||||
add_diagnostic (NULL, enode, snode, stmt, finder, NULL_TREE, 0, d);
|
||||
add_diagnostic (NULL, enode, snode, stmt, finder, NULL_TREE, NULL, 0, d);
|
||||
}
|
||||
|
||||
/* A class for identifying sets of duplicated pending_diagnostic.
|
||||
|
@ -329,6 +336,8 @@ public:
|
|||
class dedupe_winners
|
||||
{
|
||||
public:
|
||||
dedupe_winners (engine *eng) : m_engine (eng) {}
|
||||
|
||||
~dedupe_winners ()
|
||||
{
|
||||
/* Delete all keys and candidates. */
|
||||
|
@ -347,6 +356,7 @@ public:
|
|||
|
||||
void add (logger *logger,
|
||||
const shortest_exploded_paths &sp,
|
||||
const exploded_graph *eg,
|
||||
saved_diagnostic *sd)
|
||||
{
|
||||
/* Build a dedupe_candidate for SD.
|
||||
|
@ -368,7 +378,7 @@ public:
|
|||
sd->m_snode->m_index);
|
||||
|
||||
feasibility_problem *p = NULL;
|
||||
if (!dc->get_path ().feasible_p (logger, &p))
|
||||
if (!dc->get_path ().feasible_p (logger, &p, m_engine, eg))
|
||||
{
|
||||
if (logger)
|
||||
logger->log ("rejecting %qs at EN: %i, SN: %i"
|
||||
|
@ -463,6 +473,7 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
engine *m_engine;
|
||||
|
||||
/* This maps from each dedupe_key to a current best dedupe_candidate. */
|
||||
|
||||
|
@ -499,12 +510,12 @@ diagnostic_manager::emit_saved_diagnostics (const exploded_graph &eg)
|
|||
instance. This partitions the saved diagnostics by dedupe_key,
|
||||
generating exploded_paths for them, and retaining the best one in each
|
||||
partition. */
|
||||
dedupe_winners best_candidates;
|
||||
dedupe_winners best_candidates (eg.get_engine ());
|
||||
|
||||
int i;
|
||||
saved_diagnostic *sd;
|
||||
FOR_EACH_VEC_ELT (m_saved_diagnostics, i, sd)
|
||||
best_candidates.add (get_logger (), sp, sd);
|
||||
best_candidates.add (get_logger (), sp, &eg, sd);
|
||||
|
||||
/* For each dedupe-key, call emit_saved_diagnostic on the "best"
|
||||
saved_diagnostic. */
|
||||
|
@ -539,7 +550,7 @@ diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg,
|
|||
build_emission_path (pb, epath, &emission_path);
|
||||
|
||||
/* Now prune it to just cover the most pertinent events. */
|
||||
prune_path (&emission_path, sd.m_sm, sd.m_var, sd.m_state);
|
||||
prune_path (&emission_path, sd.m_sm, sd.m_sval, sd.m_state);
|
||||
|
||||
/* Add a final event to the path, covering the diagnostic itself.
|
||||
We use the final enode from the epath, which might be different from
|
||||
|
@ -556,7 +567,7 @@ diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg,
|
|||
|
||||
emission_path.prepare_for_emission (sd.m_d);
|
||||
|
||||
gcc_rich_location rich_loc (stmt->location);
|
||||
gcc_rich_location rich_loc (get_stmt_location (stmt, sd.m_snode->m_fun));
|
||||
rich_loc.set_path (&emission_path);
|
||||
|
||||
auto_diagnostic_group d;
|
||||
|
@ -571,56 +582,6 @@ diagnostic_manager::emit_saved_diagnostic (const exploded_graph &eg,
|
|||
delete pp;
|
||||
}
|
||||
|
||||
/* Given a state change to DST_REP, determine a tree that gives the origin
|
||||
of that state at STMT, using DST_STATE's region model, so that state
|
||||
changes based on assignments can be tracked back to their origins.
|
||||
|
||||
For example, if we have
|
||||
|
||||
(S1) _1 = malloc (64);
|
||||
(S2) EXPR = _1;
|
||||
|
||||
then at stmt S2 we can get the origin of EXPR's state as being _1,
|
||||
and thus track the allocation back to S1. */
|
||||
|
||||
static tree
|
||||
get_any_origin (const gimple *stmt,
|
||||
tree dst_rep,
|
||||
const program_state &dst_state)
|
||||
{
|
||||
if (!stmt)
|
||||
return NULL_TREE;
|
||||
|
||||
gcc_assert (dst_rep);
|
||||
|
||||
if (const gassign *assign = dyn_cast <const gassign *> (stmt))
|
||||
{
|
||||
tree lhs = gimple_assign_lhs (assign);
|
||||
/* Use region IDs to compare lhs with DST_REP, bulletproofing against
|
||||
cases where they can't have lvalues by using
|
||||
tentative_region_model_context. */
|
||||
tentative_region_model_context ctxt;
|
||||
region_id lhs_rid = dst_state.m_region_model->get_lvalue (lhs, &ctxt);
|
||||
region_id dst_rep_rid
|
||||
= dst_state.m_region_model->get_lvalue (dst_rep, &ctxt);
|
||||
if (lhs_rid == dst_rep_rid && !ctxt.had_errors_p ())
|
||||
{
|
||||
tree rhs1 = gimple_assign_rhs1 (assign);
|
||||
enum tree_code op = gimple_assign_rhs_code (assign);
|
||||
switch (op)
|
||||
{
|
||||
default:
|
||||
//gcc_unreachable (); // TODO
|
||||
break;
|
||||
case COMPONENT_REF:
|
||||
case SSA_NAME:
|
||||
return rhs1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL_TREE;
|
||||
}
|
||||
|
||||
/* Emit a "path" of events to EMISSION_PATH describing the exploded path
|
||||
EPATH within EG. */
|
||||
|
||||
|
@ -668,10 +629,10 @@ public:
|
|||
stmt,
|
||||
stack_depth,
|
||||
sm,
|
||||
NULL_TREE,
|
||||
NULL,
|
||||
src_sm_val,
|
||||
dst_sm_val,
|
||||
NULL_TREE,
|
||||
NULL,
|
||||
dst_state));
|
||||
return false;
|
||||
}
|
||||
|
@ -679,8 +640,8 @@ public:
|
|||
bool on_state_change (const state_machine &sm,
|
||||
state_machine::state_t src_sm_val,
|
||||
state_machine::state_t dst_sm_val,
|
||||
tree dst_rep,
|
||||
svalue_id dst_origin_sid) FINAL OVERRIDE
|
||||
const svalue *sval,
|
||||
const svalue *dst_origin_sval) FINAL OVERRIDE
|
||||
{
|
||||
const exploded_node *src_node = m_eedge.m_src;
|
||||
const program_point &src_point = src_node->get_point ();
|
||||
|
@ -705,19 +666,14 @@ public:
|
|||
if (!stmt)
|
||||
return false;
|
||||
|
||||
tree origin_rep
|
||||
= dst_state.get_representative_tree (dst_origin_sid);
|
||||
|
||||
if (origin_rep == NULL_TREE)
|
||||
origin_rep = get_any_origin (stmt, dst_rep, dst_state);
|
||||
m_emission_path->add_event (new state_change_event (supernode,
|
||||
stmt,
|
||||
stack_depth,
|
||||
sm,
|
||||
dst_rep,
|
||||
sval,
|
||||
src_sm_val,
|
||||
dst_sm_val,
|
||||
origin_rep,
|
||||
dst_origin_sval,
|
||||
dst_state));
|
||||
return false;
|
||||
}
|
||||
|
@ -771,44 +727,119 @@ for_each_state_change (const program_state &src_state,
|
|||
iter != dst_smap.end ();
|
||||
++iter)
|
||||
{
|
||||
/* Ideally we'd directly compare the SM state between src state
|
||||
and dst state, but there's no guarantee that the IDs can
|
||||
be meaningfully compared. */
|
||||
svalue_id dst_sid = (*iter).first;
|
||||
const svalue *sval = (*iter).first;
|
||||
state_machine::state_t dst_sm_val = (*iter).second.m_state;
|
||||
|
||||
auto_vec<path_var> dst_pvs;
|
||||
dst_state.m_region_model->get_path_vars_for_svalue (dst_sid,
|
||||
&dst_pvs);
|
||||
|
||||
unsigned j;
|
||||
path_var *dst_pv;
|
||||
FOR_EACH_VEC_ELT (dst_pvs, j, dst_pv)
|
||||
state_machine::state_t src_sm_val
|
||||
= src_smap.get_state (sval, ext_state);
|
||||
if (dst_sm_val != src_sm_val)
|
||||
{
|
||||
tree dst_rep = dst_pv->m_tree;
|
||||
gcc_assert (dst_rep);
|
||||
if (dst_pv->m_stack_depth
|
||||
>= src_state.m_region_model->get_stack_depth ())
|
||||
continue;
|
||||
tentative_region_model_context ctxt;
|
||||
svalue_id src_sid
|
||||
= src_state.m_region_model->get_rvalue (*dst_pv, &ctxt);
|
||||
if (src_sid.null_p () || ctxt.had_errors_p ())
|
||||
continue;
|
||||
state_machine::state_t src_sm_val = src_smap.get_state (src_sid);
|
||||
if (dst_sm_val != src_sm_val)
|
||||
{
|
||||
svalue_id dst_origin_sid = (*iter).second.m_origin;
|
||||
if (visitor->on_state_change (sm, src_sm_val, dst_sm_val,
|
||||
dst_rep, dst_origin_sid))
|
||||
return true;
|
||||
}
|
||||
const svalue *origin_sval = (*iter).second.m_origin;
|
||||
if (visitor->on_state_change (sm, src_sm_val, dst_sm_val,
|
||||
sval, origin_sval))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* An sm_context for adding state_change_event on assignments to NULL,
|
||||
where the default state isn't m_start. Storing such state in the
|
||||
sm_state_map would lead to bloat of the exploded_graph, so we want
|
||||
to leave it as a default state, and inject state change events here
|
||||
when we have a diagnostic.
|
||||
Find transitions of constants, for handling on_zero_assignment. */
|
||||
|
||||
struct null_assignment_sm_context : public sm_context
|
||||
{
|
||||
null_assignment_sm_context (int sm_idx,
|
||||
const state_machine &sm,
|
||||
const program_state *new_state,
|
||||
const gimple *stmt,
|
||||
const program_point *point,
|
||||
checker_path *emission_path)
|
||||
: sm_context (sm_idx, sm), m_new_state (new_state),
|
||||
m_stmt (stmt), m_point (point), m_emission_path (emission_path)
|
||||
{
|
||||
}
|
||||
|
||||
tree get_fndecl_for_call (const gcall */*call*/) FINAL OVERRIDE
|
||||
{
|
||||
return NULL_TREE;
|
||||
}
|
||||
|
||||
void on_transition (const supernode *node ATTRIBUTE_UNUSED,
|
||||
const gimple *stmt ATTRIBUTE_UNUSED,
|
||||
tree var,
|
||||
state_machine::state_t from,
|
||||
state_machine::state_t to,
|
||||
tree origin ATTRIBUTE_UNUSED) FINAL OVERRIDE
|
||||
{
|
||||
if (from != 0)
|
||||
return;
|
||||
|
||||
const svalue *var_new_sval
|
||||
= m_new_state->m_region_model->get_rvalue (var, NULL);
|
||||
const supernode *supernode = m_point->get_supernode ();
|
||||
int stack_depth = m_point->get_stack_depth ();
|
||||
|
||||
m_emission_path->add_event (new state_change_event (supernode,
|
||||
m_stmt,
|
||||
stack_depth,
|
||||
m_sm,
|
||||
var_new_sval,
|
||||
from, to,
|
||||
NULL,
|
||||
*m_new_state));
|
||||
|
||||
}
|
||||
|
||||
void warn_for_state (const supernode *, const gimple *,
|
||||
tree, state_machine::state_t,
|
||||
pending_diagnostic *d) FINAL OVERRIDE
|
||||
{
|
||||
delete d;
|
||||
}
|
||||
|
||||
tree get_diagnostic_tree (tree expr) FINAL OVERRIDE
|
||||
{
|
||||
return expr;
|
||||
}
|
||||
|
||||
state_machine::state_t get_global_state () const FINAL OVERRIDE
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void set_global_state (state_machine::state_t) FINAL OVERRIDE
|
||||
{
|
||||
/* No-op. */
|
||||
}
|
||||
|
||||
void on_custom_transition (custom_transition *) FINAL OVERRIDE
|
||||
{
|
||||
}
|
||||
|
||||
tree is_zero_assignment (const gimple *stmt) FINAL OVERRIDE
|
||||
{
|
||||
const gassign *assign_stmt = dyn_cast <const gassign *> (stmt);
|
||||
if (!assign_stmt)
|
||||
return NULL_TREE;
|
||||
if (const svalue *sval
|
||||
= m_new_state->m_region_model->get_gassign_result (assign_stmt, NULL))
|
||||
if (tree cst = sval->maybe_get_constant ())
|
||||
if (::zerop(cst))
|
||||
return gimple_assign_lhs (assign_stmt);
|
||||
return NULL_TREE;
|
||||
}
|
||||
|
||||
const program_state *m_new_state;
|
||||
const gimple *m_stmt;
|
||||
const program_point *m_point;
|
||||
state_change_visitor *m_visitor;
|
||||
checker_path *m_emission_path;
|
||||
};
|
||||
|
||||
/* Subroutine of diagnostic_manager::build_emission_path.
|
||||
Add any events for EEDGE to EMISSION_PATH. */
|
||||
|
||||
|
@ -898,6 +929,41 @@ diagnostic_manager::add_events_for_eedge (const path_builder &pb,
|
|||
(new statement_event (stmt,
|
||||
dst_point.get_fndecl (),
|
||||
dst_stack_depth, dst_state));
|
||||
|
||||
/* Create state change events for assignment to NULL.
|
||||
Iterate through the stmts in dst_enode, adding state change
|
||||
events for them. */
|
||||
if (dst_state.m_region_model)
|
||||
{
|
||||
program_state iter_state (dst_state);
|
||||
program_point iter_point (dst_point);
|
||||
while (1)
|
||||
{
|
||||
const gimple *stmt = iter_point.get_stmt ();
|
||||
if (const gassign *assign = dyn_cast<const gassign *> (stmt))
|
||||
{
|
||||
const extrinsic_state &ext_state = pb.get_ext_state ();
|
||||
iter_state.m_region_model->on_assignment (assign, NULL);
|
||||
for (unsigned i = 0; i < ext_state.get_num_checkers (); i++)
|
||||
{
|
||||
const state_machine &sm = ext_state.get_sm (i);
|
||||
null_assignment_sm_context sm_ctxt (i, sm,
|
||||
&iter_state,
|
||||
stmt,
|
||||
&iter_point,
|
||||
emission_path);
|
||||
sm.on_stmt (&sm_ctxt, dst_point.get_supernode (), stmt);
|
||||
// TODO: what about phi nodes?
|
||||
}
|
||||
}
|
||||
iter_point.next_stmt ();
|
||||
if (iter_point.get_kind () == PK_AFTER_SUPERNODE
|
||||
|| (dst_node->m_succs.length () > 1
|
||||
&& (iter_point
|
||||
== dst_node->m_succs[0]->m_dest->get_point ())))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -1076,12 +1142,12 @@ diagnostic_manager::add_events_for_superedge (const path_builder &pb,
|
|||
void
|
||||
diagnostic_manager::prune_path (checker_path *path,
|
||||
const state_machine *sm,
|
||||
tree var,
|
||||
const svalue *sval,
|
||||
state_machine::state_t state) const
|
||||
{
|
||||
LOG_FUNC (get_logger ());
|
||||
path->maybe_log (get_logger (), "path");
|
||||
prune_for_sm_diagnostic (path, sm, var, state);
|
||||
prune_for_sm_diagnostic (path, sm, sval, state);
|
||||
prune_interproc_events (path);
|
||||
finish_pruning (path);
|
||||
path->maybe_log (get_logger (), "pruned");
|
||||
|
@ -1125,11 +1191,9 @@ can_be_expr_of_interest_p (tree expr)
|
|||
void
|
||||
diagnostic_manager::prune_for_sm_diagnostic (checker_path *path,
|
||||
const state_machine *sm,
|
||||
tree var,
|
||||
const svalue *sval,
|
||||
state_machine::state_t state) const
|
||||
{
|
||||
update_for_unsuitable_sm_exprs (&var);
|
||||
|
||||
int idx = path->num_events () - 1;
|
||||
while (idx >= 0 && idx < (signed)path->num_events ())
|
||||
{
|
||||
|
@ -1138,17 +1202,22 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path,
|
|||
{
|
||||
if (sm)
|
||||
{
|
||||
if (var)
|
||||
log ("considering event %i, with var: %qE, state: %qs",
|
||||
idx, var, sm->get_state_name (state));
|
||||
if (sval)
|
||||
{
|
||||
label_text sval_desc = sval->get_desc ();
|
||||
log ("considering event %i (%s), with sval: %qs, state: %qs",
|
||||
idx, event_kind_to_string (base_event->m_kind),
|
||||
sval_desc.m_buffer, sm->get_state_name (state));
|
||||
}
|
||||
else
|
||||
log ("considering event %i, with global state: %qs",
|
||||
idx, sm->get_state_name (state));
|
||||
log ("considering event %i (%s), with global state: %qs",
|
||||
idx, event_kind_to_string (base_event->m_kind),
|
||||
sm->get_state_name (state));
|
||||
}
|
||||
else
|
||||
log ("considering event %i", idx);
|
||||
}
|
||||
gcc_assert (var == NULL || can_be_expr_of_interest_p (var));
|
||||
|
||||
switch (base_event->m_kind)
|
||||
{
|
||||
default:
|
||||
|
@ -1168,19 +1237,6 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path,
|
|||
|
||||
case EK_STMT:
|
||||
{
|
||||
/* If this stmt is the origin of "var", update var. */
|
||||
if (var)
|
||||
{
|
||||
statement_event *stmt_event = (statement_event *)base_event;
|
||||
tree new_var = get_any_origin (stmt_event->m_stmt, var,
|
||||
stmt_event->m_dst_state);
|
||||
if (new_var)
|
||||
{
|
||||
log ("event %i: switching var of interest from %qE to %qE",
|
||||
idx, var, new_var);
|
||||
var = new_var;
|
||||
}
|
||||
}
|
||||
if (m_verbosity < 4)
|
||||
{
|
||||
log ("filtering event %i: statement event", idx);
|
||||
|
@ -1200,21 +1256,23 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path,
|
|||
case EK_STATE_CHANGE:
|
||||
{
|
||||
state_change_event *state_change = (state_change_event *)base_event;
|
||||
/* Use region IDs to compare var with the state_change's m_var,
|
||||
bulletproofing against cases where they can't have lvalues by
|
||||
using tentative_region_model_context. */
|
||||
tentative_region_model_context ctxt;
|
||||
region_id state_var_rid
|
||||
= state_change->get_lvalue (state_change->m_var, &ctxt);
|
||||
region_id var_rid = state_change->get_lvalue (var, &ctxt);
|
||||
if (state_var_rid == var_rid && !ctxt.had_errors_p ())
|
||||
gcc_assert (state_change->m_dst_state.m_region_model);
|
||||
|
||||
if (state_change->m_sval == sval)
|
||||
{
|
||||
if (state_change->m_origin)
|
||||
{
|
||||
log ("event %i: switching var of interest from %qE to %qE",
|
||||
idx, var, state_change->m_origin);
|
||||
var = state_change->m_origin;
|
||||
update_for_unsuitable_sm_exprs (&var);
|
||||
if (get_logger ())
|
||||
{
|
||||
label_text sval_desc = sval->get_desc ();
|
||||
label_text origin_sval_desc
|
||||
= state_change->m_origin->get_desc ();
|
||||
log ("event %i:"
|
||||
" switching var of interest from %qs to %qs",
|
||||
idx, sval_desc.m_buffer,
|
||||
origin_sval_desc.m_buffer);
|
||||
}
|
||||
sval = state_change->m_origin;
|
||||
}
|
||||
log ("event %i: switching state of interest from %qs to %qs",
|
||||
idx, sm->get_state_name (state_change->m_to),
|
||||
|
@ -1223,15 +1281,27 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path,
|
|||
}
|
||||
else if (m_verbosity < 4)
|
||||
{
|
||||
if (var)
|
||||
log ("filtering event %i:"
|
||||
" state change to %qE unrelated to %qE",
|
||||
idx, state_change->m_var, var);
|
||||
else
|
||||
log ("filtering event %i: state change to %qE",
|
||||
idx, state_change->m_var);
|
||||
if (ctxt.had_errors_p ())
|
||||
log ("context had errors");
|
||||
if (get_logger ())
|
||||
{
|
||||
if (state_change->m_sval)
|
||||
{
|
||||
label_text change_sval_desc
|
||||
= state_change->m_sval->get_desc ();
|
||||
if (sval)
|
||||
{
|
||||
label_text sval_desc = sval->get_desc ();
|
||||
log ("filtering event %i:"
|
||||
" state change to %qs unrelated to %qs",
|
||||
idx, change_sval_desc.m_buffer,
|
||||
sval_desc.m_buffer);
|
||||
}
|
||||
else
|
||||
log ("filtering event %i: state change to %qs",
|
||||
idx, change_sval_desc.m_buffer);
|
||||
}
|
||||
else
|
||||
log ("filtering event %i: global state change", idx);
|
||||
}
|
||||
path->delete_event (idx);
|
||||
}
|
||||
}
|
||||
|
@ -1240,34 +1310,6 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path,
|
|||
case EK_START_CFG_EDGE:
|
||||
{
|
||||
cfg_edge_event *event = (cfg_edge_event *)base_event;
|
||||
const cfg_superedge& cfg_superedge
|
||||
= event->get_cfg_superedge ();
|
||||
const supernode *dest = event->m_sedge->m_dest;
|
||||
/* Do we have an SSA_NAME defined via a phi node in
|
||||
the dest CFG node? */
|
||||
if (var && TREE_CODE (var) == SSA_NAME)
|
||||
if (SSA_NAME_DEF_STMT (var)->bb == dest->m_bb)
|
||||
{
|
||||
if (gphi *phi
|
||||
= dyn_cast <gphi *> (SSA_NAME_DEF_STMT (var)))
|
||||
{
|
||||
/* Update var based on its phi node. */
|
||||
tree old_var = var;
|
||||
var = cfg_superedge.get_phi_arg (phi);
|
||||
log ("updating from %qE to %qE based on phi node",
|
||||
old_var, var);
|
||||
if (get_logger ())
|
||||
{
|
||||
pretty_printer pp;
|
||||
pp_gimple_stmt_1 (&pp, phi, 0, (dump_flags_t)0);
|
||||
log (" phi: %s", pp_formatted_text (&pp));
|
||||
}
|
||||
/* If we've chosen a bad exploded_path, then the
|
||||
phi arg might be a constant. Fail gracefully for
|
||||
this case. */
|
||||
update_for_unsuitable_sm_exprs (&var);
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: is this edge significant to var?
|
||||
See if var can be in other states in the dest, but not
|
||||
|
@ -1276,7 +1318,7 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path,
|
|||
|
||||
if (event->should_filter_p (m_verbosity))
|
||||
{
|
||||
log ("filtering event %i: CFG edge", idx);
|
||||
log ("filtering events %i and %i: CFG edge", idx, idx + 1);
|
||||
path->delete_event (idx);
|
||||
/* Also delete the corresponding EK_END_CFG_EDGE. */
|
||||
gcc_assert (path->get_checker_event (idx)->m_kind
|
||||
|
@ -1296,45 +1338,58 @@ diagnostic_manager::prune_for_sm_diagnostic (checker_path *path,
|
|||
call_event *event = (call_event *)base_event;
|
||||
const callgraph_superedge& cg_superedge
|
||||
= event->get_callgraph_superedge ();
|
||||
const region_model *callee_model
|
||||
= event->m_eedge.m_dest->get_state ().m_region_model;
|
||||
tree callee_var = callee_model->get_representative_tree (sval);
|
||||
/* We could just use caller_model->get_representative_tree (sval);
|
||||
to get the caller_var, but for now use
|
||||
map_expr_from_callee_to_caller so as to only record critical
|
||||
state for parms and the like. */
|
||||
callsite_expr expr;
|
||||
tree caller_var
|
||||
= cg_superedge.map_expr_from_callee_to_caller (var, &expr);
|
||||
= cg_superedge.map_expr_from_callee_to_caller (callee_var, &expr);
|
||||
if (caller_var)
|
||||
{
|
||||
log ("event %i:"
|
||||
" switching var of interest"
|
||||
" from %qE in callee to %qE in caller",
|
||||
idx, var, caller_var);
|
||||
var = caller_var;
|
||||
if (get_logger ())
|
||||
{
|
||||
label_text sval_desc = sval->get_desc ();
|
||||
log ("event %i:"
|
||||
" recording critical state for %qs at call"
|
||||
" from %qE in callee to %qE in caller",
|
||||
idx, sval_desc.m_buffer, callee_var, caller_var);
|
||||
}
|
||||
if (expr.param_p ())
|
||||
event->record_critical_state (var, state);
|
||||
update_for_unsuitable_sm_exprs (&var);
|
||||
event->record_critical_state (caller_var, state);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case EK_RETURN_EDGE:
|
||||
// TODO: potentially update var/state based on return value,
|
||||
// args etc
|
||||
{
|
||||
if (var)
|
||||
if (sval)
|
||||
{
|
||||
return_event *event = (return_event *)base_event;
|
||||
const callgraph_superedge& cg_superedge
|
||||
= event->get_callgraph_superedge ();
|
||||
const region_model *caller_model
|
||||
= event->m_eedge.m_dest->get_state ().m_region_model;
|
||||
tree caller_var = caller_model->get_representative_tree (sval);
|
||||
callsite_expr expr;
|
||||
tree callee_var
|
||||
= cg_superedge.map_expr_from_caller_to_callee (var, &expr);
|
||||
= cg_superedge.map_expr_from_caller_to_callee (caller_var,
|
||||
&expr);
|
||||
if (callee_var)
|
||||
{
|
||||
log ("event %i:"
|
||||
" switching var of interest"
|
||||
" from %qE in caller to %qE in callee",
|
||||
idx, var, callee_var);
|
||||
var = callee_var;
|
||||
if (get_logger ())
|
||||
{
|
||||
label_text sval_desc = sval->get_desc ();
|
||||
log ("event %i:"
|
||||
" recording critical state for %qs at return"
|
||||
" from %qE in caller to %qE in callee",
|
||||
idx, sval_desc.m_buffer, callee_var, callee_var);
|
||||
}
|
||||
if (expr.return_value_p ())
|
||||
event->record_critical_state (var, state);
|
||||
update_for_unsuitable_sm_exprs (&var);
|
||||
event->record_critical_state (callee_var, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,8 @@ public:
|
|||
const exploded_node *enode,
|
||||
const supernode *snode, const gimple *stmt,
|
||||
stmt_finder *stmt_finder,
|
||||
tree var, state_machine::state_t state,
|
||||
tree var, const svalue *sval,
|
||||
state_machine::state_t state,
|
||||
pending_diagnostic *d);
|
||||
~saved_diagnostic ();
|
||||
|
||||
|
@ -73,6 +74,7 @@ public:
|
|||
const gimple *m_stmt;
|
||||
stmt_finder *m_stmt_finder;
|
||||
tree m_var;
|
||||
const svalue *m_sval;
|
||||
state_machine::state_t m_state;
|
||||
pending_diagnostic *m_d;
|
||||
exploded_edge *m_trailing_eedge;
|
||||
|
@ -99,13 +101,17 @@ class path_builder;
|
|||
class diagnostic_manager : public log_user
|
||||
{
|
||||
public:
|
||||
diagnostic_manager (logger *logger, int verbosity);
|
||||
diagnostic_manager (logger *logger, engine *eng, int verbosity);
|
||||
|
||||
engine *get_engine () const { return m_eng; }
|
||||
|
||||
void add_diagnostic (const state_machine *sm,
|
||||
const exploded_node *enode,
|
||||
const supernode *snode, const gimple *stmt,
|
||||
stmt_finder *finder,
|
||||
tree var, state_machine::state_t state,
|
||||
tree var,
|
||||
const svalue *sval,
|
||||
state_machine::state_t state,
|
||||
pending_diagnostic *d);
|
||||
|
||||
void add_diagnostic (const exploded_node *enode,
|
||||
|
@ -152,16 +158,22 @@ private:
|
|||
|
||||
void prune_path (checker_path *path,
|
||||
const state_machine *sm,
|
||||
tree var, state_machine::state_t state) const;
|
||||
const svalue *sval,
|
||||
state_machine::state_t state) const;
|
||||
|
||||
void prune_for_sm_diagnostic (checker_path *path,
|
||||
const state_machine *sm,
|
||||
tree var,
|
||||
state_machine::state_t state) const;
|
||||
void prune_for_sm_diagnostic (checker_path *path,
|
||||
const state_machine *sm,
|
||||
const svalue *sval,
|
||||
state_machine::state_t state) const;
|
||||
void update_for_unsuitable_sm_exprs (tree *expr) const;
|
||||
void prune_interproc_events (checker_path *path) const;
|
||||
void finish_pruning (checker_path *path) const;
|
||||
|
||||
engine *m_eng;
|
||||
auto_delete_vec<saved_diagnostic> m_saved_diagnostics;
|
||||
const int m_verbosity;
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -36,44 +36,30 @@ class impl_region_model_context : public region_model_context
|
|||
old state, rather than the new? */
|
||||
const program_state *old_state,
|
||||
program_state *new_state,
|
||||
state_change *change,
|
||||
|
||||
const gimple *stmt,
|
||||
stmt_finder *stmt_finder = NULL);
|
||||
|
||||
impl_region_model_context (program_state *state,
|
||||
state_change *change,
|
||||
const extrinsic_state &ext_state,
|
||||
logger *logger = NULL);
|
||||
|
||||
void warn (pending_diagnostic *d) FINAL OVERRIDE;
|
||||
|
||||
void remap_svalue_ids (const svalue_id_map &map) FINAL OVERRIDE;
|
||||
|
||||
int on_svalue_purge (svalue_id first_unused_sid,
|
||||
const svalue_id_map &map) FINAL OVERRIDE;
|
||||
|
||||
void on_svalue_leak (const svalue *) OVERRIDE;
|
||||
void on_liveness_change (const svalue_set &live_svalues,
|
||||
const region_model *model) FINAL OVERRIDE;
|
||||
logger *get_logger () FINAL OVERRIDE
|
||||
{
|
||||
return m_logger.get_logger ();
|
||||
}
|
||||
|
||||
void on_state_leak (const state_machine &sm,
|
||||
int sm_idx,
|
||||
svalue_id sid,
|
||||
svalue_id first_unused_sid,
|
||||
const svalue_id_map &map,
|
||||
const svalue *sval,
|
||||
state_machine::state_t state);
|
||||
|
||||
void on_inherited_svalue (svalue_id parent_sid,
|
||||
svalue_id child_sid) FINAL OVERRIDE;
|
||||
|
||||
void on_cast (svalue_id src_sid,
|
||||
svalue_id dst_sid) FINAL OVERRIDE;
|
||||
|
||||
void on_condition (tree lhs, enum tree_code op, tree rhs) FINAL OVERRIDE;
|
||||
|
||||
void on_unknown_change (svalue_id sid ATTRIBUTE_UNUSED) FINAL OVERRIDE;
|
||||
void on_unknown_change (const svalue *sval, bool is_mutable) FINAL OVERRIDE;
|
||||
|
||||
void on_phi (const gphi *phi, tree rhs) FINAL OVERRIDE;
|
||||
|
||||
|
@ -85,7 +71,6 @@ class impl_region_model_context : public region_model_context
|
|||
const exploded_node *m_enode_for_diag;
|
||||
const program_state *m_old_state;
|
||||
program_state *m_new_state;
|
||||
state_change *m_change;
|
||||
const gimple *m_stmt;
|
||||
stmt_finder *m_stmt_finder;
|
||||
const extrinsic_state &m_ext_state;
|
||||
|
@ -145,6 +130,9 @@ struct eg_traits
|
|||
struct dump_args_t
|
||||
{
|
||||
dump_args_t (const exploded_graph &eg) : m_eg (eg) {}
|
||||
|
||||
bool show_enode_details_p (const exploded_node &enode) const;
|
||||
|
||||
const exploded_graph &m_eg;
|
||||
};
|
||||
typedef exploded_cluster cluster_t;
|
||||
|
@ -172,7 +160,7 @@ class exploded_node : public dnode<eg_traits>
|
|||
|
||||
/* Node was left unprocessed due to merger; it won't have had
|
||||
exploded_graph::process_node called on it. */
|
||||
STATUS_MERGER
|
||||
STATUS_MERGER,
|
||||
};
|
||||
|
||||
exploded_node (const point_and_state &ps, int index);
|
||||
|
@ -224,13 +212,11 @@ class exploded_node : public dnode<eg_traits>
|
|||
on_stmt_flags on_stmt (exploded_graph &eg,
|
||||
const supernode *snode,
|
||||
const gimple *stmt,
|
||||
program_state *state,
|
||||
state_change *change) const;
|
||||
program_state *state) const;
|
||||
bool on_edge (exploded_graph &eg,
|
||||
const superedge *succ,
|
||||
program_point *next_point,
|
||||
program_state *next_state,
|
||||
state_change *change) const;
|
||||
program_state *next_state) const;
|
||||
void on_longjmp (exploded_graph &eg,
|
||||
const gcall *call,
|
||||
program_state *new_state,
|
||||
|
@ -252,6 +238,7 @@ class exploded_node : public dnode<eg_traits>
|
|||
return get_point ().get_stack_depth ();
|
||||
}
|
||||
const gimple *get_stmt () const { return get_point ().get_stmt (); }
|
||||
const gimple *get_processed_stmt (unsigned idx) const;
|
||||
|
||||
const program_state &get_state () const { return m_ps.get_state (); }
|
||||
|
||||
|
@ -279,6 +266,10 @@ private:
|
|||
public:
|
||||
/* The index of this exploded_node. */
|
||||
const int m_index;
|
||||
|
||||
/* The number of stmts that were processed when process_node was
|
||||
called on this enode. */
|
||||
unsigned m_num_processed_stmts;
|
||||
};
|
||||
|
||||
/* An edge within the exploded graph.
|
||||
|
@ -307,9 +298,7 @@ class exploded_edge : public dedge<eg_traits>
|
|||
};
|
||||
|
||||
exploded_edge (exploded_node *src, exploded_node *dest,
|
||||
const extrinsic_state &ext_state,
|
||||
const superedge *sedge,
|
||||
const state_change &change,
|
||||
custom_info_t *custom_info);
|
||||
~exploded_edge ();
|
||||
void dump_dot (graphviz_out *gv, const dump_args_t &args)
|
||||
|
@ -318,8 +307,6 @@ class exploded_edge : public dedge<eg_traits>
|
|||
//private:
|
||||
const superedge *const m_sedge;
|
||||
|
||||
const state_change m_change;
|
||||
|
||||
/* NULL for most edges; will be non-NULL for special cases
|
||||
such as an unwind from a longjmp to a setjmp, or when
|
||||
a signal is delivered to a signal-handler.
|
||||
|
@ -733,6 +720,7 @@ public:
|
|||
|
||||
const supergraph &get_supergraph () const { return m_sg; }
|
||||
const extrinsic_state &get_ext_state () const { return m_ext_state; }
|
||||
engine *get_engine () const { return m_ext_state.get_engine (); }
|
||||
const state_purge_map *get_purge_map () const { return m_purge_map; }
|
||||
const analysis_plan &get_analysis_plan () const { return m_plan; }
|
||||
|
||||
|
@ -746,14 +734,15 @@ public:
|
|||
|
||||
exploded_node *get_or_create_node (const program_point &point,
|
||||
const program_state &state,
|
||||
state_change *change);
|
||||
const exploded_node *enode_for_diag);
|
||||
exploded_edge *add_edge (exploded_node *src, exploded_node *dest,
|
||||
const superedge *sedge,
|
||||
const state_change &change,
|
||||
exploded_edge::custom_info_t *custom = NULL);
|
||||
|
||||
per_program_point_data *
|
||||
get_or_create_per_program_point_data (const program_point &);
|
||||
per_program_point_data *
|
||||
get_per_program_point_data (const program_point &) const;
|
||||
|
||||
per_call_string_data *
|
||||
get_or_create_per_call_string_data (const call_string &);
|
||||
|
@ -785,6 +774,8 @@ public:
|
|||
void dump_states_for_supernode (FILE *, const supernode *snode) const;
|
||||
void dump_exploded_nodes () const;
|
||||
|
||||
exploded_node *get_node_by_index (int idx) const;
|
||||
|
||||
const call_string_data_map_t *get_per_call_string_data () const
|
||||
{ return &m_per_call_string_data; }
|
||||
|
||||
|
@ -857,6 +848,8 @@ public:
|
|||
void dump () const;
|
||||
|
||||
bool feasible_p (logger *logger, feasibility_problem **out) const;
|
||||
bool feasible_p (logger *logger, feasibility_problem **out,
|
||||
engine *eng, const exploded_graph *eg) const;
|
||||
|
||||
auto_vec<const exploded_edge *> m_edges;
|
||||
};
|
||||
|
|
|
@ -42,6 +42,7 @@ along with GCC; see the file COPYING3. If not see
|
|||
#include "bitmap.h"
|
||||
#include "tristate.h"
|
||||
#include "selftest.h"
|
||||
#include "analyzer/store.h"
|
||||
#include "analyzer/region-model.h"
|
||||
#include "analyzer/sm.h"
|
||||
#include "analyzer/program-state.h"
|
||||
|
@ -84,6 +85,22 @@ point_kind_to_string (enum point_kind pk)
|
|||
|
||||
/* class function_point. */
|
||||
|
||||
function_point::function_point (const supernode *supernode,
|
||||
const superedge *from_edge,
|
||||
unsigned stmt_idx,
|
||||
enum point_kind kind)
|
||||
: m_supernode (supernode), m_from_edge (from_edge),
|
||||
m_stmt_idx (stmt_idx), m_kind (kind)
|
||||
{
|
||||
if (from_edge)
|
||||
{
|
||||
gcc_checking_assert (m_kind == PK_BEFORE_SUPERNODE);
|
||||
gcc_checking_assert (from_edge->get_kind () == SUPEREDGE_CFG_EDGE);
|
||||
}
|
||||
if (stmt_idx)
|
||||
gcc_checking_assert (m_kind == PK_BEFORE_STMT);
|
||||
}
|
||||
|
||||
/* Print this function_point to PP. */
|
||||
|
||||
void
|
||||
|
@ -149,6 +166,17 @@ function_point::hash () const
|
|||
return hstate.end ();
|
||||
}
|
||||
|
||||
/* Get the function at this point, if any. */
|
||||
|
||||
function *
|
||||
function_point::get_function () const
|
||||
{
|
||||
if (m_supernode)
|
||||
return m_supernode->m_fun;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Get the gimple stmt for this function_point, if any. */
|
||||
|
||||
const gimple *
|
||||
|
@ -174,6 +202,26 @@ function_point::get_location () const
|
|||
return UNKNOWN_LOCATION;
|
||||
}
|
||||
|
||||
/* Create a function_point representing the entrypoint of function FUN. */
|
||||
|
||||
function_point
|
||||
function_point::from_function_entry (const supergraph &sg, function *fun)
|
||||
{
|
||||
return before_supernode (sg.get_node_for_function_entry (fun), NULL);
|
||||
}
|
||||
|
||||
/* Create a function_point representing entering supernode SUPERNODE,
|
||||
having reached it via FROM_EDGE (which could be NULL). */
|
||||
|
||||
function_point
|
||||
function_point::before_supernode (const supernode *supernode,
|
||||
const superedge *from_edge)
|
||||
{
|
||||
if (from_edge && from_edge->get_kind () != SUPEREDGE_CFG_EDGE)
|
||||
from_edge = NULL;
|
||||
return function_point (supernode, from_edge, 0, PK_BEFORE_SUPERNODE);
|
||||
}
|
||||
|
||||
/* A subclass of diagnostic_context for use by
|
||||
program_point::print_source_line. */
|
||||
|
||||
|
@ -468,6 +516,19 @@ function_point::cmp_within_supernode (const function_point &point_a,
|
|||
return result;
|
||||
}
|
||||
|
||||
/* For PK_BEFORE_STMT, go to next stmt (or to PK_AFTER_SUPERNODE). */
|
||||
|
||||
void
|
||||
function_point::next_stmt ()
|
||||
{
|
||||
gcc_assert (m_kind == PK_BEFORE_STMT);
|
||||
if (++m_stmt_idx == m_supernode->m_stmts.length ())
|
||||
{
|
||||
m_kind = PK_AFTER_SUPERNODE;
|
||||
m_stmt_idx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#if CHECKING_P
|
||||
|
||||
namespace selftest {
|
||||
|
|
|
@ -72,18 +72,7 @@ public:
|
|||
function_point (const supernode *supernode,
|
||||
const superedge *from_edge,
|
||||
unsigned stmt_idx,
|
||||
enum point_kind kind)
|
||||
: m_supernode (supernode), m_from_edge (from_edge),
|
||||
m_stmt_idx (stmt_idx), m_kind (kind)
|
||||
{
|
||||
if (from_edge)
|
||||
{
|
||||
gcc_checking_assert (m_kind == PK_BEFORE_SUPERNODE);
|
||||
gcc_checking_assert (from_edge->get_kind () == SUPEREDGE_CFG_EDGE);
|
||||
}
|
||||
if (stmt_idx)
|
||||
gcc_checking_assert (m_kind == PK_BEFORE_STMT);
|
||||
}
|
||||
enum point_kind kind);
|
||||
|
||||
void print (pretty_printer *pp, const format &f) const;
|
||||
void print_source_line (pretty_printer *pp) const;
|
||||
|
@ -101,13 +90,7 @@ public:
|
|||
/* Accessors. */
|
||||
|
||||
const supernode *get_supernode () const { return m_supernode; }
|
||||
function *get_function () const
|
||||
{
|
||||
if (m_supernode)
|
||||
return m_supernode->m_fun;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
function *get_function () const;
|
||||
const gimple *get_stmt () const;
|
||||
location_t get_location () const;
|
||||
enum point_kind get_kind () const { return m_kind; }
|
||||
|
@ -124,19 +107,10 @@ public:
|
|||
/* Factory functions for making various kinds of program_point. */
|
||||
|
||||
static function_point from_function_entry (const supergraph &sg,
|
||||
function *fun)
|
||||
{
|
||||
return before_supernode (sg.get_node_for_function_entry (fun),
|
||||
NULL);
|
||||
}
|
||||
function *fun);
|
||||
|
||||
static function_point before_supernode (const supernode *supernode,
|
||||
const superedge *from_edge)
|
||||
{
|
||||
if (from_edge && from_edge->get_kind () != SUPEREDGE_CFG_EDGE)
|
||||
from_edge = NULL;
|
||||
return function_point (supernode, from_edge, 0, PK_BEFORE_SUPERNODE);
|
||||
}
|
||||
const superedge *from_edge);
|
||||
|
||||
static function_point before_stmt (const supernode *supernode,
|
||||
unsigned stmt_idx)
|
||||
|
@ -165,6 +139,9 @@ public:
|
|||
static int cmp_within_supernode (const function_point &point_a,
|
||||
const function_point &point_b);
|
||||
|
||||
/* For before_stmt, go to next stmt. */
|
||||
void next_stmt ();
|
||||
|
||||
private:
|
||||
const supernode *m_supernode;
|
||||
|
||||
|
@ -204,6 +181,10 @@ public:
|
|||
return (m_function_point == other.m_function_point
|
||||
&& m_call_string == other.m_call_string);
|
||||
}
|
||||
bool operator!= (const program_point &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
/* Accessors. */
|
||||
|
||||
|
@ -257,6 +238,12 @@ public:
|
|||
}
|
||||
|
||||
/* Factory functions for making various kinds of program_point. */
|
||||
static program_point origin ()
|
||||
{
|
||||
return program_point (function_point (NULL, NULL,
|
||||
0, PK_ORIGIN),
|
||||
call_string ());
|
||||
}
|
||||
|
||||
static program_point from_function_entry (const supergraph &sg,
|
||||
function *fun)
|
||||
|
@ -304,8 +291,11 @@ public:
|
|||
|
||||
void validate () const;
|
||||
|
||||
/* For before_stmt, go to next stmt. */
|
||||
void next_stmt () { m_function_point.next_stmt (); }
|
||||
|
||||
private:
|
||||
const function_point m_function_point;
|
||||
function_point m_function_point;
|
||||
call_string m_call_string;
|
||||
};
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -28,8 +28,10 @@ namespace ana {
|
|||
class extrinsic_state
|
||||
{
|
||||
public:
|
||||
extrinsic_state (auto_delete_vec <state_machine> &checkers)
|
||||
: m_checkers (checkers)
|
||||
extrinsic_state (auto_delete_vec <state_machine> &checkers,
|
||||
logger *logger = NULL,
|
||||
engine *eng = NULL)
|
||||
: m_checkers (checkers), m_logger (logger), m_engine (eng)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -45,65 +47,24 @@ public:
|
|||
|
||||
unsigned get_num_checkers () const { return m_checkers.length (); }
|
||||
|
||||
logger *get_logger () const { return m_logger; }
|
||||
|
||||
void dump_to_pp (pretty_printer *pp) const;
|
||||
void dump_to_file (FILE *outf) const;
|
||||
void dump () const;
|
||||
|
||||
engine *get_engine () const { return m_engine; }
|
||||
region_model_manager *get_model_manager () const;
|
||||
|
||||
private:
|
||||
/* The state machines. */
|
||||
auto_delete_vec <state_machine> &m_checkers;
|
||||
|
||||
logger *m_logger;
|
||||
engine *m_engine;
|
||||
};
|
||||
|
||||
} // namespace ana
|
||||
|
||||
template <> struct default_hash_traits<svalue_id>
|
||||
: public pod_hash_traits<svalue_id>
|
||||
{
|
||||
static const bool empty_zero_p = false;
|
||||
};
|
||||
|
||||
template <>
|
||||
inline hashval_t
|
||||
pod_hash_traits<svalue_id>::hash (value_type v)
|
||||
{
|
||||
return v.as_int ();
|
||||
}
|
||||
|
||||
template <>
|
||||
inline bool
|
||||
pod_hash_traits<svalue_id>::equal (const value_type &existing,
|
||||
const value_type &candidate)
|
||||
{
|
||||
return existing == candidate;
|
||||
}
|
||||
template <>
|
||||
inline void
|
||||
pod_hash_traits<svalue_id>::mark_deleted (value_type &v)
|
||||
{
|
||||
v = svalue_id::from_int (-2);
|
||||
}
|
||||
template <>
|
||||
inline void
|
||||
pod_hash_traits<svalue_id>::mark_empty (value_type &v)
|
||||
{
|
||||
v = svalue_id::null ();
|
||||
}
|
||||
template <>
|
||||
inline bool
|
||||
pod_hash_traits<svalue_id>::is_deleted (value_type v)
|
||||
{
|
||||
return v.as_int () == -2;
|
||||
}
|
||||
template <>
|
||||
inline bool
|
||||
pod_hash_traits<svalue_id>::is_empty (value_type v)
|
||||
{
|
||||
return v.null_p ();
|
||||
}
|
||||
|
||||
namespace ana {
|
||||
|
||||
/* Map from svalue_id to state machine state, also capturing the origin of
|
||||
/* Map from svalue * to state machine state, also capturing the origin of
|
||||
each state. */
|
||||
|
||||
class sm_state_map
|
||||
|
@ -114,12 +75,12 @@ public:
|
|||
{
|
||||
/* Default ctor needed by hash_map::empty. */
|
||||
entry_t ()
|
||||
: m_state (0), m_origin (svalue_id::null ())
|
||||
: m_state (0), m_origin (NULL)
|
||||
{
|
||||
}
|
||||
|
||||
entry_t (state_machine::state_t state,
|
||||
svalue_id origin)
|
||||
const svalue *origin)
|
||||
: m_state (state), m_origin (origin)
|
||||
{}
|
||||
|
||||
|
@ -134,21 +95,19 @@ public:
|
|||
}
|
||||
|
||||
state_machine::state_t m_state;
|
||||
svalue_id m_origin;
|
||||
const svalue *m_origin;
|
||||
};
|
||||
typedef hash_map <svalue_id, entry_t> map_t;
|
||||
typedef hash_map <const svalue *, entry_t> map_t;
|
||||
typedef map_t::iterator iterator_t;
|
||||
|
||||
sm_state_map ();
|
||||
sm_state_map (const state_machine &sm, int m_sm_idx);
|
||||
|
||||
sm_state_map *clone () const;
|
||||
|
||||
sm_state_map *
|
||||
clone_with_remapping (const one_way_svalue_id_map &id_map) const;
|
||||
|
||||
void print (const state_machine &sm, const region_model *model,
|
||||
void print (const region_model *model,
|
||||
bool simple, bool multiline,
|
||||
pretty_printer *pp) const;
|
||||
void dump (const state_machine &sm) const;
|
||||
void dump (bool simple) const;
|
||||
|
||||
bool is_empty_p () const;
|
||||
|
||||
|
@ -160,51 +119,47 @@ public:
|
|||
return !(*this == other);
|
||||
}
|
||||
|
||||
state_machine::state_t get_state (svalue_id sid) const;
|
||||
svalue_id get_origin (svalue_id sid) const;
|
||||
state_machine::state_t get_state (const svalue *sval,
|
||||
const extrinsic_state &ext_state) const;
|
||||
const svalue *get_origin (const svalue *sval,
|
||||
const extrinsic_state &ext_state) const;
|
||||
|
||||
void set_state (region_model *model,
|
||||
svalue_id sid,
|
||||
const svalue *sval,
|
||||
state_machine::state_t state,
|
||||
svalue_id origin);
|
||||
const svalue *origin,
|
||||
const extrinsic_state &ext_state);
|
||||
bool set_state (const equiv_class &ec,
|
||||
state_machine::state_t state,
|
||||
svalue_id origin);
|
||||
bool impl_set_state (svalue_id sid,
|
||||
const svalue *origin,
|
||||
const extrinsic_state &ext_state);
|
||||
bool impl_set_state (const svalue *sval,
|
||||
state_machine::state_t state,
|
||||
svalue_id origin);
|
||||
const svalue *origin,
|
||||
const extrinsic_state &ext_state);
|
||||
|
||||
void set_global_state (state_machine::state_t state);
|
||||
state_machine::state_t get_global_state () const;
|
||||
|
||||
void purge_for_unknown_fncall (const exploded_graph &eg,
|
||||
const state_machine &sm,
|
||||
const gcall *call, tree fndecl,
|
||||
region_model *new_model,
|
||||
region_model_context *ctxt);
|
||||
|
||||
void remap_svalue_ids (const svalue_id_map &map);
|
||||
|
||||
int on_svalue_purge (const state_machine &sm,
|
||||
int sm_idx,
|
||||
svalue_id first_unused_sid,
|
||||
const svalue_id_map &map,
|
||||
void on_svalue_leak (const svalue *sval,
|
||||
impl_region_model_context *ctxt);
|
||||
void on_liveness_change (const svalue_set &live_svalues,
|
||||
const region_model *model,
|
||||
impl_region_model_context *ctxt);
|
||||
|
||||
void on_inherited_svalue (svalue_id parent_sid,
|
||||
svalue_id child_sid);
|
||||
|
||||
void on_cast (svalue_id src_sid,
|
||||
svalue_id dst_sid);
|
||||
|
||||
void on_unknown_change (svalue_id sid);
|
||||
|
||||
void validate (const state_machine &sm, int num_svalues) const;
|
||||
void on_unknown_change (const svalue *sval,
|
||||
bool is_mutable,
|
||||
const extrinsic_state &ext_state);
|
||||
|
||||
iterator_t begin () const { return m_map.begin (); }
|
||||
iterator_t end () const { return m_map.end (); }
|
||||
|
||||
static const svalue *
|
||||
canonicalize_svalue (const svalue *sval, const extrinsic_state &ext_state);
|
||||
|
||||
private:
|
||||
const state_machine &m_sm;
|
||||
int m_sm_idx;
|
||||
map_t m_map;
|
||||
state_machine::state_t m_global_state;
|
||||
};
|
||||
|
@ -229,7 +184,6 @@ public:
|
|||
|
||||
#if __cplusplus >= 201103
|
||||
program_state (program_state &&other);
|
||||
program_state& operator= (program_state &&other); // doesn't seem to be used
|
||||
#endif
|
||||
|
||||
~program_state ();
|
||||
|
@ -244,27 +198,27 @@ public:
|
|||
void print (const extrinsic_state &ext_state,
|
||||
pretty_printer *pp) const;
|
||||
|
||||
void dump_to_pp (const extrinsic_state &ext_state, bool summarize,
|
||||
pretty_printer *pp) const;
|
||||
void dump_to_file (const extrinsic_state &ext_state, bool summarize,
|
||||
FILE *outf) const;
|
||||
void dump (const extrinsic_state &ext_state, bool summarize) const;
|
||||
void dump_to_pp (const extrinsic_state &ext_state, bool simple,
|
||||
bool multiline, pretty_printer *pp) const;
|
||||
void dump_to_file (const extrinsic_state &ext_state, bool simple,
|
||||
bool multiline, FILE *outf) const;
|
||||
void dump (const extrinsic_state &ext_state, bool simple) const;
|
||||
|
||||
void push_frame (const extrinsic_state &ext_state, function *fun);
|
||||
function * get_current_function () const;
|
||||
|
||||
bool on_edge (exploded_graph &eg,
|
||||
const exploded_node &enode,
|
||||
const superedge *succ,
|
||||
state_change *change);
|
||||
const superedge *succ);
|
||||
|
||||
program_state prune_for_point (exploded_graph &eg,
|
||||
const program_point &point,
|
||||
state_change *change) const;
|
||||
const exploded_node *enode_for_diag) const;
|
||||
|
||||
void remap_svalue_ids (const svalue_id_map &map);
|
||||
|
||||
tree get_representative_tree (svalue_id sid) const;
|
||||
tree get_representative_tree (const svalue *sval) const;
|
||||
|
||||
bool can_purge_p (const extrinsic_state &ext_state,
|
||||
svalue_id sid)
|
||||
const svalue *sval)
|
||||
{
|
||||
/* Don't purge vars that have non-purgeable sm state, to avoid
|
||||
generating false "leak" complaints. */
|
||||
|
@ -273,18 +227,24 @@ public:
|
|||
FOR_EACH_VEC_ELT (m_checker_states, i, smap)
|
||||
{
|
||||
const state_machine &sm = ext_state.get_sm (i);
|
||||
if (!sm.can_purge_p (smap->get_state (sid)))
|
||||
if (!sm.can_purge_p (smap->get_state (sval, ext_state)))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool can_merge_with_p (const program_state &other,
|
||||
const extrinsic_state &ext_state,
|
||||
const program_point &point,
|
||||
program_state *out) const;
|
||||
|
||||
void validate (const extrinsic_state &ext_state) const;
|
||||
|
||||
static void detect_leaks (const program_state &src_state,
|
||||
const program_state &dest_state,
|
||||
const svalue *extra_sval,
|
||||
const extrinsic_state &ext_state,
|
||||
region_model_context *ctxt);
|
||||
|
||||
/* TODO: lose the pointer here (const-correctness issues?). */
|
||||
region_model *m_region_model;
|
||||
auto_delete_vec<sm_state_map> m_checker_states;
|
||||
|
@ -311,76 +271,14 @@ public:
|
|||
virtual bool on_state_change (const state_machine &sm,
|
||||
state_machine::state_t src_sm_val,
|
||||
state_machine::state_t dst_sm_val,
|
||||
tree dst_rep,
|
||||
svalue_id dst_origin_sid) = 0;
|
||||
const svalue *dst_sval,
|
||||
const svalue *dst_origin_sval) = 0;
|
||||
};
|
||||
|
||||
extern bool for_each_state_change (const program_state &src_state,
|
||||
const program_state &dst_state,
|
||||
const extrinsic_state &ext_state,
|
||||
state_change_visitor *visitor);
|
||||
|
||||
/* A class for recording "interesting" state changes.
|
||||
This is used for annotating edges in the GraphViz output of the
|
||||
exploded_graph, and for recording sm-state-changes, so that
|
||||
values that change aren't purged (to make it easier to generate
|
||||
state_change_event instances in the diagnostic_path). */
|
||||
|
||||
class state_change
|
||||
{
|
||||
public:
|
||||
struct sm_change
|
||||
{
|
||||
sm_change (int sm_idx,
|
||||
svalue_id new_sid,
|
||||
state_machine::state_t old_state,
|
||||
state_machine::state_t new_state)
|
||||
: m_sm_idx (sm_idx),
|
||||
m_new_sid (new_sid),
|
||||
m_old_state (old_state), m_new_state (new_state)
|
||||
{}
|
||||
|
||||
const state_machine &get_sm (const extrinsic_state &ext_state) const
|
||||
{
|
||||
return ext_state.get_sm (m_sm_idx);
|
||||
}
|
||||
|
||||
void dump (pretty_printer *pp, const extrinsic_state &ext_state) const;
|
||||
|
||||
void remap_svalue_ids (const svalue_id_map &map);
|
||||
int on_svalue_purge (svalue_id first_unused_sid);
|
||||
|
||||
void validate (const program_state &new_state,
|
||||
const extrinsic_state &ext_state) const;
|
||||
|
||||
int m_sm_idx;
|
||||
svalue_id m_new_sid;
|
||||
state_machine::state_t m_old_state;
|
||||
state_machine::state_t m_new_state;
|
||||
};
|
||||
|
||||
state_change ();
|
||||
state_change (const state_change &other);
|
||||
|
||||
void add_sm_change (int sm_idx,
|
||||
svalue_id new_sid,
|
||||
state_machine::state_t old_state,
|
||||
state_machine::state_t new_state);
|
||||
|
||||
bool affects_p (svalue_id sid) const;
|
||||
|
||||
void dump (pretty_printer *pp, const extrinsic_state &ext_state) const;
|
||||
void dump (const extrinsic_state &ext_state) const;
|
||||
|
||||
void remap_svalue_ids (const svalue_id_map &map);
|
||||
int on_svalue_purge (svalue_id first_unused_sid);
|
||||
|
||||
void validate (const program_state &new_state,
|
||||
const extrinsic_state &ext_state) const;
|
||||
|
||||
private:
|
||||
auto_vec<sm_change> m_sm_changes;
|
||||
};
|
||||
const program_state &dst_state,
|
||||
const extrinsic_state &ext_state,
|
||||
state_change_visitor *visitor);
|
||||
|
||||
} // namespace ana
|
||||
|
||||
|
|
352
gcc/analyzer/region-model-impl-calls.cc
Normal file
352
gcc/analyzer/region-model-impl-calls.cc
Normal file
|
@ -0,0 +1,352 @@
|
|||
/* Handling for the known behavior of various specific functions.
|
||||
Copyright (C) 2020 Free Software Foundation, Inc.
|
||||
Contributed by David Malcolm <dmalcolm@redhat.com>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>. */
|
||||
|
||||
#include "config.h"
|
||||
#include "system.h"
|
||||
#include "coretypes.h"
|
||||
#include "tree.h"
|
||||
#include "function.h"
|
||||
#include "basic-block.h"
|
||||
#include "gimple.h"
|
||||
#include "gimple-iterator.h"
|
||||
#include "diagnostic-core.h"
|
||||
#include "graphviz.h"
|
||||
#include "options.h"
|
||||
#include "cgraph.h"
|
||||
#include "tree-dfa.h"
|
||||
#include "stringpool.h"
|
||||
#include "convert.h"
|
||||
#include "target.h"
|
||||
#include "fold-const.h"
|
||||
#include "tree-pretty-print.h"
|
||||
#include "diagnostic-color.h"
|
||||
#include "diagnostic-metadata.h"
|
||||
#include "tristate.h"
|
||||
#include "bitmap.h"
|
||||
#include "selftest.h"
|
||||
#include "function.h"
|
||||
#include "analyzer/analyzer.h"
|
||||
#include "analyzer/analyzer-logging.h"
|
||||
#include "ordered-hash-map.h"
|
||||
#include "options.h"
|
||||
#include "cgraph.h"
|
||||
#include "cfg.h"
|
||||
#include "digraph.h"
|
||||
#include "analyzer/supergraph.h"
|
||||
#include "sbitmap.h"
|
||||
#include "analyzer/call-string.h"
|
||||
#include "analyzer/program-point.h"
|
||||
#include "analyzer/store.h"
|
||||
#include "analyzer/region-model.h"
|
||||
#include "gimple-pretty-print.h"
|
||||
|
||||
#if ENABLE_ANALYZER
|
||||
|
||||
namespace ana {
|
||||
|
||||
/* class call_details. */
|
||||
|
||||
/* call_details's ctor. */
|
||||
|
||||
call_details::call_details (const gcall *call, region_model *model,
|
||||
region_model_context *ctxt)
|
||||
: m_call (call), m_model (model), m_ctxt (ctxt),
|
||||
m_lhs_type (NULL_TREE), m_lhs_region (NULL)
|
||||
{
|
||||
m_lhs_type = NULL_TREE;
|
||||
if (tree lhs = gimple_call_lhs (call))
|
||||
{
|
||||
m_lhs_region = model->get_lvalue (lhs, ctxt);
|
||||
m_lhs_type = TREE_TYPE (lhs);
|
||||
}
|
||||
}
|
||||
|
||||
/* If the callsite has a left-hand-side region, set it to RESULT
|
||||
and return true.
|
||||
Otherwise do nothing and return false. */
|
||||
|
||||
bool
|
||||
call_details::maybe_set_lhs (const svalue *result) const
|
||||
{
|
||||
gcc_assert (result);
|
||||
if (m_lhs_region)
|
||||
{
|
||||
m_model->set_value (m_lhs_region, result, m_ctxt);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Get argument IDX at the callsite as a tree. */
|
||||
|
||||
tree
|
||||
call_details::get_arg_tree (unsigned idx) const
|
||||
{
|
||||
return gimple_call_arg (m_call, idx);
|
||||
}
|
||||
|
||||
/* Get argument IDX at the callsite as an svalue. */
|
||||
|
||||
const svalue *
|
||||
call_details::get_arg_svalue (unsigned idx) const
|
||||
{
|
||||
tree arg = get_arg_tree (idx);
|
||||
return m_model->get_rvalue (arg, m_ctxt);
|
||||
}
|
||||
|
||||
/* Dump a multiline representation of this call to PP. */
|
||||
|
||||
void
|
||||
call_details::dump_to_pp (pretty_printer *pp, bool simple) const
|
||||
{
|
||||
pp_string (pp, "gcall: ");
|
||||
pp_gimple_stmt_1 (pp, m_call, 0 /* spc */, TDF_NONE /* flags */);
|
||||
pp_newline (pp);
|
||||
pp_string (pp, "return region: ");
|
||||
if (m_lhs_region)
|
||||
m_lhs_region->dump_to_pp (pp, simple);
|
||||
else
|
||||
pp_string (pp, "NULL");
|
||||
pp_newline (pp);
|
||||
for (unsigned i = 0; i < gimple_call_num_args (m_call); i++)
|
||||
{
|
||||
const svalue *arg_sval = get_arg_svalue (i);
|
||||
pp_printf (pp, "arg %i: ", i);
|
||||
arg_sval->dump_to_pp (pp, simple);
|
||||
pp_newline (pp);
|
||||
}
|
||||
}
|
||||
|
||||
/* Dump a multiline representation of this call to stderr. */
|
||||
|
||||
DEBUG_FUNCTION void
|
||||
call_details::dump (bool simple) const
|
||||
{
|
||||
pretty_printer pp;
|
||||
pp_format_decoder (&pp) = default_tree_printer;
|
||||
pp_show_color (&pp) = pp_show_color (global_dc->printer);
|
||||
pp.buffer->stream = stderr;
|
||||
dump_to_pp (&pp, simple);
|
||||
pp_flush (&pp);
|
||||
}
|
||||
|
||||
/* Implementations of specific functions. */
|
||||
|
||||
/* Handle the on_call_pre part of "alloca". */
|
||||
|
||||
bool
|
||||
region_model::impl_call_alloca (const call_details &cd)
|
||||
{
|
||||
const svalue *size_sval = cd.get_arg_svalue (0);
|
||||
const region *new_reg = create_region_for_alloca (size_sval);
|
||||
const svalue *ptr_sval
|
||||
= m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
|
||||
cd.maybe_set_lhs (ptr_sval);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Handle a call to "__analyzer_describe".
|
||||
|
||||
Emit a warning describing the 2nd argument (which can be of any
|
||||
type), at the given verbosity level. This is for use when
|
||||
debugging, and may be of use in DejaGnu tests. */
|
||||
|
||||
void
|
||||
region_model::impl_call_analyzer_describe (const gcall *call,
|
||||
region_model_context *ctxt)
|
||||
{
|
||||
tree t_verbosity = gimple_call_arg (call, 0);
|
||||
tree t_val = gimple_call_arg (call, 1);
|
||||
const svalue *sval = get_rvalue (t_val, ctxt);
|
||||
bool simple = zerop (t_verbosity);
|
||||
label_text desc = sval->get_desc (simple);
|
||||
warning_at (call->location, 0, "svalue: %qs", desc.m_buffer);
|
||||
}
|
||||
|
||||
/* Handle a call to "__analyzer_eval" by evaluating the input
|
||||
and dumping as a dummy warning, so that test cases can use
|
||||
dg-warning to validate the result (and so unexpected warnings will
|
||||
lead to DejaGnu failures).
|
||||
Broken out as a subroutine to make it easier to put a breakpoint on it
|
||||
- though typically this doesn't help, as we have an SSA name as the arg,
|
||||
and what's more interesting is usually the def stmt for that name. */
|
||||
|
||||
void
|
||||
region_model::impl_call_analyzer_eval (const gcall *call,
|
||||
region_model_context *ctxt)
|
||||
{
|
||||
tree t_arg = gimple_call_arg (call, 0);
|
||||
tristate t = eval_condition (t_arg, NE_EXPR, integer_zero_node, ctxt);
|
||||
warning_at (call->location, 0, "%s", t.as_string ());
|
||||
}
|
||||
|
||||
/* Handle the on_call_pre part of "__builtin_expect" etc. */
|
||||
|
||||
bool
|
||||
region_model::impl_call_builtin_expect (const call_details &cd)
|
||||
{
|
||||
/* __builtin_expect's return value is its initial argument. */
|
||||
const svalue *sval = cd.get_arg_svalue (0);
|
||||
cd.maybe_set_lhs (sval);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Handle the on_call_pre part of "calloc". */
|
||||
|
||||
bool
|
||||
region_model::impl_call_calloc (const call_details &cd)
|
||||
{
|
||||
const svalue *nmemb_sval = cd.get_arg_svalue (0);
|
||||
const svalue *size_sval = cd.get_arg_svalue (1);
|
||||
/* TODO: check for overflow here? */
|
||||
const svalue *prod_sval
|
||||
= m_mgr->get_or_create_binop (size_type_node, MULT_EXPR,
|
||||
nmemb_sval, size_sval);
|
||||
const region *new_reg = create_region_for_heap_alloc (prod_sval);
|
||||
zero_fill_region (new_reg);
|
||||
if (cd.get_lhs_type ())
|
||||
{
|
||||
const svalue *ptr_sval
|
||||
= m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
|
||||
cd.maybe_set_lhs (ptr_sval);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Handle the on_call_post part of "free", after sm-handling.
|
||||
|
||||
If the ptr points to an underlying heap region, delete the region,
|
||||
poisoning pointers to it and regions within it.
|
||||
|
||||
We delay this until after sm-state has been updated so that the
|
||||
sm-handling can transition all of the various casts of the pointer
|
||||
to a "freed" state *before* we delete the related region here.
|
||||
|
||||
This has to be done here so that the sm-handling can use the fact
|
||||
that they point to the same region to establish that they are equal
|
||||
(in region_model::eval_condition_without_cm), and thus transition
|
||||
all pointers to the region to the "freed" state together, regardless
|
||||
of casts. */
|
||||
|
||||
void
|
||||
region_model::impl_call_free (const call_details &cd)
|
||||
{
|
||||
const svalue *ptr_sval = cd.get_arg_svalue (0);
|
||||
if (const region_svalue *ptr_to_region_sval
|
||||
= ptr_sval->dyn_cast_region_svalue ())
|
||||
{
|
||||
/* If the ptr points to an underlying heap region, delete it,
|
||||
poisoning pointers. */
|
||||
const region *freed_reg = ptr_to_region_sval->get_pointee ();
|
||||
unbind_region_and_descendents (freed_reg, POISON_KIND_FREED);
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle the on_call_pre part of "malloc". */
|
||||
|
||||
bool
|
||||
region_model::impl_call_malloc (const call_details &cd)
|
||||
{
|
||||
const svalue *size_sval = cd.get_arg_svalue (0);
|
||||
const region *new_reg = create_region_for_heap_alloc (size_sval);
|
||||
if (cd.get_lhs_type ())
|
||||
{
|
||||
const svalue *ptr_sval
|
||||
= m_mgr->get_ptr_svalue (cd.get_lhs_type (), new_reg);
|
||||
cd.maybe_set_lhs (ptr_sval);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Handle the on_call_pre part of "memset" and "__builtin_memset". */
|
||||
|
||||
bool
|
||||
region_model::impl_call_memset (const call_details &cd)
|
||||
{
|
||||
const svalue *dest_sval = cd.get_arg_svalue (0);
|
||||
const svalue *fill_value_sval = cd.get_arg_svalue (1);
|
||||
const svalue *num_bytes_sval = cd.get_arg_svalue (2);
|
||||
|
||||
const region *dest_reg = deref_rvalue (dest_sval, cd.get_arg_tree (0),
|
||||
cd.get_ctxt ());
|
||||
|
||||
if (tree num_bytes = num_bytes_sval->maybe_get_constant ())
|
||||
{
|
||||
/* "memset" of zero size is a no-op. */
|
||||
if (zerop (num_bytes))
|
||||
return true;
|
||||
|
||||
/* Set with known amount. */
|
||||
byte_size_t reg_size;
|
||||
if (dest_reg->get_byte_size (®_size))
|
||||
{
|
||||
/* Check for an exact size match. */
|
||||
if (reg_size == wi::to_offset (num_bytes))
|
||||
{
|
||||
if (tree cst = fill_value_sval->maybe_get_constant ())
|
||||
{
|
||||
if (zerop (cst))
|
||||
{
|
||||
zero_fill_region (dest_reg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Otherwise, mark region's contents as unknown. */
|
||||
mark_region_as_unknown (dest_reg);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Handle the on_call_pre part of "strlen".
|
||||
Return true if the LHS is updated. */
|
||||
|
||||
bool
|
||||
region_model::impl_call_strlen (const call_details &cd)
|
||||
{
|
||||
region_model_context *ctxt = cd.get_ctxt ();
|
||||
const svalue *arg_sval = cd.get_arg_svalue (0);
|
||||
const region *buf_reg = deref_rvalue (arg_sval, cd.get_arg_tree (0), ctxt);
|
||||
if (const string_region *str_reg
|
||||
= buf_reg->dyn_cast_string_region ())
|
||||
{
|
||||
tree str_cst = str_reg->get_string_cst ();
|
||||
/* TREE_STRING_LENGTH is sizeof, not strlen. */
|
||||
int sizeof_cst = TREE_STRING_LENGTH (str_cst);
|
||||
int strlen_cst = sizeof_cst - 1;
|
||||
if (cd.get_lhs_type ())
|
||||
{
|
||||
tree t_cst = build_int_cst (cd.get_lhs_type (), strlen_cst);
|
||||
const svalue *result_sval
|
||||
= m_mgr->get_or_create_constant_svalue (t_cst);
|
||||
cd.maybe_set_lhs (result_sval);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
/* Otherwise an unknown value. */
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ana
|
||||
|
||||
#endif /* #if ENABLE_ANALYZER */
|
1088
gcc/analyzer/region-model-manager.cc
Normal file
1088
gcc/analyzer/region-model-manager.cc
Normal file
File diff suppressed because it is too large
Load diff
280
gcc/analyzer/region-model-reachability.cc
Normal file
280
gcc/analyzer/region-model-reachability.cc
Normal file
|
@ -0,0 +1,280 @@
|
|||
/* Finding reachable regions and values.
|
||||
Copyright (C) 2020 Free Software Foundation, Inc.
|
||||
Contributed by David Malcolm <dmalcolm@redhat.com>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>. */
|
||||
|
||||
#include "config.h"
|
||||
#include "system.h"
|
||||
#include "coretypes.h"
|
||||
#include "tree.h"
|
||||
#include "function.h"
|
||||
#include "basic-block.h"
|
||||
#include "gimple.h"
|
||||
#include "gimple-iterator.h"
|
||||
#include "diagnostic-core.h"
|
||||
#include "graphviz.h"
|
||||
#include "options.h"
|
||||
#include "cgraph.h"
|
||||
#include "tree-dfa.h"
|
||||
#include "stringpool.h"
|
||||
#include "convert.h"
|
||||
#include "target.h"
|
||||
#include "fold-const.h"
|
||||
#include "tree-pretty-print.h"
|
||||
#include "tristate.h"
|
||||
#include "bitmap.h"
|
||||
#include "selftest.h"
|
||||
#include "function.h"
|
||||
#include "analyzer/analyzer.h"
|
||||
#include "analyzer/analyzer-logging.h"
|
||||
#include "ordered-hash-map.h"
|
||||
#include "options.h"
|
||||
#include "cgraph.h"
|
||||
#include "cfg.h"
|
||||
#include "digraph.h"
|
||||
#include "analyzer/call-string.h"
|
||||
#include "analyzer/program-point.h"
|
||||
#include "analyzer/store.h"
|
||||
#include "analyzer/region-model.h"
|
||||
#include "analyzer/region-model-reachability.h"
|
||||
|
||||
#if ENABLE_ANALYZER
|
||||
|
||||
namespace ana {
|
||||
|
||||
reachable_regions::reachable_regions (store *store,
|
||||
region_model_manager *mgr)
|
||||
: m_store (store), m_mgr (mgr),
|
||||
m_reachable_base_regs (), m_mutable_base_regs ()
|
||||
{
|
||||
}
|
||||
|
||||
/* Callback called for each cluster when initializing this object. */
|
||||
|
||||
void
|
||||
reachable_regions::init_cluster_cb (const region *base_reg,
|
||||
reachable_regions *this_ptr)
|
||||
{
|
||||
this_ptr->init_cluster (base_reg);
|
||||
}
|
||||
|
||||
/* Called for each cluster when initializing this object. */
|
||||
void
|
||||
reachable_regions::init_cluster (const region *base_reg)
|
||||
{
|
||||
/* Mark any globals as mutable (and traverse what they point to). */
|
||||
const region *parent = base_reg->get_parent_region ();
|
||||
gcc_assert (parent);
|
||||
if (parent->get_kind () == RK_GLOBALS)
|
||||
add (base_reg, true);
|
||||
|
||||
/* Mark any clusters that already escaped in previous unknown calls
|
||||
as mutable (and traverse what they currently point to). */
|
||||
if (m_store->escaped_p (base_reg))
|
||||
add (base_reg, true);
|
||||
|
||||
/* If BASE_REG is *INIT_VAL(REG) for some other REG, see if REG is
|
||||
unbound and untouched. If so, then add BASE_REG as a root. */
|
||||
if (const symbolic_region *sym_reg = base_reg->dyn_cast_symbolic_region ())
|
||||
{
|
||||
const svalue *ptr = sym_reg->get_pointer ();
|
||||
if (const initial_svalue *init_sval = ptr->dyn_cast_initial_svalue ())
|
||||
{
|
||||
const region *init_sval_reg = init_sval->get_region ();
|
||||
const region *other_base_reg = init_sval_reg->get_base_region ();
|
||||
const binding_cluster *other_cluster
|
||||
= m_store->get_cluster (other_base_reg);
|
||||
if (other_cluster == NULL
|
||||
|| !other_cluster->touched_p ())
|
||||
add (base_reg, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Lazily mark the cluster containing REG as being reachable, recursively
|
||||
adding clusters reachable from REG's cluster. */
|
||||
void
|
||||
reachable_regions::add (const region *reg, bool is_mutable)
|
||||
{
|
||||
gcc_assert (reg);
|
||||
|
||||
const region *base_reg = const_cast <region *> (reg->get_base_region ());
|
||||
gcc_assert (base_reg);
|
||||
|
||||
/* Bail out if this cluster is already in the sets at the IS_MUTABLE
|
||||
level of mutability. */
|
||||
if (!is_mutable && m_reachable_base_regs.contains (base_reg))
|
||||
return;
|
||||
m_reachable_base_regs.add (base_reg);
|
||||
|
||||
if (is_mutable)
|
||||
{
|
||||
if (m_mutable_base_regs.contains (base_reg))
|
||||
return;
|
||||
else
|
||||
m_mutable_base_regs.add (base_reg);
|
||||
}
|
||||
|
||||
/* Add values within the cluster. If any are pointers, add the pointee. */
|
||||
if (binding_cluster *bind_cluster = m_store->get_cluster (base_reg))
|
||||
bind_cluster->for_each_value (handle_sval_cb, this);
|
||||
else
|
||||
handle_sval (m_mgr->get_or_create_initial_value (base_reg));
|
||||
}
|
||||
|
||||
void
|
||||
reachable_regions::handle_sval_cb (const svalue *sval,
|
||||
reachable_regions *this_ptr)
|
||||
{
|
||||
this_ptr->handle_sval (sval);
|
||||
}
|
||||
|
||||
/* Add SVAL. If it is a pointer, add the pointed-to region. */
|
||||
|
||||
void
|
||||
reachable_regions::handle_sval (const svalue *sval)
|
||||
{
|
||||
m_reachable_svals.add (sval);
|
||||
if (const region_svalue *ptr = sval->dyn_cast_region_svalue ())
|
||||
{
|
||||
const region *pointee = ptr->get_pointee ();
|
||||
/* Use const-ness of pointer type to affect mutability. */
|
||||
bool ptr_is_mutable = true;
|
||||
if (ptr->get_type ()
|
||||
&& TREE_CODE (ptr->get_type ()) == POINTER_TYPE
|
||||
&& TYPE_READONLY (TREE_TYPE (ptr->get_type ())))
|
||||
{
|
||||
ptr_is_mutable = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_mutable_svals.add (sval);
|
||||
}
|
||||
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 ())
|
||||
{
|
||||
for (compound_svalue::iterator_t iter = compound_sval->begin ();
|
||||
iter != compound_sval->end (); ++iter)
|
||||
{
|
||||
const svalue *iter_sval = (*iter).second;
|
||||
handle_sval (iter_sval);
|
||||
}
|
||||
}
|
||||
if (const svalue *cast = sval->maybe_undo_cast ())
|
||||
handle_sval (cast);
|
||||
}
|
||||
|
||||
/* Add SVAL. If it is a pointer, add the pointed-to region.
|
||||
Use PARAM_TYPE for determining mutability. */
|
||||
|
||||
void
|
||||
reachable_regions::handle_parm (const svalue *sval, tree param_type)
|
||||
{
|
||||
bool is_mutable = true;
|
||||
if (param_type
|
||||
&& TREE_CODE (param_type) == POINTER_TYPE
|
||||
&& TYPE_READONLY (TREE_TYPE (param_type)))
|
||||
is_mutable = false;
|
||||
if (is_mutable)
|
||||
m_mutable_svals.add (sval);
|
||||
else
|
||||
m_reachable_svals.add (sval);
|
||||
if (const region_svalue *parm_ptr
|
||||
= sval->dyn_cast_region_svalue ())
|
||||
{
|
||||
const region *pointee_reg = parm_ptr->get_pointee ();
|
||||
add (pointee_reg, is_mutable);
|
||||
}
|
||||
}
|
||||
|
||||
/* Update m_store to mark the clusters that were found to be mutable
|
||||
as having escaped. */
|
||||
|
||||
void
|
||||
reachable_regions::mark_escaped_clusters ()
|
||||
{
|
||||
for (hash_set<const region *>::iterator iter = m_mutable_base_regs.begin ();
|
||||
iter != m_mutable_base_regs.end (); ++iter)
|
||||
{
|
||||
const region *base_reg = *iter;
|
||||
m_store->mark_as_escaped (base_reg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Dump a multiline representation of this object to PP. */
|
||||
|
||||
void
|
||||
reachable_regions::dump_to_pp (pretty_printer *pp) const
|
||||
{
|
||||
pp_string (pp, "reachable clusters: ");
|
||||
pp_newline (pp);
|
||||
for (hash_set<const region *>::iterator iter = m_reachable_base_regs.begin ();
|
||||
iter != m_reachable_base_regs.end (); ++iter)
|
||||
{
|
||||
pp_string (pp, " ");
|
||||
(*iter)->dump_to_pp (pp, true);
|
||||
pp_newline (pp);
|
||||
}
|
||||
pp_string (pp, "mutable clusters: ");
|
||||
pp_newline (pp);
|
||||
for (hash_set<const region *>::iterator iter = m_mutable_base_regs.begin ();
|
||||
iter != m_mutable_base_regs.end (); ++iter)
|
||||
{
|
||||
pp_string (pp, " ");
|
||||
(*iter)->dump_to_pp (pp, true);
|
||||
pp_newline (pp);
|
||||
}
|
||||
pp_string (pp, "reachable svals: ");
|
||||
pp_newline (pp);
|
||||
for (svalue_set::iterator iter = m_reachable_svals.begin ();
|
||||
iter != m_reachable_svals.end (); ++iter)
|
||||
{
|
||||
pp_string (pp, " ");
|
||||
(*iter)->dump_to_pp (pp, true);
|
||||
pp_newline (pp);
|
||||
}
|
||||
pp_string (pp, "mutable svals: ");
|
||||
pp_newline (pp);
|
||||
for (svalue_set::iterator iter = m_mutable_svals.begin ();
|
||||
iter != m_mutable_svals.end (); ++iter)
|
||||
{
|
||||
pp_string (pp, " ");
|
||||
(*iter)->dump_to_pp (pp, true);
|
||||
pp_newline (pp);
|
||||
}
|
||||
}
|
||||
|
||||
/* Dump a multiline representation of this object to stderr. */
|
||||
|
||||
DEBUG_FUNCTION void
|
||||
reachable_regions::dump () const
|
||||
{
|
||||
pretty_printer pp;
|
||||
pp_format_decoder (&pp) = default_tree_printer;
|
||||
pp_show_color (&pp) = pp_show_color (global_dc->printer);
|
||||
pp.buffer->stream = stderr;
|
||||
dump_to_pp (&pp);
|
||||
pp_flush (&pp);
|
||||
}
|
||||
|
||||
} // namespace ana
|
||||
|
||||
#endif /* #if ENABLE_ANALYZER */
|
117
gcc/analyzer/region-model-reachability.h
Normal file
117
gcc/analyzer/region-model-reachability.h
Normal file
|
@ -0,0 +1,117 @@
|
|||
/* Finding reachable regions and values.
|
||||
Copyright (C) 2020 Free Software Foundation, Inc.
|
||||
Contributed by David Malcolm <dmalcolm@redhat.com>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>. */
|
||||
|
||||
#ifndef GCC_ANALYZER_REGION_MODEL_REACHABILITY_H
|
||||
#define GCC_ANALYZER_REGION_MODEL_REACHABILITY_H
|
||||
|
||||
namespace ana {
|
||||
|
||||
/* A class for determining which regions and svalues are reachable.
|
||||
|
||||
Used by region_model::handle_unrecognized_call for keeping
|
||||
track of all regions that are reachable, and, of those, which are
|
||||
mutable.
|
||||
|
||||
Used by program_state::detect_leaks
|
||||
(via region_model::get_reachable_svalues) for detecting leaks. */
|
||||
|
||||
class reachable_regions
|
||||
{
|
||||
public:
|
||||
reachable_regions (store *store, region_model_manager *mgr);
|
||||
|
||||
/* Callback called for each cluster when initializing this object. */
|
||||
static void init_cluster_cb (const region *base_reg,
|
||||
reachable_regions *this_ptr);
|
||||
|
||||
/* Called for each cluster when initializing this object. */
|
||||
void init_cluster (const region *base_reg);
|
||||
|
||||
/* Lazily mark the cluster containing REG as being reachable, recursively
|
||||
adding clusters reachable from REG's cluster. */
|
||||
void add (const region *reg, bool is_mutable);
|
||||
|
||||
static void handle_sval_cb (const svalue *sval,
|
||||
reachable_regions *this_ptr);
|
||||
|
||||
/* Add SVAL. If it is a pointer, add the pointed-to region. */
|
||||
void handle_sval (const svalue *sval);
|
||||
|
||||
/* Add SVAL. If it is a pointer, add the pointed-to region.
|
||||
Use PARAM_TYPE for determining mutability. */
|
||||
void handle_parm (const svalue *sval, tree param_type);
|
||||
|
||||
/* Update the store to mark the clusters that were found to be mutable
|
||||
as having escaped. */
|
||||
void mark_escaped_clusters ();
|
||||
|
||||
/* Iteration over reachable base regions. */
|
||||
hash_set<const region *>::iterator begin ()
|
||||
{
|
||||
return m_reachable_base_regs.begin ();
|
||||
}
|
||||
hash_set<const region *>::iterator end ()
|
||||
{
|
||||
return m_reachable_base_regs.end ();
|
||||
}
|
||||
|
||||
svalue_set::iterator begin_reachable_svals ()
|
||||
{
|
||||
return m_reachable_svals.begin ();
|
||||
}
|
||||
svalue_set::iterator end_reachable_svals ()
|
||||
{
|
||||
return m_reachable_svals.end ();
|
||||
}
|
||||
svalue_set::iterator begin_mutable_svals ()
|
||||
{
|
||||
return m_mutable_svals.begin ();
|
||||
}
|
||||
svalue_set::iterator end_mutable_svals ()
|
||||
{
|
||||
return m_mutable_svals.end ();
|
||||
}
|
||||
|
||||
void dump_to_pp (pretty_printer *pp) const;
|
||||
|
||||
DEBUG_FUNCTION void dump () const;
|
||||
|
||||
private:
|
||||
store *m_store;
|
||||
region_model_manager *m_mgr;
|
||||
|
||||
/* The base regions already seen. */
|
||||
hash_set<const region *> m_reachable_base_regs;
|
||||
|
||||
/* The base regions that can be changed (accessed via non-const pointers). */
|
||||
hash_set<const region *> m_mutable_base_regs;
|
||||
|
||||
/* svalues that were passed as const pointers, so e.g. couldn't have
|
||||
been freed (but could have e.g. had "close" called on them if an
|
||||
int file-descriptor). */
|
||||
svalue_set m_reachable_svals;
|
||||
/* svalues that were passed as non-const pointers, so e.g. could have
|
||||
been freed. */
|
||||
svalue_set m_mutable_svals;
|
||||
};
|
||||
|
||||
} // namespace ana
|
||||
|
||||
#endif /* GCC_ANALYZER_REGION_MODEL_REACHABILITY_H */
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
1064
gcc/analyzer/region.cc
Normal file
1064
gcc/analyzer/region.cc
Normal file
File diff suppressed because it is too large
Load diff
|
@ -36,6 +36,12 @@ along with GCC; see the file COPYING3. If not see
|
|||
#include "analyzer/pending-diagnostic.h"
|
||||
#include "analyzer/function-set.h"
|
||||
#include "analyzer/analyzer-selftests.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"
|
||||
|
||||
#if ENABLE_ANALYZER
|
||||
|
||||
|
@ -52,6 +58,17 @@ public:
|
|||
|
||||
bool inherited_state_p () const FINAL OVERRIDE { return false; }
|
||||
|
||||
state_machine::state_t
|
||||
get_default_state (const svalue *sval) const FINAL OVERRIDE
|
||||
{
|
||||
if (tree cst = sval->maybe_get_constant ())
|
||||
{
|
||||
if (zerop (cst))
|
||||
return m_null;
|
||||
}
|
||||
return m_start;
|
||||
}
|
||||
|
||||
bool on_stmt (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt) const FINAL OVERRIDE;
|
||||
|
@ -310,10 +327,7 @@ fileptr_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
{
|
||||
tree lhs = gimple_call_lhs (call);
|
||||
if (lhs)
|
||||
{
|
||||
lhs = sm_ctxt->get_readable_tree (lhs);
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
|
||||
}
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
|
||||
else
|
||||
{
|
||||
/* TODO: report leak. */
|
||||
|
@ -324,7 +338,7 @@ fileptr_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
if (is_named_call_p (callee_fndecl, "fclose", call, 1))
|
||||
{
|
||||
tree arg = gimple_call_arg (call, 0);
|
||||
arg = sm_ctxt->get_readable_tree (arg);
|
||||
tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
|
||||
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_start, m_closed);
|
||||
|
||||
|
@ -335,7 +349,7 @@ fileptr_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
sm_ctxt->on_transition (node, stmt , arg, m_nonnull, m_closed);
|
||||
|
||||
sm_ctxt->warn_for_state (node, stmt, arg, m_closed,
|
||||
new double_fclose (*this, arg));
|
||||
new double_fclose (*this, diag_arg));
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_closed, m_stop);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -35,6 +35,12 @@ along with GCC; see the file COPYING3. If not see
|
|||
#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"
|
||||
|
||||
#if ENABLE_ANALYZER
|
||||
|
||||
|
@ -53,6 +59,23 @@ public:
|
|||
|
||||
bool inherited_state_p () const FINAL OVERRIDE { return false; }
|
||||
|
||||
state_machine::state_t
|
||||
get_default_state (const svalue *sval) const FINAL OVERRIDE
|
||||
{
|
||||
if (tree cst = sval->maybe_get_constant ())
|
||||
{
|
||||
if (zerop (cst))
|
||||
return m_null;
|
||||
}
|
||||
if (const region_svalue *ptr = sval->dyn_cast_region_svalue ())
|
||||
{
|
||||
const region *reg = ptr->get_pointee ();
|
||||
if (reg->get_kind () == RK_STRING)
|
||||
return m_non_heap;
|
||||
}
|
||||
return m_start;
|
||||
}
|
||||
|
||||
bool on_stmt (sm_context *sm_ctxt,
|
||||
const supernode *node,
|
||||
const gimple *stmt) const FINAL OVERRIDE;
|
||||
|
@ -72,6 +95,9 @@ public:
|
|||
bool can_purge_p (state_t s) const FINAL OVERRIDE;
|
||||
pending_diagnostic *on_leak (tree var) const FINAL OVERRIDE;
|
||||
|
||||
bool reset_when_passed_to_unknown_fn_p (state_t s,
|
||||
bool is_mutable) const FINAL OVERRIDE;
|
||||
|
||||
/* Start state. */
|
||||
state_t m_start;
|
||||
|
||||
|
@ -127,16 +153,34 @@ public:
|
|||
return label_text::borrow ("allocated here");
|
||||
if (change.m_old_state == m_sm.m_unchecked
|
||||
&& change.m_new_state == m_sm.m_nonnull)
|
||||
return change.formatted_print ("assuming %qE is non-NULL",
|
||||
change.m_expr);
|
||||
{
|
||||
if (change.m_expr)
|
||||
return change.formatted_print ("assuming %qE is non-NULL",
|
||||
change.m_expr);
|
||||
else
|
||||
return change.formatted_print ("assuming %qs is non-NULL",
|
||||
"<unknown>");
|
||||
}
|
||||
if (change.m_new_state == m_sm.m_null)
|
||||
{
|
||||
if (change.m_old_state == m_sm.m_unchecked)
|
||||
return change.formatted_print ("assuming %qE is NULL",
|
||||
change.m_expr);
|
||||
{
|
||||
if (change.m_expr)
|
||||
return change.formatted_print ("assuming %qE is NULL",
|
||||
change.m_expr);
|
||||
else
|
||||
return change.formatted_print ("assuming %qs is NULL",
|
||||
"<unknown>");
|
||||
}
|
||||
else
|
||||
return change.formatted_print ("%qE is NULL",
|
||||
change.m_expr);
|
||||
{
|
||||
if (change.m_expr)
|
||||
return change.formatted_print ("%qE is NULL",
|
||||
change.m_expr);
|
||||
else
|
||||
return change.formatted_print ("%qs is NULL",
|
||||
"<unknown>");
|
||||
}
|
||||
}
|
||||
|
||||
return label_text ();
|
||||
|
@ -406,9 +450,15 @@ public:
|
|||
auto_diagnostic_group d;
|
||||
diagnostic_metadata m;
|
||||
m.add_cwe (690);
|
||||
bool warned = warning_meta (rich_loc, m, OPT_Wanalyzer_null_argument,
|
||||
"use of NULL %qE where non-null expected",
|
||||
m_arg);
|
||||
|
||||
bool warned;
|
||||
if (zerop (m_arg))
|
||||
warned = warning_meta (rich_loc, m, OPT_Wanalyzer_null_argument,
|
||||
"use of NULL where non-null expected");
|
||||
else
|
||||
warned = warning_meta (rich_loc, m, OPT_Wanalyzer_null_argument,
|
||||
"use of NULL %qE where non-null expected",
|
||||
m_arg);
|
||||
if (warned)
|
||||
inform_nonnull_attribute (m_fndecl, m_arg_idx);
|
||||
return warned;
|
||||
|
@ -416,9 +466,13 @@ public:
|
|||
|
||||
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
|
||||
{
|
||||
return ev.formatted_print ("argument %u (%qE) NULL"
|
||||
" where non-null expected",
|
||||
m_arg_idx + 1, ev.m_expr);
|
||||
if (zerop (ev.m_expr))
|
||||
return ev.formatted_print ("argument %u NULL where non-null expected",
|
||||
m_arg_idx + 1);
|
||||
else
|
||||
return ev.formatted_print ("argument %u (%qE) NULL"
|
||||
" where non-null expected",
|
||||
m_arg_idx + 1, ev.m_expr);
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -480,8 +534,12 @@ public:
|
|||
{
|
||||
diagnostic_metadata m;
|
||||
m.add_cwe (401);
|
||||
return warning_meta (rich_loc, m, OPT_Wanalyzer_malloc_leak,
|
||||
"leak of %qE", m_arg);
|
||||
if (m_arg)
|
||||
return warning_meta (rich_loc, m, OPT_Wanalyzer_malloc_leak,
|
||||
"leak of %qE", m_arg);
|
||||
else
|
||||
return warning_meta (rich_loc, m, OPT_Wanalyzer_malloc_leak,
|
||||
"leak of %qs", "<unknown>");
|
||||
}
|
||||
|
||||
label_text describe_state_change (const evdesc::state_change &change)
|
||||
|
@ -497,11 +555,22 @@ public:
|
|||
|
||||
label_text describe_final_event (const evdesc::final_event &ev) FINAL OVERRIDE
|
||||
{
|
||||
if (m_malloc_event.known_p ())
|
||||
return ev.formatted_print ("%qE leaks here; was allocated at %@",
|
||||
ev.m_expr, &m_malloc_event);
|
||||
if (ev.m_expr)
|
||||
{
|
||||
if (m_malloc_event.known_p ())
|
||||
return ev.formatted_print ("%qE leaks here; was allocated at %@",
|
||||
ev.m_expr, &m_malloc_event);
|
||||
else
|
||||
return ev.formatted_print ("%qE leaks here", ev.m_expr);
|
||||
}
|
||||
else
|
||||
return ev.formatted_print ("%qE leaks here", ev.m_expr);
|
||||
{
|
||||
if (m_malloc_event.known_p ())
|
||||
return ev.formatted_print ("%qs leaks here; was allocated at %@",
|
||||
"<unknown>", &m_malloc_event);
|
||||
else
|
||||
return ev.formatted_print ("%qs leaks here", "<unknown>");
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -618,10 +687,7 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
{
|
||||
tree lhs = gimple_call_lhs (call);
|
||||
if (lhs)
|
||||
{
|
||||
lhs = sm_ctxt->get_readable_tree (lhs);
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
|
||||
}
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked);
|
||||
else
|
||||
{
|
||||
/* TODO: report leak. */
|
||||
|
@ -634,10 +700,7 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
{
|
||||
tree lhs = gimple_call_lhs (call);
|
||||
if (lhs)
|
||||
{
|
||||
lhs = sm_ctxt->get_readable_tree (lhs);
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
|
||||
}
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -646,8 +709,7 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
|| is_named_call_p (callee_fndecl, "__builtin_free", call, 1))
|
||||
{
|
||||
tree arg = gimple_call_arg (call, 0);
|
||||
|
||||
arg = sm_ctxt->get_readable_tree (arg);
|
||||
tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
|
||||
|
||||
/* start/unchecked/nonnull -> freed. */
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_start, m_freed);
|
||||
|
@ -659,12 +721,12 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
|
||||
/* freed -> stop, with warning. */
|
||||
sm_ctxt->warn_for_state (node, stmt, arg, m_freed,
|
||||
new double_free (*this, arg));
|
||||
new double_free (*this, diag_arg));
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_freed, m_stop);
|
||||
|
||||
/* non-heap -> stop, with warning. */
|
||||
sm_ctxt->warn_for_state (node, stmt, arg, m_non_heap,
|
||||
new free_of_non_heap (*this, arg));
|
||||
new free_of_non_heap (*this, diag_arg));
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_non_heap, m_stop);
|
||||
return true;
|
||||
}
|
||||
|
@ -685,15 +747,17 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
if (bitmap_empty_p (nonnull_args)
|
||||
|| bitmap_bit_p (nonnull_args, i))
|
||||
{
|
||||
tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
|
||||
sm_ctxt->warn_for_state
|
||||
(node, stmt, arg, m_unchecked,
|
||||
new possible_null_arg (*this, arg, callee_fndecl, i));
|
||||
new possible_null_arg (*this, diag_arg, callee_fndecl,
|
||||
i));
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_unchecked,
|
||||
m_nonnull);
|
||||
|
||||
sm_ctxt->warn_for_state
|
||||
(node, stmt, arg, m_null,
|
||||
new null_arg (*this, arg, callee_fndecl, i));
|
||||
new null_arg (*this, diag_arg, callee_fndecl, i));
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
|
||||
}
|
||||
}
|
||||
|
@ -702,10 +766,15 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
}
|
||||
}
|
||||
|
||||
if (tree lhs = is_zero_assignment (stmt))
|
||||
if (tree lhs = sm_ctxt->is_zero_assignment (stmt))
|
||||
if (any_pointer_p (lhs))
|
||||
on_zero_assignment (sm_ctxt, node, stmt,lhs);
|
||||
|
||||
/* If we have "LHS = &EXPR;" and EXPR is something other than a MEM_REF,
|
||||
transition LHS from start to non_heap.
|
||||
Doing it for ADDR_EXPR(MEM_REF()) is likely wrong, and can lead to
|
||||
unbounded chains of unmergeable sm-state on pointer arithmetic in loops
|
||||
when optimization is enabled. */
|
||||
if (const gassign *assign_stmt = dyn_cast <const gassign *> (stmt))
|
||||
{
|
||||
enum tree_code op = gimple_assign_rhs_code (assign_stmt);
|
||||
|
@ -714,8 +783,9 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
tree lhs = gimple_assign_lhs (assign_stmt);
|
||||
if (lhs)
|
||||
{
|
||||
lhs = sm_ctxt->get_readable_tree (lhs);
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
|
||||
tree addr_expr = gimple_assign_rhs1 (assign_stmt);
|
||||
if (TREE_CODE (TREE_OPERAND (addr_expr, 0)) != MEM_REF)
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_start, m_non_heap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -732,18 +802,18 @@ malloc_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
if (TREE_CODE (op) == MEM_REF)
|
||||
{
|
||||
tree arg = TREE_OPERAND (op, 0);
|
||||
arg = sm_ctxt->get_readable_tree (arg);
|
||||
tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
|
||||
|
||||
sm_ctxt->warn_for_state (node, stmt, arg, m_unchecked,
|
||||
new possible_null_deref (*this, arg));
|
||||
new possible_null_deref (*this, diag_arg));
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_unchecked, m_nonnull);
|
||||
|
||||
sm_ctxt->warn_for_state (node, stmt, arg, m_null,
|
||||
new null_deref (*this, arg));
|
||||
new null_deref (*this, diag_arg));
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_null, m_stop);
|
||||
|
||||
sm_ctxt->warn_for_state (node, stmt, arg, m_freed,
|
||||
new use_after_free (*this, arg));
|
||||
new use_after_free (*this, diag_arg));
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_freed, m_stop);
|
||||
}
|
||||
}
|
||||
|
@ -818,6 +888,22 @@ malloc_state_machine::on_leak (tree var) const
|
|||
return new malloc_leak (*this, var);
|
||||
}
|
||||
|
||||
/* Implementation of state_machine::reset_when_passed_to_unknown_fn_p vfunc
|
||||
for malloc_state_machine. */
|
||||
|
||||
bool
|
||||
malloc_state_machine::reset_when_passed_to_unknown_fn_p (state_t s,
|
||||
bool is_mutable) const
|
||||
{
|
||||
/* An on-stack ptr doesn't stop being stack-allocated when passed to an
|
||||
unknown fn. */
|
||||
if (s == m_non_heap)
|
||||
return false;
|
||||
|
||||
/* Otherwise, pointers passed as non-const can be freed. */
|
||||
return is_mutable;
|
||||
}
|
||||
|
||||
/* Shared logic for handling GIMPLE_ASSIGNs and GIMPLE_PHIs that
|
||||
assign zero to LHS. */
|
||||
|
||||
|
|
|
@ -177,8 +177,9 @@ sensitive_state_machine::warn_for_any_exposure (sm_context *sm_ctxt,
|
|||
const gimple *stmt,
|
||||
tree arg) const
|
||||
{
|
||||
tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
|
||||
sm_ctxt->warn_for_state (node, stmt, arg, m_sensitive,
|
||||
new exposure_through_output_file (*this, arg));
|
||||
new exposure_through_output_file (*this, diag_arg));
|
||||
}
|
||||
|
||||
/* Implementation of state_machine::on_stmt vfunc for
|
||||
|
|
|
@ -41,6 +41,9 @@ along with GCC; see the file COPYING3. If not see
|
|||
#include "tristate.h"
|
||||
#include "ordered-hash-map.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"
|
||||
|
@ -49,8 +52,6 @@ along with GCC; see the file COPYING3. If not see
|
|||
#include "gimple-iterator.h"
|
||||
#include "cgraph.h"
|
||||
#include "analyzer/supergraph.h"
|
||||
#include "analyzer/call-string.h"
|
||||
#include "analyzer/program-point.h"
|
||||
#include "alloc-pool.h"
|
||||
#include "fibonacci_heap.h"
|
||||
#include "analyzer/diagnostic-manager.h"
|
||||
|
@ -157,8 +158,7 @@ public:
|
|||
if (change.is_global_p ()
|
||||
&& change.m_new_state == m_sm.m_in_signal_handler)
|
||||
{
|
||||
function *handler
|
||||
= change.m_event.m_dst_state.m_region_model->get_current_function ();
|
||||
function *handler = change.m_event.get_dest_function ();
|
||||
return change.formatted_print ("registering %qD as signal handler",
|
||||
handler->decl);
|
||||
}
|
||||
|
@ -208,8 +208,9 @@ static void
|
|||
update_model_for_signal_handler (region_model *model,
|
||||
function *handler_fun)
|
||||
{
|
||||
gcc_assert (model);
|
||||
/* Purge all state within MODEL. */
|
||||
*model = region_model ();
|
||||
*model = region_model (model->get_manager ());
|
||||
model->push_frame (handler_fun, NULL, NULL);
|
||||
}
|
||||
|
||||
|
@ -273,9 +274,9 @@ public:
|
|||
|
||||
exploded_node *dst_enode = eg->get_or_create_node (entering_handler,
|
||||
state_entering_handler,
|
||||
NULL);
|
||||
src_enode);
|
||||
if (dst_enode)
|
||||
eg->add_edge (src_enode, dst_enode, NULL, state_change (),
|
||||
eg->add_edge (src_enode, dst_enode, NULL, /*state_change (),*/
|
||||
new signal_delivery_edge_info_t ());
|
||||
}
|
||||
|
||||
|
|
|
@ -208,7 +208,6 @@ taint_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
if (is_named_call_p (callee_fndecl, "fread", call, 4))
|
||||
{
|
||||
tree arg = gimple_call_arg (call, 0);
|
||||
arg = sm_ctxt->get_readable_tree (arg);
|
||||
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_start, m_tainted);
|
||||
|
||||
|
@ -231,7 +230,7 @@ taint_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
if (op == ARRAY_REF)
|
||||
{
|
||||
tree arg = TREE_OPERAND (rhs1, 1);
|
||||
arg = sm_ctxt->get_readable_tree (arg);
|
||||
tree diag_arg = sm_ctxt->get_diagnostic_tree (arg);
|
||||
|
||||
/* Unsigned types have an implicit lower bound. */
|
||||
bool is_unsigned = false;
|
||||
|
@ -241,14 +240,14 @@ taint_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
/* Complain about missing bounds. */
|
||||
sm_ctxt->warn_for_state
|
||||
(node, stmt, arg, m_tainted,
|
||||
new tainted_array_index (*this, arg,
|
||||
new tainted_array_index (*this, diag_arg,
|
||||
is_unsigned
|
||||
? BOUNDS_LOWER : BOUNDS_NONE));
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_tainted, m_stop);
|
||||
|
||||
/* Complain about missing upper bound. */
|
||||
sm_ctxt->warn_for_state (node, stmt, arg, m_has_lb,
|
||||
new tainted_array_index (*this, arg,
|
||||
new tainted_array_index (*this, diag_arg,
|
||||
BOUNDS_LOWER));
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_has_lb, m_stop);
|
||||
|
||||
|
@ -256,7 +255,7 @@ taint_state_machine::on_stmt (sm_context *sm_ctxt,
|
|||
if (!is_unsigned)
|
||||
{
|
||||
sm_ctxt->warn_for_state (node, stmt, arg, m_has_ub,
|
||||
new tainted_array_index (*this, arg,
|
||||
new tainted_array_index (*this, diag_arg,
|
||||
BOUNDS_UPPER));
|
||||
sm_ctxt->on_transition (node, stmt, arg, m_has_ub, m_stop);
|
||||
}
|
||||
|
|
|
@ -35,24 +35,7 @@ along with GCC; see the file COPYING3. If not see
|
|||
|
||||
#if ENABLE_ANALYZER
|
||||
|
||||
/* If STMT is an assignment from zero, return the LHS. */
|
||||
|
||||
tree
|
||||
is_zero_assignment (const gimple *stmt)
|
||||
{
|
||||
const gassign *assign_stmt = dyn_cast <const gassign *> (stmt);
|
||||
if (!assign_stmt)
|
||||
return NULL_TREE;
|
||||
|
||||
enum tree_code op = gimple_assign_rhs_code (assign_stmt);
|
||||
if (TREE_CODE_CLASS (op) != tcc_constant)
|
||||
return NULL_TREE;
|
||||
|
||||
if (!zerop (gimple_assign_rhs1 (assign_stmt)))
|
||||
return NULL_TREE;
|
||||
|
||||
return gimple_assign_lhs (assign_stmt);
|
||||
}
|
||||
namespace ana {
|
||||
|
||||
/* Return true if VAR has pointer or reference type. */
|
||||
|
||||
|
@ -62,8 +45,6 @@ any_pointer_p (tree var)
|
|||
return POINTER_TYPE_P (TREE_TYPE (var));
|
||||
}
|
||||
|
||||
namespace ana {
|
||||
|
||||
/* Add a state with name NAME to this state_machine.
|
||||
The string is required to outlive the state_machine.
|
||||
|
||||
|
|
|
@ -23,15 +23,14 @@ along with GCC; see the file COPYING3. If not see
|
|||
|
||||
/* Utility functions for use by state machines. */
|
||||
|
||||
extern tree is_zero_assignment (const gimple *stmt);
|
||||
extern bool any_pointer_p (tree var);
|
||||
|
||||
namespace ana {
|
||||
|
||||
class state_machine;
|
||||
class sm_context;
|
||||
class pending_diagnostic;
|
||||
|
||||
extern bool any_pointer_p (tree var);
|
||||
|
||||
/* An abstract base class for a state machine describing an API.
|
||||
A mapping from state IDs to names, and various virtual functions
|
||||
for pattern-matching on statements. */
|
||||
|
@ -53,6 +52,11 @@ public:
|
|||
within a heap-allocated struct. */
|
||||
virtual bool inherited_state_p () const = 0;
|
||||
|
||||
virtual state_machine::state_t get_default_state (const svalue *) const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *get_name () const { return m_name; }
|
||||
|
||||
const char *get_state_name (state_t s) const;
|
||||
|
@ -87,6 +91,19 @@ public:
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/* Return true if S should be reset to "start" for values passed (or reachable
|
||||
from) calls to unknown functions. IS_MUTABLE is true for pointers as
|
||||
non-const, false if only passed as const-pointers.
|
||||
|
||||
For example, in sm-malloc.cc, an on-stack ptr doesn't stop being
|
||||
stack-allocated when passed to an unknown fn, but a malloc-ed pointer
|
||||
could be freed when passed to an unknown fn (unless passed as "const"). */
|
||||
virtual bool reset_when_passed_to_unknown_fn_p (state_t s ATTRIBUTE_UNUSED,
|
||||
bool is_mutable) const
|
||||
{
|
||||
return is_mutable;
|
||||
}
|
||||
|
||||
void validate (state_t s) const;
|
||||
|
||||
void dump_to_pp (pretty_printer *pp) const;
|
||||
|
@ -154,7 +171,12 @@ public:
|
|||
tree var, state_machine::state_t state,
|
||||
pending_diagnostic *d) = 0;
|
||||
|
||||
virtual tree get_readable_tree (tree expr)
|
||||
/* For use when generating trees when creating pending_diagnostics, so that
|
||||
rather than e.g.
|
||||
"double-free of '<unknown>'"
|
||||
we can print:
|
||||
"double-free of 'inbuf.data'". */
|
||||
virtual tree get_diagnostic_tree (tree expr)
|
||||
{
|
||||
return expr;
|
||||
}
|
||||
|
@ -166,6 +188,11 @@ public:
|
|||
a signal handler. */
|
||||
virtual void on_custom_transition (custom_transition *transition) = 0;
|
||||
|
||||
/* If STMT is an assignment known to assign zero to its LHS, return
|
||||
the LHS.
|
||||
Otherwise return NULL_TREE. */
|
||||
virtual tree is_zero_assignment (const gimple *stmt) = 0;
|
||||
|
||||
protected:
|
||||
sm_context (int sm_idx, const state_machine &sm)
|
||||
: m_sm_idx (sm_idx), m_sm (sm) {}
|
||||
|
|
2008
gcc/analyzer/store.cc
Normal file
2008
gcc/analyzer/store.cc
Normal file
File diff suppressed because it is too large
Load diff
613
gcc/analyzer/store.h
Normal file
613
gcc/analyzer/store.h
Normal file
|
@ -0,0 +1,613 @@
|
|||
/* Classes for modeling the state of memory.
|
||||
Copyright (C) 2020 Free Software Foundation, Inc.
|
||||
Contributed by David Malcolm <dmalcolm@redhat.com>.
|
||||
|
||||
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
|
||||
<http://www.gnu.org/licenses/>. */
|
||||
|
||||
#ifndef GCC_ANALYZER_STORE_H
|
||||
#define GCC_ANALYZER_STORE_H
|
||||
|
||||
/* Implementation of the region-based ternary model described in:
|
||||
"A Memory Model for Static Analysis of C Programs"
|
||||
(Zhongxing Xu, Ted Kremenek, and Jian Zhang)
|
||||
http://lcs.ios.ac.cn/~xuzb/canalyze/memmodel.pdf */
|
||||
|
||||
/* The store models memory as a collection of "clusters", where regions
|
||||
are partitioned into clusters via their base region.
|
||||
|
||||
For example, given:
|
||||
int a, b, c;
|
||||
struct coord { double x; double y; } verts[3];
|
||||
then "verts[0].y" and "verts[1].x" both have "verts" as their base region.
|
||||
Each of a, b, c, and verts will have their own clusters, so that we
|
||||
know that writes to e.g. "verts[1].x".don't affect e.g. "a".
|
||||
|
||||
Within each cluster we store a map of bindings to values, where the
|
||||
binding keys can be either concrete or symbolic.
|
||||
|
||||
Concrete bindings affect a specific range of bits relative to the start
|
||||
of the base region of the cluster, whereas symbolic bindings affect
|
||||
a specific subregion within the cluster.
|
||||
|
||||
Consider (from the symbolic-1.c testcase):
|
||||
|
||||
char arr[1024];
|
||||
arr[2] = a; (1)
|
||||
arr[3] = b; (2)
|
||||
After (1) and (2), the cluster for "arr" has concrete bindings
|
||||
for bits 16-23 and for bits 24-31, with svalues "INIT_VAL(a)"
|
||||
and "INIT_VAL(b)" respectively:
|
||||
cluster: {bits 16-23: "INIT_VAL(a)",
|
||||
bits 24-31: "INIT_VAL(b)";
|
||||
flags: {}}
|
||||
Attempting to query unbound subregions e.g. arr[4] will
|
||||
return "UNINITIALIZED".
|
||||
"a" and "b" are each in their own clusters, with no explicit
|
||||
bindings, and thus implicitly have value INIT_VAL(a) and INIT_VAL(b).
|
||||
|
||||
arr[3] = c; (3)
|
||||
After (3), the concrete binding for bits 24-31 is replaced with the
|
||||
svalue "INIT_VAL(c)":
|
||||
cluster: {bits 16-23: "INIT_VAL(a)", (from before)
|
||||
bits 24-31: "INIT_VAL(c)"; (updated)
|
||||
flags: {}}
|
||||
|
||||
arr[i] = d; (4)
|
||||
After (4), we lose the concrete bindings and replace them with a
|
||||
symbolic binding for "arr[i]", with svalue "INIT_VAL(d)". We also
|
||||
mark the cluster as having been "symbolically touched": future
|
||||
attempts to query the values of subregions other than "arr[i]",
|
||||
such as "arr[3]" are "UNKNOWN", since we don't know if the write
|
||||
to arr[i] affected them.
|
||||
cluster: {symbolic_key(arr[i]): "INIT_VAL(d)";
|
||||
flags: {TOUCHED}}
|
||||
|
||||
arr[j] = e; (5)
|
||||
After (5), we lose the symbolic binding for "arr[i]" since we could
|
||||
have overwritten it, and add a symbolic binding for "arr[j]".
|
||||
cluster: {symbolic_key(arr[j]): "INIT_VAL(d)"; (different symbolic
|
||||
flags: {TOUCHED}} binding)
|
||||
|
||||
arr[3] = f; (6)
|
||||
After (6), we lose the symbolic binding for "arr[j]" since we could
|
||||
have overwritten it, and gain a concrete binding for bits 24-31
|
||||
again, this time with svalue "INIT_VAL(e)":
|
||||
cluster: {bits 24-31: "INIT_VAL(d)";
|
||||
flags: {TOUCHED}}
|
||||
The cluster is still flagged as touched, so that we know that
|
||||
accesses to other elements are "UNKNOWN" rather than
|
||||
"UNINITIALIZED".
|
||||
|
||||
Handling symbolic regions requires us to handle aliasing.
|
||||
|
||||
In the first example above, each of a, b, c and verts are non-symbolic
|
||||
base regions and so their clusters are "concrete clusters", whereas given:
|
||||
struct coord *p, *q;
|
||||
then "*p" and "*q" are symbolic base regions, and thus "*p" and "*q"
|
||||
have "symbolic clusters".
|
||||
|
||||
In the above, "verts[i].x" will have a symbolic *binding* within a
|
||||
concrete cluster for "verts", whereas "*p" is a symbolic *cluster*.
|
||||
|
||||
Writes to concrete clusters can't affect other concrete clusters,
|
||||
but can affect symbolic clusters; e.g. after:
|
||||
verts[0].x = 42;
|
||||
we bind 42 in the cluster for "verts", but the clusters for "b" and "c"
|
||||
can't be affected. Any symbolic clusters for *p and for *q can be
|
||||
affected, *p and *q could alias verts.
|
||||
|
||||
Writes to a symbolic cluster can affect other clusters, both
|
||||
concrete and symbolic; e.g. after:
|
||||
p->x = 17;
|
||||
we bind 17 within the cluster for "*p". The concrete clusters for a, b,
|
||||
c, and verts could be affected, depending on whether *p aliases them.
|
||||
Similarly, the symbolic cluster to *q could be affected. */
|
||||
|
||||
namespace ana {
|
||||
|
||||
class concrete_binding;
|
||||
|
||||
/* An enum for discriminating between "direct" vs "default" levels of
|
||||
mapping. */
|
||||
|
||||
enum binding_kind
|
||||
{
|
||||
/* Special-case value for hash support.
|
||||
This is the initial entry, so that hash traits can have
|
||||
empty_zero_p = true. */
|
||||
BK_empty = 0,
|
||||
|
||||
/* Special-case value for hash support. */
|
||||
BK_deleted,
|
||||
|
||||
/* The normal kind of mapping. */
|
||||
BK_direct,
|
||||
|
||||
/* A lower-priority kind of mapping, for use when inheriting
|
||||
default values from a parent region. */
|
||||
BK_default
|
||||
};
|
||||
|
||||
extern const char *binding_kind_to_string (enum binding_kind kind);
|
||||
|
||||
/* Abstract base class for describing ranges of bits within a binding_map
|
||||
that can have svalues bound to them. */
|
||||
|
||||
class binding_key
|
||||
{
|
||||
public:
|
||||
virtual ~binding_key () {}
|
||||
virtual bool concrete_p () const = 0;
|
||||
bool symbolic_p () const { return !concrete_p (); }
|
||||
|
||||
static const binding_key *make (store_manager *mgr, const region *r,
|
||||
enum binding_kind kind);
|
||||
|
||||
virtual void dump_to_pp (pretty_printer *pp, bool simple) const;
|
||||
void dump (bool simple) const;
|
||||
|
||||
static int cmp_ptrs (const void *, const void *);
|
||||
static int cmp (const binding_key *, const binding_key *);
|
||||
|
||||
virtual const concrete_binding *dyn_cast_concrete_binding () const
|
||||
{ return NULL; }
|
||||
|
||||
enum binding_kind get_kind () const { return m_kind; }
|
||||
|
||||
void mark_deleted () { m_kind = BK_deleted; }
|
||||
void mark_empty () { m_kind = BK_empty; }
|
||||
bool is_deleted () const { return m_kind == BK_deleted; }
|
||||
bool is_empty () const { return m_kind == BK_empty; }
|
||||
|
||||
protected:
|
||||
binding_key (enum binding_kind kind) : m_kind (kind) {}
|
||||
|
||||
hashval_t impl_hash () const
|
||||
{
|
||||
return m_kind;
|
||||
}
|
||||
bool impl_eq (const binding_key &other) const
|
||||
{
|
||||
return m_kind == other.m_kind;
|
||||
}
|
||||
|
||||
private:
|
||||
enum binding_kind m_kind;
|
||||
};
|
||||
|
||||
/* Concrete subclass of binding_key, for describing a concrete range of
|
||||
bits within the binding_map (e.g. "bits 8-15"). */
|
||||
|
||||
class concrete_binding : public binding_key
|
||||
{
|
||||
public:
|
||||
/* This class is its own key for the purposes of consolidation. */
|
||||
typedef concrete_binding key_t;
|
||||
|
||||
concrete_binding (bit_offset_t start_bit_offset, bit_size_t size_in_bits,
|
||||
enum binding_kind kind)
|
||||
: binding_key (kind),
|
||||
m_start_bit_offset (start_bit_offset),
|
||||
m_size_in_bits (size_in_bits)
|
||||
{}
|
||||
bool concrete_p () const FINAL OVERRIDE { return true; }
|
||||
|
||||
hashval_t hash () const
|
||||
{
|
||||
inchash::hash hstate;
|
||||
hstate.add_wide_int (m_start_bit_offset);
|
||||
hstate.add_wide_int (m_size_in_bits);
|
||||
return hstate.end () ^ binding_key::impl_hash ();
|
||||
}
|
||||
bool operator== (const concrete_binding &other) const
|
||||
{
|
||||
if (!binding_key::impl_eq (other))
|
||||
return false;
|
||||
return (m_start_bit_offset == other.m_start_bit_offset
|
||||
&& m_size_in_bits == other.m_size_in_bits);
|
||||
}
|
||||
|
||||
void dump_to_pp (pretty_printer *pp, bool simple) const FINAL OVERRIDE;
|
||||
|
||||
const concrete_binding *dyn_cast_concrete_binding () const FINAL OVERRIDE
|
||||
{ return this; }
|
||||
|
||||
bit_offset_t get_start_bit_offset () const { return m_start_bit_offset; }
|
||||
bit_size_t get_size_in_bits () const { return m_size_in_bits; }
|
||||
/* Return the next bit offset after the end of this binding. */
|
||||
bit_offset_t get_next_bit_offset () const
|
||||
{
|
||||
return m_start_bit_offset + m_size_in_bits;
|
||||
}
|
||||
|
||||
bool overlaps_p (const concrete_binding &other) const;
|
||||
|
||||
private:
|
||||
bit_offset_t m_start_bit_offset;
|
||||
bit_size_t m_size_in_bits;
|
||||
};
|
||||
|
||||
} // namespace ana
|
||||
|
||||
template <> struct default_hash_traits<ana::concrete_binding>
|
||||
: public member_function_hash_traits<ana::concrete_binding>
|
||||
{
|
||||
static const bool empty_zero_p = true;
|
||||
};
|
||||
|
||||
namespace ana {
|
||||
|
||||
/* Concrete subclass of binding_key, for describing a symbolic set of
|
||||
bits within the binding_map in terms of a region (e.g. "arr[i]"). */
|
||||
|
||||
class symbolic_binding : public binding_key
|
||||
{
|
||||
public:
|
||||
/* This class is its own key for the purposes of consolidation. */
|
||||
typedef symbolic_binding key_t;
|
||||
|
||||
symbolic_binding (const region *region, enum binding_kind kind)
|
||||
: binding_key (kind),
|
||||
m_region (region)
|
||||
{}
|
||||
bool concrete_p () const FINAL OVERRIDE { return false; }
|
||||
|
||||
hashval_t hash () const
|
||||
{
|
||||
return (binding_key::impl_hash () ^ (long)m_region);
|
||||
}
|
||||
bool operator== (const symbolic_binding &other) const
|
||||
{
|
||||
if (!binding_key::impl_eq (other))
|
||||
return false;
|
||||
return (m_region == other.m_region);
|
||||
}
|
||||
|
||||
void dump_to_pp (pretty_printer *pp, bool simple) const FINAL OVERRIDE;
|
||||
|
||||
const region *get_region () const { return m_region; }
|
||||
|
||||
private:
|
||||
const region *m_region;
|
||||
};
|
||||
|
||||
} // namespace ana
|
||||
|
||||
template <> struct default_hash_traits<ana::symbolic_binding>
|
||||
: public member_function_hash_traits<ana::symbolic_binding>
|
||||
{
|
||||
static const bool empty_zero_p = true;
|
||||
};
|
||||
|
||||
namespace ana {
|
||||
|
||||
/* A mapping from binding_keys to svalues, for use by binding_cluster
|
||||
and compound_svalue. */
|
||||
|
||||
class binding_map
|
||||
{
|
||||
public:
|
||||
typedef hash_map <const binding_key *, const svalue *> map_t;
|
||||
typedef map_t::iterator iterator_t;
|
||||
|
||||
binding_map () : m_map () {}
|
||||
binding_map (const binding_map &other);
|
||||
binding_map& operator=(const binding_map &other);
|
||||
|
||||
bool operator== (const binding_map &other) const;
|
||||
bool operator!= (const binding_map &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
hashval_t hash () const;
|
||||
|
||||
const svalue *get (const binding_key *key) const
|
||||
{
|
||||
const svalue **slot = const_cast<map_t &> (m_map).get (key);
|
||||
if (slot)
|
||||
return *slot;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
bool put (const binding_key *k, const svalue *v)
|
||||
{
|
||||
gcc_assert (v);
|
||||
return m_map.put (k, v);
|
||||
}
|
||||
|
||||
void remove (const binding_key *k) { m_map.remove (k); }
|
||||
void empty () { m_map.empty (); }
|
||||
|
||||
iterator_t begin () const { return m_map.begin (); }
|
||||
iterator_t end () const { return m_map.end (); }
|
||||
size_t elements () const { return m_map.elements (); }
|
||||
|
||||
void dump_to_pp (pretty_printer *pp, bool simple, bool multiline) const;
|
||||
void dump (bool simple) const;
|
||||
|
||||
private:
|
||||
map_t m_map;
|
||||
};
|
||||
|
||||
/* Concept: BindingVisitor, for use by binding_cluster::for_each_binding
|
||||
and store::for_each_binding.
|
||||
|
||||
Should implement:
|
||||
void on_binding (const binding_key *key, const svalue *&sval);
|
||||
*/
|
||||
|
||||
/* All of the bindings within a store for regions that share the same
|
||||
base region. */
|
||||
|
||||
class binding_cluster
|
||||
{
|
||||
public:
|
||||
friend class store;
|
||||
|
||||
typedef hash_map <const binding_key *, const svalue *> map_t;
|
||||
typedef map_t::iterator iterator_t;
|
||||
|
||||
binding_cluster (const region *base_region)
|
||||
: m_base_region (base_region), m_map (),
|
||||
m_escaped (false), m_touched (false) {}
|
||||
binding_cluster (const binding_cluster &other);
|
||||
binding_cluster& operator=(const binding_cluster &other);
|
||||
|
||||
bool operator== (const binding_cluster &other) const;
|
||||
bool operator!= (const binding_cluster &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
hashval_t hash () const;
|
||||
|
||||
bool symbolic_p () const;
|
||||
|
||||
void dump_to_pp (pretty_printer *pp, bool simple, bool multiline) const;
|
||||
void dump (bool simple) const;
|
||||
|
||||
void bind (store_manager *mgr, const region *, const svalue *,
|
||||
binding_kind kind);
|
||||
|
||||
void clobber_region (store_manager *mgr, const region *reg);
|
||||
void purge_region (store_manager *mgr, const region *reg);
|
||||
void zero_fill_region (store_manager *mgr, const region *reg);
|
||||
void mark_region_as_unknown (store_manager *mgr, const region *reg);
|
||||
|
||||
const svalue *get_binding (store_manager *mgr, const region *reg,
|
||||
binding_kind kind) const;
|
||||
const svalue *get_binding_recursive (store_manager *mgr,
|
||||
const region *reg,
|
||||
enum binding_kind kind) const;
|
||||
const svalue *get_any_binding (store_manager *mgr,
|
||||
const region *reg) const;
|
||||
const svalue *maybe_get_compound_binding (store_manager *mgr,
|
||||
const region *reg) const;
|
||||
|
||||
void remove_overlapping_bindings (store_manager *mgr, const region *reg);
|
||||
|
||||
template <typename T>
|
||||
void for_each_value (void (*cb) (const svalue *sval, T user_data),
|
||||
T user_data)
|
||||
{
|
||||
for (map_t::iterator iter = m_map.begin (); iter != m_map.end (); ++iter)
|
||||
cb ((*iter).second, user_data);
|
||||
}
|
||||
|
||||
static bool can_merge_p (const binding_cluster *cluster_a,
|
||||
const binding_cluster *cluster_b,
|
||||
binding_cluster *out_cluster,
|
||||
store_manager *mgr,
|
||||
model_merger *merger);
|
||||
void make_unknown_relative_to (const binding_cluster *other_cluster,
|
||||
store_manager *mgr);
|
||||
|
||||
void mark_as_escaped ();
|
||||
void on_unknown_fncall (const gcall *call, store_manager *mgr);
|
||||
|
||||
bool escaped_p () const { return m_escaped; }
|
||||
bool touched_p () const { return m_touched; }
|
||||
|
||||
bool redundant_p () const;
|
||||
bool empty_p () const { return m_map.elements () == 0; }
|
||||
|
||||
void get_representative_path_vars (const region_model *model,
|
||||
svalue_set *visited,
|
||||
const region *base_reg,
|
||||
const svalue *sval,
|
||||
auto_vec<path_var> *out_pvs) const;
|
||||
|
||||
const svalue *maybe_get_simple_value (store_manager *mgr) const;
|
||||
|
||||
template <typename BindingVisitor>
|
||||
void for_each_binding (BindingVisitor &v)
|
||||
{
|
||||
for (map_t::iterator iter = m_map.begin (); iter != m_map.end (); ++iter)
|
||||
{
|
||||
const binding_key *key = (*iter).first;
|
||||
const svalue *&sval = (*iter).second;
|
||||
v.on_binding (key, sval);
|
||||
}
|
||||
}
|
||||
|
||||
iterator_t begin () const { return m_map.begin (); }
|
||||
iterator_t end () const { return m_map.end (); }
|
||||
|
||||
private:
|
||||
const svalue *get_any_value (const binding_key *key) const;
|
||||
void get_overlapping_bindings (store_manager *mgr, const region *reg,
|
||||
auto_vec<const binding_key *> *out);
|
||||
void bind_compound_sval (store_manager *mgr,
|
||||
const region *reg,
|
||||
const compound_svalue *compound_sval);
|
||||
void bind_key (const binding_key *key, const svalue *sval);
|
||||
|
||||
const region *m_base_region;
|
||||
|
||||
binding_map m_map;
|
||||
|
||||
/* Has a pointer to this cluster "escaped" into a part of the program
|
||||
we don't know about (via a call to a function with an unknown body,
|
||||
or by being passed in as a pointer param of a "top-level" function call).
|
||||
Such regions could be overwritten when other such functions are called,
|
||||
even if the region is no longer reachable by pointers that we are
|
||||
tracking. */
|
||||
bool m_escaped;
|
||||
|
||||
/* Has this cluster been written to via a symbolic binding?
|
||||
If so, then we don't know anything about unbound subregions,
|
||||
so we can't use initial_svalue, treat them as uninitialized, or
|
||||
inherit values from a parent region. */
|
||||
bool m_touched;
|
||||
};
|
||||
|
||||
/* The mapping from regions to svalues.
|
||||
This is actually expressed by subdividing into clusters, to better
|
||||
handle aliasing. */
|
||||
|
||||
class store
|
||||
{
|
||||
public:
|
||||
typedef hash_map <const region *, binding_cluster *> cluster_map_t;
|
||||
|
||||
store ();
|
||||
store (const store &other);
|
||||
~store ();
|
||||
|
||||
store &operator= (const store &other);
|
||||
|
||||
bool operator== (const store &other) const;
|
||||
bool operator!= (const store &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
hashval_t hash () const;
|
||||
|
||||
void dump_to_pp (pretty_printer *pp, bool summarize, bool multiline,
|
||||
store_manager *mgr) const;
|
||||
void dump (bool simple) const;
|
||||
void summarize_to_pp (pretty_printer *pp, bool simple) const;
|
||||
|
||||
const svalue *get_direct_binding (store_manager *mgr, const region *reg);
|
||||
const svalue *get_default_binding (store_manager *mgr, const region *reg);
|
||||
const svalue *get_any_binding (store_manager *mgr, const region *reg) const;
|
||||
|
||||
bool called_unknown_fn_p () const { return m_called_unknown_fn; }
|
||||
|
||||
void set_value (store_manager *mgr, const region *lhs_reg,
|
||||
const svalue *rhs_sval, enum binding_kind kind);
|
||||
void clobber_region (store_manager *mgr, const region *reg);
|
||||
void purge_region (store_manager *mgr, const region *reg);
|
||||
void zero_fill_region (store_manager *mgr, const region *reg);
|
||||
void mark_region_as_unknown (store_manager *mgr, const region *reg);
|
||||
|
||||
const binding_cluster *get_cluster (const region *base_reg) const;
|
||||
binding_cluster *get_cluster (const region *base_reg);
|
||||
binding_cluster *get_or_create_cluster (const region *base_reg);
|
||||
void purge_cluster (const region *base_reg);
|
||||
|
||||
template <typename T>
|
||||
void for_each_cluster (void (*cb) (const region *base_reg, T user_data),
|
||||
T user_data) const
|
||||
{
|
||||
for (cluster_map_t::iterator iter = m_cluster_map.begin ();
|
||||
iter != m_cluster_map.end (); ++iter)
|
||||
cb ((*iter).first, user_data);
|
||||
}
|
||||
|
||||
static bool can_merge_p (const store *store_a, const store *store_b,
|
||||
store *out_store, store_manager *mgr,
|
||||
model_merger *merger);
|
||||
|
||||
void mark_as_escaped (const region *base_reg);
|
||||
void on_unknown_fncall (const gcall *call, store_manager *mgr);
|
||||
bool escaped_p (const region *reg) const;
|
||||
|
||||
void get_representative_path_vars (const region_model *model,
|
||||
svalue_set *visited,
|
||||
const svalue *sval,
|
||||
auto_vec<path_var> *out_pvs) const;
|
||||
|
||||
cluster_map_t::iterator begin () const { return m_cluster_map.begin (); }
|
||||
cluster_map_t::iterator end () const { return m_cluster_map.end (); }
|
||||
|
||||
tristate eval_alias (const region *base_reg_a,
|
||||
const region *base_reg_b);
|
||||
|
||||
template <typename BindingVisitor>
|
||||
void for_each_binding (BindingVisitor &v)
|
||||
{
|
||||
for (cluster_map_t::iterator iter = m_cluster_map.begin ();
|
||||
iter != m_cluster_map.end (); ++iter)
|
||||
(*iter).second->for_each_binding (v);
|
||||
}
|
||||
|
||||
void canonicalize (store_manager *mgr);
|
||||
void loop_replay_fixup (const store *other_store,
|
||||
region_model_manager *mgr);
|
||||
|
||||
private:
|
||||
void remove_overlapping_bindings (store_manager *mgr, const region *reg);
|
||||
|
||||
cluster_map_t m_cluster_map;
|
||||
|
||||
/* If this is true, then unknown code has been called, and so
|
||||
any global variable that isn't currently modelled by the store
|
||||
has unknown state, rather than being in an "initial state".
|
||||
This is to avoid having to mark (and thus explicitly track)
|
||||
every global when an unknown function is called; instead, they
|
||||
can be tracked implicitly. */
|
||||
bool m_called_unknown_fn;
|
||||
};
|
||||
|
||||
/* A class responsible for owning and consolidating binding keys
|
||||
(both concrete and symbolic).
|
||||
Key instances are immutable as far as clients are concerned, so they
|
||||
are provided as "const" ptrs. */
|
||||
|
||||
class store_manager
|
||||
{
|
||||
public:
|
||||
store_manager (region_model_manager *mgr) : m_mgr (mgr) {}
|
||||
|
||||
/* binding consolidation. */
|
||||
const concrete_binding *
|
||||
get_concrete_binding (bit_offset_t start_bit_offset,
|
||||
bit_offset_t size_in_bits,
|
||||
enum binding_kind kind);
|
||||
const symbolic_binding *
|
||||
get_symbolic_binding (const region *region,
|
||||
enum binding_kind kind);
|
||||
|
||||
region_model_manager *get_svalue_manager () const
|
||||
{
|
||||
return m_mgr;
|
||||
}
|
||||
|
||||
void log_stats (logger *logger, bool show_objs) const;
|
||||
|
||||
private:
|
||||
region_model_manager *m_mgr;
|
||||
consolidation_map<concrete_binding> m_concrete_binding_key_mgr;
|
||||
consolidation_map<symbolic_binding> m_symbolic_binding_key_mgr;
|
||||
};
|
||||
|
||||
} // namespace ana
|
||||
|
||||
#endif /* GCC_ANALYZER_STORE_H */
|
1004
gcc/analyzer/svalue.cc
Normal file
1004
gcc/analyzer/svalue.cc
Normal file
File diff suppressed because it is too large
Load diff
|
@ -245,51 +245,34 @@ This is an implementation of the region-based ternary model described in
|
|||
(Zhongxing Xu, Ted Kremenek, and Jian Zhang).
|
||||
|
||||
A @code{region_model} encapsulates a representation of the state of
|
||||
memory, with a tree of @code{region} instances, along with their associated
|
||||
values. The representation is graph-like because values can be pointers
|
||||
to regions. It also stores a constraint_manager, capturing relationships
|
||||
between the values.
|
||||
memory, with a @code{store} recording a binding between @code{region}
|
||||
instances, to @code{svalue} instances. The bindings are organized into
|
||||
clusters, where regions accessible via well-defined pointer arithmetic
|
||||
are in the same cluster. The representation is graph-like because values
|
||||
can be pointers to regions. It also stores a constraint_manager,
|
||||
capturing relationships between the values.
|
||||
|
||||
Because each node in the @code{exploded_graph} has a @code{region_model},
|
||||
and each of the latter is graph-like, the @code{exploded_graph} is in some
|
||||
ways a graph of graphs.
|
||||
|
||||
Here's an example of printing a @code{region_model}, showing the ASCII-art
|
||||
used to visualize the region hierarchy (colorized when printing to stderr):
|
||||
Here's an example of printing a @code{program_state}, showing the
|
||||
@code{region_model} within it, along with state for the @code{malloc}
|
||||
state machine.
|
||||
|
||||
@smallexample
|
||||
(gdb) call debug (*this)
|
||||
r0: @{kind: 'root', parent: null, sval: null@}
|
||||
|-stack: r1: @{kind: 'stack', parent: r0, sval: sv1@}
|
||||
| |: sval: sv1: @{poisoned: uninit@}
|
||||
| |-frame for 'test': r2: @{kind: 'frame', parent: r1, sval: null, map: @{'ptr_3': r3@}, function: 'test', depth: 0@}
|
||||
| | `-'ptr_3': r3: @{kind: 'map', parent: r2, sval: sv3, type: 'void *', map: @{@}@}
|
||||
| | |: sval: sv3: @{type: 'void *', unknown@}
|
||||
| | |: type: 'void *'
|
||||
| `-frame for 'calls_malloc': r4: @{kind: 'frame', parent: r1, sval: null, map: @{'result_3': r7, '_4': r8, '<anonymous>': r5@}, function: 'calls_malloc', depth: 1@}
|
||||
| |-'<anonymous>': r5: @{kind: 'map', parent: r4, sval: sv4, type: 'void *', map: @{@}@}
|
||||
| | |: sval: sv4: @{type: 'void *', &r6@}
|
||||
| | |: type: 'void *'
|
||||
| |-'result_3': r7: @{kind: 'map', parent: r4, sval: sv4, type: 'void *', map: @{@}@}
|
||||
| | |: sval: sv4: @{type: 'void *', &r6@}
|
||||
| | |: type: 'void *'
|
||||
| `-'_4': r8: @{kind: 'map', parent: r4, sval: sv4, type: 'void *', map: @{@}@}
|
||||
| |: sval: sv4: @{type: 'void *', &r6@}
|
||||
| |: type: 'void *'
|
||||
`-heap: r9: @{kind: 'heap', parent: r0, sval: sv2@}
|
||||
|: sval: sv2: @{poisoned: uninit@}
|
||||
`-r6: @{kind: 'symbolic', parent: r9, sval: null, map: @{@}@}
|
||||
svalues:
|
||||
sv0: @{type: 'size_t', '1024'@}
|
||||
sv1: @{poisoned: uninit@}
|
||||
sv2: @{poisoned: uninit@}
|
||||
sv3: @{type: 'void *', unknown@}
|
||||
sv4: @{type: 'void *', &r6@}
|
||||
constraint manager:
|
||||
rmodel:
|
||||
stack depth: 1
|
||||
frame (index 0): frame: ‘test’@@1
|
||||
clusters within frame: ‘test’@@1
|
||||
cluster for: ptr_3: &HEAP_ALLOCATED_REGION(12)
|
||||
m_called_unknown_fn: FALSE
|
||||
constraint_manager:
|
||||
equiv classes:
|
||||
ec0: @{sv0 == '1024'@}
|
||||
ec1: @{sv4@}
|
||||
constraints:
|
||||
malloc:
|
||||
0x2e89590: &HEAP_ALLOCATED_REGION(12): unchecked ('ptr_3')
|
||||
@end smallexample
|
||||
|
||||
This is the state at the point of returning from @code{calls_malloc} back
|
||||
|
@ -310,13 +293,12 @@ void test (void)
|
|||
@}
|
||||
@end smallexample
|
||||
|
||||
The ``root'' region (``r0'') has a ``stack'' child (``r1''), with two
|
||||
children: a frame for @code{test} (``r2''), and a frame for
|
||||
@code{calls_malloc} (``r4''). These frame regions have child regions for
|
||||
storing their local variables. For example, the return region
|
||||
and that of various other regions within the ``calls_malloc'' frame all have
|
||||
value ``sv4'', a pointer to a heap-allocated region ``r6''. Within the parent
|
||||
frame, @code{ptr_3} has value ``sv3'', an unknown @code{void *}.
|
||||
Within the store, there is the cluster for @code{ptr_3} within the frame
|
||||
for @code{test}, where the whole cluster is bound to a pointer value,
|
||||
pointing at @code{HEAP_ALLOCATED_REGION(12)}. Additionally, this pointer
|
||||
has the @code{unchecked} state for the @code{malloc} state machine
|
||||
indicating it hasn't yet been checked against NULL since the allocation
|
||||
call.
|
||||
|
||||
@subsection Analyzer Paths
|
||||
|
||||
|
@ -393,21 +375,6 @@ The constraint-handling code assumes reflexivity in some places
|
|||
As a simple workaround, constraints on floating-point values are
|
||||
currently ignored.
|
||||
@item
|
||||
The region model code creates lots of little mutable objects at each
|
||||
@code{region_model} (and thus per @code{exploded_node}) rather than
|
||||
sharing immutable objects and having the mutable state in the
|
||||
@code{program_state} or @code{region_model}. The latter approach might be
|
||||
more efficient, and might avoid dealing with IDs rather than pointers
|
||||
(which requires us to impose an ordering to get meaningful equality).
|
||||
@item
|
||||
The region model code doesn't yet support @code{memcpy}. At the
|
||||
gimple-ssa level these have been optimized to statements like this:
|
||||
@smallexample
|
||||
_10 = MEM <long unsigned int> [(char * @{ref-all@})&c]
|
||||
MEM <long unsigned int> [(char * @{ref-all@})&d] = _10;
|
||||
@end smallexample
|
||||
Perhaps they could be supported via a new @code{compound_svalue} type.
|
||||
@item
|
||||
There are various other limitations in the region model (grep for TODO/xfail
|
||||
in the testsuite).
|
||||
@item
|
||||
|
@ -455,6 +422,16 @@ to the source being analyzed to trigger a breakpoint in the analyzer when
|
|||
that source is reached. By putting a series of these in the source, it's
|
||||
much easier to effectively step through the program state as it's analyzed.
|
||||
|
||||
The analyzer handles:
|
||||
|
||||
@smallexample
|
||||
__analyzer_describe (0, expr);
|
||||
@end smallexample
|
||||
|
||||
by emitting a warning describing the 2nd argument (which can be of any
|
||||
type), at a verbosity level given by the 1st argument. This is for use when
|
||||
debugging, and may be of use in DejaGnu tests.
|
||||
|
||||
@smallexample
|
||||
__analyzer_dump ();
|
||||
@end smallexample
|
||||
|
@ -508,21 +485,4 @@ truthfulness of the argument. This is useful for writing DejaGnu tests.
|
|||
|
||||
One approach when tracking down where a particular bogus state is
|
||||
introduced into the @code{exploded_graph} is to add custom code to
|
||||
@code{region_model::validate}.
|
||||
|
||||
For example, this custom code (added to @code{region_model::validate})
|
||||
breaks with an assertion failure when a variable called @code{ptr}
|
||||
acquires a value that's unknown, using
|
||||
@code{region_model::get_value_by_name} to locate the variable
|
||||
|
||||
@smallexample
|
||||
/* Find a variable matching "ptr". */
|
||||
svalue_id sid = get_value_by_name ("ptr");
|
||||
if (!sid.null_p ())
|
||||
@{
|
||||
svalue *sval = get_svalue (sid);
|
||||
gcc_assert (sval->get_kind () != SK_UNKNOWN);
|
||||
@}
|
||||
@end smallexample
|
||||
|
||||
making it easier to investigate further in a debugger when this occurs.
|
||||
@code{program_state::validate}.
|
||||
|
|
|
@ -5,8 +5,11 @@ auto lol()
|
|||
{
|
||||
int aha = 3;
|
||||
return [&aha] {
|
||||
return aha;
|
||||
return aha; // { dg-warning "dereferencing pointer '.*' to within stale stack frame" }
|
||||
};
|
||||
/* TODO: may be worth special-casing the reporting of dangling
|
||||
references from lambdas, to highlight the declaration, and maybe fix
|
||||
the wording (it's a reference, not a pointer, for one thing). */
|
||||
}
|
||||
|
||||
int main()
|
||||
|
|
|
@ -9,9 +9,7 @@ struct d
|
|||
};
|
||||
void h (e * i)
|
||||
{
|
||||
void *j = nullptr; // { dg-bogus "NULL" "" { xfail *-*-* } }
|
||||
// TODO(xfail): we report "'i' is NULL" above, which is the wrong location
|
||||
|
||||
void *j = nullptr; // { dg-bogus "NULL" }
|
||||
i->f = *i->g; // { dg-warning "dereference of NULL 'i'" }
|
||||
}
|
||||
virtual void c (int, int)
|
||||
|
|
16
gcc/testsuite/g++.dg/analyzer/pr94011.C
Normal file
16
gcc/testsuite/g++.dg/analyzer/pr94011.C
Normal file
|
@ -0,0 +1,16 @@
|
|||
// { dg-do compile { target c++11 } }
|
||||
// { dg-additional-options "-O1" }
|
||||
|
||||
template <typename DV> DV
|
||||
vu (DV j4)
|
||||
{
|
||||
return [j4] () { return j4 () ? j4 : throw j4 (); } ();
|
||||
}
|
||||
|
||||
void
|
||||
foo ()
|
||||
{
|
||||
auto n1 = [] { return nullptr; };
|
||||
|
||||
vu (n1);
|
||||
}
|
|
@ -19,11 +19,11 @@ struct j
|
|||
throw()
|
||||
#endif
|
||||
{
|
||||
return calloc (b, sizeof (int)); // { dg-warning "leak" }
|
||||
return calloc (b, sizeof (int)); // { dg-bogus "leak" "" { xfail c++98_only } }
|
||||
}
|
||||
j (B *, int)
|
||||
{
|
||||
} // { dg-warning "leak" }
|
||||
}
|
||||
};
|
||||
|
||||
j *
|
||||
|
|
25
gcc/testsuite/g++.dg/analyzer/pr94503.C
Normal file
25
gcc/testsuite/g++.dg/analyzer/pr94503.C
Normal file
|
@ -0,0 +1,25 @@
|
|||
template <typename> class allocator {
|
||||
public:
|
||||
allocator(const allocator &);
|
||||
allocator();
|
||||
};
|
||||
|
||||
template <typename> struct allocator_traits;
|
||||
template <typename _Tp> struct allocator_traits<allocator<_Tp> > {
|
||||
static allocator<_Tp> select_on_container_copy_construction() {
|
||||
return allocator<_Tp>();
|
||||
}
|
||||
static allocator<_Tp> _S_select_on_copy() {
|
||||
return select_on_container_copy_construction();
|
||||
}
|
||||
};
|
||||
|
||||
class basic_string {
|
||||
struct _Alloc_hider {
|
||||
_Alloc_hider(allocator<char>);
|
||||
} _M_dataplus;
|
||||
|
||||
public:
|
||||
basic_string(basic_string &)
|
||||
: _M_dataplus(allocator_traits<allocator<char> >::_S_select_on_copy()) {}
|
||||
} xxx(xxx);
|
28
gcc/testsuite/g++.dg/analyzer/pr95042.C
Normal file
28
gcc/testsuite/g++.dg/analyzer/pr95042.C
Normal file
|
@ -0,0 +1,28 @@
|
|||
// { dg-additional-options "-O1" }
|
||||
|
||||
class kz {
|
||||
public:
|
||||
kz ();
|
||||
|
||||
private:
|
||||
int yu;
|
||||
};
|
||||
|
||||
const kz vl;
|
||||
kz ax;
|
||||
|
||||
void
|
||||
c1 (bool va, bool ze)
|
||||
{
|
||||
kz ny, fb = vl;
|
||||
|
||||
if (va)
|
||||
{
|
||||
if (ze)
|
||||
ny = vl;
|
||||
|
||||
fb = ny;
|
||||
}
|
||||
|
||||
ax = fb;
|
||||
}
|
30
gcc/testsuite/gcc.dg/analyzer/CVE-2005-1689-dedupe-issue-2.c
Normal file
30
gcc/testsuite/gcc.dg/analyzer/CVE-2005-1689-dedupe-issue-2.c
Normal file
|
@ -0,0 +1,30 @@
|
|||
/* { dg-additional-options "-fanalyzer-show-duplicate-count" } */
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct _krb5_data {
|
||||
char *data;
|
||||
} krb5_data;
|
||||
|
||||
/* Ensure that we de-duplicate the various paths to reach here,
|
||||
and only emit one diagnostic. */
|
||||
|
||||
void
|
||||
recvauth_common(krb5_data common)
|
||||
{
|
||||
free(common.data);
|
||||
free(common.data); /* { dg-warning "double-'free' of 'common.data'" "inner warning" } */
|
||||
/* { dg-warning "double-'free' of 'inbuf_a.data' " "inbuf_a warning" { target *-*-* } .-1 } */
|
||||
/* { dg-warning "double-'free' of 'inbuf_b.data' " "inbuf_b warning" { target *-*-* } .-2 } */
|
||||
/* { dg-message "2 duplicates" "duplicates notification" { xfail *-*-* } .-3 } */
|
||||
}
|
||||
|
||||
void krb5_recvauth(krb5_data inbuf_a)
|
||||
{
|
||||
recvauth_common(inbuf_a);
|
||||
}
|
||||
|
||||
void krb5_recvauth_version(krb5_data inbuf_b)
|
||||
{
|
||||
recvauth_common(inbuf_b);
|
||||
}
|
|
@ -14,7 +14,7 @@ recvauth_common(krb5_data inbuf)
|
|||
{
|
||||
free(inbuf.data);
|
||||
free(inbuf.data); /* { dg-warning "double-'free'" "warning" } */
|
||||
/* { dg-message "2 duplicates" "duplicates notification" { target *-*-* } .-1 } */
|
||||
/* { dg-message "2 duplicates" "duplicates notification" { xfail *-*-* } .-1 } */
|
||||
}
|
||||
|
||||
void krb5_recvauth(krb5_data inbuf)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include <stdlib.h>
|
||||
#include "analyzer-decls.h"
|
||||
|
||||
typedef struct _krb5_data {
|
||||
char *data;
|
||||
|
@ -28,3 +29,63 @@ test_3 (krb5_data inbuf, int flag)
|
|||
}
|
||||
free((char *)inbuf.data); /* { dg-warning "double-'free' of 'inbuf.data'" } */
|
||||
}
|
||||
|
||||
extern void unknown_fn (void *);
|
||||
|
||||
void
|
||||
test_4 (krb5_data inbuf)
|
||||
{
|
||||
unknown_fn (NULL);
|
||||
free(inbuf.data); /* { dg-message "first 'free' here" } */
|
||||
free(inbuf.data); /* { dg-warning "double-'free' of 'inbuf.data'" } */
|
||||
}
|
||||
|
||||
void
|
||||
test_5 (krb5_data inbuf)
|
||||
{
|
||||
unknown_fn (&inbuf);
|
||||
free(inbuf.data); /* { dg-message "first 'free' here" } */
|
||||
free(inbuf.data); /* { dg-warning "double-'free' of 'inbuf.data'" "inbuf.data" } */
|
||||
/* { dg-bogus "double-'free' of 'inbuf'" "inbuf" { target *-*-* } .-1 } */
|
||||
}
|
||||
|
||||
typedef struct _padded_krb5_data {
|
||||
int pad;
|
||||
char *data;
|
||||
} padded_krb5_data;
|
||||
|
||||
void
|
||||
test_6 (padded_krb5_data inbuf)
|
||||
{
|
||||
unknown_fn (&inbuf.data);
|
||||
free((char *)inbuf.data); /* { dg-message "first 'free' here" } */
|
||||
free((char *)inbuf.data); /* { dg-warning "double-'free' of 'inbuf.data'" "inbuf.data" } */
|
||||
}
|
||||
|
||||
void
|
||||
test_7 (padded_krb5_data inbuf)
|
||||
{
|
||||
unknown_fn (&inbuf.data);
|
||||
free((char *)inbuf.data);
|
||||
|
||||
unknown_fn (&inbuf.data);
|
||||
free((char *)inbuf.data);
|
||||
}
|
||||
|
||||
void
|
||||
test_8 (padded_krb5_data inbuf, int flag)
|
||||
{
|
||||
if (flag)
|
||||
{
|
||||
unknown_fn (&inbuf.data);
|
||||
free((char *)inbuf.data);
|
||||
}
|
||||
/* Should have two enodes, one for the explicit "freed" state, and one
|
||||
for the implicit "start" state. */
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
|
||||
|
||||
unknown_fn (&inbuf.data);
|
||||
|
||||
/* Should have just one enode, for the implicit "start" state. */
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
|
||||
}
|
||||
|
|
22
gcc/testsuite/gcc.dg/analyzer/abs-1.c
Normal file
22
gcc/testsuite/gcc.dg/analyzer/abs-1.c
Normal file
|
@ -0,0 +1,22 @@
|
|||
#include "analyzer-decls.h"
|
||||
|
||||
extern long int labs (long int x)
|
||||
__attribute__ ((__nothrow__ , __leaf__))
|
||||
__attribute__ ((__const__));
|
||||
|
||||
long int test_1 (long int x)
|
||||
{
|
||||
return labs (x);
|
||||
}
|
||||
|
||||
static long __attribute__((noinline))
|
||||
hide_long (long x)
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
long int test_2 (long int x)
|
||||
{
|
||||
__analyzer_eval (labs (hide_long (42)) == 42); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (labs (hide_long (-17)) == 17); /* { dg-warning "TRUE" } */
|
||||
}
|
25
gcc/testsuite/gcc.dg/analyzer/aliasing-1.c
Normal file
25
gcc/testsuite/gcc.dg/analyzer/aliasing-1.c
Normal file
|
@ -0,0 +1,25 @@
|
|||
#include "analyzer-decls.h"
|
||||
|
||||
int a;
|
||||
void test (int *p, int x)
|
||||
{
|
||||
int y;
|
||||
|
||||
a = 17;
|
||||
x = 42;
|
||||
y = 13;
|
||||
|
||||
__analyzer_eval (a == 17); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (x == 42); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (y == 13); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_eval (p == &a); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (p == &x); /* { dg-warning "FALSE" } */
|
||||
__analyzer_eval (p == &y); /* { dg-warning "FALSE" } */
|
||||
|
||||
*p = 73;
|
||||
|
||||
__analyzer_eval (a == 17); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (x == 42); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (y == 13); /* { dg-warning "TRUE" } */
|
||||
}
|
32
gcc/testsuite/gcc.dg/analyzer/aliasing-2.c
Normal file
32
gcc/testsuite/gcc.dg/analyzer/aliasing-2.c
Normal file
|
@ -0,0 +1,32 @@
|
|||
#include "analyzer-decls.h"
|
||||
|
||||
extern void escape (int *p);
|
||||
|
||||
int a;
|
||||
void test (int *p, int x)
|
||||
{
|
||||
int y;
|
||||
|
||||
a = 17;
|
||||
x = 42;
|
||||
y = 13;
|
||||
|
||||
__analyzer_eval (a == 17); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (x == 42); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (y == 13); /* { dg-warning "TRUE" } */
|
||||
|
||||
escape (&x);
|
||||
__analyzer_eval (a == 17); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (x == 42); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (y == 13); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_eval (p == &a); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (p == &x); /* { dg-warning "FALSE" } */
|
||||
__analyzer_eval (p == &y); /* { dg-warning "FALSE" } */
|
||||
|
||||
*p = 73;
|
||||
|
||||
__analyzer_eval (a == 17); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (x == 42); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (y == 13); /* { dg-warning "TRUE" } */
|
||||
}
|
|
@ -7,6 +7,11 @@
|
|||
/* Trigger a breakpoint in the analyzer when reached. */
|
||||
extern void __analyzer_break (void);
|
||||
|
||||
/* Emit a warning describing the 2nd argument (which can be of any
|
||||
type), at the given verbosity level. This is for use when
|
||||
debugging, and may be of use in DejaGnu tests. */
|
||||
extern void __analyzer_describe (int verbosity, ...);
|
||||
|
||||
/* Dump copious information about the analyzer’s state when reached. */
|
||||
extern void __analyzer_dump (void);
|
||||
|
||||
|
@ -20,8 +25,6 @@ extern void __analyzer_dump (void);
|
|||
will also dump all of the states within those nodes. */
|
||||
extern void __analyzer_dump_exploded_nodes (int);
|
||||
|
||||
extern void __analyzer_dump_num_heap_regions (void);
|
||||
|
||||
/* Emit a placeholder "note" diagnostic with a path to this call site,
|
||||
if the analyzer finds a feasible path to it. */
|
||||
extern void __analyzer_dump_path (void);
|
||||
|
|
|
@ -12,9 +12,10 @@ extern void bar(void *ptrA, void *ptrB, void *ptrC) /* { dg-message "argument 1
|
|||
void test_1 (void *p, void *q, void *r)
|
||||
{
|
||||
foo(p, q, r);
|
||||
foo(NULL, q, r);
|
||||
foo(NULL, q, r); /* { dg-warning "use of NULL where non-null expected" "warning" } */
|
||||
/* { dg-message "argument 1 NULL where non-null expected" "note" { target *-*-* } .-1 } */
|
||||
foo(p, NULL, r);
|
||||
foo(p, q, NULL);
|
||||
foo(p, q, NULL); /* { dg-warning "use of NULL where non-null expected" } */
|
||||
}
|
||||
|
||||
void test_1a (void *q, void *r)
|
||||
|
@ -27,9 +28,10 @@ void test_1a (void *q, void *r)
|
|||
void test_2 (void *p, void *q, void *r)
|
||||
{
|
||||
bar(p, q, r);
|
||||
bar(NULL, q, r);
|
||||
bar(p, NULL, r);
|
||||
bar(p, q, NULL);
|
||||
bar(NULL, q, r); /* { dg-warning "use of NULL where non-null expected" "warning" } */
|
||||
bar(p, NULL, r); /* { dg-warning "use of NULL where non-null expected" "warning" } */
|
||||
/* { dg-message "argument 2 NULL where non-null expected" "note" { target *-*-* } .-1 } */
|
||||
bar(p, q, NULL); /* { dg-warning "use of NULL where non-null expected" "warning" } */
|
||||
}
|
||||
|
||||
void test_3 (void *q, void *r)
|
||||
|
|
11
gcc/testsuite/gcc.dg/analyzer/bzero-1.c
Normal file
11
gcc/testsuite/gcc.dg/analyzer/bzero-1.c
Normal file
|
@ -0,0 +1,11 @@
|
|||
#include "analyzer-decls.h"
|
||||
|
||||
extern void bzero(void *s, __SIZE_TYPE__ n);
|
||||
|
||||
void test_1 (void)
|
||||
{
|
||||
char tmp[1024];
|
||||
bzero (tmp, 1024);
|
||||
__analyzer_eval (tmp[0] == 0); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (tmp[1023] == 0); /* { dg-warning "TRUE" } */
|
||||
}
|
49
gcc/testsuite/gcc.dg/analyzer/casts-1.c
Normal file
49
gcc/testsuite/gcc.dg/analyzer/casts-1.c
Normal file
|
@ -0,0 +1,49 @@
|
|||
#include "analyzer-decls.h"
|
||||
|
||||
struct s1
|
||||
{
|
||||
char a;
|
||||
char b;
|
||||
char c;
|
||||
char d;
|
||||
};
|
||||
|
||||
struct s2
|
||||
{
|
||||
char arr[4];
|
||||
};
|
||||
|
||||
void test_1 ()
|
||||
{
|
||||
struct s1 x = {'A', 'B', 'C', 'D'};
|
||||
__analyzer_eval (x.a == 'A'); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (x.b == 'B'); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (x.c == 'C'); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (x.d == 'D'); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (((struct s2 *)&x)->arr[0] == 'A'); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (((struct s2 *)&x)->arr[1] == 'B'); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (((struct s2 *)&x)->arr[2] == 'C'); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (((struct s2 *)&x)->arr[3] == 'D'); /* { dg-warning "TRUE" } */
|
||||
|
||||
((struct s2 *)&x)->arr[1] = '#';
|
||||
__analyzer_eval (((struct s2 *)&x)->arr[1] == '#'); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (x.b == '#'); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_2 ()
|
||||
{
|
||||
struct s2 x = {{'A', 'B', 'C', 'D'}};
|
||||
__analyzer_eval (x.arr[0] == 'A'); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (x.arr[1] == 'B'); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (x.arr[2] == 'C'); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (x.arr[3] == 'D'); /* { dg-warning "TRUE" } */
|
||||
struct s1 *p = (struct s1 *)&x;
|
||||
__analyzer_eval (p->a == 'A'); /* { dg-warning "TRUE" "true" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (p->b == 'B'); /* { dg-warning "TRUE" "true" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (p->c == 'C'); /* { dg-warning "TRUE" "true" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (p->d == 'D'); /* { dg-warning "TRUE" "true" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
}
|
15
gcc/testsuite/gcc.dg/analyzer/casts-2.c
Normal file
15
gcc/testsuite/gcc.dg/analyzer/casts-2.c
Normal file
|
@ -0,0 +1,15 @@
|
|||
#include "analyzer-decls.h"
|
||||
|
||||
void test_1 (int i)
|
||||
{
|
||||
char c1 = i;
|
||||
char c2 = i;
|
||||
__analyzer_eval (c1 == i); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (c1 == c2); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_2 (char c)
|
||||
{
|
||||
int i = c;
|
||||
__analyzer_eval (i == c); /* { dg-warning "TRUE" } */
|
||||
}
|
|
@ -40,13 +40,13 @@ void test_4 (void)
|
|||
struct ptr_wrapper r;
|
||||
r.ptr = malloc (sizeof (int)); /* { dg-message "allocated here" } */
|
||||
} /* { dg-warning "leak of 'r.ptr'" } */
|
||||
/* { dg-bogus "leak of '<unknown>'" "unknown leak" { xfail *-*-* } .-1 } */
|
||||
/* { dg-bogus "leak of '<unknown>'" "unknown leak" { target *-*-* } .-1 } */
|
||||
|
||||
static struct ptr_wrapper __attribute__((noinline))
|
||||
called_by_test_5a (void)
|
||||
{
|
||||
struct ptr_wrapper r;
|
||||
r.ptr = malloc (sizeof (int));
|
||||
r.ptr = malloc (sizeof (int)); /* { dg-message "allocated here" } */
|
||||
return r;
|
||||
}
|
||||
|
||||
|
@ -54,15 +54,14 @@ void test_5a (void)
|
|||
{
|
||||
struct ptr_wrapper q = called_by_test_5a ();
|
||||
} /* { dg-warning "leak of 'q.ptr'" } */
|
||||
/* TODO: show the allocation point. */
|
||||
|
||||
static struct ptr_wrapper __attribute__((noinline))
|
||||
called_by_test_5b (void)
|
||||
{
|
||||
struct ptr_wrapper r;
|
||||
r.ptr = malloc (sizeof (int));
|
||||
return r; /* { dg-warning "leak" } */
|
||||
/* TODO: show the allocation point. */
|
||||
return r; /* { dg-warning "leak of '<return-value>.ptr'" } */
|
||||
/* TODO: show the allocation point; improve above message. */
|
||||
}
|
||||
|
||||
void test_5b (void)
|
||||
|
|
|
@ -22,4 +22,4 @@ test_2 (void)
|
|||
{
|
||||
struct union_wrapper uw2;
|
||||
uw2.u.ptr = malloc (1024);
|
||||
} /* { dg-warning "leak of '\\(void \\*\\)uw2.u'" } */
|
||||
} /* { dg-warning "leak of 'uw2.u.ptr'" } */
|
||||
|
|
28
gcc/testsuite/gcc.dg/analyzer/compound-assignment-4.c
Normal file
28
gcc/testsuite/gcc.dg/analyzer/compound-assignment-4.c
Normal file
|
@ -0,0 +1,28 @@
|
|||
#include "analyzer-decls.h"
|
||||
|
||||
struct coord
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
};
|
||||
|
||||
void test_1 (void)
|
||||
{
|
||||
struct coord arr[16];
|
||||
|
||||
arr[2].y = 4;
|
||||
arr[3].x = 5;
|
||||
arr[3].y = 6;
|
||||
arr[4].x = 7;
|
||||
arr[6].y = 8;
|
||||
arr[8].x = 9;
|
||||
|
||||
arr[7] = arr[3];
|
||||
|
||||
__analyzer_eval (arr[7].x == 5); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (arr[7].y == 6); /* { dg-warning "TRUE" } */
|
||||
|
||||
/* Make sure we don't touch the neighbors. */
|
||||
__analyzer_eval (arr[6].y == 8); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (arr[8].x == 9); /* { dg-warning "TRUE" } */
|
||||
}
|
142
gcc/testsuite/gcc.dg/analyzer/compound-assignment-5.c
Normal file
142
gcc/testsuite/gcc.dg/analyzer/compound-assignment-5.c
Normal file
|
@ -0,0 +1,142 @@
|
|||
#include "analyzer-decls.h"
|
||||
|
||||
struct coord
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
};
|
||||
|
||||
/* Copying from one on-stack array to another. */
|
||||
|
||||
void test_1 (void)
|
||||
{
|
||||
struct coord arr_a[16];
|
||||
struct coord arr_b[16];
|
||||
arr_a[3].x = 5;
|
||||
arr_a[3].y = 6;
|
||||
|
||||
arr_b[7] = arr_a[3];
|
||||
|
||||
__analyzer_eval (arr_b[7].x == 5); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (arr_b[7].y == 6); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
/* Copying from an on-stack array to a global array. */
|
||||
|
||||
struct coord glob_arr[16];
|
||||
|
||||
void test_2 (void)
|
||||
{
|
||||
struct coord arr[16];
|
||||
arr[3].x = 5;
|
||||
arr[3].y = 6;
|
||||
|
||||
glob_arr[7] = arr[3];
|
||||
|
||||
__analyzer_eval (glob_arr[7].x == 5); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (glob_arr[7].y == 6); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
/* Copying from a partially initialized on-stack array to a global array. */
|
||||
|
||||
struct coord glob_arr[16];
|
||||
|
||||
void test_3 (void)
|
||||
{
|
||||
struct coord arr[16];
|
||||
arr[3].y = 6;
|
||||
|
||||
glob_arr[7] = arr[3]; // or should the uninit warning be here?
|
||||
|
||||
__analyzer_eval (glob_arr[7].x); /* { dg-warning "uninitialized" "uninit" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (glob_arr[7].y == 6); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
/* Symbolic bindings: copying from one array to another. */
|
||||
|
||||
struct coord glob_arr[16];
|
||||
|
||||
void test_4 (int i)
|
||||
{
|
||||
struct coord arr_a[16];
|
||||
struct coord arr_b[16];
|
||||
arr_a[i].x = 5;
|
||||
arr_a[i].y = 6;
|
||||
__analyzer_eval (arr_a[i].x == 5); /* { dg-warning "TRUE" "TRUE" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "UNKNOWN" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (arr_a[i].y == 6); /* { dg-warning "TRUE" } */
|
||||
|
||||
arr_b[i] = arr_a[i];
|
||||
|
||||
__analyzer_eval (arr_b[i].x == 5); /* { dg-warning "TRUE" "TRUE" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "UNKNOWN" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (arr_b[i].y == 6); /* { dg-warning "TRUE" "TRUE" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "UNKNOWN" { xfail *-*-* } .-1 } */
|
||||
}
|
||||
|
||||
/* Symbolic bindings: copying within an array: symbolic src and dest */
|
||||
|
||||
struct coord glob_arr[16];
|
||||
|
||||
void test_5a (int i, int j)
|
||||
{
|
||||
struct coord arr[16];
|
||||
arr[i].x = 5;
|
||||
arr[i].y = 6;
|
||||
|
||||
arr[j] = arr[i];
|
||||
|
||||
__analyzer_eval (arr[j].x == 5); /* { dg-warning "TRUE" "TRUE" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "UNKNOWN" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (arr[j].y == 6); /* { dg-warning "TRUE" "TRUE" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "UNKNOWN" { xfail *-*-* } .-1 } */
|
||||
}
|
||||
|
||||
/* Symbolic bindings: copying within an array: symbolic src, concrete dest. */
|
||||
|
||||
struct coord glob_arr[16];
|
||||
|
||||
void test_5b (int i)
|
||||
{
|
||||
struct coord arr[16];
|
||||
arr[i].x = 5;
|
||||
arr[i].y = 6;
|
||||
|
||||
arr[3] = arr[i];
|
||||
|
||||
__analyzer_eval (arr[3].x == 5); /* { dg-warning "TRUE" "TRUE" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "UNKNOWN" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (arr[3].y == 6); /* { dg-warning "TRUE" "TRUE" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "UNKNOWN" { xfail *-*-* } .-1 } */
|
||||
}
|
||||
|
||||
/* Symbolic bindings: copying within an array: concrete src, symbolic dest. */
|
||||
|
||||
struct coord glob_arr[16];
|
||||
|
||||
void test_5c (int i)
|
||||
{
|
||||
struct coord arr[16];
|
||||
arr[3].x = 5;
|
||||
arr[3].y = 6;
|
||||
|
||||
arr[i] = arr[3];
|
||||
|
||||
__analyzer_eval (arr[i].x == 5); /* { dg-warning "TRUE" "TRUE" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "UNKNOWN" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (arr[i].y == 6); /* { dg-warning "TRUE" "TRUE" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "UNKNOWN" { xfail *-*-* } .-1 } */
|
||||
}
|
||||
|
||||
/* No info on the subregion being copied, and hence
|
||||
binding_cluster2::maybe_get_compound_binding should return NULL. */
|
||||
|
||||
void test_6 (void)
|
||||
{
|
||||
struct coord arr[16];
|
||||
arr[7] = glob_arr[3];
|
||||
|
||||
__analyzer_eval (arr[7].x == 5); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (arr[7].y == 6); /* { dg-warning "UNKNOWN" } */
|
||||
}
|
|
@ -7,15 +7,12 @@ void test (int i, int j)
|
|||
{
|
||||
__analyzer_eval (i > 4); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (i <= 4); /* { dg-warning "FALSE" } */
|
||||
__analyzer_eval (i > 3); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (i > 3); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_eval (i > 5); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (i != 3); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (i != 3); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_eval (i == 3); /* { dg-warning "FALSE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (i == 3); /* { dg-warning "FALSE" } */
|
||||
|
||||
__analyzer_eval (i != 4); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (i == 4); /* { dg-warning "FALSE" } */
|
||||
|
@ -43,21 +40,17 @@ void test (int i, int j)
|
|||
__analyzer_eval (i <= 4); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (i > 3); /* { dg-warning "UNKNOWN" } */
|
||||
|
||||
__analyzer_eval (i > 5); /* { dg-warning "FALSE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (i > 5); /* { dg-warning "FALSE" } */
|
||||
__analyzer_eval (i != 3); /* { dg-warning "UNKNOWN" } */
|
||||
|
||||
__analyzer_eval (i == 3); /* { dg-warning "UNKNOWN" } */
|
||||
|
||||
__analyzer_eval (i != 4); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (i == 4); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (i == 5); /* { dg-warning "FALSE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (i != 5); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (i == 5); /* { dg-warning "FALSE" } */
|
||||
__analyzer_eval (i != 5); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (i < 5); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (i <= 5); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (i <= 5); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,8 +94,7 @@ void test_range_int_gt_lt (int i)
|
|||
{
|
||||
if (i > 3)
|
||||
if (i < 5)
|
||||
__analyzer_eval (i == 4); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (i == 4); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_range_float_gt_lt (float f)
|
||||
|
@ -116,8 +108,7 @@ void test_range_int_ge_lt (int i)
|
|||
{
|
||||
if (i >= 4)
|
||||
if (i < 5)
|
||||
__analyzer_eval (i == 4); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (i == 4); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_range_float_ge_lt (float f)
|
||||
|
@ -131,8 +122,7 @@ void test_range_int_gt_le (int i)
|
|||
{
|
||||
if (i > 3)
|
||||
if (i <= 4)
|
||||
__analyzer_eval (i == 4); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (i == 4); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_range_float_gt_le (float f)
|
||||
|
@ -146,8 +136,7 @@ void test_range_int_ge_le (int i)
|
|||
{
|
||||
if (i >= 4)
|
||||
if (i <= 4)
|
||||
__analyzer_eval (i == 4); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-1 } */
|
||||
__analyzer_eval (i == 4); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_range_float_ge_le (float f)
|
||||
|
|
|
@ -181,15 +181,8 @@ int test_12d (struct coord c)
|
|||
{
|
||||
struct coord d;
|
||||
d = c;
|
||||
__analyzer_eval (d.x == c.x); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "actual" { target *-*-* } .-1 } */
|
||||
/* TODO(xfail): c and d share the same unknown value of type "coord", but
|
||||
attempts to access the fields lead to different unknown values. */
|
||||
|
||||
__analyzer_eval (d.y == c.y); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "actual" { target *-*-* } .-1 } */
|
||||
// TODO(xfail): likewise
|
||||
|
||||
__analyzer_eval (d.x == c.x); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (d.y == c.y); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (d.x == d.y); /* { dg-warning "UNKNOWN" } */
|
||||
/* d and c share an unknown value of type "struct coord".
|
||||
But d.x and d.y should be different unknown values (although they inherit
|
||||
|
@ -211,25 +204,21 @@ void test_13 (struct outer *o)
|
|||
{
|
||||
__analyzer_eval (o->mid.in.f == 0.f); /* { dg-warning "UNKNOWN" } */
|
||||
o->mid.in.f = 0.f;
|
||||
__analyzer_eval (o->mid.in.f == 0.f); /* { dg-warning "TRUE" "PR 93356" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "disabled float comparisons" { target *-*-* } .-1 } */
|
||||
__analyzer_eval (o->mid.in.f == 0.f); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_14 (struct outer o)
|
||||
{
|
||||
__analyzer_eval (o.mid.in.f == 0.f); /* { dg-warning "UNKNOWN" } */
|
||||
o.mid.in.f = 0.f;
|
||||
__analyzer_eval (o.mid.in.f == 0.f); /* { dg-warning "TRUE" "PR 93356" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "disabled float comparisons" { target *-*-* } .-1 } */
|
||||
__analyzer_eval (o.mid.in.f == 0.f); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_15 (const char *str)
|
||||
{
|
||||
char ch = str[0];
|
||||
__analyzer_eval (ch == 'a'); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (ch == str[0]); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
|
||||
// TODO(xfail)
|
||||
__analyzer_eval (ch == str[0]); /* { dg-warning "TRUE" } */
|
||||
|
||||
ch = 'a';
|
||||
__analyzer_eval (ch == 'a'); /* { dg-warning "TRUE" } */
|
||||
|
@ -242,15 +231,15 @@ void test_16 (void)
|
|||
|
||||
__analyzer_eval (msg != NULL); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_eval (msg[0] == 'h'); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
|
||||
// TODO(xfail)
|
||||
__analyzer_eval (msg[0] == 'h'); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_eval (msg[1] == 'e'); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
|
||||
// TODO(xfail)
|
||||
__analyzer_eval (msg[1] == 'e'); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_eval (strlen (msg) == 11); /* { dg-warning "TRUE" } */
|
||||
|
||||
/* Out-of-bounds. */
|
||||
__analyzer_eval (msg[100] == 'e'); /* { dg-warning "UNKNOWN" } */
|
||||
// TODO: some kind of warning for the out-of-bounds access
|
||||
}
|
||||
|
||||
static const char *__attribute__((noinline))
|
||||
|
@ -265,13 +254,9 @@ void test_16_alt (void)
|
|||
|
||||
__analyzer_eval (msg != NULL); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_eval (msg[0] == 'h'); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
|
||||
// TODO(xfail)
|
||||
__analyzer_eval (msg[0] == 'h'); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_eval (msg[1] == 'e'); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
|
||||
// TODO(xfail)
|
||||
__analyzer_eval (msg[1] == 'e'); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_eval (strlen (msg) == 11); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
@ -334,9 +319,6 @@ void test_16e (int i)
|
|||
__analyzer_eval (j == i); /* { dg-warning "UNKNOWN" } */
|
||||
}
|
||||
|
||||
/* TODO: and more complicated graph-like examples, where anything that's
|
||||
reachable from the pointer might be modified. */
|
||||
|
||||
void test_17 (int i)
|
||||
{
|
||||
int j = 42;
|
||||
|
@ -477,13 +459,7 @@ void test_23 (struct foo *f, struct foo *g)
|
|||
i = f->i + g->i;
|
||||
j = f->i + g->i;
|
||||
k = f->i * g->i;
|
||||
__analyzer_eval (i == j); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
|
||||
/* TODO(xfail): we'd need to record that the two unknown values are both
|
||||
the sum of the two unknown input values (and thus are the same); not
|
||||
yet sure if we want arbitrary expression trees in the representation
|
||||
(analysis termination concerns). */
|
||||
|
||||
__analyzer_eval (i == j); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (i == k); /* { dg-warning "UNKNOWN" } */
|
||||
}
|
||||
|
||||
|
@ -496,9 +472,7 @@ void test_24 (struct foo *f)
|
|||
/* Overwriting a whole struct should invalidate our knowledge
|
||||
about fields within it. */
|
||||
g = *f;
|
||||
__analyzer_eval (g.i == 42); /* { dg-warning "UNKNOWN" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "TRUE" "status quo" { target *-*-* } .-1 } */
|
||||
// TODO(xfail)
|
||||
__analyzer_eval (g.i == 42); /* { dg-warning "UNKNOWN" } */
|
||||
}
|
||||
|
||||
void test_25 (struct foo *f)
|
||||
|
@ -513,16 +487,14 @@ void test_25 (struct foo *f)
|
|||
source value should update our knowledge about fields within
|
||||
the dest value. */
|
||||
g = *f;
|
||||
__analyzer_eval (g.i == 43); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "FALSE" "status quo" { target *-*-* } .-1 } */
|
||||
// TODO(xfail)
|
||||
__analyzer_eval (g.i == 43); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_26 (struct coord *p, struct coord *q)
|
||||
{
|
||||
p->x = 42;
|
||||
q->y = 17;
|
||||
__analyzer_eval (p->x == 42); /* { dg-warning "TRUE" } */
|
||||
q->y = 17; /* could clobber p->x. */
|
||||
__analyzer_eval (p->x == 42); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (p->y); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (q->x); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (q->y == 17); /* { dg-warning "TRUE" } */
|
||||
|
@ -531,26 +503,21 @@ void test_26 (struct coord *p, struct coord *q)
|
|||
source value should update our knowledge about fields within
|
||||
the dest value. */
|
||||
*p = *q;
|
||||
__analyzer_eval (p->x); /* { dg-warning "UNKNOWN" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "TRUE" "status quo" { target *-*-* } .-1 } */
|
||||
// TODO(xfail): should have been overwritten
|
||||
__analyzer_eval (p->x); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (p->y == 17); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
|
||||
// TODO(xfail): should have been overwritten with q->y
|
||||
|
||||
__analyzer_eval (q->x); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (q->y == 17); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (q->y == 17); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
|
||||
}
|
||||
|
||||
void test_27 (struct coord *p)
|
||||
{
|
||||
memset (p, 0, sizeof (struct coord));
|
||||
__analyzer_eval (p->x == 0); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
|
||||
// TODO(xfail):
|
||||
__analyzer_eval (p->y == 0); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
|
||||
// TODO(xfail):
|
||||
__analyzer_eval (p->x == 0); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (p->y == 0); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_28 (struct coord *p)
|
||||
|
@ -663,6 +630,8 @@ void test_29a (struct coord p[])
|
|||
__analyzer_eval (q[-2].y == 107025); /* { dg-warning "TRUE" } */
|
||||
|
||||
q -= 2;
|
||||
__analyzer_eval (q == &p[7]); /* { dg-warning "UNKNOWN" } */
|
||||
// TODO: make this be TRUE
|
||||
|
||||
__analyzer_eval (q->x == 107024); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (q->y == 107025); /* { dg-warning "TRUE" } */
|
||||
|
@ -711,6 +680,7 @@ void test_29b (void)
|
|||
__analyzer_eval (q[-2].y == 107025); /* { dg-warning "TRUE" } */
|
||||
|
||||
q -= 2;
|
||||
__analyzer_eval (q == &p[7]); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_eval (q->x == 107024); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (q->y == 107025); /* { dg-warning "TRUE" } */
|
||||
|
@ -759,6 +729,7 @@ void test_29c (int len)
|
|||
__analyzer_eval (q[-2].y == 107025); /* { dg-warning "TRUE" } */
|
||||
|
||||
q -= 2;
|
||||
__analyzer_eval (q == &p[7]); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_eval (q->x == 107024); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (q->y == 107025); /* { dg-warning "TRUE" } */
|
||||
|
@ -936,12 +907,12 @@ void test_41 (void)
|
|||
union u u;
|
||||
u.i = 42;
|
||||
__analyzer_eval (u.i == 42); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (u.ptr == NULL); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (u.ptr == NULL); /* { dg-warning "UNKNOWN|FALSE" } */
|
||||
|
||||
/* Writes to a union member should invalidate knowledge about other members. */
|
||||
u.ptr = NULL;
|
||||
__analyzer_eval (u.ptr == NULL); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (u.i == 42); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (u.i == 42); /* { dg-warning "UNKNOWN|FALSE" } */
|
||||
}
|
||||
|
||||
void test_42 (void)
|
||||
|
@ -950,8 +921,7 @@ void test_42 (void)
|
|||
float f;
|
||||
i = 42;
|
||||
f = i;
|
||||
__analyzer_eval (f == 42.0); /* { dg-warning "TRUE" "PR 93356" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "disabled float comparisons" { target *-*-* } .-1 } */
|
||||
__analyzer_eval (f == 42.0); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_43 (void)
|
||||
|
@ -1063,10 +1033,8 @@ void test_51 (struct coord c)
|
|||
{
|
||||
struct coord d;
|
||||
memcpy (&d, &c, sizeof (struct coord));
|
||||
__analyzer_eval (c.x == d.x); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
|
||||
__analyzer_eval (c.y == d.y); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
|
||||
__analyzer_eval (c.x == d.x); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (c.y == d.y); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
struct big
|
||||
|
|
|
@ -14,8 +14,5 @@ void test_1 (void)
|
|||
void test_2 (void)
|
||||
{
|
||||
global_union.ptr_val = malloc (1024); /* { dg-message "allocated here" } */
|
||||
global_union.int_val = 0;
|
||||
} /* { dg-warning "leak of '<unknown>' " } */
|
||||
/* TODO: something better than "<unknown>". */
|
||||
/* TODO: better location for the leak. */
|
||||
|
||||
global_union.int_val = 0; /* { dg-warning "leak of 'global_union.ptr_val' " } */
|
||||
}
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
/* FIXME: we shouldn't need this. */
|
||||
/* { dg-additional-options "-fanalyzer-fine-grained" } */
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
void *global_ptr;
|
||||
|
@ -8,14 +5,12 @@ void *global_ptr;
|
|||
void test_1 (int i)
|
||||
{
|
||||
global_ptr = malloc (1024); /* { dg-message "allocated here" } */
|
||||
*(int *)&global_ptr = i; /* { dg-warning "leak of '<unknown>'" } */
|
||||
// TODO: something better than "<unknown>" here ^^^
|
||||
*(int *)&global_ptr = i; /* { dg-warning "leak of 'global_ptr'" } */
|
||||
}
|
||||
|
||||
void test_2 (int i)
|
||||
{
|
||||
void *p = malloc (1024); /* { dg-message "allocated here" "" { xfail *-*-* } } */
|
||||
// TODO(xfail)
|
||||
void *p = malloc (1024); /* { dg-message "allocated here" } */
|
||||
global_ptr = p;
|
||||
*(int *)&p = i;
|
||||
p = global_ptr;
|
||||
|
|
|
@ -16,7 +16,5 @@ void test (int *p, int i, int j)
|
|||
|
||||
__analyzer_eval (p[3] == 42); /* { dg-warning "UNKNOWN" } */
|
||||
__analyzer_eval (p[i] == 17); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (p[j] == 17); /* { dg-warning "UNKNOWN" "desired" { xfail *-*-* } } */
|
||||
/* { dg-bogus "TRUE" "status quo" { xfail *-*-* } .-1 } */
|
||||
// FIXME(xfails) ^^^
|
||||
__analyzer_eval (p[j] == 17); /* { dg-warning "UNKNOWN" } */
|
||||
}
|
||||
|
|
25
gcc/testsuite/gcc.dg/analyzer/data-model-20.c
Normal file
25
gcc/testsuite/gcc.dg/analyzer/data-model-20.c
Normal file
|
@ -0,0 +1,25 @@
|
|||
/* { dg-additional-options "-Wno-analyzer-too-complex" } */
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
struct foo { int dummy; };
|
||||
|
||||
struct foo **
|
||||
test (int n) {
|
||||
struct foo **arr;
|
||||
int i;
|
||||
|
||||
if ((arr = (struct foo **)malloc(n * sizeof(struct foo *))) == NULL)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
if ((arr[i] = (struct foo *)malloc(sizeof(struct foo))) == NULL) {
|
||||
for (; i >= 0; i++) {
|
||||
free(arr[i]); /* { dg-bogus "double-'free'" } */
|
||||
}
|
||||
free(arr);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
|
@ -90,11 +90,20 @@ void unref (base_obj *obj)
|
|||
{
|
||||
if (--obj->ob_refcnt == 0) /* { dg-bogus "dereference of uninitialized pointer 'obj'" } */
|
||||
obj->ob_type->tp_dealloc (obj);
|
||||
/* { dg-warning "dereference of NULL 'obj'" "deref of NULL" { target *-*-* } .-2 } */
|
||||
/* FIXME: ideally we wouldn't issue this, as we've already issued a
|
||||
warning about str_obj which is now in the "stop" state; the cast
|
||||
confuses things. */
|
||||
}
|
||||
|
||||
void test_1 (const char *str)
|
||||
{
|
||||
base_obj *obj = new_string_obj (str);
|
||||
//__analyzer_dump();
|
||||
unref (obj);
|
||||
} /* { dg-bogus "leak" } */
|
||||
} /* { dg-bogus "leak" "" { xfail *-*-* } } */
|
||||
/* XFAIL (false leak):
|
||||
Given that we only know "len" symbolically, this line:
|
||||
str_obj->str_buf[len] = '\0';
|
||||
is a symbolic write which could clobber the ob_type or ob_refcnt.
|
||||
It reports a leak when following the path where the refcount is clobbered
|
||||
to be a value that leads to the deallocator not being called. */
|
||||
|
|
|
@ -86,8 +86,10 @@ void test_1 (const char *str)
|
|||
//__analyzer_dump();
|
||||
if (obj)
|
||||
unref (obj);
|
||||
} /* { dg-bogus "leak of 'obj'" "" { xfail *-*-* } } */
|
||||
/* TODO(xfail): the false leak report involves the base_obj.ob_refcnt
|
||||
being 1, but the string_obj.str_base.ob_refcnt being unknown (when
|
||||
they ought to be the same region), thus allowing for a path in which
|
||||
the object is allocated but not freed. */
|
||||
} /* { dg-bogus "leak" "" { xfail *-*-* } } */
|
||||
/* XFAIL (false leak):
|
||||
Given that we only know "len" symbolically, this line:
|
||||
str_obj->str_buf[len] = '\0';
|
||||
is a symbolic write which could clobber the ob_type or ob_refcnt.
|
||||
It reports a leak when following the path where the refcount is clobbered
|
||||
to be a value that leads to the deallocator not being called. */
|
||||
|
|
|
@ -75,9 +75,10 @@ void test_1 (const char *str)
|
|||
string_obj *obj = new_string_obj (str);
|
||||
if (obj)
|
||||
unref (obj);
|
||||
} /* { dg-bogus "leak of 'obj'" "" { xfail *-*-* } } */
|
||||
/* TODO(xfail): the false leak report involves the base_obj.ob_refcnt
|
||||
being 1, but the string_obj.str_base.ob_refcnt being unknown (when
|
||||
they ought to be the same region), thus allowing for a path in which
|
||||
the object is allocated but not freed. */
|
||||
|
||||
} /* { dg-bogus "leak" "" { xfail *-*-* } } */
|
||||
/* XFAIL (false leak):
|
||||
Given that we only know "len" symbolically, this line:
|
||||
str_obj->str_buf[len] = '\0';
|
||||
is a symbolic write which could clobber the ob_type or ob_refcnt.
|
||||
It reports a leak when following the path where the refcount is clobbered
|
||||
to be a value that leads to the deallocator not being called. */
|
||||
|
|
|
@ -5,34 +5,33 @@
|
|||
#include <stdlib.h>
|
||||
#include "analyzer-decls.h"
|
||||
|
||||
typedef struct base_obj base_obj;
|
||||
typedef struct type_obj type_obj;
|
||||
typedef struct string_obj string_obj;
|
||||
|
||||
struct base_obj
|
||||
typedef struct base_obj
|
||||
{
|
||||
struct type_obj *ob_type;
|
||||
int ob_refcnt;
|
||||
};
|
||||
} base_obj;
|
||||
|
||||
struct type_obj
|
||||
typedef struct type_obj
|
||||
{
|
||||
base_obj tp_base;
|
||||
};
|
||||
void (*tp_dealloc) (base_obj *);
|
||||
} type_obj;
|
||||
|
||||
struct string_obj
|
||||
typedef struct boxed_int_obj
|
||||
{
|
||||
base_obj str_base;
|
||||
size_t str_len;
|
||||
char str_buf[];
|
||||
};
|
||||
base_obj int_base;
|
||||
int int_val;
|
||||
} boxed_int_obj;
|
||||
|
||||
extern void int_del (base_obj *);
|
||||
|
||||
type_obj type_type = {
|
||||
{ &type_type, 1},
|
||||
{ &type_type, 1}
|
||||
};
|
||||
|
||||
type_obj str_type = {
|
||||
{ &str_type, 1},
|
||||
type_obj boxed_int_type = {
|
||||
{ &type_type, 1},
|
||||
int_del
|
||||
};
|
||||
|
||||
base_obj *alloc_obj (type_obj *ob_type, size_t sz)
|
||||
|
@ -45,20 +44,28 @@ base_obj *alloc_obj (type_obj *ob_type, size_t sz)
|
|||
return obj;
|
||||
}
|
||||
|
||||
void unref (base_obj *obj)
|
||||
base_obj *new_int_obj (int val)
|
||||
{
|
||||
//__analyzer_dump();
|
||||
if (--obj->ob_refcnt == 0)
|
||||
free (obj);
|
||||
boxed_int_obj *int_obj
|
||||
= (boxed_int_obj *)alloc_obj (&boxed_int_type, sizeof (boxed_int_obj));
|
||||
if (!int_obj)
|
||||
return NULL;
|
||||
int_obj->int_val = val;
|
||||
return (base_obj *)int_obj;
|
||||
}
|
||||
|
||||
void test_1 ()
|
||||
void unref (base_obj *obj)
|
||||
{
|
||||
base_obj *obj = alloc_obj (&str_type, sizeof (string_obj));
|
||||
if (obj)
|
||||
{
|
||||
__analyzer_dump_num_heap_regions (); /* { dg-warning "num heap regions: '1'" } */
|
||||
unref (obj);
|
||||
__analyzer_dump_num_heap_regions (); /* { dg-warning "num heap regions: '0'" } */
|
||||
}
|
||||
if (--obj->ob_refcnt == 0)
|
||||
obj->ob_type->tp_dealloc (obj);
|
||||
}
|
||||
|
||||
void test_1 (const char *str)
|
||||
{
|
||||
base_obj *obj = new_int_obj (42);
|
||||
if (!obj)
|
||||
return;
|
||||
__analyzer_eval (((boxed_int_obj *)obj)->int_val == 42); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (obj->ob_refcnt == 1); /* { dg-warning "TRUE" } */
|
||||
unref (obj);
|
||||
} /* { dg-bogus "leak" "" } */
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
#include <stdlib.h>
|
||||
#include "analyzer-decls.h"
|
||||
|
||||
/* Verify that we don't accumulate state after a malloc/free pair. */
|
||||
|
||||
void test (void)
|
||||
{
|
||||
void *ptr;
|
||||
__analyzer_dump_num_heap_regions (); /* { dg-warning "num heap regions: '0'" } */
|
||||
ptr = malloc (1024);
|
||||
__analyzer_dump_num_heap_regions (); /* { dg-warning "num heap regions: '1'" } */
|
||||
free (ptr);
|
||||
__analyzer_dump_num_heap_regions (); /* { dg-warning "num heap regions: '0'" } */
|
||||
}
|
|
@ -21,6 +21,5 @@ void test (void)
|
|||
|
||||
struct base *bp = (struct base *)&s;
|
||||
|
||||
__analyzer_eval (bp->i == 3); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
|
||||
__analyzer_eval (bp->i == 3); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
|
11
gcc/testsuite/gcc.dg/analyzer/describe-1.c
Normal file
11
gcc/testsuite/gcc.dg/analyzer/describe-1.c
Normal file
|
@ -0,0 +1,11 @@
|
|||
/* Smoketest for __analyzer_describe. */
|
||||
|
||||
#include "analyzer-decls.h"
|
||||
|
||||
void test (int i)
|
||||
{
|
||||
__analyzer_describe (0, 42); /* { dg-warning "svalue: '\\(int\\)42'" } */
|
||||
__analyzer_describe (0, i); /* { dg-warning "svalue: 'INIT_VAL\\(i.*\\)'" } */
|
||||
__analyzer_describe (0, &i); /* { dg-warning "svalue: '&i'" } */
|
||||
/* Further cases would risk overspecifying things. */
|
||||
}
|
|
@ -18,8 +18,7 @@ int *test (int *buf, int n, int *out)
|
|||
|
||||
/* A loop, to ensure we have phi nodes. */
|
||||
for (i = 0; i < n; i++)
|
||||
result[i] = buf[i] + i; /* { dg-warning "possibly-NULL" "" { xfail *-*-* } } */
|
||||
/* TODO(xfail): why isn't the warning appearing? */
|
||||
result[i] = buf[i] + i; /* { dg-warning "possibly-NULL" } */
|
||||
|
||||
/* Example of a "'" (to test quoting). */
|
||||
*out = some_call (i, 'a');
|
||||
|
|
|
@ -47,7 +47,7 @@ void test (void)
|
|||
{
|
||||
default:
|
||||
case 0:
|
||||
*pp = malloc (16);
|
||||
*pp = malloc (16); /* { dg-warning "leak" } */
|
||||
break;
|
||||
case 1:
|
||||
free (*pp);
|
||||
|
|
|
@ -19,31 +19,31 @@ void test (void)
|
|||
{
|
||||
default:
|
||||
case 0:
|
||||
p0 = malloc (16);
|
||||
p0 = malloc (16); /* { dg-warning "leak" } */
|
||||
break;
|
||||
case 1:
|
||||
free (p0); /* { dg-warning "double-'free' of 'p0'" } */
|
||||
free (p0); /* { dg-warning "double-'free' of 'p0'" "" { xfail *-*-* } } */
|
||||
break;
|
||||
|
||||
case 2:
|
||||
p1 = malloc (16);
|
||||
p1 = malloc (16); /* { dg-warning "leak" } */
|
||||
break;
|
||||
case 3:
|
||||
free (p1); /* { dg-warning "double-'free' of 'p1'" } */
|
||||
free (p1); /* { dg-warning "double-'free' of 'p1'" "" { xfail *-*-* } } */
|
||||
break;
|
||||
|
||||
case 4:
|
||||
p2 = malloc (16);
|
||||
p2 = malloc (16); /* { dg-warning "leak" } */
|
||||
break;
|
||||
case 5:
|
||||
free (p2); /* { dg-warning "double-'free' of 'p2'" } */
|
||||
free (p2); /* { dg-warning "double-'free' of 'p2'" "" { xfail *-*-* } } */
|
||||
break;
|
||||
|
||||
case 6:
|
||||
p3 = malloc (16);
|
||||
p3 = malloc (16); /* { dg-warning "leak" } */
|
||||
break;
|
||||
case 7:
|
||||
free (p3); /* { dg-warning "double-'free' of 'p3'" } */
|
||||
free (p3); /* { dg-warning "double-'free' of 'p3'" "" { xfail *-*-* } } */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
62
gcc/testsuite/gcc.dg/analyzer/feasibility-1.c
Normal file
62
gcc/testsuite/gcc.dg/analyzer/feasibility-1.c
Normal file
|
@ -0,0 +1,62 @@
|
|||
#include "analyzer-decls.h"
|
||||
|
||||
void test_1 (void)
|
||||
{
|
||||
__analyzer_dump_path (); /* { dg-message "path" } */
|
||||
}
|
||||
|
||||
void test_2 (int flag)
|
||||
{
|
||||
if (flag)
|
||||
__analyzer_dump_path (); /* { dg-message "path" } */
|
||||
}
|
||||
|
||||
void test_3 (int flag)
|
||||
{
|
||||
if (flag)
|
||||
if (!flag)
|
||||
__analyzer_dump_path (); /* { dg-bogus "path" } */
|
||||
}
|
||||
|
||||
int global_for_test_4;
|
||||
static void __attribute__((noinline)) called_by_test_4 () {}
|
||||
void test_4 (void)
|
||||
{
|
||||
/* Verify that a state change that happens in a stmt that
|
||||
isn't the first within its BB can affect path feasibility. */
|
||||
global_for_test_4 = 0;
|
||||
global_for_test_4 = 1;
|
||||
/* Thwart the optimizer. */
|
||||
called_by_test_4 ();
|
||||
if (global_for_test_4)
|
||||
__analyzer_dump_path (); /* { dg-message "path" } */
|
||||
}
|
||||
|
||||
/* Verify that loops don't confuse the feasibility checker. */
|
||||
|
||||
void test_5 (void)
|
||||
{
|
||||
for (int i = 0; i < 1024; i++)
|
||||
{
|
||||
}
|
||||
__analyzer_dump_path (); /* { dg-message "path" } */
|
||||
}
|
||||
|
||||
/* Reproducer for an issue seen with CVE-2005-1689 (PR analyzer/96374): if we
|
||||
take the shortest path and update state and check feasibility per-edge, we
|
||||
can erroneously reject valid diagnostics. */
|
||||
|
||||
int test_6 (int a, int b)
|
||||
{
|
||||
int problem = 0;
|
||||
if (a)
|
||||
problem = 1;
|
||||
if (b)
|
||||
{
|
||||
if (!problem)
|
||||
problem = 2;
|
||||
__analyzer_dump_path (); /* { dg-message "path" "" { xfail *-*-* } } */
|
||||
/* XFAIL is PR analyzer/96374. */
|
||||
}
|
||||
return problem;
|
||||
}
|
24
gcc/testsuite/gcc.dg/analyzer/first-field-1.c
Normal file
24
gcc/testsuite/gcc.dg/analyzer/first-field-1.c
Normal file
|
@ -0,0 +1,24 @@
|
|||
#include "analyzer-decls.h"
|
||||
|
||||
typedef struct base_obj
|
||||
{
|
||||
int m_first;
|
||||
int m_second;
|
||||
} base_obj;
|
||||
|
||||
typedef struct sub_obj
|
||||
{
|
||||
base_obj base;
|
||||
} sub_obj;
|
||||
|
||||
void test (sub_obj *sub)
|
||||
{
|
||||
sub->base.m_first = 1;
|
||||
sub->base.m_second = 2;
|
||||
__analyzer_eval (sub->base.m_first == 1); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (sub->base.m_second == 2); /* { dg-warning "TRUE" } */
|
||||
|
||||
base_obj *base = (struct base_obj *)sub;
|
||||
__analyzer_eval (base->m_first == 1); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (base->m_second == 2); /* { dg-warning "TRUE" } */
|
||||
}
|
33
gcc/testsuite/gcc.dg/analyzer/first-field-2.c
Normal file
33
gcc/testsuite/gcc.dg/analyzer/first-field-2.c
Normal file
|
@ -0,0 +1,33 @@
|
|||
/* A toy re-implementation of CPython's object model. */
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "analyzer-decls.h"
|
||||
|
||||
typedef struct base_obj base_obj;
|
||||
typedef struct string_obj string_obj;
|
||||
|
||||
struct base_obj
|
||||
{
|
||||
int ob_refcnt;
|
||||
};
|
||||
|
||||
struct string_obj
|
||||
{
|
||||
base_obj str_base;
|
||||
size_t str_len;
|
||||
char str_buf[];
|
||||
};
|
||||
|
||||
base_obj *alloc_obj (const char *str)
|
||||
{
|
||||
size_t len = strlen (str);
|
||||
base_obj *obj = (base_obj *)malloc (sizeof (string_obj) + len + 1);
|
||||
if (!obj)
|
||||
return NULL;
|
||||
obj->ob_refcnt = 1;
|
||||
string_obj *str_obj = (string_obj *)obj;
|
||||
__analyzer_eval (str_obj->str_base.ob_refcnt == 1); /* { dg-warning "TRUE" } */
|
||||
return obj;
|
||||
}
|
136
gcc/testsuite/gcc.dg/analyzer/init.c
Normal file
136
gcc/testsuite/gcc.dg/analyzer/init.c
Normal file
|
@ -0,0 +1,136 @@
|
|||
/* Tests of brace-enclosed initializers
|
||||
Some of these use the CONSTRUCTOR tree code, but it appears
|
||||
only for a full zero-init; it appears that by the time the analyzer
|
||||
runs that this initialization has been converted into field-wise
|
||||
gimple assign stmts, with just "zero-init everything" CONSTRUCTORs
|
||||
and "clobber" CONSTRUCTORs. */
|
||||
|
||||
#include "analyzer-decls.h"
|
||||
|
||||
struct coord
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
};
|
||||
|
||||
struct tri
|
||||
{
|
||||
struct coord v[3];
|
||||
};
|
||||
|
||||
union iap
|
||||
{
|
||||
int i;
|
||||
void *p;
|
||||
};
|
||||
|
||||
void test_1 (void)
|
||||
{
|
||||
struct coord c = {3, 4};
|
||||
__analyzer_eval (c.x == 3); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (c.y == 4); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_2 (void)
|
||||
{
|
||||
struct coord c = {3};
|
||||
__analyzer_eval (c.x == 3); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (c.y == 0); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_3 (void)
|
||||
{
|
||||
struct coord c = {};
|
||||
__analyzer_eval (c.x == 0); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (c.y == 0); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_4 (void)
|
||||
{
|
||||
int c[2] = {3, 4};
|
||||
__analyzer_eval (c[0] == 3); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (c[1] == 4); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_5 (void)
|
||||
{
|
||||
int c[2] = {3};
|
||||
__analyzer_eval (c[0] == 3); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (c[1] == 0); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_6 (void)
|
||||
{
|
||||
int c[2] = {};
|
||||
__analyzer_eval (c[0] == 0); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (c[1] == 0); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_7 (void)
|
||||
{
|
||||
struct coord c[2] = {{3, 4}, {5, 6}};
|
||||
__analyzer_eval (c[0].x == 3); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (c[0].y == 4); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (c[1].x == 5); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (c[1].y == 6); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_8 (void)
|
||||
{
|
||||
struct coord c[2] = {{3}, {5}};
|
||||
__analyzer_eval (c[0].x == 3); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (c[0].y == 0); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (c[1].x == 5); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (c[1].y == 0); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_9 (void)
|
||||
{
|
||||
struct coord c[2] = {{}, {}};
|
||||
__analyzer_eval (c[0].x == 0); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (c[0].y == 0); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (c[1].x == 0); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (c[1].y == 0); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_10 (void)
|
||||
{
|
||||
struct coord c[2] = {{.y = 4, .x = 3}, {5, 6}};
|
||||
__analyzer_eval (c[0].x == 3); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (c[0].y == 4); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (c[1].x == 5); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (c[1].y == 6); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_11 (void)
|
||||
{
|
||||
struct coord c[2] = {{.y = 4}, {5, 6}};
|
||||
__analyzer_eval (c[0].x == 0); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (c[0].y == 4); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (c[1].x == 5); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (c[1].y == 6); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_12 (void)
|
||||
{
|
||||
struct tri t = {};
|
||||
__analyzer_eval (t.v[0].x == 0); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (t.v[2].y == 0); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_13 (void)
|
||||
{
|
||||
struct tri t = {3, 4, 5, 6, 7, 8};
|
||||
__analyzer_eval (t.v[0].x == 3); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (t.v[0].y == 4); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (t.v[1].x == 5); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (t.v[1].y == 6); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (t.v[2].x == 7); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (t.v[2].y == 8); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
void test_14 (void)
|
||||
{
|
||||
union iap u = {};
|
||||
__analyzer_eval (u.i == 0); /* { dg-warning "TRUE" } */
|
||||
}
|
9
gcc/testsuite/gcc.dg/analyzer/leak-2.c
Normal file
9
gcc/testsuite/gcc.dg/analyzer/leak-2.c
Normal file
|
@ -0,0 +1,9 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
void *ptr;
|
||||
|
||||
void *test (void)
|
||||
{
|
||||
ptr = malloc (1024);
|
||||
ptr = NULL; /* { dg-warning "leak of 'ptr'" } */
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
#include "analyzer-decls.h"
|
||||
|
||||
struct iter
|
||||
{
|
||||
int start;
|
||||
int end;
|
||||
int step;
|
||||
int val;
|
||||
};
|
||||
|
||||
struct iter * __attribute__((noinline))
|
||||
iter_new (int start, int end, int step)
|
||||
{
|
||||
struct iter *it = (struct iter *)malloc (sizeof (struct iter));
|
||||
if (!it)
|
||||
abort ();
|
||||
it->start = start;
|
||||
it->end = end;
|
||||
it->step = step;
|
||||
it->val = start;
|
||||
return it;
|
||||
}
|
||||
|
||||
int __attribute__((noinline))
|
||||
iter_done_p (struct iter *it)
|
||||
{
|
||||
return it->val >= it->end;
|
||||
}
|
||||
|
||||
void __attribute__((noinline))
|
||||
iter_next (struct iter *it)
|
||||
{
|
||||
it->val += it->step;
|
||||
}
|
||||
|
||||
/* Example of an iterator object, to see how well we cope with a well-disguised
|
||||
iteration from 0 to n with a step of 1. */
|
||||
|
||||
void test(int n)
|
||||
{
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
|
||||
|
||||
struct iter *it = iter_new (0, n, 1);
|
||||
while (!iter_done_p (it))
|
||||
{
|
||||
__analyzer_eval (it->val < n); /* { dg-warning "TRUE" "true" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
/* TODO(xfail^^^): ideally we ought to figure out i > 0 after 1st iteration. */
|
||||
|
||||
__analyzer_eval (it->val == 0); /* { dg-warning "TRUE" "true on 1st iter" } */
|
||||
/* { dg-warning "UNKNOWN" "unknown" { target *-*-* } .-1 } */
|
||||
/* TODO: should we ought to figure out i > 0 after 1st iteration? */
|
||||
|
||||
__analyzer_eval (it->val >= 0); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
|
||||
|
||||
iter_next (it);
|
||||
}
|
||||
|
||||
__analyzer_eval (it->val >= n); /* { dg-warning "TRUE" "true" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
|
||||
__analyzer_eval (it->val == n); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
|
||||
/* TODO(xfail^^^): it only figures out i >= 256, rather than i == 256. */
|
||||
|
||||
free (it);
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
|
||||
}
|
31
gcc/testsuite/gcc.dg/analyzer/loop-0-up-to-n-by-1.c
Normal file
31
gcc/testsuite/gcc.dg/analyzer/loop-0-up-to-n-by-1.c
Normal file
|
@ -0,0 +1,31 @@
|
|||
#include "analyzer-decls.h"
|
||||
|
||||
void test(int n)
|
||||
{
|
||||
int i;
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
__analyzer_eval (i < n); /* { dg-warning "TRUE" } */
|
||||
/* (should report TRUE twice). */
|
||||
|
||||
__analyzer_eval (i == 0); /* { dg-warning "TRUE" "1st" } */
|
||||
/* { dg-warning "FALSE" "2nd" { xfail *-*-* } .-1 } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-2 } */
|
||||
/* TODO(xfail^^^): ideally we ought to figure out i > 0 after 1st iteration. */
|
||||
|
||||
__analyzer_eval (i >= 0); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
|
||||
}
|
||||
|
||||
__analyzer_eval (i >= n); /* { dg-warning "TRUE" "true" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
|
||||
__analyzer_eval (i == n); /* { dg-warning "TRUE" "true" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
/* TODO(xfail^^^): it only figures out i >= 256, rather than i == 256. */
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
|
||||
}
|
|
@ -14,10 +14,7 @@ void test(void)
|
|||
|
||||
|
||||
for (u.i=0; u.i<256; u.i++) {
|
||||
__analyzer_eval (u.i < 256); /* { dg-warning "TRUE" "1st" } */
|
||||
/* { dg-warning "TRUE" "2nd" { xfail *-*-* } .-1 } */
|
||||
/* { dg-bogus "UNKNOWN" "status quo" { xfail *-*-* } .-2 } */
|
||||
/* (should report TRUE twice). */
|
||||
__analyzer_eval (u.i < 256); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
|
||||
|
||||
|
@ -26,11 +23,10 @@ void test(void)
|
|||
/* TODO(xfail^^^): we're only capturing the first iteration, so
|
||||
we erroneously get i == 0. */
|
||||
|
||||
//__analyzer_eval (u.i >= 0); /* { d-todo-g-warning "TRUE" } */
|
||||
__analyzer_eval (u.i >= 0); /* { dg-warning "TRUE" } */
|
||||
}
|
||||
|
||||
__analyzer_eval (u.i >= 256); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
|
||||
__analyzer_eval (u.i >= 256); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_eval (u.i == 256); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
|
||||
|
|
|
@ -6,11 +6,8 @@ void test(int c)
|
|||
char *buffer = (char*)malloc(256);
|
||||
|
||||
for (i=0; i<255; i++) {
|
||||
buffer[i] = c; /* { dg-warning "use after 'free' of 'buffer'" } */
|
||||
/* BUG: the malloc could have failed
|
||||
TODO: the checker doesn't yet pick up on this, perhaps
|
||||
due to the pointer arithmetic not picking up on the
|
||||
state */
|
||||
buffer[i] = c; /* { dg-warning "use after 'free' of 'buffer'" "use after free" { xfail *-*-* } } */
|
||||
/* { dg-warning "possibly-NULL 'buffer'" "deref of unchecked" { target *-*-* } .-1 } */
|
||||
free(buffer); /* { dg-warning "double-'free' of 'buffer'" } */
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
// FIXME:
|
||||
/* { dg-additional-options "-fno-analyzer-state-purge" } */
|
||||
|
||||
/* Example of nested loops. */
|
||||
|
||||
#include "analyzer-decls.h"
|
||||
|
@ -13,8 +10,7 @@ void test(void)
|
|||
|
||||
for (i=0; i<256; i++) {
|
||||
|
||||
__analyzer_eval (i >= 0); /* { dg-warning "TRUE" "true" } */
|
||||
/* { dg-warning "UNKNOWN" "unknown" { target *-*-* } .-1 } */
|
||||
__analyzer_eval (i >= 0); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_eval (i < 256); /* { dg-warning "TRUE" } */
|
||||
|
||||
|
@ -23,7 +19,9 @@ void test(void)
|
|||
__analyzer_eval (j >= 0); /* { dg-warning "TRUE" "true" } */
|
||||
/* { dg-warning "UNKNOWN" "unknown" { target *-*-* } .-1 } */
|
||||
|
||||
__analyzer_eval (j < 256); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (j < 256); /* { dg-warning "TRUE" "true" } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
/* TODO(xfail^^^): should report TRUE twice. */
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "3 processed enodes" } */
|
||||
|
||||
|
@ -32,7 +30,8 @@ void test(void)
|
|||
__analyzer_eval (k >= 0); /* { dg-warning "TRUE" "true" } */
|
||||
/* { dg-warning "UNKNOWN" "unknown" { target *-*-* } .-1 } */
|
||||
|
||||
__analyzer_eval (k < 256); /* { dg-warning "TRUE" } */
|
||||
__analyzer_eval (k < 256); /* { dg-warning "TRUE" "true" } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "4 processed enodes" } */
|
||||
}
|
||||
|
|
35
gcc/testsuite/gcc.dg/analyzer/loop-n-down-to-1-by-1.c
Normal file
35
gcc/testsuite/gcc.dg/analyzer/loop-n-down-to-1-by-1.c
Normal file
|
@ -0,0 +1,35 @@
|
|||
#include "analyzer-decls.h"
|
||||
|
||||
void test(int n)
|
||||
{
|
||||
int i;
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
|
||||
|
||||
for (i = n; i > 0; i--) {
|
||||
__analyzer_eval (i > 0); /* { dg-warning "TRUE" "true" } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
/* TODO(xfail^^^): should report TRUE twice. */
|
||||
|
||||
__analyzer_eval (i == n); /* { dg-warning "TRUE" "1st" } */
|
||||
/* { dg-warning "FALSE" "2nd" { xfail *-*-* } .-1 } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-2 } */
|
||||
/* TODO(xfail^^^): ideally we ought to figure out i > 0 after 1st iteration. */
|
||||
|
||||
__analyzer_eval (i <= n); /* { dg-warning "TRUE" "1st" } */
|
||||
/* { dg-warning "TRUE" "2nd" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-2 } */
|
||||
/* TODO(xfail^^^): ideally we ought to figure out i >= 0 for all iterations. */
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
|
||||
}
|
||||
|
||||
__analyzer_eval (i <= 0); /* { dg-warning "TRUE" "true" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
|
||||
__analyzer_eval (i == 0); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
|
||||
/* TODO(xfail^^^): it only figures out i >= 256, rather than i == 256. */
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
|
||||
}
|
35
gcc/testsuite/gcc.dg/analyzer/loop-start-down-to-end-by-1.c
Normal file
35
gcc/testsuite/gcc.dg/analyzer/loop-start-down-to-end-by-1.c
Normal file
|
@ -0,0 +1,35 @@
|
|||
#include "analyzer-decls.h"
|
||||
|
||||
void test(int start, int end, int step)
|
||||
{
|
||||
int i;
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
|
||||
|
||||
for (i = start; i > end; i --) {
|
||||
__analyzer_eval (i > end); /* { dg-warning "TRUE" "true" } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
/* TODO(xfail^^^): should report TRUE twice. */
|
||||
|
||||
__analyzer_eval (i == start); /* { dg-warning "TRUE" "1st" } */
|
||||
/* { dg-warning "FALSE" "2nd" { xfail *-*-* } .-1 } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-2 } */
|
||||
/* TODO(xfail^^^): ideally we ought to figure out i > 0 after 1st iteration. */
|
||||
|
||||
__analyzer_eval (i <= start); /* { dg-warning "TRUE" "true" } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
/* TODO(xfail^^^): should report TRUE twice. */
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
|
||||
}
|
||||
|
||||
__analyzer_eval (i >= end); /* { dg-warning "TRUE" "true" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
|
||||
// FIXME: do we know this? What if we overshoot?
|
||||
__analyzer_eval (i == end); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
|
||||
/* TODO(xfail^^^): it only figures out i >= 256, rather than i == 256. */
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
#include "analyzer-decls.h"
|
||||
|
||||
void test(int start, int end, int step)
|
||||
{
|
||||
int i;
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
|
||||
|
||||
for (i = start; i > end; i -= step) {
|
||||
__analyzer_eval (i > end); /* { dg-warning "TRUE" "true" } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
/* TODO(xfail^^^): should report TRUE twice. */
|
||||
|
||||
__analyzer_eval (i == start); /* { dg-warning "TRUE" "1st" } */
|
||||
/* { dg-warning "FALSE" "2nd" { xfail *-*-* } .-1 } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-2 } */
|
||||
/* TODO(xfail^^^): ideally we ought to figure out i > 0 after 1st iteration. */
|
||||
|
||||
/* We don't know the direction of step. */
|
||||
__analyzer_eval (i <= start); /* { dg-warning "TRUE" "true" } */
|
||||
/* { dg-warning "UNKNOWN" "unknown" { target *-*-* } .-1 } */
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
|
||||
}
|
||||
|
||||
__analyzer_eval (i <= end); /* { dg-warning "TRUE" "true" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
|
||||
}
|
36
gcc/testsuite/gcc.dg/analyzer/loop-start-to-end-by-step.c
Normal file
36
gcc/testsuite/gcc.dg/analyzer/loop-start-to-end-by-step.c
Normal file
|
@ -0,0 +1,36 @@
|
|||
#include "analyzer-decls.h"
|
||||
|
||||
void test(int start, int end, int step)
|
||||
{
|
||||
int i;
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
|
||||
|
||||
for (i = start; i < end; i += step) {
|
||||
__analyzer_eval (i < end); /* { dg-warning "TRUE" "true" } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
/* TODO(xfail^^^): should report TRUE twice. */
|
||||
|
||||
__analyzer_eval (i == start); /* { dg-warning "TRUE" "1st" } */
|
||||
/* { dg-warning "FALSE" "2nd" { xfail *-*-* } .-1 } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-2 } */
|
||||
/* TODO(xfail^^^): ideally we ought to figure out i > 0 after 1st iteration. */
|
||||
|
||||
/* We don't know the direction of step. */
|
||||
__analyzer_eval (i >= start); /* { dg-warning "TRUE" "true" } */
|
||||
/* { dg-warning "UNKNOWN" "unknown" { target *-*-* } .-1 } */
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
|
||||
}
|
||||
|
||||
// FIXME: do we know this? What about direction of step?
|
||||
__analyzer_eval (i >= end); /* { dg-warning "TRUE" "true" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
|
||||
// FIXME: do we know this? What if we overshoot?
|
||||
__analyzer_eval (i == end); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
|
||||
/* TODO(xfail^^^): it only figures out i >= 256, rather than i == 256. */
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
|
||||
}
|
34
gcc/testsuite/gcc.dg/analyzer/loop-start-up-to-end-by-1.c
Normal file
34
gcc/testsuite/gcc.dg/analyzer/loop-start-up-to-end-by-1.c
Normal file
|
@ -0,0 +1,34 @@
|
|||
#include "analyzer-decls.h"
|
||||
|
||||
void test(int start, int end)
|
||||
{
|
||||
int i;
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
|
||||
|
||||
for (i = start; i < end; i++) {
|
||||
__analyzer_eval (i < end); /* { dg-warning "TRUE" "true" } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
/* TODO(xfail^^^): should report TRUE twice. */
|
||||
|
||||
__analyzer_eval (i == start); /* { dg-warning "TRUE" "1st" } */
|
||||
/* { dg-warning "FALSE" "2nd" { xfail *-*-* } .-1 } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-2 } */
|
||||
/* TODO(xfail^^^): ideally we ought to figure out i > 0 after 1st iteration. */
|
||||
|
||||
__analyzer_eval (i >= start); /* { dg-warning "TRUE" "true" } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
/* TODO(xfail^^^): should report TRUE twice. */
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
|
||||
}
|
||||
|
||||
__analyzer_eval (i >= end); /* { dg-warning "TRUE" "true" { xfail *-*-* } } */
|
||||
/* { dg-bogus "UNKNOWN" "unknown" { xfail *-*-* } .-1 } */
|
||||
|
||||
__analyzer_eval (i == end); /* { dg-warning "TRUE" "desired" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-1 } */
|
||||
/* TODO(xfail^^^): it only figures out i >= end, rather than i == end. */
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
/* { dg-additional-options "-fno-analyzer-state-purge" } */
|
||||
|
||||
#include "analyzer-decls.h"
|
||||
|
||||
void test(void)
|
||||
|
@ -12,15 +10,12 @@ void test(void)
|
|||
__analyzer_eval (i < 256); /* { dg-warning "TRUE" } */
|
||||
/* (should report TRUE twice). */
|
||||
|
||||
__analyzer_eval (i == 0); /* { dg-warning "TRUE" "1st" } */
|
||||
__analyzer_eval (i == 0); /* { dg-warning "TRUE" } */
|
||||
/* { dg-warning "FALSE" "2nd" { xfail *-*-* } .-1 } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-2 } */
|
||||
/* TODO(xfail^^^): ideally we ought to figure out i > 0 after 1st iteration. */
|
||||
|
||||
__analyzer_eval (i >= 0); /* { dg-warning "TRUE" "1st" } */
|
||||
/* { dg-warning "TRUE" "2nd" { xfail *-*-* } } */
|
||||
/* { dg-warning "UNKNOWN" "status quo" { target *-*-* } .-2 } */
|
||||
/* TODO(xfail^^^): ideally we ought to figure out i >= 0 for all iterations. */
|
||||
__analyzer_eval (i >= 0); /* { dg-warning "TRUE" } */
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
|
||||
}
|
||||
|
|
|
@ -360,7 +360,7 @@ void test_30 (void)
|
|||
struct link tmp;
|
||||
tmp.m_ptr = (struct link *)malloc (sizeof (struct link)); /* { dg-message "allocated here" } */
|
||||
} /* { dg-warning "leak of 'tmp.m_ptr'" } */
|
||||
/* { dg-bogus "leak of '<unknown>'" "" { xfail *-*-* } .-1 } */
|
||||
/* { dg-bogus "leak of '<unknown>'" "leak of unknown" { target *-*-* } .-1 } */
|
||||
|
||||
void test_31 (void)
|
||||
{
|
||||
|
@ -368,7 +368,7 @@ void test_31 (void)
|
|||
void *ptr = malloc (sizeof (struct link)); /* { dg-message "allocated here" } */
|
||||
tmp.m_ptr = (struct link *)ptr;
|
||||
} /* { dg-warning "leak of 'ptr'" } */
|
||||
/* { dg-bogus "leak of 'tmp.m_ptr'" "" { xfail *-*-* } .-1 } */
|
||||
/* { dg-bogus "leak of 'tmp.m_ptr'" "" { target *-*-* } .-1 } */
|
||||
|
||||
void test_32 (void)
|
||||
{
|
||||
|
@ -507,8 +507,7 @@ void test_42c (void)
|
|||
void *p = malloc (1024);
|
||||
void *q = p + 64;
|
||||
free (q - 64); /* this is probably OK. */
|
||||
} /* { dg-bogus "leak of 'p'" "" { xfail *-*-* } } */
|
||||
// TODO(xfail)
|
||||
} /* { dg-bogus "leak of 'p'" } */
|
||||
|
||||
#if 0
|
||||
void test_31 (void *p)
|
||||
|
@ -531,10 +530,8 @@ struct link global_link;
|
|||
void test_43 (void)
|
||||
{
|
||||
global_link.m_ptr = malloc (sizeof (struct link)); /* { dg-message "allocated here" } */
|
||||
global_link.m_ptr = NULL;
|
||||
} /* { dg-warning "leak of '<unknown>'" } */
|
||||
/* TODO: should be more precise than just '<unknown>', and
|
||||
ideally would be at the assigment to NULL. */
|
||||
global_link.m_ptr = NULL; /* { dg-warning "leak of 'global_link.m_ptr'" } */
|
||||
}
|
||||
|
||||
struct link *global_ptr;
|
||||
|
||||
|
@ -591,3 +588,16 @@ void test_48 (void)
|
|||
int *p = NULL; /* { dg-message "'p' is NULL" } */
|
||||
*p = 1; /* { dg-warning "dereference of NULL 'p'" } */
|
||||
}
|
||||
|
||||
/* As test_48, but where the assignment of NULL is not at the start of a BB. */
|
||||
|
||||
int test_49 (int i)
|
||||
{
|
||||
int *p;
|
||||
int x;
|
||||
|
||||
x = i * 2;
|
||||
p = NULL; /* { dg-message "'p' is NULL" } */
|
||||
*p = 1; /* { dg-warning "dereference of NULL 'p'" } */
|
||||
return x;
|
||||
}
|
||||
|
|
|
@ -10,11 +10,11 @@ void *hv (struct foo **tm)
|
|||
*tm = p;
|
||||
if (!p)
|
||||
abort ();
|
||||
return p; /* { dg-warning "leak of 'tm'" } */
|
||||
return p;
|
||||
}
|
||||
|
||||
void a5 (void)
|
||||
{
|
||||
struct bar *qb = NULL;
|
||||
hv (&qb);
|
||||
} /* { dg-warning "leak of '\\(struct foo \\*\\)qb'" } */
|
||||
} /* { dg-warning "leak of 'qb'" } */
|
||||
|
|
19
gcc/testsuite/gcc.dg/analyzer/malloc-in-loop.c
Normal file
19
gcc/testsuite/gcc.dg/analyzer/malloc-in-loop.c
Normal file
|
@ -0,0 +1,19 @@
|
|||
#include <stdlib.h>
|
||||
#include "analyzer-decls.h"
|
||||
|
||||
extern void foo (int *);
|
||||
|
||||
void test (int n)
|
||||
{
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
int *ptr = (int *)malloc (sizeof (int) * i);
|
||||
foo (ptr);
|
||||
free (ptr);
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
|
||||
}
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "1 processed enode" } */
|
||||
}
|
|
@ -61,111 +61,133 @@ void test (int i)
|
|||
| | |
|
||||
| | (2) calling 'make_boxed_int' from 'test'
|
||||
|
|
||||
+--> 'make_boxed_int': events 3-6
|
||||
+--> 'make_boxed_int': events 3-4
|
||||
|
|
||||
| NN | make_boxed_int (int i)
|
||||
| | ^~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (3) entry to 'make_boxed_int'
|
||||
|......
|
||||
| NN | {
|
||||
| NN | boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
|
||||
| | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (4) calling 'wrapped_malloc' from 'make_boxed_int'
|
||||
|
|
||||
+--> 'wrapped_malloc': events 5-6
|
||||
|
|
||||
| NN | void *wrapped_malloc (size_t size)
|
||||
| | ^~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (5) entry to 'wrapped_malloc'
|
||||
| NN | {
|
||||
| NN | return malloc (size);
|
||||
| | ~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (6) allocated here (state of '<unknown>': 'start' -> 'unchecked', NULL origin)
|
||||
|
|
||||
<------+
|
||||
|
|
||||
'make_boxed_int': events 7-10
|
||||
|
|
||||
| NN | boxed_int *result = (boxed_int *)wrapped_malloc (sizeof (boxed_int));
|
||||
| | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (7) returning to 'make_boxed_int' from 'wrapped_malloc'
|
||||
| NN | if (!result)
|
||||
| | ~
|
||||
| | ~
|
||||
| | |
|
||||
| | (4) following 'false' branch (when 'result' is non-NULL)...
|
||||
| | (8) assuming 'result' is non-NULL (state of 'result': 'unchecked' -> 'nonnull', NULL origin)
|
||||
| | (9) following 'false' branch (when 'result' is non-NULL)...
|
||||
| NN | abort ();
|
||||
| NN | result->i = i;
|
||||
| | ~~~~~~~~~~~~~
|
||||
| | ~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (5) ...to here
|
||||
| NN | return result;
|
||||
| | ~~~~~~
|
||||
| | |
|
||||
| | (6) state of '<return-value>': 'start' -> 'nonnull' (origin: NULL)
|
||||
| | (10) ...to here
|
||||
|
|
||||
<------+
|
||||
|
|
||||
'test': events 7-8
|
||||
'test': events 11-12
|
||||
|
|
||||
| NN | boxed_int *obj = make_boxed_int (i);
|
||||
| | ^~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (7) returning to 'test' from 'make_boxed_int'
|
||||
| | (11) returning to 'test' from 'make_boxed_int'
|
||||
| NN |
|
||||
| NN | free_boxed_int (obj);
|
||||
| | ~~~~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (8) calling 'free_boxed_int' from 'test'
|
||||
| | (12) calling 'free_boxed_int' from 'test'
|
||||
|
|
||||
+--> 'free_boxed_int': events 9-10
|
||||
+--> 'free_boxed_int': events 13-14
|
||||
|
|
||||
| NN | free_boxed_int (boxed_int *bi)
|
||||
| | ^~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (9) entry to 'free_boxed_int'
|
||||
| | (13) entry to 'free_boxed_int'
|
||||
| NN | {
|
||||
| NN | wrapped_free (bi);
|
||||
| | ~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (10) calling 'wrapped_free' from 'free_boxed_int'
|
||||
| | (14) calling 'wrapped_free' from 'free_boxed_int'
|
||||
|
|
||||
+--> 'wrapped_free': events 11-12
|
||||
+--> 'wrapped_free': events 15-16
|
||||
|
|
||||
| NN | void wrapped_free (void *ptr)
|
||||
| | ^~~~~~~~~~~~
|
||||
| | |
|
||||
| | (11) entry to 'wrapped_free'
|
||||
| | (15) entry to 'wrapped_free'
|
||||
| NN | {
|
||||
| NN | free (ptr);
|
||||
| | ~~~~~~~~~~
|
||||
| | |
|
||||
| | (12) first 'free' here (state of 'ptr': 'nonnull' -> 'freed', origin: NULL)
|
||||
| | (16) first 'free' here (state of 'ptr': 'nonnull' -> 'freed', NULL origin)
|
||||
|
|
||||
<------+
|
||||
|
|
||||
'free_boxed_int': event 13
|
||||
'free_boxed_int': event 17
|
||||
|
|
||||
| NN | wrapped_free (bi);
|
||||
| | ^~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (13) returning to 'free_boxed_int' from 'wrapped_free'
|
||||
| | (17) returning to 'free_boxed_int' from 'wrapped_free'
|
||||
|
|
||||
<------+
|
||||
|
|
||||
'test': events 14-15
|
||||
'test': events 18-19
|
||||
|
|
||||
| NN | free_boxed_int (obj);
|
||||
| | ^~~~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (14) returning to 'test' from 'free_boxed_int'
|
||||
| | (18) returning to 'test' from 'free_boxed_int'
|
||||
| NN |
|
||||
| NN | free_boxed_int (obj);
|
||||
| | ~~~~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (15) passing freed pointer 'obj' in call to 'free_boxed_int' from 'test'
|
||||
| | (19) passing freed pointer 'obj' in call to 'free_boxed_int' from 'test'
|
||||
|
|
||||
+--> 'free_boxed_int': events 16-17
|
||||
+--> 'free_boxed_int': events 20-21
|
||||
|
|
||||
| NN | free_boxed_int (boxed_int *bi)
|
||||
| | ^~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (16) entry to 'free_boxed_int'
|
||||
| | (20) entry to 'free_boxed_int'
|
||||
| NN | {
|
||||
| NN | wrapped_free (bi);
|
||||
| | ~~~~~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (17) passing freed pointer 'bi' in call to 'wrapped_free' from 'free_boxed_int'
|
||||
| | (21) passing freed pointer 'bi' in call to 'wrapped_free' from 'free_boxed_int'
|
||||
|
|
||||
+--> 'wrapped_free': events 18-19
|
||||
+--> 'wrapped_free': events 22-23
|
||||
|
|
||||
| NN | void wrapped_free (void *ptr)
|
||||
| | ^~~~~~~~~~~~
|
||||
| | |
|
||||
| | (18) entry to 'wrapped_free'
|
||||
| | (22) entry to 'wrapped_free'
|
||||
| NN | {
|
||||
| NN | free (ptr);
|
||||
| | ~~~~~~~~~~
|
||||
| | |
|
||||
| | (19) second 'free' here; first 'free' was at (12) ('ptr' is in state 'freed')
|
||||
| | (23) second 'free' here; first 'free' was at (16) ('ptr' is in state 'freed')
|
||||
|
|
||||
{ dg-end-multiline-output "" } */
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* Example of a multilevel wrapper around malloc, with an unchecked write. */
|
||||
|
||||
/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-path-format=inline-events -fanalyzer-checker=malloc -fdiagnostics-show-caret -fanalyzer-verbose-state-changes" } */
|
||||
/* { dg-additional-options "-fdiagnostics-show-line-numbers -fdiagnostics-path-format=inline-events -fanalyzer-checker=malloc -fdiagnostics-show-caret" } */
|
||||
/* { dg-enable-nn-line-numbers "" } */
|
||||
|
||||
#include <stdlib.h>
|
||||
|
@ -49,7 +49,7 @@ make_boxed_int (int i)
|
|||
| NN | return malloc (size);
|
||||
| | ~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (4) this call could return NULL (state of '<return-value>': 'start' -> 'unchecked', origin: NULL)
|
||||
| | (4) this call could return NULL
|
||||
|
|
||||
<------+
|
||||
|
|
||||
|
@ -62,6 +62,6 @@ make_boxed_int (int i)
|
|||
| NN | result->i = i;
|
||||
| | ~~~~~~~~~~~~~
|
||||
| | |
|
||||
| | (6) 'result' could be NULL: unchecked value from (4) ('result' is in state 'unchecked')
|
||||
| | (6) 'result' could be NULL: unchecked value from (4)
|
||||
|
|
||||
{ dg-end-multiline-output "" } */
|
||||
|
|
|
@ -112,8 +112,7 @@ int test_3 (int x, int y)
|
|||
free (ptr); /* No double-'free' warning: we've already attempted
|
||||
to dereference it above. */
|
||||
return *ptr; /* { dg-warning "use after 'free' of 'ptr'" "use-after-free" } */
|
||||
// TODO: two warnings here: one is from sm-malloc, the other from region model
|
||||
/* { dg-warning "leak of 'ptr'" "leak" { target *-*-* } .-2 } */
|
||||
/* { dg-warning "leak of 'ptr'" "leak" { target *-*-* } .-1 } */
|
||||
}
|
||||
|
||||
/* "dereference of possibly-NULL 'ptr'". */
|
||||
|
@ -241,59 +240,3 @@ int test_3 (int x, int y)
|
|||
| | (7) 'ptr' leaks here; was allocated at (1)
|
||||
|
|
||||
{ dg-end-multiline-output "" } */
|
||||
|
||||
/* "use after 'free' of 'ptr'". */
|
||||
/* { dg-begin-multiline-output "" }
|
||||
NN | *ptr = 19;
|
||||
| ~~~~~^~~~
|
||||
'test_3': events 1-3
|
||||
|
|
||||
| NN | if (x)
|
||||
| | ^
|
||||
| | |
|
||||
| | (1) following 'true' branch (when 'x != 0')...
|
||||
| NN | free (ptr);
|
||||
| | ~~~~~~~~~~
|
||||
| | |
|
||||
| | (2) ...to here
|
||||
| NN |
|
||||
| NN | *ptr = 19;
|
||||
| | ~~~~~~~~~
|
||||
| | |
|
||||
| | (3) use after 'free' of 'ptr' here
|
||||
|
|
||||
{ dg-end-multiline-output "" } */
|
||||
|
||||
/* "use after 'free' of 'ptr'". */
|
||||
/* { dg-begin-multiline-output "" }
|
||||
NN | return *ptr;
|
||||
| ^~~~
|
||||
'test_3': events 1-5
|
||||
|
|
||||
| NN | if (x)
|
||||
| | ^
|
||||
| | |
|
||||
| | (1) following 'false' branch (when 'x == 0')...
|
||||
|......
|
||||
| NN | *ptr = 19;
|
||||
| | ~~~~~~~~~
|
||||
| | |
|
||||
| | (2) ...to here
|
||||
|......
|
||||
| NN | if (y)
|
||||
| | ~
|
||||
| | |
|
||||
| | (3) following 'true' branch (when 'y != 0')...
|
||||
| NN | free (ptr);
|
||||
| | ~~~~~~~~~~
|
||||
| | |
|
||||
| | (4) ...to here
|
||||
| NN | to dereference it above
|
||||
| NN | return *ptr;
|
||||
| | ~~~~
|
||||
| | |
|
||||
| | (5) use after 'free' of 'ptr' here
|
||||
|
|
||||
{ dg-end-multiline-output "" } */
|
||||
/* TODO: this is really a duplicate; can we either eliminate it, or
|
||||
improve the path? */
|
||||
|
|
|
@ -11,7 +11,7 @@ do_stuff (int *p, int n)
|
|||
int sum = 0;
|
||||
int i;
|
||||
for (i = 0; i < n; i++)
|
||||
p[i] = i;
|
||||
p[i] = i; /* { dg-warning "dereference of possibly-NULL 'p'" } */
|
||||
for (i = 0; i < n; i++)
|
||||
sum += foo (p[i]); /* { dg-bogus "uninitialized" } */
|
||||
return sum;
|
||||
|
@ -48,10 +48,10 @@ int test_repeated_predicate_1 (int n)
|
|||
|
||||
result = do_stuff (ptr, n);
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "3 processed enodes" } */
|
||||
// FIXME: why 3 here?
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "3 processed enodes" } */
|
||||
// FIXME: why 3 here?
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "5 processed enodes" } */
|
||||
// FIXME: why 5 here?
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "5 processed enodes" } */
|
||||
// FIXME: why 5 here?
|
||||
|
||||
if (n > 10)
|
||||
free (ptr); /* { dg-bogus "not on the heap" } */
|
||||
|
@ -105,8 +105,8 @@ int test_explicit_flag (int n)
|
|||
|
||||
result = do_stuff (ptr, n);
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "3 processed enodes" } */
|
||||
// FIXME: why 3 here?
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "5 processed enodes" } */
|
||||
// FIXME: why 5 here?
|
||||
|
||||
if (need_to_free)
|
||||
free (ptr); /* { dg-bogus "not on the heap" } */
|
||||
|
@ -131,8 +131,8 @@ int test_pointer_comparison (int n)
|
|||
|
||||
result = do_stuff (ptr, n);
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "3 processed enodes" } */
|
||||
// FIXME: why 3 here?
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "5 processed enodes" } */
|
||||
// FIXME: why 5 here?
|
||||
|
||||
if (ptr != buf)
|
||||
free (ptr); /* { dg-bogus "not on the heap" } */
|
||||
|
@ -169,8 +169,8 @@ int test_initial_flag (int n)
|
|||
|
||||
result = do_stuff (ptr, n);
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "5 processed enodes" } */
|
||||
// FIXME: why 5 here?
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "6 processed enodes" } */
|
||||
// FIXME: why 6 here?
|
||||
|
||||
if (n > 10)
|
||||
free (ptr); /* { dg-bogus "not on the heap" } */
|
||||
|
|
|
@ -31,13 +31,13 @@ int test_repeated_predicate_1 (int n)
|
|||
int sum = 0;
|
||||
int i;
|
||||
for (i = 0; i < n; i++)
|
||||
p[i] = i;
|
||||
p[i] = i; /* { dg-warning "dereference of possibly-NULL" } */
|
||||
for (i = 0; i < n; i++)
|
||||
sum += foo (p[i]); /* { dg-bogus "uninitialized" } */
|
||||
result = sum;
|
||||
}
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "3 processed enodes" } */
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
|
||||
|
||||
if (n > 10)
|
||||
free (ptr); /* { dg-bogus "not on the heap" } */
|
||||
|
@ -65,11 +65,11 @@ int test_repeated_predicate_1a (int n)
|
|||
int sum = 0;
|
||||
int i;
|
||||
for (i = 0; i < n; i++)
|
||||
p[i] = i;
|
||||
p[i] = i; /* { dg-warning "dereference of possibly-NULL" } */
|
||||
result = sum;
|
||||
}
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "3 processed enodes" } */
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
|
||||
|
||||
if (n > 10)
|
||||
free (ptr); /* { dg-bogus "not on the heap" } */
|
||||
|
@ -126,13 +126,13 @@ int test_explicit_flag (int n)
|
|||
int sum = 0;
|
||||
int i;
|
||||
for (i = 0; i < n; i++)
|
||||
p[i] = i;
|
||||
p[i] = i; /* { dg-warning "dereference of possibly-NULL" } */
|
||||
for (i = 0; i < n; i++)
|
||||
sum += foo (p[i]); /* { dg-bogus "uninitialized" } */
|
||||
result = sum;
|
||||
}
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "3 processed enodes" } */
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
|
||||
|
||||
if (need_to_free)
|
||||
free (ptr); /* { dg-bogus "not on the heap" } */
|
||||
|
@ -160,13 +160,13 @@ int test_pointer_comparison (int n)
|
|||
int sum = 0;
|
||||
int i;
|
||||
for (i = 0; i < n; i++)
|
||||
p[i] = i;
|
||||
p[i] = i; /* { dg-warning "dereference of possibly-NULL" } */
|
||||
for (i = 0; i < n; i++)
|
||||
sum += foo (p[i]); /* { dg-bogus "uninitialized" } */
|
||||
result = sum;
|
||||
}
|
||||
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "3 processed enodes" } */
|
||||
__analyzer_dump_exploded_nodes (0); /* { dg-warning "2 processed enodes" } */
|
||||
|
||||
if (ptr != buf)
|
||||
free (ptr); /* { dg-bogus "not on the heap" } */
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue