JPS mappings for incremental compilation refactoring: add usages implementation, bytecode parsing, diff implementation

GitOrigin-RevId: 4d48d46b9f3906ac2356db9a2b133a1e1ad01cbb
This commit is contained in:
Eugene Zhuravlev
2023-09-12 14:38:52 +02:00
committed by intellij-monorepo-bot
parent bc39cf7eb4
commit 8c7e3a6aa8
38 changed files with 1856 additions and 267 deletions

View File

@@ -1,6 +1,8 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency;
import java.util.function.Predicate;
public interface DifferentiateContext {
/**
@@ -15,5 +17,7 @@ public interface DifferentiateContext {
void affectUsage(Usage usage);
void affectUsage(Predicate<Usage> usageQuery);
void affectNodeSource(NodeSource source);
}

View File

@@ -5,23 +5,10 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.dependency.diff.DiffCapable;
import org.jetbrains.jps.dependency.diff.Difference;
import java.util.Set;
public interface Node<T extends Node<T, D>, D extends Difference> extends DiffCapable<T, D>, SerializableGraphElement {
@NotNull
ReferenceID getReferenceID();
Iterable<Usage> getUsages();
default boolean containsAny(Set<Usage> usages) {
if (!usages.isEmpty()) {
for (Usage usage : getUsages()) {
if (usages.contains(usage)) {
return true;
}
}
}
return false;
}
}

View File

@@ -6,5 +6,35 @@ public interface DiffCapable<T extends DiffCapable<T, D>, D extends Difference>
int diffHashCode();
D difference(T other);
D difference(T past);
interface Adapter<T> extends DiffCapable<Adapter<T>, Difference>{
T getValue();
}
static <T> Adapter<T> wrap(T value) {
return new Adapter<>() {
@Override
public T getValue() {
return value;
}
@Override
public boolean isSame(DiffCapable<?, ?> other) {
return other instanceof Adapter && value.equals(((Adapter<?>)other).getValue());
}
@Override
public int diffHashCode() {
return value.hashCode();
}
@Override
public Difference difference(Adapter past) {
return () -> value.equals(past.getValue());
}
};
}
}

View File

@@ -59,7 +59,29 @@ public interface Difference {
}
}
static <T extends DiffCapable<T, D>, D extends Difference> Specifier<T, D> make(@Nullable Iterable<T> past, @Nullable Iterable<T> now) {
static <V> Specifier<V, ?> diff(@Nullable Iterable<V> past, @Nullable Iterable<V> now) {
var _past = Iterators.map(past, v -> DiffCapable.wrap(v));
var _now = Iterators.map(now, v -> DiffCapable.wrap(v));
var diff = deepDiff(_past, _now);
return new Specifier<>() {
@Override
public Iterable<V> added() {
return Iterators.map(diff.added(), adapter -> adapter.getValue());
}
@Override
public Iterable<V> removed() {
return Iterators.map(diff.removed(), adapter -> adapter.getValue());
}
@Override
public Iterable<Change<V, Difference>> changed() {
return Iterators.map(diff.changed(), c -> Change.create(c.getPast().getValue(), c.getNow().getValue(), c.getDiff()));
}
};
}
static <T extends DiffCapable<T, D>, D extends Difference> Specifier<T, D> deepDiff(@Nullable Iterable<T> past, @Nullable Iterable<T> now) {
if (Iterators.isEmpty(past)) {
if (Iterators.isEmpty(now)) {
return new Specifier<>() {
@@ -151,6 +173,5 @@ public interface Difference {
}
};
}
}

View File

@@ -11,6 +11,7 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
public class DependencyGraphImpl extends GraphImpl implements DependencyGraph {
private List<DifferentiateStrategy> myDifferentiateStrategies = new ArrayList<>(); // todo: fill the list
@@ -32,6 +33,7 @@ public class DependencyGraphImpl extends GraphImpl implements DependencyGraph {
var diffContext = new DifferentiateContext() {
final Set<Usage> affectedUsages = new HashSet<>();
final Set<Predicate<Usage>> usageQueries = new HashSet<>();
final Set<NodeSource> affectedSources = new HashSet<>();
@Override
@@ -49,10 +51,29 @@ public class DependencyGraphImpl extends GraphImpl implements DependencyGraph {
affectedUsages.add(usage);
}
@Override
public void affectUsage(Predicate<Usage> usageQuery) {
usageQueries.add(usageQuery);
}
@Override
public void affectNodeSource(NodeSource source) {
affectedSources.add(source);
}
boolean isNodeAffected(Node<?, ?> node) {
for (Usage usage : node.getUsages()) {
if (affectedUsages.contains(usage)) {
return true;
}
for (Predicate<Usage> query : usageQueries) {
if (query.test(usage)) {
return true;
}
}
}
return false;
}
};
for (DifferentiateStrategy diffStrategy : myDifferentiateStrategies) {
@@ -73,7 +94,7 @@ public class DependencyGraphImpl extends GraphImpl implements DependencyGraph {
for (NodeSource depSrc : getSources(dependent)) {
if (!affectedSources.contains(depSrc)) {
for (var depNode : getNodes(depSrc)) {
if (depNode.containsAny(diffContext.affectedUsages)) {
if (diffContext.isNodeAffected(depNode)) {
affectedSources.add(depSrc);
break;
}

View File

@@ -0,0 +1,49 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.impl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.dependency.NodeSource;
import java.io.File;
import java.nio.file.Path;
public class FileSource implements NodeSource {
private final Path myPath;
public FileSource(@NotNull File file) {
this(file.toPath());
}
public FileSource(@NotNull Path path) {
myPath = path;
}
@Override
public Path getPath() {
return myPath;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final FileSource that = (FileSource)o;
if (!myPath.equals(that.myPath)) {
return false;
}
return true;
}
@Override
public int hashCode() {
return myPath.hashCode();
}
}

View File

@@ -10,6 +10,10 @@ public final class StringReferenceID implements ReferenceID {
myValue = value;
}
public String getValue() {
return myValue;
}
@Override
public boolean equals(Object o) {
if (this == o) {

View File

@@ -1,54 +0,0 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.dependency.Usage;
import org.jetbrains.jps.dependency.diff.Difference;
import java.lang.annotation.RetentionPolicy;
public class AnnotationType extends ObjectType<AnnotationType, AnnotationType.Diff> {
private final Iterable<ElemType> myAnnotationTargets;
private final RetentionPolicy myRetentionPolicy;
public AnnotationType(JVMFlags flags, String signature, String name, String outFilePath,
@NotNull Iterable<TypeRepr.ClassType> annotations,
@NotNull Iterable<Field> fields,
@NotNull Iterable<Method> methods,
@NotNull Iterable<Usage> usages,
@NotNull Iterable<ElemType> annotationTargets,
@NotNull RetentionPolicy retentionPolicy
) {
super(flags, signature, name, outFilePath, fields, methods, annotations, usages);
myAnnotationTargets = annotationTargets;
myRetentionPolicy = retentionPolicy;
}
public Iterable<ElemType> getAnnotationTargets() {
return myAnnotationTargets;
}
public RetentionPolicy getRetentionPolicy() {
return myRetentionPolicy;
}
@Override
public Diff difference(AnnotationType other) {
return new Diff(other);
}
public static class Diff implements Difference {
public Diff(AnnotationType other) {
// todo: diff necessary data
}
@Override
public boolean unchanged() {
return false;
}
}
}

View File

@@ -0,0 +1,62 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.jps.dependency.impl.StringReferenceID;
public class AnnotationUsage extends JvmElementUsage {
private final TypeRepr.ClassType myClassType;
private final Iterable<String> myUserArgNames;
private final Iterable<ElemType> myTargets;
public AnnotationUsage(TypeRepr.ClassType classType, Iterable<String> userArgNames, Iterable<ElemType> targets) {
super(new StringReferenceID(classType.getJvmName()));
myClassType = classType;
myUserArgNames = userArgNames;
myTargets = targets;
}
public TypeRepr.ClassType getClassType() {
return myClassType;
}
public Iterable<String> getUserArgNames() {
return myUserArgNames;
}
public Iterable<ElemType> getTargets() {
return myTargets;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final AnnotationUsage that = (AnnotationUsage)o;
if (!myClassType.equals(that.myClassType)) {
return false;
}
if (!myUserArgNames.equals(that.myUserArgNames)) {
return false;
}
if (!myTargets.equals(that.myTargets)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = myClassType.hashCode();
result = 31 * result + myUserArgNames.hashCode();
result = 31 * result + myTargets.hashCode();
return result;
}
}

View File

@@ -0,0 +1,16 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
public class ClassAsGenericBoundUsage extends ClassUsage {
public ClassAsGenericBoundUsage(@NotNull String className) {
super(className);
}
@Override
public int hashCode() {
return super.hashCode() + 3;
}
}

View File

@@ -0,0 +1,16 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
public class ClassExtendsUsage extends ClassUsage {
public ClassExtendsUsage(@NotNull String className) {
super(className);
}
@Override
public int hashCode() {
return super.hashCode() + 1;
}
}

View File

@@ -0,0 +1,16 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
public class ClassNewUsage extends ClassUsage {
public ClassNewUsage(@NotNull String className) {
super(className);
}
@Override
public int hashCode() {
return super.hashCode() + 2;
}
}

View File

@@ -2,21 +2,16 @@
package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.dependency.ReferenceID;
import org.jetbrains.jps.dependency.Usage;
import org.jetbrains.jps.dependency.impl.StringReferenceID;
public class ClassUsage implements Usage {
public class ClassUsage extends JvmElementUsage {
@NotNull
private final ReferenceID myClassNode;
public ClassUsage(@NotNull ReferenceID classNode) {
myClassNode = classNode;
public ClassUsage(@NotNull String className) {
super(new StringReferenceID(className));
}
@Override
public @NotNull ReferenceID getElementOwner() {
return myClassNode;
public String getClassName() {
return ((StringReferenceID)getElementOwner()).getValue();
}
}

View File

@@ -1,41 +0,0 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.dependency.diff.DiffCapable;
import org.jetbrains.jps.dependency.diff.Difference;
public class Field extends ProtoMember implements DiffCapable<Field, Field.Diff> {
public Field(JVMFlags flags, String signature, String name, String descriptor, @NotNull Iterable<TypeRepr.ClassType> annotations, Object value) {
super(flags, signature, name, TypeRepr.getType(descriptor), annotations, value);
}
@Override
public boolean isSame(DiffCapable<?, ?> other) {
return other instanceof Field && getName().equals(((Field)other).getName());
}
@Override
public int diffHashCode() {
return getName().hashCode();
}
@Override
public Field.Diff difference(Field other) {
return new Diff(other);
}
public static class Diff implements Difference {
public Diff(Field other) {
// todo: diff necessary data
}
@Override
public boolean unchanged() {
return false;
}
}
}

View File

@@ -0,0 +1,14 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
public class FieldAssignUsage extends FieldUsage{
public FieldAssignUsage(String className, String name, String descriptor) {
super(className, name, descriptor);
}
@Override
public int hashCode() {
return super.hashCode() + 2;
}
}

View File

@@ -0,0 +1,38 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
public class FieldUsage extends MemberUsage{
private final String myDescriptor;
public FieldUsage(String className, String name, String descriptor) {
super(className, name);
myDescriptor = descriptor;
}
public String getDescriptor() {
return myDescriptor;
}
@Override
public boolean equals(Object o) {
if (!super.equals(o)) {
return false;
}
final FieldUsage that = (FieldUsage)o;
if (!myDescriptor.equals(that.myDescriptor)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + myDescriptor.hashCode();
return result;
}
}

View File

@@ -0,0 +1,14 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
public class ImportStaticMemberUsage extends MemberUsage{
public ImportStaticMemberUsage(String className, String name) {
super(className, name);
}
@Override
public int hashCode() {
return super.hashCode() + 1;
}
}

View File

@@ -0,0 +1,16 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.dependency.impl.StringReferenceID;
public class ImportStaticOnDemandUsage extends JvmElementUsage {
public ImportStaticOnDemandUsage(@NotNull String importedClassName) {
super(new StringReferenceID(importedClassName));
}
public String getImportedClassName() {
return ((StringReferenceID)getElementOwner()).getValue();
}
}

View File

@@ -4,6 +4,8 @@ package org.jetbrains.jps.dependency.java;
import org.jetbrains.org.objectweb.asm.Opcodes;
public final class JVMFlags {
public static final JVMFlags EMPTY = new JVMFlags(0);
// using the highes 4th byte
private static final int LOCAL_MASK = 0x1000000;
private static final int ANON_MASK = 0x2000000;
@@ -27,6 +29,14 @@ public final class JVMFlags {
return new JVMFlags(myFlags | GENERATED_MASK);
}
public JVMFlags deriveAdded(JVMFlags past) {
return new JVMFlags(~past.myFlags & myFlags);
}
public JVMFlags deriveRemoved(JVMFlags past) {
return new JVMFlags(~myFlags & past.myFlags);
}
// standard access flags
public boolean isPublic() {
return isSet(Opcodes.ACC_PUBLIC);
@@ -150,4 +160,28 @@ public final class JVMFlags {
private boolean isSet(int mask) {
return (myFlags & mask) != 0;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final JVMFlags jvmFlags = (JVMFlags)o;
if (myFlags != jvmFlags.myFlags) {
return false;
}
return true;
}
@Override
public int hashCode() {
return myFlags;
}
}

View File

@@ -1,32 +0,0 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.dependency.Usage;
import org.jetbrains.jps.dependency.diff.Difference;
public class JavaModule extends JVMClassNode<JavaModule, JavaModule.Diff>{
public JavaModule(JVMFlags flags, String signature, String name, String outFilePath, @NotNull Iterable<TypeRepr.ClassType> annotations, @NotNull Iterable<Usage> usages) {
super(flags, signature, name, outFilePath, annotations, usages);
}
@Override
public Diff difference(JavaModule other) {
return new Diff(other);
}
public static class Diff implements Difference {
public Diff(JavaModule other) {
// todo: diff necessary data
}
@Override
public boolean unchanged() {
return false;
}
}
}

View File

@@ -0,0 +1,63 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.dependency.Usage;
import org.jetbrains.jps.dependency.diff.Difference;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
public class JvmAnnotation extends JvmObjectType<JvmAnnotation, JvmAnnotation.Diff> {
private final Iterable<ElemType> myAnnotationTargets;
private final RetentionPolicy myRetentionPolicy;
public JvmAnnotation(JVMFlags flags, String signature, String name, String outFilePath,
@NotNull Iterable<TypeRepr.ClassType> annotations,
@NotNull Iterable<JvmField> fields,
@NotNull Iterable<JvmMethod> methods,
@NotNull Iterable<Usage> usages,
@NotNull Iterable<ElemType> annotationTargets,
@NotNull RetentionPolicy retentionPolicy
) {
super(flags, signature, name, outFilePath, fields, methods, annotations, usages);
myAnnotationTargets = annotationTargets;
myRetentionPolicy = retentionPolicy;
}
public Iterable<ElemType> getAnnotationTargets() {
return myAnnotationTargets;
}
public RetentionPolicy getRetentionPolicy() {
return myRetentionPolicy;
}
@Override
public Diff difference(JvmAnnotation past) {
return new Diff(past);
}
public class Diff extends JvmObjectType<JvmAnnotation, JvmAnnotation.Diff>.Diff<JvmAnnotation> {
public Diff(JvmAnnotation past) {
super(past);
}
@Override
public boolean unchanged() {
return super.unchanged() && !retentionPolicyChanged() && annotations().unchanged();
}
public boolean retentionPolicyChanged() {
return !Objects.equals(myPast.getRetentionPolicy(), getRetentionPolicy());
}
public Specifier<ElemType, ?> annotationTargets() {
return Difference.diff(myPast.getAnnotationTargets(), getAnnotationTargets());
}
}
}

View File

@@ -4,18 +4,20 @@ package org.jetbrains.jps.dependency.java;
import org.jetbrains.jps.dependency.Usage;
import org.jetbrains.jps.dependency.diff.Difference;
public class JavaClassType extends ObjectType<JavaClassType, JavaClassType.Diff> {
import java.util.Objects;
public class JvmClass extends JvmObjectType<JvmClass, JvmClass.Diff> {
private final String mySuperFqName;
private final String myOuterFqName;
private final Iterable<String> myInterfaces;
public JavaClassType(
public JvmClass(
JVMFlags flags, String signature, String fqName, String outFilePath,
String superFqName,
String outerFqName,
Iterable<String> interfaces,
Iterable<Field> fields,
Iterable<Method> methods,
Iterable<JvmField> fields,
Iterable<JvmMethod> methods,
Iterable<TypeRepr.ClassType> annotations,
Iterable<Usage> usages
) {
@@ -39,20 +41,32 @@ public class JavaClassType extends ObjectType<JavaClassType, JavaClassType.Diff>
}
@Override
public Diff difference(JavaClassType other) {
return new Diff(other);
public Diff difference(JvmClass past) {
return new Diff(past);
}
public static class Diff implements Difference {
public class Diff extends JvmObjectType<JvmClass, JvmClass.Diff>.Diff<JvmClass> {
public Diff(JavaClassType other) {
// todo: diff necessary data
public Diff(JvmClass past) {
super(past);
}
@Override
public boolean unchanged() {
return false;
return super.unchanged() && !superClassChanged() && !outerClassChanged() && interfaces().unchanged();
}
public boolean superClassChanged() {
return !Objects.equals(myPast.getSuperFqName(), getSuperFqName());
}
public boolean outerClassChanged() {
return !Objects.equals(myPast.getOuterFqName(), getOuterFqName());
}
public Specifier<String, ?> interfaces() {
return Difference.diff(myPast.getInterfaces(), getInterfaces());
}
}
}

View File

@@ -0,0 +1,848 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Ref;
import com.intellij.util.ArrayUtil;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.dependency.Usage;
import org.jetbrains.jps.javac.Iterators;
import org.jetbrains.org.objectweb.asm.*;
import org.jetbrains.org.objectweb.asm.signature.SignatureReader;
import org.jetbrains.org.objectweb.asm.signature.SignatureVisitor;
import java.lang.annotation.RetentionPolicy;
import java.util.*;
final class JvmClassAnalyzer {
private final static Logger LOG = Logger.getInstance(JvmClassAnalyzer.class);
public static final String LAMBDA_FACTORY_CLASS = "java/lang/invoke/LambdaMetafactory";
private static final String KOTLIN_LAMBDA_USAGE_CLASS_MARKER = "$sam$";
private static final int ASM_API_VERSION = Opcodes.API_VERSION;
JvmClassAnalyzer() {
}
private static class ClassCrawler extends ClassVisitor {
private final class AnnotationRetentionPolicyCrawler extends AnnotationVisitor {
private AnnotationRetentionPolicyCrawler() {
super(ASM_API_VERSION);
}
@Override
public void visit(String name, Object value) { }
@Override
public void visitEnum(String name, String desc, String value) {
myRetentionPolicy = RetentionPolicy.valueOf(value);
}
@Override
public AnnotationVisitor visitAnnotation(String name, String desc) {
return null;
}
@Override
public AnnotationVisitor visitArray(String name) {
return null;
}
@Override
public void visitEnd() { }
}
private final class AnnotationTargetCrawler extends AnnotationVisitor {
private AnnotationTargetCrawler() {
super(ASM_API_VERSION);
}
@Override
public void visit(String name, Object value) {
}
@Override
public void visitEnum(final String name, String desc, final String value) {
myTargets.add(ElemType.valueOf(value));
}
@Override
public AnnotationVisitor visitAnnotation(String name, String desc) {
return this;
}
@Override
public AnnotationVisitor visitArray(String name) {
return this;
}
@Override
public void visitEnd() {
}
}
private final class AnnotationCrawler extends AnnotationVisitor {
private final TypeRepr.ClassType myType;
private final ElemType myTarget;
private final Set<String> myUsedArguments = new HashSet<>();
private AnnotationCrawler(final TypeRepr.ClassType type, final ElemType target) {
super(ASM_API_VERSION);
this.myType = type;
this.myTarget = target;
final Set<ElemType> targets = myAnnotationTargets.get(type);
if (targets == null) {
myAnnotationTargets.put(type, EnumSet.of(target));
}
else {
targets.add(target);
}
myUsages.add(new ClassUsage(type.getJvmName()));
}
private String getMethodDescr(final Object value, boolean isArray) {
final StringBuilder descriptor = new StringBuilder();
descriptor.append("()");
if (isArray) {
descriptor.append("[");
}
if (value instanceof Type) {
descriptor.append("Ljava/lang/Class;");
}
else {
final String name = Type.getType(value.getClass()).getInternalName();
// only primitive, String, Class, Enum, another Annotation or array of any of these are allowed
switch (name) {
case "java/lang/Integer":
descriptor.append("I;");
break;
case "java/lang/Short":
descriptor.append("S;");
break;
case "java/lang/Long":
descriptor.append("J;");
break;
case "java/lang/Byte":
descriptor.append("B;");
break;
case "java/lang/Char":
descriptor.append("C;");
break;
case "java/lang/Boolean":
descriptor.append("Z;");
break;
case "java/lang/Float":
descriptor.append("F;");
break;
case "java/lang/Double":
descriptor.append("D;");
break;
default:
descriptor.append("L").append(name).append(";");
break;
}
}
return descriptor.toString();
}
@Nullable
private String myArrayName;
@Override
public void visit(String name, Object value) {
final boolean isArray = name == null && myArrayName != null;
final String argName;
if (name != null) {
argName = name;
}
else {
argName = myArrayName;
// not interested in collecting complete array value; need to know just array type
myArrayName = null;
}
if (argName != null) {
registerUsages(argName, getMethodDescr(value, isArray), value);
}
}
@Override
public void visitEnum(String name, String desc, String value) {
final boolean isArray = name == null && myArrayName != null;
final String argName;
if (name != null) {
argName = name;
}
else {
argName = myArrayName;
// not interested in collecting complete array value; need to know just array type
myArrayName = null;
}
if (argName != null) {
registerUsages(argName, (isArray? "()[" : "()") + desc, value);
}
}
@Override
public AnnotationVisitor visitAnnotation(String name, String desc) {
return new AnnotationCrawler((TypeRepr.ClassType)TypeRepr.getType(desc), myTarget);
}
@Override
public AnnotationVisitor visitArray(String name) {
myArrayName = name;
return this;
}
private void registerUsages(String methodName, String methodDescr, Object value) {
if (value instanceof Type) {
final String className = ((Type)value).getClassName().replace('.', '/');
myUsages.add(new ClassUsage(className));
}
myUsages.add(new MethodUsage(myType.getJvmName(), methodName, methodDescr));
//myUsages.add(UsageRepr.createMetaMethodUsage(myContext, methodName, myType.className));
myUsedArguments.add(methodName);
}
@Override
public void visitEnd() {
Set<String> s = myAnnotationArguments.get(myType);
if (s == null) {
myAnnotationArguments.put(myType, myUsedArguments);
}
else {
s.retainAll(myUsedArguments);
}
}
}
private class ModuleCrawler extends ModuleVisitor {
ModuleCrawler() {
super(ASM_API_VERSION);
}
@Override
public void visitMainClass(String mainClass) {
myUsages.add(new ClassUsage(mainClass));
}
@Override
public void visitRequire(String module, int access, String version) {
if (isExplicit(access)) {
// collect non-synthetic dependencies only
myModuleRequires.add(new ModuleRequires(new JVMFlags(access), module, version));
}
}
@Override
public void visitExport(String packaze, int access, String... modules) {
if (isExplicit(access)) {
// collect non-synthetic dependencies only
myModuleExports.add(new ModulePackage(packaze, modules != null? Arrays.asList(modules) : Collections.emptyList()));
}
}
@Override
public void visitUse(String service) {
myUsages.add(new ClassUsage(service));
}
@Override
public void visitProvide(String service, String... providers) {
myUsages.add(new ClassUsage(service));
if (providers != null) {
for (String provider : providers) {
myUsages.add(new ClassUsage(provider));
}
}
}
private boolean isExplicit(int access) {
return (access & (Opcodes.ACC_SYNTHETIC | Opcodes.ACC_MANDATED)) == 0;
}
}
private void processSignature(final String sig) {
if (sig != null) {
try {
new SignatureReader(sig).accept(mySignatureCrawler);
}
catch (Exception e) {
LOG.info("Problems parsing signature \"" + sig + "\" in " + myFileName, e);
}
}
}
private final SignatureVisitor mySignatureCrawler = new BaseSignatureVisitor() {
@Override
public SignatureVisitor visitClassBound() {
return mySignatureWithGenericBoundUsageCrawler;
}
@Override
public SignatureVisitor visitInterfaceBound() {
return mySignatureWithGenericBoundUsageCrawler;
}
@Override
public SignatureVisitor visitTypeArgument(char wildcard) {
return wildcard == '+' || wildcard == '-' ? mySignatureWithGenericBoundUsageCrawler : super.visitTypeArgument(wildcard);
}
};
private final SignatureVisitor mySignatureWithGenericBoundUsageCrawler = new BaseSignatureVisitor() {
@Override
public void visitClassType(String name) {
myUsages.add(new ClassUsage(name));
myUsages.add(new ClassAsGenericBoundUsage(name));
}
};
private boolean myTakeIntoAccount = false;
private boolean myIsModule = false;
private final String myFileName;
private final boolean myIsGenerated;
private int myAccess;
private String myName;
private String myVersion; // for class contains a class bytecode version, for module contains a module version
private String mySuperClass;
private String[] myInterfaces;
private String mySignature;
private final Ref<String> myClassNameHolder = Ref.create();
private final Ref<String> myOuterClassName = Ref.create();
private final Ref<Boolean> myLocalClassFlag = Ref.create(false);
private final Ref<Boolean> myAnonymousClassFlag = Ref.create(false);
private final Set<JvmMethod> myMethods = new HashSet<>();
private final Set<JvmField> myFields = new HashSet<>();
private final Set<Usage> myUsages = new HashSet<>();
private final Set<ElemType> myTargets = EnumSet.noneOf(ElemType.class);
private RetentionPolicy myRetentionPolicy = null;
private final Map<TypeRepr.ClassType, Set<String>> myAnnotationArguments = new HashMap<>();
private final Map<TypeRepr.ClassType, Set<ElemType>> myAnnotationTargets = new HashMap<>();
private final Set<TypeRepr.ClassType> myAnnotations = new HashSet<>();
private final Set<ModuleRequires> myModuleRequires = new HashSet<>();
private final Set<ModulePackage> myModuleExports = new HashSet<>();
ClassCrawler(final String fn, boolean isGenerated) {
super(ASM_API_VERSION);
myFileName = fn;
myIsGenerated = isGenerated;
}
private boolean notPrivate(final int access) {
return (access & Opcodes.ACC_PRIVATE) == 0;
}
public JVMClassNode getResult() {
if (!myTakeIntoAccount) {
return null;
}
JVMFlags flags = new JVMFlags(myAccess);
if (myLocalClassFlag.get()) {
flags = flags.deriveIsLocal();
}
if (myAnonymousClassFlag.get()) {
flags = flags.deriveIsAnonymous();
}
if (myIsGenerated) {
flags = flags.deriveIsGenerated();
}
if (myIsModule) {
return new JvmModule(flags, myVersion, myFileName, myName, myModuleRequires, myModuleExports, myUsages);
}
if ((myAccess & Opcodes.ACC_ANNOTATION) > 0) {
return new JvmAnnotation(flags, mySignature, myName, myFileName, myAnnotations, myFields, myMethods, myUsages, myTargets, myRetentionPolicy);
}
return new JvmClass(flags, mySignature, myName, myFileName, mySuperClass, myOuterClassName.get(), Arrays.asList(myInterfaces), myFields, myMethods, myAnnotations, myUsages);
}
@Override
public void visit(int version, int access, String name, String sig, String superName, String[] interfaces) {
myTakeIntoAccount = notPrivate(access);
myAccess = access;
myName = name;
myVersion = String.valueOf(version);
mySignature = sig;
mySuperClass = superName;
myInterfaces = interfaces;
myClassNameHolder.set(name);
if (mySuperClass != null) {
myUsages.add(new ClassUsage(mySuperClass));
}
if (myInterfaces != null) {
for (String ifaceName : myInterfaces) {
myUsages.add(new ClassUsage(ifaceName));
}
}
processSignature(sig);
}
@Override
public void visitEnd() {
for (Map.Entry<TypeRepr.ClassType, Set<ElemType>> entry : myAnnotationTargets.entrySet()) {
TypeRepr.ClassType type = entry.getKey();
Set<ElemType> targets = entry.getValue();
Set<String> usedArguments = myAnnotationArguments.get(type);
myUsages.add(new AnnotationUsage(type, usedArguments != null? usedArguments : Collections.emptyList(), targets));
}
}
@Override
public ModuleVisitor visitModule(String name, int access, String version) {
myIsModule = true;
myAccess = access;
myName = name;
myVersion = version;
return new ModuleCrawler();
}
@Override
public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
if (desc.equals("Ljava/lang/annotation/Target;")) {
return new AnnotationTargetCrawler();
}
if (desc.equals("Ljava/lang/annotation/Retention;")) {
return new AnnotationRetentionPolicyCrawler();
}
TypeRepr.ClassType annotationType = (TypeRepr.ClassType)TypeRepr.getType(desc);
myAnnotations.add(annotationType);
return new AnnotationCrawler(annotationType, (myAccess & Opcodes.ACC_ANNOTATION) > 0 ? ElemType.ANNOTATION_TYPE : ElemType.TYPE);
}
@Override
public void visitSource(String source, String debug) {
}
@Override
public FieldVisitor visitField(final int access, final String name, final String desc, final String signature, final Object value) {
processSignature(signature);
return new FieldVisitor(ASM_API_VERSION) {
final Set<TypeRepr.ClassType> annotations = new HashSet<>();
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
final TypeRepr.ClassType annotation = (TypeRepr.ClassType)TypeRepr.getType(desc);
annotations.add(annotation);
return new AnnotationCrawler(annotation, ElemType.FIELD);
}
@Override
public void visitEnd() {
try {
super.visitEnd();
}
finally {
if ((access & Opcodes.ACC_SYNTHETIC) == 0) {
myFields.add(new JvmField(new JVMFlags(access), signature, name, desc, annotations, value));
}
}
}
};
}
@Override
public MethodVisitor visitMethod(final int access, final String n, final String desc, final String signature, final String[] exceptions) {
final Ref<Object> defaultValue = Ref.create();
final Set<TypeRepr.ClassType> annotations = new HashSet<>();
final Set<ParamAnnotation> paramAnnotations = new HashSet<>();
processSignature(signature);
return new MethodVisitor(ASM_API_VERSION) {
@Override
public void visitEnd() {
if ((access & Opcodes.ACC_SYNTHETIC) == 0 || (access & Opcodes.ACC_BRIDGE) > 0) {
myMethods.add(new JvmMethod(new JVMFlags(access), signature, n, desc, annotations, paramAnnotations, exceptions, defaultValue.get()));
}
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
final TypeRepr.ClassType annoType = (TypeRepr.ClassType)TypeRepr.getType(desc);
annotations.add(annoType);
return new AnnotationCrawler(annoType, "<init>".equals(n) ? ElemType.CONSTRUCTOR : ElemType.METHOD);
}
@Override
public AnnotationVisitor visitAnnotationDefault() {
return new AnnotationVisitor(ASM_API_VERSION) {
private @Nullable List<Object> myAcc;
@Override
public void visit(String name, Object value) {
collectValue(value);
}
@Override
public void visitEnum(String name, String desc, String value) {
collectValue(value);
}
@Override
public AnnotationVisitor visitArray(String name) {
myAcc = new SmartList<>();
return this;
}
@Override
public void visitEnd() {
if (myAcc != null) {
Object[] template = null;
if (!myAcc.isEmpty()) {
final Object elem = myAcc.get(0);
if (elem != null) {
template = ArrayUtil.newArray(elem.getClass(), 0);
}
}
defaultValue.set(template != null? myAcc.toArray(template) : myAcc.toArray());
}
}
private void collectValue(Object value) {
if (myAcc != null) {
myAcc.add(value);
}
else {
defaultValue.set(value);
}
}
};
}
@Override
public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
final TypeRepr.ClassType annoType = (TypeRepr.ClassType)TypeRepr.getType(desc);
paramAnnotations.add(new ParamAnnotation(parameter, annoType));
return new AnnotationCrawler(annoType, ElemType.PARAMETER);
}
@Override
public void visitLdcInsn(Object cst) {
if (cst instanceof Type) {
myUsages.add(new ClassUsage(((Type)cst).getInternalName()));
}
super.visitLdcInsn(cst);
}
@Override
public void visitMultiANewArrayInsn(String desc, int dims) {
final TypeRepr.ArrayType typ = (TypeRepr.ArrayType)TypeRepr.getType(desc);
Iterators.collect(typ.getUsages(), myUsages);
final TypeRepr element = typ.getDeepElementType();
if (element instanceof TypeRepr.ClassType) {
myUsages.add(new ClassNewUsage(((TypeRepr.ClassType)element).getJvmName()));
}
super.visitMultiANewArrayInsn(desc, dims);
}
@Override
public void visitLocalVariable(String n, String desc, String signature, Label start, Label end, int index) {
processSignature(signature);
Iterators.collect(TypeRepr.getType(desc).getUsages(), myUsages);
super.visitLocalVariable(n, desc, signature, start, end, index);
}
@Override
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
if (type != null) {
Iterators.collect(new TypeRepr.ClassType(type).getUsages(), myUsages);
}
super.visitTryCatchBlock(start, end, handler, type);
}
@Override
public void visitTypeInsn(int opcode, String type) {
final TypeRepr typ = type.startsWith("[")? TypeRepr.getType(type) : new TypeRepr.ClassType(type);
if (opcode == Opcodes.NEW) {
myUsages.add(new ClassUsage(((TypeRepr.ClassType)typ).getJvmName()));
myUsages.add(new ClassNewUsage(((TypeRepr.ClassType)typ).getJvmName()));
final int ktLambdaMarker = type.indexOf(KOTLIN_LAMBDA_USAGE_CLASS_MARKER);
if (ktLambdaMarker > 0) {
final int ifNameStart = ktLambdaMarker + KOTLIN_LAMBDA_USAGE_CLASS_MARKER.length();
final int ifNameEnd = type.indexOf("$", ifNameStart);
if (ifNameEnd > ifNameStart) {
myUsages.add(new ClassNewUsage(type.substring(ifNameStart, ifNameEnd).replace('_', '/')));
}
}
}
else if (opcode == Opcodes.ANEWARRAY) {
if (typ instanceof TypeRepr.ClassType) {
myUsages.add(new ClassUsage(((TypeRepr.ClassType)typ).getJvmName()));
myUsages.add(new ClassNewUsage(((TypeRepr.ClassType)typ).getJvmName()));
}
}
Iterators.collect(typ.getUsages(), myUsages);
super.visitTypeInsn(opcode, type);
}
@Override
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
registerFieldUsage(opcode, owner, name, desc);
super.visitFieldInsn(opcode, owner, name, desc);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
registerMethodUsage(owner, name, desc);
super.visitMethodInsn(opcode, owner, name, desc, itf);
}
@Override
public void visitInvokeDynamicInsn(String methodName, String desc, Handle bsm, Object... bsmArgs) {
final Type returnType = Type.getReturnType(desc);
addClassUsage(TypeRepr.getType(returnType));
// common args processing
for (Object arg : bsmArgs) {
if (arg instanceof Type) {
final Type type = (Type)arg;
if (type.getSort() == Type.METHOD) {
for (Type argType : type.getArgumentTypes()) {
addClassUsage(TypeRepr.getType(argType));
}
addClassUsage(TypeRepr.getType(type.getReturnType()));
}
else {
addClassUsage(TypeRepr.getType(type));
}
}
else if (arg instanceof Handle) {
processMethodHandle((Handle)arg);
}
}
if (LAMBDA_FACTORY_CLASS.equals(bsm.getOwner())) {
// This invokeDynamic implements a lambda or method reference usage.
// Need to register method usage for the corresponding SAM-type.
// First three arguments to the bootstrap methods are provided automatically by VM.
// Arguments in args array are expected to be as following:
// [0]: Type: Signature and return type of method to be implemented by the function object.
// [1]: Handle: implementation method handle
// [2]: Type: The signature and return type that should be enforced dynamically at invocation time. May be the same as samMethodType, or may be a specialization of it
// [...]: optional additional arguments
if (returnType.getSort() == Type.OBJECT && bsmArgs.length >= 3) {
if (bsmArgs[0] instanceof Type) {
final Type samMethodType = (Type)bsmArgs[0];
if (samMethodType.getSort() == Type.METHOD) {
registerMethodUsage(returnType.getInternalName(), methodName, samMethodType.getDescriptor());
// reflect dynamic proxy instantiation with NewClassUsage
myUsages.add(new ClassNewUsage(returnType.getInternalName()));
}
}
}
}
super.visitInvokeDynamicInsn(methodName, desc, bsm, bsmArgs);
}
private void processMethodHandle(Handle handle) {
final String memberOwner = handle.getOwner();
if (memberOwner != null && !memberOwner.equals(myClassNameHolder.get())) {
// do not register access to own class members
final String memberName = handle.getName();
final String memberDescriptor = handle.getDesc();
final int opCode = getFieldAccessOpcode(handle);
if (opCode > 0) {
registerFieldUsage(opCode, memberOwner, memberName, memberDescriptor);
}
else {
registerMethodUsage(memberOwner, memberName, memberDescriptor);
}
}
}
private void registerFieldUsage(int opcode, String owner, String fName, String desc) {
if (opcode == Opcodes.PUTFIELD || opcode == Opcodes.PUTSTATIC) {
myUsages.add(new FieldAssignUsage(owner, fName, desc));
}
if (opcode == Opcodes.GETFIELD || opcode == Opcodes.GETSTATIC) {
addClassUsage(TypeRepr.getType(desc));
}
myUsages.add(new FieldUsage(owner, fName, desc));
}
private void registerMethodUsage(String owner, String name, @Nullable String desc) {
//myUsages.add(UsageRepr.createMetaMethodUsage(myContext, methodName, methodOwner));
if (desc != null) {
myUsages.add(new MethodUsage(owner, name, desc));
addClassUsage(TypeRepr.getType(Type.getReturnType(desc)));
}
else {
// todo: verify for which methods null descriptor is passed
myUsages.add(new MethodUsage(owner, name, ""));
}
}
private void addClassUsage(final TypeRepr type) {
TypeRepr.ClassType classType = null;
if (type instanceof TypeRepr.ClassType) {
classType = (TypeRepr.ClassType)type;
}
else if (type instanceof TypeRepr.ArrayType) {
final TypeRepr elemType = ((TypeRepr.ArrayType)type).getDeepElementType();
if (elemType instanceof TypeRepr.ClassType) {
classType = (TypeRepr.ClassType)elemType;
}
}
if (classType != null) {
myUsages.add(new ClassUsage(classType.getJvmName()));
}
}
};
}
/**
* @return corresponding field access opcode or -1 if the handle does not represent field access handle
*/
private int getFieldAccessOpcode(Handle handle) {
switch (handle.getTag()) {
case Opcodes.H_GETFIELD: return Opcodes.GETFIELD;
case Opcodes.H_GETSTATIC: return Opcodes.GETSTATIC;
case Opcodes.H_PUTFIELD: return Opcodes.PUTFIELD;
case Opcodes.H_PUTSTATIC: return Opcodes.PUTSTATIC;
default: return -1;
}
}
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
if (name != null && name.equals(myClassNameHolder.get())) {
// set outer class name only if we are parsing the real inner class and
// not the reference to inner class inside some top-level class
myAccess |= access; // information about some access flags for the inner class is missing from the mask passed to 'visit' method
if (outerName != null) {
myOuterClassName.set(outerName);
}
if (innerName == null) {
myAnonymousClassFlag.set(true);
}
}
}
@Override
public void visitOuterClass(final String owner, final String name, final String desc) {
myOuterClassName.set(owner);
if (name != null) {
myLocalClassFlag.set(true);
}
}
private class BaseSignatureVisitor extends SignatureVisitor {
BaseSignatureVisitor() {
super(ASM_API_VERSION);
}
@Override
public void visitFormalTypeParameter(String name) { }
@Override
public SignatureVisitor visitClassBound() {
return super.visitClassBound();
}
@Override
public SignatureVisitor visitInterfaceBound() {
return super.visitInterfaceBound();
}
@Override
public SignatureVisitor visitSuperclass() {
return super.visitSuperclass();
}
@Override
public SignatureVisitor visitInterface() {
return super.visitInterface();
}
@Override
public SignatureVisitor visitParameterType() {
return super.visitParameterType();
}
@Override
public SignatureVisitor visitReturnType() {
return super.visitReturnType();
}
@Override
public SignatureVisitor visitExceptionType() {
return super.visitExceptionType();
}
@Override
public void visitBaseType(char descriptor) {
}
@Override
public void visitTypeVariable(String name) {
}
@Override
public SignatureVisitor visitArrayType() {
return super.visitArrayType();
}
@Override
public void visitInnerClassType(String name) {
}
@Override
public void visitTypeArgument() {
super.visitTypeArgument();
}
@Override
public SignatureVisitor visitTypeArgument(char wildcard) {
return this;
}
@Override
public void visitEnd() {
super.visitEnd();
}
@Override
public void visitClassType(String name) {
myUsages.add(new ClassUsage(name));
}
}
}
public JVMClassNode analyze(String fileName, ClassReader cr, boolean isGenerated) {
ClassCrawler visitor = new ClassCrawler(fileName, isGenerated);
try {
cr.accept(visitor, 0);
}
catch (RuntimeException e) {
throw new RuntimeException("Corrupted .class file: " + fileName, e);
}
return visitor.getResult();
}
}

