diff mbox series

[v2] analyzer: New option fanalyzer-show-events-in-system-headers [PR110543]

Message ID 20230814154853.2371420-1-vultkayn@gcc.gnu.org
State New
Headers show
Series [v2] analyzer: New option fanalyzer-show-events-in-system-headers [PR110543] | expand

Commit Message

Li, Pan2 via Gcc-patches Aug. 14, 2023, 3:48 p.m. UTC
From: benjamin priour <vultkayn@gcc.gnu.org>

Plenty useful, thanks David. I've adjusted some few things, especially
the artifacts of earlier versions I missed when building the commit.

I didn't how to test for warnings within <memory>, I couldn't figure a portable test.
I cannot pinpoint the line the warning is issued at in an inline DejaGNU directive,
nor can I safely say the stack depth if I check a multiline-output (nor the methods names)

In the end, I found out an alternative, I am checking for the presence of event "entry of 'main'".
Indeed, diagnostic_manager::finish_pruning comment's reads
If all we're left with is in one function, then filter function entry events.
The provided test case can only goes into main and std::* frames, so if "entry of 'main'" exists,
it means we are also going into std::* frames.

I've also adjusted the comment of prune_system_headers, analyzer.opt and added an entry to invoker.texi.

Successfully regstrapped off trunk
54be338589ea93ad4ff53d22adde476a0582537b on x86_64-linux-gnu.

Thanks,
Benjamin.

Patch below.
----

This patch introduces -fanalyzer-show-events-in-system-headers,
disabled by default.

This option reduces the noise of the analyzer emitted diagnostics
when dealing with system headers.
The new option only affects the display of the diagnostics,
but doesn't hinder the actual analysis.

Given a diagnostics path diving into a system header in the form
[
  prefix events...,
  system header call,
    system header entry,
    events within system headers...,
  system header return,
  suffix events...
]
then disabling the option (either by default or explicitly)
will shorten the path into:
[
  prefix events...,
  system header call,
  system header return,
  suffix events...
]

Signed-off-by: benjamin priour <priour.be@gmail.com>

gcc/analyzer/ChangeLog:

	PR analyzer/110543
	* analyzer.opt: Add new option.
	* diagnostic-manager.cc
	(diagnostic_manager::prune_path): Call prune_system_headers.
	(prune_frame): New function that deletes all events in a frame.
	(diagnostic_manager::prune_system_headers): New function.
	* diagnostic-manager.h: Add prune_system_headers declaration.

gcc/ChangeLog:

	PR analyzer/110543
	* doc/invoke.texi: Add documentation of
	fanalyzer-show-events-in-system-headers

gcc/testsuite/ChangeLog:

	PR analyzer/110543
	* g++.dg/analyzer/fanalyzer-show-events-in-system-headers-default.C:
	New test.
	* g++.dg/analyzer/fanalyzer-show-events-in-system-headers-no.C:
	New test.
	* g++.dg/analyzer/fanalyzer-show-events-in-system-headers.C:
	New test.
---
 gcc/analyzer/analyzer.opt                     |  4 +
 gcc/analyzer/diagnostic-manager.cc            | 96 +++++++++++++++++++
 gcc/analyzer/diagnostic-manager.h             |  1 +
 gcc/doc/invoke.texi                           |  9 ++
 ...er-show-events-in-system-headers-default.C | 18 ++++
 ...nalyzer-show-events-in-system-headers-no.C | 19 ++++
 .../fanalyzer-show-events-in-system-headers.C | 14 +++
 7 files changed, 161 insertions(+)
 create mode 100644 gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers-default.C
 create mode 100644 gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers-no.C
 create mode 100644 gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers.C

Comments

David Malcolm Aug. 14, 2023, 4:14 p.m. UTC | #1
On Mon, 2023-08-14 at 17:48 +0200, priour.be@gmail.com wrote:
> From: benjamin priour <vultkayn@gcc.gnu.org>
> 
> Plenty useful, thanks David. I've adjusted some few things, especially
> the artifacts of earlier versions I missed when building the commit.
> 
> I didn't how to test for warnings within <memory>, I couldn't figure a portable test.
> I cannot pinpoint the line the warning is issued at in an inline DejaGNU directive,
> nor can I safely say the stack depth if I check a multiline-output (nor the methods names)
> 
> In the end, I found out an alternative, I am checking for the presence of event "entry of 'main'".
> Indeed, diagnostic_manager::finish_pruning comment's reads
> If all we're left with is in one function, then filter function entry events.
> The provided test case can only goes into main and std::* frames, so if "entry of 'main'" exists,
> it means we are also going into std::* frames.
> 
> I've also adjusted the comment of prune_system_headers, analyzer.opt and added an entry to invoker.texi.
> 
> Successfully regstrapped off trunk
> 54be338589ea93ad4ff53d22adde476a0582537b on x86_64-linux-gnu.

