Skip to content

Fix mechanical CodeQL note findings: reflection, boxing, deprecated closes - #134

Merged
vharseko merged 4 commits into
OpenIdentityPlatform:masterfrom
vharseko:codeql-note-safe-mechanics
Oct 4, 2026
Merged

vharseko merged 4 commits into
OpenIdentityPlatform:masterfrom
vharseko:codeql-note-safe-mechanics

Conversation

@vharseko

@vharseko vharseko commented Sep 18, 2026 •

Copy link
Copy Markdown
Member

Closes ~76 of the open note-severity CodeQL alerts (no security rating) that are safe, mechanical fixes: java/deprecated-call (Class.newInstance, SQLUtil.closeQuietly, plexus IOUtil.close), java/inefficient-boxed-constructor, java/inefficient-string-constructor, java/ignored-error-status-of-call, java/inefficient-key-set-iterator, java/inefficient-empty-string-test, java/missing-space-in-concatenation, java/unknown-javadoc-parameter, plus the 8 java/uncaught-number-format-exception alerts in AttributeTypeUtil that the boxing change touched anyway.

Class.newInstance() — 20 sites, 16 files

Replaced with the new ReflectionUtil.newInstance(MethodHandles.lookup(), clazz). Class.newInstance() rethrew whatever the constructor threw, unchanged. A bare getDeclaredConstructor().newInstance() wraps it in InvocationTargetException instead, and the catch-and-wrap sites would then turn e.g. a ConfigurationException from a connector constructor into ConnectorException("java.lang.reflect.InvocationTargetException"). The helper invokes the constructor through a MethodHandle, so the exception reaches the caller as is. Access is checked against the caller's lookup, so a package-private class (StdOutLogger from Log) stays instantiable from its own package. Every site is already inside a catch (Exception …)/catch (Throwable …) or declared throws Exception, so the reflective exceptions need no new handling anywhere.

Boxed constructors — 19 sites

new Integer/Long/Boolean/Double/Float/Character(String) → valueOf/parseX in AttributeTypeUtil, RandomGenerator, SQLUtil; new String(x) → x in AttributeTypeUtil. SQLUtil.string2Timestamp/string2Date use Long.parseLong (a primitive is what the Date/Timestamp constructor needs, so no boxing round-trip at all).

AttributeTypeUtil.createInstantiatedObject (xml-connector) also gets the NumberFormatException handling it was missing: a malformed numeric value in the XML data file now fails with a ConnectorException naming the value and the target type instead of a bare NumberFormatException with no indication of which attribute it came from. This closes the 8 java/uncaught-number-format-exception alerts in that class. The parse calls sit in a helper that declares throws NumberFormatException — CodeQL's query (NumberFormatException.ql) only accepts a catch in the same method or a throws clause on the enclosing one, so the first cut with just a wrapper method left all eight alerts open on the PR's own CodeQL run.

Deprecated closes — 26 sites

  • SQLUtil.closeQuietly(Connection/Statement/ResultSet) (deprecated in favour of IOUtil.quietClose) is replaced at its 23 call sites, including its own 6 internal uses.
  • Found and fixed a real regression along the way: IOUtil.quietClose(Connection) did not check isClosed() before calling close(), unlike the SQLUtil.closeQuietly(Connection) it replaces. Harmless against a real JDBC driver (Connection.close() on an already-closed connection is a spec-mandated no-op), but two tests using a strict-call-sequence mock (DatabaseConnectionTest.testDispose, SQLUtilTests.quietConnectionClose) caught the difference immediately. IOUtil.quietClose(Connection) now checks isClosed() first, matching what it replaces and what its own javadoc promises.
  • Plexus IOUtil.close(Closeable) — its javadoc says "deprecated: use try-with-resources instead" — is replaced by try-with-resources at its 3 call sites in the maven plugin.

ignored-error-status-of-call — 3 sites

XMLConfiguration.validate: getParentFile().mkdir()'s return value is checked now, distinguishing "the directory already existed" from a real failure. GroovyDataProvider (×2): an out file that cannot be created (a directory or a dangling symlink at that path) now throws into the existing catch (IOException), which clears the field, so nothing is stored. An already existing file is still used, as before.

Small mechanical ones

PrettyStringBuilder: map.keySet().iterator() + map.get(key) → map.entrySet().iterator(). StringUtil.isEmpty and an XSD annotation check: "".equals(x) / x.equals("") → x.isEmpty(). A missing space in a concatenated log message (ActiveDirectoryChangeLogSyncStrategy) and a stray comma in a @param javadoc tag (MultiOpTests) are fixed.

Tests

New: AttributeTypeUtilTest (21) — each numeric type parses, and a malformed or blank value fails with a ConnectorException naming the value and the type, caused by the NumberFormatException, for each of the 10 numeric types including BigInteger/BigDecimal. ReflectionUtilTests (+5) — the constructor's RuntimeException, checked exception and Error reach the caller unchanged, and a class without a no-arg constructor fails. XMLConfigurationTests (+1) — a file path without a parent directory validates. Everything else is a mechanical replacement with identical behaviour, except the IOUtil.quietClose(Connection) fix, which is proven by the two existing tests that caught the regression and now pass.

Local runs of all 11 touched modules, all green: connector-framework 197, dbcommon 86, connector-test-common 5, connector-framework-internal 499 (2 skipped, same as on master), connector-framework-osgi (compiles), connector-framework-contract 43, maven-plugin 1, connector-framework-server 32, databasetable-connector 78, ldap-connector 159 (embedded OpenDJ), xml-connector 103. (dbcommon, databasetable-connector and ldap-connector are from the first round; the review round did not touch them.)