View File

@@ -0,0 +1,44 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.dependency.ReferenceID;
import org.jetbrains.jps.dependency.Usage;
abstract class JvmElementUsage implements Usage {
@NotNull
private final ReferenceID myOwner;
JvmElementUsage(@NotNull ReferenceID owner) {
myOwner = owner;
}
@Override
public @NotNull ReferenceID getElementOwner() {
return myOwner;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final JvmElementUsage jvmUsage = (JvmElementUsage)o;
if (!myOwner.equals(jvmUsage.myOwner)) {
return false;
}
return true;
}
@Override
public int hashCode() {
return myOwner.hashCode();
}
}

View File

@@ -0,0 +1,35 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.dependency.diff.DiffCapable;
public class JvmField extends ProtoMember implements DiffCapable<JvmField, JvmField.Diff> {
public JvmField(JVMFlags flags, String signature, String name, String descriptor, @NotNull Iterable<TypeRepr.ClassType> annotations, Object value) {
super(flags, signature, name, TypeRepr.getType(descriptor), annotations, value);
}
@Override
public boolean isSame(DiffCapable<?, ?> other) {
return other instanceof JvmField && getName().equals(((JvmField)other).getName());
}
@Override
public int diffHashCode() {
return getName().hashCode();
}
@Override
public JvmField.Diff difference(JvmField past) {
return new Diff(past);
}
public class Diff extends ProtoMember.Diff<JvmField> {
public Diff(JvmField past) {
super(past);
}
}
}