Thanks for the updated patch.

This is ready to push to trunk.

Dave
diff mbox series

Patch

diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
index 2760aaa8151..7917473d122 100644
--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -290,6 +290,10 @@  fanalyzer-transitivity
 Common Var(flag_analyzer_transitivity) Init(0)
 Enable transitivity of constraints during analysis.
 
+fanalyzer-show-events-in-system-headers
+Common Var(flag_analyzer_show_events_in_system_headers) Init(0)
+Show events within system headers in analyzer execution paths.
+
 fanalyzer-call-summaries
 Common Var(flag_analyzer_call_summaries) Init(0)
 Approximate the effect of function calls to simplify analysis.
diff --git a/gcc/analyzer/diagnostic-manager.cc b/gcc/analyzer/diagnostic-manager.cc
index cfca305d552..430c4dc3d58 100644
--- a/gcc/analyzer/diagnostic-manager.cc
+++ b/gcc/analyzer/diagnostic-manager.cc
@@ -23,6 +23,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "system.h"
 #include "coretypes.h"
 #include "tree.h"
+#include "input.h"
 #include "pretty-print.h"
 #include "gcc-rich-location.h"
 #include "gimple-pretty-print.h"
@@ -2281,6 +2282,8 @@  diagnostic_manager::prune_path (checker_path *path,
   path->maybe_log (get_logger (), "path");
   prune_for_sm_diagnostic (path, sm, sval, state);
   prune_interproc_events (path);
+  if (! flag_analyzer_show_events_in_system_headers)
+    prune_system_headers (path);
   consolidate_conditions (path);
   finish_pruning (path);
   path->maybe_log (get_logger (), "pruned");
@@ -2667,6 +2670,99 @@  diagnostic_manager::prune_interproc_events (checker_path *path) const
   while (changed);
 }
 
+/* Remove everything within [call point, IDX]. For consistency,
+   IDX should represent the return event of the frame to delete,
+   or if there is none it should be the last event of the frame.
+   After this function, IDX designates the event prior to calling
+   this frame.  */
+
+static void
+prune_frame (checker_path *path, int &idx)
+{
+  gcc_assert (idx >= 0);
+  int nesting = 1;
+  if (path->get_checker_event (idx)->is_return_p ())
+    nesting = 0;
+  do
+    {
+      if (path->get_checker_event (idx)->is_call_p ())
+	nesting--;
+      else if (path->get_checker_event (idx)->is_return_p ())
+	nesting++;
+
+      path->delete_event (idx--);
+    } while (idx >= 0 && nesting != 0);
+}
+
+/* This function is called when fanalyzer-show-events-in-system-headers
+   is disabled and will prune the diagnostic of all events within a
+   system header, only keeping the entry and exit events to the header.
+   This should be called after diagnostic_manager::prune_interproc_events
+   so that sucessive events [system header call, system header return]
+   are preserved thereafter.
+
+   Given a diagnostics path diving into a system header in the form
+   [
+     prefix events...,
+     system header call,
+       system header entry,
+       events within system headers...,
+     system header return,
+     suffix events...
+   ]
+
+   then transforms it into
+   [
+     prefix events...,
+     system header call,
+     system header return,
+     suffix events...
+   ].  */
+
+void
+diagnostic_manager::prune_system_headers (checker_path *path) const
+{
+  int idx = (signed)path->num_events () - 1;
+  while (idx >= 0)
+    {
+      const checker_event *event = path->get_checker_event (idx);
+      /* Prune everything between
+	 [..., system entry, (...), system return, ...].  */
+      if (event->is_return_p ()
+	  && in_system_header_at (event->get_location ()))
+      {
+	int ret_idx = idx;
+	prune_frame (path, idx);
+
+	if (get_logger ())
+	{
+	  log ("filtering system headers events %i-%i:",
+	       idx, ret_idx);
+	}
+	// Delete function entry within system headers.
+	if (idx >= 0)
+	  {
+	    event = path->get_checker_event (idx);
+	    if (event->is_function_entry_p ()
+		&& in_system_header_at (event->get_location ()))
+	      {
+		if (get_logger ())
+		  {
+		    label_text desc (event->get_desc (false));
+		    log ("filtering event %i:"
+			 "system header entry event: %s",
+			 idx, desc.get ());
+		  }
+
+		path->delete_event (idx);
+	      }
+	  }
+      }
+
+      idx--;
+    }
+}
+
 /* Return true iff event IDX within PATH is on the same line as REF_EXP_LOC.  */
 
 static bool
