Skip to content

Commit a3faa41

Browse files
authored
Merge pull request gousiosg#12 from bitslab/maazur
Maazur
2 parents 78101b9 + 132932b commit a3faa41

File tree

3 files changed

+238
-23
lines changed

3 files changed

+238
-23
lines changed

src/main/java/gr/gousiosg/javacg/stat/JCallGraph.java

Lines changed: 224 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,31 @@
3737
import gr.gousiosg.javacg.stat.support.GitArguments;
3838
import gr.gousiosg.javacg.stat.support.RepoTool;
3939
import gr.gousiosg.javacg.stat.support.TestArguments;
40+
import org.apache.bcel.classfile.AnnotationEntry;
41+
import org.apache.bcel.classfile.ClassParser;
42+
import org.apache.bcel.classfile.JavaClass;
43+
import org.apache.bcel.classfile.Method;
44+
import org.apache.bcel.generic.Type;
4045
import org.eclipse.jgit.api.errors.GitAPIException;
4146
import org.eclipse.jgit.api.errors.JGitInternalException;
4247
import org.jgrapht.Graph;
4348
import org.jgrapht.graph.DefaultEdge;
4449
import org.slf4j.Logger;
4550
import org.slf4j.LoggerFactory;
4651
import org.xml.sax.SAXException;
52+
import org.yaml.snakeyaml.DumperOptions;
53+
import org.yaml.snakeyaml.Yaml;
4754

4855
import javax.xml.bind.JAXBException;
4956
import javax.xml.parsers.ParserConfigurationException;
5057
import java.io.*;
51-
import java.util.InputMismatchException;
52-
import java.util.List;
53-
import java.util.Map;
54-
import java.util.Optional;
58+
import java.util.*;
59+
import java.util.jar.JarEntry;
60+
import java.util.jar.JarFile;
61+
import java.util.jar.JarInputStream;
62+
import java.util.stream.Collectors;
63+
64+
import static java.util.Map.entry;
5565