View File

@@ -0,0 +1,82 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.dependency.diff.DiffCapable;
import org.jetbrains.jps.dependency.diff.Difference;
import org.jetbrains.jps.javac.Iterators;
import org.jetbrains.org.objectweb.asm.Type;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public class JvmMethod extends ProtoMember implements DiffCapable<JvmMethod, JvmMethod.Diff> {
public final Iterable<TypeRepr> myArgTypes;
private final Set<ParamAnnotation> myParamAnnotations;
private final Set<TypeRepr.ClassType> myExceptions;
public JvmMethod(
JVMFlags flags, String signature, String name, String descriptor,
@NotNull Iterable<TypeRepr.ClassType> annotations, Set<ParamAnnotation> parameterAnnotations,
String[] exceptions, Object defaultValue) {
super(flags, signature, name, TypeRepr.getType(Type.getReturnType(descriptor)), annotations, defaultValue);
myParamAnnotations = parameterAnnotations;
myExceptions = Iterators.collect(Iterators.map(Arrays.asList(exceptions), s -> new TypeRepr.ClassType(s)), new HashSet<>());
myArgTypes = TypeRepr.getTypes(Type.getArgumentTypes(descriptor));
}
public Set<ParamAnnotation> getParamAnnotations() {
return myParamAnnotations;
}
public Set<TypeRepr.ClassType> getExceptions() {
return myExceptions;
}
@Override
public boolean isSame(DiffCapable<?, ?> other) {
if (!(other instanceof JvmMethod)) {
return false;
}
JvmMethod that = (JvmMethod)other;
return getName().equals(that.getName()) && Iterators.equals(myArgTypes, that.myArgTypes);
}
@Override
public int diffHashCode() {
return 31 * (31 * Iterators.hashCode(myArgTypes) + getName().hashCode());
}
@Override
public JvmMethod.Diff difference(JvmMethod past) {
return new Diff(past);
}
public class Diff extends ProtoMember.Diff<JvmMethod> {
public Diff(JvmMethod past) {
super(past);
}
@Override
public boolean unchanged() {
return super.unchanged() && !returnTypeChanged() && paramAnnotationsChanged().unchanged() && exceptionsChanged().unchanged();
}
public boolean returnTypeChanged() {
return !Objects.equals(myPast.getType(), getType());
}
public Specifier<ParamAnnotation, ?> paramAnnotationsChanged() {
return Difference.diff(myPast.getParamAnnotations(), getParamAnnotations());
}
public Specifier<TypeRepr.ClassType, ?> exceptionsChanged() {
return Difference.diff(myPast.getExceptions(), getExceptions());
}
}
}