diff --git a/gcc/analyzer/diagnostic-manager.h b/gcc/analyzer/diagnostic-manager.h
index 9b3e903fc5d..d3022b888dd 100644
--- a/gcc/analyzer/diagnostic-manager.h
+++ b/gcc/analyzer/diagnostic-manager.h
@@ -180,6 +180,7 @@  private:
 				state_machine::state_t state) const;
   void update_for_unsuitable_sm_exprs (tree *expr) const;
   void prune_interproc_events (checker_path *path) const;
+  void prune_system_headers (checker_path *path) const;
   void consolidate_conditions (checker_path *path) const;
   void finish_pruning (checker_path *path) const;
 
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 674f956f4b8..c317d4b5af3 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -430,6 +430,7 @@  Objective-C and Objective-C++ Dialects}.
 -fanalyzer-checker=@var{name}
 -fno-analyzer-feasibility
 -fanalyzer-fine-grained
+-fanalyzer-show-events-in-system-headers
 -fno-analyzer-state-merge
 -fno-analyzer-state-purge
 -fno-analyzer-suppress-followups
@@ -11203,6 +11204,14 @@  have been detected as being duplicates of each other, it emits a note when
 reporting the best diagnostic, giving the number of additional diagnostics
 that were suppressed by the deduplication logic.
 
+@opindex fanalyzer-show-events-in-system-headers
+@opindex fno-analyzer-show-events-in-system-headers
+@item -fanalyzer-show-events-in-system-headers
+By default the analyzer emits simplified diagnostics paths by hiding
+events fully located within a system header.
+With @option{-fanalyzer-show-events-in-system-headers} such
+events are no longer suppressed.
+
 @opindex fanalyzer-state-merge
 @opindex fno-analyzer-state-merge
 @item -fno-analyzer-state-merge
diff --git a/gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers-default.C b/gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers-default.C
new file mode 100644
index 00000000000..26f047d5471
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers-default.C
@@ -0,0 +1,18 @@ 
+/* { dg-skip-if "no shared_ptr in C++98" { c++98_only }  } */
+
+#include <memory>
+
+struct A {int x; int y;};
+
+int main () {
+  std::shared_ptr<A> a; /* { dg-line declare_a } */
+  a->x = 4; /* { dg-line deref_a } */ 
+  /* { dg-warning "dereference of NULL" "" { target *-*-* } deref_a } */
+
+  return 0;
+}
+
+/* { dg-note "\\(1\\) 'a\\.std::.+::_M_ptr' is NULL" "" { target c++14_down } declare_a } */
+/* { dg-note "dereference of NULL 'a\\.std::.+::operator->\\(\\)'" "" { target *-*-* } deref_a } */
+/* { dg-note "calling 'std::.+::operator->' from 'main'" "" { target *-*-* } deref_a } */
+/* { dg-note "returning to 'main' from 'std::.+::operator->'" "" { target *-*-* } deref_a } */
diff --git a/gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers-no.C b/gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers-no.C
new file mode 100644
index 00000000000..fd0df318051
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers-no.C
@@ -0,0 +1,19 @@ 
+/* { dg-additional-options "-fno-analyzer-show-events-in-system-headers" } */
+/* { dg-skip-if "no shared_ptr in C++98" { c++98_only }  } */
+
+#include <memory>
+
+struct A {int x; int y;};
+
+int main () {
+  std::shared_ptr<A> a; /* { dg-line declare_a } */
+  a->x = 4; /* { dg-line deref_a } */ 
+  /* { dg-warning "dereference of NULL" "" { target *-*-* } deref_a } */
+
+  return 0;
+}
+
+/* { dg-note "\\(1\\) 'a\\.std::.+::_M_ptr' is NULL" "" { target c++14_down } declare_a } */
+/* { dg-note "dereference of NULL 'a\\.std::.+::operator->\\(\\)'" "" { target *-*-* } deref_a } */
+/* { dg-note "calling 'std::.+::operator->' from 'main'" "" { target *-*-* } deref_a } */
+/* { dg-note "returning to 'main' from 'std::.+::operator->'" "" { target *-*-* } deref_a } */
diff --git a/gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers.C b/gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers.C
new file mode 100644
index 00000000000..4cc93d129f0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/analyzer/fanalyzer-show-events-in-system-headers.C
@@ -0,0 +1,14 @@ 
+/* { dg-additional-options "-fanalyzer-show-events-in-system-headers" } */
+/* { dg-skip-if "no shared_ptr in C++98" { c++98_only }  } */
+
+#include <memory>
+
+struct A {int x; int y;};
+
+int main () { /* { dg-message "\\(1\\) entry to 'main'" "telltale event that we are going within a deeper frame than 'main'" } */
+  std::shared_ptr<A> a; /* { dg-line declare_a } */ 
+  a->x = 4; /* { dg-line deref_a } */ 
+  /* { dg-warning "dereference of NULL" "" { target *-*-* } deref_a } */
+
+  return 0;
+}