Skip to content

Conversation

@blackcathj
Copy link

@blackcathj blackcathj commented Jan 7, 2026

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work for users)
  • Requiring change in macros repository (Please provide links to the macros pull request in the last section)
  • I am a member of GitHub organization of sPHENIX Collaboration, EIC, or ECCE (contact Chris Pinkenburg to join)

What kind of change does this PR introduce? (Bug fix, feature, ...)

TODOs (if applicable)

Links to other PRs in macros and calibration repositories (if applicable)

Summary by CodeRabbit

  • Bug Fixes

    • Calorimeter energy totals and QA histograms now exclude unhealthy towers and apply corrected QA selection.
    • Several empty/invalid raw-data cases now skip or gracefully skip processing instead of producing corrupt outputs.
  • New Features

    • Micromegas waveform evaluation now records a matched flag and always emits evaluation entries.
    • New particle-trigger and a tracking residuals QA tool added for configurable event selection and diagnostics.
    • Pre-file-opening hook to run user scripts before input files are opened.
  • Refactor

    • Streaming input and various reconstruction modules optimized for clearer iteration and safer resource handling.

✏️ Tip: You can customize this high-level summary in your review settings.

Daniel J Lis and others added 30 commits December 17, 2025 20:59
Fill ntuple for both matched and unmatched waveforms
…s_pool_evaluation_update

Small update on TPOT event combining evaluation
if multiple hits are found on the same strip only the first one, timewise, is kept.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
add quality flag to total energy calc and correlations
silas-gross and others added 29 commits January 12, 2026 22:23
…Particle. Keeping the Pythia Trigger style for now
fixed crash when calopackets are empty
…-drift-pythia8

this change fixes the tpc drift velocity being set to zero
Docstrings generation was requested by @pinkenburg.

* sPHENIX-Collaboration#4111 (comment)

The following files were modified:

* `generators/PHPythia8/PHPythia8.cc`
Add newline at the end of PHPythia8.cc file
…ion/coderabbitai/docstrings/63e54e6

📝 Add docstrings to `fix-tpc-drift-pythia8`
abort events with any mbd packet empty
New QA module to make cluster-state residual plots
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
generators/PHPythia8/PHPythia8.cc (1)

53-71: Guard against null m_Pythia8 when PYTHIA8 is unset.

The constructor returns early if PYTHIA8 is missing, leaving m_Pythia8 null; Init()/process_event() then dereference it and will crash. Add a guard (and return ABORTRUN) before first use so misconfiguration fails gracefully.

