Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Aggregator, TypeAssigner, LocalNameStandardizer #741

Merged
merged 25 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1007485
cleanup srcType..
swissiety Nov 3, 2023
3d7a38c
add test input and enable interceptors
swissiety Nov 3, 2023
d0c977d
handle only operands that we can handle
swissiety Nov 3, 2023
1a1b66f
check if a Local is used *somewhere* not just on the rigth side of a …
swissiety Nov 3, 2023
5489b09
LocalNameStandardizer: dont rename Local if Type is not handled;
swissiety Nov 6, 2023
6a94685
fix: dont replace the rhs of a non-invoke completely by the aggregatee
swissiety Nov 6, 2023
7b5dc03
fix value replacement in aggregation via a hacky "ValueBox.canContain…
swissiety Nov 6, 2023
12dcf42
cleanup
swissiety Nov 6, 2023
894fbf9
adapting test to casts with non-field local names /add $ prefix
swissiety Nov 6, 2023
3e5e94b
fix test: adding name standardizer
swissiety Nov 6, 2023
316e537
SourceType.Application vs SourceType.Libarary works already with main…
swissiety Nov 6, 2023
b270b08
tame another class not in the view exception throwing; it seems typeh…
swissiety Nov 7, 2023
fadcf43
try if test is not executed because of naming convention
swissiety Nov 7, 2023
c4da4d4
ignore - previously ignored testcase
swissiety Nov 7, 2023
05afa28
fix Body.collectUses() signature to use value instead of LValue as e.…
swissiety Nov 8, 2023
62fa162
Merge branch 'develop' into fix/739_Aggregator
swissiety Nov 8, 2023
f557de2
fix porting bug in DeadAssignmentEliminator: checks now if a def/use …
swissiety Nov 9, 2023
080bfd7
cleanup
swissiety Nov 9, 2023
d462a66
fix early execution end conditions
swissiety Nov 9, 2023
0a10b59
work directly on essentialStmts while collecting uses - removes the n…
swissiety Nov 9, 2023
edbcc4b
break early
swissiety Nov 9, 2023
59fef4d
cleanup
swissiety Nov 9, 2023
94feb00
correct visibility
swissiety Nov 9, 2023
f684a98
dont print in testcase
swissiety Nov 9, 2023
cc8a35a
don't distinguish explicitly between incomplete/complete superclassesof
swissiety Nov 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions shared-test-resources/bugfixes/Issue739_Aggregator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
public class Issue739_Aggregator {
public static void main(String[] args) {
int a = Integer.valueOf(args[0]);
int b = a;
int c = b;
System.out.println(a + b + c);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ protected Stream<MethodSignature> resolveAllStaticInitializerCallsFromSourceMeth
classType ->
Stream.concat(
Stream.of(classType),
view.getTypeHierarchy().incompleteSuperClassesOf(classType).stream()))
view.getTypeHierarchy().superClassesOf(classType).stream()))
.filter(Objects::nonNull)
.map(classType -> view.getMethod(classType.getStaticInitializer()))
.filter(Optional::isPresent)
Expand All @@ -291,7 +291,7 @@ protected final <T extends Method> T findMethodInHierarchy(
if (optSc.isPresent()) {
SootClass<?> sc = optSc.get();

List<ClassType> superClasses = view.getTypeHierarchy().incompleteSuperClassesOf(sc.getType());
List<ClassType> superClasses = view.getTypeHierarchy().superClassesOf(sc.getType());
Set<ClassType> interfaces = view.getTypeHierarchy().implementedInterfacesOf(sc.getType());
superClasses.addAll(interfaces);

Expand Down Expand Up @@ -364,7 +364,7 @@ public CallGraph addClass(@Nonnull CallGraph oldCallGraph, @Nonnull JavaClassTyp
processWorkList(view, workList, processed, updated);

// Step 2: Add edges from old methods to methods overridden in the new class
List<ClassType> superClasses = view.getTypeHierarchy().incompleteSuperClassesOf(classType);
List<ClassType> superClasses = view.getTypeHierarchy().superClassesOf(classType);
Set<ClassType> implementedInterfaces =
view.getTypeHierarchy().implementedInterfacesOf(classType);
Stream<ClassType> superTypes =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.junit.Test;
import sootup.core.model.SootClass;
import sootup.core.model.SootMethod;
import sootup.core.model.SourceType;
import sootup.core.signatures.MethodSignature;
import sootup.java.bytecode.inputlocation.JavaClassPathAnalysisInputLocation;
import sootup.java.core.JavaIdentifierFactory;
Expand Down Expand Up @@ -36,7 +37,7 @@ private JavaView createViewForClassPath(String classPath, boolean useSourceCodeF
JavaProject.builder(new JavaLanguage(8))
.addInputLocation(
new JavaClassPathAnalysisInputLocation(
System.getProperty("java.home") + "/lib/rt.jar"));
System.getProperty("java.home") + "/lib/rt.jar", SourceType.Library));
if (useSourceCodeFrontend) {
javaProjectBuilder.addInputLocation(new JavaSourcePathAnalysisInputLocation(classPath));
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,10 @@ public Local getBase() {
@Override
@Nonnull
public List<Value> getUses() {
List<Value> list = new ArrayList<>();

List<? extends Value> args = getArgs();
if (args != null) {
list.addAll(args);
for (Value arg : args) {
list.addAll(arg.getUses());
}
List<Value> list = new ArrayList<>(args);
for (Value arg : args) {
list.addAll(arg.getUses());
}
list.addAll(base.getUses());
list.add(base);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ public List<LValue> getDefs() {
public final List<Value> getUses() {
final List<Value> defsuses = getLeftOp().getUses();
final Value rightOp = getRightOp();
final List<Value> uses = rightOp.getUses();
List<Value> list = new ArrayList<>(defsuses.size() + uses.size() + 1);
final List<Value> rightOpUses = rightOp.getUses();
List<Value> list = new ArrayList<>(defsuses.size() + rightOpUses.size() + 1);
list.addAll(defsuses);
list.add(rightOp);
list.addAll(uses);
list.addAll(rightOpUses);
return list;
}

Expand Down
23 changes: 12 additions & 11 deletions sootup.core/src/main/java/sootup/core/model/Body.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@
import java.util.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import sootup.core.graph.*;
import sootup.core.graph.MutableBlockStmtGraph;
import sootup.core.graph.MutableStmtGraph;
import sootup.core.graph.StmtGraph;
import sootup.core.jimple.basic.*;
import sootup.core.jimple.common.ref.*;
import sootup.core.jimple.common.ref.JParameterRef;
import sootup.core.jimple.common.ref.JThisRef;
import sootup.core.jimple.common.stmt.*;
import sootup.core.signatures.MethodSignature;
import sootup.core.util.Copyable;
Expand Down Expand Up @@ -597,19 +600,17 @@
* @param stmts The searched list of statements
* @return A map of Locals and their using statements
*/
public static Map<LValue, Collection<Stmt>> collectUses(Collection<Stmt> stmts) {
Map<LValue, Collection<Stmt>> allUses = new HashMap<>();
public static Map<Value, Collection<Stmt>> collectUses(Collection<Stmt> stmts) {
Map<Value, Collection<Stmt>> allUses = new HashMap<>();

Check warning on line 604 in sootup.core/src/main/java/sootup/core/model/Body.java

View check run for this annotation

Codecov / codecov/patch

sootup.core/src/main/java/sootup/core/model/Body.java#L604

Added line #L604 was not covered by tests
for (Stmt stmt : stmts) {
Collection<Value> uses = stmt.getUses();
for (Value value : uses) {
if (value instanceof LValue) {
Collection<Stmt> localUses = allUses.get(value);
if (localUses == null) {
localUses = new ArrayList<>();
}
localUses.add(stmt);
allUses.put((LValue) value, localUses);
Collection<Stmt> localUses = allUses.get(value);

Check warning on line 608 in sootup.core/src/main/java/sootup/core/model/Body.java

View check run for this annotation

Codecov / codecov/patch

sootup.core/src/main/java/sootup/core/model/Body.java#L608

Added line #L608 was not covered by tests
if (localUses == null) {
localUses = new ArrayList<>();

Check warning on line 610 in sootup.core/src/main/java/sootup/core/model/Body.java

View check run for this annotation

Codecov / codecov/patch

sootup.core/src/main/java/sootup/core/model/Body.java#L610

Added line #L610 was not covered by tests
}
localUses.add(stmt);
allUses.put(value, localUses);

Check warning on line 613 in sootup.core/src/main/java/sootup/core/model/Body.java

View check run for this annotation

Codecov / codecov/patch

sootup.core/src/main/java/sootup/core/model/Body.java#L612-L613

Added lines #L612 - L613 were not covered by tests
}
}
return allUses;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,7 @@ public static boolean canDispatch(
private static List<SootClass<?>> findSuperClassesInclusive(
View<? extends SootClass<?>> view, ClassType classType) {
return Stream.concat(
Stream.of(classType),
view.getTypeHierarchy().incompleteSuperClassesOf(classType).stream())
Stream.of(classType), view.getTypeHierarchy().superClassesOf(classType).stream())
.flatMap(t -> view.getClass(t).map(Stream::of).orElseGet(Stream::empty))
.collect(Collectors.toList());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ default boolean isSubtype(@Nonnull Type supertype, @Nonnull Type potentialSubtyp
return (supertypeName.equals("java.lang.Object")
&& !potentialSubtypeName.equals("java.lang.Object"))
|| supertype.equals(superClassOf((ClassType) potentialSubtype))
|| incompleteSuperClassesOf((ClassType) potentialSubtype).contains(supertype)
|| superClassesOf((ClassType) potentialSubtype).contains(supertype)
|| implementedInterfacesOf((ClassType) potentialSubtype).contains(supertype);
} else if (potentialSubtype instanceof ArrayType) {
// Arrays are subtypes of java.lang.Object, java.io.Serializable and java.lang.Cloneable
Expand All @@ -169,27 +169,12 @@ default boolean isSubtype(@Nonnull Type supertype, @Nonnull Type potentialSubtyp
}
}

/**
* Returns all superclasses of <code>classType</code> up to <code>java.lang.Object</code>, which
* will be the last entry in the list. i.e. its ordered from bottom level to top level.
*/
@Nonnull
default List<ClassType> superClassesOf(@Nonnull ClassType classType) {
List<ClassType> superClasses = new ArrayList<>();
ClassType currentSuperClass = superClassOf(classType);
while (currentSuperClass != null) {
superClasses.add(currentSuperClass);
currentSuperClass = superClassOf(currentSuperClass);
}
return superClasses;
}

/**
* Returns all superclasses of <code>classType</code> up to <code>java.lang.Object</code>, which
* will be the last entry in the list, or till one of the superclasses is not contained in view.
*/
@Nonnull
default List<ClassType> incompleteSuperClassesOf(@Nonnull ClassType classType) {
default List<ClassType> superClassesOf(@Nonnull ClassType classType) {
List<ClassType> superClasses = new ArrayList<>();
ClassType currentSuperClass = null;
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
import javax.annotation.Nullable;
import org.jgrapht.Graph;
import org.jgrapht.graph.SimpleDirectedGraph;
import sootup.core.frontend.ResolveException;
import sootup.core.model.SootClass;
import sootup.core.typehierarchy.ViewTypeHierarchy.ScanResult.Edge;
import sootup.core.typehierarchy.ViewTypeHierarchy.ScanResult.EdgeType;
Expand Down Expand Up @@ -67,10 +66,10 @@
public Set<ClassType> implementersOf(@Nonnull ClassType interfaceType) {
Vertex vertex = lazyScanResult.get().typeToVertex.get(interfaceType);
if (vertex == null) {
throw new ResolveException("Could not find " + interfaceType + " in hierarchy.");
throw new IllegalArgumentException("Could not find '" + interfaceType + "' in hierarchy.");

Check warning on line 69 in sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java

View check run for this annotation

Codecov / codecov/patch

sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java#L69

Added line #L69 was not covered by tests
}
if (vertex.type != VertexType.Interface) {
throw new IllegalArgumentException(interfaceType + " is not an interface.");
throw new IllegalArgumentException("'" + interfaceType + "' is not an interface.");

Check warning on line 72 in sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java

View check run for this annotation

Codecov / codecov/patch

sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java#L72

Added line #L72 was not covered by tests
}
return subtypesOf(interfaceType);
}
Expand All @@ -80,10 +79,10 @@
public Set<ClassType> subclassesOf(@Nonnull ClassType classType) {
Vertex vertex = lazyScanResult.get().typeToVertex.get(classType);
if (vertex == null) {
throw new ResolveException("Could not find " + classType + " in hierarchy.");
throw new IllegalArgumentException("Could not find '" + classType + "' in hierarchy.");

Check warning on line 82 in sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java

View check run for this annotation

Codecov / codecov/patch

sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java#L82

Added line #L82 was not covered by tests
}
if (vertex.type != VertexType.Class) {
throw new IllegalArgumentException(classType + " is not a class.");
throw new IllegalArgumentException("'" + classType + "' is not a class.");

Check warning on line 85 in sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java

View check run for this annotation

Codecov / codecov/patch

sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java#L85

Added line #L85 was not covered by tests
}
return subtypesOf(classType);
}
Expand All @@ -94,7 +93,7 @@
ScanResult scanResult = lazyScanResult.get();
Vertex vertex = scanResult.typeToVertex.get(type);
if (vertex == null) {
throw new ResolveException("Could not find " + type + " in hierarchy.");
throw new IllegalArgumentException("Could not find '" + type + "' in hierarchy.");
}

Set<ClassType> subclasses = new HashSet<>();
Expand All @@ -110,7 +109,7 @@
ScanResult scanResult = lazyScanResult.get();
Vertex vertex = scanResult.typeToVertex.get(type);
if (vertex == null) {
throw new ResolveException("Could not find " + type + " in hierarchy.");
throw new IllegalArgumentException("Could not find '" + type + "' in hierarchy.");

Check warning on line 112 in sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java

View check run for this annotation

Codecov / codecov/patch

sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java#L112

Added line #L112 was not covered by tests
}

Set<ClassType> subclasses = new HashSet<>();
Expand Down Expand Up @@ -141,7 +140,7 @@
}

@Nonnull
public List<Vertex> superClassesOf(@Nonnull Vertex classVertex, boolean includingSelf) {
protected List<Vertex> superClassesOf(@Nonnull Vertex classVertex, boolean includingSelf) {
ScanResult scanResult = lazyScanResult.get();
Graph<Vertex, Edge> graph = scanResult.graph;

Expand All @@ -167,21 +166,21 @@
return superClasses;
}

public Stream<Vertex> directlyImplementedInterfacesOf(@Nonnull Vertex classVertex) {
protected Stream<Vertex> directlyImplementedInterfacesOf(@Nonnull Vertex classVertex) {
Graph<Vertex, Edge> graph = lazyScanResult.get().graph;
return graph.outgoingEdgesOf(classVertex).stream()
.filter(edge -> edge.type == EdgeType.ClassDirectlyImplements)
.map(graph::getEdgeTarget);
}

public Stream<Vertex> directlyExtendedInterfacesOf(@Nonnull Vertex interfaceVertex) {
protected Stream<Vertex> directlyExtendedInterfacesOf(@Nonnull Vertex interfaceVertex) {
Graph<Vertex, Edge> graph = lazyScanResult.get().graph;
return graph.outgoingEdgesOf(interfaceVertex).stream()
.filter(edge -> edge.type == EdgeType.InterfaceDirectlyExtends)
.map(graph::getEdgeTarget);
}

public Stream<Vertex> directSuperClassOf(@Nonnull Vertex classVertex) {
protected Stream<Vertex> directSuperClassOf(@Nonnull Vertex classVertex) {
Graph<Vertex, Edge> graph = lazyScanResult.get().graph;
return graph.outgoingEdgesOf(classVertex).stream()
.filter(edge -> edge.type == EdgeType.ClassDirectlyExtends)
Expand All @@ -191,7 +190,7 @@
public Set<ClassType> directlyImplementedInterfacesOf(@Nonnull ClassType classType) {
Vertex vertex = lazyScanResult.get().typeToVertex.get(classType);
if (vertex == null) {
throw new IllegalStateException("Could not find '" + classType + "' in hierarchy.");
throw new IllegalArgumentException("Could not find '" + classType + "' in hierarchy.");

Check warning on line 193 in sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java

View check run for this annotation

Codecov / codecov/patch

sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java#L193

Added line #L193 was not covered by tests
}
if (vertex.type != VertexType.Class) {
throw new IllegalArgumentException(classType + " is not a class.");
Expand All @@ -205,7 +204,7 @@
public Set<ClassType> directlyExtendedInterfacesOf(@Nonnull ClassType interfaceType) {
Vertex vertex = lazyScanResult.get().typeToVertex.get(interfaceType);
if (vertex == null) {
throw new IllegalStateException("Could not find " + interfaceType + " in hierarchy.");
throw new IllegalArgumentException("Could not find " + interfaceType + " in hierarchy.");

Check warning on line 207 in sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java

View check run for this annotation

Codecov / codecov/patch

sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java#L207

Added line #L207 was not covered by tests
}
if (vertex.type != VertexType.Interface) {
throw new IllegalArgumentException(interfaceType + " is not a class.");
Expand All @@ -229,7 +228,7 @@
public ClassType directSuperClassOf(@Nonnull ClassType classType) {
Vertex vertex = lazyScanResult.get().typeToVertex.get(classType);
if (vertex == null) {
throw new IllegalStateException("Could not find " + classType + " in hierarchy.");
throw new IllegalArgumentException("Could not find " + classType + " in hierarchy.");

Check warning on line 231 in sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java

View check run for this annotation

Codecov / codecov/patch

sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java#L231

Added line #L231 was not covered by tests
}
Graph<Vertex, Edge> graph = lazyScanResult.get().graph;
List<Vertex> list =
Expand All @@ -242,7 +241,7 @@
/* is java.lang.Object */
return null;
} else if (list.size() > 1) {
throw new RuntimeException(classType + "cannot have multiple superclasses");
throw new IllegalArgumentException(classType + "cannot have multiple superclasses");

Check warning on line 244 in sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java

View check run for this annotation

Codecov / codecov/patch

sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java#L244

Added line #L244 was not covered by tests
} else {
return list.get(0).javaClassType;
}
Expand All @@ -255,7 +254,8 @@
Vertex vertex = scanResult.typeToVertex.get(type);

if (vertex == null) {
throw new IllegalStateException("Could not find " + type + " in hierarchy for view " + view);
throw new IllegalArgumentException(

Check warning on line 257 in sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java

View check run for this annotation

Codecov / codecov/patch

sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java#L257

Added line #L257 was not covered by tests
"Could not find " + type + " in hierarchy for view " + view);
}

switch (vertex.type) {
Expand Down Expand Up @@ -299,21 +299,25 @@
@Nullable
@Override
public ClassType superClassOf(@Nonnull ClassType classType) {
return sootClassFor(classType).getSuperclass().orElse(null);
final Optional<? extends SootClass<?>> classOpt = view.getClass(classType);
if (!classOpt.isPresent()) {
throw new IllegalArgumentException("Could not find '" + classType + "' in the view.");
}
return classOpt.get().getSuperclass().orElse(null);
}

public boolean isInterface(@Nonnull ClassType type) {
Vertex vertex = lazyScanResult.get().typeToVertex.get(type);
if (vertex == null) {
throw new RuntimeException("Could not find " + type + " in hierarchy.");
throw new IllegalArgumentException("Could not find '" + type + "' in hierarchy.");

Check warning on line 312 in sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java

View check run for this annotation

Codecov / codecov/patch

sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java#L312

Added line #L312 was not covered by tests
}
return vertex.type == VertexType.Interface;
}

public boolean isClass(@Nonnull ClassType type) {
Vertex vertex = lazyScanResult.get().typeToVertex.get(type);
if (vertex == null) {
throw new RuntimeException("Could not find " + type + " in hierarchy.");
throw new IllegalArgumentException("Could not find '" + type + "' in hierarchy.");

Check warning on line 320 in sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java

View check run for this annotation

Codecov / codecov/patch

sootup.core/src/main/java/sootup/core/typehierarchy/ViewTypeHierarchy.java#L320

Added line #L320 was not covered by tests
}
return vertex.type == VertexType.Class;
}
Expand Down Expand Up @@ -416,19 +420,14 @@
return interfaceVertex;
}

@Nonnull
private SootClass<?> sootClassFor(@Nonnull ClassType classType) {
return view.getClassOrThrow(classType);
}

@Override
public void addType(@Nonnull SootClass<?> sootClass) {
ScanResult scanResult = lazyScanResult.get();
addSootClassToGraph(sootClass, scanResult.typeToVertex, scanResult.graph);
}

/** Holds a vertex for each {@link ClassType} encountered during the scan. */
static class ScanResult {
protected static class ScanResult {

enum VertexType {
Class,
Expand All @@ -439,7 +438,7 @@
* @see #javaClassType
* @see #type
*/
static class Vertex {
protected static class Vertex {
@Nonnull final ClassType javaClassType;
@Nonnull final VertexType type;

Expand All @@ -449,7 +448,7 @@
}
}

enum EdgeType {
protected enum EdgeType {
/** Edge to an interface vertex this interface extends directly, non-transitively. */
InterfaceDirectlyExtends,
/** Edge to an interface extending this interface directly, non-transitively. */
Expand All @@ -459,7 +458,7 @@
}

/** @see #type */
static class Edge {
protected static class Edge {
@Nonnull final EdgeType type;

Edge(@Nonnull EdgeType type) {
Expand Down
Loading