diff --git a/External/CheckerGccPlugins/CMakeLists.txt b/External/CheckerGccPlugins/CMakeLists.txt
index 37eccb20e194297d788f4893e7956806e2160fef..9ff789ba9cae9664abf6e69042f726d34cc26c5a 100644
--- a/External/CheckerGccPlugins/CMakeLists.txt
+++ b/External/CheckerGccPlugins/CMakeLists.txt
@@ -1,4 +1,4 @@
-# Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
+# Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
 
 # The name of the package:
 atlas_subdir( CheckerGccPlugins )
@@ -46,7 +46,11 @@ function( CheckerGccPlugins_test1 testName mode )
   cmake_parse_arguments( ARG "" "ENVIRONMENT" "" ${ARGN} )
 
   set( _fullName ${testName}_${mode} )
-  set( _checkerTestFlags ${CMAKE_CXX_FLAGS_${mode}} )
+  if( "${mode}" STREQUAL "LTO" )
+    set( _checkerTestFlags "${CMAKE_CXX_FLAGS_RELEASE} -flto " )
+  else()
+    set( _checkerTestFlags ${CMAKE_CXX_FLAGS_${mode}} )
+  endif()
   configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/test/checker_test.sh.in
                   ${CMAKE_CURRENT_BINARY_DIR}/CheckerGccPlugins_${_fullName}.sh
                   @ONLY )
@@ -62,6 +66,7 @@ endfunction( CheckerGccPlugins_test1 )
 function( CheckerGccPlugins_test testName )
 CheckerGccPlugins_test1( ${testName} RELEASE ${ARGN} )
 CheckerGccPlugins_test1( ${testName} DEBUG ${ARGN} )
+CheckerGccPlugins_test1( ${testName} LTO ${ARGN} )
 endfunction( CheckerGccPlugins_test )
 
 