🛠️ Proposed fix (add a guard in Init)
 int PHPythia8::Init(PHCompositeNode *topNode)
 {
+  if (!m_Pythia8)
+  {
+    std::cout << PHWHERE << " ERROR: PYTHIA8 not set; Pythia8 not initialized." << std::endl;
+    return Fun4AllReturnCodes::ABORTRUN;
+  }
   if (!m_ConfigFileName.empty())
   {
     read_config(m_ConfigFileName);
   }
🤖 Fix all issues with AI agents
In `@generators/Herwig/HepMCTrigger/HepMCParticleTrigger.cc`:
- Around line 79-107: The function currently treats a non-null but empty
PHHepMCGenEventMap (phg) as a successful event because the loop never runs and
good_event stays false, leading to returning EVENT_OK; fix this by explicitly
checking phg->empty() after retrieving phg and returning
Fun4AllReturnCodes::ABORTEVENT if it is empty so empty maps do not count as
valid events; update the logic around phg, the for-loop over
PHHepMCGenEventMap::ConstIter, and the n_good increment (leave n_good++ only
when a good_event was actually found via isGoodEvent on a real HepMC::GenEvent).
- Around line 344-351: The code uses std::map::contains (C++20) on
particle_types with pid which breaks builds on C++17; change the check to use
particle_types.find(pid) != particle_types.end() (or capture the iterator once)
and then increment the mapped value or insert a new entry accordingly, updating
the block that references particle_types and pid in HepMCParticleTrigger.cc so
it is C++17-compatible.

In `@offline/framework/ffamodules/HeadReco.cc`:
- Around line 87-93: When genevt->get_flow_psi_map() is empty the existing loop
that sets evtheader->set_FlowPsiN(n, genevt->get_flow_psi(n)) is skipped,
leaving stale values in EventHeader; add an else branch that iterates n = 1..6
and calls evtheader->set_FlowPsiN(n, 0.0) (or NaN if preferred) to reset all
harmonics, ensuring any previous FlowPsiN values are cleared when
genevt->get_flow_psi_map() is empty.

In `@offline/framework/fun4all/InputFileHandler.cc`:
- Around line 177-182: The current owner-only permission check rejects scripts
executable by group/other or allowed via the process's effective UID/GID;
replace the owner_exec bit test in InputFileHandler.cc (the block using
m_RunBeforeOpeningScript) with a real executable check using
access(m_RunBeforeOpeningScript.c_str(), X_OK) from <unistd.h> (include the
header), and keep the same error path (std::cout + return -1) but update the
message to indicate the script is not executable by the current process; ensure
you call access on the C-string form and check for a zero return to allow
execution.
- Around line 183-193: The code builds a shell command by concatenating
m_RunBeforeOpeningScript, m_OpeningArgs and entries of stringvec and then calls
gSystem->Exec, which allows command injection; modify InputFileHandler to avoid
shell interpretation by replacing gSystem->Exec(fullcmd.c_str()) with a direct
exec-style invocation (fork + execv or posix_spawn) that passes
m_RunBeforeOpeningScript as the program and a vector<char*> argv built from
m_RunBeforeOpeningScript, m_OpeningArgs split safely (or omitted) and each
element of stringvec as separate arguments; if exec-style replacement is not
possible, at minimum escape or strictly validate/quote each argument (validate
against a safe character whitelist) before concatenation and logging (refer to
m_RunBeforeOpeningScript, m_OpeningArgs, stringvec, gSystem->Exec and PHWHERE to
locate the affected code).

In `@offline/packages/jetbackground/DetermineTowerBackground.cc`:
- Around line 598-602: The code in DetermineTowerBackground::process_event
checks epmap and currently calls exit(-1) on failure; replace that exit call
with a framework-friendly return of Fun4AllReturnCodes::ABORTRUN so the routine
returns gracefully (i.e., change exit(-1) to return
Fun4AllReturnCodes::ABORTRUN), and ensure the translation unit can see
Fun4AllReturnCodes (add the appropriate include or fully qualify the symbol if
necessary); keep the diagnostic cout that logs _do_flow and epmap absence.
- Around line 953-957: The code in DetermineTowerBackground::process_event
aborts the program with exit(-1) when centinfo is null; replace that exit call
with returning Fun4AllReturnCodes::ABORTRUN to use the framework's consistent
error handling. Locate the null-check for centinfo (and the associated log line
that mentions _do_flow) and change the control flow to return
Fun4AllReturnCodes::ABORTRUN instead of calling exit, keeping the std::cout
error message intact.
- Around line 542-546: In DetermineTowerBackground::process_event where the code
returns raw -1 when truthinfo is null, replace the literal -1 with the framework
constant Fun4AllReturnCodes::ABORTRUN and ensure the translation unit can see
that symbol (add the appropriate include for Fun4AllReturnCodes if missing).
Update the return statement that currently returns -1 to return
Fun4AllReturnCodes::ABORTRUN and keep the existing diagnostic cout intact.
- Around line 87-91: The code currently calls exit(-1) when calibdir.empty();
replace that hard exit with a graceful return of Fun4AllReturnCodes::ABORTRUN
from the enclosing function (e.g., in DetermineTowerBackground.cc where the
calibdir.empty() check lives), and ensure the Fun4AllReturnCodes symbol is
visible (include the appropriate header or qualify the enum) so the function
returns the abort code instead of calling exit.

In `@offline/packages/mbd/MbdEvent.cc`:
- Around line 494-499: The code sets _mbdsig[feech].SetNSamples(_nsamples) but
skips SetXY when _nsamples > 30, leaving prior waveform data in _mbdsig[feech];
to fix, detect the out-of-range case (_nsamples <= 0 or _nsamples > 30) and mark
the channel empty by resetting the sample count and clearing waveform data
(e.g., call SetNSamples(0) and any available clear/reset method on
_mbdsig[feech]) instead of leaving the old samples; keep SetXY(m_samp[feech],
m_adc[feech]) only for the valid branch where 0 < _nsamples <= 30 so downstream
processing never sees stale data.

In `@offline/packages/mbd/MbdReco.cc`:
- Around line 104-108: The code leaves _evtnum unchanged when m_evtheader is
null, causing stale event numbers to persist; update the event-processing code
in MbdReco.cc (where m_evtheader, _evtnum and m_mbdevent->set_EventNumber are
used) to reset _evtnum at the start of each event (e.g., assign a sentinel like
-1 or 0) and then only overwrite it when m_evtheader!=nullptr, and ensure
m_mbdevent->set_EventNumber is always called with the sentinel when the header
is missing; apply the same change to the other block referenced (around the
124-140 region) so both places consistently clear and set the event number.

In `@offline/packages/micromegas/MicromegasClusterizer.cc`:
- Around line 223-275: The code stores iterator ranges into ranges using
TrkrHitSet::ConstRange but you built a LocalMap with custom comparator
(StripSortFtor), so TrkrHitSet::ConstRange iterators are incompatible; change
the range type to use LocalMap iterators (e.g., define a LocalRange type as
std::pair<LocalMap::const_iterator, LocalMap::const_iterator> or an alias using
LocalMap::const_iterator) and make the variable ranges (or range_list_t) use
that LocalRange, then emplace_back(begin, hit_it) and emplace_back(begin,
local_hitmap.end()) as before so the stored iterators match LocalMap.

In `@offline/packages/mvtx/MvtxClusterPruner.cc`:
- Around line 235-238: The current print in MvtxClusterPruner::End computes
double(m_cluster_counter_deleted) / m_cluster_counter_total which can divide by
zero; update the End method to guard the division by checking
m_cluster_counter_total > 0 (e.g., compute a local double fraction =
(m_cluster_counter_total > 0 ? double(m_cluster_counter_deleted) /
m_cluster_counter_total : 0.0) or use a sentinel like NAN/"N/A") and then print
m_cluster_counter_deleted, m_cluster_counter_total, and the safe fraction
variable instead of performing the raw division inline.

In `@offline/packages/tpc/LaserClusterizer.cc`:
- Around line 210-214: The code assumes regions[0] exists when copying into
clusHits; add a guard to avoid out-of-bounds access by checking clusHits (or
regions) before using regions[0] — e.g., if clusHits.empty() return/continue or
if regions.empty() return/continue — and ensure unvisited is populated only when
clusHits has elements so the subsequent while loop and access to regions[0] are
safe; update the block around clusHits.clear(), the loop that populates
clusHits, and any callers that rely on regions to handle the empty-case
consistently.

In `@offline/QA/Tracking/StateClusterResidualsQA.cc`:
- Around line 113-118: The loop that fills m_histograms_x, m_histograms_y, and
m_histograms_z uses dynamic_cast on hm->getHisto without checking for nullptr,
so add validation after retrieving each histogram pointer (from hm->getHisto and
after dynamic_cast to TH1*) and only push_back if the pointer is non-null; if
any retrieval fails, log a warning including cfg.name (or otherwise handle it)
and do not push into the vectors (or push a placeholder consistently) to keep
vector sizes aligned and avoid dereferencing nulls later in process_event (which
reads m_histograms_x/y/z).
- Around line 185-198: The code calls
geometry->getGlobalPosition(state->get_cluskey(), cluster) before verifying
cluster is non-null, risking a null dereference from cluster_map->findCluster;
change the control flow so you first call auto *cluster =
cluster_map->findCluster(state->get_cluskey()) and immediately check if
(cluster) before invoking geometry->getGlobalPosition or using cluster, and only
then compute glob, cluster_x/cluster_y/cluster_z and fill m_histograms_x[h],
m_histograms_y[h], m_histograms_z[h]; leave state->get_x(), get_y(), get_z() as
needed but ensure getGlobalPosition is not called with a null cluster.

In `@offline/QA/Tracking/StateClusterResidualsQA.h`:
- Line 127: The trailing `#endif` comment is wrong: replace the incorrect comment
"TRACKFITTINGQA_H" with the actual include guard name
"STATECLUSTERRESIDUALSQA_H" so the end of the header matches the opening guard;
update the comment after the `#endif` accordingly in StateClusterResidualsQA.h to
read `#endif`  // STATECLUSTERRESIDUALSQA_H.
- Around line 56-101: All setter methods (setNMvtx, setNIntt, setNTpc,
setPhiRange, setEtaRange, setPtRange, setPositiveTracks, setNegativeTracks)
access m_pending.back() without ensuring m_pending is non-empty; update each
setter to guard against an empty m_pending by either creating a default pending
entry (e.g., if (m_pending.empty()) m_pending.emplace_back();) before using
back(), or by throwing/checking with a clear error message instructing the
caller to call addHistogram() first—apply the same fix to every setter so
m_pending.back() is always safe to access.
🧹 Nitpick comments (31)
generators/phhepmc/PHHepMCGenEventv1.cc (1)

113-114: Verify NaN propagation and prefer project logging over std::cout.
Switching missing psi_n from 0.0 to NaN is a behavior change; please confirm downstream callers (cuts, histogram fills, aggregations, serialization) explicitly handle NaN or check existence before use. Also consider routing this warning through the project logging facility to avoid noisy stdout from a const getter.

offline/packages/mvtx/CylinderGeom_Mvtx.h (1)

14-20: Update stale comment in default constructor.

Line 26 has a comment /*int in_Nstaves*/ that is now inconsistent with the parameter name in_N_staves on line 16.

Suggested fix
   //! default ctor to allow ROOT stream of this class. Implemented using c++11 feature of delegating constructors
   CylinderGeom_Mvtx()
     : CylinderGeom_Mvtx(
           /*int layer*/ 0,
-          /*int in_Nstaves*/ 0,
+          /*int in_N_staves*/ 0,
           /*double in_layer_nominal_radius*/ 3,
           /*double in_phistep*/ 0,
           /*double in_phitilt*/ 0,
           /*double in_phi0*/ 0)
offline/packages/mvtx/CylinderGeom_Mvtx.cc (1)

124-134: Consider initializing variables for defensive coding.

The split declarations are fine. The variables are set by get_pixel_from_local_coords before use, but initializing them to safe defaults would be slightly more defensive.

Optional: Initialize variables
-  int Ngridx;
-  int Ngridz;
+  int Ngridx = -1;
+  int Ngridz = -1;
offline/packages/trackbase/AlignmentTransformation.cc (2)

280-291: Consider passing use_module_tilt as a parameter to newMakeTransform.

The use_module_tilt flag is set here and then read in newMakeTransform, creating implicit coupling through class member state. While this works correctly (the flag is reset at line 280 before each iteration), passing it as an explicit parameter would make the data flow clearer and the function more self-contained.

Suggested approach

Add bool use_module_tilt as a parameter to newMakeTransform and pass it explicitly at the call site (line 295), similar to how survey is already passed.


491-498: Consider using Eigen's built-in subtraction for cleaner output.

The nested loop manually computes element-wise differences. Eigen supports direct matrix subtraction which could simplify this.

♻️ Suggested simplification
       std::cout << "overall - ideal " << std::endl;
-      for (int test = 0; test < transform.matrix().rows(); test++)
-	{
-	  for (int test2 = 0; test2 < transform.matrix().cols(); test2++)
-	    {
-	      std::cout << transform(test, test2) - actstransform(test, test2) << ", ";
-	    }
-	  std::cout << std::endl;
-	}
+      std::cout << (transform.matrix() - actstransform.matrix()) << std::endl;
offline/framework/fun4all/InputFileHandler.h (1)

36-40: API additions look good.

The getter/setter pattern is consistent with the existing class interface. Consider adding brief documentation (inline or in a separate doc) clarifying:

  • What m_OpeningArgs format is expected (space-separated? shell-escaped?)
  • That RunBeforeOpening executes a shell command (security implications for callers)
offline/packages/jetbackground/DetermineTowerBackground.h (2)

16-16: Unused #include <array>.

The <array> header is included but std::array doesn't appear to be used in this file. Consider removing it if not needed.

Suggested fix
-#include <array>

41-45: Parameter should be const std::string&.

Taking a non-const lvalue reference prevents callers from passing string literals or temporaries directly. Use const std::string& for flexibility.

Suggested fix
-  void SetOverwriteCaloV2(std::string &url)
+  void SetOverwriteCaloV2(const std::string &url)
   {
     m_overwrite_average_calo_v2 = true;
     m_overwrite_average_calo_v2_path = url;
   }
offline/packages/jetbackground/DetermineTowerBackground.cc (1)

93-105: Consider using std::unique_ptr for exception safety.

Using a smart pointer would prevent memory leaks if an exception occurs during calibration loading.

Suggested fix
-  cdbtree_calo_v2 = new CDBTTree(calibdir);
-  
-    
-  cdbtree_calo_v2->LoadCalibrations();
+  auto cdbtree_calo_v2 = std::make_unique<CDBTTree>(calibdir);
+  cdbtree_calo_v2->LoadCalibrations();

   _CENTRALITY_V2.assign(100,0);

   for (int icent = 0; icent < 100; icent++)
     {
       _CENTRALITY_V2[icent] = cdbtree_calo_v2->GetFloatValue(icent, "jet_calo_v2");
     }
-      
-  delete cdbtree_calo_v2;
+  // unique_ptr automatically deletes
offline/QA/Tracking/StateClusterResidualsQA.cc (4)

32-33: Unused helper function.

The square template function is defined but never used in this file. Consider removing it to avoid dead code.


46-47: Mark begin() and end() as const member functions.

For proper range-based for loop semantics and const-correctness, these methods should be const.

Suggested fix
-    T const& begin() { return m_begin; }
-    T const& end() { return m_end; }
+    T const& begin() const { return m_begin; }
+    T const& end() const { return m_end; }

210-211: Consider graceful error handling instead of assert.

createHistos() is called (line 63) before the histogram manager null-check in InitRun (lines 102-111). If getHistoManager() returns nullptr, the assert(hm) will terminate the program rather than allowing a graceful ABORTRUN. Consider moving the histogram manager validation before createHistos() or returning a status from createHistos().


239-245: Unused variable hm in EndRun.

The histogram manager is retrieved and asserted but never used. If no end-of-run processing is needed, consider removing the unused code to reduce confusion.

Suggested simplification
 int StateClusterResidualsQA::EndRun(const int /*unused*/)
 {
-  auto *hm = QAHistManagerDef::getHistoManager();
-  assert(hm);
-
   return Fun4AllReturnCodes::EVENT_OK;
 }
offline/QA/Tracking/StateClusterResidualsQA.h (2)

9-11: Unused include and inconsistent max value style.

<limits> is included but unused. Line 35 uses FLT_MAX from <cfloat>. Consider using std::numeric_limits<float>::max() for consistency with C++ idioms, or remove the unused <limits> include.


22-27: Micromegas cluster count selection not exposed.

The implementation tracks micromegas cluster counts (in StateClusterResidualsQA.cc line 141) but ResidualHistConfig doesn't include min_micromegas_clusters/max_micromegas_clusters fields, and there's no setNMicromegas() setter. If micromegas selection is needed in the future, consider adding these for consistency with the other detectors.

offline/packages/tpc/TpcClusterMover.h (1)

30-30: LGTM!

The const qualifier aligns with the implementation in TpcClusterMover.cc which defines the method as const.

Optional consistency note: PHTpcClusterMover::get_circle_circle_intersection in offline/packages/trackreco/PHTpcClusterMover.cc has a similar signature without const. Consider aligning both classes for consistency in a follow-up.

offline/packages/tpc/Tpc3DClusterizer.cc (1)

523-528: Consider using std::numeric_limits for sentinel values.

The magic numbers (6666, -1, 66666666.6) work but could be more expressive using std::numeric_limits.

♻️ Optional improvement
-  int iphimin = 6666;
-  int iphimax = -1;
-  int ilaymin = 6666;
-  int ilaymax = -1;
-  float itmin = 66666666.6;
-  float itmax = -6666666666.6;
+  int iphimin = std::numeric_limits<int>::max();
+  int iphimax = std::numeric_limits<int>::min();
+  int ilaymin = std::numeric_limits<int>::max();
+  int ilaymax = std::numeric_limits<int>::min();
+  float itmin = std::numeric_limits<float>::max();
+  float itmax = std::numeric_limits<float>::lowest();
offline/packages/tpc/LaserClusterizer.cc (3)

833-836: Consider documenting the run number threshold.

The magic number 66153 determines which laser event check method to use. Consider adding a comment explaining the significance of this run number or extracting it as a named constant for maintainability.


1006-1007: Consider removing commented-out code.

The commented line // const auto &data(thread_pair.data); at line 1006 is unused. If no longer needed, consider removing it to keep the code clean.

♻️ Proposed fix
-      // const auto &data(thread_pair.data);
-
       for (int index = 0; index < (int) thread_pair.data.cluster_vector.size(); ++index)

69-70: Remove unused global arrays layerMins and layerMaxes.

These arrays are declared but never referenced in this file or elsewhere in the codebase. They can be safely removed.

generators/Herwig/HepMCTrigger/HepMCJetTrigger.cc (2)

99-103: Minor typo in comment.

The comment has a typo: "behavioro" should be "behaviour" or "behavior".

The physics logic is correct—skipping PDG IDs 12–18 excludes neutrinos (12, 14, 16) and charged leptons (13, 15) from jet clustering, which is standard practice since these particles typically don't contribute to hadronic jet energy.

📝 Fix typo
       if (pd >= 12 && pd <= 18)
       {
-        continue;  // keep jet in the expected behavioro
+        continue;  // keep jet in the expected behavior
       }

130-133: LGTM, but consider making the eta cut configurable.

The eta cut is reasonable for central detector acceptance. However, the value 1.1 is hardcoded. For consistency with HepMCParticleTrigger (which uses configurable _theEtaHigh/_theEtaLow), consider adding a setter or constructor parameter for the jet eta cut.

generators/Herwig/HepMCTrigger/Makefile.am (1)

45-46: Consider adding link test for the new library.

testexternals only links against libHepMCJetTrigger.la. For completeness, consider adding libHepMCParticleTrigger.la to verify linkage, or create a separate test target.

generators/Herwig/HepMCTrigger/HepMCParticleTrigger.h (2)

8-8: Unnecessary include.

fastjet/PseudoJet.hh is not used in this header. It can be removed to reduce compilation dependencies.

♻️ Proposed fix
 `#include` <fun4all/SubsysReco.h>
 
-#include <fastjet/PseudoJet.hh>
-
 `#include` <cmath>

85-90: Inconsistent default for _thePtLow.

The header initializes _thePtLow{-999.9} but the constructor sets it to 0. Since pT is always non-negative, the header default is misleading. Align the defaults for clarity.

♻️ Proposed fix
-  float _thePtLow{-999.9};
+  float _thePtLow{0.0};
generators/Herwig/HepMCTrigger/HepMCParticleTrigger.cc (2)

12-16: Remove unused FastJet includes.

These FastJet headers are not used in this file. They appear to be copy-paste artifacts from HepMCJetTrigger.cc.

♻️ Proposed fix
 `#include` <phool/getClass.h>
 
-#include <fastjet/JetDefinition.hh>
-
 `#include` <HepMC/GenEvent.h>
 
-#include <fastjet/PseudoJet.hh>
 `#include` <map>

22-65: Constructor initializer list has unusual formatting.

The blank lines within the member initializer list (e.g., lines 35–36, 40–41) are unconventional. Consider running clang-format for consistency with the rest of the codebase.

offline/packages/mbd/MbdSig.h (1)

35-35: Make the accessor const (and optionally noexcept).

This accessor doesn’t mutate state; marking it const improves const-correctness and usage on const instances.

♻️ Proposed change
-  int  GetNSamples() { return _nsamples; }
+  int GetNSamples() const noexcept { return _nsamples; }
offline/packages/tpc/LaserEventIdentifier.cc (1)

141-148: Consider extracting magic number and handling BCO rollover at counter boundaries.

Two observations on this existing logic:

  1. Magic number: 350.0/30*16 (~187) should be a named constant with a comment explaining its derivation (appears to be a timing threshold in BCO units).

  2. BCO rollover: When BCO counters wrap around, if prev_BCO > gl1pkt->getBCO(), the unsigned subtraction produces a very large value due to underflow, causing the pileup condition to be false when it should potentially be true. Consider using a helper function or modular arithmetic to safely compare BCO values across rollover boundaries.

offline/QA/Tracking/MicromegasClusterQA.h (1)

54-58: Guard invalid sample window configurations

If m_sample_min >= m_sample_max, the new filtering logic will drop every cluster. Consider validating (or at least warning) on invalid ranges in setters or InitRun to avoid silent misconfiguration.

Also applies to: 108-112

offline/QA/Tracking/MicromegasClusterQA.cc (1)

168-173: Validate sample window to avoid dropping all clusters

If m_sample_min >= m_sample_max, the predicate is always false and every cluster is skipped. Consider guarding or warning on invalid ranges (in setters or InitRun) so misconfiguration is caught early.

Comment on lines +79 to +107
bool good_event{false};
PHHepMCGenEventMap* phg = findNode::getClass<PHHepMCGenEventMap>(topNode, "PHHepMCGenEventMap");
if (!phg)
{
return Fun4AllReturnCodes::ABORTEVENT;
}
for (PHHepMCGenEventMap::ConstIter eventIter = phg->begin(); eventIter != phg->end(); ++eventIter)
{
PHHepMCGenEvent* hepev = eventIter->second;
if (!hepev)
{
return Fun4AllReturnCodes::ABORTEVENT;
}
HepMC::GenEvent* ev = hepev->getEvent();
if (!ev)
{
return Fun4AllReturnCodes::ABORTEVENT;
}
good_event = isGoodEvent(ev);
if (!good_event)
{
return Fun4AllReturnCodes::ABORTEVENT;
}
}
if (good_event)
{
n_good++;
}
return Fun4AllReturnCodes::EVENT_OK;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Empty event map returns EVENT_OK with good_event=false.

If PHHepMCGenEventMap is non-null but empty, the for-loop never executes, good_event remains false, and the function returns EVENT_OK without incrementing n_good. This may not be the intended behavior.

🐛 Proposed fix
   PHHepMCGenEventMap* phg = findNode::getClass<PHHepMCGenEventMap>(topNode, "PHHepMCGenEventMap");
   if (!phg)
   {
     return Fun4AllReturnCodes::ABORTEVENT;
   }
+  if (phg->size() == 0)
+  {
+    return Fun4AllReturnCodes::ABORTEVENT;
+  }
   for (PHHepMCGenEventMap::ConstIter eventIter = phg->begin(); eventIter != phg->end(); ++eventIter)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
bool good_event{false};
PHHepMCGenEventMap* phg = findNode::getClass<PHHepMCGenEventMap>(topNode, "PHHepMCGenEventMap");
if (!phg)
{
return Fun4AllReturnCodes::ABORTEVENT;
}
for (PHHepMCGenEventMap::ConstIter eventIter = phg->begin(); eventIter != phg->end(); ++eventIter)
{
PHHepMCGenEvent* hepev = eventIter->second;
if (!hepev)
{
return Fun4AllReturnCodes::ABORTEVENT;
}
HepMC::GenEvent* ev = hepev->getEvent();
if (!ev)
{
return Fun4AllReturnCodes::ABORTEVENT;
}
good_event = isGoodEvent(ev);
if (!good_event)
{
return Fun4AllReturnCodes::ABORTEVENT;
}
}
if (good_event)
{
n_good++;
}
return Fun4AllReturnCodes::EVENT_OK;
bool good_event{false};
PHHepMCGenEventMap* phg = findNode::getClass<PHHepMCGenEventMap>(topNode, "PHHepMCGenEventMap");
if (!phg)
{
return Fun4AllReturnCodes::ABORTEVENT;
}
if (phg->size() == 0)
{
return Fun4AllReturnCodes::ABORTEVENT;
}
for (PHHepMCGenEventMap::ConstIter eventIter = phg->begin(); eventIter != phg->end(); ++eventIter)
{
PHHepMCGenEvent* hepev = eventIter->second;
if (!hepev)
{
return Fun4AllReturnCodes::ABORTEVENT;
}
HepMC::GenEvent* ev = hepev->getEvent();
if (!ev)
{
return Fun4AllReturnCodes::ABORTEVENT;
}
good_event = isGoodEvent(ev);
if (!good_event)
{
return Fun4AllReturnCodes::ABORTEVENT;
}
}
if (good_event)
{
n_good++;
}
return Fun4AllReturnCodes::EVENT_OK;
🤖 Prompt for AI Agents
In `@generators/Herwig/HepMCTrigger/HepMCParticleTrigger.cc` around lines 79 -
107, The function currently treats a non-null but empty PHHepMCGenEventMap (phg)
as a successful event because the loop never runs and good_event stays false,
leading to returning EVENT_OK; fix this by explicitly checking phg->empty()
after retrieving phg and returning Fun4AllReturnCodes::ABORTEVENT if it is empty
so empty maps do not count as valid events; update the logic around phg, the
for-loop over PHHepMCGenEventMap::ConstIter, and the n_good increment (leave
n_good++ only when a good_event was actually found via isGoodEvent on a real
HepMC::GenEvent).

Comment on lines +344 to +351
if (particle_types.contains(pid))
{
particle_types[pid]++;
}
else
{
particle_types[pid] = 1;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Search for build configuration files
find . -maxdepth 3 -type f \( -name "CMakeLists.txt" -o -name "configure.ac" -o -name "Makefile.am" \) | head -20

Repository: sPHENIX-Test/coresoftware

Length of output: 852


🏁 Script executed:

# Search for C++ standard declarations across the repository
rg -i 'c\+\+20|c\+\+2a|cxx_std_20|CXX_STANDARD.*20|std=c\+\+20' --type-list | head -5
rg -i 'c\+\+20|c\+\+2a|cxx_std_20|CXX_STANDARD.*20|std=c\+\+20' -g "*.txt" -g "*.ac" -g "*.am" -g "*.cmake" 2>/dev/null | head -20

Repository: sPHENIX-Test/coresoftware

Length of output: 458


🏁 Script executed:

# Look for any C++ standard specifications
rg -i 'CXX_STANDARD|c\+\+17|c\+\+11|c\+\+14' -g "CMakeLists.txt" -g "configure.ac" -g "Makefile.am" 2>/dev/null | head -30

Repository: sPHENIX-Test/coresoftware

Length of output: 471


🏁 Script executed:

# Check the specific directory and parent directories for build files
ls -la generators/Herwig/HepMCTrigger/ 2>/dev/null | head -20
ls -la generators/Herwig/ 2>/dev/null | head -20
ls -la generators/ 2>/dev/null | grep -i cmake

Repository: sPHENIX-Test/coresoftware

Length of output: 816


🏁 Script executed:

# Check the Makefile.am in the HepMCTrigger directory
cat generators/Herwig/HepMCTrigger/Makefile.am

Repository: sPHENIX-Test/coresoftware

Length of output: 1134


🏁 Script executed:

# Check the configure.ac in the HepMCTrigger directory
cat generators/Herwig/HepMCTrigger/configure.ac

Repository: sPHENIX-Test/coresoftware

Length of output: 440


🏁 Script executed:

# Check if there's a parent configure.ac in generators/Herwig
find generators/Herwig -maxdepth 1 -name "configure.ac" -o -name "Makefile.am" 2>/dev/null

Repository: sPHENIX-Test/coresoftware

Length of output: 51


🏁 Script executed:

# Search for CXX_STANDARD or C++ flag declarations in the entire generators directory
rg -i 'CXX_STANDARD|AM_CXXFLAGS|std=' generators/Herwig/HepMCTrigger/ --type=make --type=cmake 2>/dev/null || echo "No matches found"

Repository: sPHENIX-Test/coresoftware

Length of output: 85


🏁 Script executed:

# Check if there's a root configure.ac or global C++ standard configuration
head -50 configure.ac 2>/dev/null || echo "No root configure.ac"

Repository: sPHENIX-Test/coresoftware

Length of output: 89


🏁 Script executed:

# Search for any other C++20 features in the HepMCTrigger files
rg 'contains\(|spaceship|concepts|requires|co_await' generators/Herwig/HepMCTrigger/ --type=cc --type=h

Repository: sPHENIX-Test/coresoftware

Length of output: 95


🏁 Script executed:

# Check if there's a toplevel CMakeLists.txt
ls -la CMakeLists.txt 2>/dev/null || echo "No CMakeLists.txt at root"

Repository: sPHENIX-Test/coresoftware

Length of output: 94


🏁 Script executed:

# Check the specific line in HepMCParticleTrigger.cc to confirm context
sed -n '340,355p' generators/Herwig/HepMCTrigger/HepMCParticleTrigger.cc

Repository: sPHENIX-Test/coresoftware

Length of output: 366


Replace std::map::contains() with C++17-compatible find() method.

The build configuration for this module does not explicitly require C++20. The code uses std::map::contains() (C++20) on line 344, which will fail to compile on systems defaulting to C++17 or earlier. Replace with find() for compatibility:

C++17-compatible fix
-    if (particle_types.contains(pid))
+    if (particle_types.find(pid) != particle_types.end())
     {
       particle_types[pid]++;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (particle_types.contains(pid))
{
particle_types[pid]++;
}
else
{
particle_types[pid] = 1;
}
if (particle_types.find(pid) != particle_types.end())
{
particle_types[pid]++;
}
else
{
particle_types[pid] = 1;
}
🤖 Prompt for AI Agents
In `@generators/Herwig/HepMCTrigger/HepMCParticleTrigger.cc` around lines 344 -
351, The code uses std::map::contains (C++20) on particle_types with pid which
breaks builds on C++17; change the check to use particle_types.find(pid) !=
particle_types.end() (or capture the iterator once) and then increment the
mapped value or insert a new entry accordingly, updating the block that
references particle_types and pid in HepMCParticleTrigger.cc so it is
C++17-compatible.

Comment on lines +87 to +93
if (! genevt->get_flow_psi_map().empty())
{
for (unsigned int n = 1; n <= 6; ++n)
{
evtheader->set_FlowPsiN(n, genevt->get_flow_psi(n));
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Explore the file structure and context around lines 87-93
head -100 offline/framework/ffamodules/HeadReco.cc | tail -30

Repository: sPHENIX-Test/coresoftware

Length of output: 1006


🏁 Script executed:

# Get broader context of the HeadReco.cc file
wc -l offline/framework/ffamodules/HeadReco.cc

Repository: sPHENIX-Test/coresoftware

Length of output: 113


🏁 Script executed:

# Look for EventHeader creation/instantiation patterns
rg -n "EventHeader\s*\*|new\s+EventHeader|evtheader\s*=" offline/framework/ffamodules/HeadReco.cc -A 2 -B 2

Repository: sPHENIX-Test/coresoftware

Length of output: 606


🏁 Script executed:

# Search for reset/initialization patterns in the file
rg -n "evtheader->set_|evtheader->reset|FlowPsiN" offline/framework/ffamodules/HeadReco.cc -B 1

Repository: sPHENIX-Test/coresoftware

Length of output: 679


🏁 Script executed:

# Check the EventHeader class definition to understand FlowPsiN
fd -e h -e hh | xargs rg -l "class EventHeader|FlowPsiN" | head -5

Repository: sPHENIX-Test/coresoftware

Length of output: 312


🏁 Script executed:

# Check EventHeaderv1 class definition for FlowPsiN
cat offline/framework/ffaobjects/EventHeaderv1.h

Repository: sPHENIX-Test/coresoftware

Length of output: 1796


🏁 Script executed:

# Also check v2 since the code might use that
cat offline/framework/ffaobjects/EventHeaderv2.h

Repository: sPHENIX-Test/coresoftware

Length of output: 1250


🏁 Script executed:

# Check if there's any initialization of EventHeader at the start of process_event
sed -n '65,110p' offline/framework/ffamodules/HeadReco.cc

Repository: sPHENIX-Test/coresoftware

Length of output: 1463


🏁 Script executed:

# Search for set_FlowPsiN implementation
rg -n "set_FlowPsiN|get_FlowPsiN" --type cpp --type h -B 3 -A 5

Repository: sPHENIX-Test/coresoftware

Length of output: 1807


🏁 Script executed:

# Check if Reset() is called anywhere in HeadReco.cc or at the start of process_event
rg -n "Reset()" offline/framework/ffamodules/HeadReco.cc

Repository: sPHENIX-Test/coresoftware

Length of output: 51


🏁 Script executed:

# Search for EventHeader base class to find where FlowPsiN is implemented
cat offline/framework/ffaobjects/EventHeader.h | head -150

Repository: sPHENIX-Test/coresoftware

Length of output: 3036


🏁 Script executed:

# Check EventHeaderv1 Reset implementation
fd -e cc -e cpp | xargs rg -l "EventHeaderv1::Reset" | head -1 | xargs cat

Repository: sPHENIX-Test/coresoftware

Length of output: 669


🏁 Script executed:

# Also look for EventHeaderv2::Reset if it exists
fd -e cc -e cpp | xargs rg -l "EventHeaderv2::Reset" | head -1 | xargs cat

Repository: sPHENIX-Test/coresoftware

Length of output: 669


🏁 Script executed:

# Check if there's any pattern where FlowPsiN or "psi_" values are reset/cleared elsewhere
rg -n "psi_|FlowPsiN" --type cpp --type h -B 2 -A 2 | head -100

Repository: sPHENIX-Test/coresoftware

Length of output: 9039


🏁 Script executed:

# Find EventHeaderv1.cc and check Reset() implementation
fd -e cc -e cpp | xargs rg -l "EventHeaderv1::Reset" | head -1 | xargs cat

Repository: sPHENIX-Test/coresoftware

Length of output: 2090


🏁 Script executed:

# Also search for EventHeaderv1::Reset in all files
rg -n "EventHeaderv1::Reset\|void.*Reset\(\)" offline/framework/ffaobjects/EventHeaderv1.cc -A 10

Repository: sPHENIX-Test/coresoftware

Length of output: 51


Reset FlowPsiN values when the flow_psi_map is empty to prevent stale data leakage.

The EventHeader object persists across events in a run and is never reset between them. When get_flow_psi_map() is empty, the code skips updating FlowPsiN, leaving old values from the previous event in the object. This causes data corruption for events without flow information. Add an else clause to reset all FlowPsiN harmonics to a known default (0.0 or NaN) when the map is empty.

Suggested fix
-            if (! genevt->get_flow_psi_map().empty())
-            {
-              for (unsigned int n = 1; n <= 6; ++n)
-              {
-                evtheader->set_FlowPsiN(n, genevt->get_flow_psi(n));
-              }
-            }
+            if (!genevt->get_flow_psi_map().empty())
+            {
+              for (unsigned int n = 1; n <= 6; ++n)
+              {
+                evtheader->set_FlowPsiN(n, genevt->get_flow_psi(n));
+              }
+            }
+            else
+            {
+              for (unsigned int n = 1; n <= 6; ++n)
+              {
+                evtheader->set_FlowPsiN(n, 0.0);
+              }
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (! genevt->get_flow_psi_map().empty())
{
for (unsigned int n = 1; n <= 6; ++n)
{
evtheader->set_FlowPsiN(n, genevt->get_flow_psi(n));
}
}
if (!genevt->get_flow_psi_map().empty())
{
for (unsigned int n = 1; n <= 6; ++n)
{
evtheader->set_FlowPsiN(n, genevt->get_flow_psi(n));
}
}
else
{
for (unsigned int n = 1; n <= 6; ++n)
{
evtheader->set_FlowPsiN(n, 0.0);
}
}
🤖 Prompt for AI Agents
In `@offline/framework/ffamodules/HeadReco.cc` around lines 87 - 93, When
genevt->get_flow_psi_map() is empty the existing loop that sets
evtheader->set_FlowPsiN(n, genevt->get_flow_psi(n)) is skipped, leaving stale
values in EventHeader; add an else branch that iterates n = 1..6 and calls
evtheader->set_FlowPsiN(n, 0.0) (or NaN if preferred) to reset all harmonics,
ensuring any previous FlowPsiN values are cleared when
genevt->get_flow_psi_map() is empty.

Comment on lines +177 to +182
if (!((std::filesystem::status(m_RunBeforeOpeningScript).permissions() & std::filesystem::perms::owner_exec) == std::filesystem::perms::owner_exec))
{
std::cout << PHWHERE << "RunBeforeOpeningScript script "
<< m_RunBeforeOpeningScript << " is not owner executable" << std::endl;
return -1;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Permission check may be too restrictive.

Only owner_exec is verified. If the script is owned by another user (e.g., root) but has group or world execute permissions, this check will incorrectly reject a valid executable script.

Consider using access(path, X_OK) from <unistd.h> which checks if the current process can execute the file, accounting for actual user/group permissions.

🔧 Suggested fix using access()
+#include <unistd.h>
 // ...
-  if (!((std::filesystem::status(m_RunBeforeOpeningScript).permissions() & std::filesystem::perms::owner_exec) == std::filesystem::perms::owner_exec))
+  if (access(m_RunBeforeOpeningScript.c_str(), X_OK) != 0)
   {
     std::cout << PHWHERE << "RunBeforeOpeningScript script "
               << m_RunBeforeOpeningScript << " is not executable" << std::endl;
     return -1;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!((std::filesystem::status(m_RunBeforeOpeningScript).permissions() & std::filesystem::perms::owner_exec) == std::filesystem::perms::owner_exec))
{
std::cout << PHWHERE << "RunBeforeOpeningScript script "
<< m_RunBeforeOpeningScript << " is not owner executable" << std::endl;
return -1;
}
if (access(m_RunBeforeOpeningScript.c_str(), X_OK) != 0)
{
std::cout << PHWHERE << "RunBeforeOpeningScript script "
<< m_RunBeforeOpeningScript << " is not executable" << std::endl;
return -1;
}
🤖 Prompt for AI Agents
In `@offline/framework/fun4all/InputFileHandler.cc` around lines 177 - 182, The
current owner-only permission check rejects scripts executable by group/other or
allowed via the process's effective UID/GID; replace the owner_exec bit test in
InputFileHandler.cc (the block using m_RunBeforeOpeningScript) with a real
executable check using access(m_RunBeforeOpeningScript.c_str(), X_OK) from
<unistd.h> (include the header), and keep the same error path (std::cout +
return -1) but update the message to indicate the script is not executable by
the current process; ensure you call access on the C-string form and check for a
zero return to allow execution.

Comment on lines +183 to +193
std::string fullcmd = m_RunBeforeOpeningScript + " " + m_OpeningArgs;
for (const auto& iter : stringvec)
{
fullcmd += " " + iter;
}

if (m_Verbosity > 1)
{
std::cout << PHWHERE << " running " << fullcmd << std::endl;
}
unsigned int iret = gSystem->Exec(fullcmd.c_str());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Command injection vulnerability via unsanitized arguments.

Filenames and m_OpeningArgs are concatenated directly into a shell command without escaping. Filenames containing shell metacharacters (;, $(), backticks, |, etc.) could execute arbitrary code.

Consider:

  1. Quoting each argument (though this doesn't fully prevent injection)
  2. Using an exec-style API that passes arguments as a vector (avoiding shell interpretation)
  3. At minimum, validating that arguments don't contain dangerous characters
🛡️ Minimal mitigation: quote arguments
-  std::string fullcmd = m_RunBeforeOpeningScript + " " + m_OpeningArgs;
+  std::string fullcmd = "'" + m_RunBeforeOpeningScript + "'";
+  if (!m_OpeningArgs.empty())
+  {
+    fullcmd += " " + m_OpeningArgs;  // User-provided args - document they must be safe
+  }
   for (const auto& iter : stringvec)
   {
-    fullcmd += " " + iter;
+    // Escape single quotes within the filename and wrap in single quotes
+    std::string escaped = iter;
+    size_t pos = 0;
+    while ((pos = escaped.find('\'', pos)) != std::string::npos)
+    {
+      escaped.replace(pos, 1, "'\\''");
+      pos += 4;
+    }
+    fullcmd += " '" + escaped + "'";
   }

Note: The safest approach would be to use fork/execv or a similar API that doesn't invoke a shell, passing arguments as separate strings.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
std::string fullcmd = m_RunBeforeOpeningScript + " " + m_OpeningArgs;
for (const auto& iter : stringvec)
{
fullcmd += " " + iter;
}
if (m_Verbosity > 1)
{
std::cout << PHWHERE << " running " << fullcmd << std::endl;
}
unsigned int iret = gSystem->Exec(fullcmd.c_str());
std::string fullcmd = "'" + m_RunBeforeOpeningScript + "'";
if (!m_OpeningArgs.empty())
{
fullcmd += " " + m_OpeningArgs; // User-provided args - document they must be safe
}
for (const auto& iter : stringvec)
{
// Escape single quotes within the filename and wrap in single quotes
std::string escaped = iter;
size_t pos = 0;
while ((pos = escaped.find('\'', pos)) != std::string::npos)
{
escaped.replace(pos, 1, "'\\''");
pos += 4;
}
fullcmd += " '" + escaped + "'";
}
if (m_Verbosity > 1)
{
std::cout << PHWHERE << " running " << fullcmd << std::endl;
}
unsigned int iret = gSystem->Exec(fullcmd.c_str());
🤖 Prompt for AI Agents
In `@offline/framework/fun4all/InputFileHandler.cc` around lines 183 - 193, The
code builds a shell command by concatenating m_RunBeforeOpeningScript,
m_OpeningArgs and entries of stringvec and then calls gSystem->Exec, which
allows command injection; modify InputFileHandler to avoid shell interpretation
by replacing gSystem->Exec(fullcmd.c_str()) with a direct exec-style invocation
(fork + execv or posix_spawn) that passes m_RunBeforeOpeningScript as the
program and a vector<char*> argv built from m_RunBeforeOpeningScript,
m_OpeningArgs split safely (or omitted) and each element of stringvec as
separate arguments; if exec-style replacement is not possible, at minimum escape
or strictly validate/quote each argument (validate against a safe character
whitelist) before concatenation and logging (refer to m_RunBeforeOpeningScript,
m_OpeningArgs, stringvec, gSystem->Exec and PHWHERE to locate the affected
code).

Comment on lines 210 to 214
clusHits.clear();
for(auto hit : regions[0])
for (auto hit : regions[0])
{
clusHits.push_back(hit);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential out-of-bounds access if clusHits is empty.

If clusHits is empty when passed to this function, unvisited will be empty, the while loop will not execute, and regions will remain empty. Accessing regions[0] on line 211 would then cause undefined behavior.

Consider adding a guard:

🐛 Proposed fix
   clusHits.clear();
+  if (regions.empty())
+  {
+    return;
+  }
   for (auto hit : regions[0])
   {
     clusHits.push_back(hit);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
clusHits.clear();
for(auto hit : regions[0])
for (auto hit : regions[0])
{
clusHits.push_back(hit);
}
clusHits.clear();
if (regions.empty())
{
return;
}
for (auto hit : regions[0])
{
clusHits.push_back(hit);
}
🤖 Prompt for AI Agents
In `@offline/packages/tpc/LaserClusterizer.cc` around lines 210 - 214, The code
assumes regions[0] exists when copying into clusHits; add a guard to avoid
out-of-bounds access by checking clusHits (or regions) before using regions[0] —
e.g., if clusHits.empty() return/continue or if regions.empty() return/continue
— and ensure unvisited is populated only when clusHits has elements so the
subsequent while loop and access to regions[0] are safe; update the block around
clusHits.clear(), the loop that populates clusHits, and any callers that rely on
regions to handle the empty-case consistently.

Comment on lines +113 to +118
for (const auto& cfg : m_pending)
{
m_histograms_x.push_back(dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_x"))));
m_histograms_y.push_back(dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_y"))));
m_histograms_z.push_back(dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_z"))));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing null-check after histogram retrieval.

If getHisto returns nullptr or the dynamic_cast fails, nullptr will be stored in the histogram vectors. Later in process_event, these will be dereferenced (lines 195-197) causing a crash. Consider validating the histograms exist.

Suggested validation
  for (const auto& cfg : m_pending)
  {
-   m_histograms_x.push_back(dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_x"))));
-   m_histograms_y.push_back(dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_y"))));
-   m_histograms_z.push_back(dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_z"))));
+   auto* h_x = dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_x")));
+   auto* h_y = dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_y")));
+   auto* h_z = dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_z")));
+   if (!h_x || !h_y || !h_z)
+   {
+     std::cout << PHWHERE << "\n"
+               << "\tCould not retrieve histograms for config: " << cfg.name << "\n"
+               << "\tAborting\n" << std::endl;
+     return Fun4AllReturnCodes::ABORTRUN;
+   }
+   m_histograms_x.push_back(h_x);
+   m_histograms_y.push_back(h_y);
+   m_histograms_z.push_back(h_z);
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (const auto& cfg : m_pending)
{
m_histograms_x.push_back(dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_x"))));
m_histograms_y.push_back(dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_y"))));
m_histograms_z.push_back(dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_z"))));
}
for (const auto& cfg : m_pending)
{
auto* h_x = dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_x")));
auto* h_y = dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_y")));
auto* h_z = dynamic_cast<TH1 *>(hm->getHisto(std::string(cfg.name + "_z")));
if (!h_x || !h_y || !h_z)
{
std::cout << PHWHERE << "\n"
<< "\tCould not retrieve histograms for config: " << cfg.name << "\n"
<< "\tAborting\n" << std::endl;
return Fun4AllReturnCodes::ABORTRUN;
}
m_histograms_x.push_back(h_x);
m_histograms_y.push_back(h_y);
m_histograms_z.push_back(h_z);
}
🤖 Prompt for AI Agents
In `@offline/QA/Tracking/StateClusterResidualsQA.cc` around lines 113 - 118, The
loop that fills m_histograms_x, m_histograms_y, and m_histograms_z uses
dynamic_cast on hm->getHisto without checking for nullptr, so add validation
after retrieving each histogram pointer (from hm->getHisto and after
dynamic_cast to TH1*) and only push_back if the pointer is non-null; if any
retrieval fails, log a warning including cfg.name (or otherwise handle it) and
do not push into the vectors (or push a placeholder consistently) to keep vector
sizes aligned and avoid dereferencing nulls later in process_event (which reads
m_histograms_x/y/z).

Comment on lines +185 to +198
auto *cluster = cluster_map->findCluster(state->get_cluskey());
float state_x = state->get_x();
float state_y = state->get_y();
float state_z = state->get_z();
Acts::Vector3 glob = geometry->getGlobalPosition(state->get_cluskey(), cluster);
float cluster_x = glob.x();
float cluster_y = glob.y();
float cluster_z = glob.z();
if (cluster)
{
m_histograms_x[h]->Fill(state_x - cluster_x);
m_histograms_y[h]->Fill(state_y - cluster_y);
m_histograms_z[h]->Fill(state_z - cluster_z);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Null pointer dereference: cluster used before null check.

On line 189, cluster is passed to getGlobalPosition before validating it's non-null on line 193. If findCluster returns nullptr, this will cause undefined behavior or a crash.

Proposed fix: check cluster before use
          auto *cluster = cluster_map->findCluster(state->get_cluskey());
+         if (!cluster) { continue; }
          float state_x = state->get_x();
          float state_y = state->get_y();
          float state_z = state->get_z();
          Acts::Vector3 glob = geometry->getGlobalPosition(state->get_cluskey(), cluster);
          float cluster_x = glob.x();
          float cluster_y = glob.y();
          float cluster_z = glob.z();
-         if (cluster)
-         {
-           m_histograms_x[h]->Fill(state_x - cluster_x);
-           m_histograms_y[h]->Fill(state_y - cluster_y);
-           m_histograms_z[h]->Fill(state_z - cluster_z);
-         }
+         m_histograms_x[h]->Fill(state_x - cluster_x);
+         m_histograms_y[h]->Fill(state_y - cluster_y);
+         m_histograms_z[h]->Fill(state_z - cluster_z);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
auto *cluster = cluster_map->findCluster(state->get_cluskey());
float state_x = state->get_x();
float state_y = state->get_y();
float state_z = state->get_z();
Acts::Vector3 glob = geometry->getGlobalPosition(state->get_cluskey(), cluster);
float cluster_x = glob.x();
float cluster_y = glob.y();
float cluster_z = glob.z();
if (cluster)
{
m_histograms_x[h]->Fill(state_x - cluster_x);
m_histograms_y[h]->Fill(state_y - cluster_y);
m_histograms_z[h]->Fill(state_z - cluster_z);
}
auto *cluster = cluster_map->findCluster(state->get_cluskey());
if (!cluster) { continue; }
float state_x = state->get_x();
float state_y = state->get_y();
float state_z = state->get_z();
Acts::Vector3 glob = geometry->getGlobalPosition(state->get_cluskey(), cluster);
float cluster_x = glob.x();
float cluster_y = glob.y();
float cluster_z = glob.z();
m_histograms_x[h]->Fill(state_x - cluster_x);
m_histograms_y[h]->Fill(state_y - cluster_y);
m_histograms_z[h]->Fill(state_z - cluster_z);
🤖 Prompt for AI Agents
In `@offline/QA/Tracking/StateClusterResidualsQA.cc` around lines 185 - 198, The
code calls geometry->getGlobalPosition(state->get_cluskey(), cluster) before
verifying cluster is non-null, risking a null dereference from
cluster_map->findCluster; change the control flow so you first call auto
*cluster = cluster_map->findCluster(state->get_cluskey()) and immediately check
if (cluster) before invoking geometry->getGlobalPosition or using cluster, and
only then compute glob, cluster_x/cluster_y/cluster_z and fill
m_histograms_x[h], m_histograms_y[h], m_histograms_z[h]; leave state->get_x(),
get_y(), get_z() as needed but ensure getGlobalPosition is not called with a
null cluster.

Comment on lines +56 to +101
StateClusterResidualsQA& setNMvtx(int min, int max)
{
m_pending.back().min_mvtx_clusters = min;
m_pending.back().max_mvtx_clusters = max;
return *this;
}
StateClusterResidualsQA& setNIntt(int min, int max)
{
m_pending.back().min_intt_clusters = min;
m_pending.back().max_intt_clusters = max;
return *this;
}
StateClusterResidualsQA& setNTpc(int min, int max)
{
m_pending.back().min_tpc_clusters = min;
m_pending.back().max_tpc_clusters = max;
return *this;
}
StateClusterResidualsQA& setPhiRange(float min, float max)
{
m_pending.back().phi_min = min;
m_pending.back().phi_max = max;
return *this;
}
StateClusterResidualsQA& setEtaRange(float min, float max)
{
m_pending.back().eta_min = min;
m_pending.back().eta_max = max;
return *this;
}
StateClusterResidualsQA& setPtRange(float min, float max)
{
m_pending.back().pt_min = min;
m_pending.back().pt_max = max;
return *this;
}
StateClusterResidualsQA& setPositiveTracks()
{
m_pending.back().charge = 1;
return *this;
}
StateClusterResidualsQA& setNegativeTracks()
{
m_pending.back().charge = -1;
return *this;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Calling setters before addHistogram() will cause undefined behavior.

All setter methods access m_pending.back() without checking if the vector is non-empty. If a user calls any setter (e.g., setNMvtx()) before addHistogram(), this triggers undefined behavior on an empty vector.

Consider adding a defensive check or documenting the required call order in the header.

Example defensive check
  StateClusterResidualsQA& setNMvtx(int min, int max)
  {
+   if (m_pending.empty())
+   {
+     std::cerr << "Error: setNMvtx called before addHistogram()" << std::endl;
+     return *this;
+   }
    m_pending.back().min_mvtx_clusters = min;
    m_pending.back().max_mvtx_clusters = max;
    return *this;
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
StateClusterResidualsQA& setNMvtx(int min, int max)
{
m_pending.back().min_mvtx_clusters = min;
m_pending.back().max_mvtx_clusters = max;
return *this;
}
StateClusterResidualsQA& setNIntt(int min, int max)
{
m_pending.back().min_intt_clusters = min;
m_pending.back().max_intt_clusters = max;
return *this;
}
StateClusterResidualsQA& setNTpc(int min, int max)
{
m_pending.back().min_tpc_clusters = min;
m_pending.back().max_tpc_clusters = max;
return *this;
}
StateClusterResidualsQA& setPhiRange(float min, float max)
{
m_pending.back().phi_min = min;
m_pending.back().phi_max = max;
return *this;
}
StateClusterResidualsQA& setEtaRange(float min, float max)
{
m_pending.back().eta_min = min;
m_pending.back().eta_max = max;
return *this;
}
StateClusterResidualsQA& setPtRange(float min, float max)
{
m_pending.back().pt_min = min;
m_pending.back().pt_max = max;
return *this;
}
StateClusterResidualsQA& setPositiveTracks()
{
m_pending.back().charge = 1;
return *this;
}
StateClusterResidualsQA& setNegativeTracks()
{
m_pending.back().charge = -1;
return *this;
}
StateClusterResidualsQA& setNMvtx(int min, int max)
{
if (m_pending.empty())
{
std::cerr << "Error: setNMvtx called before addHistogram()" << std::endl;
return *this;
}
m_pending.back().min_mvtx_clusters = min;
m_pending.back().max_mvtx_clusters = max;
return *this;
}
StateClusterResidualsQA& setNIntt(int min, int max)
{
m_pending.back().min_intt_clusters = min;
m_pending.back().max_intt_clusters = max;
return *this;
}
StateClusterResidualsQA& setNTpc(int min, int max)
{
m_pending.back().min_tpc_clusters = min;
m_pending.back().max_tpc_clusters = max;
return *this;
}
StateClusterResidualsQA& setPhiRange(float min, float max)
{
m_pending.back().phi_min = min;
m_pending.back().phi_max = max;
return *this;
}
StateClusterResidualsQA& setEtaRange(float min, float max)
{
m_pending.back().eta_min = min;
m_pending.back().eta_max = max;
return *this;
}
StateClusterResidualsQA& setPtRange(float min, float max)
{
m_pending.back().pt_min = min;
m_pending.back().pt_max = max;
return *this;
}
StateClusterResidualsQA& setPositiveTracks()
{
m_pending.back().charge = 1;
return *this;
}
StateClusterResidualsQA& setNegativeTracks()
{
m_pending.back().charge = -1;
return *this;
}
🤖 Prompt for AI Agents
In `@offline/QA/Tracking/StateClusterResidualsQA.h` around lines 56 - 101, All
setter methods (setNMvtx, setNIntt, setNTpc, setPhiRange, setEtaRange,
setPtRange, setPositiveTracks, setNegativeTracks) access m_pending.back()
without ensuring m_pending is non-empty; update each setter to guard against an
empty m_pending by either creating a default pending entry (e.g., if
(m_pending.empty()) m_pending.emplace_back();) before using back(), or by
throwing/checking with a clear error message instructing the caller to call
addHistogram() first—apply the same fix to every setter so m_pending.back() is
always safe to access.

std::vector<TH1*> m_histograms_z{};
};

#endif // TRACKFITTINGQA_H
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Incorrect include guard comment.

The #endif comment references TRACKFITTINGQA_H but the actual include guard is STATECLUSTERRESIDUALSQA_H. This appears to be a copy-paste error.

Suggested fix
-#endif  // TRACKFITTINGQA_H
+#endif  // STATECLUSTERRESIDUALSQA_H
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#endif // TRACKFITTINGQA_H
`#endif` // STATECLUSTERRESIDUALSQA_H
🤖 Prompt for AI Agents
In `@offline/QA/Tracking/StateClusterResidualsQA.h` at line 127, The trailing
`#endif` comment is wrong: replace the incorrect comment "TRACKFITTINGQA_H" with
the actual include guard name "STATECLUSTERRESIDUALSQA_H" so the end of the
header matches the opening guard; update the comment after the `#endif`
accordingly in StateClusterResidualsQA.h to read `#endif`  //
STATECLUSTERRESIDUALSQA_H.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.