/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * 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.
 */

import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class Main {
  static final String DEX_FILE = System.getenv("DEX_LOCATION") + "/616-cha-unloading-ex.jar";
  static final String LIBRARY_SEARCH_PATH = System.getProperty("java.library.path");
  static Constructor<? extends ClassLoader> sConstructor;

  private static class CHAUnloaderRetType {
    private CHAUnloaderRetType(WeakReference<ClassLoader> cl,
                              AbstractCHATester obj,
                              long methodPtr) {
      this.cl = cl;
      this.obj = obj;
      this.methodPtr = methodPtr;
    }
    public WeakReference<ClassLoader> cl;
    public AbstractCHATester obj;
    public long methodPtr;
  }

  public static void main(String[] args) throws Exception {
    System.loadLibrary(args[0]);

    Class<ClassLoader> pathClassLoader = (Class<ClassLoader>) Class.forName("dalvik.system.PathClassLoader");
    sConstructor =
        pathClassLoader.getDeclaredConstructor(String.class, String.class, ClassLoader.class);

    testUnload();
  }

  private static void testUnload() throws Exception {
    // Load a concrete class, then unload it. Get a deleted ArtMethod to test if it'll be inlined.
    CHAUnloaderRetType result = doUnloadLoader();
    WeakReference<ClassLoader> loader = result.cl;
    long methodPtr = result.methodPtr;
    // Check that the classloader is indeed unloaded.
    if (loader.get() != null) {
      throw new Error("Expected class loader to be unloaded");
    }

    // Reuse the linear alloc used by the unloaded class loader.
    reuseArenaOfMethod(methodPtr);

    // Try to JIT-compile under dangerous conditions.
    ensureJitCompiled(Main.class, "targetMethodForJit");
    System.out.println("Done");
  }

  private static void doUnloading() {
    // Do multiple GCs to prevent rare flakiness if some other thread is keeping the
    // classloader live.
    for (int i = 0; i < 5; ++i) {
       Runtime.getRuntime().gc();
    }
  }

  private static CHAUnloaderRetType setupLoader()
      throws Exception {
    ClassLoader loader = sConstructor.newInstance(
        DEX_FILE, LIBRARY_SEARCH_PATH, ClassLoader.getSystemClassLoader());
    Class<?> concreteCHATester = loader.loadClass("ConcreteCHATester");

    // Preemptively compile methods to prevent delayed JIT tasks from blocking the unloading.
    ensureJitCompiled(concreteCHATester, "<init>");
    ensureJitCompiled(concreteCHATester, "lonelyMethod");

    Object obj = concreteCHATester.newInstance();
    Method lonelyMethod = concreteCHATester.getDeclaredMethod("lonelyMethod");

    // Get a pointer to a region that shall be not used after the unloading.
    long artMethod = getArtMethod(lonelyMethod);

    AbstractCHATester ret = null;
    return new CHAUnloaderRetType(new WeakReference(loader), ret, artMethod);
  }

  private static CHAUnloaderRetType targetMethodForJit(int mode)
      throws Exception {
    CHAUnloaderRetType ret = new CHAUnloaderRetType(null, null, 0);
    if (mode == 0) {
      ret = setupLoader();
    } else if (mode == 1) {
      // This branch is not supposed to be executed. It shall trigger "lonelyMethod" inlining
      // during jit compilation of "targetMethodForJit".
      ret = setupLoader();
      AbstractCHATester obj = ret.obj;
      obj.lonelyMethod();
    }
    return ret;
  }

  private static CHAUnloaderRetType doUnloadLoader()
      throws Exception {
    CHAUnloaderRetType result = targetMethodForJit(0);
    doUnloading();
    return result;
  }

  private static native void ensureJitCompiled(Class<?> itf, String method_name);
  private static native long getArtMethod(Object javaMethod);
  private static native void reuseArenaOfMethod(long artMethod);
}