View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
5    */
6   package mockit.internal.expectations.transformation;
7   
8   import edu.umd.cs.findbugs.annotations.NonNull;
9   import edu.umd.cs.findbugs.annotations.Nullable;
10  
11  import java.lang.instrument.ClassFileTransformer;
12  import java.security.ProtectionDomain;
13  
14  import mockit.asm.classes.ClassReader;
15  import mockit.asm.classes.ClassVisitor;
16  import mockit.asm.classes.ClassWriter;
17  import mockit.asm.classes.WrappingClassVisitor;
18  import mockit.asm.methods.MethodVisitor;
19  import mockit.asm.methods.MethodWriter;
20  import mockit.internal.util.ClassNaming;
21  import mockit.internal.util.VisitInterruptedException;
22  
23  public final class ExpectationsTransformer implements ClassFileTransformer {
24      private static final String BASE_CLASSES = "mockit/Expectations mockit/Verifications mockit/VerificationsInOrder mockit/FullVerifications";
25  
26      @Nullable
27      @Override
28      public byte[] transform(@Nullable ClassLoader loader, @NonNull String className,
29              @Nullable Class<?> classBeingRedefined, @Nullable ProtectionDomain protectionDomain,
30              @NonNull byte[] classfileBuffer) {
31          if (classBeingRedefined == null && protectionDomain != null && className != null) {
32              boolean anonymousClass = ClassNaming.isAnonymousClass(className);
33  
34              if (anonymousClass && !isJMockitClass(className) && !className.startsWith("org/junit/")) {
35                  ClassReader cr = new ClassReader(classfileBuffer);
36                  String superClassName = cr.getSuperName();
37  
38                  if (!BASE_CLASSES.contains(superClassName)) {
39                      return null;
40                  }
41  
42                  return modifyInvocationsSubclass(cr, className);
43              }
44          }
45  
46          return null;
47      }
48  
49      private static boolean isJMockitClass(@NonNull String classDesc) {
50          return classDesc.startsWith("mockit/") && (classDesc.startsWith("mockit/internal/")
51                  || classDesc.startsWith("mockit/coverage/") || classDesc.startsWith("mockit/integration/"));
52      }
53  
54      @Nullable
55      private static byte[] modifyInvocationsSubclass(@NonNull ClassReader cr, @NonNull final String classDesc) {
56          ClassWriter cw = new ClassWriter(cr);
57  
58          ClassVisitor modifier = new WrappingClassVisitor(cw) {
59              @Override
60              public MethodVisitor visitMethod(int access, @NonNull String name, @NonNull String desc,
61                      @Nullable String signature, @Nullable String[] exceptions) {
62                  MethodWriter mw = cw.visitMethod(access, name, desc, signature, exceptions);
63  
64                  if (!"<init>".equals(name)) {
65                      return mw;
66                  }
67  
68                  return new InvocationBlockModifier(mw, classDesc);
69              }
70          };
71  
72          try {
73              cr.accept(modifier);
74              return modifier.toByteArray();
75          } catch (VisitInterruptedException ignore) {
76          } catch (Throwable e) {
77              e.printStackTrace();
78          }
79  
80          return null;
81      }
82  }