View File

@@ -0,0 +1,60 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.dependency.Usage;
import org.jetbrains.jps.dependency.diff.Difference;
import java.util.Collections;
public class JvmModule extends JVMClassNode<JvmModule, JvmModule.Diff>{
private final String myVersion;
private final Iterable<ModuleRequires> myRequires;
private final Iterable<ModulePackage> myExports;
public JvmModule(JVMFlags flags, String name, String outFilePath, String version, @NotNull Iterable<ModuleRequires> requires, @NotNull Iterable<ModulePackage> exports, @NotNull Iterable<Usage> usages) {
super(flags, "", name, outFilePath, Collections.emptyList(), usages);
myVersion = version;
myRequires = requires;
myExports = exports;
}
public final String getVersion() {
return myVersion;
}
public Iterable<ModuleRequires> getRequires() {
return myRequires;
}
public Iterable<ModulePackage> getExports() {
return myExports;
}
@Override
public Diff difference(JvmModule past) {
return new Diff(past);
}
public class Diff extends Proto.Diff<JvmModule> {
public Diff(JvmModule past) {
super(past);
}
@Override
public boolean unchanged() {
return super.unchanged() && requires().unchanged() && exports().unchanged();
}
public Specifier<ModuleRequires, ModuleRequires.Diff> requires() {
return Difference.deepDiff(myPast.getRequires(), getRequires());
}
public Specifier<ModulePackage, ModulePackage.Diff> exports() {
return Difference.deepDiff(myPast.getExports(), getExports());
}
}
}

View File

@@ -0,0 +1,49 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.dependency.Usage;
import org.jetbrains.jps.dependency.diff.Difference;
public abstract class JvmObjectType<T extends JvmObjectType<T, D>, D extends Difference> extends JVMClassNode<T, D>{
private final Iterable<JvmField> myFields;
private final Iterable<JvmMethod> myMethods;
public JvmObjectType(JVMFlags flags, String signature, String name, String outFilePath,
Iterable<JvmField> fields,
Iterable<JvmMethod> methods,
@NotNull Iterable<TypeRepr.ClassType> annotations,
@NotNull Iterable<Usage> usages) {
super(flags, signature, name, outFilePath, annotations, usages);
myFields = fields;
myMethods = methods;
}
public Iterable<JvmField> getFields() {
return myFields;
}
public Iterable<JvmMethod> getMethods() {
return myMethods;
}
public class Diff<V extends JvmObjectType<T, D>> extends Proto.Diff<V> {
public Diff(V past) {
super(past);
}
@Override
public boolean unchanged() {
return super.unchanged() && methods().unchanged() && fields().unchanged();
}
public Specifier<JvmMethod, JvmMethod.Diff> methods() {
return Difference.deepDiff(myPast.getMethods(), getMethods());
}
public Specifier<JvmField, JvmField.Diff> fields() {
return Difference.deepDiff(myPast.getFields(), getFields());
}
}
}

