Package org.springsource.loaded.test

Source Code of org.springsource.loaded.test.TypeRewriterTests

/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded.test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

import org.junit.Test;
import org.springsource.loaded.MethodDelta;
import org.springsource.loaded.MethodInvokerRewriter;
import org.springsource.loaded.MethodMember;
import org.springsource.loaded.ReloadableType;
import org.springsource.loaded.TypeDelta;
import org.springsource.loaded.TypeRegistry;
import org.springsource.loaded.ri.ReflectiveInterceptor;
import org.springsource.loaded.test.infra.ClassPrinter;
import org.springsource.loaded.test.infra.Result;
import org.springsource.loaded.test.infra.ResultException;


/**
* Tests for rewriting classes to include registry checks. When a class is loaded it must be 'fully instrumented' since it cannot be
* changed later on. To consider just method live updating for the moment, this means: <br>
* - methods all instrumented at load to do a registry check and possibly obtain the latest version of the dispatcher<br>
* - method invocations all modified to involve the registry
*
* The latter is necessary for the case where the method never existed on the original type. The former is sufficient if you simply
* allow method bodies to be updated over time.
*
* @author Andy Clement
*/
public class TypeRewriterTests extends SpringLoadedTests {

  /*
   * How constructor reloading works
   *
   * A constructor should be considered in 3 pieces.  There is some code that runs before the INVOKESPECIAL,
   * the INVOKESPECIAL itself, then some code that runs after the INVOKESPECIAL. It is the INVOKESPECIAL
   * that moves the 'this' (local variable 0) from UNINITIALIZED to INITIALIZED.  Whilst UNINITIALIZED it cannot
   * be passed around and any attempt to do so will leave you with something like:
   *
   * - cannot reference this before supertype constructor has been called
   *
   * or
   *
   * - expected initialized object on the stack
   *
   * Because objects need to be initialized before they get passed around, we have to ensure the INVOKESPECIAL
   * chain is run to initialized the object before we can pass it off to our dispatchers/executors.  The normal
   * approach (used for method reloading) is to change all methods as follows:
   *
   * Check if this is still the up-to-date version.  If it is then run the code as it was originally, if it was
   * not then call the dispatcher to run the new version.
   *
   * However, the object *must* be initialized before we can pass it off to the dispatcher, so the INVOKESPECIAL
   * must be run before the dispatcher is called.  This leaves us with a slightly unusual rewrite of the
   * constructors, in pseudocode:
   *
   * A() {
   *   if (hasThisConstructorChanged()?) {
   *     // Use our 'special' constructors to initialized this
   *     // call the dispatcher.
   *     // The invokespecial is rewritten in the executor to call super.___init___ since there may be stuff
   *     // to do in it.
   *   } else {
   *     run the original code
   *   }
   * }
   *
   * This will cause us problems when the top constructor is not in a reloadable type.  THis means there is
   * another variant that would be nice, where the constructor changes but the INVOKESPECIAL has not:
   *
   * A() {
   *   if (hasThisConstructorChanged()?) {
   *     if ( Has the invokespecial changed?) {
   *       // Use our 'special' constructors to initialized this
   *       // call the dispatcher.
   *       // The invokespecial is rewritten in the executor to call super.___init___ since there may be stuff
   *       // to do in it.
   *     } else {
   *       // dispatcher - do before ctor stuff
   *       // invokespecial
   *       // dispatcher - do after ctor stuff
   *     }
   *   } else {
   *     run the original code
   *   }
   *    
   * This avoids the problem for the invokespecial chain.  it will also perform a little better.
   *
   * Right now the woven code goes as follows.  Types get a new ___init___(ReloadableType) ctor in them,
   * the parameter avoids a clash occurring and gives us the superchain to initialize uninitialized objects.
   * Constructors exist in the dispatcher and executor as ___init___ methods, with a rewritten invokespecial
   * that calls the ___init___ in the supertypes real instance (which then simply dispatches back through
   * the dispatcher at its 'level').
   *
   */

  /**
   * The static initializer has had two things inserted: (1) the setting of the reloadable type (2) the static state manager
   */
  @Test
  public void staticInitializers() throws Exception {
    String t = "test.Initializers";
    String t2 = "test.SubInitializers";
    TypeRegistry r = getTypeRegistry(t + "," + t2);
    ReloadableType rtype = r.addType(t, loadBytesForClass(t));
    ReloadableType subtype = r.addType(t2, loadBytesForClass(t2));

    // Check the format of the modified static initializer
    // @formatter:off
    assertEquals(
      // initialization of reloadabletype
      "    LDC 0\n"+
      "    LDC 0\n"+
      "    INVOKESTATIC org/springsource/loaded/TypeRegistry.getReloadableType(II)Lorg/springsource/loaded/ReloadableType;\n"+
      "    PUTSTATIC test/Initializers.r$type Lorg/springsource/loaded/ReloadableType;\n"+
      // initialized of static static manager
      "    GETSTATIC test/Initializers.r$sfields Lorg/springsource/loaded/SSMgr;\n"+
      "    IFNONNULL L0\n"+
      "    NEW org/springsource/loaded/SSMgr\n"+
      "    DUP\n"+
      "    INVOKESPECIAL org/springsource/loaded/SSMgr.<init>()V\n"+
      "    PUTSTATIC test/Initializers.r$sfields Lorg/springsource/loaded/SSMgr;\n"+
      " L0\n"+
      // redirecting to another clinit - in cases where clinit changes before type is initialized
      "    GETSTATIC test/Initializers.r$type Lorg/springsource/loaded/ReloadableType;\n"+
      "    INVOKEVIRTUAL org/springsource/loaded/ReloadableType.clinitchanged()I\n"+
      "    IFEQ L1\n"+
      "    GETSTATIC test/Initializers.r$type Lorg/springsource/loaded/ReloadableType;\n"+
      "    INVOKEVIRTUAL org/springsource/loaded/ReloadableType.fetchLatest()Ljava/lang/Object;\n"+
      "    CHECKCAST test/Initializers__I\n"+
      "    INVOKEINTERFACE test/Initializers__I.___clinit___()V\n"+
      "    RETURN\n"+
      " L1\n"+
      // original code from the clinit
      "    GETSTATIC java/lang/System.out Ljava/io/PrintStream;\n"+
        "    LDC abc\n"+
        "    INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V\n"+
        " L2\n"+
      "    RETURN\n",toStringMethod(rtype.bytesLoaded,"<clinit>",false));
    // @formatter:on

    // Check that the top most reloadable type has the static state manager
    assertEquals("0x19(public static final) r$sfields Lorg/springsource/loaded/SSMgr;",
        toStringField(rtype.bytesLoaded, "r$sfields"));
    assertNull(toStringField(subtype.bytesLoaded, "r$sfields"));

    System.out.println(toStringMethod(subtype.bytesLoaded, "<clinit>", false));
    // Check the format of the modified static initializer
    // @formatter:off
    assertEquals(
      // initialization of reloadabletype
      "    LDC 0\n"+
      "    LDC 1\n"+
      "    INVOKESTATIC org/springsource/loaded/TypeRegistry.getReloadableType(II)Lorg/springsource/loaded/ReloadableType;\n"+
      "    PUTSTATIC test/SubInitializers.r$type Lorg/springsource/loaded/ReloadableType;\n"+
      // initialized of static static manager
      "    GETSTATIC test/SubInitializers.r$sfields Lorg/springsource/loaded/SSMgr;\n"+
      "    IFNONNULL L0\n"+
      "    NEW org/springsource/loaded/SSMgr\n"+
      "    DUP\n"+
      "    INVOKESPECIAL org/springsource/loaded/SSMgr.<init>()V\n"+
      "    PUTSTATIC test/SubInitializers.r$sfields Lorg/springsource/loaded/SSMgr;\n"+
      " L0\n"+
      "    GETSTATIC test/SubInitializers.r$type Lorg/springsource/loaded/ReloadableType;\n"+
      "    INVOKEVIRTUAL org/springsource/loaded/ReloadableType.clinitchanged()I\n"+
      "    IFEQ L1\n"+
      "    GETSTATIC test/SubInitializers.r$type Lorg/springsource/loaded/ReloadableType;\n"+
      "    INVOKEVIRTUAL org/springsource/loaded/ReloadableType.fetchLatest()Ljava/lang/Object;\n"+
      "    CHECKCAST test/SubInitializers__I\n"+
      "    INVOKEINTERFACE test/SubInitializers__I.___clinit___()V\n"+
      "    RETURN\n"+
      " L1\n"+
      // original code from the clinit
      "    GETSTATIC java/lang/System.out Ljava/io/PrintStream;\n"+
        "    LDC def\n"+
        "    INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/String;)V\n"+
        " L2\n"+
      "    RETURN\n",toStringMethod(subtype.bytesLoaded,"<clinit>",false));
    // @formatter:on

    // Now look at the set/get field accessing forwarders
    // @formatter:off
    assertEquals(
        "    ALOAD 0\n"+
        "    GETFIELD test/Initializers.r$fields Lorg/springsource/loaded/ISMgr;\n"+
        "    IFNONNULL L0\n"+
        "    ALOAD 0\n"+
        "    NEW org/springsource/loaded/ISMgr\n"+
        "    DUP\n"+
          "    ALOAD 0\n"+
          "    GETSTATIC test/Initializers.r$type Lorg/springsource/loaded/ReloadableType;\n"+
          "    INVOKESPECIAL org/springsource/loaded/ISMgr.<init>(Ljava/lang/Object;Lorg/springsource/loaded/ReloadableType;)V\n"+
//        "    INVOKESPECIAL org/springsource/loaded/ISMgr.<init>()V\n"+
        "    PUTFIELD test/Initializers.r$fields Lorg/springsource/loaded/ISMgr;\n"+
        " L0\n"+
        "    ALOAD 0\n"+
        "    GETFIELD test/Initializers.r$fields Lorg/springsource/loaded/ISMgr;\n"+
        "    GETSTATIC test/Initializers.r$type Lorg/springsource/loaded/ReloadableType;\n"+
        "    ALOAD 2\n"+
        "    ALOAD 1\n"+
        "    ALOAD 3\n"+
        "    INVOKEVIRTUAL org/springsource/loaded/ISMgr.setValue(Lorg/springsource/loaded/ReloadableType;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)V\n"+
        "    RETURN\n",
        toStringMethod(rtype.bytesLoaded, "r$set", false));
    // @formatter:on

    // The subtype and supertype one do vary, due to using different reloadabletype objects
    // @formatter:off
    assertEquals(
        "    ALOAD 0\n"+
        "    GETFIELD test/SubInitializers.r$fields Lorg/springsource/loaded/ISMgr;\n"+
        "    IFNONNULL L0\n"+
        "    ALOAD 0\n"+
        "    NEW org/springsource/loaded/ISMgr\n"+
        "    DUP\n"+
          "    ALOAD 0\n"+
          "    GETSTATIC test/SubInitializers.r$type Lorg/springsource/loaded/ReloadableType;\n"+
          "    INVOKESPECIAL org/springsource/loaded/ISMgr.<init>(Ljava/lang/Object;Lorg/springsource/loaded/ReloadableType;)V\n"+
//        "    INVOKESPECIAL org/springsource/loaded/ISMgr.<init>()V\n"+
        "    PUTFIELD test/SubInitializers.r$fields Lorg/springsource/loaded/ISMgr;\n"+
        " L0\n"+
        "    ALOAD 0\n"+
        "    GETFIELD test/SubInitializers.r$fields Lorg/springsource/loaded/ISMgr;\n"+
        "    GETSTATIC test/SubInitializers.r$type Lorg/springsource/loaded/ReloadableType;\n"+
        "    ALOAD 2\n"+
        "    ALOAD 1\n"+
        "    ALOAD 3\n"+
        "    INVOKEVIRTUAL org/springsource/loaded/ISMgr.setValue(Lorg/springsource/loaded/ReloadableType;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/String;)V\n"+
        "    RETURN\n",
        toStringMethod(subtype.bytesLoaded, "r$set", false));
    // @formatter:on
  }