@@ -111,3 +116,21 @@ CheckerGccPlugins_test( thread10 ENVIRONMENT CHECKERGCCPLUGINS_CONFIG=${CMAKE_CU
 CheckerGccPlugins_test( thread16 ENVIRONMENT CHECKERGCCPLUGINS_CONFIG=${CMAKE_CURRENT_SOURCE_DIR}/share/test_unchecked.config )
 CheckerGccPlugins_test( config ENVIRONMENT CHECKERGCCPLUGINS_CONFIG=${CMAKE_CURRENT_SOURCE_DIR}/share/test.config )
 CheckerGccPlugins_test( check_ts2 ENVIRONMENT CHECKERGCCPLUGINS_CONFIG=${CMAKE_CURRENT_SOURCE_DIR}/share/test.config )
+
+
+function( CheckerGccPlugins_ltolink_test )
+  set( testName "ltolink" )
+  set( _checkerTestFlags "${CMAKE_CXX_FLAGS_RELEASE} -flto " )
+  configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/test/checker_ltolink_test.sh.in
+                  ${CMAKE_CURRENT_BINARY_DIR}/CheckerGccPlugins_ltolink.sh
+                  @ONLY )
+  atlas_add_test( ltolink
+                  SCRIPT ${CMAKE_CURRENT_BINARY_DIR}/CheckerGccPlugins_ltolink.sh
+                  ENVIRONMENT ${ARG_ENVIRONMENT}
+                  POST_EXEC_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/test/post.sh ltolink '' ${CMAKE_CURRENT_SOURCE_DIR}/share/ltolink_test.ref"
+                   )
+endfunction( CheckerGccPlugins_ltolink_test )
+
+CheckerGccPlugins_ltolink_test()
+
+
diff --git a/External/CheckerGccPlugins/share/ltolink_test.ref b/External/CheckerGccPlugins/share/ltolink_test.ref
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/External/CheckerGccPlugins/src/callcheck_plugin.cxx b/External/CheckerGccPlugins/src/callcheck_plugin.cxx
index 3df23ca66cf57f71a58fa6283acf6977c13f9da2..dc85352f0d3b81f0db4e569e8d40b2a2b2829d03 100644
--- a/External/CheckerGccPlugins/src/callcheck_plugin.cxx
+++ b/External/CheckerGccPlugins/src/callcheck_plugin.cxx
@@ -4,7 +4,7 @@
  * @date Apr, 2019
  * @brief Various ATLAS-specific function call checks.
  *
- * Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
+ * Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
  */
 
 
@@ -36,6 +36,7 @@
 #include "stringpool.h"
 #include "attribs.h"
 #include "gcc-rich-location.h"
+#include "feshim.h"
 #include <vector>
 #include <unordered_map>
 #include <string>
@@ -343,7 +344,7 @@ unsigned int callcheck_pass::callcheck_execute (function* fun)
           if (!fndecl) {
             fndecl = vcall_fndecl (stmt);
           }
-          std::string name = decl_as_string (fndecl, TFF_SCOPE + TFF_TEMPLATE_NAME);
+          std::string name = CheckerGccPlugins::decl_as_string (fndecl, TFF_SCOPE + TFF_TEMPLATE_NAME);
           hide_targs (name);
           std::string::size_type ipos = name.find (" [with");
           if (ipos != std::string::npos) {
diff --git a/External/CheckerGccPlugins/src/check_thread_safety_p.cxx b/External/CheckerGccPlugins/src/check_thread_safety_p.cxx
index 4a739609e44680c57c3515ce60bee73dadaca43b..a952a14395a1ab29cca3cd155337a39840d3b970 100644
--- a/External/CheckerGccPlugins/src/check_thread_safety_p.cxx
+++ b/External/CheckerGccPlugins/src/check_thread_safety_p.cxx
@@ -16,7 +16,7 @@
  * If a decl has the attribute check_thread_safety_debug, then a diagnostic
  * will be printed saying if that decl has thread-safety checking enabled.
  *
- * Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
+ * Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
  */
 
 
@@ -395,7 +395,8 @@ bool check_thread_safety_location_p (location_t loc)
     return false;
   }
 
-  std::string file = LOCATION_FILE(loc);
+  const char* s = LOCATION_FILE(loc);
+  std::string file = s ? s : "";
   thread_safe_files_t::iterator it = thread_safe_files.find (file);
   if (it != thread_safe_files.end()) return it->second;
 
diff --git a/External/CheckerGccPlugins/src/checker_gccplugins.cxx b/External/CheckerGccPlugins/src/checker_gccplugins.cxx
index 30ceea8b66ebc966701c61b2375d6878e75e89ed..e217f04095f9c4b7ae5333adce5e9fa977336c2a 100644
--- a/External/CheckerGccPlugins/src/checker_gccplugins.cxx
+++ b/External/CheckerGccPlugins/src/checker_gccplugins.cxx
@@ -4,7 +4,7 @@
  * @date Aug, 2014
  * @brief Framework for running checker plugins in gcc.
  *
- * Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
+ * Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
  */
 
 
@@ -20,11 +20,13 @@
 #include "tree-core.h"
 #include "tree-pass.h"
 #include "plugin.h"
-#include "c-family/c-pragma.h"
 #include "tree.h"
 #include "cp/cp-tree.h"
 #include "diagnostic.h"
 #include "gimple.h"
+#include "stringpool.h"
+#include "attribs.h"
+#include "feshim.h"
 
 
 /* Declare GPL compatible license. */
@@ -174,7 +176,7 @@ checker_plugin_info = {
 
 
 # define ATTRIB(A) {A, 0, 0, false, false, false, false, nullptr, nullptr}
-static struct
+static const struct
 attribute_spec attribs[] =
                      
   { ATTRIB("thread_safe"),
@@ -184,8 +186,16 @@ attribute_spec attribs[] =
     ATTRIB("argument_not_const_thread_safe"),
     ATTRIB("check_thread_safety"),
     ATTRIB("check_thread_safety_debug"),
+#if GCC_VERSION < 14000
     ATTRIB(nullptr),
+#endif
+  };
+#if GCC_VERSION >= 14000
+static const scoped_attribute_specs attrib_table =
+  {
+    "ATLAS", attribs
   };
+#endif
 #undef ATTRIB
 
 
@@ -194,7 +204,12 @@ void
 register_checker_attributes (void* /*event_data*/, void* /*data*/)
 {
   //fprintf (stderr, "register_attributes %s\n", thread_safe_attr.name);
-  register_scoped_attributes (attribs, "ATLAS"
+  register_scoped_attributes (
+#if GCC_VERSION >= 14000
+                              attrib_table
+#else
+                              attribs, "ATLAS"
+#endif
 #if GCC_VERSION >= 12000
                               , false
 #endif
@@ -206,12 +221,14 @@ static
 void
 register_checker_pragmas (void* /*event_data*/, void* /*data*/)
 {
+  using CheckerGccPlugins::c_register_pragma;
   c_register_pragma ("ATLAS", "check_thread_safety",
                      CheckerGccPlugins::handle_check_thread_safety_pragma);
   c_register_pragma ("ATLAS", "no_check_thread_safety",
                      CheckerGccPlugins::handle_no_check_thread_safety_pragma);
 
-  cpp_define (parse_in, "ATLAS_GCC_CHECKERS");
+  using CheckerGccPlugins::cpp_define;
+  cpp_define ("ATLAS_GCC_CHECKERS");
 }
 
 
@@ -231,6 +248,8 @@ plugin_init(struct plugin_name_args   *plugin_info,
     return 1;
   }
 
+  if (!CheckerGccPlugins::init_shims()) return 0;
+
   // Handle command-line arguments.
   if (collect_plugin_args(plugin_info)!=0)
     return 1;
@@ -279,7 +298,13 @@ CheckerConfig config;
 
 void inform_url (location_t loc, const char* url)
 {
-  if (!in_system_header_at(loc) && !global_dc->dc_warn_system_headers)
+  if (!in_system_header_at(loc) &&
+#if GCC_VERSION < 14000
+      !global_dc->dc_warn_system_headers
+#else
+      !global_dc->m_warn_system_headers
+#endif
+      )
     inform (loc, "See %s.", url);
 }
 
@@ -343,7 +368,8 @@ tree vcall_fndecl (gimplePtr stmt)
           // collide with what we're looking for.  Just ignore such entries.
           // But careful: Can't just compare the types, because TYP
           // may be a const type.
-          TYPE_NAME (strip_typedefs (typ)) == TYPE_NAME (strip_typedefs (CP_DECL_CONTEXT (*it)))
+          TYPE_NAME (CheckerGccPlugins::strip_typedefs (typ)) ==
+          TYPE_NAME (CheckerGccPlugins::strip_typedefs (CP_DECL_CONTEXT (*it)))
           )
       {
         tree fn_ndx_tree = DECL_VINDEX (*it);
diff --git a/External/CheckerGccPlugins/src/divcheck_plugin.cxx b/External/CheckerGccPlugins/src/divcheck_plugin.cxx
index e1de8202c1993c8bb992eaefa46cf7414c064015..21a7071438ff67d372758a1c487f81a0605c6263 100644
--- a/External/CheckerGccPlugins/src/divcheck_plugin.cxx
+++ b/External/CheckerGccPlugins/src/divcheck_plugin.cxx
@@ -4,7 +4,7 @@
  * @date May, 2015
  * @brief Check for redundant divisions.
  *
- * Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
+ * Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
  */
 
 
@@ -53,11 +53,7 @@ const pass_data divcheck_pass_data =
 {
   GIMPLE_PASS, /* type */
   "divcheck", /* name */
-#if GCC_VERSION < 9000
-  0, /* optinfo_flags */
-#else
   OPTGROUP_NONE,  /* optinfo_flags */
-#endif
   TV_NONE, /* tv_id */
   0, /* properties_required */
   0, /* properties_provided */
diff --git a/External/CheckerGccPlugins/src/feshim.cxx b/External/CheckerGccPlugins/src/feshim.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..1031de1b3ed498c0c64b4dfa006a742c44ede7c4
--- /dev/null
+++ b/External/CheckerGccPlugins/src/feshim.cxx
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration.
+ */
+/**
+ * @file CheckerGccPlugins/src/feshim.cxx
+ * @author scott snyder <snyder@bnl.gov>
+ * @date Apr, 2024
+ * @brief Shims for accessing front-end symbols.
+ */
+
+#define FESHIM_CXX
+#define scope_chain dum_scope_chain
+#include "checker_gccplugins.h"
+#include "tree.h"
+#include "cp/cp-tree.h"
+#include "feshim.h"
+#include <dlfcn.h>
+#undef scope_chain
+
+// Shim variables.
+tree cp_global_trees[CPTI_MAX];
+cpp_reader* parse_in = nullptr;
+
+
+namespace CheckerGccPlugins
+{
+
+
+//********************************************************************
+// The shim functions.
+// These will bounce the calls to the real_* pointers which
+// we retrieved in init_shims().
+
+
+typedef void (*c_register_pragma_t) (const char* space,
+                                     const char* type,
+                                     pragma_handler_1arg handler);
+c_register_pragma_t real_c_register_pragma = nullptr;
+
+
+void c_register_pragma (const char *space, const char *name,
+                        pragma_handler_1arg handler)
+{
+  (*real_c_register_pragma) (space, name, handler);
+}
+
+
+//********
+
+
+typedef void (*cpp_define_t) (cpp_reader *, const char *);
+cpp_define_t real_cpp_define = nullptr;
+void cpp_define (const char* def)
+{
+  (*real_cpp_define) (parse_in, def);
+}
+
+
+//********
+
+
+typedef tree (*strip_typedefs_t) (tree t, bool* remove_attributes, unsigned int flags);
+strip_typedefs_t real_strip_typedefs = nullptr;
+tree strip_typedefs (tree t, bool* remove_attributes, unsigned int flags)
+{
+  return (*real_strip_typedefs) (t, remove_attributes, flags);
+}
+
+
+//********
+
+
+typedef tree (*lookup_base_t) (tree t, tree base, /*base_access*/int access,
+                               /*base_kind*/int *kind_ptr,
+                               /*tsubst_flags_t*/int complain
+#if GCC_VERSION >= 14000
+                               , HOST_WIDE_INT offset
+#endif
+                               );
+lookup_base_t real_lookup_base = nullptr;
+tree lookup_base (tree t, tree base)
+{
+  return (*real_lookup_base) (t, base,
+                              0, // ba_any
+                              NULL,
+                              0 // tf_none
+#if GCC_VERSION >= 14000
+                              , -1
+#endif
+                              );
+}
+
+
+//********
+
+
+typedef const char* (*type_as_string_t) (tree t, int flags);
+type_as_string_t real_type_as_string = nullptr;
+const char* type_as_string (tree t, int flags)
+{
+  return (*real_type_as_string) (t, flags);
+}
+
+
+//********
+
+
+typedef const char* (*decl_as_string_t) (tree t, int flags);
+decl_as_string_t real_decl_as_string = nullptr;
+const char* decl_as_string (tree t, int flags)
+{
+  return (*real_decl_as_string) (t, flags);
+}
+
+
+//********
+
+
+typedef tree (*skip_artificial_parms_for_t) (const_tree fn, tree list);
+skip_artificial_parms_for_t real_skip_artificial_parms_for = nullptr;
+tree skip_artificial_parms_for (const_tree fn, tree list)
+{
+  return (*real_skip_artificial_parms_for) (fn, list);
+}
+
+
+//********
+
+
+typedef tree (*dfs_walk_all_t) (tree binfo, tree (*pre_fn) (tree, void *),
+                                tree (*post_fn) (tree, void *), void *data);
+dfs_walk_all_t real_dfs_walk_all = nullptr;
+tree
+dfs_walk_all (tree binfo, tree (*pre_fn) (tree, void *),
+              tree (*post_fn) (tree, void *), void *data)
+{
+  return (*real_dfs_walk_all) (binfo, pre_fn, post_fn, data);
+}
+
+
+//********
+
+
+typedef int (*cp_type_quals_t) (const_tree t);
+cp_type_quals_t real_cp_type_quals = nullptr;
+int cp_type_quals (const_tree t)
+{
+  return (*real_cp_type_quals) (t);
+}
+
+
+//********
+
+
+typedef tree (*look_for_overrides_here_t) (tree type, tree fndecl);
+look_for_overrides_here_t real_look_for_overrides_here = nullptr;
+tree look_for_overrides_here (tree type, tree fndecl)
+{
+  return (*real_look_for_overrides_here) (type, fndecl);
+}
+
+
+//********************************************************************
+// Shim for getting the current namespace.
+
+
+saved_scope** scope_chain_ptr = nullptr;
+tree get_current_namespace()
+{
+  return (*scope_chain_ptr)->old_namespace;
+}
+
+
+//********************************************************************
+
+
+bool init_shims()
+{
+  // Handle referencing the main program.
+  void* handle = dlopen (NULL, 0);
+
+  // Check to see if any FE symbols are present.
+  // If not, exit gracefully and disable the checkers.
+  if (!dlsym (handle, "scope_chain")) {
+    return false;
+  }
+
+  // Helper for looking up a symbol.
+  // Abort if we don't find the symbol.
+  auto findsym = [&] (auto& fn, const char* sym)
+  {
+    void* p = dlsym (handle, sym);
+    if (!p) {
+      fatal_error (input_location, "CheckerGccPlugins: cannot find symbol %<%s%>", sym);
+    }
+    fn = (std::remove_reference_t<decltype(fn)>)p;
+  };
+
+  // Look up this symbol and copy it to our local array.
+  tree* trees;
+  findsym (trees, "cp_global_trees");
+  memcpy (cp_global_trees, trees, sizeof (cp_global_trees));
+
+  // Variable pointers.
+  
+  findsym (scope_chain_ptr, "scope_chain");
+
+  cpp_reader** parse_in_ptr = nullptr;
+  findsym (parse_in_ptr, "parse_in");
+  parse_in = *parse_in_ptr;
+
+
+  // Function pointers.
+
+  findsym (real_c_register_pragma, "_Z17c_register_pragmaPKcS0_PFvP10cpp_readerE");
+  findsym (real_cpp_define, "_Z10cpp_defineP10cpp_readerPKc");
+  findsym (real_strip_typedefs, "_Z14strip_typedefsP9tree_nodePbj");
+  findsym (real_lookup_base,
+#if GCC_VERSION >= 14000
+                                           "_Z11lookup_baseP9tree_nodeS0_iP9base_kindil"
+#else
+                                           "_Z11lookup_baseP9tree_nodeS0_iP9base_kindi"
+#endif
+            );
+  findsym (real_type_as_string, "_Z14type_as_stringP9tree_nodei");
+  findsym (real_decl_as_string, "_Z14decl_as_stringP9tree_nodei");
+  findsym (real_skip_artificial_parms_for, "_Z25skip_artificial_parms_forPK9tree_nodePS_");
+  findsym (real_dfs_walk_all, "_Z12dfs_walk_allP9tree_nodePFS0_S0_PvES3_S1_");
+  findsym (real_cp_type_quals, "_Z13cp_type_qualsPK9tree_node");
+  findsym (real_look_for_overrides_here, "_Z23look_for_overrides_hereP9tree_nodeS0_");
+
+  return true;
+}
+
+
+} // namespace CheckerGccPlugins
+
diff --git a/External/CheckerGccPlugins/src/feshim.h b/External/CheckerGccPlugins/src/feshim.h
new file mode 100644
index 0000000000000000000000000000000000000000..c125c3c1600c3337955e34522e0563048912db07
--- /dev/null
+++ b/External/CheckerGccPlugins/src/feshim.h
@@ -0,0 +1,95 @@
+// This file's extension implies that it's C, but it's really -*- C++ -*-.
+/*
+ * Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration.
+ */
+/**
+ * @file CheckerGccPlugins/src/feshim.h
+ * @author scott snyder <snyder@bnl.gov>
+ * @date Apr, 2024
+ * @brief Shims for accessing front-end symbols.
+ *
+ * In the cmake builds, the -fplugin switch will be given for both
+ * the compile and link steps.  Normally, when that switch is given
+ * during linking, it will not have any effect.  But if link-time
+ * optimization (LTO) is being done, then the compiler will run at link
+ * time and the plugin gets loaded.  The problem then is that it's not
+ * the full compiler that runs, but only the middle/backend.  If the
+ * plugin references symbols from the frontend, then it will fail
+ * to load during the link step, causing the link to fail.
+ * (gcc will load the plugin with RTLD_NOW.)
+ *
+ * To resolve this, we launder references to FE symbols through
+ * the shims defined here, which access the symbols via dlsym().
+ * If the first symbol is missing then we return gracefully and
+ * disable the checkers.
+ */
+
+
+#ifndef CHECKERGCCPLUGINS_FESHIM_H
+#define CHECKERGCCPLUGINS_FESHIM_H
+
+
+#include "tree-core.h"
+
+
+struct cpp_reader;
+
+
+namespace CheckerGccPlugins
+{
+
+
+//
+// Initialize shims for accessing symbols from the front-end.
+// Returns true on success, false if frontend symbols are not available.
+//
+bool init_shims();
+
+
+// The shim functions.
+
+
+typedef void (*pragma_handler_1arg)(struct cpp_reader *);
+void c_register_pragma (const char *space, const char *name,
+                        pragma_handler_1arg handler);
+
+void cpp_define (const char* def);
+
+
+tree strip_typedefs (tree t,
+                     bool* remove_attributes = NULL,
+                     unsigned int flags = 0);
+
+tree lookup_base (tree t, tree base);
+
+const char* type_as_string (tree t, int flags);
+
+const char* decl_as_string (tree t, int flags);
+
+tree skip_artificial_parms_for (const_tree fn, tree list);
+
+tree
+dfs_walk_all (tree binfo, tree (*pre_fn) (tree, void *),
+              tree (*post_fn) (tree, void *), void *data);
+
+
+int cp_type_quals (const_tree t);
+
+
+tree look_for_overrides_here (tree type, tree fndecl);
+
+
+tree get_current_namespace();
+
+
+} // namespace CheckerGccPlugins
+
+
+// Define this here so that the macros in cp-tree.h that use this function
+// will pick up the shim instead.
+#ifndef FESHIM_CXX
+#define cp_type_quals CheckerGccPlugins::cp_type_quals
+#endif
+
+
+#endif // not CHECKERGCCPLUGINS_FESHIM_H
diff --git a/External/CheckerGccPlugins/src/gaudi_inheritance_plugin.cxx b/External/CheckerGccPlugins/src/gaudi_inheritance_plugin.cxx
index ee0ea09cbacc032c41f2f9e569d5a19207637631..ae482f6e34f039dccfc208c22b5d0bb710a6f769 100644
--- a/External/CheckerGccPlugins/src/gaudi_inheritance_plugin.cxx
+++ b/External/CheckerGccPlugins/src/gaudi_inheritance_plugin.cxx
@@ -11,7 +11,7 @@
  * The AthenaBaseComps classes themselves (AthAlgorithm, AthAlgTool,
  * AthService) are excluded from the check.
  *
- * Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
+ * Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
  */
 
 
@@ -21,6 +21,7 @@
 #include "cp/cp-tree.h"
 #include "diagnostic.h"
 #include "print-tree.h"
+#include "feshim.h"
 
 
 namespace {
@@ -33,10 +34,10 @@ const char* type_name (tree t)
 {
   unsigned int save = flag_sanitize;
   flag_sanitize = 0;
-  const char* ret = type_as_string (t,
-                                    TFF_PLAIN_IDENTIFIER +
-                                    //TFF_UNQUALIFIED_NAME +
-                                    TFF_NO_OMIT_DEFAULT_TEMPLATE_ARGUMENTS);
+  const char* ret = CheckerGccPlugins::type_as_string (t,
+                                                       TFF_PLAIN_IDENTIFIER +
+                                                       //TFF_UNQUALIFIED_NAME +
+                                                       TFF_NO_OMIT_DEFAULT_TEMPLATE_ARGUMENTS);
   flag_sanitize = save;
   return ret;
 }
diff --git a/External/CheckerGccPlugins/src/naming_plugin.cxx b/External/CheckerGccPlugins/src/naming_plugin.cxx
index e71c1a301ed902aadd51ce927595c5246a8f70a9..f5442a8dcae9a9a172374d71ca8800b46adc394b 100644
--- a/External/CheckerGccPlugins/src/naming_plugin.cxx
+++ b/External/CheckerGccPlugins/src/naming_plugin.cxx
@@ -4,7 +4,7 @@
  * @date Aug, 2015
  * @brief Check ATLAS naming conventions.
  *
- * Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
+ * Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
  */
 
 
@@ -33,6 +33,7 @@
 #include "tree-phinodes.h"
 #include "gimple-ssa.h"
 #include "ssa-iterators.h"
+#include "feshim.h"
 
 
 #if GCC_VERSION < 12000
@@ -115,8 +116,7 @@ bool ignore_decl_p1 (tree decl)
 
     // Ignore classes deriving from SG::IAuxStore.
     if (TREE_CODE (type) == RECORD_TYPE && iauxstore_type) {
-      tree base = lookup_base (type, iauxstore_type,
-                               ba_any, NULL, tf_none);
+      tree base = CheckerGccPlugins::lookup_base (type, iauxstore_type);
       if (base != NULL_TREE && base != error_mark_node)
         return true;
     }
@@ -364,10 +364,11 @@ void naming_finishdecl_callback (void* gcc_data, void* /*user_data*/)
 
         // Gaudi plugin service v2
         if (strncmp (name, "_register_", 10) == 0 && !TREE_PUBLIC (decl)) {
-          const char* name = type_as_string (TREE_TYPE (decl),
-                                             TFF_PLAIN_IDENTIFIER +
-                                             TFF_SCOPE +
-                                             TFF_CHASE_TYPEDEF);
+          const char* name = CheckerGccPlugins::type_as_string
+            (TREE_TYPE (decl),
+             TFF_PLAIN_IDENTIFIER +
+             TFF_SCOPE +
+             TFF_CHASE_TYPEDEF);
           if (strncmp (name, "Gaudi::PluginService::", 22) == 0) {
             return;
           }
diff --git a/External/CheckerGccPlugins/src/thread_plugin.cxx b/External/CheckerGccPlugins/src/thread_plugin.cxx
index 8435bb9374d239ee9be8985f961f1aaf5f4c60ab..e470fb383f733a757b459c877b618a6a27a89d17 100644
--- a/External/CheckerGccPlugins/src/thread_plugin.cxx
+++ b/External/CheckerGccPlugins/src/thread_plugin.cxx
@@ -4,7 +4,7 @@
  * @date Sep, 2015
  * @brief Check for possible thread-safety violations.
  *
- * Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
+ * Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
  */
 
 // FIXME: can i unify pointer_is_const_arg / expr_from_arg_p / expr_from_static_p / value_from_struct?
@@ -41,6 +41,7 @@
 #include <unordered_map>
 #include "stringpool.h"
 #include "attribs.h"
+#include "feshim.h"
 
 
 using namespace CheckerGccPlugins;
@@ -151,11 +152,7 @@ const pass_data thread_pass_data =
 {
   GIMPLE_PASS, /* type */
   "thread", /* name */
-#if GCC_VERSION < 9000
-  0, /* optinfo_flags */
-#else
   OPTGROUP_NONE,  /* optinfo_flags */
-#endif
   TV_NONE, /* tv_id */
   0, /* properties_required */
   0, /* properties_provided */
@@ -194,10 +191,10 @@ const char* type_name (tree t)
 {
   unsigned int save = flag_sanitize;
   flag_sanitize = 0;
-  const char* ret = type_as_string (t,
-                                    TFF_PLAIN_IDENTIFIER +
-                                    //TFF_UNQUALIFIED_NAME +
-                                    TFF_NO_OMIT_DEFAULT_TEMPLATE_ARGUMENTS);
+  const char* ret = CheckerGccPlugins::type_as_string (t,
+                                                       TFF_PLAIN_IDENTIFIER +
+                                                       //TFF_UNQUALIFIED_NAME +
+                                                       TFF_NO_OMIT_DEFAULT_TEMPLATE_ARGUMENTS);
   flag_sanitize = save;
   return ret;
 }
@@ -323,10 +320,10 @@ bool is_in_std (tree decl_or_type)
 // Is TYPE a mutex type?
 bool is_mutex (tree type)
 {
-  const char* name = type_as_string (type,
-                                     TFF_PLAIN_IDENTIFIER +
-                                     TFF_SCOPE +
-                                     TFF_CHASE_TYPEDEF);
+  const char* name = CheckerGccPlugins::type_as_string (type,
+                                                        TFF_PLAIN_IDENTIFIER +
+                                                        TFF_SCOPE +
+                                                        TFF_CHASE_TYPEDEF);
   if (strcmp (name, "std::mutex") == 0) return true;
   if (strcmp (name, "std::shared_mutex") == 0) return true;
   if (strcmp (name, "std::shared_timed_mutex") == 0) return true;
@@ -353,10 +350,10 @@ bool is_mutex_maybearr (tree type)
 bool is_atomic (tree type)
 {
   if (TREE_CODE (type) != RECORD_TYPE) return false;
-  const char* name = type_as_string (type,
-                                     TFF_PLAIN_IDENTIFIER +
-                                     TFF_SCOPE +
-                                     TFF_CHASE_TYPEDEF);
+  const char* name = CheckerGccPlugins::type_as_string (type,
+                                                        TFF_PLAIN_IDENTIFIER +
+                                                        TFF_SCOPE +
+                                                        TFF_CHASE_TYPEDEF);
   if (strncmp (name, "const ", 6) == 0)
     name += 6;
 
@@ -404,7 +401,9 @@ bool is_atomic_maybearr (tree type)
 // True if the first argument of FNDECL is a TBuffer pointer or reference..
 bool has_tbuffer_arg (tree fndecl)
 {
-  tree args = FUNCTION_FIRST_USER_PARMTYPE (fndecl);
+  //tree args = FUNCTION_FIRST_USER_PARMTYPE (fndecl);
+  tree args = CheckerGccPlugins::skip_artificial_parms_for
+    (fndecl, TYPE_ARG_TYPES (TREE_TYPE (fndecl)));
   if (!args) return false;
   tree arg = TREE_VALUE (args);
   if (!arg) return false;
@@ -423,10 +422,10 @@ bool has_tbuffer_arg (tree fndecl)
 // Is TYPE a thread-local type?
 bool is_thread_local (tree type)
 {
-  const char* name = type_as_string (type,
-                                     TFF_PLAIN_IDENTIFIER +
-                                     TFF_SCOPE +
-                                     TFF_CHASE_TYPEDEF);
+  const char* name = CheckerGccPlugins::type_as_string (type,
+                                                        TFF_PLAIN_IDENTIFIER +
+                                                        TFF_SCOPE +
+                                                        TFF_CHASE_TYPEDEF);
   if (strncmp (name, "boost::thread_specific_ptr<", 27) == 0) return true;
   return false;
 }
@@ -440,6 +439,10 @@ bool static_p (tree expr)
     return false;
   }
 
+#if GCC_VERSION >= 14000
+  if (TREE_CODE (expr) == CONSTRUCTOR) return false;
+#endif
+
   if (DECL_P (expr)) {
     if (TREE_CODE (expr) == FUNCTION_DECL) return false;
     if (DECL_THREAD_LOCAL_P (expr)) return false;
@@ -464,7 +467,12 @@ bool static_p (tree expr)
 
 bool const_p (tree expr)
 {
+#if GCC_VERSION < 14000
+  // Maybe this was never needed?
+  // But with gcc14, this can be set for non-const static variables
+  // with no state.
   if (TREE_CONSTANT (expr)) return true;
+#endif
   if (TREE_READONLY (expr)) return true;
   tree typ = TREE_TYPE (expr);
   if (typ && TREE_CODE (typ) == REFERENCE_TYPE) {
@@ -518,7 +526,7 @@ bool is_service (tree typ)
   std::string bname;
   tree binfo = TYPE_BINFO (typ);
   if (!binfo) return false;
-  if (dfs_walk_all (binfo, service_test, nullptr, &bname) != nullptr) {
+  if (CheckerGccPlugins::dfs_walk_all (binfo, service_test, nullptr, &bname) != nullptr) {
     if (bname == "IInterface") {
       std::string typ_name = type_name (typ);
       return endswith (typ_name.c_str(), "Svc");
@@ -1534,7 +1542,11 @@ void check_nonconst_call_from_const_member (tree fndecl,
                                             function* fun)
 {
   if (!DECL_CONST_MEMFUNC_P (fun->decl)) return;
+#if GCC_VERSION >= 14000
+  if (!DECL_OBJECT_MEMBER_FUNCTION_P (fndecl) || DECL_CONST_MEMFUNC_P (fndecl)) return;
+#else
   if (!DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl) || DECL_CONST_MEMFUNC_P (fndecl)) return;
+#endif
   if (gimple_call_num_args (stmt) < 1) return;
   tree arg0 = gimple_call_arg (stmt, 0);
   if (TREE_CODE (arg0) != SSA_NAME) return;
@@ -1727,7 +1739,11 @@ void check_nonconst_pointer_member_passing (tree fndecl,
   // If we're calling a non-static member function, don't check for the
   // first (this) argument.  That will be checked separately.
   if (!fndecl) return;
+#if GCC_VERSION >= 14000
+  if (DECL_OBJECT_MEMBER_FUNCTION_P (fndecl) && i == 0) return;
+#else
   if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl) && i == 0) return;
+#endif
 
   // Don't warn about thread-safe libc functions.
   if (is_cstdlib (fndecl).second) return;
@@ -2009,7 +2025,7 @@ void find_overridden_functions_r (tree type,
                                   tree fndecl,
                                   std::vector<tree>& basedecls)
 {
-  tree fn = look_for_overrides_here (type, fndecl);
+  tree fn = CheckerGccPlugins::look_for_overrides_here (type, fndecl);
   if (fn) {
     basedecls.push_back (fn);
     return;
@@ -2121,22 +2137,95 @@ void thread_finishdecl_callback (void* gcc_data, void* /*user_data*/)
 }
 
 
+//******************************************************************************
+// Dummy do-nothing pass.  Used to replace free_lang_data in LTO builds.
+// See below.
+//
+
+const pass_data dummy_pass_data =
+{
+  SIMPLE_IPA_PASS, /* type */
+  "*dummy", /* name */
+  OPTGROUP_NONE,  /* optinfo_flags */
+  TV_NONE, /* tv_id */
+  0, /* properties_required */
+  0, /* properties_provided */
+  0, /* properties_destroyed */
+  0, /* todo_flags_start */
+  0  /* todo_flags_finish */
+};
+
+
+class dummy_pass : public simple_ipa_opt_pass
+{
+public:
+  dummy_pass (gcc::context* ctxt)
+    : simple_ipa_opt_pass (dummy_pass_data, ctxt)
+  { 
+  }
+
+  virtual unsigned int execute (function* ) override
+  { return 0; }
+
+  virtual opt_pass* clone() override { return new dummy_pass(*this); }
+};
+
+
 } // anonymous namespace
 
 
 void init_thread_checker (plugin_name_args* plugin_info)
 {
-  struct register_pass_info pass_info = {
+  struct register_pass_info thread_pass_info = {
     new thread_pass(g),
     "ssa",
     0,
     PASS_POS_INSERT_AFTER
   };
-  
+
   register_callback (plugin_info->base_name,
                      PLUGIN_PASS_MANAGER_SETUP,
                      NULL,
-                     &pass_info);
+                     &thread_pass_info);
+
+  // The free_lang_data pass runs just after gimplification
+  // (and before the conversion to SSA).  Normally, it doesn't do much,
+  // but if LTO is enabled, then it will free the C++-specific parts
+  // from the types and declarations.  However, the thread checker
+  // relies on this information.  Further, dataflow analysis requires that
+  // the SSA conversion has taken place, so we can't just shift it
+  // to before free_lang_data.  But it doesn't really matter that much
+  // exactly where free_lang_data runs, so we instead move it to slightly
+  // later, after the checker runs.
+  //
+  // The plugin API doesn't seem to provide a way to reorder existing passes,
+  // so we do this by replacing free_lang_data with a dummy and then adding
+  // a new instance of it further on.
+  if (flag_lto) {
+    struct register_pass_info dummy_pass_info = {
+      new dummy_pass(g),
+      "*free_lang_data",
+      0,
+      PASS_POS_REPLACE
+    };
+
+    struct register_pass_info freelang_pass_info = {
+      make_pass_ipa_free_lang_data(g),
+      "build_ssa_passes",
+      1,
+      PASS_POS_INSERT_AFTER
+    };
+
+    register_callback (plugin_info->base_name,
+                       PLUGIN_PASS_MANAGER_SETUP,
+                       NULL,
+                       &dummy_pass_info);
+
+    register_callback (plugin_info->base_name,
+                       PLUGIN_PASS_MANAGER_SETUP,
+                       NULL,
+                       &freelang_pass_info);
+  }
 
   register_callback (plugin_info->base_name,
                      PLUGIN_FINISH_TYPE,
diff --git a/External/CheckerGccPlugins/src/usingns_plugin.cxx b/External/CheckerGccPlugins/src/usingns_plugin.cxx
index 09f8514f09ba2ff086311a7a25355b2214b64bba..3d03bdef659d3af5fa3df77c542e12790c09041c 100644
--- a/External/CheckerGccPlugins/src/usingns_plugin.cxx
+++ b/External/CheckerGccPlugins/src/usingns_plugin.cxx
@@ -1,5 +1,5 @@
 /**
- * Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
+ * Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
  *
  * @file CheckerGccPlugins/src/usingns_plugin.cxx
  * @author scott snyder <snyder@bnl.gov>
@@ -45,6 +45,7 @@
 #include "tree-phinodes.h"
 #include "gimple-ssa.h"
 #include "ssa-iterators.h"
+#include "feshim.h"
 
 
 #if GCC_VERSION < 12000
@@ -84,7 +85,7 @@ std::vector<location_t> global_using_decls;
 void handle_using_decl (tree /*decl*/)
 {
   // Only warn about declarations in the global namespace.
-  if (current_namespace == global_namespace)
+  if (CheckerGccPlugins::get_current_namespace() == global_namespace)
   {
     // Give a warning now if this decl was from a header file.
     // nb. DECL_SOURCE_LOCATION(decl) is not reliable.
@@ -140,7 +141,7 @@ std::vector<location_t> global_using_stmts;
 void handle_using_stmt (tree stmt)
 {
   // Only warn if we're in the global namespace.
-  if (current_namespace != global_namespace) return;
+  if (CheckerGccPlugins::get_current_namespace() != global_namespace) return;
   if (current_function_decl) return;
 
   // Give a warning now if we're in a header file.
diff --git a/External/CheckerGccPlugins/test/checker_ltolink_test.sh.in b/External/CheckerGccPlugins/test/checker_ltolink_test.sh.in
new file mode 100755
index 0000000000000000000000000000000000000000..fcc6d7b659ef10179c384c03ba79d15fe0dfdbc6
--- /dev/null
+++ b/External/CheckerGccPlugins/test/checker_ltolink_test.sh.in
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
+#
+
+@CMAKE_CXX_COMPILER@ -c -o @testName@_test.o $EXTRA_FLAGS --std=c++20 @_checkerTestFlags@ -fplugin=@CMAKE_LIBRARY_OUTPUT_DIRECTORY@/libchecker_gccplugins.so -fplugin-arg-libchecker_gccplugins-checkers=all @CMAKE_CURRENT_SOURCE_DIR@/test/@testName@_test.cxx
+@CMAKE_CXX_COMPILER@  $EXTRA_FLAGS --std=c++20 @_checkerTestFlags@ -fplugin=@CMAKE_LIBRARY_OUTPUT_DIRECTORY@/libchecker_gccplugins.so -fplugin-arg-libchecker_gccplugins-checkers=all @testName@_test.o -o @testName@_test.exe
+
diff --git a/External/CheckerGccPlugins/test/checker_test.sh.in b/External/CheckerGccPlugins/test/checker_test.sh.in
index 55b6d5f4ea62c559ccb7ef5a38ab06f28ffa2305..db0cd8954cb08f584b435ab3a433ec45cef88a6a 100755
--- a/External/CheckerGccPlugins/test/checker_test.sh.in
+++ b/External/CheckerGccPlugins/test/checker_test.sh.in
@@ -1,6 +1,6 @@
 #!/bin/sh
 #
-# Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+# Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
 #
 
 # Diagnostic format changed in gcc9, but can be changed back to the old format
@@ -12,5 +12,5 @@ if [ $MAJOR_VERSION -ge 9 ]; then
     EXTRA_FLAGS="-fno-diagnostics-show-line-numbers"
 fi
 
-@CMAKE_CXX_COMPILER@ -c -o /dev/null $EXTRA_FLAGS -fdiagnostics-generate-patch --std=c++17 @_checkerTestFlags@ -fplugin=@CMAKE_LIBRARY_OUTPUT_DIRECTORY@/libchecker_gccplugins.so -fplugin-arg-libchecker_gccplugins-checkers=all @CMAKE_CURRENT_SOURCE_DIR@/test/@testName@_test.cxx
+@CMAKE_CXX_COMPILER@ -c -o /dev/null $EXTRA_FLAGS -fdiagnostics-generate-patch --std=c++20 @_checkerTestFlags@ -fplugin=@CMAKE_LIBRARY_OUTPUT_DIRECTORY@/libchecker_gccplugins.so -fplugin-arg-libchecker_gccplugins-checkers=all @CMAKE_CURRENT_SOURCE_DIR@/test/@testName@_test.cxx
 
diff --git a/External/CheckerGccPlugins/test/ltolink_test.cxx b/External/CheckerGccPlugins/test/ltolink_test.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..f8b643afbf2c84dc03b777743d3e53a22045cf49
--- /dev/null
+++ b/External/CheckerGccPlugins/test/ltolink_test.cxx
@@ -0,0 +1,4 @@
+int main()
+{
+  return 0;
+}
diff --git a/External/CheckerGccPlugins/test/thread14_test.cxx b/External/CheckerGccPlugins/test/thread14_test.cxx
index c141359e3fedba6c4217527a983dcf43c753787b..2c29fccb17cd0f76b8f7e7384a49d97f96c4bbf1 100644
--- a/External/CheckerGccPlugins/test/thread14_test.cxx
+++ b/External/CheckerGccPlugins/test/thread14_test.cxx
@@ -1,4 +1,4 @@
-// Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration
+// Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
 //
 // thread14_test: testing check_direct_static_use/check_assign_address_of_static
 //                with a const static object
@@ -59,4 +59,10 @@ const int* f12()
   return &s2.x;
 }
 
+void f20()
+{
+  [[maybe_unused]] int a[256] = {0};
+}
+
+