Left open on purpose

java/missing-override-annotation (1327 — mechanically safe but would touch ~200 files and conflict with the six other open CodeQL PRs), java/deprecated-call on MessagesUtil.*Legacy (67 — the framework's own deprecated API, its only path for script arguments today), java/uncaught-number-format-exception (54 after the 8 closed here — needs a per-site judgment call, not mechanical; #139 takes them), java/unused-parameter / java/constants-only-interface / java/jdk-internal-api-access (public API or no JDK alternative), java/chained-type-tests (needs an actual refactor), java/call-to-object-tostring / java/local-shadows-field / java/confusing-method-signature (judgment calls, not mechanics).

@vharseko vharseko added java Pull requests that update java code framework OpenICF-java-framework labels Sep 18, 2026
@vharseko vharseko added dbcommon OpenICF-dbcommon connector:ldap LDAP connector connector:xml XML connector connector:databasetable Database table connector maven-plugin OpenICF-maven-plugin refactoring Code cleanup / tech debt, no behavior change labels Sep 18, 2026
@vharseko vharseko added the tests Test additions or fixes label Sep 19, 2026
vharseko added a commit to vharseko/OpenICF that referenced this pull request Sep 21, 2026
AttributeTypeUtil.java, MultiOpTests.java, PrettyStringBuilder.java,
StringUtil.java, ActiveDirectoryChangeLogSyncStrategy.java and
XSDAnnotationParser.java were independently fixed here and in OpenIdentityPlatform#134
with byte-identical diffs; reverting them here to avoid merging the
same change twice and to let OpenIdentityPlatform#134 own them. Drops
AttributeTypeUtilTests.java too since it exercises the
NumberFormatException-wrapping behavior that lived in the now-reverted
AttributeTypeUtil.java (still present in OpenIdentityPlatform#134, just not in this PR
anymore).
@vharseko

Copy link
Copy Markdown
Member Author

The red CodeQL check here is the 2 pre-existing java/zipslip (high) alerts in DocBookResourceMojo.java:414 and ConnectorInfoReportMojo.java:343. Both already exist on master (since 2026-07-10) and are unrelated to this PR's diff — they're only flagged because this diff touches nearby lines in the same files.

#126 fixes exactly those two lines (wraps the new File(...) calls in IOUtil.resolveEntry(...)), and CodeQL on #126 is green. Once #126 merges to master and this branch is rebased on top, the check here should clear on its own.

@maximthomas maximthomas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

praise: The cleanup finds and fixes real behaviour along the way, and each fix has a test.

  • IOUtil.quietClose(Connection) gets back the isClosed() guard of the SQLUtil.closeQuietly(Connection) it replaces. DatabaseConnectionTest.testDispose and SQLUtilTests.quietConnectionClose caught the missing guard.
  • AttributeTypeUtil.createInstantiatedObject now turns a malformed numeric value into a ConnectorException that names the value and the type. The new AttributeTypeUtilTest pins this for all 8 types CodeQL flagged.

issue (blocking): getDeclaredConstructor().newInstance() wraps constructor exceptions in InvocationTargetException, so the catch-and-wrap sites now hide the original exception type.

OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/ConnectorPoolManager.java:169, OpenICF-java-framework/connector-framework-internal/src/main/java/org/identityconnectors/framework/impl/api/local/LocalConnectorInfoManagerImpl.java:329, OpenICF-java-framework/connector-framework-osgi/src/main/java/org/forgerock/openicf/framework/impl/api/osgi/internal/OsgiConnectorInfoManagerImpl.java:338, OpenICF-java-framework/connector-framework-server/src/main/java/org/forgerock/openicf/framework/osgi/internal/AsyncOsgiConnectorInfoManagerImpl.java:245, OpenICF-java-framework/connector-framework-contract/src/main/java/org/identityconnectors/contract/test/ConnectorHelper.java:122, :125

Class.newInstance() rethrew whatever the constructor threw, unchanged. Constructor.newInstance() wraps every throwable, Errors included, in the checked InvocationTargetException. Each of these sites ends in catch (Exception e) { throw ConnectorException.wrap(e); } (ContractException.wrap in ConnectorHelper). wrap passes a RuntimeException through and rethrows an Error, but turns the ITE into new ConnectorException(ite).

The result: a ConnectionFailedException or ConfigurationException from a connector or configuration constructor now reaches the caller as a plain ConnectorException with the message java.lang.reflect.InvocationTargetException. A NoClassDefFoundError becomes a RuntimeException. You can see it by running the contract tests without -DconnectorName. That used to fail with GroovyDataProvider's "To run contract tests, you must specify valid [connectorName] ...". It now fails with ContractException("java.lang.reflect.InvocationTargetException"), and that message sits two causes down. ConnectorAPIOperationRunnerProxy:111 is the only site that already unwraps.

} catch (InvocationTargetException e) {
    throw ConnectorException.wrap(e.getCause()); // ContractException.wrap in ConnectorHelper
} catch (Exception e) {
    throw ConnectorException.wrap(e);
}

