Project

General

Profile

MethodCallAnalyzer.java

Teodor Gorghe, 04/24/2026 08:01 AM

Download (11.3 KB)

 
1
import org.objectweb.asm.*;
2
import java.io.*;
3
import java.util.*;
4
import java.util.jar.*;
5

    
6
public class MethodCallAnalyzer
7
{
8
   private static final Set<String> appcdsClasses = new TreeSet<>();
9
   private static final Map<String, byte[]> jarClasses = new HashMap<>();
10
   private static final Queue<String> analysisQueue = new LinkedList<>();
11
   private static final Set<String> alreadyAnalyzed = new HashSet<>();
12
   
13
   private static final List<String> includeFilters = new ArrayList<>();
14
   private static final List<String> excludeFilters = new ArrayList<>();
15
   private static final List<String> explicitStops = new ArrayList<>();
16
   private static boolean implicitBridgeMode = false;
17

    
18
   private static final Map<String, String> dependencyGraph = new HashMap<>();
19
   private static String traceTarget = null;
20

    
21
   public static void main(String[] args)
22
   {
23
      if (args.length < 1)
24
      {
25
         System.err.println("Usage: java MethodCallAnalyzer <jar> [includes] [-excludes] [!stops] [--implicit-bridge] [--trace=class]");
26
         System.exit(1);
27
      }
28

    
29
      for (int i = 1; i < args.length; i++)
30
      {
31
         String arg = args[i];
32
         if (arg.startsWith("--trace="))
33
         {
34
            traceTarget = arg.substring(8).replace('.', '/');
35
         }
36
         else if (arg.equals("--implicit-bridge"))
37
         {
38
            implicitBridgeMode = true;
39
         }
40
         else if (arg.startsWith("-"))
41
         {
42
            excludeFilters.add(arg.substring(1).replace('.', '/'));
43
         }
44
         else if (arg.startsWith("!"))
45
         {
46
            explicitStops.add(arg.substring(1).replace('.', '/'));
47
         }
48
         else
49
         {
50
            includeFilters.add(arg.replace('.', '/'));
51
         }
52
      }
53

    
54
      try (JarFile jarFile = new JarFile(args[0]))
55
      {
56
         System.out.println("Loading JAR and SPIs...");
57
         loadJarAndSPIs(jarFile);
58
         seedQueue();
59

    
60
         System.out.println("Scanning...");
61
         while (!analysisQueue.isEmpty())
62
         {
63
            String current = analysisQueue.poll();
64
            if (!alreadyAnalyzed.add(current))
65
            {
66
               continue;
67
            }
68

    
69
            byte[] bytes = jarClasses.get(current);
70
            if (bytes == null)
71
            {
72
               continue;
73
            }
74

    
75
            // Shallow scan if it's a bridge or outside include list (if mode enabled)
76
            boolean isBridge = isExplicitStop(current) || (implicitBridgeMode && !isIncluded(current));
77

    
78
            analyzeClassBytes(current, bytes, isBridge);
79
         }
80

    
81
         try (PrintWriter w = new PrintWriter(new FileWriter("appcds_classes.lst")))
82
         {
83
            appcdsClasses.forEach(w::println);
84
         }
85

    
86
         System.out.println("Done! Total classes: " + appcdsClasses.size());
87
         if (traceTarget != null)
88
         {
89
            printTrace();
90
         }
91

    
92
      }
93
      catch (Exception e)
94
      {
95
         e.printStackTrace();
96
      }
97
   }
98

    
99
   private static void analyzeClassBytes(final String sourceClass, byte[] bytes, boolean shallowScan)
100
   {
101
      new ClassReader(bytes).accept(new ClassVisitor(Opcodes.ASM9)
102
      {
103
         private void reg(String name)
104
         {
105
            if (name == null || name.startsWith("[") || isExcluded(name))
106
            {
107
               return;
108
            }
109
            if (appcdsClasses.add(name))
110
            {
111
               analysisQueue.add(name);
112
               if (!dependencyGraph.containsKey(name))
113
               {
114
                  dependencyGraph.put(name, sourceClass);
115
               }
116
            }
117
         }
118

    
119
         private void regType(Type t)
120
         {
121
            if (t.getSort() == Type.OBJECT)
122
            {
123
               reg(t.getInternalName());
124
            }
125
            else if (t.getSort() == Type.ARRAY)
126
            {
127
               regType(t.getElementType());
128
            }
129
            else if (t.getSort() == Type.METHOD)
130
            {
131
               regType(t.getReturnType());
132
               for (Type a : t.getArgumentTypes())
133
               {
134
                  regType(a);
135
               }
136
            }
137
         }
138

    
139
         private void regDesc(String d)
140
         {
141
            if (d != null)
142
            {
143
               try
144
               {
145
                  regType(Type.getType(d));
146
               }
147
               catch (Exception e)
148
               {
149
               }
150
            }
151
         }
152

    
153
         @Override
154
         public void visit(int v, int a, String n, String s, String sn, String[] i)
155
         {
156
            reg(n);
157
            reg(sn);
158
            if (i != null)
159
            {
160
               for (String iface : i)
161
               {
162
                  reg(iface);
163
               }
164
            }
165
         }
166

    
167
         @Override
168
         public FieldVisitor visitField(int a, String n, String d, String s, Object v)
169
         {
170
            if (!shallowScan)
171
            {
172
               regDesc(d);
173
            }
174
            return null;
175
         }
176

    
177
         @Override
178
         public MethodVisitor visitMethod(int a, String n, String d, String s, String[] e)
179
         {
180
            if (!shallowScan)
181
            {
182
               regDesc(d);
183
               if (e != null)
184
               {
185
                  for (String ex : e)
186
                  {
187
                     reg(ex);
188
                  }
189
               }
190
            }
191
            if (shallowScan)
192
            {
193
               return null; // STOP HERE for bridge classes
194
            }
195

    
196
            return new MethodVisitor(Opcodes.ASM9)
197
            {
198
               @Override
199
               public void visitMethodInsn(int o, String ow, String n, String d, boolean i)
200
               {
201
                  reg(ow);
202
                  regDesc(d);
203
                  checkSmartTriggers(ow.replace('/', '.'));
204
               }
205

    
206
               @Override
207
               public void visitFieldInsn(int o, String ow, String n, String d)
208
               {
209
                  reg(ow);
210
                  regDesc(d);
211
               }
212

    
213
               @Override
214
               public void visitTypeInsn(int o, String t)
215
               {
216
                  if (t.startsWith("["))
217
                  {
218
                     regDesc(t);
219
                  }
220
                  else
221
                  {
222
                     reg(t);
223
                  }
224
               }
225

    
226
               @Override
227
               public void visitLdcInsn(Object v)
228
               {
229
                  if (v instanceof Type)
230
                  {
231
                     regType((Type) v);
232
                  }
233
               }
234

    
235
               @Override
236
               public void visitTryCatchBlock(Label s, Label e, Label h, String t)
237
               {
238
                  if (t != null)
239
                  {
240
                     reg(t);
241
                  }
242
               }
243

    
244
               @Override
245
               public void visitInvokeDynamicInsn(String name, String descriptor, Handle bsm, Object... bsmArgs)
246
               {
247
                  regDesc(descriptor);
248
                  reg(bsm.getOwner());
249
                  for (Object arg : bsmArgs)
250
                  {
251
                     if (arg instanceof Type)
252
                     {
253
                        regType((Type) arg);
254
                     }
255
                     else if (arg instanceof Handle)
256
                     {
257
                        Handle h = (Handle) arg;
258
                        reg(h.getOwner());
259
                        regDesc(h.getDesc());
260
                     }
261
                  }
262
               }
263
            };
264
         }
265

    
266
         private void checkSmartTriggers(String cleanOwner)
267
         {
268
            // XML / Xerces
269
            if (cleanOwner.startsWith("javax.xml") || cleanOwner.startsWith("org.w3c"))
270
            {
271
               reg("com/sun/org/apache/xerces/internal/jaxp/DocumentBuilderFactoryImpl");
272
               reg("com/sun/org/apache/xerces/internal/parsers/DOMParser");
273
            }
274
            // Security / SSL
275
            else if (cleanOwner.startsWith("java.security") || cleanOwner.startsWith("javax.crypto") || cleanOwner.startsWith("javax.net.ssl"))
276
            {
277
               reg("sun/security/provider/Sun");
278
               reg("com/sun/crypto/provider/SunJCE");
279
               reg("sun/security/ssl/SSLContextImpl");
280
            }
281
            // NIO
282
            else if (cleanOwner.startsWith("java.nio.file"))
283
            {
284
               reg("sun/nio/fs/UnixFileSystemProvider");
285
               reg("sun/nio/ch/FileChannelImpl");
286
            }
287
         }
288
      }, 0);
289
   }
290

    
291
   private static void loadJarAndSPIs(JarFile jarFile) throws IOException
292
   {
293
      Enumeration<JarEntry> entries = jarFile.entries();
294
      while (entries.hasMoreElements())
295
      {
296
         JarEntry entry = entries.nextElement();
297
         String name = entry.getName();
298
         if (name.startsWith("META-INF/services/"))
299
         {
300
            try (BufferedReader r = new BufferedReader(new InputStreamReader(jarFile.getInputStream(entry))))
301
            {
302
               String line;
303
               while ((line = r.readLine()) != null)
304
               {
305
                  line = line.trim();
306
                  if (!line.isEmpty() && !line.startsWith("#"))
307
                  {
308
                     String spi = line.replace('.', '/');
309
                     if (!isExcluded(spi) && appcdsClasses.add(spi))
310
                     {
311
                        analysisQueue.add(spi);
312
                        dependencyGraph.put(spi, "[SPI]");
313
                     }
314
                  }
315
               }
316
            }
317
         }
318
         else if (name.endsWith(".class"))
319
         {
320
            String cls = name.substring(0, name.length() - 6);
321
            InputStream is = jarFile.getInputStream(entry);
322
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
323
            byte[] buf = new byte[8192];
324
            int n;
325
            while ((n = is.read(buf)) != -1)
326
            {
327
               bos.write(buf, 0, n);
328
            }
329
            jarClasses.put(cls, bos.toByteArray());
330
         }
331
      }
332
   }
333

    
334
   private static void seedQueue()
335
   {
336
      for (String cls : jarClasses.keySet())
337
      {
338
         if (isIncluded(cls) && !isExcluded(cls))
339
         {
340
            if (appcdsClasses.add(cls))
341
            {
342
               analysisQueue.add(cls);
343
               dependencyGraph.put(cls, "[Root]");
344
            }
345
         }
346
      }
347
   }
348

    
349
   private static boolean isIncluded(String cls)
350
   {
351
      for (String f : includeFilters)
352
      {
353
         if (cls.startsWith(f + "/") || cls.equals(f) || cls.startsWith(f + "$"))
354
         {
355
            return true;
356
         }
357
      }
358
      return false;
359
   }
360

    
361
   private static boolean isExcluded(String cls)
362
   {
363
      for (String ex : excludeFilters)
364
      {
365
         if (cls.startsWith(ex + "/") || cls.equals(ex) || cls.startsWith(ex + "$"))
366
         {
367
            return true;
368
         }
369
      }
370
      return false;
371
   }
372

    
373
   private static boolean isExplicitStop(String cls)
374
   {
375
      for (String st : explicitStops)
376
      {
377
         if (cls.startsWith(st + "/") || cls.equals(st) || cls.startsWith(st + "$"))
378
         {
379
            return true;
380
         }
381
      }
382
      return false;
383
   }
384

    
385
   private static void printTrace()
386
   {
387
      System.out.println("\nTRACE FOR: " + traceTarget);
388
      String c = traceTarget;
389
      while (c != null)
390
      {
391
         System.out.println("" + c);
392
         c = dependencyGraph.get(c);
393
         if (c != null && c.startsWith("["))
394
         {
395
            System.out.println("" + c);
396
            break;
397
         }
398
      }
399
   }
400
}