blob: d2927c0011b7835b2d9a9d18dd98a9c44df846dc [file] [log] [blame]
/*
* Copyright 2014 The Kythe Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devtools.kythe.analyzers.java;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;
import com.google.common.flogger.FluentLogger;
import com.google.devtools.kythe.analyzers.base.CorpusPath;
import com.google.devtools.kythe.analyzers.base.EdgeKind;
import com.google.devtools.kythe.analyzers.base.EntrySet;
import com.google.devtools.kythe.analyzers.base.FactEmitter;
import com.google.devtools.kythe.analyzers.base.KytheEntrySets;
import com.google.devtools.kythe.analyzers.base.NodeKind;
import com.google.devtools.kythe.analyzers.java.SourceText.Positions;
import com.google.devtools.kythe.platform.java.helpers.SignatureGenerator;
import com.google.devtools.kythe.platform.shared.StatisticsCollector;
import com.google.devtools.kythe.proto.Analysis.CompilationUnit.FileInput;
import com.google.devtools.kythe.proto.Diagnostic;
import com.google.devtools.kythe.proto.MarkedSource;
import com.google.devtools.kythe.proto.Storage.VName;
import com.google.devtools.kythe.util.Span;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Symbol.PackageSymbol;
import com.sun.tools.javac.tree.JCTree;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Name;
import javax.tools.JavaFileObject;
/** Specialization of {@link KytheEntrySets} for Java. */
public class JavaEntrySets extends KytheEntrySets {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final Map<Symbol, VName> symbolNodes = new HashMap<>();
private final Map<Symbol, Integer> symbolHashes = new HashMap<>();
private final boolean ignoreVNamePaths;
private final boolean ignoreVNameRoots;
private final String overrideJdkCorpus;
private final Map<String, Integer> sourceToWildcardCounter = new HashMap<>();
public JavaEntrySets(
StatisticsCollector statistics,
FactEmitter emitter,
VName compilationVName,
List<FileInput> requiredInputs,
boolean ignoreVNamePaths,
boolean ignoreVNameRoots,
String overrideJdkCorpus) {
super(statistics, emitter, compilationVName, requiredInputs);
this.ignoreVNamePaths = ignoreVNamePaths;
this.ignoreVNameRoots = ignoreVNameRoots;
this.overrideJdkCorpus = overrideJdkCorpus;
}
/**
* Returns a node for the given {@link Symbol} and its signature. A new node is created and
* emitted if necessary. If non-null, msBuilder will be used to generate a signature.
*/
@Deprecated
public VName getNode(
final SignatureGenerator signatureGenerator,
Symbol sym,
String signature,
// TODO(schroederc): separate MarkedSource generation from JavaEntrySets
@Nullable MarkedSource.Builder msBuilder,
@Nullable Iterable<MarkedSource> postChildren) {
return getNode(
signatureGenerator,
sym,
signature,
MarkedSources.construct(
signatureGenerator,
sym,
msBuilder,
postChildren,
// TODO(schroederc): do not construct entire node to just determine its VName
s ->
signatureGenerator
.getSignature(s)
.map(sig -> getNode(signatureGenerator, s, sig, null, null))));
}
Map<Symbol, VName> getSymbolNodes() {
return Collections.unmodifiableMap(symbolNodes);
}
/**
* Returns a node for the given {@link Symbol} and its signature. A new node is created and
* emitted if necessary.
*/
public VName getNode(
SignatureGenerator signatureGenerator,
Symbol sym,
String signature,
MarkedSource markedSource) {
EntrySet node;
if (symbolNodes.containsKey(sym)) {
return symbolNodes.get(sym);
}
ClassSymbol enclClass = sym.enclClass();
VName v = lookupVName(enclClass);
if ((v == null || overrideJdkCorpus != null) && fromJDK(sym)) {
v =
VName.newBuilder()
.setCorpus(overrideJdkCorpus != null ? overrideJdkCorpus : "jdk")
.build();
}
if (v == null) {
getStatisticsCollector().incrementCounter("unextracted-input-file");
String msg =
String.format(
"Couldn't generate vname for symbol %s. Input file for enclosing class %s not seen"
+ " during extraction.",
sym, enclClass);
logger.atWarning().log(msg);
Diagnostic.Builder d = Diagnostic.newBuilder().setMessage(msg);
return emitDiagnostic(d.build()).getVName();
} else {
if (ignoreVNamePaths) {
v = v.toBuilder().setPath(enclClass != null ? enclClass.toString() : "").build();
}
if (ignoreVNameRoots) {
v = v.toBuilder().clearRoot().build();
}
NodeKind kind = elementNodeKind(sym.getKind());
NodeBuilder builder = kind != null ? newNode(kind) : newNode(sym.getKind().toString());
builder.setCorpusPath(CorpusPath.fromVName(v)).setProperty("code", markedSource);
if (signatureGenerator.getUseJvmSignatures()) {
builder.setSignature(signature);
} else {
builder.addSignatureSalt(signature).addSignatureSalt("" + hashSymbol(sym));
}
node = builder.build();
node.emit(getEmitter());
}
symbolNodes.put(sym, node.getVName());
return node.getVName();
}
/** Emits and returns a new {@link EntrySet} representing a lambda object. */
public EntrySet newLambdaAndEmit(Positions filePositions, JCTree.JCLambda lambda) {
VName fileVName = getFileVName(getDigest(filePositions.getSourceFile()));
return emitAndReturn(
newNode(NodeKind.FUNCTION)
.setCorpusPath(CorpusPath.fromVName(fileVName))
.addSignatureSalt("" + filePositions.getSpan(lambda)));
}
/**
* Emits and returns a new {@link EntrySet} representing a Java comment with an optional subkind.
*/
public EntrySet newDocAndEmit(
Optional<String> subkind, Positions filePositions, String text, Iterable<VName> params) {
VName fileVName = getFileVName(getDigest(filePositions.getSourceFile()));
NodeBuilder builder =
newNode(NodeKind.DOC.getKind(), subkind)
.setCorpusPath(CorpusPath.fromVName(fileVName))
.setProperty("text", text.getBytes(UTF_8))
.addSignatureSalt(text);
params.forEach(builder::addSignatureSalt);
EntrySet node = emitAndReturn(builder);
emitOrdinalEdges(node.getVName(), EdgeKind.PARAM, params);
return node;
}
/** Emits and returns a new {@link EntrySet} representing the Java file. */
public EntrySet newFileNodeAndEmit(Positions file) {
return newFileNodeAndEmit(getDigest(file.getSourceFile()), file.getData(), file.getEncoding());
}
/** Emits and returns a new {@link EntrySet} representing a Java package. */
public EntrySet newPackageNodeAndEmit(PackageSymbol sym) {
return newPackageNodeAndEmit(sym.getQualifiedName().toString());
}
/** Emits and returns a new {@link EntrySet} representing a Java package. */
public EntrySet newPackageNodeAndEmit(String name) {
EntrySet node =
emitAndReturn(
newNode(NodeKind.PACKAGE)
.addSignatureSalt(name)
.setProperty(
"code",
MarkedSource.newBuilder()
.setPreText(name)
.setKind(MarkedSource.Kind.IDENTIFIER)
.build()));
return node;
}
/** Emits and returns a new {@link EntrySet} for the given wildcard. */
public EntrySet newWildcardNodeAndEmit(JCTree.JCWildcard wild, String sourcePath) {
int counter = sourceToWildcardCounter.getOrDefault(sourcePath, 0);
sourceToWildcardCounter.put(sourcePath, counter + 1);
return emitAndReturn(newNode(NodeKind.ABS_VAR).addSignatureSalt(sourcePath + counter));
}
/** Returns and emits a Java anchor for the given offset span. */
public EntrySet newAnchorAndEmit(Positions filePositions, Span loc) {
return newAnchorAndEmit(filePositions, loc, null);
}
/** Returns and emits a Java anchor for the given offset span. */
public EntrySet newAnchorAndEmit(Positions filePositions, Span loc, Span snippet) {
return newAnchorAndEmit(getFileVName(getDigest(filePositions.getSourceFile())), loc, snippet);
}
/** Returns and emits a Java anchor for the given identifier. */
public EntrySet newAnchorAndEmit(
Positions filePositions, Name name, int startOffset, Span snippet) {
Span span = filePositions.findIdentifier(name, startOffset);
return span == null
? null
: newAnchorAndEmit(getFileVName(getDigest(filePositions.getSourceFile())), span, snippet);
}
/** Emits and returns a DIAGNOSTIC node attached to the given file. */
public EntrySet emitDiagnostic(Positions filePositions, Diagnostic d) {
return emitDiagnostic(getFileVName(getDigest(filePositions.getSourceFile())), d);
}
/** Returns the equivalent {@link NodeKind} for the given {@link ElementKind}. */
@Nullable
private static NodeKind elementNodeKind(ElementKind kind) {
switch (kind) {
case CLASS:
return NodeKind.RECORD_CLASS;
case ENUM:
return NodeKind.SUM_ENUM_CLASS;
case ENUM_CONSTANT:
return NodeKind.CONSTANT;
case ANNOTATION_TYPE:
case INTERFACE:
return NodeKind.INTERFACE;
case EXCEPTION_PARAMETER:
return NodeKind.VARIABLE_EXCEPTION;
case FIELD:
return NodeKind.VARIABLE_FIELD;
case LOCAL_VARIABLE:
return NodeKind.VARIABLE_LOCAL;
case PARAMETER:
return NodeKind.VARIABLE_PARAMETER;
case RESOURCE_VARIABLE:
return NodeKind.VARIABLE_RESOURCE;
case CONSTRUCTOR:
return NodeKind.FUNCTION_CONSTRUCTOR;
case METHOD:
return NodeKind.FUNCTION;
case TYPE_PARAMETER:
return NodeKind.ABS_VAR;
default:
// TODO(#1845): handle all cases, make this exceptional, and remove all null checks
return null;
}
}
// Returns a consistent hash for the given symbol across separate compilations and JVM instances.
private int hashSymbol(Symbol sym) {
// This method is necessary because Symbol, and most other javac internals, do not overload the
// Object#hashCode() method and the default implementation, System#identityHashCode(Object), is
// practically useless because it can change across JVM instances. This method instead only
// uses stable hashing methods such as String#hashCode(), Multiset#hashCode(), and
// Integer#hashCode().
if (symbolHashes.containsKey(sym)) {
return symbolHashes.get(sym);
}
Multiset<Integer> hashes = HashMultiset.create();
if (sym.members() != null) {
for (Symbol member : sym.members().getSymbols()) {
if (member.isPrivate()
|| (member instanceof MethodSymbol && ((MethodSymbol) member).isStaticOrInstanceInit())
|| ((member.flags_field & (Flags.BRIDGE | Flags.SYNTHETIC)) != 0)) {
// Ignore initializers, private members, and synthetic members. It's possible these do
// not appear in the symbol's scope outside of its .java source compilation (i.e. they do
// not appear in dependent compilations for Bazel's java rules).
continue;
}
// We can't recursively get the result of hashSymbol(member) since the extractor removes all
// .class files not directly used by a compilation meaning that member may not be complete.
hashes.add(member.getSimpleName().toString().hashCode());
hashes.add(member.kind.ordinal());
}
}
hashes.add(sym.getQualifiedName().toString().hashCode());
hashes.add(sym.getKind().ordinal());
// XXX: ignore Symbol modifiers since they can be inconsistent between definitions/references.
int h = hashes.hashCode();
symbolHashes.put(sym, h);
return h;
}
@Nullable
private VName lookupVName(@Nullable ClassSymbol cls) {
if (cls == null) {
return null;
}
VName clsVName = lookupVName(getDigest(cls.classfile));
return clsVName != null ? clsVName : lookupVName(getDigest(cls.sourcefile));
}
@Nullable
private static String getDigest(@Nullable JavaFileObject sourceFile) {
if (sourceFile == null) {
return null;
}
// This matches our {@link CustomFileObject#toUri()} logic
return sourceFile.toUri().getHost();
}
private static boolean fromJDK(@Nullable Symbol sym) {
if (sym == null || sym.enclClass() == null) {
return false;
}
String cls = sym.enclClass().className();
// For performance, first check common package prefixes, then delegate
// to the slower loadedByJDKClassLoader() method.
return SignatureGenerator.isArrayHelperClass(sym.enclClass())
|| cls.startsWith("java.")
|| cls.startsWith("javax.")
|| cls.startsWith("com.sun.")
|| cls.startsWith("sun.")
|| loadedByJDKClassLoader(cls);
}
/**
* Detects whether the class with the given name was loaded by the ClassLoader used to load JDK
* classes.
*
* <p>Although this is not required by the JVM spec, standard JVM implementations use separate
* classloaders for loading JDK classes vs. user classes.
*
* <p>Therefore, although not completely bulletproof, this is a good heuristic in practice for
* detecting whether a class is supplied by the JDK.
*/
private static boolean loadedByJDKClassLoader(String cls) {
try {
return Class.forName(cls).getClassLoader() == String.class.getClassLoader();
} catch (ClassNotFoundException | SecurityException e) {
return false;
}
}
}