From 36019314e91c7a2e8eae597159675091fd05c002 Mon Sep 17 00:00:00 2001
From: Alex Shvartzkop <dudesuchamazing@gmail.com>
Date: Tue, 27 Aug 2024 05:49:37 +0300
Subject: [PATCH] Make simulation reporter warnings non-fatal.

Adds REPORTER::HasMessageOfSeverity for WX_STRING_REPORTER,
which allows us to show simulation warnings and fail only if there's errors.

Also fixes a crash when SPICE_GENERATOR_KIBIS throws an IO_ERROR.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/18143
---
 common/reporter.cpp                           | 34 +++++++++--
 common/widgets/wx_html_report_panel.cpp       |  9 ++-
 common/widgets/wx_html_report_panel.h         |  1 +
 eeschema/dialogs/dialog_bom.cpp               |  5 +-
 eeschema/dialogs/dialog_sim_model.cpp         | 25 ++++----
 eeschema/erc/erc.cpp                          |  8 +--
 eeschema/files-io.cpp                         |  4 +-
 .../netlist_exporter_spice.cpp                | 46 ++++++++-------
 eeschema/sim/sim_lib_mgr.cpp                  |  2 +-
 eeschema/sim/sim_model.cpp                    |  3 +-
 eeschema/sim/sim_model_ibis.cpp               |  5 +-
 eeschema/sim/simulator_frame.cpp              | 57 +++++++++++--------
 eeschema/sim/simulator_frame.h                |  2 +-
 eeschema/sim/simulator_frame_ui.cpp           |  6 +-
 eeschema/tools/sch_editor_control.cpp         |  5 +-
 eeschema/tools/simulator_control.cpp          | 18 ++++--
 gerbview/files.cpp                            | 12 ++--
 gerbview/job_file_reader.cpp                  |  7 +--
 include/reporter.h                            | 17 +++++-
 include/widgets/report_severity.h             | 16 +++---
 pcbnew/files.cpp                              |  6 +-
 qa/schematic_utils/eeschema_test_utils.cpp    |  5 +-
 22 files changed, 173 insertions(+), 120 deletions(-)

diff --git a/common/reporter.cpp b/common/reporter.cpp
index 859d06597e..0ec4ed264a 100644
--- a/common/reporter.cpp
+++ b/common/reporter.cpp
@@ -50,6 +50,13 @@ REPORTER& REPORTER::Report( const char* aText, SEVERITY aSeverity )
 }
 
 
