feat(java_indexer): emit refs to JVM graph for externally defined nodes (#3800)
Experimental feature to support cross-language references for JVM-based
languages. Currently, the indexer will be highly conservative when
deciding whether to emit references to the JVM language vs Java-native
nodes. Only nodes within the same source file as the enclosing class
and JDK symbols will be considered Java-native when
--emit_jvm_references is enabled.
See: https://github.com/kythe/kythe/blob/master/kythe/java/com/google/devtools/kythe/analyzers/jvm/DESIGN.md#relations-with-higher-level-jvm-languages
diff --git a/kythe/java/com/google/devtools/kythe/analyzers/java/JavaEntrySets.java b/kythe/java/com/google/devtools/kythe/analyzers/java/JavaEntrySets.java
index 7310c63..8594935 100644
--- a/kythe/java/com/google/devtools/kythe/analyzers/java/JavaEntrySets.java
+++ b/kythe/java/com/google/devtools/kythe/analyzers/java/JavaEntrySets.java
@@ -345,7 +345,7 @@
return sourceFile.toUri().getHost();
}
- private static boolean fromJDK(@Nullable Symbol sym) {
+ static boolean fromJDK(@Nullable Symbol sym) {
if (sym == null || sym.enclClass() == null) {
return false;
}
diff --git a/kythe/java/com/google/devtools/kythe/analyzers/java/JavaIndexerConfig.java b/kythe/java/com/google/devtools/kythe/analyzers/java/JavaIndexerConfig.java
index c4ad586..06cfebb 100644
--- a/kythe/java/com/google/devtools/kythe/analyzers/java/JavaIndexerConfig.java
+++ b/kythe/java/com/google/devtools/kythe/analyzers/java/JavaIndexerConfig.java
@@ -60,6 +60,13 @@
private JvmMode jvmMode = JvmMode.SEMANTIC;
@Parameter(
+ names = "--emit_jvm_references",
+ description =
+ "Whether to reference the JVM graph when encountering nodes from outside the analyzed"
+ + " compilation unit")
+ private boolean jvmReferences = false;
+
+ @Parameter(
names = "--emit_anchor_scopes",
description =
"Whether to emit childof edges from anchors to their lexical scope's semantic node")
@@ -94,6 +101,10 @@
return jvmMode;
}
+ public boolean getEmitJvmReferences() {
+ return jvmReferences;
+ }
+
public boolean getEmitAnchorScopes() {
return emitAnchorScopes;
}
@@ -118,6 +129,11 @@
return this;
}
+ public JavaIndexerConfig setEmitJvmReferences(boolean jvmReferences) {
+ this.jvmReferences = jvmReferences;
+ return this;
+ }
+
public JavaIndexerConfig setEmitAnchorScopes(boolean emitAnchorScopes) {
this.emitAnchorScopes = emitAnchorScopes;
return this;
diff --git a/kythe/java/com/google/devtools/kythe/analyzers/java/KytheTreeScanner.java b/kythe/java/com/google/devtools/kythe/analyzers/java/KytheTreeScanner.java
index 2d2169c..67b9502 100644
--- a/kythe/java/com/google/devtools/kythe/analyzers/java/KytheTreeScanner.java
+++ b/kythe/java/com/google/devtools/kythe/analyzers/java/KytheTreeScanner.java
@@ -992,12 +992,38 @@
/** Returns the {@link JavaNode} associated with a {@link Symbol} or {@code null}. */
private JavaNode getJavaNode(Symbol sym) {
+ if (jvmGraph != null && config.getEmitJvmReferences() && isExternal(sym)) {
+ // Symbol is external to the analyzed compilation and may not be defined in Java. Return the
+ // related JVM node to accomodate cross-language references.
+ Type type = externalType(sym);
+ if (type instanceof Type.MethodType) {
+ JvmGraph.Type.MethodType methodJvmType = toMethodJvmType((Type.MethodType) type);
+ ReferenceType parentClass = referenceType(externalType(sym.enclClass()));
+ String methodName = sym.getQualifiedName().toString();
+ return new JavaNode(jvmGraph.getMethodVName(parentClass, methodName, methodJvmType));
+ } else if (type instanceof Type.ClassType) {
+ return new JavaNode(jvmGraph.getReferenceVName(referenceType(sym.type)));
+ } else if (sym instanceof Symbol.VarSymbol
+ && ((Symbol.VarSymbol) sym).getKind() == ElementKind.FIELD) {
+ ReferenceType parentClass = referenceType(externalType(sym.enclClass()));
+ String fieldName = sym.getSimpleName().toString();
+ return new JavaNode(jvmGraph.getFieldVName(parentClass, fieldName));
+ }
+ }
+
return signatureGenerator
.getSignature(sym)
.map(sig -> new JavaNode(entrySets.getNode(signatureGenerator, sym, sig, null)))
.orElse(null);
}
+ private boolean isExternal(Symbol sym) {
+ // TODO(schroederc): check if Symbol comes from any source file in compilation
+ // TODO(schroederc): research other methods to hueristically determine if a Symbol is defined in
+ // a Java compilation (vs. some other JVM language)
+ return sym.enclClass().sourcefile != filePositions.getSourceFile() && !entrySets.fromJDK(sym);
+ }
+
private void visitAnnotations(
VName owner, List<JCAnnotation> annotations, TreeContext ownerContext) {
for (JCAnnotation annotation : annotations) {
diff --git a/kythe/java/com/google/devtools/kythe/common/BUILD b/kythe/java/com/google/devtools/kythe/common/BUILD
index 31d9628..e4925d5 100644
--- a/kythe/java/com/google/devtools/kythe/common/BUILD
+++ b/kythe/java/com/google/devtools/kythe/common/BUILD
@@ -18,7 +18,10 @@
java_library(
name = "autovalue",
exported_plugins = [":auto_plugin"],
- exports = ["@com_google_auto_value_auto_value//jar"],
+ exports = [
+ "@com_google_auto_value_auto_value//jar",
+ "@javax_annotation_jsr250_api//jar",
+ ],
)
java_plugin(
@@ -34,5 +37,8 @@
java_library(
name = "autoservice",
exported_plugins = [":auto_service_plugin"],
- exports = ["@com_google_auto_service_auto_service//jar"],
+ exports = [
+ "@com_google_auto_service_auto_service//jar",
+ "@javax_annotation_jsr250_api//jar",
+ ],
)
diff --git a/kythe/javatests/com/google/devtools/kythe/analyzers/java/testdata/pkg/BUILD b/kythe/javatests/com/google/devtools/kythe/analyzers/java/testdata/pkg/BUILD
index 9d2235b..e6dd6af 100644
--- a/kythe/javatests/com/google/devtools/kythe/analyzers/java/testdata/pkg/BUILD
+++ b/kythe/javatests/com/google/devtools/kythe/analyzers/java/testdata/pkg/BUILD
@@ -323,7 +323,6 @@
size = "small",
srcs = ["Jvm.java"],
extra_goals = ["//kythe/javatests/com/google/devtools/kythe/analyzers/jvm/testdata:jvm_nodes.kythe_verifier.txt"],
- indexer_opts = ["--emit_jvm=semantic"],
verifier_opts = [
"--ignore_dups",
"--convert_marked_source",
@@ -333,6 +332,14 @@
)
java_verifier_test(
+ name = "jvm_reference_tests",
+ size = "small",
+ srcs = ["JvmCrossFile.java"],
+ indexer_opts = ["--emit_jvm_references"],
+ verifier_deps = [":files_tests"],
+)
+
+java_verifier_test(
name = "release9_test",
size = "small",
srcs = [
diff --git a/kythe/javatests/com/google/devtools/kythe/analyzers/java/testdata/pkg/JvmCrossFile.java b/kythe/javatests/com/google/devtools/kythe/analyzers/java/testdata/pkg/JvmCrossFile.java
new file mode 100644
index 0000000..5fe3296
--- /dev/null
+++ b/kythe/javatests/com/google/devtools/kythe/analyzers/java/testdata/pkg/JvmCrossFile.java
@@ -0,0 +1,49 @@
+package pkg;
+
+// Check that nodes unify across file/compilation boundaries
+
+//- @CONSTANT ref/imports JvmConstantMember
+import static pkg.Files.CONSTANT;
+//- @Inner ref/imports JvmInnerClass
+import static pkg.Files.Inner;
+//- @staticMethod ref/imports JvmStaticMethod
+import static pkg.Files.staticMethod;
+
+import pkg.Files.Inter;
+import pkg.Files.OtherDecl;
+
+//- ConstantMember generates JvmConstantMember
+//- FilesClass generates JvmFilesClass
+//- InnerClass generates JvmInnerClass
+//- Inter generates JvmInter
+//- ODecl generates JvmODecl
+//- StaticMethod generates JvmStaticMethod
+
+public class JvmCrossFile {
+ //- @Files ref JvmFilesClass
+ Files f1;
+
+ //- @Files ref JvmFilesClass
+ //- @Inner ref JvmInnerClass
+ Files.Inner f2;
+
+ //- @OtherDecl ref JvmODecl
+ OtherDecl f3;
+
+ //- @Inter ref JvmInter
+ Inter i;
+
+ //- @InterSub defines/binding InterSub
+ //- InterSub.node/kind interface
+ //- InterSub extends JvmInter
+ interface InterSub extends Inter {}
+
+ public static void main(String[] args) {
+ //- @staticMethod ref JvmStaticMethod
+ Files.staticMethod();
+ //- @staticMethod ref JvmStaticMethod
+ staticMethod();
+ //- @CONSTANT ref JvmConstantMember
+ System.out.println(Files.CONSTANT);
+ }
+}