Skip to content

Fix mismatched placeholders in log and exception messages - #173

Merged
vharseko merged 2 commits into
OpenIdentityPlatform:masterfrom
vharseko:fix-log-format-placeholders
Oct 22, 2026
Merged

vharseko merged 2 commits into
OpenIdentityPlatform:masterfrom
vharseko:fix-log-format-placeholders

Conversation

@vharseko

@vharseko vharseko commented Sep 18, 2026 •

Copy link
Copy Markdown
Member

Closes code scanning alerts #29, #30, #31 (java/unused-format-argument, warning). All three are real message bugs: the argument was supplied but never rendered.

Alert Location Before After
#29 Heaplets.java logger.warn("… instantiate %s as a Heaplet", c.getName(), e) — SLF4J logs a literal %s, the class name is lost {}
#30 ResolveLocationJsonValueFunction.java String.format("$location value ({}) cannot be null …", …) — exception message shows a literal {} %s, filled with the raw (unevaluated) $location value, so "${missing}" is reported as $location value (${missing}) instead of (null)
#31 PolicyEnforcementFilter.java "Returned resource ('{}' does not match current request URI (''))" — second placeholder missing, original never printed, unbalanced parentheses ('{}') … ('{}')

Tests added:

  • HeapletsTest.shouldLogClassNameWhenHeapletCannotBeInstantiated — captures the WARN with a logback ListAppender via a heaplet whose constructor throws; failed before the fix with …instantiate [%s] as a Heaplet.
  • ResolveLocationJsonValueFunctionTest.shouldReportTheValueWhenLocationIsNull — failed before the fix with $location value ({}) cannot be null ….
  • ResolveLocationJsonValueFunctionTest.shouldReportTheExpressionWhenLocationEvaluatesToNull — "$location": "${missing}"; failed with $location value (null) … while the value was evaluated before fetch().

No test for #31: openig-openam runs its tests on slf4j-nop, so the log line cannot be captured without adding a logging backend to the module's test dependencies; the change is confined to the format string of a // Should never happen branch.

@vharseko vharseko added bug java Pull requests that update Java code tests Adds or updates tests labels Sep 18, 2026

@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: Each fix puts the placeholder where the argument already was, and the two testable fixes get tests that fail at the base.

  • Heaplets.java:86 now logs c.getName() through {} instead of a literal %s.
  • PolicyEnforcementFilter.java:325 has two {} for its two arguments and balanced parentheses.
  • HeapletsTest.shouldLogClassNameWhenHeapletCannotBeInstantiated captures the WARN with a logback ListAppender and asserts the exact text.

suggestion (non-blocking): The %s argument in ResolveLocationJsonValueFunction.fetch is always null, so the message never shows a $location value.

openig-core/src/main/java/org/forgerock/openig/util/ResolveLocationJsonValueFunction.java:104-108, :72

fetch() receives the node after as(evaluated(bindings)) (:72). as(url()) returns null only when the value isNull() (commons util JsonValueFunctions.TO_URL); a malformed or non-string value throws before this point. So {"$location": null} and {"$location": "${missing}"} both produce $location value (null) cannot be null (or evaluated to null). The expression that evaluated to null is lost, although the commit body says the exception now "reports the $location value". Passing the raw value through keeps the alert fixed and makes the message useful:

if (node.isMap() && node.size() == 1 && node.isDefined("$location")) {
    JsonValue location = node.get("$location");
    return fetch(location.as(evaluated(bindings)), location.getObject());
}

private JsonValue fetch(JsonValue location, Object expression) {
    URL url = location.as(url());
    if (url == null) {
        throw new JsonValueException(location,
                                     format("$location value (%s) cannot be null (or evaluated to null)",
                                            expression));
    }

Pin: a case with "$location": "${missing}" that asserts $location value (${missing}). It fails against the current code.


note (non-blocking): The commit text does not match the behaviour.

  • openig-core/src/main/java/org/forgerock/openig/util/ResolveLocationJsonValueFunction.java: the commit says the exception now reports the $location value, but on this branch the value is always null (see the suggestion above).

- Heaplets: use the SLF4J "{}" placeholder instead of "%s", so the
  warning names the class that could not be instantiated
- ResolveLocationJsonValueFunction: use "%s" in String.format() instead
  of "{}", so the exception reports the $location value
- PolicyEnforcementFilter: add the missing placeholder for the request
  URI in the "returned resource does not match" error
The $location node was evaluated before fetch(), and url() returns null
only for a null node, so the exception always read
"$location value (null)". Evaluate the node inside fetch() and print
the raw value, so "${missing}" is reported as
"$location value (${missing})".
@vharseko
vharseko force-pushed the fix-log-format-placeholders branch from f0f68c1 to f2ea5a1 Compare October 22, 2026 11:55
@vharseko

Copy link
Copy Markdown
Member Author

Both points are addressed in f2ea5a1; the branch is also rebased onto current master (no conflicts).

  • suggestion: fetch() now takes the raw $location node and evaluates it itself (location.as(evaluated(bindings)).as(url())), so the message prints the unevaluated value. I kept the single-parameter signature instead of fetch(evaluated, raw): evaluated() keeps the node's pointer, so the JsonValueExceptions on the malformed-URL and IOException paths still point at the same node.
    Pin added: shouldReportTheExpressionWhenLocationEvaluatesToNull ("$location": "${missing}" → $location value (${missing}) …). On the previous head it failed with /$location: $location value (null) cannot be null (or evaluated to null).
  • note: the new commit describes the actual behaviour, and the PR description (row fix ibm mq jms_text message processing #30 and the test list) is updated to match.

@vharseko
vharseko requested a review from maximthomas October 22, 2026 11:56

@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: Moving the $location evaluation into fetch() makes the #30 message report what the user configured, without changing anything else the exception shows.

  • fetch() evaluates the value itself (openig-core/src/main/java/org/forgerock/openig/util/ResolveLocationJsonValueFunction.java:104) and formats the raw location.getObject() (:106-108), so "${missing}" is reported as $location value (${missing}); shouldReportTheExpressionWhenLocationEvaluatesToNull pins it.
  • The exception's pointer is unchanged: ExpressionJsonTransformFunction.apply returns the evaluated value with the input's pointer (openig-core/src/main/java/org/forgerock/openig/util/ExpressionJsonTransformFunction.java:73), and JsonValueException.getMessage() renders only that pointer, so the IOException message at ResolveLocationJsonValueFunction.java:115 reads as before.

@vharseko
vharseko merged commit 9b7fc01 into OpenIdentityPlatform:master Oct 22, 2026
14 checks passed
@vharseko
vharseko deleted the fix-log-format-placeholders branch October 22, 2026 16:44
vharseko added a commit that referenced this pull request Oct 23, 2026
Closes the 16 `note`-level code scanning alerts that are worth fixing
(#10–#15, #32, #46–#53, #58). The remaining notes are either pure
`@Override`/`static` cosmetics (done separately in #180) or by-design
findings to be dismissed.

### `openig-oauth2` — `OAuth2Session`

| Alert | Change |
|---|---|
| #32 `missing-space-in-concatenation` | `"…ID token" + "could not be
decoded"` produced `ID tokencould not be decoded` in the `server_error`
description. |
| #53 `uncaught-number-format-exception` | A string `expires_in` that is
not a number now raises `OAuth2ErrorException(E_SERVER_ERROR,
"'expires_in' field value is not a number: …")`, consistent with the
sibling "neither a Number nor a String" branch, instead of a raw
`NumberFormatException`. |
| #58 `local-shadows-field` | The merged token response local is renamed
`mergedResponse`; its uses (`extractIdToken`, the constructor) are
pointed at it. No behavior change — `testStateRefreshed` covers the
merge semantics. |

### `openig-core`

| Alert | Change |
|---|---|
| #48, #49 `uncaught-number-format-exception` | `ICAPFilter`: a
`connect_timeout` / `read_timeout` that is not an `int` now fails with a
`JsonValueException` that points at the field (`/connect_timeout:
Expecting a number of milliseconds that fits in an int, got: soon`)
instead of `NumberFormatException: For input string: "soon"`. |
| #12 `deprecated-call` (`Object.finalize`) | `LdapClient.finalize()`
removed: the class is a singleton (`INSTANCE`), so the finalizer never
ran. |
| #11, #13 `deprecated-call` (`Class.newInstance`) | `Heaplets`,
`JsonValues`: `getDeclaredConstructor().newInstance()`. The
`InvocationTargetException` it adds is unwrapped so that an `Error`
thrown by the constructor still propagates as it did with
`Class.newInstance()`. Otherwise `JsonValues.asNewInstance` makes the
constructor's own exception the cause of a `JsonValueException` (other
reflective failures are caught as `ReflectiveOperationException`), and
`Heaplets.getHeaplet` logs and ignores it as before. **One difference:**
an `ExceptionInInitializerError` raised inside the constructor (by
touching a class whose static initializer fails) used to be wrapped by
`asNewInstance`'s `catch (ExceptionInInitializerError …)`; it now
propagates like any other `Error`, as it already did from
`Class.forName(name, true, …)` for the target class itself and from
`Heaplets.getHeaplet`. |
| #10 `deprecated-call` | `OpenApiMockResponseHandler`:
`responses.get(ApiResponses.DEFAULT)`. |
| #50 `uncaught-number-format-exception` | `Principal`: the
`org.openidentityplatform.openig.websocket.ttl` property is parsed once
at class load by `ttlMillis(String)`; an invalid value logs a warning
and falls back to 180 s instead of throwing on every websocket message.
`authorize()` checks the interval through `expired(now)`. **Semantic
change:** the property used to be re-read on every `authorize()` call,
i.e. it could be changed at runtime with `System.setProperty`. I assumed
that is not a supported use — say so if it is. |
| #46, #47 `uncaught-number-format-exception` | `MockDataGenerator`:
latitude/longitude generated with `FAKER.number().randomDouble(6, …)`
instead of parsing a locale-formatted string. |

### `openig-doc` — `SampleApplication`

| Alert | Change |
|---|---|
| #14, #15 `deprecated-call` | `validateConfiguration(true)` +
`createSSLContext()` → `createSSLContext(true)`, catching
`SSLContextConfigurator.GenericStoreException`, which grizzly-framework
3.0.1 (the version `openig-doc` resolves, through `opendj-grizzly`)
throws when the bundled key store cannot be loaded. The configurator is
created with `SSLContextConfigurator(false)`, so it no longer reads the
`javax.net.ssl.*` system properties: the sample sets its own key store
and passwords, and `createSSLContext(true)` only logs a missing key
store provider, which would have left the HTTPS listener without a key.
Exercised by the `openig-doc` integration tests, which start the sample
with HTTPS. |
| #51, #52 `uncaught-number-format-exception` | A non-numeric port
argument prints `Port numbers must be integers: …` plus the usage and
exits, instead of a stack trace. |

### Tests

- `OAuth2SessionTest`:
`shouldStateAuthorizedWithNonNumericExpiresInFailWithOAuth2Error`
(failed before with `NumberFormatException: For input string: "soon"`;
asserts the `server_error` code) and
`shouldStateAuthorizedWithUndecodableIdTokenFailWithReadableMessage`
(failed before with `ID tokencould`).
- `ICAPFilterTest` (new): configured and default (5000 / 15000 ms)
timeouts are applied; a non-numeric `connect_timeout` / `read_timeout`
(data provider) fails with a `JsonValueException` naming the field —
failed before with a bare `NumberFormatException`.
- `PrincipalTest` (new): `ttlMillis` — seconds to millis, trimming, and
the fallback for `null` / empty / non-numeric values;
`shouldReauthorizeWithTtlReadAtClassLoad` and
`shouldAuthorizeFromCacheWithTtlReadAtClassLoad` pin that the interval
is read once at class load, in `expired()` and through `authorize()`;
`shouldExpireWithoutCachedResult` pins that a missing result is always
refreshed.
- `JsonValuesTest`, `HeapletsTest`: an `Error` thrown by the constructor
propagates, including an `ExceptionInInitializerError` raised in it
(`asNewInstance`); an exception is wrapped in a `JsonValueException`
(`asNewInstance`) or ignored (`getHeaplet`).
- `MockDataGeneratorTest.generate_returnsCoordinatesInRange`,
`OpenApiMockResponseHandlerTest.bestResponseSchema_fallsBackToDefaultResponse`.

Each test added in the review rounds was run against the mutant it
targets (per-call TTL read in `expired()` and in `authorize()`,
`res==null` dropped from `expired()`, `Error` wrapped or swallowed,
`ExceptionInInitializerError` wrapped again, `E_INVALID_REQUEST`,
swapped ICAP defaults, latitude in ±180, `return null` for `default`):
all ten fail.

Verified, rebased on current `master`: `openig-core` 1029 tests,
`openig-oauth2` 160 and `openig-doc` 11 (integration tests,
`testGetLoginPageHttps` included) under `mvn verify`, javadoc with
`failOnWarnings` included.

#173, which also touches `Heaplets.java` (the `warn` line, left
untouched here) and `HeapletsTest.java`, has been merged; the branch is
rebased onto it, and `HeapletsTest` keeps the tests of both PRs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug java Pull requests that update Java code tests Adds or updates tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants