Home | History | Annotate | Download | only in validation
      1 /*******************************************************************************
      2  * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors
      3  * All rights reserved. This program and the accompanying materials
      4  * are made available under the terms of the Eclipse Public License v1.0
      5  * which accompanies this distribution, and is available at
      6  * http://www.eclipse.org/legal/epl-v10.html
      7  *
      8  * Contributors:
      9  *    Evgeny Mandrikov - initial API and implementation
     10  *
     11  *******************************************************************************/
     12 package org.jacoco.core.test.validation;
     13 
     14 import static org.junit.Assert.assertEquals;
     15 
     16 import java.lang.invoke.CallSite;
     17 import java.lang.invoke.ConstantCallSite;
     18 import java.lang.invoke.MethodHandles;
     19 import java.lang.invoke.MethodType;
     20 import java.lang.reflect.InvocationTargetException;
     21 
     22 import org.jacoco.core.instr.Instrumenter;
     23 import org.jacoco.core.runtime.IRuntime;
     24 import org.jacoco.core.runtime.RuntimeData;
     25 import org.jacoco.core.runtime.SystemPropertiesRuntime;
     26 import org.jacoco.core.test.TargetLoader;
     27 import org.junit.After;
     28 import org.junit.Before;
     29 import org.junit.Test;
     30 import org.objectweb.asm.ClassWriter;
     31 import org.objectweb.asm.Handle;
     32 import org.objectweb.asm.Label;
     33 import org.objectweb.asm.MethodVisitor;
     34 import org.objectweb.asm.Opcodes;
     35 
     36 /**
     37  * Test of ASM bug
     38  * <a href="https://gitlab.ow2.org/asm/asm/issues/317748">#317748</a> that
     39  * caused
     40  * {@code java.lang.ClassFormatError: Short length on BootstrapMethods in class file}
     41  * during instrumentation.
     42  */
     43 public class BootstrapMethodReferenceTest {
     44 
     45 	private final IRuntime runtime = new SystemPropertiesRuntime();
     46 	private final Instrumenter instrumenter = new Instrumenter(runtime);
     47 
     48 	@Before
     49 	public void setup() throws Exception {
     50 		runtime.startup(new RuntimeData());
     51 	}
     52 
     53 	@After
     54 	public void teardown() {
     55 		runtime.shutdown();
     56 	}
     57 
     58 	@Test
     59 	public void test() throws Exception {
     60 		final String className = "Example";
     61 
     62 		final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
     63 		cw.visit(Opcodes.V1_7, Opcodes.ACC_PUBLIC, className, null,
     64 				"java/lang/Object", null);
     65 
     66 		final MethodVisitor mv = cw.visitMethod(
     67 				Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "run", "()I", null,
     68 				null);
     69 		mv.visitCode();
     70 		addCauseOfResizeInstructions(mv);
     71 		final MethodType methodType = MethodType.methodType(CallSite.class,
     72 				MethodHandles.Lookup.class, String.class, MethodType.class);
     73 		final Handle handle = new Handle(Opcodes.H_INVOKESTATIC,
     74 				this.getClass().getCanonicalName().replace('.', '/'),
     75 				"bootstrap", methodType.toMethodDescriptorString(), false);
     76 		mv.visitInvokeDynamicInsn("invoke", "()I", handle);
     77 		mv.visitInsn(Opcodes.IRETURN);
     78 		mv.visitMaxs(1, 0);
     79 		mv.visitEnd();
     80 
     81 		cw.visitEnd();
     82 
     83 		final byte[] original = cw.toByteArray();
     84 		assertEquals(42, run(className, original));
     85 
     86 		final byte[] instrumented = instrumenter.instrument(original,
     87 				className);
     88 		assertEquals(42, run(className, instrumented));
     89 	}
     90 
     91 	private static int run(final String className, final byte[] bytes)
     92 			throws ClassNotFoundException, NoSuchMethodException,
     93 			InvocationTargetException, IllegalAccessException {
     94 		return (Integer) new TargetLoader().add(className, bytes)
     95 				.getMethod("run").invoke(null);
     96 	}
     97 
     98 	/**
     99 	 * Adds code that triggers usage of
    100 	 * {@link org.objectweb.asm.MethodWriter#INSERTED_FRAMES} during
    101 	 * instrumentation.
    102 	 */
    103 	private static void addCauseOfResizeInstructions(final MethodVisitor mv) {
    104 		mv.visitInsn(Opcodes.ICONST_0);
    105 		mv.visitInsn(Opcodes.ICONST_1);
    106 		final Label target = new Label();
    107 		mv.visitJumpInsn(Opcodes.IFLE, target);
    108 		for (int i = 0; i < Short.MAX_VALUE; i++) {
    109 			mv.visitInsn(Opcodes.NOP);
    110 		}
    111 		mv.visitLabel(target);
    112 	}
    113 
    114 	@SuppressWarnings("unused")
    115 	public static CallSite bootstrap(final MethodHandles.Lookup caller,
    116 			final String name, final MethodType type) throws Exception {
    117 		return new ConstantCallSite(caller.findStatic(BootstrapMethodReferenceTest.class,
    118 				"callTarget", MethodType.methodType(int.class)));
    119 	}
    120 
    121 	@SuppressWarnings("unused")
    122 	public static int callTarget() {
    123 		return 42;
    124 	}
    125 
    126 }
    127