  @Test
  public void staticInitializersAndInterfaces() throws Exception {
    String t = "test.Interface";
    String t2 = "test.SubInterface";
    TypeRegistry r = getTypeRegistry(t + "," + t2);
    ReloadableType itype = r.addType(t, loadBytesForClass(t));
    ReloadableType subitype = r.addType(t2, loadBytesForClass(t2));

//    ClassPrinter.print(subitype.bytesLoaded);

    // An interface will get the reloadable type field
    assertEquals("0x19(public static final) r$type Lorg/springsource/loaded/ReloadableType;",
        toStringField(itype.bytesLoaded, "r$type"));

    // An interface will get the reloadable static state manager instance
    assertEquals("0x19(public static final) r$sfields Lorg/springsource/loaded/SSMgr;",
        toStringField(itype.bytesLoaded, "r$sfields"));

    // The static initializer will be augmented to initialize both of these
    // @formatter:off
    assertEquals(
      "    LDC 0\n"+
      "    LDC "+itype.getId()+"\n"+
      "    INVOKESTATIC org/springsource/loaded/TypeRegistry.getReloadableType(II)Lorg/springsource/loaded/ReloadableType;\n"+
      "    PUTSTATIC test/Interface.r$type Lorg/springsource/loaded/ReloadableType;\n"+
      "    GETSTATIC test/Interface.r$sfields Lorg/springsource/loaded/SSMgr;\n"+
      "    IFNONNULL L0\n"+
      "    NEW org/springsource/loaded/SSMgr\n"+
      "    DUP\n"+
      "    INVOKESPECIAL org/springsource/loaded/SSMgr.<init>()V\n"+
      "    PUTSTATIC test/Interface.r$sfields Lorg/springsource/loaded/SSMgr;\n"+
      " L0\n"+
      "    GETSTATIC test/Interface.r$type Lorg/springsource/loaded/ReloadableType;\n"+
      "    INVOKEVIRTUAL org/springsource/loaded/ReloadableType.clinitchanged()I\n"+
      "    IFEQ L1\n"+
      "    GETSTATIC test/Interface.r$type Lorg/springsource/loaded/ReloadableType;\n"+
      "    INVOKEVIRTUAL org/springsource/loaded/ReloadableType.fetchLatest()Ljava/lang/Object;\n"+
      "    CHECKCAST test/Interface__I\n"+
      "    INVOKEINTERFACE test/Interface__I.___clinit___()V\n"+
      "    RETURN\n"+
      " L1\n"+
      "    RETURN\n",
      toStringMethod(itype.bytesLoaded, "<clinit>", false));
    // @formatter:on

    // Sub interface should look identical
    // An interface will get the reloadable type field
    assertEquals("0x19(public static final) r$type Lorg/springsource/loaded/ReloadableType;",
        toStringField(itype.bytesLoaded, "r$type"));

    // An interface will get the reloadable static state manager instance
    assertEquals("0x19(public static final) r$sfields Lorg/springsource/loaded/SSMgr;",
        toStringField(itype.bytesLoaded, "r$sfields"));

    // The static initializer will be augmented to initialize both of these
    // @formatter:off
    assertEquals(
      "    LDC 0\n"+
      "    LDC 1\n"+
      "    INVOKESTATIC org/springsource/loaded/TypeRegistry.getReloadableType(II)Lorg/springsource/loaded/ReloadableType;\n"+
      "    PUTSTATIC test/SubInterface.r$type Lorg/springsource/loaded/ReloadableType;\n"+
      "    GETSTATIC test/SubInterface.r$sfields Lorg/springsource/loaded/SSMgr;\n"+
      "    IFNONNULL L0\n"+
      "    NEW org/springsource/loaded/SSMgr\n"+
      "    DUP\n"+
      "    INVOKESPECIAL org/springsource/loaded/SSMgr.<init>()V\n"+
      "    PUTSTATIC test/SubInterface.r$sfields Lorg/springsource/loaded/SSMgr;\n"+
      " L0\n"+
      "    GETSTATIC test/SubInterface.r$type Lorg/springsource/loaded/ReloadableType;\n"+
      "    INVOKEVIRTUAL org/springsource/loaded/ReloadableType.clinitchanged()I\n"+
      "    IFEQ L1\n"+
      "    GETSTATIC test/SubInterface.r$type Lorg/springsource/loaded/ReloadableType;\n"+
      "    INVOKEVIRTUAL org/springsource/loaded/ReloadableType.fetchLatest()Ljava/lang/Object;\n"+
      "    CHECKCAST test/SubInterface__I\n"+
      "    INVOKEINTERFACE test/SubInterface__I.___clinit___()V\n"+
      "    RETURN\n"+
      " L1\n"+
      "    RETURN\n",
      toStringMethod(subitype.bytesLoaded, "<clinit>", false));
    // @formatter:on

    // Although the interface has fields, they are constants and so there is no code to implement them
    assertEquals("0x9(public static) i I 234", toStringField(itype.bytesLoaded, "i"));
    assertEquals("0x9(public static) j I 456", toStringField(subitype.bytesLoaded, "j"));
  }

  // Looking at a type with only a default ctor (so didn't originally declare anything)
  @Test
  public void constructorReloadingDefault() throws Exception {
    String t = "ctors.Default";
    TypeRegistry r = getTypeRegistry(t);
    ReloadableType rtype = r.addType(t, loadBytesForClass(t));
    MethodMember[] ctor = rtype.getLatestTypeDescriptor().getConstructors();
    assertEquals(1, ctor.length); // Only the ctor in the original type is in the descriptor
    assertEquals("0x1 <init>()V", ctor[0].toString());
    // There are in fact two constructors, one is our special one
    result = runConstructor(rtype.getClazz(), magicDescriptorForGeneratedCtors, new Object[] { null });
    assertNotNull(result.returnValue);
  }