Apply the same unwrap at the factory singletons (ConnectorFacadeFactory:55/:74, ConnectorInfoManagerFactory:51, ObjectSerializerFactory:54, ConnectorServer:113, TestHelpers:219/:312) to keep the old behaviour there too. Also put it ahead of the catch (RuntimeException e) branches in Log.java:149 and EncryptorFactory.java:43, which no longer see constructor exceptions.


issue (non-blocking): ScriptExecutorFactory.getFactoryCache now silently skips a factory whose constructor throws, and logs the cause as Exception:null.

OpenICF-java-framework/connector-framework/src/main/java/org/identityconnectors/common/script/ScriptExecutorFactory.java:74, :80, :82, :173

The catch (RuntimeException e) { throw e; } at :80 no longer sees constructor exceptions. They arrive as InvocationTargetException in catch (Throwable) at :82, which logs e.getMessage(), and that is null for an ITE. Say JavaScriptExecutorFactory() throws "JavaScript Engine is not found". Every later newInstance("JavaScript") then fails with "Language not supported: JavaScript", and nothing in the log says why. newInstance(language) at :173 now throws RuntimeException(ITE) instead of the original exception.

} catch (Throwable e) {
    Throwable cause = e instanceof InvocationTargetException ? e.getCause() : e;
    logger.ok("ScriptExecutorFactory {0} can not be activated. Exception:{1}", factory, cause);
}

Or: unwrap as in the blocking issue to restore the old propagation.


issue (non-blocking): In GroovyDataProvider, the new !createNewFile() throw only fires on a race or a dangling symlink. When it does, it logs "will not be stored" and the parameters are stored anyway.

OpenICF-java-framework/connector-framework-contract/src/main/java/org/identityconnectors/contract/data/GroovyDataProvider.java:241, :250, :266

File.createNewFile() returns false only when the path already exists. A real I/O failure already threw IOException into the same catch before this PR. So the new branch fires in only two cases: the file appears between exists() and createNewFile(), or the path is a dangling symlink (exists() false, createNewFile() false). The catch warns "Unable to create ... the test parameters will not be stored" but leaves _queriedPropsOutFile / _propertyOutFile set. The writers check only for null, so they store the parameters anyway, through the symlink. Before this PR, the symlink case reached canWrite() == false, nulled the field and stored nothing.

_queriedPropsOutFile = new File(pOut);
if (!_queriedPropsOutFile.createNewFile() && !_queriedPropsOutFile.isFile()) {
    throw new IOException("Could not create " + _queriedPropsOutFile);
}
// ...
} catch (IOException iOException) {
    _queriedPropsOutFile = null;
    LOG.warn("Unable to create ''{0}'' file, the test parameters will not be stored", pOut);
}

Make the same change for _propertyOutFile at :266.


suggestion (non-blocking): AttributeTypeUtilTest checks only the exception class, not the message, and leaves out BigInteger/BigDecimal.

OpenICF-xml-connector/src/test/java/org/forgerock/openicf/connectors/xml/util/AttributeTypeUtilTest.java:45, :51, :55

The test's Javadoc promises an exception that names the value and the target type, but nothing checks the message. Change AttributeTypeUtil.java:50 to throw new ConnectorException(e) and all 16 rows stay green. The BIG_INTEGER/BIG_DECIMAL branches (AttributeTypeUtil.java:107-113) sit behind the same catch, and no test calls them.

@Test(dataProvider = "numericTypes")
public void createInstantiatedObjectWrapsMalformedNumber(String type) {
    try {
        AttributeTypeUtil.createInstantiatedObject("not-a-number", type);
        fail("ConnectorException expected for " + type);
    } catch (ConnectorException e) {
        assertEquals(e.getMessage(), "Value 'not-a-number' is not a valid " + type);
        assertTrue(e.getCause() instanceof NumberFormatException);
    }
}

Pin: the message assertion catches the mutant above. Add the rows { XmlHandlerUtil.BIG_INTEGER } and { XmlHandlerUtil.BIG_DECIMAL } to numericTypes() to cover the Big* branches.


suggestion (non-blocking): No test covers the new parentFile != null guard in XMLConfiguration.validate.

OpenICF-xml-connector/src/main/java/org/forgerock/openicf/connectors/xml/XMLConfiguration.java:110

Take a relative xmlFilePath with no parent directory (getParentFile() == null). Before this PR it threw NullPointerException, and it now validates. Drop parentFile != null && and the suite stays green. Every setCreateFileIfNotExists(true) test uses a file whose parent directory exists.

@Test
public void shouldValidateXmlFilePathWithoutParentDirectory() throws IOException {
    File xsd = File.createTempFile("schema", ".xsd");
    xsd.deleteOnExit();
    File xml = new File("xml-config-" + System.nanoTime() + ".xml"); // getParentFile() == null
    config.setXsdFilePath(xsd);
    config.setXmlFilePath(xml);
    config.setCreateFileIfNotExists(true);
    config.validate(); // NullPointerException before the guard
    AssertJUnit.assertFalse(xml.exists());
}

Pin: this test fails with NullPointerException as soon as the guard is removed.

…loses

Class.newInstance() is replaced with getDeclaredConstructor().newInstance()
at every call site (20): all are already inside a catch (Exception) or
catch (Throwable), or declared throws Exception, so the extra checked
NoSuchMethodException needs no new handling.

new Integer/Long/Boolean/Double/Float/Character(String) become valueOf/
parseX (19 sites); new String(x) becomes x.

