From 45c1075b2439ec9dfbe00e44038e46d4802a1597 Mon Sep 17 00:00:00 2001 From: Jared <72281577+jachiaram@users.noreply.github.com> Date: Wed, 7 Aug 2024 13:17:23 +0200 Subject: [PATCH] type hierarchy construction method added --- src/main/java/org/tudo/sse/Main.java | 6 +- .../java/org/tudo/sse/model/Artifact.java | 146 +++++++++++++++++- .../org/tudo/sse/model/jar/ClassFileNode.java | 35 +++++ .../org/tudo/sse/model/jar/FoundInfoNode.java | 34 ++++ .../org/tudo/sse/model/jar/NotFoundNode.java | 8 + .../org/tudo/sse/model/TypeStructureTest.java | 122 +++++++++++++++ src/test/resources/TypeStructure.json | 67 ++++++++ 7 files changed, 416 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/tudo/sse/model/jar/ClassFileNode.java create mode 100644 src/main/java/org/tudo/sse/model/jar/FoundInfoNode.java create mode 100644 src/main/java/org/tudo/sse/model/jar/NotFoundNode.java create mode 100644 src/test/java/org/tudo/sse/model/TypeStructureTest.java create mode 100644 src/test/resources/TypeStructure.json diff --git a/src/main/java/org/tudo/sse/Main.java b/src/main/java/org/tudo/sse/Main.java index 5c6d1ae..bb2568a 100644 --- a/src/main/java/org/tudo/sse/Main.java +++ b/src/main/java/org/tudo/sse/Main.java @@ -3,12 +3,16 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.tudo.sse.model.Artifact; -import org.tudo.sse.resolution.PomResolutionException; +import org.tudo.sse.model.ArtifactIdent; +import org.tudo.sse.model.jar.ClassFileNode; +import org.tudo.sse.model.jar.FoundInfoNode; +import org.tudo.sse.resolution.*; import org.tudo.sse.utils.GAUtils; import java.io.IOException; import java.net.URISyntaxException; import java.util.List; +import java.util.Map; public class Main { diff --git a/src/main/java/org/tudo/sse/model/Artifact.java b/src/main/java/org/tudo/sse/model/Artifact.java index 0c6a785..dbb415e 100644 --- a/src/main/java/org/tudo/sse/model/Artifact.java +++ b/src/main/java/org/tudo/sse/model/Artifact.java @@ -1,8 +1,18 @@ package org.tudo.sse.model; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.tudo.sse.Main; import org.tudo.sse.model.index.IndexInformation; -import org.tudo.sse.model.jar.JarInformation; +import org.tudo.sse.model.jar.*; import org.tudo.sse.model.pom.PomInformation; +import org.tudo.sse.resolution.JarResolutionException; +import org.tudo.sse.resolution.JarResolver; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * This class holds all the artifact information. For each artifact index, jar, and pom information can be defined. @@ -13,6 +23,8 @@ public class Artifact { * The identifier object for the artifact. */ public final ArtifactIdent ident; + public static final Logger log = LogManager.getLogger(Artifact.class); + /** * A secondary identifier, for if its pom information has been moved on the maven central repository. */ @@ -75,4 +87,136 @@ public void setJarInformation(JarInformation jarInformation) { this.jarInformation = jarInformation; } + + public Map buildTypeStructure() { + Map roots = new HashMap<>(); + roots.put("java/lang/Object", new NotFoundNode(new ObjType(0, "java/lang/Object", "java/lang"))); + if(jarInformation != null) { + Map> packages = jarInformation.getPackages(); + Map depArts = new HashMap<>(); + + if(pomInformation != null) { + JarResolver resolver = new JarResolver(); + for(Artifact artifact : pomInformation.getEffectiveTransitiveDependencies()) { + try { + artifact.setJarInformation(resolver.parseJar(artifact.getIdent()).getJarInformation()); + depArts.put(artifact.getIdent().getGroupID() + ":" + artifact.getIdent().getArtifactID(), artifact); + } catch (JarResolutionException e) { + log.error(e); + } + } + } + + for(Map.Entry> classes : packages.entrySet()) { + for(ClassFile clase : classes.getValue()) { + resolveNode(roots, clase, packages, depArts); + } + } + } + return roots; + } + + private ClassFileNode resolveNode(Map root, ClassFile clase, Map> packages, Map depArts) { + ClassFileNode node = new FoundInfoNode(clase.getAccessFlags(), clase.getThistype(), clase.getVersion()); + + if(clase.getSuperType() != null) { + resolveSuperClass(root, node, clase, packages, depArts); + } + + if(!clase.getInterfaceTypes().isEmpty()) { + resolveInterfaces(root, node, clase, packages, depArts); + } + + return node; + } + + private void resolveSuperClass(Map roots, ClassFileNode node, ClassFile clase, Map> packages, Map depArts) { + String packName = clase.getSuperType().getPackageName(); + + //Check if the superclass can be found in the root map + if(roots.containsKey(clase.getSuperType().getFqn())) { + node.setSuperClass(roots.get(clase.getSuperType().getFqn())); + roots.get(clase.getSuperType().getFqn()).addChild(node); + } + //Check if + else if(packages.containsKey(packName)) { + List toLookThrough = packages.get(packName); + for(ClassFile cls : toLookThrough) { + if(cls.getThistype().getFqn().equals(clase.getSuperType().getFqn())) { + node.setSuperClass(resolveNode(roots, cls, packages, depArts)); + node.getSuperClass().addChild(node); + break; + } + } + } else { + boolean found = false; + + for(Map.Entry entry : depArts.entrySet()) { + if(entry.getValue().getJarInformation().getPackages().containsKey(packName)) { + for(ClassFile depClass : entry.getValue().getJarInformation().getPackages().get(packName)) { + if(depClass.getThistype().getFqn().equals(clase.getSuperType().getFqn())) { + found = true; + node.setSuperClass(resolveNode(roots, depClass, entry.getValue().getJarInformation().getPackages(), depArts)); + node.getSuperClass().addChild(node); + break; + } + } + if(found) { + break; + } + } + } + + //add a new root to the map + if(!found) { + node.setSuperClass(new NotFoundNode(clase.getSuperType())); + node.getSuperClass().addChild(node); + roots.put(node.getSuperClass().getThistype().getFqn(), node.getSuperClass()); + } + } + } + + private void resolveInterfaces(Map roots, ClassFileNode node, ClassFile clase, Map> packages, Map depArts) { + for(ObjType itfe : clase.getInterfaceTypes()) { + String packName = itfe.getPackageName(); + + if(packages.containsKey(packName)) { + List toLookThrough = packages.get(packName); + for(ClassFile cls : toLookThrough) { + if(cls.getThistype().getFqn().equals(itfe.getFqn())) { + ClassFileNode resolved = resolveNode(roots, cls, packages, depArts); + resolved.addChild(node); + ((FoundInfoNode) node).addInterfaceNode(resolved); + break; + } + } + } else { + boolean found = false; + + for(Map.Entry entry : depArts.entrySet()) { + if(entry.getValue().getJarInformation().getPackages().containsKey(packName)) { + for(ClassFile depClass : entry.getValue().getJarInformation().getPackages().get(packName)) { + if(depClass.getThistype().getFqn().equals(itfe.getFqn())) { + found = true; + ClassFileNode resolved = resolveNode(roots, depClass, entry.getValue().getJarInformation().getPackages(), depArts); + resolved.addChild(node); + ((FoundInfoNode) node).addInterfaceNode(resolved); + break; + } + } + if(found) { + break; + } + } + } + + if(!found) { + ClassFileNode notFound = new NotFoundNode(itfe); + ((FoundInfoNode) node).addInterfaceNode(notFound); + notFound.addChild(node); + } + } + } + } + } diff --git a/src/main/java/org/tudo/sse/model/jar/ClassFileNode.java b/src/main/java/org/tudo/sse/model/jar/ClassFileNode.java new file mode 100644 index 0000000..04f164e --- /dev/null +++ b/src/main/java/org/tudo/sse/model/jar/ClassFileNode.java @@ -0,0 +1,35 @@ +package org.tudo.sse.model.jar; + +import java.util.ArrayList; +import java.util.List; + +public class ClassFileNode { + private final ObjType thistype; + private ClassFileNode superclass; + private List children; + + public ClassFileNode(ObjType thistype) { + this.thistype = thistype; + children = new ArrayList<>(); + } + + public ClassFileNode getSuperClass() { + return superclass; + } + + public void setSuperClass(ClassFileNode superclass) { + this.superclass = superclass; + } + + public ObjType getThistype() { + return thistype; + } + + public List getChildren() { + return children; + } + + public void addChild(ClassFileNode child) { + children.add(child); + } +} diff --git a/src/main/java/org/tudo/sse/model/jar/FoundInfoNode.java b/src/main/java/org/tudo/sse/model/jar/FoundInfoNode.java new file mode 100644 index 0000000..f319c07 --- /dev/null +++ b/src/main/java/org/tudo/sse/model/jar/FoundInfoNode.java @@ -0,0 +1,34 @@ +package org.tudo.sse.model.jar; + +import java.util.ArrayList; +import java.util.List; + +public class FoundInfoNode extends ClassFileNode { + private final int accessFlags; + private final long version; + private List interfaceNodes; + + + public FoundInfoNode(int accessFlags, ObjType thisType, long version) { + super(thisType); + this.accessFlags = accessFlags; + this.version = version; + interfaceNodes = new ArrayList<>(); + } + + public int getAccessFlags() { + return accessFlags; + } + + public long getVersion() { + return version; + } + + public List getInterfaceNodes() { + return interfaceNodes; + } + + public void addInterfaceNode(ClassFileNode interfaceNode) { + interfaceNodes.add(interfaceNode); + } +} diff --git a/src/main/java/org/tudo/sse/model/jar/NotFoundNode.java b/src/main/java/org/tudo/sse/model/jar/NotFoundNode.java new file mode 100644 index 0000000..ef1adad --- /dev/null +++ b/src/main/java/org/tudo/sse/model/jar/NotFoundNode.java @@ -0,0 +1,8 @@ +package org.tudo.sse.model.jar; + +public class NotFoundNode extends ClassFileNode { + + public NotFoundNode(ObjType thistype) { + super(thistype); + } +} diff --git a/src/test/java/org/tudo/sse/model/TypeStructureTest.java b/src/test/java/org/tudo/sse/model/TypeStructureTest.java new file mode 100644 index 0000000..38fe494 --- /dev/null +++ b/src/test/java/org/tudo/sse/model/TypeStructureTest.java @@ -0,0 +1,122 @@ +package org.tudo.sse.model; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.Test; +import org.tudo.sse.ArtifactFactory; +import org.tudo.sse.model.jar.ClassFileNode; +import org.tudo.sse.model.jar.FoundInfoNode; +import org.tudo.sse.resolution.*; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +class TypeStructureTest { + + JarResolver resolver = new JarResolver(); + PomResolver pomResolver = new PomResolver(true); + Map json; + Gson gson = new Gson(); + + private static final Logger log = LogManager.getLogger(TypeStructureTest.class); + + { + InputStream resource = this.getClass().getClassLoader().getResourceAsStream("TypeStructure.json"); + assert resource != null; + Reader targetReader = new InputStreamReader(resource); + json = gson.fromJson(targetReader, new TypeToken>() {}.getType()); + } + + @Test + void buildTypeStructure() { + + Artifact jarArt1; + ArtifactIdent ident = new ArtifactIdent("org.springframework", "spring-web", "6.1.11"); + Artifact jarArt2; + jarArt2 = ArtifactFactory.getArtifact(ident); + try { + jarArt1 = resolver.parseJar(new ArtifactIdent("org.bouncycastle", "bcpg-lts8on", "2.73.6")); + jarArt2 = resolver.parseJar(ident); + pomResolver.resolveArtifact(ident); + } catch (JarResolutionException | PomResolutionException | FileNotFoundException | IOException e) { + throw new RuntimeException(e); + } + + ClassFileNode results1 = jarArt1.buildTypeStructure().get("java/lang/Object"); + Map expected1 = (Map) json.get("artifact1"); + + singleChild(results1, (Map) expected1.get("test1")); + interfacesCheck(results1, (Map) expected1.get("test2")); + interfacesCheck(results1, (Map) expected1.get("test3")); + + Map results2 = jarArt2.buildTypeStructure(); + Map expected2 = (Map) json.get("artifact2"); + + + multipleChildren(results2.get("reactor/core/observability/DefaultSignalListener"), (Map) expected2.get("test1")); + multiLevelChild(results2.get("java/lang/RuntimeException"), (Map) expected2.get("test2")); + interfacesCheck(results2.get("java/lang/Object"), (Map) expected2.get("test3")); + + } + + ClassFileNode findNode(ClassFileNode root, String toFind) { + + for(ClassFileNode cur : root.getChildren()) { + if(cur.getThistype().getFqn().equals(toFind)) { + return cur; + } + } + + throw new RuntimeException("Didn't find expected node"); + } + + void singleChild(ClassFileNode root, Map expected) { + ClassFileNode toTest = findNode(root, (String) expected.get("name")); + Map child = (Map) expected.get("child"); + assert(toTest.getChildren().get(0).getThistype().getFqn().equals((String) child.get("name"))); + } + + void interfacesCheck(ClassFileNode root, Map expected) { + ClassFileNode toTest = findNode(root, (String) expected.get("name")); + + List> expectedInterfaces = (List>) expected.get("interfaces"); + List interfaces = ((FoundInfoNode) toTest).getInterfaceNodes(); + + assertEquals(interfaces.size(), expectedInterfaces.size()); + + for(int i = 0; i < interfaces.size(); i++) { + assertEquals(interfaces.get(i).getThistype().getFqn(), expectedInterfaces.get(i).get("name")); + assertEquals(((FoundInfoNode) interfaces.get(i)).getSuperClass().getThistype().getFqn(), expectedInterfaces.get(i).get("super")); + } + } + + void multipleChildren(ClassFileNode root, Map expected) { + assertEquals(root.getThistype().getFqn(), expected.get("name")); + + List> children = (List>) expected.get("children"); + assertEquals(root.getChildren().size(), children.size()); + + for(int i = 0; i < root.getChildren().size(); i++) { + assertEquals(children.get(i).get("name"), root.getChildren().get(i).getThistype().getFqn()); + } + log.info("Got here"); + } + + void multiLevelChild(ClassFileNode root, Map expected) { + assertEquals(root.getThistype().getFqn(), expected.get("name")); + List> children = (List>) expected.get("children"); + assertEquals(root.getChildren().get(0).getThistype().getFqn(), children.get(0).get("name")); + List> secondLevel = (List>) children.get(0).get("children"); + assertEquals(root.getChildren().get(0).getChildren().get(0).getThistype().getFqn(), secondLevel.get(0).get("name")); + } + +} \ No newline at end of file diff --git a/src/test/resources/TypeStructure.json b/src/test/resources/TypeStructure.json new file mode 100644 index 0000000..a975106 --- /dev/null +++ b/src/test/resources/TypeStructure.json @@ -0,0 +1,67 @@ +{ + "artifact1" : { + "test1" : { + "name" : "org/bouncycastle/bcpg/UserAttributeSubpacket", + "child" : { + "name" : "org/bouncycastle/bcpg/attr/ImageAttribute" + } + }, + "test2" : { + "name" : "org/bouncycastle/openpgp/operator/bc/BcAEADUtil$1", + "interfaces" : [ + { + "name" : "org/bouncycastle/openpgp/operator/bc/BcAEADUtil$Engine", + "super" : "java/lang/Object" + } + ] + }, + "test3" : { + "name" : "org/bouncycastle/openpgp/PGPEncryptedData", + "interfaces" : [ + { + "name" : "org/bouncycastle/bcpg/SymmetricKeyAlgorithmTags", + "super" : "java/lang/Object" + }, + { + "name" : "org/bouncycastle/bcpg/AEADAlgorithmTags", + "super" : "java/lang/Object" + } + ] + } + }, + "artifact2" : { + "test1" : { + "name" : "reactor/core/observability/DefaultSignalListener", + "children" : [ + { + "name" : "org/springframework/web/filter/reactive/ServerHttpObservationFilter$ObservationSignalListener" + }, + { + "name" : "org/springframework/web/server/adapter/HttpWebHandlerAdapter$ObservationSignalListener" + } + ] + }, + "test2" : { + "name" : "java/lang/RuntimeException", + "children" : [ + { + "name" : "org/springframework/core/NestedRuntimeException", + "children" : [ + { + "name" : "org/springframework/beans/BeansException" + } + ] + } + ] + }, + "test3" : { + "name" : "org/springframework/web/context/request/AbstractRequestAttributesScope", + "interfaces" : [ + { + "name" : "org/springframework/beans/factory/config/Scope", + "super" : "java/lang/Object" + } + ] + } + } +} \ No newline at end of file