  // Tests for reloading the body of an existing constructor
  @Test
  public void constructorReloading() throws Exception {
    String t = "ctors.One";
    TypeRegistry r = getTypeRegistry(t);
    ReloadableType rtype = r.addType(t, loadBytesForClass(t));

    result = runConstructor(rtype.getClazz(), "");
    assertEquals("Hello Andy", result.stdout);
    assertEquals(rtype.getClazz().getName(), result.returnValue.getClass().getName());

    // Just reload that same version (creates new CurrentLiveVersion)
    rtype.loadNewVersion("000", rtype.bytesInitial);

    result = runConstructor(rtype.getClazz(), "");
    assertEquals("Hello Andy", result.stdout);
    assertEquals(rtype.getClazz().getName(), result.returnValue.getClass().getName());

    // Load a real new version
    rtype.loadNewVersion("002", retrieveRename(t, t + "2"));
    result = runConstructor(rtype.getClazz(), "");
    assertEquals("Hello World", result.stdout);
    assertEquals(rtype.getClazz().getName(), result.returnValue.getClass().getName());
  }

  // similar to previous but constructor takes a parameter
  @Test
  public void constructorReloading2() throws Exception {
    String t = "ctors.Two";
    TypeRegistry r = getTypeRegistry(t);
    ReloadableType rtype = r.addType(t, loadBytesForClass(t));

    result = runConstructor(rtype.getClazz(), "java.lang.String", "Wibble");
    assertEquals("Wibble", result.stdout);
    assertEquals(rtype.getClazz().getName(), result.returnValue.getClass().getName());

    // Just reload that same version (creates new CurrentLiveVersion)
    rtype.loadNewVersion("000", rtype.bytesInitial);

    result = runConstructor(rtype.getClazz(), "java.lang.String", "Wobble");
    assertEquals("Wobble", result.stdout);
    assertEquals(rtype.getClazz().getName(), result.returnValue.getClass().getName());

    // Load a real new version
    rtype.loadNewVersion("002", retrieveRename(t, t + "2"));
//    ClassPrinter.print(rtype.getLatestExecutorBytes());
    result = runConstructor(rtype.getClazz(), "java.lang.String", "Wabble");
    assertEquals("WabbleWabble", result.stdout);
    assertEquals(rtype.getClazz().getName(), result.returnValue.getClass().getName());
  }

  /**
   * Annotation reloading, currently not allowed but hopefully doesn't impact regular code development, they just have no
   * reloadable type representation.
   */
  @Test
  public void annotation() throws Exception {
    String a = "annos.SimpleAnnotation";
    String b = "annos.AnnotatedType";
    TypeRegistry r = getTypeRegistry(a + "," + b);
    ReloadableType annotationType = r.addType(a, loadBytesForClass(a));
    ReloadableType annotatedType = r.addType(b, loadBytesForClass(b));

    assertNull(annotationType);
    result = runUnguarded(annotatedType.getClazz(), "printit");
    assertEquals("@annos.SimpleAnnotation(value=hello)", result.stdout);

    // reload annotated type
    annotatedType.loadNewVersion("2", retrieveRename(b, b + "2"));

    result = runUnguarded(annotatedType.getClazz(), "printit");
    assertEquals(">>@annos.SimpleAnnotation(value=hello)", result.stdout);
  }

  /**
   * Testing reflective field access when the field flips from static to non-static.
   */
  @Test
  public void reflectiveFieldGet() throws Exception {
    String a = "reflect.FieldAccessing";
    TypeRegistry r = getTypeRegistry(a);
    ReloadableType type = r.addType(a, loadBytesForClass(a));

    Object o = type.getClazz().newInstance();

    // Access the fields
    result = runOnInstance(type.getClazz(), o, "geti");
    assertEquals(4, ((Integer) result.returnValue).intValue());
    result = runOnInstance(type.getClazz(), o, "getj");
    assertEquals(5, ((Integer) result.returnValue).intValue());

    // Load a new version that switches them from static<>non-static
    type.loadNewVersion("2", retrieveRename(a, a + "2"));
    try {
      result = runOnInstance(type.getClazz(), o, "geti");
      fail();
    } catch (ResultException re) {
      Throwable cause = re.getCause();
      assertTrue(cause instanceof InvocationTargetException);
      cause = ((InvocationTargetException) cause).getCause();
      assertTrue(cause instanceof IncompatibleClassChangeError);
      assertEquals("Expected non-static field reflect/FieldAccessing.i", cause.getMessage());
    }

    try {
      result = runOnInstance(type.getClazz(), o, "getj");
      fail();
    } catch (ResultException re) {
      Throwable cause = re.getCause();
      assertTrue(cause instanceof InvocationTargetException);
      cause = ((InvocationTargetException) cause).getCause();
      assertTrue(cause instanceof IncompatibleClassChangeError);
      assertEquals("Expected static field reflect/FieldAccessing.j", cause.getMessage());
    }
  }

  /**
   * Testing reflective field access when the field flips from static to non-static.
   */
  @Test
  public void reflectiveFieldSet() throws Exception {
    String a = "reflect.FieldWriting";
    TypeRegistry r = getTypeRegistry(a);
    ReloadableType type = r.addType(a, loadBytesForClass(a));

    Object o = type.getClazz().newInstance();

    // Access the fields
    result = runOnInstance(type.getClazz(), o, "seti", 123);
    result = runOnInstance(type.getClazz(), o, "setj", 456);

    // Load a new version that switches them from static<>non-static
    type.loadNewVersion("2", retrieveRename(a, a + "2"));
    try {
      result = runOnInstance(type.getClazz(), o, "seti", 456);
      fail();
    } catch (ResultException re) {
      Throwable cause = re.getCause();
      assertTrue(cause instanceof InvocationTargetException);
      cause = ((InvocationTargetException) cause).getCause();
      assertTrue(cause instanceof IncompatibleClassChangeError);
      assertEquals("Expected non-static field reflect/FieldWriting.i", cause.getMessage());
    }

    try {
      result = runOnInstance(type.getClazz(), o, "setj", 789);
      fail();
    } catch (ResultException re) {
      Throwable cause = re.getCause();
      assertTrue(cause instanceof InvocationTargetException);
      cause = ((InvocationTargetException) cause).getCause();
      assertTrue(cause instanceof IncompatibleClassChangeError);
      assertEquals("Expected static field reflect/FieldWriting.j", cause.getMessage());
    }
  }

  // test that also relies on correct dispatch to super constructors to do things
  @Test
  public void constructorReloading3() throws Exception {
    String t = "ctors.Three";
    String st = "ctors.SuperThree";
    TypeRegistry r = getTypeRegistry(t + ",ctors.SuperThree");

    ReloadableType supertype = r.addType("ctors.SuperThree", loadBytesForClass("ctors.SuperThree"));
    ReloadableType rtype = r.addType(t, loadBytesForClass(t));

    result = runConstructor(rtype.getClazz(), "");
    assertEquals("Hello from SuperThree.Hello from Three.", result.stderr);
    assertEquals(rtype.getClazz().getName(), result.returnValue.getClass().getName());

    // Just reload that same version (creates new CurrentLiveVersion)
    rtype.loadNewVersion("000", rtype.bytesInitial);

    result = runConstructor(rtype.getClazz(), "");
    assertEquals("Hello from SuperThree.Hello from Three.", result.stderr);
    assertEquals(rtype.getClazz().getName(), result.returnValue.getClass().getName());

    // Load a real new version
    rtype.loadNewVersion("002", retrieveRename(t, t + "2"));
    result = runConstructor(rtype.getClazz(), "");
    assertEquals("Hello from SuperThree.Hello from Three2.", result.stderr);
    assertEquals(rtype.getClazz().getName(), result.returnValue.getClass().getName());

    supertype.loadNewVersion("002", retrieveRename(st, st + "2"));
    result = runConstructor(rtype.getClazz(), "");
    assertEquals("Hello from SuperThree2.Hello from Three2.", result.stderr);
    assertEquals(rtype.getClazz().getName(), result.returnValue.getClass().getName());
  }

  // Now looking at simply changing what fields we initialize in a ctor - the simplest case really
  @Test
  public void constructorReloading4() throws Exception {
    String theType = "ctors.Setter";
    TypeRegistry r = getTypeRegistry(theType);
    ReloadableType rtype = r.addType(theType, loadBytesForClass(theType));

    result = runConstructor(rtype.getClazz(), "");
    Result res = runOnInstance(rtype.getClazz(), result.returnValue, "getInteger");
    assertEquals(1, ((Integer) res.returnValue).intValue());
    res = runOnInstance(rtype.getClazz(), result.returnValue, "getString");
    assertEquals("one", (res.returnValue));

    rtype.loadNewVersion("000", rtype.bytesInitial);
    result = runConstructor(rtype.getClazz(), "");
    res = runOnInstance(rtype.getClazz(), result.returnValue, "getInteger");
    assertEquals(1, ((Integer) res.returnValue).intValue());
    res = runOnInstance(rtype.getClazz(), result.returnValue, "getString");
    assertEquals("one", (res.returnValue));

    rtype.loadNewVersion("002", retrieveRename(theType, theType + "2"));
    result = runConstructor(rtype.getClazz(), "");
    res = runOnInstance(rtype.getClazz(), result.returnValue, "getInteger");
    assertEquals(2, ((Integer) res.returnValue).intValue());
    res = runOnInstance(rtype.getClazz(), result.returnValue, "getString");
    assertEquals("two", (res.returnValue));

    // version 3 no longer sets the string
    rtype.loadNewVersion("003", retrieveRename(theType, theType + "3"));
    result = runConstructor(rtype.getClazz(), "");
    res = runOnInstance(rtype.getClazz(), result.returnValue, "getInteger");
    assertEquals(3, ((Integer) res.returnValue).intValue());
    res = runOnInstance(rtype.getClazz(), result.returnValue, "getString");
    assertNull(res.returnValue);
  }

  @Test
  public void constructorReloading5() throws Exception {
    String supertype = "ctors.A";
    String subtype = "ctors.B";
    TypeRegistry r = getTypeRegistry(supertype + "," + subtype);
    ReloadableType rsupertype = r.addType(supertype, loadBytesForClass(supertype));
    ReloadableType rsubtype = r.addType(subtype, loadBytesForClass(subtype));
    Result res = null;

    // Use the code 'untouched'
    result = runConstructor(rsubtype.getClazz(), "int", 3);
    res = runOnInstance(rsubtype.getClazz(), result.returnValue, "getString");
    assertEquals("3", (res.returnValue));

    // reload the types
    rsubtype.loadNewVersion("000", rsubtype.bytesInitial);
    rsupertype.loadNewVersion("000", rsupertype.bytesInitial);
    result = runConstructor(rsubtype.getClazz(), "int", 5);
    res = runOnInstance(rsubtype.getClazz(), result.returnValue, "getString");
    assertEquals("5", (res.returnValue));

    // load a new version of the subtype which adjusts the super call
    rsubtype.loadNewVersion("001", retrieveRename(subtype, subtype + "2"));
    result = runConstructor(rsubtype.getClazz(), "int", 5);
    res = runOnInstance(rsubtype.getClazz(), result.returnValue, "getString");
    assertEquals("27", (res.returnValue));
  }

  // Looking at how constructors get rewritten when the target did not originally declare the constructor
  @Test
  public void newConstructors() throws Exception {
    String caller = "ctors.Caller";
    String callee = "ctors.Callee";
    TypeRegistry r = getTypeRegistry(caller + "," + callee);
    ReloadableType rcaller = r.addType(caller, loadBytesForClass(caller));
    ReloadableType rcallee = r.addType(callee, loadBytesForClass(callee));
    Result res = null;

    // Use the code 'untouched'
    Object callerInstance = rcaller.getClazz().newInstance();
    res = runOnInstance(rcaller.getClazz(), callerInstance, "runA");
    assertEquals("callee", res.returnValue.toString());

    // Reload the code, a new constructor in the callee and runB() invokes it
    rcaller.loadNewVersion("002", retrieveRename(caller, caller + "2", "ctors.Callee2:ctors.Callee"));
    rcallee.loadNewVersion("002", retrieveRename(callee, callee + "2"));

    // The new runB() method will include a call 'new Callee("abcde")'
    // Without a rewrite, it will cause this problem:
    // Caused by: java.lang.NoSuchMethodError: ctors.Callee.<init>(Ljava/lang/String;)V
    //   at ctors.Caller__E002.runB(Caller2.java:10)
    res = runOnInstance(rcaller.getClazz(), callerInstance, "runB");
    assertEquals("callee", res.returnValue.toString());
  }

  /**
   * Final fields. Final fields are typically inlined at their usage sites, which means in a reloadable scenario they can
   * introduce unexpected (but correct) behaviour.
   */
  @Test
  public void constructorsAndFinalFields() throws Exception {
    String caller = "ctors.Finals";
    TypeRegistry r = getTypeRegistry(caller);
    ReloadableType rcaller = r.addType(caller, loadBytesForClass(caller));
    Result res = null;

    // Use the code 'untouched'
    Object callerInstance = rcaller.getClazz().newInstance();
    res = runOnInstance(rcaller.getClazz(), callerInstance, "getValue");
    assertEquals("324 abc", res.returnValue.toString());

    // Reload the code
    rcaller.loadNewVersion("002", retrieveRename(caller, caller + "2"));

    // Constants are inlined - that is why getValue() returns the new value for the String
    res = runOnInstance(rcaller.getClazz(), callerInstance, "getValue");
    assertEquals("324 def", res.returnValue.toString());

    // Without changing visibility from final this would cause an IllegalAccessError from the ___init___ method.
    // That is because, if the constant hasn't changed value, there will be  PUTFIELD for an already set final
    // field in the ___init___ that gets run.  If the value is changed an entirely different codepath is used.
    callerInstance = rcaller.getClazz().newInstance();

  }

  /**
   * Super/subtypes and in the reload the new constructor in the subtype calls a constructor in the supertype that did not
   * initially exist. I think the rewrite of the invokespecial should be able to determine it isn't there at the start and use the
   * all powerful _execute method added to the instance at startup (rather than the ___init___) which will then call through the
   * dispatcher.
   */
  @Test
  public void newConstructors2() throws Exception {
    String caller = "ctors.CallerB";
    String callee = "ctors.CalleeB";
    String calleeSuper = "ctors.CalleeSuperB";
    TypeRegistry r = getTypeRegistry(caller + "," + callee + "," + calleeSuper);
    ReloadableType rcaller = r.addType(caller, loadBytesForClass(caller));
    ReloadableType rcalleeSuper = r.addType(calleeSuper, loadBytesForClass(calleeSuper));
    ReloadableType rcallee = r.addType(callee, loadBytesForClass(callee));
    Result res = null;

    // Use the code 'untouched'
    Object callerInstance = rcaller.getClazz().newInstance();
    res = runOnInstance(rcaller.getClazz(), callerInstance, "runA");
    assertEquals("callee", res.returnValue.toString());

    // Reload the code, a new constructor in the callee and runB() invokes it
    rcalleeSuper.loadNewVersion("002", retrieveRename(calleeSuper, calleeSuper + "2"));
    rcaller.loadNewVersion("002", retrieveRename(caller, caller + "2", "ctors.CalleeB2:ctors.CalleeB"));
    rcallee.loadNewVersion("002", retrieveRename(callee, callee + "2", "ctors.CalleeSuperB2:ctors.CalleeSuperB"));

    // The new runB() method will include a call 'new Callee("abcde")'
    // Without a rewrite, it will cause this problem:
    // Caused by: java.lang.NoSuchMethodError: ctors.Callee.<init>(Ljava/lang/String;)V
    //   at ctors.Caller__E002.runB(Caller2.java:10)
    // This new Callee constructor also invokes a constructor in the supertype that wasn't there initially
    res = runOnInstance(rcaller.getClazz(), callerInstance, "runB");
    assertEquals("callee", res.returnValue.toString());
    assertContains("Super number was 32768", res.toString());
    assertContains("abcde", res.toString());
  }

  // TODO synchronized fields
  // TODO synchronization around access to the static/instance field maps
  @Test
  public void rewriteInstanceFields() throws Exception {
    // turn off to simplify debugging verify problems:
    // GlobalConfiguration.rewriteMethodExecutions = false;
    TypeRegistry r = getTypeRegistry("data.HelloWorldFields");
    ReloadableType rtype = r.addType("data.HelloWorldFields", loadBytesForClass("data.HelloWorldFields"));
    assertEquals("Hello Andy", runUnguarded(rtype.getClazz(), "greet").stdout);

    // Just reload that same version (creates new CurrentLiveVersion)
    rtype.loadNewVersion("000", rtype.bytesInitial);
    assertEquals("Hello Andy", runUnguarded(rtype.getClazz(), "greet").stdout);

    // Load a real new version
    rtype.loadNewVersion("002", retrieveRename("data.HelloWorldFields", "data.HelloWorldFields002"));
    assertEquals("Hello Christian", runUnguarded(rtype.getClazz(), "greet").stdout);

    Object o = rtype.getClazz().newInstance();
    assertEquals("Hello Christian", runOnInstance(rtype.getClazz(), o, "greet").stdout);
    runOnInstance(rtype.getClazz(), o, "setMessage", "Hello Christian");
    assertEquals("Hello Christian", runOnInstance(rtype.getClazz(), o, "greet").stdout);
  }