SQLUtil.closeQuietly(Connection/Statement/ResultSet), deprecated in favour
of IOUtil.quietClose, is replaced at its 23 call sites, including its own
internal use. IOUtil.quietClose(Connection) was missing the isClosed()
check SQLUtil.closeQuietly(Connection) has, so it called close() on an
already-closed connection where the old code did not; two tests using a
strict-sequence mock caught it. IOUtil.quietClose(Connection) now checks
isClosed() first, matching what it replaces.
Plexus IOUtil.close(Closeable) ("deprecated: use try-with-resources
instead") is replaced by try-with-resources at its 3 call sites in the
maven plugin.

XMLConfiguration.validate and GroovyDataProvider no longer ignore the
return value of mkdir()/createNewFile(): a real failure now surfaces
through the existing IOException handling instead of being silently
masked by the following canWrite() check.

Small ones: PrettyStringBuilder iterates entrySet() instead of keySet()
followed by get(); StringUtil.isEmpty and an XSD annotation check use
isEmpty() instead of comparing to ""; a missing space in a concatenated
message and a stray comma in a @PARAM tag are fixed.
createInstantiatedObject used to let a NumberFormatException from
Integer/Long/Double/Float.valueOf (and BigInteger/BigDecimal) escape
uncaught, with no indication of which attribute or target type failed to
parse. Converting the deprecated boxed constructors to valueOf earlier in
this branch made CodeQL recognise the parse call and flag it
(java/uncaught-number-format-exception) - the bug was already there, just
invisible to the query through the old constructor form.
CodeQL's uncaught-number-format-exception query only sees a catch in the
same method or a throws clause on the enclosing one; the wrapper added in
the previous commit left all eight alerts open at the helper's lines.
vharseko added a commit that referenced this pull request Oct 2, 2026
… SecurityManager (#138)

Closes the remaining `java/deprecated-call` alerts on real
(non-`MessagesUtil.*Legacy`) code:
`SSLContextConfigurator.createSSLContext` (3) and
`System.getSecurityManager` (1). Together with the 67
`MessagesUtil.serializeLegacy`/`deserializeLegacy` alerts (dismissed as
"won't fix" — see below) and the reflection/boxing/close alerts fixed in
#134, this closes `java/deprecated-call` entirely.

## SSLContextConfigurator.createSSLContext() — 3 sites

Grizzly's `SSLContextConfigurator.createSSLContext()` is deprecated in
favour of `createSSLContext(boolean throwException)`. Checked the
Grizzly source: the no-arg version is literally `return
createSSLContext(false);` — identical behaviour, so
`ConnectionManager.java` (×2) and `ConnectorServer.java` (grizzly, ×1)
switch to `createSSLContext(false)` with no behaviour change.

## System.getSecurityManager() — CCLWatchThreadFactory

This class's own comment says it is "Copied from
java.util.concurrent.Executors.DefaultThreadFactory". Checked the
current JDK's own copy of that class (OpenJDK 26 source): it has since
dropped the `SecurityManager` branch entirely, now unconditionally
`group = Thread.currentThread().getThreadGroup();` — matching the
permanent disabling of the Security Manager in JEP 486. This PR makes
the same change here, with a comment explaining why.

## MessagesUtil.serializeLegacy / deserializeLegacy — 67 alerts,
dismissed, not touched

These are the framework's only serialization path for payloads its own
wire protocol defines as opaque `bytes` rather than structured protobuf
messages — `scriptArguments` (`CommonObjectMessages.proto`),
`connectorObject`, `attributes`, and the sync token `value`
(`OperationMessages.proto`). There is no drop-in non-deprecated
alternative without redefining the wire protocol, which would break
compatibility with the .NET connector server and existing clients.
Dismissed on GitHub as "won't fix" with that reasoning recorded on each
alert.

## Tests

No new tests: both changes are behaviour-preserving (verified against
the Grizzly source and the current JDK source respectively), and neither
has an observable difference a test could assert on.

Local runs of the three touched modules, all green:
connector-framework-internal 469 tests (2 skipped, same as on master),
connector-framework-server 29, connector-server-grizzly 34.
vharseko added a commit to vharseko/OpenICF that referenced this pull request Oct 2, 2026
AttributeTypeUtil.java, MultiOpTests.java, PrettyStringBuilder.java,
StringUtil.java, ActiveDirectoryChangeLogSyncStrategy.java and
XSDAnnotationParser.java were independently fixed here and in OpenIdentityPlatform#134
with byte-identical diffs; reverting them here to avoid merging the
same change twice and to let OpenIdentityPlatform#134 own them. Drops
AttributeTypeUtilTests.java too since it exercises the
NumberFormatException-wrapping behavior that lived in the now-reverted
AttributeTypeUtil.java (still present in OpenIdentityPlatform#134, just not in this PR
anymore).
…ectively

getDeclaredConstructor().newInstance() wraps whatever the constructor
throws in InvocationTargetException, so the catch-and-wrap sites turned
e.g. a ConfigurationException from a connector constructor into a plain
ConnectorException("java.lang.reflect.InvocationTargetException"), and
ScriptExecutorFactory logged a failing factory as "Exception:null".
ReflectionUtil.newInstance(Lookup, Class) restores what Class.newInstance()
did: the constructor's exception reaches the caller as is, and access is
checked against the caller's lookup, so a package-private class such as
StdOutLogger stays instantiable from its own package.

GroovyDataProvider no longer keeps an out file it failed to create: a
dangling symlink or a directory at that path now stores nothing, as
before, instead of logging "will not be stored" and writing through it.

AttributeTypeUtilTest pins the exception message and cause and covers
BigInteger/BigDecimal; XMLConfigurationTests covers a file path without
a parent directory.
@vharseko
vharseko force-pushed the codeql-note-safe-mechanics branch from 73a6a75 to 7500a98 Compare October 2, 2026 12:17
@vharseko

vharseko commented Oct 2, 2026

Copy link
Copy Markdown
Member Author

@maximthomas addressed in 7500a98 (the branch is also rebased on current master, so the java/zipslip note above no longer applies — #126 is in):

InvocationTargetException (blocking) and ScriptExecutorFactory. Fixed with one helper rather than a catch at every site: ReflectionUtil.newInstance(MethodHandles.lookup(), clazz) invokes the no-arg constructor through a MethodHandle, so whatever the constructor throws reaches the caller unchanged, as with Class.newInstance(). All 20 sites use it, including JavaClassProperties.createBean2 and ConnectorDocBuilder, which weren't in your list. The caller's lookup matters: a first cut that called Constructor.newInstance() inside the helper broke Log → StdOutLogger (package-private), because the access check then ran against ReflectionUtil; LogTests.checkSystemProperty caught it. ReflectionUtilTests pins RuntimeException, checked exception and Error passing through unchanged. The Class.newInstance paragraph in the description was wrong and is corrected.

GroovyDataProvider. Taken as suggested: !createNewFile() && !isFile(), and the catch clears the field, at both sites.

AttributeTypeUtilTest. It now asserts the message and the NumberFormatException cause, and has BIG_INTEGER/BIG_DECIMAL rows.

XMLConfiguration. Added shouldValidateXmlFilePathWithoutParentDirectory, with cleanup in finally.

@vharseko
vharseko requested a review from maximthomas October 2, 2026 12:26

@maximthomas maximthomas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

praise: The round-1 exception-type regression is fixed in one place, with the old semantics kept exactly.

  • ReflectionUtil.newInstance invokes the constructor through lookup.findConstructor(clazz, methodType(void.class)).invoke() and rethrows Exception | Error as is. A ConfigurationException from a connector constructor reaches ConnectorException.wrap / ContractException.wrap unchanged again, as it did under Class.newInstance(). All 20 sites use it; no bare getDeclaredConstructor().newInstance() is left in main code.
  • Access is checked against the caller's MethodHandles.lookup(), so Log.getLog still instantiates the package-private StdOutLogger. A probe with two sibling class loaders (the OSGi bundle layout) behaves the same as Class.newInstance(): a public class instantiates, a constructor exception passes through unchanged, and an abstract class throws InstantiationException.

@vharseko
vharseko merged commit 88760f1 into OpenIdentityPlatform:master Oct 4, 2026
14 checks passed
@vharseko
vharseko deleted the codeql-note-safe-mechanics branch October 4, 2026 07:39
vharseko added a commit that referenced this pull request Oct 4, 2026
Closes the remaining 1320 open `java/missing-override-annotation` CodeQL
alerts on `master` — the last large `note`-severity category from the
ongoing CodeQL cleanup series (#122, #123, #126, #128, #130–#134).

## Change

Every location CodeQL's `java/missing-override-annotation` flagged gets
an `@Override` line inserted immediately above the method signature, at
the same indentation, at the exact line the alert points to. 225 files,
purely additive (`+1488 / −0`):

- 1320 `@Override` insertions;
- 164 `Portions Copyrighted 2026 3A Systems, LLC` header lines: 162
appended to an existing header, and 2 in a new `/* */` block for files
that had no header at all (`AttributeMappingConfig`, `ContainsQuery`).
The other 61 files already carried a 3A line covering 2026, so their
headers are left alone.

No other code changes: no renames, no logic touched, no reordering.

## Verification

`@Override` is compiler-checked — `javac` fails hard when it is placed
on a method that does not actually override or implement anything from a
supertype ("method does not override a method from its superclass").
Rather than review 1320 insertions by hand, the real proof is a clean
build: the entire reactor was built from the root `pom.xml` (`mvn
install`, all 28 modules) after the insertions, with no manual fixes
needed afterward.

Result on the original head: **BUILD SUCCESS**, all 28 modules, **1442
tests, 0 failures, 0 errors** (164 skipped, same skips as on `master`) —
including `connector-framework-internal` (469 tests) and
`ldap-connector` with its embedded OpenDJ (159 tests). Also spot-checked
~30 random insertions by hand across different files and annotation
styles (interface method declarations, anonymous inner classes, nested
static classes) before the build, all correctly placed. After the rebase
below, the whole reactor was rebuilt (`mvn install -DskipTests`, main
and test sources) and `build.yml` runs on the new head.

## Rebase onto master

The branch was rebased onto `master` at c7648cf. Two files conflicted
with PRs merged in the meantime:

- `RemoteFrameworkConnectionInfoConverter` (#122): #122 had already
added its own 3A header line and replaced the anonymous
`X509TrustManager` with an `X509ExtendedTrustManager` whose seven
methods are annotated. Master's side is kept for both hunks; this PR now
only adds `@Override` to `enableLogging`, `canConvert` and
`fromConfiguration`.
- `RemoteRequest` (#127): #127 rewrote the anonymous classes and already
annotates all four methods, so the file is taken from master unchanged
and drops out of this PR.

In six files — the converter above plus `EncryptorImpl`,
`LocalConnectorInfoManagerImpl`, `RemoteFrameworkConnection`,
`CCLWatchThreadFactory` and `ConnectionManager` — other PRs of the
series had already added a 3A header line; that line now comes from
master, and no file carries two 3A lines.

The remaining open PRs of the series may still touch files changed here;
whichever merges second needs a rebase.
vharseko added a commit to vharseko/OpenICF that referenced this pull request Oct 4, 2026
AttributeTypeUtil.java, MultiOpTests.java, PrettyStringBuilder.java,
StringUtil.java, ActiveDirectoryChangeLogSyncStrategy.java and
XSDAnnotationParser.java were independently fixed here and in OpenIdentityPlatform#134
with byte-identical diffs; reverting them here to avoid merging the
same change twice and to let OpenIdentityPlatform#134 own them. Drops
AttributeTypeUtilTests.java too since it exercises the
NumberFormatException-wrapping behavior that lived in the now-reverted
AttributeTypeUtil.java (still present in OpenIdentityPlatform#134, just not in this PR
anymore).
vharseko added a commit that referenced this pull request Oct 5, 2026
## Summary

Follow-up to the unused-parameter cleanup (#141): investigated all the
newly-surfaced small `note`-severity CodeQL categories.

- **`GuardedString`** now overrides `toString()` (returns
`"GuardedString(...)"`), so it no longer inherits `Object`'s default.
Fixes `call-to-object-tostring` at its root instead of patching the two
current call sites (`SharedSecretPrincipal`,
`ScriptOnResourceApiOpTests`) individually — any future logging of a
`GuardedString` is safe by construction. This is more than cosmetic: the
inherited output printed `hashCode()`, which is derived from the
unsalted SHA-1 hash of the clear text, so a fingerprint of the secret
reached log text — through `SharedSecretPrincipal.toString()`, and, with
OK-level logging on, through `LoggingProxy`, which prints every API
argument and return value: a `GuardedString` attribute value (e.g.
`__PASSWORD__`) passed to create/update or returned by getObject was
printed via `Attribute.toString()`.
- **`GuardedByteArray`** gets the same override
(`"GuardedByteArray(...)"`): its `hashCode()` is derived from the clear
bytes the same way, and it is a supported attribute type, so the same
`LoggingProxy` → `Attribute.toString()` path printed it.
- Fixes a shadowed local in `LdapInternalSearch.execute()`
(`local-shadows-field`).

Also dismissed on GitHub: 3 `ignored-error-status-of-call` (the ignored
return values are already covered by a subsequent check or exception
path), 8 `jdk-internal-api-access` (AD DirSync's
`com.sun.jndi.ldap.Ber*`, no public JDK alternative exists), 1
`confusing-method-signature` (`Log.log(..)` is long-standing,
heavily-used public API — renaming to remove the overload is too
invasive for the benefit).

Covered by other PRs instead of this one:
- #134 (opened independently, earlier) fixes byte-for-byte the same
lines in `AttributeTypeUtil.java`, `MultiOpTests.java`,
`PrettyStringBuilder.java`, `StringUtil.java`,
`ActiveDirectoryChangeLogSyncStrategy.java`, `XSDAnnotationParser.java`
(with `AttributeTypeUtilTests.java`):
`uncaught-number-format-exception`, `inefficient-boxed-constructor`,
`inefficient-empty-string-test`, `inefficient-key-set-iterator`,
`unknown-javadoc-parameter`, `missing-space-in-concatenation`. Dropped
here on 2026-09-21.
- #132 removes the dead `ContractTestFactory` inner class from
`ContractITCase` (`unused-reference-type`) along with more of that
file's dead code; the two PRs conflicted there, so this PR no longer
touches `ContractITCase`. Dropped here on 2026-10-04.

**Update 2026-10-02 (review round 1):** rebased onto the current
`master`; added the `GuardedByteArray` override; replaced the
`toString()` test, which passed even without the override, by one that
pins the fix; reworded the Javadoc to say why the override matters.

**Update 2026-10-04 (review round 2):** rebased onto the current
`master` (picks up #146, which fixes the `docs.groovy-lang.org` javadoc
failure that turned the `windows-latest, 11` cell red); both
`toString()` tests now also pin the exact output; corrected the
description of the `GuardedByteArray` leak (it was reachable, not
latent); dropped the `ContractITCase` change in favour of #132; aligned
the added 3A copyright lines to the repository's `Portions Copyrighted
2026 3A Systems, LLC`.

## Test plan
- [x] `GuardedStringTests.testToStringDoesNotDependOnTheSecret` and
`GuardedByteArrayTests.testToStringDoesNotDependOnTheSecret`: two
different secrets print the same `toString()`, and it is exactly
`"GuardedString(...)"` / `"GuardedByteArray(...)"`. Verified red with
the overrides removed (round 1) and with `toString()` returning `null` /
`""` (round 2).
- [x] `connector-framework` test suite after round 2 — 194 tests, 0
failures.
- [x] `connector-framework`, `connector-framework-contract`,
`OpenICF-ldap-connector` build after round 2.
- [x] `mvn install` on `connector-framework`,
`connector-framework-contract`, `OpenICF-ldap-connector` (incl. existing
test suites, LDAP/OpenDJ integration tests included) — all green, 946
tests, 0 failures (before the review rounds).
vharseko added a commit that referenced this pull request Oct 5, 2026
… NumberFormatException (#139)

Closes 48 of the 54 remaining `java/uncaught-number-format-exception`
alerts (the 8 in `AttributeTypeUtil` were already fixed in #134).
Dismisses the remaining 6 as unreachable in practice, with the reasoning
recorded on each alert.

## AD/LDAP cluster — 26 of 28 sites

`ADUserAccountControl`, `ADGroupType` and
`ActiveDirectoryChangeLogSyncStrategy` share one root cause: Active
Directory bitmask attributes (`userAccountControl`, `groupType`) and
sync tokens are parsed with plain `Integer.parseInt`, nothing around it.
`ADLdapUtil` gets `parseADInteger`/`parseADLong` (new, unit-tested), and
all three classes route through it — a corrupted AD attribute, or a sync
token tampered with by whoever calls the connector's `sync()` operation,
now fails with a `ConnectorException` naming the value instead of a bare
`NumberFormatException` three frames deep with no context.

The sync strategy's USNs (`uSNChanged`, `highestCommittedUSN`) are
64-bit in AD, so they go through `parseADLong` and the changes are
ordered in a `TreeMap<Long, SyncDelta>`; before, a domain controller
past `Integer.MAX_VALUE` could not sync at all.

`PagedSearchStrategy`'s paged-results cookie is `<base64
cookie>:<context index>`; the base64 half was already inside a
`try`/`catch(RuntimeException)` reporting `ConnectorException`, the
numeric half was one line below it, outside. Moved in, and an index
outside `baseDNs` (`AAAA:5`, `AAAA:-1`, and `AAAA:1` against a single
base DN) is now rejected the same way instead of escaping as an
`IndexOutOfBoundsException`.

Left alone and dismissed as unreachable (`ADLdapUtil.binarySIDtoString`,
2 sites): the hex strings it parses are built two lines above via
`String.format("%02X...")` from raw bytes, so they are always valid hex.

## Elsewhere — 22 sites, same idiom as what already surrounds them

- `PropertyBag.castValue` (maven-plugin, 8): POM `<configuration>`
values converted to typed connector config properties at build time; the
conversion chain is wrapped as a whole, reporting through the
`MojoExecutionException` the method already uses two lines above for the
equivalent blank-value case.
- `SQLUtil.attribute2jdbcValue` (dbcommon, 6): converts an attribute
value to a JDBC-bindable value for a numeric SQL column; wrapped with
`ConnectorException`, already imported and used elsewhere in the file.
- `XmlObjectDecoder` (connector-framework-internal, 5): the five
primitive decoders of the XML wire protocol — the one genuinely
network-facing trust boundary in this batch. Wrapped with
`ConnectorException`, matching `decodeClass`'s existing convention two
methods above; an empty element (no text node, which `#PCDATA` allows)
is rejected the same way instead of an NPE.
- `RemoteWSFrameworkConnectionInfo.loadSystemProxy` (1): a typo in
`-Dhttp.proxyPort` now fails clearly at startup instead of with a bare
NFE.
- `ConnectorHelper` / `AuthenticationApiOpTests`
(connector-framework-contract, 2): contract-test configuration
(`-DserverPort`, a test-suite attribute) now reports through
`ContractException`, the framework's own convention, instead of a bare
NFE.

## Dismissed as unreachable — 6

- `ADLdapUtil.binarySIDtoString` ×2 (see above).
- `TstStatefulConnectorConfig` (testbundlev1): parses a revision the
same test connector generated two lines above via
`AtomicInteger.getAndIncrement()`/`String.valueOf()` — no external
input.
- `JavaScriptExecutorFactory`: parses the JVM's own
`java.specification.version`, guaranteed valid by the JDK.
- `CSVFileConnector` ×2: parses a substring the regex `\.[0-9]{13}$`
just matched immediately before — always exactly 13 ASCII digits.

## Tests

New: `ADLdapUtilTest` (8), `ADUserAccountControlTest` (2),
`ADGroupTypeTest` (2), `PagedSearchStrategyTest` (4), `SQLUtilTests`
(+3), `XmlObjectDecoderTest` (11, corrupts or empties a value inside a
real serialized document rather than hand-building the XML schema),
`RemoteWSFrameworkConnectionInfoTest` (1), `PropertyBagTests` (2,
through the public `mergeConfigurationProperties`) — 33 new/changed
tests. Each is RED before its fix and green after, except four that are
green on master by design: `decodesAValidInt`,
`isAccountDisabledReadsTheBitmask` and `isScopeGlobalReadsTheBitmask`
are positive controls for the parse path, and
`convertsAnIntWithoutTheUnsupportedWarning` guards against the round-1
regression where a split `if` chain logged "Cast to targetType ... is
not supported" for every numeric property. The wrapping tests
(`ADLdapUtilTest`, `XmlObjectDecoderTest`, `SQLUtilTests`,
`RemoteWSFrameworkConnectionInfoTest`, `PropertyBagTests`) pin the exact
message and the `NumberFormatException` cause, so a catch that drops
either fails. `ConnectorHelper`/`AuthenticationApiOpTests` are
contract-test configuration parsers with no dedicated unit tests of
their own; verified by compiling and by the module's existing suite.

Local runs after rebasing onto current master (`1abfe741`, through
#143), one reactor build of every touched module with its dependencies,
all green: connector-framework 197, dbcommon 89,
connector-framework-internal 520 (2 skipped, same as on master),
connector-framework-contract 43, maven-plugin 3,
connector-framework-server 33, ldap-connector 179 (embedded OpenDJ). The
counts are higher than in earlier rounds because master gained tests
meanwhile (e.g. `ADUserAccountControlTests` from #141).
vharseko added a commit that referenced this pull request Oct 6, 2026
…nterned-string locks, racy lazy init (#132)

Closes the remaining 10 open CodeQL alerts of severity `error` that
carry no security rating: `java/contradictory-type-checks` #1536 #1537,
`java/dereferenced-value-is-always-null` #1560,
`java/sync-on-boxed-types` #1554 #1555 #1556,
`java/unsafe-double-checked-locking` #1550 #1551,
`java/unsynchronized-getter` #1544 #1545. The eleventh, #1549
(`ScriptedConfiguration`), was closed by #133.

Fixes #148
Fixes #149

| Where | Finding | Change |
|---|---|---|
| `SQLUtil.setParam` | `else if (val instanceof Integer)` appears twice;
the second branch can never run | second branch removed |
| `ContractITCase.createInstances` | `IObjectFactory objectFactory =
null` is dereferenced for any test class with a `(String)` constructor.
None of the default classes has one today, so the code only worked
because the `NoSuchMethodException` path was always taken | the
`(String)` constructor is called through plain reflection, so the method
no longer depends on TestNG's internal `ObjectFactoryImpl`, which TestNG
7.5 moved and whose `IObjectFactory` 7.10.2 removed. The unused nested
`ContractTestFactory` with its three never-read fields is gone |
| `BatchRemoteCache` (testbundlev1) | `synchronized (resultLock)` on the
interned literal `"resultLock"`, a monitor shared with any other code in
the JVM that synchronises on the same string | `new Object()` |
| `WebSocketConnectionGroup.operationContext`,
`TstStatefulConnectorConfig.executorService` | double-checked lazy
initialisation reads a non-volatile field outside the lock, so a thread
may see a partially constructed object | fields declared `volatile`,
which makes the pattern correct under the JMM |
| `ScriptedConfiguration.getGroovyScriptEngine()` | #133 made the getter
`synchronized` for the whole initialisation. That closed the
double-checked-locking alert and #149 (the getter and `release()` share
the monitor now), and concurrent callers wait for the customizer. But
the getter still stored the engine before its customizer ran, so a
failed customizer left an uncustomized engine in place for good (#148) |
on top of #133's synchronised getter, the engine is published only after
`initializeCustomizer()` succeeds. The customizer's own re-entrant calls
get the engine under construction from a second lock-guarded field. If
the customizer fails, nothing is published and the next call retries;
the failed customizer class is removed with `InvokerHelper.removeClass`,
so the metaclass the REST, CREST and SSH configurations register on it
does not keep one class loader alive per retry. The customizer runs
under the configuration's monitor, so it must not wait for another
thread that uses the engine; the comment on `initializeCustomizer()`
says so |
| `customize` in `SSHConfiguration`, `ScriptedCRESTConfiguration` | with
the retry above, a failed attempt that registered `release {}` left its
closure for `release()` to run after a successful retry that registered
none: the SSH `customize` never reset it, and CREST's `release = null`
wrote a private field nothing reads | both reset it with
`setReleaseClosure(null)`, as REST's `customize` already does; CREST's
unread field is removed |
| `FrameworkUtil.getFrameworkVersion()` | unsynchronised lazy init while
`setFrameworkVersion` is synchronised | `static synchronized` (not a hot
path). The `(ClassLoader)` overload reads
`connectors-framework.properties` rather than the field and is only used
by `FrameworkUtilTests`, so it is renamed to `readFrameworkVersion` and
the getter/setter pairing no longer applies to it |

`ScriptedConfigurationTest` covers #148: a customizer that fails once is
retried on the next call, a second thread blocks until the customizer
has finished, and a failed customizer class has no metaclass left
registered. Each test fails when its part of the fix is reverted (engine
published before the customizer, no `removeClass`, getter not
synchronised). `ScriptedConfigurationTest` (CREST) and
`SSHConfigurationCustomizerTest` (SSH) cover the release closure: a
first attempt registers `release {}` and fails, the retry registers
none, and `release()` must not run the stale closure; both fail without
the `customize` reset. #149 is fixed on master by #133; it was a window
between two adjacent field reads that no test can hold open, so it has
no test. The other changes have no behaviour a unit test can observe:
dead branches, `volatile`, a private monitor and a synchronised getter.
`createInstances` takes its classes from a fixed list with no `(String)`
constructors, so its reflective path cannot be reached from a test.

The class of a customizer that **succeeded** is still never
unregistered, so `release()` keeps its loader alive; master behaves the
same, and that is tracked separately in #154.

Local reactor run (rebased on current master, after #131 #133 #134 #135
#136 #139 #141 #142 #143 #145) of all touched modules and everything
they depend on: connector-framework 200, dbcommon 89, framework-internal
524 (2 skipped), contract 43, connector-framework-server 33,
groovy-connector 130 (42 skipped as on master, 4 new), ssh-connector 97
(82 skipped, 1 new), testbundlev1 compiles — all green.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

connector:databasetable Database table connector connector:ldap LDAP connector connector:xml XML connector dbcommon OpenICF-dbcommon framework OpenICF-java-framework java Pull requests that update java code maven-plugin OpenICF-maven-plugin refactoring Code cleanup / tech debt, no behavior change tests Test additions or fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants