elharo opened a new issue, #1944:
URL: https://github.com/apache/maven-resolver/issues/1944
### Affected version
HEAD
### Bug description
# Bug Analysis: maven-resolver
Generated by code analysis of `/Users/elharo/maven-resolver`
(v2.0.21-SNAPSHOT).
---
## HIGH Severity
### 1. NPE — `getClassLoader().getResource()` returns null
**File:**
`maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java:265`
```java
String url = clazz.getClassLoader().getResource(className).toString();
```
`ClassLoader.getResource()` returns `null` when the resource is not found,
and `getClassLoader()` returns `null` for classes loaded by the bootstrap class
loader. Either condition causes an NPE on `.toString()`. The method
`getJarPath()` is called during client initialization to build the classpath
for forking a child process.
### 2. NPE — `receive()` reads volatile `input` without synchronization
**File:**
`maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java:288-311`
```java
void receive() {
try {
while (true) {
int id = input.readInt(); // NPE here if input null
```
`input` is a volatile field (line 86). `receive()` reads it without holding
the `IpcClient` monitor. Meanwhile, `close(Throwable)` (line 351,
`synchronized`) sets `input = null` (line 360). Between the volatile read at
line 291 and the `input.readInt()` call, another thread can call `close()`,
nulling `input`. The same pattern applies to `send()` at line 316 with `output`.
### 3. NPE — `getAddress()` can NPE on `socket.getLocalAddress()`
**File:**
`maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java:447-453`
```java
private String getAddress() {
try {
return SocketFamily.toString(socket.getLocalAddress());
} catch (IOException e) {
return "[not bound]";
}
}
```
`socket` is volatile (line 84) and set to `null` in `close()` (line 359). If
`close()` runs concurrently with `toString()` (which calls `getAddress()`),
`socket` is null and `socket.getLocalAddress()` throws NPE, which is **not** an
`IOException` and is thus uncaught.
### 4. Resource leak — `BufferedReader` never closed in
`parseMultiResource()`
**File:**
`maven-resolver-test-util/src/main/java/org/eclipse/aether/internal/test/util/DependencyGraphParser.java:161`
```java
BufferedReader reader = new BufferedReader(new
InputStreamReader(res.openStream(), StandardCharsets.UTF_8));
```
The `BufferedReader` (and its underlying `InputStream`) is **never closed**.
If `parse(reader)` throws an IOException, the stream leaks. Compare with the
correctly-implemented `parse(URL)` method at line 174-188 which uses
try-finally.
---
## MEDIUM Severity
### 5. Catching `Throwable` in non-framework code
**File:**
`maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/PutTaskRequestContent.java:246,
298`
```java
} catch (Throwable t) {
lockedSetTerminal(Content.Chunk.from(t, true));
}
```
Catches `OutOfMemoryError`, `StackOverflowError`, `InternalError`, etc.
These should propagate. Wrapping them as terminal chunks converts fatal JVM
errors into regular failure conditions, making them invisible to diagnostics.
### 6. Production error logging to `System.out`
**File:**
`maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcServer.java:196`
```java
private static void error(String msg, Throwable t) {
System.out.println("[ipc] [error] " + msg);
t.printStackTrace(System.out);
}
```
All errors in the IPC server go to stdout instead of a proper logging
framework (SLF4J). In a production Maven build, these messages are invisible or
interleaved with build output. Same issue for `debug()` (line 184) and `info()`
(line 190) in the same file.
### 7. Listener RuntimeException silently swallowed by default
**Files:**
-
`maven-resolver-util/src/main/java/org/eclipse/aether/util/listener/ChainedRepositoryListener.java:117`
-
`maven-resolver-util/src/main/java/org/eclipse/aether/util/listener/ChainedTransferListener.java:118`
```java
@SuppressWarnings("EmptyMethod")
protected void handleError(...) {
// default just swallows errors
}
```
All `RuntimeException`s thrown by any chained listener (NPEs,
`IndexOutOfBoundsException`, etc.) are silently swallowed unless the user
subclasses and overrides `handleError`. This makes listener bugs invisible.
### 8. Volatile fields with inconsistent synchronization
**File:**
`maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcClient.java:78-87`
Fields `initialized`, `socket`, `output`, `input`, `receiver` are `volatile`
but their compound use (read + method call) lacks synchronization. `send()`
reads `output` into a local variable at line 316 without holding the monitor,
then synchronizes on the local reference at line 323, but `close()` nulls
`output` under `synchronized(this)` at line 361. `receive()` has the same
pattern with `input`.
### 9. SyncContext double-close risk
**Files:**
-
`maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultArtifactResolver.java:189-190,
393-397, 430-432`
-
`maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultMetadataResolver.java:142-143,
302-373`
Two `SyncContext` instances (`shared` and `exclusive`) are created in
try-with-resources. Inside `resolve()`, `current` starts as `shared`, and when
switching to `exclusive` (line 393-396), `current.close()` is called, then
`current = exclusive`. The outer try-with-resources also closes both on exit
(line 202 for try-with-resources, and line 431 for the inner
`current.close()`). While `SyncContext.close()` is documented as idempotent,
any implementation that violates this contract would cause issues.
### 10. InterruptedException causes task to be silently dropped
**File:**
`maven-resolver-util/src/main/java/org/eclipse/aether/util/concurrency/SmartExecutor.java:177-179`
```java
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
```
In `Limited.submit(Runnable)` (line 155), if `semaphore.acquire()` throws
`InterruptedException`, the interrupt flag is restored but the caller's task is
never executed. The method simply returns. The `submit(Callable)` variant (line
183-213) correctly handles this by returning a failed `CompletableFuture`. The
`Runnable` overload should do something analogous (e.g., run the task inline or
throw).
---
## LOW Severity
### 11. Minor resource leak in `parseLiteral()`
**File:**
`maven-resolver-test-util/src/main/java/org/eclipse/aether/internal/test/util/DependencyGraphParser.java:134-138`
```java
BufferedReader reader = new BufferedReader(new
StringReader(dependencyGraph));
DependencyNode node = parse(reader);
reader.close(); // never reached if parse() throws
```
While `StringReader.close()` is a no-op, the pattern is inconsistent with
the rest of the codebase and represents a latent bug if the implementation
changes.
### 12. Empty catch swallows exceptions in `tryUnlock()`
**File:**
`maven-resolver-named-locks-ipc/src/main/java/org/eclipse/aether/named/ipc/IpcNamedLock.java:108-115`
```java
private void tryUnlock(String contextId) {
try {
client.unlock(contextId);
} catch (Exception e) {
// Best-effort cleanup
}
}
```
All exceptions from `unlock()` during timeout cleanup are silently
discarded. While documented as intentional, this can mask real IO/connectivity
issues.
### 13. Non-thread-safe access in synchronized `Results` class
**File:**
`maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DependencyCollectorDelegate.java:571-598`
`addException()` and `addCycle()` are `synchronized` on `Results`, but they
call `result.getExceptions()` and `result.getCycles()` on a `CollectResult`
that may not be thread-safe. If `CollectResult`'s internal collections are not
synchronized or safely published, concurrent modifications may not be visible.
### 14. `putIfAbsent()` fast-path race
**File:**
`maven-resolver-util/src/main/java/org/eclipse/aether/util/concurrency/ConcurrentWeakCache.java:124-146`
```java
V existing = get(key); // fast path
if (existing != null) {
return existing;
}
```
The fast-path `get()` uses a ThreadLocal `LookupKey`. Between `get()`
returning null and `merge()` at line 135, another thread can insert a new
value. The merge correctly handles this, but if `get()` returns a non-null
reference to a key that gets GC'd immediately after, we return a stale
reference. This is a correct-by-accident pattern — the merge would fix it, but
we return early.
### 15. Pre-Java-7 stream handling in test utilities
**Files:**
-
`maven-resolver-test-util/src/main/java/org/eclipse/aether/internal/test/util/TestFileUtils.java:172-325`
-
`maven-resolver-test-util/src/main/java/org/eclipse/aether/internal/test/util/TestFileProcessor.java:63-151`
Methods manually close streams in try-catch-finally blocks instead of using
try-with-resources. If an explicit `close()` call throws before the finally
block, open resources are leaked. Functionally correct in happy-path but
fragile.
### 16. `printStackTrace()` used instead of logging in tests
Multiple test files use `e.printStackTrace()` instead of proper assertions
or test framework logging, making test failures harder to diagnose. Affected
files include:
-
`maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultArtifactResolverTest.java:740,
752, 805, 874, 975, 987`
- `maven-resolver-demos/src/main/java/.../Booter.java:111`
-
`maven-resolver-api/src/test/java/org/eclipse/aether/DefaultSessionDataTest.java:118`
-
`maven-resolver-api/src/test/java/org/eclipse/aether/DefaultRepositoryCacheTest.java:82`
---
## Summary
| Severity | Count | Key Areas |
|----------|-------|-----------|
| HIGH | 4 | IpcClient (3 NPEs), DependencyGraphParser (resource
leak) |
| MEDIUM | 6 | PutTaskRequestContent (Throwable catch), IpcServer
(stdout logging), ChainedListener (silent swallow), IpcClient (sync),
SyncContext double-close, SmartExecutor (dropped task) |
| LOW | 6 | Minor resource leak, swallowed exception, thread safety
in Results, ConcurrentWeakCache race, old stream handling, printStackTrace |
The **IPC named-locks module** (`maven-resolver-named-locks-ipc`) accounts
for the majority of HIGH-severity issues — it has multiple NPEs from
unsynchronized volatile field access.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]