#include "tree-logical-location.h"
#include "analyzer/program-state.h"
+#include "analyzer/event-loc-info.h"
namespace ana {
-/* A bundle of location information for a checker_event. */
-
-struct event_loc_info
-{
- event_loc_info (location_t loc, tree fndecl, int depth)
- : m_loc (loc), m_fndecl (fndecl), m_depth (depth)
- {}
-
- location_t m_loc;
- tree m_fndecl;
- int m_depth;
-};
-
/* An enum for discriminating between the concrete subclasses of
checker_event. */
We use the final enode from the epath, which might be different from
the sd.m_enode, as the dedupe code doesn't care about enodes, just
snodes. */
- sd.m_d->add_final_event (sd.m_sm, epath->get_final_enode (), sd.m_stmt,
- sd.m_var, sd.m_state, &emission_path);
+ {
+ const exploded_node *const enode = epath->get_final_enode ();
+ const gimple *stmt = sd.m_stmt;
+ event_loc_info loc_info (get_stmt_location (stmt, enode->get_function ()),
+ enode->get_function ()->decl,
+ enode->get_stack_depth ());
+ if (sd.m_stmt_finder)
+ sd.m_stmt_finder->update_event_loc_info (loc_info);
+ sd.m_d->add_final_event (sd.m_sm, enode, loc_info,
+ sd.m_var, sd.m_state, &emission_path);
+ }
/* The "final" event might not be final; if the saved_diagnostic has a
trailing eedge stashed, add any events for it. This is for use
return NULL;
}
+ void update_event_loc_info (event_loc_info &) final override
+ {
+ /* No-op. */
+ }
+
private:
const exploded_graph &m_eg;
tree m_var;
&old_state, &new_state, &uncertainty, NULL,
get_stmt ());
const svalue *result = NULL;
- new_state.m_region_model->pop_frame (NULL, &result, &ctxt);
+ new_state.m_region_model->pop_frame (NULL, &result, &ctxt, nullptr);
program_state::detect_leaks (old_state, new_state, result,
eg.get_ext_state (), &ctxt);
}
--- /dev/null
+/* A bundle of location information for a checker_event.
+ Copyright (C) 2019-2024 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_EVENT_LOC_INFO_H
+#define GCC_ANALYZER_EVENT_LOC_INFO_H
+
+namespace ana {
+
+/* A bundle of location information for a checker_event. */
+
+struct event_loc_info
+{
+ event_loc_info (location_t loc, tree fndecl, int depth)
+ : m_loc (loc), m_fndecl (fndecl), m_depth (depth)
+ {}
+
+ location_t m_loc;
+ tree m_fndecl;
+ int m_depth;
+};
+
+} // namespace ana
+
+#endif /* GCC_ANALYZER_EVENT_LOC_INFO_H */
virtual ~stmt_finder () {}
virtual std::unique_ptr<stmt_finder> clone () const = 0;
virtual const gimple *find_stmt (const exploded_path &epath) = 0;
+ virtual void update_event_loc_info (event_loc_info &) = 0;
};
// TODO: split the above up?
/* Customize the location where the warning_event appears. */
void add_final_event (const state_machine *,
const exploded_node *enode,
- const gimple *,
+ const event_loc_info &,
tree,
state_machine::state_t,
checker_path *emission_path) final override
it at the topmost entrypoint to the function. */
void add_final_event (const state_machine *,
const exploded_node *enode,
- const gimple *,
+ const event_loc_info &,
tree,
state_machine::state_t,
checker_path *emission_path) final override
void
pending_diagnostic::add_final_event (const state_machine *sm,
const exploded_node *enode,
- const gimple *stmt,
+ const event_loc_info &loc_info,
tree var, state_machine::state_t state,
checker_path *emission_path)
{
emission_path->add_event
(make_unique<warning_event>
- (event_loc_info (get_stmt_location (stmt, enode->get_function ()),
- enode->get_function ()->decl,
- enode->get_stack_depth ()),
+ (loc_info,
enode,
sm, var, state));
}
of the called function. */
virtual void add_final_event (const state_machine *sm,
const exploded_node *enode,
- const gimple *stmt,
+ const event_loc_info &loc_info,
tree var, state_machine::state_t state,
checker_path *emission_path);
setjmp was called. */
gcc_assert (get_stack_depth () >= setjmp_stack_depth);
while (get_stack_depth () > setjmp_stack_depth)
- pop_frame (NULL, NULL, ctxt, false);
+ pop_frame (NULL, NULL, ctxt, nullptr, false);
gcc_assert (get_stack_depth () == setjmp_stack_depth);
so that pop_frame can determine the region with respect to the
*caller* frame. */
tree lhs = gimple_call_lhs (call_stmt);
- pop_frame (lhs, NULL, ctxt);
+ pop_frame (lhs, NULL, ctxt, call_stmt);
}
/* Extract calling information from the superedge and update the model for the
return &frame->get_function ();
}
+/* Custom region_model_context for the assignment to the result
+ at a call statement when popping a frame (PR analyzer/106203). */
+
+class caller_context : public region_model_context_decorator
+{
+public:
+ caller_context (region_model_context *inner,
+ const gcall *call_stmt,
+ const frame_region &caller_frame)
+ : region_model_context_decorator (inner),
+ m_call_stmt (call_stmt),
+ m_caller_frame (caller_frame)
+ {}
+ bool warn (std::unique_ptr<pending_diagnostic> d,
+ const stmt_finder *custom_finder) override
+ {
+ if (m_inner && custom_finder == nullptr)
+ {
+ /* Custom stmt_finder to use m_call_stmt for the
+ diagnostic. */
+ class my_finder : public stmt_finder
+ {
+ public:
+ my_finder (const gcall *call_stmt,
+ const frame_region &caller_frame)
+ : m_call_stmt (call_stmt),
+ m_caller_frame (caller_frame)
+ {}
+ std::unique_ptr<stmt_finder> clone () const override
+ {
+ return ::make_unique<my_finder> (m_call_stmt, m_caller_frame);
+ }
+ const gimple *find_stmt (const exploded_path &) override
+ {
+ return m_call_stmt;
+ }
+ void update_event_loc_info (event_loc_info &loc_info) final override
+ {
+ loc_info.m_fndecl = m_caller_frame.get_fndecl ();
+ loc_info.m_depth = m_caller_frame.get_stack_depth ();
+ }
+
+ private:
+ const gcall *m_call_stmt;
+ const frame_region &m_caller_frame;
+ };
+ my_finder finder (m_call_stmt, m_caller_frame);
+ return m_inner->warn (std::move (d), &finder);
+ }
+ else
+ return region_model_context_decorator::warn (std::move (d),
+ custom_finder);
+ }
+ const gimple *get_stmt () const override
+ {
+ return m_call_stmt;
+ };
+
+private:
+ const gcall *m_call_stmt;
+ const frame_region &m_caller_frame;
+};
+
+
/* Pop the topmost frame_region from this region_model's stack;
If RESULT_LVALUE is non-null, copy any return value from the frame
If OUT_RESULT is non-null, copy any return value from the frame
into *OUT_RESULT.
+ If non-null, use CALL_STMT as the location when complaining about
+ assignment of the return value to RESULT_LVALUE.
+
If EVAL_RETURN_SVALUE is false, then don't evaluate the return value.
This is for use when unwinding frames e.g. due to longjmp, to suppress
erroneously reporting uninitialized return values.
region_model::pop_frame (tree result_lvalue,
const svalue **out_result,
region_model_context *ctxt,
+ const gcall *call_stmt,
bool eval_return_svalue)
{
gcc_assert (m_current_frame);
/* Compute result_dst_reg using RESULT_LVALUE *after* popping
the frame, but before poisoning pointers into the old frame. */
const region *result_dst_reg = get_lvalue (result_lvalue, ctxt);
- set_value (result_dst_reg, retval, ctxt);
+
+ /* Assign retval to result_dst_reg, using caller_context
+ to set the call_stmt and the popped_frame for any diagnostics
+ due to the assignment. */
+ gcc_assert (m_current_frame);
+ caller_context caller_ctxt (ctxt, call_stmt, *m_current_frame);
+ set_value (result_dst_reg, retval, call_stmt ? &caller_ctxt : ctxt);
}
unbind_region_and_descendents (frame_reg,POISON_KIND_POPPED_STACK);
ASSERT_FALSE (a_in_parent_reg->descendent_of_p (child_frame_reg));
/* Pop the "child_fn" frame from the stack. */
- model.pop_frame (NULL, NULL, &ctxt);
+ model.pop_frame (NULL, NULL, &ctxt, nullptr);
ASSERT_FALSE (model.region_exists_p (child_frame_reg));
ASSERT_TRUE (model.region_exists_p (parent_frame_reg));
/* Verify that the pointers to the alloca region are replaced by
poisoned values when the frame is popped. */
- model.pop_frame (NULL, NULL, &ctxt);
+ model.pop_frame (NULL, NULL, &ctxt, nullptr);
ASSERT_EQ (model.get_rvalue (p, NULL)->get_kind (), SK_POISONED);
}
void pop_frame (tree result_lvalue,
const svalue **out_result,
region_model_context *ctxt,
+ const gcall *call_stmt,
bool eval_return_svalue = true);
int get_stack_depth () const;
const frame_region *get_frame_at_index (int index) const;
void test_9 (void)
{
- /* FIXME: At the moment, region_model::set_value (lhs, <return_value>)
- is called at the src_node of the return edge. This edge has no stmts
- associated with it, leading to a rejection of the warning inside
- impl_region_model_context::warn. To ensure that the indentation
- in the diagnostic is right, the warning has to be emitted on an EN
- that is after the return edge. */
- int32_t *buf = (int32_t *) create_buffer(42); /* { dg-warning "" "" { xfail *-*-* } } */
+ int32_t *buf = (int32_t *) create_buffer(42); /* { dg-warning "allocated buffer size is not a multiple of the pointee's size" } */
free (buf);
}
void test_8(int32_t n)
{
- /* FIXME: At the moment, region_model::set_value (lhs, <return_value>)
- is called at the src_node of the return edge. This edge has no stmts
- associated with it, leading to a rejection of the warning inside
- impl_region_model_context::warn. To ensure that the indentation
- in the diagnostic is right, the warning has to be emitted on an EN
- that is after the return edge. */
- int32_t *buf = (int32_t *)create_buffer(n * sizeof(int16_t)); /* { dg-warning "" "" { xfail *-*-* } } */
+ int32_t *buf = (int32_t *)create_buffer(n * sizeof(int16_t)); /* { dg-warning "allocated buffer size is not a multiple of the pointee's size" } */
free (buf);
}
--- /dev/null
+/* Verify that we report events that occur at returns from functions
+ with valid-looking interprocedural paths.
+
+ C only: there are only minor C/C++ differences in e.g. how the
+ function names are printed. Trying to handle both would overcomplicate
+ the test. */
+
+/* { dg-additional-options "-fdiagnostics-show-line-numbers" } */
+/* { dg-enable-nn-line-numbers "" } */
+/* { dg-additional-options "-fdiagnostics-path-format=inline-events" } */
+/* { dg-additional-options "-fdiagnostics-show-caret" } */
+/* { dg-additional-options "-fdiagnostics-show-path-depths" } */
+
+#include <stdlib.h>
+#include <stdint.h>
+
+void *create_buffer (int32_t n)
+{
+ return malloc(n);
+}
+
+void test_9 (void)
+{
+ int32_t *buf = (int32_t *) create_buffer(42); /* { dg-warning "allocated buffer size is not a multiple of the pointee's size" } */
+ free (buf);
+}
+
+/* { dg-begin-multiline-output "" }
+ NN | int32_t *buf = (int32_t *) create_buffer(42);
+ | ^~~~~~~~~~~~~~~~~
+ 'test_9': events 1-2 (depth 1)
+ |
+ | NN | void test_9 (void)
+ | | ^~~~~~
+ | | |
+ | | (1) entry to 'test_9'
+ | NN | {
+ | NN | int32_t *buf = (int32_t *) create_buffer(42);
+ | | ~~~~~~~~~~~~~~~~~
+ | | |
+ | | (2) calling 'create_buffer' from 'test_9'
+ |
+ +--> 'create_buffer': events 3-4 (depth 2)
+ |
+ | NN | void *create_buffer (int32_t n)
+ | | ^~~~~~~~~~~~~
+ | | |
+ | | (3) entry to 'create_buffer'
+ | NN | {
+ | NN | return malloc(n);
+ | | ~~~~~~~~~
+ | | |
+ | | (4) allocated 42 bytes here
+ |
+ <------+
+ |
+ 'test_9': event 5 (depth 1)
+ |
+ | NN | int32_t *buf = (int32_t *) create_buffer(42);
+ | | ^~~~~~~~~~~~~~~~~
+ | | |
+ | | (5) assigned to 'int32_t *'
+ |
+ { dg-end-multiline-output "" } */
return NULL;
}
+ void update_event_loc_info (event_loc_info &) final override
+ {
+ /* No-op. */
+ }
+
private:
const exploded_graph &m_eg;
tree m_var;