Share
Embed
Transcript
106 slides
-
Code Coverage public class DiscountCalculator { public int calculateDiscount(int price,
boolean isMember) { if (price > 100) { return isMember ? 20 : 10; } return isMember ? 5 : 0; } } -
Code Coverage class DiscountCalculatorTest { @Test void calculateDiscount() { DiscountCalculator
calc = new DiscountCalculator(); assertEquals(0, calc.calculateDiscount(50, false)); assertEquals(5, calc.calculateDiscount(50, true)); } } -
Code Coverage public class DiscountCalculator { public int calculateDiscount(int price,
boolean isMember) { if (price > 100) { return isMember ? 20 : 10; } return isMember ? 5 : 0; } } executed in the tests? -
Code Coverage public class DiscountCalculator { public int calculateDiscount(int price,
boolean isMember) { if (price > 100) { return isMember ? 20 : 10; } return isMember ? 5 : 0; } } log(…) log(…) log(…) -
Testing @Mock List<String> mockedList; @Test public void testList() { mockedList.add("one");
Mockito.verify(mockedList).add("one"); } -
Profiling void method() { logEntry("method"); !// do something logExit("method"); }
void method() { !// do something } -
Debugging public class DiscountCalculator { public int calculateDiscount(int price, boolean
isMember) { if (price > 100) { return isMember ? 20 : 10; } return isMember ? 5 : 0; } } -
Debugging public class DiscountCalculator { public int calculateDiscount(int price, boolean
isMember) { if (price > 100) { return isMember ? 20 : 10; } return isMember ? 5 : 0; } } dbg() dgb() dbg() -
Debugging public class DiscountCalculator { public int calculateDiscount(int price, boolean
isMember) { if (price > 100) { return isMember ? 20 : 10; } return isMember ? 5 : 0; } } dbg() dgb() dbg() Not in Java -
Debugging public class DiscountCalculator { public int calculateDiscount(int price, boolean
isMember) { if (price > 100) { return isMember ? 20 : 10; } return isMember ? 5 : 0; } } dbg() dgb() dbg() But in Python* https://mostlynerdless.de/blog/tag/lets-create-a-debugger-together/ -
Example public class DiscountCalculator { public int calculateDiscount(int price, boolean
isMember) { if (price > 100) { return isMember ? 20 : 10; } return isMember ? 5 : 0; } } -
Example public class Main { public static void main(String[] args)
{ DiscountCalculator calculator = new DiscountCalculator(); use(calculator.calculateDiscount(150, true)); } } -
Example public class Main { public static void main(String[] args)
{ DiscountCalculator calculator = new DiscountCalculator(); System.out.println("cd(" + 150 + ", " + true + ")"); use(calculator.calculateDiscount(150, true)); } } -
public class AgentMain { public static void premain(String agentArgs, Instrumentation
inst) { inst.addTransformer(new DiscountTransformer()); } } -javaattach:agent.jar=args -
DiscountTransformer extends ClassFileTransformer “ An agent registers an implementation of
this interface using the addTransformer method so that the transformer’s transform method is invoked when classes are loaded, redefined, or retransformed – ClassFileTransformer Documentation -
ClassFileTransformer#transform byte[] transform( ClassLoader loader, String className, Class<?> klass, ProtectionDomain
domain, byte[] classfileBuffer) throws IllegalClassFormatException { ... } -
class DiscountTransformer implements ClassFileTransformer { private final ScopedClassPoolFactoryImpl scopedClassPoolFactory =
new ScopedClassPoolFactoryImpl(); @Override public byte[] transform(!!...) { ClassPool cp = scopedClassPoolFactory.create(loader, …); CtClass cc = cp.makeClass(new ByteArrayInputStream(buffer)); transform(cc); return cc.toBytecode(); } -
class DiscountTransformer implements ClassFileTransformer { private void transform(CtClass cc){ var
exprEditor = new DiscountEditor(); !// !!... for (CtMethod method : cc.getDeclaredMethods()) { method.instrument(exprEditor); } } } -
class DiscountTransformer implements ClassFileTransformer { private void transform(CtClass cc){ var
exprEditor = new DiscountEditor(); cc.instrument(exprEditor); } } -
Class Transformer class DiscountEditor extends ExprEditor { @Override public void
edit(MethodCall m) { if (isCalculateDiscountMethod(m)) { m.replace(""" { System.out.println("cd(" + $1 + ", " + $2 + ")"); $_ = $proceed($$); } """); } } } -
InstrumentationHandler.addTransformer(…) public static void addTransformer( Instrumentation inst, ClassFileTransformer trans, boolean
canRetransform) { !// !!... inst.addTransformer( new ClassFileTransformer() { @Override public byte[] transform(!!...) { byte[] old = classfileBuffer.clone(); byte[] current = transformer.transform(!!...); addDiff(transformer, old, current); return current; } }, canRetransform ) } https://github.com/parttimenerd/meta-agent/blob/main/src/main/java/me/bechberger/meta/runtime/InstrumentationHandler.java -
InstrumentationHandler.addTransformer(…) public static void addTransformer( Instrumentation inst, ClassFileTransformer trans, boolean
canRetransform) { !// !!... inst.addTransformer( new ClassFileTransformer() { @Override public byte[] transform(!!...) { byte[] old = classfileBuffer.clone(); byte[] current = transformer.transform(!!...); addDiff(transformer, old, current); return current; } }, canRetransform ) } https://github.com/parttimenerd/meta-agent/blob/main/src/main/java/me/bechberger/meta/runtime/InstrumentationHandler.java -
public class DiscountCalculator { public int calculateDiscount(int price, boolean isMember)
{ if (price > 100) { return isMember ? 20 : 10; } return isMember ? 5 : 0; } } -
Helps to understand your tools As JaCoCo works on byte
code level also constructors and static initializers are counted as methods. Some of these methods may not have a direct correspondence in Java source code, like implicit and thus generated default constructors or initializers for constants. “ https://www.jacoco.org/jacoco/trunk/doc/counters.html -
Object.equals(...) The equals method implements an equivalence relation on non-null
object references: • It is reflexive • It is symmetric • It is transitive • It is consistent https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#equals-java.lang.Object- -
Object.equals(...) The equals method implements an equivalence relation on non-null
object references: • It is reflexive • It is symmetric • It is transitive • It is consistent https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#equals-java.lang.Object- -
Making a runtime library available to all instrumented classes can
be a painful or impossible task in frameworks that use their own class loading mechanisms. […] As our minimum target is Java 1.5 JaCoCo decouples the instrumented classes and the coverage runtime through official JRE API types only. The instrumented classes communicate through the Object.equals(Object) method with the runtime. A instrumented class can retrieve its probe array instance with the following code. JaCoCo – Implementation Design https://www.jacoco.org/jacoco/trunk/doc/implementation.html “ -
!!/** * Container for runtime execution and meta data. All
access to the runtime data * is thread safe. !*/ public class RuntimeData { !/* !!... !*/ !!/** * In violation of the regular semantic of {@link Object#equals(Object)} * this implementation is used as the interface to the execution data store. * * @param args * the arguments as an {@link Object} array * @return has no meaning !*/ @Override public boolean equals(final Object args) { if (args instanceof Object[]) { getProbes((Object[]) args); } return super.equals(args); } !/* !!... !*/ https://github.com/jacoco/jacoco/blob/f0dcc6b0b78b04dd94617707551f6c2d18426d09/org.jacoco.core/src/org/jacoco/core/runtime/RuntimeData.java -
Testing @Mock List<String> mockedList; @Test public void testList() { mockedList.add("one");
Mockito.verify(mockedList).add("one"); } -
interface Iterable { !// !!... default void forEach(Consumer<? super T>
action) { Objects.requireNonNull(action); for (T element : this) { action.accept(element); } } } -
interface Iterable { !// !!... default void forEach(Consumer<? super T>
action) { MockMethodDispatcher mmd = MockMethodDispatcher.get("q9Lk1TRa", this); Callable handleForEach = mmd !!= null !&& mmd.isMocked(this) !&& !mmd.isOverridden(this, Iterable.class.getMethod("forEach", Consumer.class)) ? mmd.handle(this, Iterable.class.getMethod("forEach", Consumer.class), new Object[]{action}) : null; if (handleForEach !== null) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } if (handleForEach !!= null) { handleForEach.call(); } } } -
interface Iterable { !// !!... default void forEach(Consumer<? super T>
action) { MockMethodDispatcher mmd = MockMethodDispatcher.get("q9Lk1TRa", this); Callable handleForEach = mmd !!= null !&& mmd.isMocked(this) !&& !mmd.isOverridden(this, Iterable.class.getMethod("forEach", Consumer.class)) ? mmd.handle(this, Iterable.class.getMethod("forEach", Consumer.class), new Object[]{action}) : null; if (handleForEach !== null) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } if (handleForEach !!= null) { handleForEach.call(); } } } -
interface Iterable { !// !!... default void forEach(Consumer<? super T>
action) { MockMethodDispatcher mmd = MockMethodDispatcher.get("q9Lk1TRa", this); Callable handleForEach = mmd !!= null !&& mmd.isMocked(this) !&& !mmd.isOverridden(this, Iterable.class.getMethod("forEach", Consumer.class)) ? mmd.handle(this, Iterable.class.getMethod("forEach", Consumer.class), new Object[]{action}) : null; if (handleForEach !== null) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } if (handleForEach !!= null) { handleForEach.call(); } } } -
interface Iterable { !// !!... default void forEach(Consumer<? super T>
action) { MockMethodDispatcher mmd = MockMethodDispatcher.get("q9Lk1TRa", this); Callable handleForEach = mmd !!= null !&& mmd.isMocked(this) !&& !mmd.isOverridden(this, Iterable.class.getMethod("forEach", Consumer.class)) ? mmd.handle(this, Iterable.class.getMethod("forEach", Consumer.class), new Object[]{action}) : null; if (handleForEach !== null) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } if (handleForEach !!= null) { handleForEach.call(); } } } -
interface Iterable { !// !!... default void forEach(Consumer<? super T>
action) { MockMethodDispatcher mmd = MockMethodDispatcher.get("q9Lk1TRa", this); Callable handleForEach = mmd !!= null !&& mmd.isMocked(this) !&& !mmd.isOverridden(this, Iterable.class.getMethod("forEach", Consumer.class)) ? mmd.handle(this, Iterable.class.getMethod("forEach", Consumer.class), new Object[]{action}) : null; if (handleForEach !== null) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } if (handleForEach !!= null) { handleForEach.call(); } } } -
interface Iterable { !// !!... default void forEach(Consumer<? super T>
action) { MockMethodDispatcher mmd = MockMethodDispatcher.get("q9Lk1TRa", this); Callable handleForEach = mmd !!= null !&& mmd.isMocked(this) !&& !mmd.isOverridden(this, Iterable.class.getMethod("forEach", Consumer.class)) ? mmd.handle(this, Iterable.class.getMethod("forEach", Consumer.class), new Object[]{action}) : null; if (handleForEach !== null) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } if (handleForEach !!= null) { handleForEach.call(); } } } -
But where is the List implementation? @Mock List<String> mockedList; @Test
public void testList() { mockedList.add("one"); Mockito.verify(mockedList).add("one"); } -
http://localhost:7071/all/decompile?pattern=org.mockito.codegen.List$MockitoMock$iBo7tFTz public class List$MockitoMock$iBo7tFTz implements List, MockAccess { private static
final long serialVersionUID = 42L; private MockMethodInterceptor mockitoInterceptor; !// !!... !// $VF: synthetic field private static final Method cachedValue$46t0jdUa$sgg2351 = List.class.getMethod("add", Object.class); !// !!... @Override public boolean add(Object var1) { return (Boolean)DispatcherDefaultingToRealMethod .interceptAbstract(this, this.mockitoInterceptor, false, cachedValue$46t0jdUa$sgg2351, new Object[]{var1}); } public void setMockitoInterceptor(MockMethodInterceptor var1) { this.mockitoInterceptor = var1; } !// !!... } -
http://localhost:7071/all/decompile?pattern=org.mockito.codegen.List$MockitoMock$iBo7tFTz public class List$MockitoMock$iBo7tFTz implements List, MockAccess { private static
final long serialVersionUID = 42L; private MockMethodInterceptor mockitoInterceptor; !// !!... !// $VF: synthetic field private static final Method cachedValue$46t0jdUa$sgg2351 = List.class.getMethod("add", Object.class); !// !!... @Override public boolean add(Object var1) { return (Boolean)DispatcherDefaultingToRealMethod .interceptAbstract(this, this.mockitoInterceptor, false, cachedValue$46t0jdUa$sgg2351, new Object[]{var1}); } public void setMockitoInterceptor(MockMethodInterceptor var1) { this.mockitoInterceptor = var1; } !// !!... } -
Implementation public interface Instrumentation { !!/** * Returns an array
of all classes currently loaded !*/ @SuppressWarnings("rawtypes") Class[] getAllLoadedClasses(); Based on: https://github.com/openjdk/jdk/blob/bc5cde1b198baf6e2e36d370b0aaa907c8f35777/src/java.instrument/share/classes/java/lang/instrument/Instrumentation.java#L74 -
@AnnotationTest.Annotation(key = "key", value = "value") public class AnnotationTest {
@Retention(RetentionPolicy.RUNTIME) @interface Annotation { String key(); String value(); } @Test public void testAnnotation() throws InterruptedException { Annotation annotation = AnnotationTest.class.getAnnotation(Annotation.class); System.out.println(annotation.key()); System.out.println(annotation.value()); Thread.sleep(1000000); } } -
@AnnotationTest.Annotation(key = "key", value = "value") public class AnnotationTest {
@Retention(RetentionPolicy.RUNTIME) @interface Annotation { String key(); String value(); } @Test public void testAnnotation() throws InterruptedException { Annotation annotation = AnnotationTest.class.getAnnotation(Annotation.class); System.out.println(annotation.key()); System.out.println(annotation.value()); Thread.sleep(1000000); } } -
@AnnotationTest.Annotation(key = "key", value = "value") public class AnnotationTest {
@Retention(RetentionPolicy.RUNTIME) @interface Annotation { String key(); String value(); } @Test public void testAnnotation() throws InterruptedException { Annotation annotation = AnnotationTest.class.getAnnotation(Annotation.class); System.out.println(annotation.key()); System.out.println(annotation.value()); Thread.sleep(1000000); } } Type of this object? -
public final class $Proxy7 extends Proxy implements Annotation { private
static final Method hashCodeMethod; !// !!... public $Proxy7(InvocationHandler handler) { super(handler); } @Override public final int hashCode() { try { return (Integer) super.h.invoke(this, hashCodeMethod, null); } catch (RuntimeException | Error e) { throw e; } catch (Throwable t) { throw new UndeclaredThrowableException(t); } } -
Objects are created by the Annotation Parser public static Annotation
annotationForMap(final Class<? extends Annotation> type, final Map<String, Object> memberValues) { return AccessController.doPrivileged(new PrivilegedAction<Annotation>() { public Annotation run() { return (Annotation) Proxy.newProxyInstance( type.getClassLoader(), new Class<?>[] { type }, new AnnotationInvocationHandler(type, memberValues)); !}}); } Source: https://github.com/openjdk/jdk/blob/bc5cde1b198baf6e2e36d370b0aaa907c8f35777/src/java.base/share/classes/sun/reflect/annotation/AnnotationParser.java -
Class Loader Hierarchies platform app bootstrap X Y class reference
delegates to searches in runtime.jar https://mostlynerdless.de/blog/2023/06/02/class-loader-hierarchies/ -
Implementation public interface Instrumentation { void appendToBootstrapClassLoaderSearch( JarFile jarfile); Based
on: https://github.com/openjdk/jdk/blob/bc5cde1b198baf6e2e36d370b0aaa907c8f35777/src/java.instrument/share/classes/java/lang/instrument/Instrumentation.java#L74 -
public class PreventingInstrumentationHandler implements InstrumentationCallback { @Override public CallbackAction onAddTransformer(
ClassFileTransformer transformer) { System.err.println("Preventing " + transformer.getClass().getName()); return CallbackAction.IGNORE; } } -
public class PreventingInstrumentationHandler implements InstrumentationCallback { @Override public CallbackAction onAddTransformer(
ClassFileTransformer transformer) { System.err.println("Preventing " + transformer.getClass().getName()); return CallbackAction.IGNORE; } } -
<plugin> <groupId>me.bechberger!</groupId> <artifactId>meta-agent-maven-plugin!</artifactId> <version>0.0.3!</version> <executions> <execution> <phase>validate!</phase> <goals> <goal>meta-agent!</goal> !</goals>
!</execution> !</executions> <configuration> <callbackClasses> <callbackClass>PreventingInstrumentationHandler!</callbackClass> !</callbackClasses> <server>true!</server> !</configuration> !</plugin>