Skip to content

Commit 34ee662

Browse files
authored
fix(java_indexer): blame instance/static callsites on ctor/cinit methods (kythe#4734)
This matches the C++ indexer's existing behavior and better represents the actual callgraph. The cinit function is newly represented in this commit as a bare function node per class.
1 parent 019a30c commit 34ee662

File tree

6 files changed

+167
-28
lines changed

6 files changed

+167
-28
lines changed

kythe/java/com/google/devtools/kythe/analyzers/java/JavaEntrySets.java

+10
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,16 @@ public EntrySet newLambdaAndEmit(Positions filePositions, JCTree.JCLambda lambda
179179
.addSignatureSalt("" + filePositions.getSpan(lambda)));
180180
}
181181

182+
/** Emits and returns a new {@link EntrySet} representing a static class initializer. */
183+
public EntrySet newClassInitAndEmit(VName classNode) {
184+
return emitAndReturn(
185+
newNode(NodeKind.FUNCTION)
186+
.setCorpusPath(CorpusPath.fromVName(classNode))
187+
.addSignatureSalt(classNode)
188+
.addSignatureSalt("clinit")
189+
.build());
190+
}
191+
182192
/**
183193
* Emits and returns a new {@link EntrySet} representing a Java comment with an optional subkind.
184194
*/

kythe/java/com/google/devtools/kythe/analyzers/java/JavaNode.java

+23
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import com.google.common.collect.ImmutableList;
2020
import com.google.devtools.kythe.analyzers.base.EntrySet;
2121
import com.google.devtools.kythe.proto.Storage.VName;
22+
import java.util.List;
23+
import java.util.Optional;
2224