+bool REPORTER::HasMessageOfSeverity( int aSeverityMask ) const
+{
+    wxFAIL_MSG( "HasMessageOfSeverity is not implemented in this reporter" );
+    return HasMessage();
+}
+
+
 REPORTER& WX_TEXT_CTRL_REPORTER::Report( const wxString& aText, SEVERITY aSeverity )
 {
     wxCHECK_MSG( m_textCtrl != nullptr, *this,
@@ -68,17 +75,34 @@ bool WX_TEXT_CTRL_REPORTER::HasMessage() const
 
 REPORTER& WX_STRING_REPORTER::Report( const wxString& aText, SEVERITY aSeverity )
 {
-    wxCHECK_MSG( m_string != nullptr, *this,
-                 wxT( "No wxString object defined in WX_STRING_REPORTER." ) );
-
-    *m_string << aText << wxS( "\n" );
+    m_severityMask |= aSeverity;
+    m_string << aText << wxS( "\n" );
     return *this;
 }
 
 
+const wxString& WX_STRING_REPORTER::GetMessages() const
+{
+    return m_string;
+}
+
+
+void WX_STRING_REPORTER::Clear()
+{
+    m_severityMask = 0;
+    m_string.clear();
+}
+
+
 bool WX_STRING_REPORTER::HasMessage() const
 {
-    return !m_string->IsEmpty();
+    return !m_string.IsEmpty();
+}
+
+
+bool WX_STRING_REPORTER::HasMessageOfSeverity( int aSeverityMask ) const
+{
+    return ( m_severityMask & aSeverityMask ) != 0;
 }
 
 
diff --git a/common/widgets/wx_html_report_panel.cpp b/common/widgets/wx_html_report_panel.cpp
index ebdd223649..c74aa01c0e 100644
--- a/common/widgets/wx_html_report_panel.cpp
+++ b/common/widgets/wx_html_report_panel.cpp
@@ -494,5 +494,12 @@ REPORTER& WX_HTML_PANEL_REPORTER::ReportHead( const wxString& aText, SEVERITY aS
 
 bool WX_HTML_PANEL_REPORTER::HasMessage() const
 {
-    return m_panel->Count( RPT_SEVERITY_ERROR | RPT_SEVERITY_WARNING ) > 0;
+    // Check just for errors and warnings for compatibility
+    return HasMessageOfSeverity( RPT_SEVERITY_ERROR | RPT_SEVERITY_WARNING );
+}
+
+
+bool WX_HTML_PANEL_REPORTER::HasMessageOfSeverity( int aSeverityMask ) const
+{
+    return m_panel->Count( aSeverityMask ) > 0;
 }
\ No newline at end of file
diff --git a/common/widgets/wx_html_report_panel.h b/common/widgets/wx_html_report_panel.h
index 0532551fc3..aa23d00134 100644
--- a/common/widgets/wx_html_report_panel.h
+++ b/common/widgets/wx_html_report_panel.h
@@ -48,6 +48,7 @@ public:
                           SEVERITY        aSeverity = RPT_SEVERITY_UNDEFINED ) override;
 
     bool HasMessage() const override;
+    bool HasMessageOfSeverity( int aSeverityMask ) const override;
 
 private:
     WX_HTML_REPORT_PANEL* m_panel;
diff --git a/eeschema/dialogs/dialog_bom.cpp b/eeschema/dialogs/dialog_bom.cpp
index d6a7853c3c..a4969241d4 100644
--- a/eeschema/dialogs/dialog_bom.cpp
+++ b/eeschema/dialogs/dialog_bom.cpp
@@ -334,8 +334,7 @@ void DIALOG_BOM::OnRunGenerator( wxCommandEvent& event )
     wxString fullfilename = fn.GetFullPath();
     m_parent->ClearMsgPanel();
 
-    wxString reportmsg;
-    WX_STRING_REPORTER reporter( &reportmsg );
+    WX_STRING_REPORTER reporter;
     m_parent->SetNetListerCommand( m_textCtrlCommand->GetValue() );
 
 #ifdef __WINDOWS__
@@ -351,7 +350,7 @@ void DIALOG_BOM::OnRunGenerator( wxCommandEvent& event )
     if( !status )
         DisplayError( this, _( "Failed to create file." ) );
 
-    m_Messages->SetValue( reportmsg );
+    m_Messages->SetValue( reporter.GetMessages() );
 
     // Force focus back on the dialog
     SetFocus();
diff --git a/eeschema/dialogs/dialog_sim_model.cpp b/eeschema/dialogs/dialog_sim_model.cpp
index 8bfbde53ae..e8074a7983 100644
--- a/eeschema/dialogs/dialog_sim_model.cpp
+++ b/eeschema/dialogs/dialog_sim_model.cpp
@@ -171,8 +171,7 @@ bool DIALOG_SIM_MODEL<T>::TransferDataToWindow()
     wxString       pinMap;
     bool           storeInValue = false;
 
-    wxString           msg;
-    WX_STRING_REPORTER reporter( &msg );
+    WX_STRING_REPORTER reporter;
 
     auto setFieldValue =
             [&]( const wxString& aFieldName, const wxString& aValue )
@@ -220,7 +219,7 @@ bool DIALOG_SIM_MODEL<T>::TransferDataToWindow()
         if( !loadLibrary( libraryFilename, reporter ) )
         {
             if( reporter.HasMessage() )
-                m_infoBar->ShowMessage( msg );
+                m_infoBar->ShowMessage( reporter.GetMessages() );
 
             m_libraryPathText->ChangeValue( libraryFilename );
             m_curModelType = SIM_MODEL::ReadTypeFromFields( m_fields, reporter );
@@ -303,24 +302,24 @@ bool DIALOG_SIM_MODEL<T>::TransferDataToWindow()
         // The model is sourced from the instance.
         m_rbBuiltinModel->SetValue( true );
 
-        msg.clear();
+        reporter.Clear();
         m_curModelType = SIM_MODEL::ReadTypeFromFields( m_fields, reporter );
 
         if( reporter.HasMessage() )
-            DisplayErrorMessage( this, msg );
+            DisplayErrorMessage( this, reporter.GetMessages() );
     }
 
     for( SIM_MODEL::TYPE type : SIM_MODEL::TYPE_ITERATOR() )
     {
         if( m_rbBuiltinModel->GetValue() && type == m_curModelType )
         {
-            msg.clear();
+            reporter.Clear();
             m_builtinModelsMgr.CreateModel( m_fields, m_sortedPartPins, false, reporter );
 
             if( reporter.HasMessage() )
             {
                 DisplayErrorMessage( this, _( "Failed to read simulation model from fields." )
-                                           + wxT( "\n\n" ) + msg );
+                                                   + wxT( "\n\n" ) + reporter.GetMessages() );
             }
         }
         else
@@ -817,7 +816,7 @@ bool DIALOG_SIM_MODEL<T>::loadLibrary( const wxString& aLibraryPath, REPORTER& a
     m_libraryModelsMgr.SetForceFullParse();
     m_libraryModelsMgr.SetLibrary( aLibraryPath, aReporter );
 
-    if( aReporter.HasMessage() )
+    if( aReporter.HasMessageOfSeverity( RPT_SEVERITY_UNDEFINED | RPT_SEVERITY_ERROR ) )
         return false;
 
     std::string modelName = SIM_MODEL::GetFieldValue( &m_fields, SIM_LIBRARY::NAME_FIELD );
@@ -1182,14 +1181,13 @@ void DIALOG_SIM_MODEL<T>::onLibraryPathTextEnter( wxCommandEvent& aEvent )
 {
     m_rbLibraryModel->SetValue( true );
 
-    wxString           msg;
-    WX_STRING_REPORTER reporter( &msg );
+    WX_STRING_REPORTER reporter;
     wxString           path = m_libraryPathText->GetValue();
 
     if( loadLibrary( path, reporter, true ) || path.IsEmpty() )
         m_infoBar->Hide();
     else if( reporter.HasMessage() )
-        m_infoBar->ShowMessage( msg );
+        m_infoBar->ShowMessage( reporter.GetMessages() );
 
     updateWidgets();
 }
@@ -1234,13 +1232,12 @@ void DIALOG_SIM_MODEL<T>::onBrowseButtonClick( wxCommandEvent& aEvent )
     if( fn.MakeRelativeTo( Prj().GetProjectPath() ) && !fn.GetFullPath().StartsWith( wxS( ".." ) ) )
         path = fn.GetFullPath();
 
-    wxString           msg;
-    WX_STRING_REPORTER reporter( &msg );
+    WX_STRING_REPORTER reporter;
 
     if( loadLibrary( path, reporter, true ) )
         m_infoBar->Hide();
     else
-        m_infoBar->ShowMessage( msg );
+        m_infoBar->ShowMessage( reporter.GetMessages() );
 
     updateWidgets();
 }
diff --git a/eeschema/erc/erc.cpp b/eeschema/erc/erc.cpp
index e6dc8fd573..7f937a9dc7 100644
--- a/eeschema/erc/erc.cpp
+++ b/eeschema/erc/erc.cpp
@@ -1572,8 +1572,7 @@ int ERC_TESTER::TestOffGridEndpoints()
 
 int ERC_TESTER::TestSimModelIssues()
 {
-    wxString           msg;
-    WX_STRING_REPORTER reporter( &msg );
+    WX_STRING_REPORTER reporter;
     int                err_count = 0;
     SIM_LIB_MGR        libMgr( &m_schematic->Prj() );
 
@@ -1594,12 +1593,13 @@ int ERC_TESTER::TestSimModelIssues()
                 continue;
 
             // Reset for each symbol
-            msg.Clear();
+            reporter.Clear();
 
             SIM_LIBRARY::MODEL model = libMgr.CreateModel( &sheet, *symbol, reporter );
 
-            if( !msg.IsEmpty() )
+            if( reporter.HasMessage() )
             {
+                wxString                  msg = reporter.GetMessages();
                 std::shared_ptr<ERC_ITEM> ercItem = ERC_ITEM::Create( ERCE_SIMULATION_MODEL );
 
                 //Remove \n and \r at e.o.l if any:
diff --git a/eeschema/files-io.cpp b/eeschema/files-io.cpp
index 4db18055da..fc20503741 100644
--- a/eeschema/files-io.cpp
+++ b/eeschema/files-io.cpp
@@ -1294,10 +1294,10 @@ bool SCH_EDIT_FRAME::SaveProject( bool aSaveAs )
 
     if( !Kiface().IsSingle() )
     {
-        WX_STRING_REPORTER backupReporter( &msg );
+        WX_STRING_REPORTER backupReporter;
 
         if( !GetSettingsManager()->TriggerBackupIfNeeded( backupReporter ) )
-            SetStatusText( msg, 0 );
+            SetStatusText( backupReporter.GetMessages(), 0 );
     }
 
     updateTitle();
diff --git a/eeschema/netlist_exporters/netlist_exporter_spice.cpp b/eeschema/netlist_exporters/netlist_exporter_spice.cpp
index 6205f2bdd1..dffa1b1cab 100644
--- a/eeschema/netlist_exporters/netlist_exporter_spice.cpp
+++ b/eeschema/netlist_exporters/netlist_exporter_spice.cpp
@@ -37,6 +37,7 @@
 #include <sch_screen.h>
 #include <sch_textbox.h>
 #include <string_utils.h>
+#include <ki_exception.h>
 
 #include <dialogs/html_message_box.h>
 #include <fmt/core.h>
@@ -175,31 +176,38 @@ bool NETLIST_EXPORTER_SPICE::ReadSchematicAndLibraries( unsigned aNetlistOptions
             if( !symbol || symbol->GetExcludedFromSim() )
                 continue;
 
-            SPICE_ITEM            spiceItem;
-            std::vector<PIN_INFO> pins = CreatePinList( symbol, sheet, true );
-
-            for( const SCH_FIELD& field : symbol->GetFields() )
+            try
             {
-                spiceItem.fields.emplace_back( VECTOR2I(), -1, symbol, field.GetName() );
+                SPICE_ITEM            spiceItem;
+                std::vector<PIN_INFO> pins = CreatePinList( symbol, sheet, true );
 
-                if( field.GetId() == REFERENCE_FIELD )
-                    spiceItem.fields.back().SetText( symbol->GetRef( &sheet ) );
-                else
-                    spiceItem.fields.back().SetText( field.GetShownText( &sheet, false ) );
+                for( const SCH_FIELD& field : symbol->GetFields() )
+                {
+                    spiceItem.fields.emplace_back( VECTOR2I(), -1, symbol, field.GetName() );
+
+                    if( field.GetId() == REFERENCE_FIELD )
+                        spiceItem.fields.back().SetText( symbol->GetRef( &sheet ) );
+                    else
+                        spiceItem.fields.back().SetText( field.GetShownText( &sheet, false ) );
+                }
+
+                readRefName( sheet, *symbol, spiceItem, refNames );
+                readModel( sheet, *symbol, spiceItem, aReporter );
+                readPinNumbers( *symbol, spiceItem, pins );
+                readPinNetNames( *symbol, spiceItem, pins, ncCounter );
+                readNodePattern( spiceItem );
+                // TODO: transmission line handling?
+
+                m_items.push_back( std::move( spiceItem ) );
+            }
+            catch( IO_ERROR& e )
+            {
+                aReporter.Report( e.What(), RPT_SEVERITY_ERROR );
             }
-
-            readRefName( sheet, *symbol, spiceItem, refNames );
-            readModel( sheet, *symbol, spiceItem, aReporter );
-            readPinNumbers( *symbol, spiceItem, pins );
-            readPinNetNames( *symbol, spiceItem, pins, ncCounter );
-            readNodePattern( spiceItem );
-            // TODO: transmission line handling?
-
-            m_items.push_back( std::move( spiceItem ) );
         }
     }
 
-    return !aReporter.HasMessage();
+    return !aReporter.HasMessageOfSeverity( RPT_SEVERITY_UNDEFINED | RPT_SEVERITY_ERROR );
 }
 
 
diff --git a/eeschema/sim/sim_lib_mgr.cpp b/eeschema/sim/sim_lib_mgr.cpp
index 8a7716d007..37aae740f6 100644
--- a/eeschema/sim/sim_lib_mgr.cpp
+++ b/eeschema/sim/sim_lib_mgr.cpp
@@ -129,7 +129,7 @@ void SIM_LIB_MGR::SetLibrary( const wxString& aLibraryPath, REPORTER& aReporter
 {
     wxString path = ResolveLibraryPath( aLibraryPath, m_project, aReporter );
 
-    if( aReporter.HasMessage() )
+    if( aReporter.HasMessageOfSeverity( RPT_SEVERITY_UNDEFINED | RPT_SEVERITY_ERROR ) )
         return;
 
     if( !wxFileName::Exists( path ) )
diff --git a/eeschema/sim/sim_model.cpp b/eeschema/sim/sim_model.cpp
index 6bb60480eb..c6db410719 100644
--- a/eeschema/sim/sim_model.cpp
+++ b/eeschema/sim/sim_model.cpp
@@ -1706,8 +1706,7 @@ void SIM_MODEL::MigrateSimModel( T& aSymbol, const PROJECT* aProject )
 
     if( !lib.IsEmpty() )
     {
-        wxString               msg;
-        WX_STRING_REPORTER     reporter( &msg );
+        WX_STRING_REPORTER     reporter;
         SIM_LIB_MGR            libMgr( aProject );
         std::vector<SCH_FIELD> emptyFields;
 
diff --git a/eeschema/sim/sim_model_ibis.cpp b/eeschema/sim/sim_model_ibis.cpp
index 7e45c1c949..1bcd7ecb9c 100644
--- a/eeschema/sim/sim_model_ibis.cpp
+++ b/eeschema/sim/sim_model_ibis.cpp
@@ -61,12 +61,11 @@ std::string SPICE_GENERATOR_IBIS::IbisDevice( const SPICE_ITEM& aItem, const PRO
     std::string ibisModelName   = SIM_MODEL::GetFieldValue( &aItem.fields, SIM_LIBRARY_IBIS::MODEL_FIELD );
     bool        diffMode        = SIM_MODEL::GetFieldValue( &aItem.fields, SIM_LIBRARY_IBIS::DIFF_FIELD ) == "1";
 
-    wxString           msg;
-    WX_STRING_REPORTER reporter( &msg );
+    WX_STRING_REPORTER reporter;
     wxString           path = SIM_LIB_MGR::ResolveLibraryPath( ibisLibFilename, &aProject, reporter );
 
     if( reporter.HasMessage() )
-        THROW_IO_ERROR( msg );
+        THROW_IO_ERROR( reporter.GetMessages() );
 
     KIBIS kibis( std::string( path.c_str() ) );
     kibis.m_cacheDir = std::string( aCacheDir.c_str() );
diff --git a/eeschema/sim/simulator_frame.cpp b/eeschema/sim/simulator_frame.cpp
index 56062e693b..be7cdb1f4f 100644
--- a/eeschema/sim/simulator_frame.cpp
+++ b/eeschema/sim/simulator_frame.cpp
@@ -62,8 +62,7 @@
 
 
 // Reporter is stored by pointer in KIBIS, so keep this here to avoid crashes
-static wxString           s_errors;
-static WX_STRING_REPORTER s_reporter( &s_errors );
+static WX_STRING_REPORTER s_reporter;
 
 
 class SIM_THREAD_REPORTER : public SIMULATOR_REPORTER
@@ -353,9 +352,12 @@ void SIMULATOR_FRAME::UpdateTitle()
 // Don't let the dialog grow too tall: you may not be able to get to the OK button
 #define MAX_MESSAGES 20
 
-void SIMULATOR_FRAME::showNetlistErrors( const wxString& aErrors )
+void SIMULATOR_FRAME::showNetlistErrors( const WX_STRING_REPORTER& aReporter )
 {
-    wxArrayString lines = wxSplit( aErrors, '\n' );
+    if( !aReporter.HasMessage() )
+        return;
+
+    wxArrayString lines = wxSplit( aReporter.GetMessages(), '\n' );
 
     if( lines.size() > MAX_MESSAGES )
     {
@@ -363,13 +365,22 @@ void SIMULATOR_FRAME::showNetlistErrors( const wxString& aErrors )
         lines.Add( wxS( "..." ) );
     }
 
-    DisplayErrorMessage( this, _( "Errors during netlist generation." ), wxJoin( lines, '\n' ) );
+    if( aReporter.HasMessageOfSeverity( RPT_SEVERITY_UNDEFINED | RPT_SEVERITY_ERROR ) )
+    {
+        DisplayErrorMessage( this, _( "Errors during netlist generation." ),
+                             wxJoin( lines, '\n' ) );
+    }
+    else if( aReporter.HasMessageOfSeverity( RPT_SEVERITY_WARNING ) )
+    {
+        DisplayInfoMessage( this, _( "Warnings during netlist generation." ),
+                             wxJoin( lines, '\n' ) );
+    }
 }
 
 
 bool SIMULATOR_FRAME::LoadSimulator( const wxString& aSimCommand, unsigned aSimOptions )
 {
-    s_errors.clear();
+    s_reporter.Clear();
 
     if( !m_schematicFrame->ReadyToNetlist( _( "Simulator requires a fully annotated schematic." ) ) )
         return false;
@@ -378,26 +389,23 @@ bool SIMULATOR_FRAME::LoadSimulator( const wxString& aSimCommand, unsigned aSimO
     if( ADVANCED_CFG::GetCfg().m_IncrementalConnectivity )
         m_schematicFrame->RecalculateConnections( nullptr, GLOBAL_CLEANUP );
 
-    if( !m_simulator->Attach( m_circuitModel, aSimCommand, aSimOptions, Prj().GetProjectPath(),
-                              s_reporter ) )
-    {
-        showNetlistErrors( s_errors );
-        return false;
-    }
+    bool success = m_simulator->Attach( m_circuitModel, aSimCommand, aSimOptions,
+                                        Prj().GetProjectPath(), s_reporter );
 
-    return true;
+    showNetlistErrors( s_reporter );
+
+    return success;
 }
 
 
 void SIMULATOR_FRAME::ReloadSimulator( const wxString& aSimCommand, unsigned aSimOptions )
 {
-    s_errors.clear();
+    s_reporter.Clear();
 
-    if( !m_simulator->Attach( m_circuitModel, aSimCommand, aSimOptions, Prj().GetProjectPath(),
-                              s_reporter ) )
-    {
-        showNetlistErrors( s_errors );
-    }
+    m_simulator->Attach( m_circuitModel, aSimCommand, aSimOptions, Prj().GetProjectPath(),
+                         s_reporter );
+
+    showNetlistErrors( s_reporter );
 }
 
 
@@ -583,16 +591,15 @@ bool SIMULATOR_FRAME::EditAnalysis()
     SIM_TAB*           simTab = m_ui->GetCurrentSimTab();
     DIALOG_SIM_COMMAND dlg( this, m_circuitModel, m_simulator->Settings() );
 
-    s_errors.clear();
+    s_reporter.Clear();
 
     if( !simTab )
         return false;
 
-    if( !m_circuitModel->ReadSchematicAndLibraries( NETLIST_EXPORTER_SPICE::OPTION_DEFAULT_FLAGS,
-                                                    s_reporter ) )
-    {
-        showNetlistErrors( s_errors );
-    }
+    m_circuitModel->ReadSchematicAndLibraries( NETLIST_EXPORTER_SPICE::OPTION_DEFAULT_FLAGS,
+                                               s_reporter );
+
+    showNetlistErrors( s_reporter );
 
     dlg.SetSimCommand( simTab->GetSimCommand() );
     dlg.SetSimOptions( simTab->GetSimOptions() );
diff --git a/eeschema/sim/simulator_frame.h b/eeschema/sim/simulator_frame.h
index 01655fb79b..c75e9fe011 100644
--- a/eeschema/sim/simulator_frame.h
+++ b/eeschema/sim/simulator_frame.h
@@ -196,7 +196,7 @@ private:
 
     void setupUIConditions() override;
 
-    void showNetlistErrors( const wxString& aErrors );
+    void showNetlistErrors( const WX_STRING_REPORTER& aReporter );
 
     bool canCloseWindow( wxCloseEvent& aEvent ) override;
     void doCloseWindow() override;
diff --git a/eeschema/sim/simulator_frame_ui.cpp b/eeschema/sim/simulator_frame_ui.cpp
index 6ad2eca123..d93b7da662 100644
--- a/eeschema/sim/simulator_frame_ui.cpp
+++ b/eeschema/sim/simulator_frame_ui.cpp
@@ -1862,8 +1862,7 @@ void SIMULATOR_FRAME_UI::applyUserDefinedSignals()
 
 void SIMULATOR_FRAME_UI::applyTuners()
 {
-    wxString            errors;
-    WX_STRING_REPORTER  reporter( &errors );
+    WX_STRING_REPORTER reporter;
 
     for( const TUNER_SLIDER* tuner : m_tuners )
     {
@@ -1893,7 +1892,8 @@ void SIMULATOR_FRAME_UI::applyTuners()
     }
 
     if( reporter.HasMessage() )
-        DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( "\n" ) + errors );
+        DisplayErrorMessage( this, _( "Could not apply tuned value(s):" ) + wxS( "\n" )
+                                           + reporter.GetMessages() );
 }
 
 
diff --git a/eeschema/tools/sch_editor_control.cpp b/eeschema/tools/sch_editor_control.cpp
index 271d460180..914600e59f 100644
--- a/eeschema/tools/sch_editor_control.cpp
+++ b/eeschema/tools/sch_editor_control.cpp
@@ -567,14 +567,13 @@ int SCH_EDITOR_CONTROL::SimProbe( const TOOL_EVENT& aEvent )
                         SCH_PIN*    pin = static_cast<SCH_PIN*>( item )->GetLibPin();
                         SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item->GetParent() );
 
-                        wxString           msg;
-                        WX_STRING_REPORTER reporter( &msg );
+                        WX_STRING_REPORTER reporter;
                         SIM_LIB_MGR        mgr( &m_frame->Prj() );
 
                         SIM_MODEL&  model = mgr.CreateModel( &sheet, *symbol, reporter ).model;
 
                         if( reporter.HasMessage() )
-                            THROW_IO_ERROR( msg );
+                            THROW_IO_ERROR( reporter.GetMessages() );
 
                         SPICE_ITEM spiceItem;
                         spiceItem.refName = symbol->GetRef( &sheet ).ToStdString();
diff --git a/eeschema/tools/simulator_control.cpp b/eeschema/tools/simulator_control.cpp
index e214175c24..70776a22e4 100644
--- a/eeschema/tools/simulator_control.cpp
+++ b/eeschema/tools/simulator_control.cpp
@@ -71,14 +71,20 @@ void SIMULATOR_CONTROL::Reset( RESET_REASON aReason )
 int SIMULATOR_CONTROL::NewAnalysisTab( const TOOL_EVENT& aEvent )
 {
     DIALOG_SIM_COMMAND dlg( m_simulatorFrame, m_circuitModel, m_simulator->Settings() );
-    wxString           errors;
-    WX_STRING_REPORTER reporter( &errors );
+    WX_STRING_REPORTER reporter;
 
-    if( !m_circuitModel->ReadSchematicAndLibraries( NETLIST_EXPORTER_SPICE::OPTION_DEFAULT_FLAGS,
-                                                    reporter ) )
+    m_circuitModel->ReadSchematicAndLibraries( NETLIST_EXPORTER_SPICE::OPTION_DEFAULT_FLAGS,
+                                               reporter );
+
+    if( reporter.HasMessageOfSeverity( RPT_SEVERITY_UNDEFINED | RPT_SEVERITY_ERROR ) )
     {
-        DisplayErrorMessage( m_simulatorFrame,
-                             _( "Errors during netlist generation.\n\n" ) + errors );
+        DisplayErrorMessage( m_simulatorFrame, _( "Errors during netlist generation." ),
+                             reporter.GetMessages() );
+    }
+    else if( reporter.HasMessageOfSeverity( RPT_SEVERITY_WARNING ) )
+    {
+        DisplayInfoMessage( m_simulatorFrame, _( "Warnings during netlist generation." ),
+                            reporter.GetMessages() );
     }
 
     dlg.SetSimCommand( wxS( "*" ) );
diff --git a/gerbview/files.cpp b/gerbview/files.cpp
index 93eec6e937..c57b130435 100644
--- a/gerbview/files.cpp
+++ b/gerbview/files.cpp
@@ -259,8 +259,7 @@ bool GERBVIEW_FRAME::LoadListOfGerberAndDrillFiles( const wxString&      aPath,
     LSET visibility = GetVisibleLayers();
 
     // Manage errors when loading files
-    wxString msg;
-    WX_STRING_REPORTER reporter( &msg );
+    WX_STRING_REPORTER reporter;
 
     // Create progress dialog (only used if more than 1 file to load
     std::unique_ptr<WX_PROGRESS_REPORTER> progress = nullptr;
@@ -403,7 +402,7 @@ bool GERBVIEW_FRAME::LoadListOfGerberAndDrillFiles( const wxString&      aPath,
         wxSafeYield();  // Allows slice of time to redraw the screen
                         // to refresh widgets, before displaying messages
         HTML_MESSAGE_BOX mbox( this, _( "Errors" ) );
-        mbox.ListSet( msg );
+        mbox.ListSet( reporter.GetMessages() );
         mbox.ShowModal();
     }
 
@@ -658,8 +657,7 @@ bool GERBVIEW_FRAME::LoadZipArchiveFile( const wxString& aFullFileName )
         m_mruPath = currentPath;
     }
 
-    wxString msg;
-    WX_STRING_REPORTER reporter( &msg );
+    WX_STRING_REPORTER reporter;
 
     if( filename.IsOk() )
         unarchiveFiles( filename.GetFullPath(), &reporter );
@@ -672,12 +670,12 @@ bool GERBVIEW_FRAME::LoadZipArchiveFile( const wxString& aFullFileName )
     m_LayersManager->UpdateLayerIcons();
     syncLayerBox();
 
-    if( !msg.IsEmpty() )
+    if( reporter.HasMessage() )
     {
         wxSafeYield();  // Allows slice of time to redraw the screen
                         // to refresh widgets, before displaying messages
         HTML_MESSAGE_BOX mbox( this, _( "Messages" ) );
-        mbox.ListSet( msg );
+        mbox.ListSet( reporter.GetMessages() );
         mbox.ShowModal();
     }
 
diff --git a/gerbview/job_file_reader.cpp b/gerbview/job_file_reader.cpp
index d45438cb5f..01b80e1cb2 100644
--- a/gerbview/job_file_reader.cpp
+++ b/gerbview/job_file_reader.cpp
@@ -208,8 +208,7 @@ bool GERBVIEW_FRAME::LoadGerberJobFile( const wxString& aFullFileName )
         m_mruPath = currentPath;
     }
 
-    wxString msg;
-    WX_STRING_REPORTER reporter( &msg );
+    WX_STRING_REPORTER reporter;
 
     if( filename.IsOk() )
     {
@@ -235,12 +234,12 @@ bool GERBVIEW_FRAME::LoadGerberJobFile( const wxString& aFullFileName )
 
     SortLayersByX2Attributes();
 
-    if( !msg.IsEmpty() )
+    if( reporter.HasMessage() )
     {
         wxSafeYield();  // Allows slice of time to redraw the screen
                         // to refresh widgets, before displaying messages
         HTML_MESSAGE_BOX mbox( this, _( "Messages" ) );
-        mbox.ListSet( msg );
+        mbox.ListSet( reporter.GetMessages() );
         mbox.ShowModal();
     }
 
diff --git a/include/reporter.h b/include/reporter.h
index 588078b90a..0ebd46ff8a 100644
--- a/include/reporter.h
+++ b/include/reporter.h
@@ -121,6 +121,12 @@ public:
      */
     virtual bool HasMessage() const = 0;
 
+    /**
+     * Returns true if the reporter has one or more messages matching the specified
+     * severity mask.
+     */
+    virtual bool HasMessageOfSeverity( int aSeverityMask ) const;
+
     virtual EDA_UNITS GetUnits() const
     {
         return EDA_UNITS::MILLIMETRES;
@@ -164,9 +170,9 @@ private:
 class KICOMMON_API WX_STRING_REPORTER : public REPORTER
 {
 public:
-    WX_STRING_REPORTER( wxString* aString ) :
+    WX_STRING_REPORTER() :
         REPORTER(),
-        m_string( aString )
+        m_severityMask( 0 )
     {
     }
 
@@ -177,9 +183,14 @@ public:
     REPORTER& Report( const wxString& aText, SEVERITY aSeverity = RPT_SEVERITY_UNDEFINED ) override;
 
     bool HasMessage() const override;
+    bool HasMessageOfSeverity( int aSeverityMask ) const override;
+
+    const wxString& GetMessages() const;
+    void            Clear();
 
 private:
-    wxString* m_string;
+    wxString m_string;
+    int      m_severityMask;
 };
 
 
diff --git a/include/widgets/report_severity.h b/include/widgets/report_severity.h
index ddfa7b33ab..8c4df164f3 100644
--- a/include/widgets/report_severity.h
+++ b/include/widgets/report_severity.h
@@ -24,14 +24,14 @@
 // Note: On windows, SEVERITY_ERROR collides with a system declaration,
 // so we used RPT_SEVERITY_xxx instead of SEVERITY_xxx
 enum SEVERITY {
-    RPT_SEVERITY_UNDEFINED = 0x00,
-    RPT_SEVERITY_INFO      = 0x01,
-    RPT_SEVERITY_EXCLUSION = 0x02,
-    RPT_SEVERITY_ACTION    = 0x04,
-    RPT_SEVERITY_WARNING   = 0x08,
-    RPT_SEVERITY_ERROR     = 0x10,
-    RPT_SEVERITY_IGNORE    = 0x20,
-    RPT_SEVERITY_DEBUG     = 0x40,
+    RPT_SEVERITY_UNDEFINED = 0x01,
+    RPT_SEVERITY_INFO      = 0x02,
+    RPT_SEVERITY_EXCLUSION = 0x04,
+    RPT_SEVERITY_ACTION    = 0x08,
+    RPT_SEVERITY_WARNING   = 0x10,
+    RPT_SEVERITY_ERROR     = 0x20,
+    RPT_SEVERITY_IGNORE    = 0x40,
+    RPT_SEVERITY_DEBUG     = 0x80,
 };
 
 #endif // REPORT_SEVERITY_H
diff --git a/pcbnew/files.cpp b/pcbnew/files.cpp
index c638b3e1a4..19a19dcc66 100644
--- a/pcbnew/files.cpp
+++ b/pcbnew/files.cpp
@@ -1079,10 +1079,10 @@ bool PCB_EDIT_FRAME::SavePcbFile( const wxString& aFileName, bool addToHistory,
 
     if( !Kiface().IsSingle() )
     {
-        WX_STRING_REPORTER backupReporter( &upperTxt );
+        WX_STRING_REPORTER backupReporter;
 
-        if( GetSettingsManager()->TriggerBackupIfNeeded( backupReporter ) )
-            upperTxt.clear();
+        if( !GetSettingsManager()->TriggerBackupIfNeeded( backupReporter ) )
+            upperTxt = backupReporter.GetMessages();
     }
 
     GetBoard()->SetFileName( pcbFileName.GetFullPath() );
diff --git a/qa/schematic_utils/eeschema_test_utils.cpp b/qa/schematic_utils/eeschema_test_utils.cpp
index f6d77d5bb8..cc9f7a1dce 100644
--- a/qa/schematic_utils/eeschema_test_utils.cpp
+++ b/qa/schematic_utils/eeschema_test_utils.cpp
@@ -133,13 +133,12 @@ void TEST_NETLIST_EXPORTER_FIXTURE<Exporter>::WriteNetlist()
     if( wxFileExists( GetNetlistPath( true ) ) )
         wxRemoveFile( GetNetlistPath( true ) );
 
-    wxString                  errors;
-    WX_STRING_REPORTER        reporter( &errors );
+    WX_STRING_REPORTER        reporter;
     std::unique_ptr<Exporter> exporter = std::make_unique<Exporter>( &m_schematic );
 
     bool success = exporter->WriteNetlist( GetNetlistPath( true ), GetNetlistOptions(), reporter );
 
-    BOOST_REQUIRE( success && errors.IsEmpty() );
+    BOOST_REQUIRE( success && reporter.GetMessages().IsEmpty() );
 }