  @Test
  public void primitiveFieldRewriting() throws Exception {
    TypeRegistry r = getTypeRegistry("data.FieldsB");
    ReloadableType rtype = r.addType("data.FieldsB", loadBytesForClass("data.FieldsB"));
    Class<?> c = rtype.getClazz();
    Object instance = c.newInstance();
    assertEquals(23, runOnInstance(c, instance, "getI").returnValue);
    assertEquals("{1,2,3}", intArrayToString(runOnInstance(c, instance, "getIs").returnValue));
    assertEquals(false, runOnInstance(c, instance, "isB").returnValue);
    assertEquals('a', runOnInstance(c, instance, "getC").returnValue);
    assertEquals((short) 123, runOnInstance(c, instance, "getS").returnValue);
    assertEquals(10715136L, runOnInstance(c, instance, "getL").returnValue);
    assertEquals(2.0d, runOnInstance(c, instance, "getD").returnValue);
    assertEquals(1.4f, runOnInstance(c, instance, "getF").returnValue);
    assertEquals("Hello Andy", runOnInstance(c, instance, "getTheMessage").returnValue);

    runOnInstance(c, instance, "setI", 312);
    assertEquals(312, runOnInstance(c, instance, "getI").returnValue);
    runOnInstance(c, instance, "setIs", new int[] { 4, 5 });
    assertEquals("{4,5}", intArrayToString(runOnInstance(c, instance, "getIs").returnValue));
    runOnInstance(c, instance, "setB", true);
    assertEquals(true, runOnInstance(c, instance, "isB").returnValue);
    runOnInstance(c, instance, "setC", 'z');
    assertEquals('z', runOnInstance(c, instance, "getC").returnValue);
    runOnInstance(c, instance, "setS", (short) 12);
    assertEquals((short) 12, runOnInstance(c, instance, "getS").returnValue);
    runOnInstance(c, instance, "setL", 36L);
    assertEquals(36L, runOnInstance(c, instance, "getL").returnValue);
    runOnInstance(c, instance, "setD", 111.0d);
    assertEquals(111.0d, runOnInstance(c, instance, "getD").returnValue);
    runOnInstance(c, instance, "setF", 123.0f);
    assertEquals(123.0f, runOnInstance(c, instance, "getF").returnValue);
    runOnInstance(c, instance, "setTheMessage", "Hello");
    assertEquals("Hello", runOnInstance(c, instance, "getTheMessage").returnValue);
  }

  @Test
  public void primitiveStaticFieldRewriting() throws Exception {
    TypeRegistry r = getTypeRegistry("data.StaticFieldsB");
    ReloadableType rtype = r.addType("data.StaticFieldsB", loadBytesForClass("data.StaticFieldsB"));
    Class<?> c = rtype.getClazz();
    Object instance = c.newInstance();
    assertEquals(23, runOnInstance(c, instance, "getI").returnValue);
    assertEquals(false, runOnInstance(c, instance, "isB").returnValue);
    assertEquals("{true,false,true}", objectArrayToString(runOnInstance(c, instance, "getBs").returnValue));
    assertEquals('a', runOnInstance(c, instance, "getC").returnValue);
    assertEquals((short) 123, runOnInstance(c, instance, "getS").returnValue);
    assertEquals(10715136L, runOnInstance(c, instance, "getL").returnValue);
    assertEquals(2.0d, runOnInstance(c, instance, "getD").returnValue);
    assertEquals(1.4f, runOnInstance(c, instance, "getF").returnValue);
    assertEquals("Hello Andy", runOnInstance(c, instance, "getTheMessage").returnValue);

    Object instance2 = c.newInstance();
    runOnInstance(c, instance2, "setI", 312);
    assertEquals(312, runOnInstance(c, instance2, "getI").returnValue);
    runOnInstance(c, instance2, "setB", true);
    assertEquals(true, runOnInstance(c, instance2, "isB").returnValue);
    runOnInstance(c, instance, "setBs", (Object) new Boolean[] { false, true });
    assertEquals("{false,true}", objectArrayToString(runOnInstance(c, instance, "getBs").returnValue));
    runOnInstance(c, instance2, "setC", 'z');
    assertEquals('z', runOnInstance(c, instance2, "getC").returnValue);
    runOnInstance(c, instance2, "setS", (short) 12);
    assertEquals((short) 12, runOnInstance(c, instance2, "getS").returnValue);
    runOnInstance(c, instance2, "setL", 36L);
    assertEquals(36L, runOnInstance(c, instance2, "getL").returnValue);
    runOnInstance(c, instance2, "setD", 111.0d);
    assertEquals(111.0d, runOnInstance(c, instance2, "getD").returnValue);
    runOnInstance(c, instance2, "setF", 123.0f);
    assertEquals(123.0f, runOnInstance(c, instance2, "getF").returnValue);
    runOnInstance(c, instance2, "setTheMessage", "Hello");
    assertEquals("Hello", runOnInstance(c, instance2, "getTheMessage").returnValue);
  }

  @Test
  public void rewriteStaticFields() throws Exception {
    // turn off to simplify debugging verify problems:
    // GlobalConfiguration.rewriteMethodExecutions = true;
    TypeRegistry r = getTypeRegistry("data.HelloWorldStaticFields");
    ReloadableType rtype = r.addType("data.HelloWorldStaticFields", loadBytesForClass("data.HelloWorldStaticFields"));
    assertEquals("Hello Andy", runUnguarded(rtype.getClazz(), "greet").stdout);

    // Just reload that same version (creates new CurrentLiveVersion)
    rtype.loadNewVersion("000", rtype.bytesInitial);
    assertEquals("Hello Andy", runUnguarded(rtype.getClazz(), "greet").stdout);

    // Load a real new version
    // won't say 'hello christian' because field static initializers are not re-run
    rtype.loadNewVersion("002", retrieveRename("data.HelloWorldStaticFields", "data.HelloWorldStaticFields002"));
    assertEquals("Hello Andy", runUnguarded(rtype.getClazz(), "greet").stdout);

    Object o = rtype.getClazz().newInstance();
    assertEquals("Hello Andy", runOnInstance(rtype.getClazz(), o, "greet").stdout);
    runOnInstance(rtype.getClazz(), o, "setMessage", "Hello Christian");
    assertEquals("Hello Christian", runOnInstance(rtype.getClazz(), o, "greet").stdout);
  }

  /**
   * Just does a rewrite and checks the result will run a simple method
   */
  @Test
  public void rewrite() throws Exception {
    TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorld");
    ReloadableType rtype = typeRegistry.addType("data.HelloWorld", loadBytesForClass("data.HelloWorld"));
    runUnguarded(rtype.getClazz(), "greet");

    // Just transform the existing version into a dispatcher/executor
    rtype.loadNewVersion("000", rtype.bytesInitial);
    assertEquals("Greet from HelloWorld", runUnguarded(rtype.getClazz(), "greet").stdout);

    // Load a real new version
    rtype.loadNewVersion("002", retrieveRename("data.HelloWorld", "data.HelloWorld002"));
    assertEquals("Greet from HelloWorld 2", runUnguarded(rtype.getClazz(), "greet").stdout);
  }

  /**
   * Simple rewrite but the class has a static initializer (which we have to merge some code into)
   */
  @Test
  public void rewriteWithStaticInitializer() throws Exception {
    TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorldClinit");
    ReloadableType rtype = typeRegistry.addType("data.HelloWorldClinit", loadBytesForClass("data.HelloWorldClinit"));

    runUnguarded(rtype.getClazz(), "greet");

    // Just transform the existing version into a dispatcher/executor
    rtype.loadNewVersion("000", rtype.bytesInitial);
    assertEquals("Greet from HelloWorldClinit", runUnguarded(rtype.getClazz(), "greet").stdout);

    // Load a real new version
    rtype.loadNewVersion("002", retrieveRename("data.HelloWorldClinit", "data.HelloWorldClinit002"));
    assertEquals("Greet from HelloWorldClinit 2", runUnguarded(rtype.getClazz(), "greet").stdout);
  }

  @Test
  public void rewriteWithReturnValues() throws Exception {
    TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorld");
    ReloadableType rtype = typeRegistry.addType("data.HelloWorld", loadBytesForClass("data.HelloWorld"));

    Result r = runUnguarded(rtype.getClazz(), "getValue");
    assertEquals("message from HelloWorld", r.returnValue);

    rtype.loadNewVersion("000", rtype.bytesInitial);
    r = runUnguarded(rtype.getClazz(), "getValue");
    assertEquals("message from HelloWorld", r.returnValue);
  }