2325
/** Kythe graph node representing a Java language construct. */
2426
class JavaNode {
@@ -27,6 +29,9 @@ class JavaNode {
2729
private final VName vName;
2830
private JavaNode type;
2931

32+
private List<VName> classConstructors = ImmutableList.of();
33+
private Optional<VName> classInit = Optional.empty();
34+
3035
// I think order matters for the wildcards because the abs node will be connected to them with
3136
// param edges, which are numbered. If order doesn't matter, we should change this to something
3237
// like bazel's NestedSet.
@@ -65,4 +70,22 @@ JavaNode setType(JavaNode type) {
6570
JavaNode getType() {
6671
return type;
6772
}
73+
74+
JavaNode setClassConstructors(List<VName> constructors) {
75+
this.classConstructors = constructors;
76+
return this;
77+
}
78+
79+
List<VName> getClassConstructors() {
80+
return classConstructors;
81+
}
82+
83+
JavaNode setClassInit(VName classInit) {
84+
this.classInit = Optional.ofNullable(classInit);
85+
return this;
86+
}
87+
88+
Optional<VName> getClassInit() {
89+
return classInit;
90+
}
6891
}

kythe/java/com/google/devtools/kythe/analyzers/java/KytheTreeScanner.java

+70-17
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
import com.sun.tools.javac.tree.JCTree.JCAssert;
6565
import com.sun.tools.javac.tree.JCTree.JCAssign;
6666
import com.sun.tools.javac.tree.JCTree.JCAssignOp;
67+
import com.sun.tools.javac.tree.JCTree.JCBlock;
6768
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
6869
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
6970
import com.sun.tools.javac.tree.JCTree.JCExpression;
@@ -336,7 +337,7 @@ public JavaNode visitClassDef(JCClassDecl classDef, TreeContext owner) {
336337
// However we can't restrict ourselves to just classes contained in methods here,
337338
// because that would miss the case of local/anonymous classes in static/member
338339
// initializers. But there's no harm in emitting the same fact twice!
339-
getScope(ctx).ifPresent(scope -> entrySets.emitEdge(classNode, EdgeKind.CHILDOF, scope));
340+
getScope(ctx).forEach(scope -> entrySets.emitEdge(classNode, EdgeKind.CHILDOF, scope));
340341

341342
NestingKind nestingKind = classDef.sym.getNestingKind();
342343
if (nestingKind != NestingKind.LOCAL
@@ -428,16 +429,58 @@ public JavaNode visitClassDef(JCClassDecl classDef, TreeContext owner) {
428429
// directly in the class body (in static initializers or member initializers).
429430
JavaNode node = ctx.setNode(new JavaNode(classNode));
430431

432+
List<VName> constructors = new ArrayList<>();
431433
for (JCTree member : classDef.getMembers()) {
432-
JavaNode n = scan(member, ctx);
433-
if (n != null) {
434-
entrySets.emitEdge(n.getVName(), EdgeKind.CHILDOF, classNode);
434+
if (member instanceof JCMethodDecl) {
435+
JCMethodDecl method = (JCMethodDecl) member;
436+
if (!method.sym.isConstructor()) {
437+
continue;
438+
}
439+
440+
JavaNode n = scanChild(member, ctx, classNode);
441+
if (n != null) {
442+
constructors.add(n.getVName());
443+
}
444+
}
445+
}
446+
node.setClassConstructors(constructors);
447+
448+
// TODO(schroeder): flesh out cinit w/ MarkedSource
449+
node.setClassInit(entrySets.newClassInitAndEmit(classNode).getVName());
450+
entrySets.emitEdge(node.getClassInit().get(), EdgeKind.CHILDOF, classNode);
451+
452+
for (JCTree member : classDef.getMembers()) {
453+
if (member instanceof JCMethodDecl) {
454+
JCMethodDecl method = (JCMethodDecl) member;
455+
if (method.sym.isConstructor()) {
456+
// Already handled above.
457+
continue;
458+
}
435459
}
460+
scanChild(member, ctx, classNode);
436461
}
437462

438463
return node;
439464
}
440465

466+
private JavaNode scanChild(JCTree child, TreeContext owner, VName parent) {
467+
JavaNode n = scan(child, owner);
468+
if (n != null) {
469+
entrySets.emitEdge(n.getVName(), EdgeKind.CHILDOF, parent);
470+
}
471+
return n;
472+
}
473+
474+
@Override
475+
public JavaNode visitBlock(JCBlock block, TreeContext owner) {
476+
TreeContext ctx = owner;
477+
if (block.isStatic() && owner.getNode().getClassInit().isPresent()) {
478+
ctx = owner.down(block);
479+
ctx.setNode(new JavaNode(owner.getNode().getClassInit().get()));
480+
}
481+
return scan(block.getStatements(), ctx);
482+
}
483+
441484
@Override
442485
public JavaNode visitMethodDef(JCMethodDecl methodDef, TreeContext owner) {
443486
TreeContext ctx = owner.down(methodDef);
@@ -667,7 +710,7 @@ public JavaNode visitVarDef(JCVariableDecl varDef, TreeContext owner) {
667710
entrySets.emitEdge(varNode, EdgeKind.NAMED, jvmNode);
668711
}
669712

670-
getScope(ctx).ifPresent(scope -> entrySets.emitEdge(varNode, EdgeKind.CHILDOF, scope));
713+
getScope(ctx).forEach(scope -> entrySets.emitEdge(varNode, EdgeKind.CHILDOF, scope));
671714
visitAnnotations(varNode, varDef.getModifiers().getAnnotations(), ctx);
672715

673716
if (varDef.getModifiers().getFlags().contains(Modifier.STATIC)) {
@@ -848,7 +891,7 @@ public JavaNode visitNewClass(JCNewClass newClass, TreeContext owner) {
848891
emitAnchor(anchor, EdgeKind.REF, ctorNode, getScope(ctx));
849892

850893
EntrySet callAnchor = entrySets.newAnchorAndEmit(filePositions, callSpan, ctx.getSnippet());
851-
emitAnchor(callAnchor, EdgeKind.REF_CALL, ctorNode, getScope(ctx));
894+
emitAnchor(callAnchor, EdgeKind.REF_CALL, ctorNode, getCallScope(ctx));
852895

853896
scanList(newClass.getTypeArguments(), ctx);
854897
scanList(newClass.getArguments(), ctx);
@@ -1002,7 +1045,7 @@ void emitDocReference(Symbol sym, int startChar, int endChar) {
10021045
filePositions.charToByteOffset(startChar), filePositions.charToByteOffset(endChar));
10031046
EntrySet anchor = entrySets.newAnchorAndEmit(filePositions, loc);
10041047
if (anchor != null) {
1005-
emitAnchor(anchor, EdgeKind.REF_DOC, node, Optional.empty());
1048+
emitAnchor(anchor, EdgeKind.REF_DOC, node, ImmutableList.of());
10061049
}
10071050
}
10081051

@@ -1180,16 +1223,27 @@ private JavaNode emitNameUsage(TreeContext ctx, Symbol sym, Name name, EdgeKind
11801223
edgeKind,
11811224
node.getVName(),
11821225
ctx.getSnippet(),
1183-
getScope(ctx));
1226+
edgeKind == EdgeKind.REF_CALL ? getCallScope(ctx) : getScope(ctx));
11841227
statistics.incrementCounter("name-usages-emitted");
11851228
}
11861229
return node;
11871230
}
11881231

1189-
private static Optional<VName> getScope(TreeContext ctx) {
1190-
return Optional.ofNullable(ctx.getClassOrMethodParent())
1232+
private static List<VName> getCallScope(TreeContext ctx) {
1233+
TreeContext parent = ctx.getScope();
1234+
if (parent.getTree() instanceof JCClassDecl) {
1235+
// Special-case callsites in non-static initializer blocks to scope to all constructors.
1236+
return parent.getNode().getClassConstructors();
1237+
}
1238+
return getScope(ctx);
1239+
}
1240+
1241+
private static List<VName> getScope(TreeContext ctx) {
1242+
return Optional.ofNullable(ctx.getScope())
11911243
.map(TreeContext::getNode)
1192-
.map(JavaNode::getVName);
1244+
.map(JavaNode::getVName)
1245+
.map(ImmutableList::of)
1246+
.orElse(ImmutableList.of());
11931247
}
11941248

11951249
// Returns the reference node for the given symbol.
@@ -1264,12 +1318,12 @@ private EntrySet emitAnchor(TreeContext anchorContext, EdgeKind kind, VName node
12641318
filePositions, anchorContext.getTreeSpan(), anchorContext.getSnippet()),
12651319
kind,
12661320
node,
1267-
getScope(anchorContext));
1321+
kind == EdgeKind.REF_CALL ? getCallScope(anchorContext) : getScope(anchorContext));
12681322
}
12691323

12701324
// Creates/emits an anchor (for an identifier) and an associated edge
12711325
private EntrySet emitAnchor(
1272-
Name name, int startOffset, EdgeKind kind, VName node, Span snippet, Optional<VName> scope) {
1326+
Name name, int startOffset, EdgeKind kind, VName node, Span snippet, List<VName> scope) {
12731327
EntrySet anchor = entrySets.newAnchorAndEmit(filePositions, name, startOffset, snippet);
12741328
if (anchor == null) {
12751329
// TODO(schroederc): Special-case these anchors (most come from visitSelect)
@@ -1304,22 +1358,21 @@ private EntrySet emitDefinesBindingAnchorEdge(
13041358
return anchor;
13051359
}
13061360

1307-
private void emitDefinesBindingEdge(
1308-
Span span, EntrySet anchor, VName node, Optional<VName> scope) {
1361+
private void emitDefinesBindingEdge(Span span, EntrySet anchor, VName node, List<VName> scope) {
13091362
emitMetadata(span, node);
13101363
emitAnchor(anchor, EdgeKind.DEFINES_BINDING, node, scope);
13111364
}
13121365

13131366
// Creates/emits an anchor and an associated edge
1314-
private EntrySet emitAnchor(EntrySet anchor, EdgeKind kind, VName node, Optional<VName> scope) {
1367+
private EntrySet emitAnchor(EntrySet anchor, EdgeKind kind, VName node, List<VName> scope) {
13151368
Preconditions.checkArgument(
13161369
kind.isAnchorEdge(), "EdgeKind was not intended for ANCHORs: %s", kind);
13171370
if (anchor == null) {
13181371
return null;
13191372
}
13201373
entrySets.emitEdge(anchor.getVName(), kind, node);
13211374
if (kind == EdgeKind.REF_CALL || config.getEmitAnchorScopes()) {
1322-
scope.ifPresent(s -> entrySets.emitEdge(anchor.getVName(), EdgeKind.CHILDOF, s));
1375+
scope.forEach(s -> entrySets.emitEdge(anchor.getVName(), EdgeKind.CHILDOF, s));
13231376
}
13241377
return anchor;
13251378
}

kythe/java/com/google/devtools/kythe/analyzers/java/TreeContext.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.google.devtools.kythe.analyzers.java.SourceText.Positions;
2121
import com.google.devtools.kythe.util.Span;
2222
import com.sun.tools.javac.tree.JCTree;
23+
import com.sun.tools.javac.tree.JCTree.JCBlock;
2324
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
2425
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
2526
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
@@ -92,10 +93,12 @@ public TreeContext getMethodParent() {
9293
return parent;
9394
}
9495

95-
public TreeContext getClassOrMethodParent() {
96+
public TreeContext getScope() {
9697
TreeContext parent = up();
9798
while (parent != null
98-
&& !(parent.getTree() instanceof JCMethodDecl || parent.getTree() instanceof JCClassDecl)) {
99+
&& !(parent.getTree() instanceof JCMethodDecl
100+
|| parent.getTree() instanceof JCClassDecl
101+
|| parent.getTree() instanceof JCBlock)) {
99102
parent = parent.up();
100103
}
101104
return parent;

kythe/javatests/com/google/devtools/kythe/analyzers/java/testdata/pkg/Callgraph.java

+54-7
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,53 @@
33
//- @Callgraph defines/binding Class
44
public class Callgraph {
55

6+
// Implicit static class initializer
7+
//- ClassInit.node/kind function
8+
//- ClassInit childof Class
9+
//- !{ ClassInit.subkind "constructor" }
10+
611
//- StaticGCall.loc/start @^"g()"
712
//- StaticGCall.loc/end @$"g()"
813
//- StaticGCall ref/call G
9-
//- StaticGCall childof Class
14+
//- StaticGCall childof ECtor
15+
//- StaticGCall childof SCtor
16+
//- !{ StaticGCall childof Class }
17+
//- !{ StaticGCall childof ClassInit }
1018
final int ZERO = g();
1119

1220
static {
1321
//- StaticBlockGCall.loc/start @^"g()"
1422
//- StaticBlockGCall.loc/end @$"g()"
1523
//- StaticBlockGCall ref/call G
16-
//- StaticBlockGCall childof Class
24+
//- StaticBlockGCall childof ClassInit
25+
//- !{ StaticBlockGCall childof ECtor }
26+
//- !{ StaticBlockGCall childof SCtor }
1727
int zero = g();
28+
29+
{
30+
//- NestedStaticBlockGCall.loc/start @^"g()"
31+
//- NestedStaticBlockGCall.loc/end @$"g()"
32+
//- NestedStaticBlockGCall ref/call G
33+
//- NestedStaticBlockGCall childof ClassInit
34+
//- NestedStaticBlockGCall childof ClassInit
35+
g();
36+
}
1837
}
1938

2039
{
2140
//- BlockGCall.loc/start @^"g()"
2241
//- BlockGCall.loc/end @$"g()"
2342
//- BlockGCall ref/call G
24-
//- BlockGCall childof Class
43+
//- BlockGCall childof ECtor
44+
//- BlockGCall childof SCtor
2545
int zero = g();
2646
}
2747

28-
//- StaticCtorCall.loc/start @^"new Callgraph()"
29-
//- StaticCtorCall.loc/end @$"new Callgraph()"
30-
//- StaticCtorCall ref/call ECtor
31-
//- StaticCtorCall childof Class
48+
//- CtorCall.loc/start @^"new Callgraph()"
49+
//- CtorCall.loc/end @$"new Callgraph()"
50+
//- CtorCall ref/call ECtor
51+
//- CtorCall childof ECtor
52+
//- CtorCall childof SCtor
3253
final Callgraph INSTANCE = new Callgraph();
3354

3455
//- @Callgraph defines/binding ECtor
@@ -72,4 +93,30 @@ static int g() {
7293
//- CallAnchor childof G
7394
return 0;
7495
}
96+
97+
//- @Nested defines/binding NestedClass
98+
static class Nested {
99+
//- NestedInit.node/kind function
100+
//- NestedInit childof NestedClass
101+
//- !{ NestedInit.subkind "constructor" }
102+
103+
//- ImplicitConstructor.node/kind function
104+
//- ImplicitConstructor.subkind "constructor"
105+
//- ImplicitConstructor childof NestedClass
106+
107+
//- NestedClassCall.loc/start @^"g()"
108+
//- NestedClassCall.loc/end @$"g()"
109+
//- NestedClassCall childof ImplicitConstructor
110+
final int INT = g();
111+
112+
static {
113+
//- NestedClassStaticCall.loc/start @^"g()"
114+
//- NestedClassStaticCall.loc/end @$"g()"
115+
//- NestedClassStaticCall childof NestedInit
116+
g();
117+
}
118+
119+
//- !{ NestedClassCall childof NestedInit
120+
//- NestedClassStaticCall childof ImplicitConstructor }
121+
}
75122
}

kythe/javatests/com/google/devtools/kythe/analyzers/java/testdata/pkg/Classes.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,13 @@ private void localFunc() {
7777
class LocalClass {};
7878
}
7979

80-
80+
//- ClassInit.node/kind function
81+
//- ClassInit childof N
82+
// !{ ClassInit.subkind "constructor" }
83+
8184
static {
8285
//- @LocalClassInStaticInitializer defines/binding LCISI
83-
//- LCISI childof N
86+
//- LCISI childof ClassInit
8487
class LocalClassInStaticInitializer {};
8588
}
8689
}

0 commit comments

Comments
 (0)