35
35
import java .util .ArrayDeque ;
36
36
import java .util .ArrayList ;
37
37
import java .util .Arrays ;
38
+ import java .util .Collection ;
38
39
import java .util .Deque ;
40
+ import java .util .HashMap ;
39
41
import java .util .HashSet ;
40
42
import java .util .Iterator ;
41
43
import java .util .LinkedHashMap ;
42
44
import java .util .List ;
43
45
import java .util .Map ;
46
+ import java .util .Objects ;
44
47
import java .util .Set ;
48
+ import java .util .concurrent .atomic .AtomicInteger ;
49
+ import java .util .regex .Matcher ;
50
+ import java .util .regex .Pattern ;
45
51
import java .util .stream .Collectors ;
52
+ import java .util .stream .Stream ;
46
53
47
54
import com .oracle .graal .pointsto .BigBang ;
48
55
import com .oracle .graal .pointsto .flow .InvokeTypeFlow ;
49
56
import com .oracle .graal .pointsto .meta .AnalysisMethod ;
50
57
51
58
import jdk .vm .ci .code .BytecodePosition ;
59
+ import jdk .vm .ci .meta .JavaKind ;
52
60
import jdk .vm .ci .meta .ResolvedJavaMethod ;
61
+ import jdk .vm .ci .meta .ResolvedJavaType ;
53
62
54
63
public final class CallTreePrinter {
55
64
65
+ public static final Pattern CAMEL_CASE_PATTERN = Pattern .compile (
66
+ "\\ b[a-zA-Z]|[A-Z]|\\ ." );
67
+
56
68
public static void print (BigBang bigbang , String reportsPath , String reportName ) {
57
69
CallTreePrinter printer = new CallTreePrinter (bigbang );
58
70
printer .buildCallTree ();
@@ -65,6 +77,8 @@ public static void print(BigBang bigbang, String reportsPath, String reportName)
65
77
writer -> printer .printClasses (writer , false ));
66
78
ReportUtils .report ("list of used packages" , reportsPath , "used_packages_" + reportName , "txt" ,
67
79
writer -> printer .printClasses (writer , true ));
80
+
81
+ printCsvFiles (printer .methodToNode , reportsPath , reportName );
68
82
}
69
83
70
84
interface Node {
@@ -299,4 +313,268 @@ private static String packagePrefix(String name) {
299
313
}
300
314
return name .substring (0 , lastDot );
301
315
}
316
+
317
+ private static void printCsvFiles (Map <AnalysisMethod , MethodNode > methodToNode , String reportsPath , String reportName ) {
318
+ // Set virtual node at next available method id
319
+ final AtomicInteger virtualNodeId = new AtomicInteger (MethodNode .methodId );
320
+
321
+ Set <Integer > entryPointIds = new HashSet <>();
322
+ Set <MethodNode > nonVirtualNodes = new HashSet <>();
323
+ Map <List <String >, Integer > virtualNodes = new HashMap <>();
324
+
325
+ Map <Integer , Set <BciEndEdge >> directEdges = new HashMap <>();
326
+ Map <Integer , Set <BciEndEdge >> virtualEdges = new HashMap <>();
327
+ Map <Integer , Set <Integer >> overridenByEdges = new HashMap <>();
328
+
329
+ final Iterator <MethodNode > iterator = methodToNode .values ().stream ().filter (n -> n .isEntryPoint ).iterator ();
330
+ while (iterator .hasNext ()) {
331
+ final MethodNode node = iterator .next ();
332
+ entryPointIds .add (node .id );
333
+ walkNodes (node , directEdges , virtualEdges , overridenByEdges , virtualNodes , nonVirtualNodes , virtualNodeId );
334
+ }
335
+
336
+ ReportUtils .report ("call tree for vm entry point" , reportsPath , "csv_call_tree_vm_" + reportName , "csv" ,
337
+ CallTreePrinter ::printVMEntryPoint );
338
+
339
+ ReportUtils .report ("call tree for methods" , reportsPath , "csv_call_tree_methods_" + reportName , "csv" ,
340
+ writer -> printMethodNodes (methodToNode .values (), writer ));
341
+
342
+ ReportUtils .report ("call tree for virtual methods" , reportsPath , "csv_call_tree_virtual_methods_" + reportName , "csv" ,
343
+ writer -> printVirtualNodes (virtualNodes , writer ));
344
+
345
+ ReportUtils .report ("call tree for entry points" , reportsPath , "csv_call_tree_entry_points_" + reportName , "csv" ,
346
+ writer -> printEntryPointIds (entryPointIds , writer ));
347
+
348
+ ReportUtils .report ("call tree for direct edges" , reportsPath , "csv_call_tree_direct_edges_" + reportName , "csv" ,
349
+ writer -> printBciEdges (directEdges , writer ));
350
+
351
+ ReportUtils .report ("call tree for overriden by edges" , reportsPath , "csv_call_tree_override_by_edges_" + reportName , "csv" ,
352
+ writer -> printNonBciEdges (overridenByEdges , writer ));
353
+
354
+ ReportUtils .report ("call tree for virtual edges" , reportsPath , "csv_call_tree_virtual_edges_" + reportName , "csv" ,
355
+ writer -> printBciEdges (virtualEdges , writer ));
356
+ }
357
+
358
+ private static void printVMEntryPoint (PrintWriter writer ) {
359
+ writer .println (convertToCSV ("Id" , "Name" ));
360
+ writer .println (convertToCSV ("0" , "VM" ));
361
+ }
362
+
363
+ private static void printMethodNodes (Collection <MethodNode > methods , PrintWriter writer ) {
364
+ writer .println (convertToCSV ("Id" , "Name" , "Type" , "Parameters" , "Return" , "Display" ));
365
+ methods .stream ()
366
+ .map (CallTreePrinter ::methodNodeInfo )
367
+ .map (CallTreePrinter ::convertToCSV )
368
+ .forEach (writer ::println );
369
+ }
370
+
371
+ private static List <String > methodNodeInfo (MethodNode method ) {
372
+ return resolvedJavaMethodInfo (method .id , method .method );
373
+ }
374
+
375
+ private static void walkNodes (MethodNode methodNode , Map <Integer , Set <BciEndEdge >> directEdges , Map <Integer , Set <BciEndEdge >> virtualEdges , Map <Integer , Set <Integer >> overridenByEdges ,
376
+ Map <List <String >, Integer > virtualNodes , Set <MethodNode > nonVirtualNodes , AtomicInteger virtualNodeId ) {
377
+ for (InvokeNode invoke : methodNode .invokes ) {
378
+ if (invoke .isDirectInvoke ) {
379
+ if (invoke .callees .size () > 0 ) {
380
+ Node calleeNode = invoke .callees .get (0 );
381
+ addDirectEdge (methodNode .id , invoke , calleeNode , directEdges , nonVirtualNodes );
382
+ if (calleeNode instanceof MethodNode ) {
383
+ walkNodes ((MethodNode ) calleeNode , directEdges , virtualEdges , overridenByEdges , virtualNodes , nonVirtualNodes , virtualNodeId );
384
+ }
385
+ }
386
+ } else {
387
+ final int nodeId = addVirtualNode (invoke , virtualNodes , virtualNodeId );
388
+ addVirtualMethodEdge (methodNode .id , invoke , nodeId , virtualEdges );
389
+ for (Node calleeNode : invoke .callees ) {
390
+ addOverridenByEdge (nodeId , calleeNode , overridenByEdges , nonVirtualNodes );
391
+ if (calleeNode instanceof MethodNode ) {
392
+ walkNodes ((MethodNode ) calleeNode , directEdges , virtualEdges , overridenByEdges , virtualNodes , nonVirtualNodes , virtualNodeId );
393
+ }
394
+ }
395
+ }
396
+ }
397
+ }
398
+
399
+ private static void addDirectEdge (int nodeId , InvokeNode invoke , Node calleeNode , Map <Integer , Set <BciEndEdge >> edges , Set <MethodNode > nodes ) {
400
+ Set <BciEndEdge > nodeEdges = edges .computeIfAbsent (nodeId , k -> new HashSet <>());
401
+ MethodNode methodNode = calleeNode instanceof MethodNode
402
+ ? (MethodNode ) calleeNode
403
+ : ((MethodNodeReference ) calleeNode ).methodNode ;
404
+ nodes .add (methodNode );
405
+ nodeEdges .add (new BciEndEdge (methodNode .id , bytecodeIndexes (invoke )));
406
+ }
407
+
408
+ private static List <Integer > bytecodeIndexes (InvokeNode node ) {
409
+ return Stream .of (node .sourceReferences )
410
+ .map (source -> source .bci )
411
+ .collect (Collectors .toList ());
412
+ }
413
+
414
+ private static int addVirtualNode (InvokeNode node , Map <List <String >, Integer > virtualNodes , AtomicInteger virtualNodeId ) {
415
+ final List <String > virtualMethodInfo = virtualMethodInfo (node .targetMethod );
416
+ return virtualNodes .computeIfAbsent (virtualMethodInfo , k -> virtualNodeId .getAndIncrement ());
417
+ }
418
+
419
+ private static void addVirtualMethodEdge (int startId , InvokeNode invoke , int endId , Map <Integer , Set <BciEndEdge >> edges ) {
420
+ Set <BciEndEdge > nodeEdges = edges .computeIfAbsent (startId , k -> new HashSet <>());
421
+ nodeEdges .add (new BciEndEdge (endId , bytecodeIndexes (invoke )));
422
+ }
423
+
424
+ private static void printVirtualNodes (Map <List <String >, Integer > virtualNodes , PrintWriter writer ) {
425
+ writer .println (convertToCSV ("Id" , "Name" , "Type" , "Parameters" , "Return" , "Display" ));
426
+ virtualNodes .entrySet ().stream ()
427
+ .map (CallTreePrinter ::virtualMethodAndIdInfo )
428
+ .map (CallTreePrinter ::convertToCSV )
429
+ .forEach (writer ::println );
430
+ }
431
+
432
+ private static List <String > virtualMethodAndIdInfo (Map .Entry <List <String >, Integer > entry ) {
433
+ final List <String > methodInfo = entry .getKey ();
434
+ final List <String > result = new ArrayList <>(methodInfo .size () + 1 );
435
+ result .add (String .valueOf (entry .getValue ()));
436
+ for (int i = 1 ; i < methodInfo .size (); i ++) {
437
+ result .add (i , methodInfo .get (i ));
438
+ }
439
+ return result ;
440
+ }
441
+
442
+ private static void printEntryPointIds (Set <Integer > entryPoints , PrintWriter writer ) {
443
+ writer .println (convertToCSV ("Id" ));
444
+ entryPoints .forEach (writer ::println );
445
+ }
446
+
447
+ private static void addOverridenByEdge (int nodeId , Node calleeNode , Map <Integer , Set <Integer >> edges , Set <MethodNode > nodes ) {
448
+ Set <Integer > nodeEdges = edges .computeIfAbsent (nodeId , k -> new HashSet <>());
449
+ MethodNode methodNode = calleeNode instanceof MethodNode
450
+ ? (MethodNode ) calleeNode
451
+ : ((MethodNodeReference ) calleeNode ).methodNode ;
452
+ nodes .add (methodNode );
453
+ nodeEdges .add (methodNode .id );
454
+ }
455
+
456
+ private static void printBciEdges (Map <Integer , Set <BciEndEdge >> edges , PrintWriter writer ) {
457
+ final Set <BciEdge > idEdges = edges .entrySet ().stream ()
458
+ .flatMap (entry -> entry .getValue ().stream ().map (endId -> new BciEdge (entry .getKey (), endId )))
459
+ .collect (Collectors .toSet ());
460
+
461
+ writer .println (convertToCSV ("StartId" , "EndId" , "BytecodeIndexes" ));
462
+ idEdges .stream ()
463
+ .map (edge -> convertToCSV (String .valueOf (edge .startId ), String .valueOf (edge .endEdge .id ), showBytecodeIndexes (edge .endEdge .bytecodeIndexes )))
464
+ .forEach (writer ::println );
465
+ }
466
+
467
+ private static String showBytecodeIndexes (List <Integer > bytecodeIndexes ) {
468
+ return bytecodeIndexes .stream ()
469
+ .map (String ::valueOf )
470
+ .collect (Collectors .joining ("->" ));
471
+ }
472
+
473
+ private static void printNonBciEdges (Map <Integer , Set <Integer >> edges , PrintWriter writer ) {
474
+ final Set <NonBciEdge > idEdges = edges .entrySet ().stream ()
475
+ .flatMap (entry -> entry .getValue ().stream ().map (endId -> new NonBciEdge (entry .getKey (), endId )))
476
+ .collect (Collectors .toSet ());
477
+
478
+ writer .println (convertToCSV ("StartId" , "EndId" ));
479
+ idEdges .stream ()
480
+ .map (edge -> convertToCSV (String .valueOf (edge .startId ), String .valueOf (edge .endId )))
481
+ .forEach (writer ::println );
482
+ }
483
+
484
+ private static List <String > virtualMethodInfo (AnalysisMethod method ) {
485
+ return resolvedJavaMethodInfo (null , method );
486
+ }
487
+
488
+ private static List <String > resolvedJavaMethodInfo (Integer id , ResolvedJavaMethod method ) {
489
+ // TODO method parameter types are opaque, but could in the future be split out and link
490
+ // together
491
+ // e.g. each method could BELONG to a type, and a method could have PARAMETER relationships
492
+ // with N types
493
+ // see https://neo4j.com/developer/guide-import-csv/#_converting_data_values_with_load_csv
494
+ // for examples
495
+ final String parameters = method .getSignature ().getParameterCount (false ) > 0
496
+ ? method .format ("%P" ).replace ("," , "" )
497
+ : "empty" ;
498
+
499
+ return Arrays .asList (
500
+ id == null ? null : Integer .toString (id ),
501
+ method .getName (),
502
+ method .getDeclaringClass ().toJavaName (true ),
503
+ parameters ,
504
+ method .getSignature ().getReturnType (null ).toJavaName (true ),
505
+ display (method ));
506
+ }
507
+
508
+ private static String display (ResolvedJavaMethod method ) {
509
+ final ResolvedJavaType type = method .getDeclaringClass ();
510
+ final String typeName = type .toJavaName (true );
511
+ if (type .getJavaKind () == JavaKind .Object ) {
512
+ List <String > matchResults = new ArrayList <>();
513
+ Matcher matcher = CAMEL_CASE_PATTERN .matcher (typeName );
514
+ while (matcher .find ()) {
515
+ matchResults .add (matcher .toMatchResult ().group ());
516
+ }
517
+
518
+ return String .join ("" , matchResults ) + "." + method .getName ();
519
+ }
520
+
521
+ return typeName + "." + method .getName ();
522
+ }
523
+
524
+ private static String convertToCSV (String ... data ) {
525
+ return String .join ("," , data );
526
+ }
527
+
528
+ private static String convertToCSV (List <String > data ) {
529
+ return String .join ("," , data );
530
+ }
531
+
532
+ private static final class NonBciEdge {
533
+
534
+ final int startId ;
535
+ final int endId ;
536
+
537
+ private NonBciEdge (int startId , int endId ) {
538
+ this .startId = startId ;
539
+ this .endId = endId ;
540
+ }
541
+ }
542
+
543
+ private static final class BciEdge {
544
+ final int startId ;
545
+ final BciEndEdge endEdge ;
546
+
547
+ private BciEdge (int startId , BciEndEdge endEdge ) {
548
+ this .startId = startId ;
549
+ this .endEdge = endEdge ;
550
+ }
551
+ }
552
+
553
+ private static final class BciEndEdge {
554
+ final int id ;
555
+ final List <Integer > bytecodeIndexes ;
556
+
557
+ private BciEndEdge (int id , List <Integer > bytecodeIndexes ) {
558
+ this .id = id ;
559
+ this .bytecodeIndexes = bytecodeIndexes ;
560
+ }
561
+
562
+ @ Override
563
+ public boolean equals (Object o ) {
564
+ if (this == o ) {
565
+ return true ;
566
+ }
567
+ if (o == null || getClass () != o .getClass ()) {
568
+ return false ;
569
+ }
570
+ BciEndEdge endEdge = (BciEndEdge ) o ;
571
+ return id == endEdge .id &&
572
+ bytecodeIndexes .equals (endEdge .bytecodeIndexes );
573
+ }
574
+
575
+ @ Override
576
+ public int hashCode () {
577
+ return Objects .hash (id , bytecodeIndexes );
578
+ }
579
+ }
302
580
}
0 commit comments