5666
/**
5767
* Constructs a callgraph out of a JAR archive. Can combine multiple archives into a single call
@@ -92,17 +102,71 @@ public static void main(String[] args) {
92102
// Build and serialize a staticcallgraph object with jar files provided
93103
BuildArguments arguments = new BuildArguments(args);
94104
StaticCallgraph callgraph = StaticCallgraph.build(arguments);
105+
callgraph.JarEntry=arguments.getJars().get(0).first;
95106
maybeSerializeStaticCallGraph(callgraph, arguments);
96107
break;
97108
}
109+
case "buildyaml":{
110+
DumperOptions options = new DumperOptions();
111+
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
112+
options.setPrettyFlow(true);
113+
Yaml yaml = new Yaml(options);
114+
115+
JarInputStream jarFileStream = new JarInputStream(new FileInputStream(args[1]));
116+
JarFile jarFile = new JarFile(args[1]);
117+
118+
ArrayList<JarEntry> listOfAllClasses = getAllClassesFromJar(jarFileStream);
119+
ArrayList<Pair<String, String>> nameEntryList = new ArrayList<>();
120+
for (JarEntry entry : listOfAllClasses)
121+
nameEntryList.addAll(fetchAllMethodSignaturesForyaml(jarFile,entry));
122+
ArrayList<Map<String,String>> entryResult = new ArrayList<>();
123+
124+
for(Pair<String, String> entry : nameEntryList)
125+
entryResult.add(Map.ofEntries(entry("name",entry.first),entry("entryPoint",entry.second)));
126+
127+
Map<String, ArrayList<Map<String,String>>> dataMap = new HashMap<>();
128+
dataMap.put("properties",entryResult);
129+
final FileWriter writer = new FileWriter(args[2]+".yaml");
130+
yaml.dump(dataMap, writer);
131+
break;
132+
}
98133
case "test": {
99134
TestArguments arguments = new TestArguments(args);
135+
String entryPoint = null;
100136
// 1. Run Tests and obtain coverage
101137
RepoTool rt = maybeObtainTool(arguments);
102-
List<Pair<String, String>> coverageFilesAndEntryPoints = rt.obtainCoverageFilesAndEntryPoints();
138+
StaticCallgraph callgraph = deserializeStaticCallGraph(arguments);
139+
List<Pair<String, ?>> coverageFilesAndEntryPointsShorthand = rt.obtainCoverageFilesAndEntryPoints();
140+
List<Pair<String, String>> coverageFilesAndEntryPoints=new ArrayList<>();
141+
for(Pair<String, ?> s : coverageFilesAndEntryPointsShorthand) {
142+
Pair<String,String> result=new Pair<>(s.first,null);
143+
if(s.second instanceof String){
144+
entryPoint= (String) s.second;
145+
}
146+
else if(s.second instanceof ArrayList){
147+
try {
148+
Optional<String> returnType = Optional.empty();
149+
if(((ArrayList) s.second).size() > 1)
150+
returnType = Optional.of(((ArrayList) s.second).get(1).toString());
151+
152+
// Seventh argument, optional, parameter types of expected method
153+
Optional<String> paramterTypes = Optional.empty();
154+
if(((ArrayList) s.second).size() > 2)
155+
paramterTypes = Optional.of(((ArrayList) s.second).get(2).toString());
156+
157+
entryPoint = generateEntryPoint(callgraph.JarEntry, (String) ((ArrayList) s.second).get(0), returnType, paramterTypes);
158+
} catch(IOException e){
159+
LOGGER.error("Could not generate method signature", e);
160+
}
161+
}
162+
LOGGER.info("Entry point inferred for name \n"+s.first.substring(s.first.lastIndexOf("/")+1)+" is\n "+entryPoint);
163+
result.second=entryPoint;
164+
coverageFilesAndEntryPoints.add(result);
165+
}
166+
103167
for(Pair<String, String> s : coverageFilesAndEntryPoints) {
104168
// 2. For each coverage file we start with a fresh deserialized callgraph
105-
StaticCallgraph callgraph = deserializeStaticCallGraph(arguments);
169+
callgraph = deserializeStaticCallGraph(arguments);
106170
LOGGER.info("----------PROPERTY------------");
107171
String propertyName = s.first.substring(s.first.lastIndexOf("/") + 1, s.first.length() - 4);
108172
LOGGER.info(propertyName);
@@ -149,6 +213,126 @@ public static void main(String[] args) {
149213

150214
}
151215

216+
//Main function to convert class.method arg and generate its respective method signature
217+
public static String generateEntryPoint(String jarPath, String shortName, Optional<String> returnType, Optional<String> parameterTypes) throws IOException {
218+
JarFile jarFile = new JarFile(jarPath);
219+
JarInputStream jarFileStream = new JarInputStream(new FileInputStream(jarPath));
220+
221+
String methodName = shortName.substring(shortName.lastIndexOf('.') + 1);
222+
String className = shortName.substring(0, shortName.lastIndexOf('.'));
223+
ArrayList<JarEntry> listOfFilteredClasses = getAllClassesFromJar(jarFileStream);
224+
className = className.replaceAll("\\.", "/") + ".class";
225+
226+
listOfFilteredClasses = getFilteredClassesFromJar(listOfFilteredClasses, className);
227+
228+
if(listOfFilteredClasses.size() > 1) {
229+
LOGGER.error("Multiple class instances found as listed below:- ");
230+
for(JarEntry entry : listOfFilteredClasses)
231+
LOGGER.error(entry.getName());
232+
System.exit(1);
233+
}
234+
if(listOfFilteredClasses.size()==0){
235+
LOGGER.error("no class instances found ");
236+
System.exit(1);
237+
}
238+
239+
return fetchMethodSignatures(jarFile, listOfFilteredClasses.get(0), methodName, returnType, parameterTypes);
240+
241+
}
242+
243+
//Fetch JarEntry of all classes in a Jan using JarInputStream
244+
public static ArrayList<JarEntry> getAllClassesFromJar(JarInputStream JarInputStream) throws IOException {
245+
JarEntry jar;
246+
ArrayList<JarEntry> listOfAllClasses = new ArrayList<>();
247+
while(true) {
248+
jar = JarInputStream.getNextJarEntry();
249+
if(jar == null)
250+
break;
251+
if((jar.getName().endsWith(".class")))
252+
listOfAllClasses.add(jar);
253+
}
254+
return listOfAllClasses;
255+
}
256+
257+
//Fetch filtered classes from a list of JarEntry
258+
public static ArrayList<JarEntry> getFilteredClassesFromJar(ArrayList<JarEntry> listOfAllClasses, String className) {
259+
ArrayList<JarEntry> listOfFilteredClasses= new ArrayList<>();
260+
for (JarEntry entry : listOfAllClasses)
261+
if (entry.getName().endsWith(className))
262+
listOfFilteredClasses.add(entry);
263+
return listOfFilteredClasses;
264+
}
265+
public static ArrayList<Pair<String, String>> fetchAllMethodSignaturesForyaml (JarFile JarFile,JarEntry jar) throws IOException {
266+
ClassParser cp = new ClassParser(JarFile.getInputStream(jar), jar.getName());
267+
JavaClass jc = cp.parse();
268+
269+
Method[] methods = jc.getMethods();
270+
String className =jc.getClassName().substring(jc.getClassName().lastIndexOf(".")+1);
271+
ArrayList<Pair<String, String>> signatureResults = new ArrayList<>();
272+
for(Method tempMethod : methods)
273+
if(Arrays.stream(tempMethod.getAnnotationEntries())
274+
.map(e->e.getAnnotationType())
275+
.anyMatch(e->e.equals("Lorg/junit/Test;"))){
276+
String methodDescriptor=tempMethod.getName() + tempMethod.getSignature();
277+
signatureResults.add(new Pair<>(className+"#"+tempMethod.getName(),jc.getClassName() + "." + methodDescriptor));
278+
}
279+
return signatureResults;
280+
}
281+
//Fetch the method signature of a method from a JarEntry
282+
public static String fetchMethodSignatures(JarFile JarFile, JarEntry jar, String methodName, Optional<String> returnType, Optional<String> paramterTypes) throws IOException {
283+
ClassParser cp = new ClassParser(JarFile.getInputStream(jar), jar.getName());
284+
JavaClass jc = cp.parse();
285+
286+
Method[] methods = jc.getMethods();
287+
ArrayList<Method> signatureResults = new ArrayList<>();
288+
289+
for(Method tempMethod : methods)
290+
if(tempMethod.getName().equals(methodName))
291+
signatureResults.add(tempMethod);
292+
293+
if(returnType.isPresent()) {
294+
ArrayList<Method> tempsignatureResults = new ArrayList<>();
295+
for(Method tempMethod : signatureResults)
296+
if(tempMethod.getReturnType().toString().contains(returnType.get()))
297+
tempsignatureResults.add(tempMethod);
298+
signatureResults=new ArrayList<>(tempsignatureResults);
299+
300+
if(paramterTypes.isPresent()) {
301+
String[] paramlist = paramterTypes.get().split(",");
302+
for(Method tempMethod : signatureResults)
303+
if(Arrays.equals(paramlist, Arrays.stream(tempMethod.getArgumentTypes())
304+
.map(Type::toString)
305+
.map(e -> e.substring(e.lastIndexOf(".") + 1))
306+
.toArray()))
307+
return jc.getClassName() + "." + tempMethod.getName() + tempMethod.getSignature();
308+
}
309+
validateMethodList(signatureResults);
310+
return jc.getClassName() + "." + signatureResults.get(0).getName() + signatureResults.get(0).getSignature();
311+
} else {
312+
validateMethodList(signatureResults);
313+
return jc.getClassName() + "." + signatureResults.get(0).getName() + signatureResults.get(0).getSignature();
314+
}
315+
}
316+
317+
// Check the size of list and submit Logger info for the methods
318+
public static void validateMethodList(ArrayList<Method> methodList) {
319+
if(methodList.size() > 1) {
320+
LOGGER.error("Multiple overloaded methods for the given method name");
321+
for(Method method : methodList) {
322+
LOGGER.info("Name:- " + method.getName() + " Return Type:- " + method.getReturnType().toString());
323+
LOGGER.info("Parameter Types:- ");
324+
for(Type t : method.getArgumentTypes()) {
325+
LOGGER.info(t.toString());
326+
}
327+
method.getArgumentTypes();
328+
}
329+
System.exit(1);
330+
} else if(methodList.size() == 0) {
331+
LOGGER.info("Incorrect arguments supplied");
332+
System.exit(1);
333+
}
334+
}
335+
152336
public static void manualMain(String[] args) {
153337

154338
// First argument: the serialized file
@@ -173,26 +357,51 @@ public static void manualMain(String[] args) {
173357
LOGGER.error("Could not read JaCoCo coverage file", e);
174358
}
175359

176-
// Third argument: the entry point
177-
String entryPoint = args[3];
178-
179-
// Fourth argument: the output file
180-
String output = args[4];
360+
// third argument: the output file
361+
String output = args[3];
181362

182363
if (callgraph == null || jacocoCoverage == null) {
183364
// Something went wrong, bail
184365
return;
185366
}
186367

187-
// Fifth argument, optional, is the depth
368+
// forth argument: Jar path to infer entry point signature
369+
String jarPath = args[4];
370+
try {
371+
new JarFile(jarPath);
372+
} catch(IOException e){
373+
LOGGER.error("Could not read inference Jar file", e);
374+
}
375+
376+
// Sixth argument, optional, return type of expected method
377+
Optional<String> returnType = Optional.empty();
378+
if(args.length > 6)
379+
returnType = Optional.of(args[6]);
380+
381+
// Seventh argument, optional, parameter types of expected method
382+
Optional<String> paramterTypes = Optional.empty();
383+
if(args.length > 7)
384+
paramterTypes = Optional.of(args[7]);
385+
386+
// Fifth argument, class.method input where class can be written as nested classes to generate exact method signature
387+
String entryPoint = null;
388+
try {
389+
entryPoint = generateEntryPoint(jarPath, args[5], returnType, paramterTypes);
390+
// System.out.println(entryPoint);
391+
} catch(IOException e){
392+
LOGGER.error("Could not generate method signature", e);
393+
}
394+
395+
396+
// Seventh argument, optional, is the depth
188397
Optional<Integer> depth = Optional.empty();
189-
if (args.length > 5)
190-
depth = Optional.of(Integer.parseInt(args[5]));
398+
// if (args.length > 6)
399+
// depth = Optional.of(Integer.parseInt(args[7]));
191400

192401
// This method changes the callgraph object
193402
Pruning.pruneOriginalGraph(callgraph, jacocoCoverage);
194403

195-
maybeInspectReachability(callgraph, depth, jacocoCoverage, args[3], args[4]);
404+
maybeInspectReachability(callgraph, depth, jacocoCoverage, entryPoint, output);
196405

197406
// maybeWriteGraph(callgraph.graph, args[4]);
198407
}

src/main/java/gr/gousiosg/javacg/stat/graph/StaticCallgraph.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public class StaticCallgraph implements Serializable {
3333

3434
public JarMetadata metadata;
3535
public SerializableDefaultDirectedGraph<String, DefaultEdge> graph;
36+
public String JarEntry;
3637

3738
private StaticCallgraph(Graph<String, DefaultEdge> graph, JarMetadata jarMetadata) {
3839
this.graph = (SerializableDefaultDirectedGraph<String, DefaultEdge>) graph;

src/main/java/gr/gousiosg/javacg/stat/support/RepoTool.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,9 @@
88
import org.slf4j.Logger;
99
import org.slf4j.LoggerFactory;
1010
import java.io.*;
11+
import java.lang.reflect.Array;
1112
import java.nio.file.*;
12-
import java.util.LinkedList;
13-
import java.util.List;
14-
import java.util.Map;
15-
import java.util.Optional;
13+
import java.util.*;
1614
import java.time.LocalDateTime;
1715
import java.time.format.DateTimeFormatter;
1816

@@ -128,10 +126,17 @@ public void cleanTarget() throws IOException, InterruptedException {
128126
process.waitFor();
129127
}
130128

131-
public List<Pair<String,String>> obtainCoverageFilesAndEntryPoints(){
132-
List<Pair<String,String>> coverageFiles = new LinkedList<>();
133-
for(Map<String, String> m : properties)
134-
coverageFiles.add(new Pair<>("artifacts/results/" + getProjectDir() + timeStamp + "/" + m.get("name") + ".xml", m.get("entryPoint")));
129+
public List<Pair<String,?>> obtainCoverageFilesAndEntryPoints(){
130+
List<Pair<String,?>> coverageFiles = new LinkedList<>();
131+
for(Map<String, ?> m : properties){
132+
if(m.get("entryPoint") instanceof String){
133+
coverageFiles.add(new Pair<>("artifacts/results/" + getProjectDir() + timeStamp + "/" + m.get("name") + ".xml", m.get("entryPoint")));
134+
}
135+
else{
136+
coverageFiles.add(new Pair<String, ArrayList>("artifacts/results/" + getProjectDir() + timeStamp + "/" + m.get("name") + ".xml", (ArrayList) m.get("entryPoint")));
137+
}
138+
}
139+
135140
return coverageFiles;
136141
}
137142

0 commit comments

Comments
 (0)