From 7af51501d5a70e6cdff45d7c1c804f0e820d1d3a Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Tue, 04 Aug 2026 11:53:39 +0000
Subject: [PATCH] Fix CodeQL note-severity alerts: Thread.run() calls and getters leaking internal state (#847)

---
 opendj-cli/src/main/java/com/forgerock/opendj/cli/Argument.java                                    |    4 +
 opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/StatsThread.java                 |   11 ++-
 opendj-server-legacy/src/main/java/org/opends/quicksetup/installer/SuffixesToReplicateOptions.java |   22 ++++---
 opendj-server-legacy/src/main/java/org/opends/server/tools/LDAPConnectionOptions.java              |    4 +
 opendj-cli/src/main/java/com/forgerock/opendj/cli/FileBasedArgument.java                           |    4 +
 opendj-server-legacy/src/main/java/org/opends/server/admin/client/cli/SecureConnectionCliArgs.java |    7 +
 opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandArgumentParser.java                    |    2 
 opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/datamodel/Category.java        |   20 +++++-
 opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/ui/MainActionsPane.java        |    3 
 opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationDomain.java    |   34 ++++++++---
 opendj-cli/src/main/java/com/forgerock/opendj/cli/ArgumentParser.java                              |   12 +++
 11 files changed, 91 insertions(+), 32 deletions(-)

diff --git a/opendj-cli/src/main/java/com/forgerock/opendj/cli/Argument.java b/opendj-cli/src/main/java/com/forgerock/opendj/cli/Argument.java
index db3da96..3cbf468 100644
--- a/opendj-cli/src/main/java/com/forgerock/opendj/cli/Argument.java
+++ b/opendj-cli/src/main/java/com/forgerock/opendj/cli/Argument.java
@@ -13,6 +13,7 @@
  *
  * Copyright 2006-2008 Sun Microsystems, Inc.
  * Portions copyright 2014-2016 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
  */
 package com.forgerock.opendj.cli;
 
@@ -374,6 +375,9 @@
 
     /**
      * Retrieves the set of string values for this argument.
+     * <p>
+     * The returned list is the live one: {@code ListBackends} removes the backend identifiers it
+     * could not resolve from it, and {@code InstallDS} adds the default base DN to it.
      *
      * @return The set of string values for this argument.
      */
diff --git a/opendj-cli/src/main/java/com/forgerock/opendj/cli/ArgumentParser.java b/opendj-cli/src/main/java/com/forgerock/opendj/cli/ArgumentParser.java
index b80f5d4..548cb3c 100644
--- a/opendj-cli/src/main/java/com/forgerock/opendj/cli/ArgumentParser.java
+++ b/opendj-cli/src/main/java/com/forgerock/opendj/cli/ArgumentParser.java
@@ -582,10 +582,20 @@
      * Retrieves the set of unnamed trailing arguments that were provided on the
      * command line.
      *
-     * @return The set of unnamed trailing arguments that were provided on the
+     * @return A copy of the set of unnamed trailing arguments that were provided on the
      *         command line.
      */
     public ArrayList<String> getTrailingArguments() {
+        return new ArrayList<>(trailingArguments);
+    }
+
+    /**
+     * Returns the live list of unnamed trailing arguments, so that a sub-class parsing the
+     * command line can fill it in. Unlike {@link #getTrailingArguments()}, this does not copy.
+     *
+     * @return The list of unnamed trailing arguments held by this parser.
+     */
+    List<String> trailingArguments() {
         return trailingArguments;
     }
 
diff --git a/opendj-cli/src/main/java/com/forgerock/opendj/cli/FileBasedArgument.java b/opendj-cli/src/main/java/com/forgerock/opendj/cli/FileBasedArgument.java
index 7a8d962..924f811 100644
--- a/opendj-cli/src/main/java/com/forgerock/opendj/cli/FileBasedArgument.java
+++ b/opendj-cli/src/main/java/com/forgerock/opendj/cli/FileBasedArgument.java
@@ -13,6 +13,7 @@
  *
  * Copyright 2006-2008 Sun Microsystems, Inc.
  * Portions copyright 2014-2016 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
  */
 package com.forgerock.opendj.cli;
 
@@ -105,6 +106,9 @@
     /**
      * Retrieves a map between the filenames specified on the command line and
      * the first lines read from those files.
+     * <p>
+     * The returned map is the live one: the tools building an equivalent command line add
+     * entries to it, so that a password read interactively is displayed as a password file.
      *
      * @return A map between the filenames specified on the command line and the
      *         first lines read from those files.
diff --git a/opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandArgumentParser.java b/opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandArgumentParser.java
index 495b03b..dbd5f2c 100644
--- a/opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandArgumentParser.java
+++ b/opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandArgumentParser.java
@@ -318,7 +318,7 @@
     @Override
     public void parseArguments(String[] rawArguments, Properties argumentProperties) throws ArgumentException {
         this.subCommand = null;
-        final ArrayList<String> trailingArguments = getTrailingArguments();
+        final List<String> trailingArguments = trailingArguments();
         trailingArguments.clear();
         setUsageOrVersionDisplayed(false);
 
diff --git a/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/StatsThread.java b/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/StatsThread.java
index 6e55710..565efc2 100644
--- a/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/StatsThread.java
+++ b/opendj-ldap-toolkit/src/main/java/com/forgerock/opendj/ldap/tools/StatsThread.java
@@ -12,6 +12,7 @@
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
  * Copyright 2016 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
  */
 package com.forgerock.opendj.ldap.tools;
 
@@ -47,8 +48,12 @@
  * Statistics thread base implementation.
  * <p>
  * The goal of this class is to compute and print rate tool general statistics.
+ * <p>
+ * This class is a {@link Runnable} rather than a {@link Thread}: it is run periodically by
+ * {@code statThreadScheduler}, and {@link #stopRecording(boolean)} calls {@link #run()} directly
+ * to print the last line of statistics.
  */
-class StatsThread extends Thread {
+class StatsThread implements Runnable {
 
     static final String STAT_ID_PREFIX = "org.forgerock.opendj.";
 
@@ -263,10 +268,10 @@
     private final RateReporter reporter;
     private long startTimeMs;
     private volatile boolean warmingUp;
-    private final ScheduledExecutorService statThreadScheduler = Executors.newSingleThreadScheduledExecutor();
+    private final ScheduledExecutorService statThreadScheduler =
+            Executors.newSingleThreadScheduledExecutor(runnable -> new Thread(runnable, "Stats Thread"));
 
     StatsThread(final PerformanceRunner performanceRunner, final ConsoleApplication application) {
-        super("Stats Thread");
         resetStats();
         this.performanceRunner = performanceRunner;
         this.app = application;
diff --git a/opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/datamodel/Category.java b/opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/datamodel/Category.java
index 86797b5..d2766c8 100644
--- a/opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/datamodel/Category.java
+++ b/opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/datamodel/Category.java
@@ -13,11 +13,14 @@
  *
  * Copyright 2008 Sun Microsystems, Inc.
  * Portions Copyright 2014-2015 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
  */
 
 package org.opends.guitools.controlpanel.datamodel;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 import org.forgerock.i18n.LocalizableMessage;
 
@@ -30,7 +33,7 @@
 public class Category
 {
   private LocalizableMessage name;
-  private ArrayList<Action> actions = new ArrayList<>();
+  private final List<Action> actions = new ArrayList<>();
 
   /**
    * Returns the name of the category.
@@ -52,10 +55,19 @@
 
   /**
    * Returns the actions associated with this category.
-   * @return the actions associated with this category.
+   * @return an unmodifiable view of the actions associated with this category.
    */
-  public ArrayList<Action> getActions()
+  public List<Action> getActions()
   {
-    return actions;
+    return Collections.unmodifiableList(actions);
+  }
+
+  /**
+   * Adds an action to this category.
+   * @param action the action to add.
+   */
+  public void addAction(Action action)
+  {
+    actions.add(action);
   }
 }
diff --git a/opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/ui/MainActionsPane.java b/opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/ui/MainActionsPane.java
index 69fc755..6ac2232 100644
--- a/opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/ui/MainActionsPane.java
+++ b/opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/ui/MainActionsPane.java
@@ -13,6 +13,7 @@
  *
  * Copyright 2008-2009 Sun Microsystems, Inc.
  * Portions Copyright 2014-2016 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
  */
 
 package org.opends.guitools.controlpanel.ui;
@@ -272,7 +273,7 @@
         action.setAssociatedPanel(classes.get(classIndex));
         classIndex ++;
 
-        category.getActions().add(action);
+        category.addAction(action);
       }
       categories.add(category);
     }
diff --git a/opendj-server-legacy/src/main/java/org/opends/quicksetup/installer/SuffixesToReplicateOptions.java b/opendj-server-legacy/src/main/java/org/opends/quicksetup/installer/SuffixesToReplicateOptions.java
index 7a5b2f9..ac76db6 100644
--- a/opendj-server-legacy/src/main/java/org/opends/quicksetup/installer/SuffixesToReplicateOptions.java
+++ b/opendj-server-legacy/src/main/java/org/opends/quicksetup/installer/SuffixesToReplicateOptions.java
@@ -13,9 +13,11 @@
  *
  * Copyright 2006-2010 Sun Microsystems, Inc.
  * Portions Copyright 2015 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
  */
 package org.opends.quicksetup.installer;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.Map;
@@ -46,10 +48,10 @@
     REPLICATE_WITH_EXISTING_SUFFIXES
   }
 
-  private Type type;
-  private Set<SuffixDescriptor> availableSuffixes;
-  private Set<SuffixDescriptor> suffixesToReplicate;
-  private Map<String, BackendTypeUIAdapter> backendsToReplicate;
+  private final Type type;
+  private final Set<SuffixDescriptor> availableSuffixes;
+  private final Set<SuffixDescriptor> suffixesToReplicate;
+  private final Map<String, BackendTypeUIAdapter> backendsToReplicate;
 
   /**
    * Constructor for the SuffixesToReplicateOptions object.
@@ -84,9 +86,9 @@
       Set<SuffixDescriptor> suffixesToReplicate, Map<String, BackendTypeUIAdapter> backendsToReplicate)
   {
     this.type = type;
-    this.availableSuffixes = new LinkedHashSet<>(availableSuffixes);
-    this.suffixesToReplicate = new LinkedHashSet<>(suffixesToReplicate);
-    this.backendsToReplicate = new HashMap<>(backendsToReplicate);
+    this.availableSuffixes = Collections.unmodifiableSet(new LinkedHashSet<>(availableSuffixes));
+    this.suffixesToReplicate = Collections.unmodifiableSet(new LinkedHashSet<>(suffixesToReplicate));
+    this.backendsToReplicate = Collections.unmodifiableMap(new HashMap<>(backendsToReplicate));
   }
 
   /**
@@ -103,7 +105,7 @@
   /**
    * Returns the set of suffixes available for replication.
    *
-   * @return the set of suffixes available for replication.
+   * @return an unmodifiable set of the suffixes available for replication.
    */
   public Set<SuffixDescriptor> getAvailableSuffixes()
   {
@@ -113,7 +115,7 @@
   /**
    * The set of suffixes that we must replicate with.
    *
-   * @return the set of suffixes that we must replicate with.
+   * @return an unmodifiable set of the suffixes that we must replicate with.
    */
   public Set<SuffixDescriptor> getSuffixes()
   {
@@ -123,7 +125,7 @@
   /**
    * Returns a map which associate backend names and backend types.
    *
-   * @return A map which associate backend names and backend types.
+   * @return an unmodifiable map which associate backend names and backend types.
    */
   public Map<String, BackendTypeUIAdapter> getSuffixBackendTypes()
   {
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/admin/client/cli/SecureConnectionCliArgs.java b/opendj-server-legacy/src/main/java/org/opends/server/admin/client/cli/SecureConnectionCliArgs.java
index 4e34adb..abc2952 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/admin/client/cli/SecureConnectionCliArgs.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/admin/client/cli/SecureConnectionCliArgs.java
@@ -13,6 +13,7 @@
  *
  * Copyright 2007-2010 Sun Microsystems, Inc.
  * Portions Copyright 2011-2016 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
  */
 package org.opends.server.admin.client.cli;
 
@@ -159,7 +160,9 @@
    * @throws ArgumentException
    *           If there is a problem with any of the parameters used to create
    *           this argument.
-   * @return a ArrayList with the options created.
+   * @return a new set holding the options created. Callers may add their own arguments to it:
+   *         the set is a copy, so doing so does not add them to the connection arguments this
+   *         object reports through {@link #argumentsPresent()}.
    */
   public Set<Argument> createGlobalArguments() throws ArgumentException
   {
@@ -230,7 +233,7 @@
     connectTimeoutArg = connectTimeOutArgument();
     argList.add(connectTimeoutArg);
 
-    return argList;
+    return new LinkedHashSet<>(argList);
   }
 
   /**
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationDomain.java b/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationDomain.java
index 5949582..68e2999 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationDomain.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/replication/service/ReplicationDomain.java
@@ -14,6 +14,7 @@
  * Copyright 2008-2010 Sun Microsystems, Inc.
  * Portions Copyright 2011-2016 ForgeRock AS.
  * Portions Copyright 2025-2026 3A Systems LLC.
+ * Portions Copyright 2026 3A Systems, LLC.
  */
 package org.opends.server.replication.service;
 
@@ -885,11 +886,11 @@
       if (initReqMsg != null)
       {
         // Do this work in a thread to allow replay thread continue working
-        ExportThread exportThread = new ExportThread(
+        ExportTask exportTask = new ExportTask(
             initReqMsg.getSenderID(), initReqMsg.getInitWindow());
         exportThreadPool.execute(() -> {
-          Thread.currentThread().setName(exportThread.getName());
-          exportThread.run();
+          Thread.currentThread().setName(exportTask.getName());
+          exportTask.run();
         });
       }
     }
@@ -1039,20 +1040,23 @@
    */
 
   /**
-   * This thread is launched when we want to export data to another server.
+   * This task is submitted to the export thread pool when we want to export data to another
+   * server.
    *
    * When a task is created locally (so this local server is the initiator)
    * of the export (Example: dsreplication initialize-all),
-   * this thread is NOT used but the task thread is running the export instead).
+   * this task is NOT used but the task thread is running the export instead).
    */
-  private class ExportThread extends DirectoryThread
+  private class ExportTask implements Runnable
   {
     /** Id of server that will be initialized. */
     private final int serverIdToInitialize;
     private final int initWindow;
+    /** Name given to the pool thread which runs this task. */
+    private final String name;
 
     /**
-     * Constructor for the ExportThread.
+     * Constructor for the ExportTask.
      *
      * @param serverIdToInitialize
      *          serverId of server that will receive entries
@@ -1060,14 +1064,24 @@
      *          The value of the initialization window for flow control between
      *          the importer and the exporter.
      */
-    public ExportThread(int serverIdToInitialize, int initWindow)
+    public ExportTask(int serverIdToInitialize, int initWindow)
     {
-      super("Export thread from serverId=" + getServerId() + " to serverId="
-          + serverIdToInitialize);
+      this.name = "Export thread from serverId=" + getServerId() + " to serverId="
+          + serverIdToInitialize;
       this.serverIdToInitialize = serverIdToInitialize;
       this.initWindow = initWindow;
     }
 
+    /**
+     * Returns the name of this task, used to name the thread running it.
+     *
+     * @return the name of this task
+     */
+    public String getName()
+    {
+      return name;
+    }
+
     @Override
     public void run()
     {
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/tools/LDAPConnectionOptions.java b/opendj-server-legacy/src/main/java/org/opends/server/tools/LDAPConnectionOptions.java
index 1bdbf7d..bb70bc7 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/tools/LDAPConnectionOptions.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/tools/LDAPConnectionOptions.java
@@ -13,6 +13,7 @@
  *
  * Copyright 2006-2008 Sun Microsystems, Inc.
  * Portions Copyright 2015 ForgeRock AS.
+ * Portions Copyright 2026 3A Systems, LLC.
  */
 package org.opends.server.tools;
 
@@ -174,6 +175,9 @@
 
   /**
    * Get the SASL options used for authentication.
+   * <p>
+   * The returned map is the live set of properties: callers may add or remove properties
+   * through it, as the DSML gateway does when it drops the authorization identity.
    *
    * @return  The SASL options used for authentication.
    */

--
Gitblit v1.10.0