DefineClassHandler.java 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. /*
  2. * Copyright The OpenTelemetry Authors
  3. * SPDX-License-Identifier: Apache-2.0
  4. */
  5. package io.opentelemetry.javaagent.tooling;
  6. import io.opentelemetry.javaagent.bootstrap.DefineClassHelper.Handler;
  7. import java.nio.charset.StandardCharsets;
  8. import java.util.Collections;
  9. import java.util.HashSet;
  10. import java.util.Set;
  11. import org.objectweb.asm.ClassReader;
  12. public class DefineClassHandler implements Handler {
  13. public static final DefineClassHandler INSTANCE = new DefineClassHandler();
  14. private static final ThreadLocal<DefineClassContextImpl> defineClassContext =
  15. ThreadLocal.withInitial(() -> DefineClassContextImpl.NOP);
  16. private DefineClassHandler() {}
  17. @Override
  18. public DefineClassContext beforeDefineClass(
  19. ClassLoader classLoader, String className, byte[] classBytes, int offset, int length) {
  20. // with OpenJ9 class data sharing we don't get real class bytes
  21. if (classBytes == null
  22. || (classBytes.length == 40
  23. && new String(classBytes, StandardCharsets.ISO_8859_1)
  24. .startsWith("J9ROMCLASSCOOKIE"))) {
  25. return null;
  26. }
  27. Set<String> superNames = new HashSet<>();
  28. DefineClassContextImpl context = DefineClassContextImpl.enter();
  29. // attempt to load super types of currently loaded class
  30. // for a class to be loaded all of its super types must be loaded, here we just change the order
  31. // of operations and load super types before transforming the bytes for current class so that
  32. // we could use these super types for resolving the advice that needs to be applied to current
  33. // class
  34. try {
  35. ClassReader cr = new ClassReader(classBytes, offset, length);
  36. String superName = cr.getSuperName();
  37. if (superName != null) {
  38. String superDotName = superName.replace('/', '.');
  39. Class<?> clazz = Class.forName(superDotName, false, classLoader);
  40. addSuperNames(superNames, clazz);
  41. }
  42. String[] interfaces = cr.getInterfaces();
  43. for (String interfaceName : interfaces) {
  44. String interfaceDotName = interfaceName.replace('/', '.');
  45. Class<?> clazz = Class.forName(interfaceDotName, false, classLoader);
  46. addSuperNames(superNames, clazz);
  47. }
  48. context.superDotNames = superNames;
  49. } catch (Throwable throwable) {
  50. // loading of super class or interface failed
  51. // mark current class as failed to skip matching and transforming it
  52. // we'll let defining the class proceed as usual so that it would throw the same exception as
  53. // it does when running without the agent
  54. context.failedClassDotName = className;
  55. }
  56. return context;
  57. }
  58. @Override
  59. public DefineClassContext beforeDefineLambdaClass(Class<?> lambdaInterface) {
  60. DefineClassContextImpl context = DefineClassContextImpl.enter();
  61. Set<String> superNames = new HashSet<>();
  62. addSuperNames(superNames, lambdaInterface);
  63. context.superDotNames = superNames;
  64. return context;
  65. }
  66. private static void addSuperNames(Set<String> superNames, Class<?> clazz) {
  67. if (clazz == null || !superNames.add(clazz.getName())) {
  68. return;
  69. }
  70. addSuperNames(superNames, clazz.getSuperclass());
  71. for (Class<?> interfaceClass : clazz.getInterfaces()) {
  72. addSuperNames(superNames, interfaceClass);
  73. }
  74. }
  75. @Override
  76. public void afterDefineClass(DefineClassContext context) {
  77. if (context != null) {
  78. context.exit();
  79. }
  80. }
  81. /**
  82. * Detect whether loading the specified class is known to fail.
  83. *
  84. * @param dotClassName class being loaded
  85. * @return true if it is known that loading class with given name will fail
  86. */
  87. public static boolean isFailedClass(String dotClassName) {
  88. DefineClassContextImpl context = defineClassContext.get();
  89. return context.failedClassDotName != null && context.failedClassDotName.equals(dotClassName);
  90. }
  91. public static Set<String> getSuperTypes() {
  92. Set<String> superNames = defineClassContext.get().superDotNames;
  93. return superNames == null ? Collections.emptySet() : superNames;
  94. }
  95. private static class DefineClassContextImpl implements DefineClassContext {
  96. private static final DefineClassContextImpl NOP = new DefineClassContextImpl();
  97. private final DefineClassContextImpl previous;
  98. String failedClassDotName;
  99. Set<String> superDotNames;
  100. private DefineClassContextImpl() {
  101. previous = null;
  102. }
  103. private DefineClassContextImpl(DefineClassContextImpl previous) {
  104. this.previous = previous;
  105. }
  106. static DefineClassContextImpl enter() {
  107. DefineClassContextImpl previous = defineClassContext.get();
  108. DefineClassContextImpl context = new DefineClassContextImpl(previous);
  109. defineClassContext.set(context);
  110. return context;
  111. }
  112. @Override
  113. public void exit() {
  114. defineClassContext.set(previous);
  115. }
  116. }
  117. }