  @Test
  public void rewriteWithPrimitiveReturnValues_int() throws Exception {
    TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorldPrimitive");
    ReloadableType rtype = typeRegistry.addType("data.HelloWorldPrimitive", loadBytesForClass("data.HelloWorldPrimitive"));

    Result r = runUnguarded(rtype.getClazz(), "getValue");
    assertTrue(r.returnValue instanceof Integer);
    assertEquals(42, r.returnValue);

    rtype.loadNewVersion("000", rtype.bytesInitial);
    r = runUnguarded(rtype.getClazz(), "getValue");
    assertTrue(r.returnValue instanceof Integer);
    assertEquals(42, r.returnValue);

    rtype.loadNewVersion("002", retrieveRename("data.HelloWorldPrimitive", "data.HelloWorldPrimitive002"));
    r = runUnguarded(rtype.getClazz(), "getValue");
    assertTrue(r.returnValue instanceof Integer);
    assertEquals(37, r.returnValue);
  }

  @Test
  public void rewriteWithPrimitiveReturnValues_boolean() throws Exception {
    TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorldPrimitive");
    ReloadableType rtype = typeRegistry.addType("data.HelloWorldPrimitive", loadBytesForClass("data.HelloWorldPrimitive"));

    Result r = runUnguarded(rtype.getClazz(), "getValueBoolean");
    assertTrue(r.returnValue instanceof Boolean);
    assertEquals(true, r.returnValue);

    rtype.loadNewVersion("000", rtype.bytesInitial);
    r = runUnguarded(rtype.getClazz(), "getValueBoolean");
    assertTrue(r.returnValue instanceof Boolean);
    assertEquals(true, r.returnValue);

    rtype.loadNewVersion("002", retrieveRename("data.HelloWorldPrimitive", "data.HelloWorldPrimitive002"));
    r = runUnguarded(rtype.getClazz(), "getValueBoolean");
    assertTrue(r.returnValue instanceof Boolean);
    assertEquals(false, r.returnValue);
  }

  @Test
  public void rewriteWithPrimitiveReturnValues_short() throws Exception {
    TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorldPrimitive");
    ReloadableType rtype = typeRegistry.addType("data.HelloWorldPrimitive", loadBytesForClass("data.HelloWorldPrimitive"));

    Result r = runUnguarded(rtype.getClazz(), "getValueShort");
    assertTrue(r.returnValue instanceof Short);
    assertEquals((short) 3, r.returnValue);

    rtype.loadNewVersion("000", rtype.bytesInitial);
    r = runUnguarded(rtype.getClazz(), "getValueShort");
    assertTrue(r.returnValue instanceof Short);
    assertEquals((short) 3, r.returnValue);

    rtype.loadNewVersion("002", retrieveRename("data.HelloWorldPrimitive", "data.HelloWorldPrimitive002"));
    r = runUnguarded(rtype.getClazz(), "getValueShort");
    assertTrue(r.returnValue instanceof Short);
    assertEquals((short) 6, r.returnValue);
  }

  @Test
  public void rewriteWithPrimitiveReturnValues_long() throws Exception {
    TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorldPrimitive");
    ReloadableType rtype = typeRegistry.addType("data.HelloWorldPrimitive", loadBytesForClass("data.HelloWorldPrimitive"));

    Result r = runUnguarded(rtype.getClazz(), "getValueLong");
    assertTrue(r.returnValue instanceof Long);
    assertEquals(3L, r.returnValue);

    rtype.loadNewVersion("000", rtype.bytesInitial);
    r = runUnguarded(rtype.getClazz(), "getValueLong");
    assertTrue(r.returnValue instanceof Long);
    assertEquals(3L, r.returnValue);

    rtype.loadNewVersion("002", retrieveRename("data.HelloWorldPrimitive", "data.HelloWorldPrimitive002"));
    r = runUnguarded(rtype.getClazz(), "getValueLong");
    assertTrue(r.returnValue instanceof Long);
    assertEquals(6L, r.returnValue);
  }

  @Test
  public void rewriteWithPrimitiveReturnValues_double() throws Exception {
    TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorldPrimitive");
    ReloadableType rtype = typeRegistry.addType("data.HelloWorldPrimitive", loadBytesForClass("data.HelloWorldPrimitive"));

    Result r = runUnguarded(rtype.getClazz(), "getValueDouble");
    assertTrue(r.returnValue instanceof Double);
    assertEquals(3.0D, r.returnValue);

    rtype.loadNewVersion("000", rtype.bytesInitial);
    r = runUnguarded(rtype.getClazz(), "getValueDouble");
    assertTrue(r.returnValue instanceof Double);
    assertEquals(3.0D, r.returnValue);

    rtype.loadNewVersion("002", retrieveRename("data.HelloWorldPrimitive", "data.HelloWorldPrimitive002"));
    r = runUnguarded(rtype.getClazz(), "getValueDouble");
    assertTrue(r.returnValue instanceof Double);
    assertEquals(6.0D, r.returnValue);
  }

  @Test
  public void rewriteWithPrimitiveReturnValues_char() throws Exception {
    TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorldPrimitive");
    ReloadableType rtype = typeRegistry.addType("data.HelloWorldPrimitive", loadBytesForClass("data.HelloWorldPrimitive"));

    Result r = runUnguarded(rtype.getClazz(), "getValueChar");
    assertTrue(r.returnValue instanceof Character);
    assertEquals('c', r.returnValue);

    rtype.loadNewVersion("000", rtype.bytesInitial);
    r = runUnguarded(rtype.getClazz(), "getValueChar");
    assertTrue(r.returnValue instanceof Character);
    assertEquals('c', r.returnValue);

    rtype.loadNewVersion("002", retrieveRename("data.HelloWorldPrimitive", "data.HelloWorldPrimitive002"));
    r = runUnguarded(rtype.getClazz(), "getValueChar");
    assertTrue(r.returnValue instanceof Character);
    assertEquals('f', r.returnValue);
  }

  @Test
  public void rewriteWithPrimitiveReturnValues_byte() throws Exception {
    TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorldPrimitive");
    ReloadableType rtype = typeRegistry.addType("data.HelloWorldPrimitive", loadBytesForClass("data.HelloWorldPrimitive"));

    Result r = runUnguarded(rtype.getClazz(), "getValueByte");
    assertTrue(r.returnValue instanceof Byte);
    assertEquals((byte) 3, r.returnValue);

    rtype.loadNewVersion("000", rtype.bytesInitial);
    r = runUnguarded(rtype.getClazz(), "getValueByte");
    assertTrue(r.returnValue instanceof Byte);
    assertEquals((byte) 3, r.returnValue);

    rtype.loadNewVersion("002", retrieveRename("data.HelloWorldPrimitive", "data.HelloWorldPrimitive002"));
    r = runUnguarded(rtype.getClazz(), "getValueByte");
    assertTrue(r.returnValue instanceof Byte);
    assertEquals((byte) 6, r.returnValue);
  }

  @Test
  public void rewriteWithPrimitiveReturnValues_intArray() throws Exception {
    TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorldPrimitive");
    ReloadableType rtype = typeRegistry.addType("data.HelloWorldPrimitive", loadBytesForClass("data.HelloWorldPrimitive"));

    Result r = runUnguarded(rtype.getClazz(), "getArrayInt");
    assertTrue(r.returnValue instanceof int[]);
    assertEquals(3, ((int[]) r.returnValue)[0]);

    rtype.loadNewVersion("000", rtype.bytesInitial);
    r = runUnguarded(rtype.getClazz(), "getArrayInt");
    assertTrue(r.returnValue instanceof int[]);
    assertEquals(3, ((int[]) r.returnValue)[0]);

    rtype.loadNewVersion("002", retrieveRename("data.HelloWorldPrimitive", "data.HelloWorldPrimitive002"));
    r = runUnguarded(rtype.getClazz(), "getArrayInt");
    assertTrue(r.returnValue instanceof int[]);
    assertEquals(5, ((int[]) r.returnValue)[0]);
  }

  @Test
  public void rewriteWithPrimitiveReturnValues_stringArray() throws Exception {
    TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorldPrimitive");
    ReloadableType rtype = typeRegistry.addType("data.HelloWorldPrimitive", loadBytesForClass("data.HelloWorldPrimitive"));

    Result r = runUnguarded(rtype.getClazz(), "getArrayString");
    assertTrue(r.returnValue instanceof String[]);
    assertEquals("ABC", ((String[]) r.returnValue)[0]);

    rtype.loadNewVersion("000", rtype.bytesInitial);
    r = runUnguarded(rtype.getClazz(), "getArrayString");
    assertTrue(r.returnValue instanceof String[]);
    assertEquals("ABC", ((String[]) r.returnValue)[0]);

    rtype.loadNewVersion("002", retrieveRename("data.HelloWorldPrimitive", "data.HelloWorldPrimitive002"));
    r = runUnguarded(rtype.getClazz(), "getArrayString");
    assertTrue(r.returnValue instanceof String[]);
    assertEquals("DEF", ((String[]) r.returnValue)[0]);
  }

