3737import gr .gousiosg .javacg .stat .support .GitArguments ;
3838import gr .gousiosg .javacg .stat .support .RepoTool ;
3939import 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 ;
4045import org .eclipse .jgit .api .errors .GitAPIException ;
4146import org .eclipse .jgit .api .errors .JGitInternalException ;
4247import org .jgrapht .Graph ;
4348import org .jgrapht .graph .DefaultEdge ;
4449import org .slf4j .Logger ;
4550import org .slf4j .LoggerFactory ;
4651import org .xml .sax .SAXException ;
52+ import org .yaml .snakeyaml .DumperOptions ;
53+ import org .yaml .snakeyaml .Yaml ;
4754
4855import javax .xml .bind .JAXBException ;
4956import javax .xml .parsers .ParserConfigurationException ;
5057import 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 }
0 commit comments