ikvmnet / ikvm

A Java Virtual Machine and Bytecode-to-IL Converter for .NET
Other
1.22k stars 111 forks source link

`sun.misc.Unsafe.compareAndSwapObject`: `java.lang.InternalError: cli.System.ArgumentException: FieldInfo must be a runtime FieldInfo object.` #331

Closed Kojoley closed 1 year ago

Kojoley commented 1 year ago

Reproducer based on guava/src/com/google/common/util/concurrent/AbstractFuture.java

public final class Main
{
    volatile Object ptr = null;

    public static sun.misc.Unsafe getUnsafe() throws Exception {
      try {
        return sun.misc.Unsafe.getUnsafe();
      }
      catch (SecurityException tryReflectionInstead) {
        Class<sun.misc.Unsafe> k = sun.misc.Unsafe.class;
        for (java.lang.reflect.Field f : k.getDeclaredFields()) {
          f.setAccessible(true);
          Object x = f.get(null);
          if (k.isInstance(x)) {
            return k.cast(x);
          }
        }
        throw new NoSuchFieldError("the Unsafe");
      }
    }

    public static void main(String[] args) throws Exception
    {
        sun.misc.Unsafe unsafe = getUnsafe();
        unsafe.compareAndSwapObject(new Main(), unsafe.objectFieldOffset(Main.class.getDeclaredField("ptr")), null, null);
    }
}
Exception in thread "main" java.lang.InternalError: cli.System.ArgumentException: FieldInfo must be a runtime FieldInfo object.
Parameter name: field
    at IKVM.Java.Externs.sun.misc.Unsafe.CompareAndSwapField(Unknown Source)
    at IKVM.Java.Externs.sun.misc.Unsafe.compareAndSwapObject(Unknown Source)
    at sun.misc.Unsafe.compareAndSwapObject(Native Method)
    at Main.main(Main.java:26)
    at java.lang.reflect.Method.invoke(Method.java:486)
    at IKVM.Java.Externs.ikvm.runtime.Launcher.run(Unknown Source)
Caused by: cli.System.ArgumentException: FieldInfo must be a runtime FieldInfo object.
Parameter name: field
    at cli.System.Reflection.Emit.DynamicILGenerator.Emit(Unknown Source)
    at IKVM.Internal.CodeEmitter.RealEmitOpCode(Unknown Source)
    at IKVM.Internal.CodeEmitter+OpCodeWrapper.RealEmit(Unknown Source)
    at IKVM.Internal.CodeEmitter.DoEmit(Unknown Source)
    at IKVM.Internal.FieldWrapper.CreateUnsafeCompareAndSwapDelegate(Unknown Source)
    at IKVM.Internal.FieldWrapper.GetUnsafeCompareAndSwapDelegate(Unknown Source)
    at IKVM.Internal.FieldWrapper.UnsafeCompareAndSwap(Unknown Source)
    at IKVM.Java.Externs.sun.misc.Unsafe.CompareAndSwapField(Unknown Source)
    at IKVM.Internal.ExceptionHelper.toString(Unknown Source)
    at java.lang.Throwable.toString(map.xml:1358)
    at java.lang.Object.toString(map.xml)
    at ikvm.extensions.ExtensionMethods.toString(ExtensionMethods.java:244)
    at java.lang.Throwable.<init>(map.xml)
    at java.lang.Error.<init>(Error.java:106)
    at java.lang.VirtualMachineError.<init>(VirtualMachineError.java:88)
    at java.lang.InternalError.<init>(InternalError.java:88)
    ... 6 more
wasabii commented 1 year ago

So the deal here I believe was that this particular unsafe method wasn't ensuring the field wrapper had been resolved before generating the delegate. So, it might have been working with a FieldBuilder instead of a real runtime field. Other things in the runtime can cause the field to be resolved, so, this bug would only appear if nothing up to the point had caused it to be resolved.

wasabii commented 1 year ago

Also, more info: there are only a handful actual calls to Unsafe in the BCL anyways. Most of them are intrinsified. The actual Unsafe class only really exists for people like the reporter who are circumventing the visibility from the outside.