  @Test
  public void rewriteWithParams() throws Exception {
    TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorld");
    ReloadableType rtype = typeRegistry.addType("data.HelloWorld", loadBytesForClass("data.HelloWorld"));

    Result r = runUnguarded(rtype.getClazz(), "getValueWithParams", "aaa", "bb");
    assertTrue(r.returnValue instanceof String);
    assertEquals("message with inserts aaa and bb", r.returnValue);

    rtype.loadNewVersion("000", rtype.bytesInitial);
    r = runUnguarded(rtype.getClazz(), "getValueWithParams", "c", "d");
    assertTrue(r.returnValue instanceof String);
    assertEquals("message with inserts c and d", r.returnValue);

    rtype.loadNewVersion("002", retrieveRename("data.HelloWorld", "data.HelloWorld002"));
    r = runUnguarded(rtype.getClazz(), "getValueWithParams", "aaa", "bb");
    assertTrue(r.returnValue instanceof String);
    assertEquals("message with inserts bb and aaa", r.returnValue);
  }

  @Test
  public void rewriteStaticWithParams() throws Exception {
    TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorld");
    ReloadableType rtype = typeRegistry.addType("data.HelloWorld", loadBytesForClass("data.HelloWorld"));

    Result r = runUnguarded(rtype.getClazz(), "getStaticValueWithParams", "aaa", "bb");
    assertTrue(r.returnValue instanceof String);
    assertEquals("static message with inserts aaa and bb", r.returnValue);

    rtype.loadNewVersion("000", rtype.bytesInitial);
    r = runUnguarded(rtype.getClazz(), "getStaticValueWithParams", "c", "d");
    assertTrue(r.returnValue instanceof String);
    assertEquals("static message with inserts c and d", r.returnValue);

    rtype.loadNewVersion("002", retrieveRename("data.HelloWorld", "data.HelloWorld002"));
    r = runUnguarded(rtype.getClazz(), "getStaticValueWithParams", "aaa", "bb");
    assertTrue(r.returnValue instanceof String);
    assertEquals("static message with inserts bb and aaa", r.returnValue);
  }

  @Test
  public void rewriteStaticWithPrimitiveParams() throws Exception {
    TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorld");
    ReloadableType rtype = typeRegistry.addType("data.HelloWorld", loadBytesForClass("data.HelloWorld"));

    Result r = runUnguarded(rtype.getClazz(), "getStaticValueWithPrimitiveParams", "a", 2, 'c');
    assertTrue(r.returnValue instanceof String);
    assertEquals("message with inserts a and 2 and c", r.returnValue);

    rtype.loadNewVersion("000", rtype.bytesInitial);
    r = runUnguarded(rtype.getClazz(), "getStaticValueWithPrimitiveParams", "a", 2, 'c');
    assertTrue(r.returnValue instanceof String);
    assertEquals("message with inserts a and 2 and c", r.returnValue);

    rtype.loadNewVersion("002", retrieveRename("data.HelloWorld", "data.HelloWorld002"));
    r = runUnguarded(rtype.getClazz(), "getStaticValueWithPrimitiveParams", "a", 2, 'c');
    assertTrue(r.returnValue instanceof String);
    assertEquals("message with inserts c and 2 and a", r.returnValue);
  }

  @Test
  public void rewriteStaticWithPrimitiveDoubleSlottersParams() throws Exception {
    TypeRegistry typeRegistry = getTypeRegistry("data.HelloWorld");
    ReloadableType rtype = typeRegistry.addType("data.HelloWorld", loadBytesForClass("data.HelloWorld"));

    Result r = runUnguarded(rtype.getClazz(), "getStaticValueWithPrimitiveDSParams", 3L, "a", 2.0d, true);
    assertTrue(r.returnValue instanceof String);
    assertEquals("message with inserts 3 and a and 2.0 and true", r.returnValue);

    rtype.loadNewVersion("000", rtype.bytesInitial);
    r = runUnguarded(rtype.getClazz(), "getStaticValueWithPrimitiveDSParams", 3L, "a", 2.0d, true);
    assertTrue(r.returnValue instanceof String);
    assertEquals("message with inserts 3 and a and 2.0 and true", r.returnValue);

    rtype.loadNewVersion("002", retrieveRename("data.HelloWorld", "data.HelloWorld002"));
    r = runUnguarded(rtype.getClazz(), "getStaticValueWithPrimitiveDSParams", 3L, "a", 2.0d, true);
    assertTrue(r.returnValue instanceof String);
    assertEquals("message with inserts true and 2.0 and a and 3", r.returnValue);
  }

  // Checking parameter loading is using correct offsets
  @Test
  public void catchers1() throws Exception {
    TypeRegistry typeRegistry = getTypeRegistry("catchers..*");
    loadType(typeRegistry, "catchers.AbstractClass");
    ReloadableType rtype = loadType(typeRegistry, "catchers.SimpleCatcher");
    // Checking for a verify error:
    rtype.getClazz().newInstance();
    runUnguarded(rtype.getClazz(), "setTelephone", "abc");
  }

  /**
   * Testing what happens if we 'reload' a type where a method that was previously final has been made non-final and has been
   * overridden in a subtype. This works even though the 'final' isn't removed on type rewriting because the new method doesn't
   * ever really exist in the subtype!
   */
  @Test
  public void overridingFinalMethods() throws Exception {
    String t = "basic.Top";
    String b = "basic.Bottom";
    TypeRegistry typeRegistry = getTypeRegistry(t + "," + b);
    ReloadableType ttype = loadType(typeRegistry, t);
    ReloadableType btype = loadType(typeRegistry, b);

    runUnguarded(btype.getClazz(), "run");

    ttype.loadNewVersion("2", retrieveRename(t, t + "2"));
    btype.loadNewVersion("2", retrieveRename(b, b + "2", "basic.Top2:basic.Top"));
    result = runUnguarded(btype.getClazz(), "run");
    assertEquals("abc", result.stdout);
  }

  // checking catchers when final methods are being overridden
  // What we are checking:  in a small type hierarchy an intermediate type provides a final version of hashCode.  There should be no catcher
  // in the lower types.
  @Test
  public void catchers2() throws Exception {
    TypeRegistry typeRegistry = getTypeRegistry("catchers..*");
    // A verify error will occur if Finality.hashCode() is generated (as a catcher for the one in the supertype)
    loadType(typeRegistry, "catchers.Super");
    ReloadableType rtype = loadType(typeRegistry, "catchers.Finality");
    rtype.getClazz().newInstance();
    result = runUnguarded(rtype.getClazz(), "hashCode");
    assertEquals(12, result.returnValue);
  }

  @Test
  public void methodDeletion() throws Exception {
    TypeRegistry tr = getTypeRegistry("methoddeletion.TypeA");
    ReloadableType rt = loadType(tr, "methoddeletion.TypeA");
    result = runUnguarded(rt.getClazz(), "forDeletion");
    boolean b = rt.loadNewVersion("2", retrieveRename("methoddeletion.TypeA", "methoddeletion.TypeA2"));
    assertTrue(b);
    try {
      result = runUnguarded(rt.getClazz(), "forDeletion");
      fail();
    } catch (InvocationTargetException ite) {
      assertEquals("java.lang.NoSuchMethodError", ite.getCause().getClass().getName());
      assertEquals("methoddeletion.TypeA.forDeletion()V", ite.getCause().getMessage());
    }
  }

  @Test
  public void fieldAccessorsAndUsageFromExecutors() throws Exception {
    TypeRegistry tr = getTypeRegistry("accessors.ProtectedFields");
    ReloadableType rt = loadType(tr, "accessors.ProtectedFields");
    result = runUnguarded(rt.getClazz(), "run");
    assertEquals("success", result.returnValue);
    // reload itself, which means the executors will now be trying to access those fields
    rt.loadNewVersion("2", rt.bytesInitial);
    result = runUnguarded(rt.getClazz(), "run");
    assertEquals("success", result.returnValue);
  }

