redhat-et / redhat-et/ripwire

Java: a method reference (Type::method) is not a call site for --uses/--callers

Open
#74 3 comments 0 reactions 0 assignees View on GitHub
help wanted
Dominant language
C++
Stars
2.1k
Forks
125
Avg merge
5h 28m
Merged PRs (30d)
163

Description

### What happens

Two functionally identical Java methods, one written with a lambda and one with a method reference:

```bash
mkdir rw-mr && cd rw-mr
```

```java
cat > Util.java <<'EOF'
public class Util {
public static String conv(Object o) { return String.valueOf(o); }
}
EOF
```

```java
cat > A.java <<'EOF'
import java.util.List;

public class A {
public List lambdaForm(List in) {
return in.stream().map(item -> Util.conv(item)).toList();
}

public List methodRefForm(List in) {
return in.stream().map(Util::conv).toList();
}
}
EOF
```

```bash
ripwire . --no-cache --uses=conv --format=rows
ripwire . --no-cache --callers=conv --json
```

--uses finds one call site out of two — only the lambda, at line 5:

```xml

0=A.java

05calllambdaForm

```

--callers likewise lists only lambdaForm; methodRefForm does not appear:

```json
{"of":"conv","defs":1,"count":1,"root":".","hop_tested":0,"hop_untested":1,
"graph_ambiguous":0,"graph_unresolved":0,"counts_floor":true,
"callers":[{"t":"method","n":"lambdaForm","p":"A.java:4"}]}
```

Environment: ripwire 0.5.0 (Release, GNU 14.2.1, built_from=unknown), Ubuntu 24.04.4 LTS x86_64, each arm in a fresh directory with --no-cache.

### Why I'm reporting it rather than reading it as the documented callback caveat

--callers' legend lists what name-based extraction cannot see: dynamic dispatch, and callbacks bound to more than one function in scope. Util::conv is neither. The receiver is a type name and the member is a literal identifier in the source text, so the target is fixed at compile time — as fixed as the Util.conv(item) three lines above it, which does produce an edge. Delete conv and both forms stop compiling; only one of the two is visible to ripwire.

I'd draw the line at the type receiver: instance::method on an expression, or a reference passed around and bound elsewhere, is a genuine callback and I'd expect it to stay unresolved. It's the Type::method form that reads as a real, statically known dependency.

### Why it matters in practice

The method reference is the preferred spelling in Java generally — Effective Java item 43 says to prefer it over the equivalent lambda, and both IntelliJ ("Lambda can be replaced with method reference") and Sonar (java:S1612) flag .map(x -> Foo.bar(x)) and suggest rewriting it as .map(Foo::bar). On the codebase where I hit this, one utility method had four call sites and two of them were method references: --uses reported half the truth. The more consistently a codebase follows the idiom, the thinner its call graph gets, and since these are missing edges rather than skipped files, nothing in the output marks the gap — counts_floor="1" covers it formally, but the reader has no way to tell this floor from a complete answer.

Contributor guide

Open the contributing guide

Research direction

Reproduce the discrepancy with Util.java and A.java using the --uses=conv and --callers=conv commands shown in the issue. Trace the Java call-site extraction entry point for Type::method references and compare it with the lambda result. Done means both the lambda and Util::conv method reference appear in the reported uses and callers.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, java
Domain
devtools
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
70/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.