View File

@@ -0,0 +1,40 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.jps.dependency.impl.StringReferenceID;
abstract class MemberUsage extends JvmElementUsage {
private final String myName;
protected MemberUsage(String className, String name) {
super(new StringReferenceID(className));
myName = name;
}
public String getName() {
return myName;
}
@Override
public boolean equals(Object o) {
if (!super.equals(o)) {
return false;
}
final MemberUsage that = (MemberUsage)o;
if (!myName.equals(that.myName)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + myName.hashCode();
return result;
}
}

View File

@@ -1,70 +0,0 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.dependency.diff.DiffCapable;
import org.jetbrains.jps.dependency.diff.Difference;
import org.jetbrains.jps.javac.Iterators;
import org.jetbrains.org.objectweb.asm.Type;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class Method extends ProtoMember implements DiffCapable<Method, Method.Diff> {
public final Iterable<TypeRepr> argTypes;
private final Set<ParamAnnotation> paramAnnotations;
private final Set<TypeRepr.ClassType> exceptions;
public Method(
JVMFlags flags, String signature, String name, String descriptor,
@NotNull Iterable<TypeRepr.ClassType> annotations, Set<ParamAnnotation> parameterAnnotations,
String[] exceptions, Object defaultValue) {
super(flags, signature, name, TypeRepr.getType(Type.getReturnType(descriptor)), annotations, defaultValue);
paramAnnotations = parameterAnnotations;
this.exceptions = Iterators.collect(Iterators.map(Arrays.asList(exceptions), s -> new TypeRepr.ClassType(s)), new HashSet<>());
argTypes = TypeRepr.getTypes(Type.getArgumentTypes(descriptor));
}
public Set<ParamAnnotation> getParamAnnotations() {
return paramAnnotations;
}
public Set<TypeRepr.ClassType> getExceptions() {
return exceptions;
}
// todo: need to additionally define normal equals-hashcode?
@Override
public boolean isSame(DiffCapable<?, ?> other) {
if (!(other instanceof Method)) {
return false;
}
Method that = (Method)other;
return getName().equals(that.getName()) && getType().equals(that.getType()) && Iterators.equals(argTypes, that.argTypes);
}
@Override
public int diffHashCode() {
return 31 * (31 * Iterators.hashCode(argTypes) + getType().hashCode()) + getName().hashCode();
}
@Override
public Method.Diff difference(Method other) {
return new Diff(other);
}
public static class Diff implements Difference {
public Diff(Method other) {
// todo: diff necessary data
}
@Override
public boolean unchanged() {
return false;
}
}
}

View File

@@ -0,0 +1,38 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
public class MethodUsage extends MemberUsage{
private final String myDescriptor;
public MethodUsage(String className, String name, String descriptor) {
super(className, name);
myDescriptor = descriptor;
}
public String getDescriptor() {
return myDescriptor;
}
@Override
public boolean equals(Object o) {
if (!super.equals(o)) {
return false;
}
final MethodUsage that = (MethodUsage)o;
if (!myDescriptor.equals(that.myDescriptor)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + myDescriptor.hashCode();
return result;
}
}

View File

@@ -0,0 +1,53 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.jps.dependency.diff.DiffCapable;
import org.jetbrains.jps.dependency.diff.Difference;
import java.util.Collections;
public class ModulePackage extends Proto implements DiffCapable<ModulePackage, ModulePackage.Diff> {
private final Iterable<String> myModules;
public ModulePackage(String name, Iterable<String> modules) {
super(JVMFlags.EMPTY, "", name, Collections.emptyList());
myModules = modules;
}
public Iterable<String> getModules() {
return myModules;
}
@Override
public boolean isSame(DiffCapable<?, ?> other) {
return other instanceof ModulePackage && getName().equals(((ModulePackage)other).getName());
}
@Override
public int diffHashCode() {
return getName().hashCode();
}
@Override
public Diff difference(ModulePackage past) {
return new Diff(past);
}
public class Diff extends Proto.Diff<ModulePackage> {
public Diff(ModulePackage past) {
super(past);
}
@Override
public boolean unchanged() {
return super.unchanged() && targetModules().unchanged();
}
public Specifier<String, ?> targetModules() {
return Difference.diff(myPast.getModules(), getModules());
}
}
}

View File

@@ -0,0 +1,56 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.jps.dependency.diff.DiffCapable;
import java.util.Collections;
import java.util.Objects;
public class ModuleRequires extends Proto implements DiffCapable<ModuleRequires, ModuleRequires.Diff> {
private final String myVersion;
public ModuleRequires(JVMFlags flags, String name, String version) {
super(flags, "", name, Collections.emptyList());
myVersion = version;
}
public String getVersion() {
return myVersion;
}
@Override
public boolean isSame(DiffCapable<?, ?> other) {
return other instanceof ModuleRequires && getName().equals(((ModuleRequires)other).getName());
}
@Override
public int diffHashCode() {
return getName().hashCode();
}
@Override
public Diff difference(ModuleRequires past) {
return new Diff(past);
}
public class Diff extends Proto.Diff<ModuleRequires> {
public Diff(ModuleRequires past) {
super(past);
}
@Override
public boolean unchanged() {
return super.unchanged() && !versionChanged();
}
public boolean versionChanged() {
return !Objects.equals(myPast.getVersion(), getVersion());
}
public boolean becameNonTransitive() {
return myPast.getFlags().isTransitive() && !getFlags().isTransitive();
}
}
}

View File

@@ -0,0 +1,16 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.dependency.impl.StringReferenceID;
public class ModuleUsage extends JvmElementUsage {
public ModuleUsage(@NotNull String moduleName) {
super(new StringReferenceID(moduleName));
}
public String getModuleName() {
return ((StringReferenceID)getElementOwner()).getValue();
}
}

View File

@@ -1,30 +0,0 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.dependency.Usage;
import org.jetbrains.jps.dependency.diff.Difference;
public abstract class ObjectType<T extends ObjectType<T, D>, D extends Difference> extends JVMClassNode<T, D>{
private final Iterable<Field> myFields;
private final Iterable<Method> myMethods;
public ObjectType(JVMFlags flags, String signature, String name, String outFilePath,
Iterable<Field> fields,
Iterable<Method> methods,
@NotNull Iterable<TypeRepr.ClassType> annotations,
@NotNull Iterable<Usage> usages) {
super(flags, signature, name, outFilePath, annotations, usages);
myFields = fields;
myMethods = methods;
}
public Iterable<Field> getFields() {
return myFields;
}
public Iterable<Method> getMethods() {
return myMethods;
}
}

View File

@@ -2,8 +2,11 @@
package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.dependency.diff.Difference;
import org.jetbrains.jps.dependency.java.TypeRepr.ClassType;
import java.util.Collections;
public class Proto {
private final JVMFlags access;
private final String signature;
@@ -92,4 +95,33 @@ public class Proto {
return false;
}
public class Diff<V extends Proto> implements Difference {
protected final V myPast;
public Diff(V past) {
myPast = past;
}
@Override
public boolean unchanged() {
return myPast.getFlags().equals(getFlags()) && signature().unchanged() && annotations().unchanged();
}
public JVMFlags getAddedFlags() {
return getFlags().deriveAdded(myPast.getFlags());
}
public JVMFlags getRemovedFlags() {
return getFlags().deriveRemoved(myPast.getFlags());
}
public Specifier<String, ?> signature() {
return Difference.diff(Collections.singleton(myPast.getSignature()), Collections.singleton(getSignature()));
}
public Specifier<ClassType, ?> annotations() {
return Difference.diff(myPast.getAnnotations(), getAnnotations());
}
}
}

View File

@@ -4,6 +4,8 @@ package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public abstract class ProtoMember extends Proto {
@NotNull
private final TypeRepr type;
@@ -23,4 +25,24 @@ public abstract class ProtoMember extends Proto {
public @Nullable Object getValue() {
return value;
}
public class Diff<V extends ProtoMember> extends Proto.Diff<V> {
public Diff(V past) {
super(past);
}
@Override
public boolean unchanged() {
return super.unchanged() && !typeChanged() && !valueChanged();
}
public boolean typeChanged() {
return !Objects.equals(myPast.getType(), getType());
}
public boolean valueChanged() {
return !Objects.equals(myPast.getValue(), getValue());
}
}
}

View File

@@ -2,6 +2,7 @@
package org.jetbrains.jps.dependency.java;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.dependency.Usage;
import org.jetbrains.jps.javac.Iterators;
import org.jetbrains.org.objectweb.asm.Type;
@@ -16,6 +17,10 @@ public abstract class TypeRepr {
public abstract int hashCode();
public Iterable<Usage> getUsages() {
return Collections.emptyList();
}
public static class PrimitiveType extends TypeRepr {
@NotNull
@@ -62,6 +67,15 @@ public abstract class TypeRepr {
myJvmName = jvmName;
}
public String getJvmName() {
return myJvmName;
}
@Override
public Iterable<Usage> getUsages() {
return Collections.singleton(new ClassUsage(myJvmName));
}
@Override
public @NotNull String getDescriptor() {
return "L" + myJvmName + ";";
@@ -104,6 +118,19 @@ public abstract class TypeRepr {
return "[" + myElementType.getDescriptor();
}
public TypeRepr getDeepElementType() {
TypeRepr current = this;
while (current instanceof ArrayType) {
current = ((ArrayType)current).myElementType;
}
return current;
}
@Override
public Iterable<Usage> getUsages() {
return getDeepElementType().getUsages();
}
@Override
public boolean equals(Object o) {
if (this == o) {