  // Checking that field accessors are created for private fields that can be used from the executors
  //  @Test
  //  public void fieldAccessors() throws Exception {
  //    registry = getTypeRegistry("accessors.PrivateFields");
  //    ReloadableType rtype = loadType(registry, "accessors.PrivateFields");
  //
  //    Class<?> clazz = rtype.getClazz();
  //    Object instance = clazz.newInstance();
  //
  //    // Fields to test are 'int i' 'String someString' 'long lng' and static 'boolean b' and 'String someStaticString'
  //    Method accessor = clazz.getMethod(Utils.getFieldAccessorName("i", "accessors/PrivateFields"));
  //    assertEquals(Integer.TYPE, accessor.getReturnType());
  //    assertTrue(Modifier.isPublic(accessor.getModifiers()));
  //    assertTrue(accessor.isSynthetic());
  //    assertEquals(23, ((Integer) accessor.invoke(instance)).intValue());
  //
  //    accessor = clazz.getMethod(Utils.getFieldAccessorName("lng", "accessors/PrivateFields"));
  //    assertEquals(Long.TYPE, accessor.getReturnType());
  //    assertTrue(Modifier.isPublic(accessor.getModifiers()));
  //    assertTrue(accessor.isSynthetic());
  //    assertEquals(32768L, ((Long) accessor.invoke(instance)).longValue());
  //
  //    accessor = clazz.getMethod(Utils.getFieldAccessorName("someString", "accessors/PrivateFields"));
  //    assertEquals(String.class, accessor.getReturnType());
  //    assertTrue(Modifier.isPublic(accessor.getModifiers()));
  //    assertFalse(Modifier.isStatic(accessor.getModifiers()));
  //    assertTrue(accessor.isSynthetic());
  //    assertEquals("abc", accessor.invoke(instance));
  //
  //    accessor = clazz.getMethod(Utils.getFieldAccessorName("b", "accessors/PrivateFields"));
  //    assertEquals(Boolean.TYPE, accessor.getReturnType());
  //    assertTrue(Modifier.isPublic(accessor.getModifiers()));
  //    assertTrue(Modifier.isStatic(accessor.getModifiers()));
  //    assertTrue(accessor.isSynthetic());
  //    assertEquals(false, accessor.invoke(instance));
  //
  //    accessor = clazz.getMethod(Utils.getFieldAccessorName("someStaticString", "accessors/PrivateFields"));
  //    assertEquals(String.class, accessor.getReturnType());
  //    assertTrue(Modifier.isPublic(accessor.getModifiers()));
  //    assertTrue(Modifier.isStatic(accessor.getModifiers()));
  //    assertTrue(accessor.isSynthetic());
  //    assertEquals("def", accessor.invoke(instance));
  //  }

  /**
   * Basically testing
   *
   * <pre>
   * class A {
   *   public void foo() {
   *   }
   * }
   *
   * class B extends A {
   *   public void foo() {
   *   }
   * }
   *
   * class C {
   *   public void m() {
   *     A a = new B();
   *     a.foo();
   *   }
   * }
   * </pre>
   *
   * then reload A and remove foo. What happens?
   *
   * @throws Exception
   */
  @Test
  public void testMethodDeletion() throws Exception {
    registry = getTypeRegistry("inheritance.A,inheritance.B");
    ReloadableType a = loadType(registry, "inheritance.A");
    //    ReloadableType b =
    loadType(registry, "inheritance.B");
    byte[] callerBytes = loadBytesForClass("inheritance.C");
    callerBytes = MethodInvokerRewriter.rewrite(registry, callerBytes);
    Class<?> callerClazz = loadit("inheritance.C", callerBytes);
    Method m = callerClazz.getMethod("run");
    assertEquals(66, ((Integer) m.invoke(null)).intValue());

    // reload A with the method foo removed
    a.loadNewVersion("002", retrieveRename("inheritance.A", "inheritance.A002"));

    // Now expect: java.lang.NoSuchMethodError: inheritance.A.foo()I
    try {
      m.invoke(null);
      fail();
    } catch (InvocationTargetException ite) {
      assertEquals("java.lang.NoSuchMethodError", ite.getCause().getClass().getName());
      assertEquals("A.foo()I", ite.getCause().getMessage());
    }
  }

  /**
   * This test is the first to allow catchers in abstract classes for non-implemented interface methods.
   */
  @Test
  public void testAbstractClass() throws Exception {
    String intface = "abs.Intface";
    String absimpl = "abs.AbsImpl";
    String impl = "abs.Impl";
    registry = getTypeRegistry(intface + "," + absimpl + "," + impl);
    //    ReloadableType rIntface =
    loadType(registry, intface);
    ReloadableType rAbsimpl = loadType(registry, absimpl);
    ReloadableType rImpl = loadType(registry, impl);
    result = runUnguarded(rImpl.getClazz(), "run");
    assertEquals("1", result.stdout);

    // Now load a new version of the abstract type with method() in it, and
    // a new version of the concrete type without method() in it
    rAbsimpl.loadNewVersion("2", retrieveRename(absimpl, absimpl + "2"));
    rImpl.loadNewVersion("2", retrieveRename(impl, impl + "2", absimpl + "2:" + absimpl));
//    ClassPrinter.print(rAbsimpl.bytesLoaded);
    result = runUnguarded(rImpl.getClazz(), "run");
    assertEquals("2", result.stdout);
  }

  //TODO similar to above case but where INVOKEINTERFACE is being used to call the method

  /**
   * Checking that rtype field is created and initialized for a reloadable interface type.
   */
  @Test
  public void testInterfaceRTypeField() {
    String interfaceName = "reflection.targets.InterfaceTarget";
    registry = getTypeRegistry(interfaceName);
    ReloadableType rtype = registry.addType(interfaceName, loadBytesForClass(interfaceName));
    assertEquals(rtype, ReflectiveInterceptor.getRType(rtype.getClazz()));
  }

  @Test
  public void staticMethodAddedlater() throws Exception {
    String t = "invokestatic.A";
    registry = getTypeRegistry(t);
    ReloadableType type = loadType(registry, t);
    result = runUnguarded(type.getClazz(), "run");
    assertEquals("hello", result.returnValue);
    type.loadNewVersion("2", retrieveRename(t, t + "2"));
//    ClassPrinter.print(type.getLatestExecutorBytes());
    result = runUnguarded(type.getClazz(), "run");
    assertEquals("world", result.returnValue);
  }

  @Test
  public void privateStaticMethod() throws Exception {
    String t = "invokestatic.B";
    registry = getTypeRegistry(t);
    ReloadableType type = loadType(registry, t);
    result = runUnguarded(type.getClazz(), "run");
    assertEquals("hello", result.returnValue);
    type.loadNewVersion("2", retrieveRename(t, t + "2"));
    result = runUnguarded(type.getClazz(), "run");
    assertEquals("goodbye", result.returnValue);
  }

  /**
   * Testing the type delta which records what has changed on a reload. Here we are reloading a type twice. In the first reload
   * nothing has changed. In the second reload the code in the constructor has changed, but the invokespecial call to super has
   * not changed.
   */
  @Test
  public void constructorChangingButNotSuper() throws Exception {
    String t = "ctors.V";
    registry = getTypeRegistry(t);
    ReloadableType type = loadType(registry, t);
    result = runConstructor(type.getClazz(), "");
    assertEquals("Hello", result.stdout);
    type.loadNewVersion("2", retrieveRename(t, t + "2"));
    result = runConstructor(type.getClazz(), "");
    assertEquals("Hello", result.stdout);

    // should be no changes
    TypeDelta td = type.getLiveVersion().getTypeDelta();
    assertNotNull(td);
    assertFalse(td.hasAnythingChanged());
    assertNull(td.getChangedMethods());

    // this version changes the constructor
    type.loadNewVersion("3", retrieveRename(t, t + "3"));
    result = runConstructor(type.getClazz(), "");
    assertEquals("Goodbye", result.stdout);
    td = type.getLiveVersion().getTypeDelta();
    assertNotNull(td);
    assertTrue(td.hasAnythingChanged());
    Map<String, MethodDelta> changedMethods = td.getChangedMethods();
    assertNotNull(changedMethods);
    assertEquals(1, changedMethods.size());
    assertEquals("MethodDelta[method:<init>()V]", changedMethods.get("<init>()V").toString());
    MethodDelta md = changedMethods.get("<init>()V");
    assertTrue(md.hasAnyChanges());
    assertFalse(md.hasInvokeSpecialChanged());
    assertTrue(md.hasCodeChanged());
  }

  /**
   * Testing that even though a type is reloaded, if the constructor body hasn't changed then we still run the original version of
   * it.
   */
  @Test
  public void originalConstructorRunOnReload() throws Exception {
    String t = "ctors.XX";
    registry = getTypeRegistry(t);
    ReloadableType type = loadType(registry, t);

    result = runConstructor(type.getClazz(), "");
    // should be running inside the constructor directly
    assertEquals("ctors.XX.<init>(XX.java:6)", result.stdout);

    // Reload, no code changes
    type.loadNewVersion("2", type.bytesInitial);
    result = runConstructor(type.getClazz(), "");
    // Now, if running reloaded code, stack frame will be: ctors.XX__E2.___init___(XX.java:6)
    // but we want it to be the same as before
    assertEquals("ctors.XX.<init>(XX.java:6)", result.stdout);

    // Reload, change the constructor this time
    type.loadNewVersion("3", retrieveRename(t, t + "2"));
    result = runConstructor(type.getClazz(), "");
    assertEquals("ctors.XX$$E3.___init___(XX2.java:7)", result.stdout);
  }

  //  TODO need some tests for static methods in a hierarchy, do we dispatch correctly, can you have a private static in between a pair of non-statics in a super and subtype?

}
TOP

Related Classes of org.springsource.loaded.test.TypeRewriterTests

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.