Preliminary implementation of the JNI invocation interface.

Change-Id: Ib144cb887864cd232a8cb8167b20d1540829a6a5
diff --git a/src/class_linker.cc b/src/class_linker.cc
index 6ee3af3..78b6d33 100644
--- a/src/class_linker.cc
+++ b/src/class_linker.cc
@@ -20,14 +20,14 @@
 
 namespace art {
 
-ClassLinker* ClassLinker::Create(std::vector<DexFile*> boot_class_path) {
+ClassLinker* ClassLinker::Create(const std::vector<DexFile*>& boot_class_path) {
   scoped_ptr<ClassLinker> class_linker(new ClassLinker);
   class_linker->Init(boot_class_path);
   // TODO: check for failure during initialization
   return class_linker.release();
 }
 
-void ClassLinker::Init(std::vector<DexFile*> boot_class_path) {
+void ClassLinker::Init(const std::vector<DexFile*>& boot_class_path) {
 
   // Allocate and partially initialize the Class, Object, Field, Method classes.
   // Initialization will be completed when the definitions are loaded.
@@ -417,11 +417,13 @@
 }
 
 void ClassLinker::AppendToBootClassPath(DexFile* dex_file) {
+  CHECK(dex_file != NULL);
   boot_class_path_.push_back(dex_file);
   RegisterDexFile(dex_file);
 }
 
 void ClassLinker::RegisterDexFile(DexFile* dex_file) {
+  CHECK(dex_file != NULL);
   dex_files_.push_back(dex_file);
   DexCache* dex_cache = AllocDexCache();
   CHECK(dex_cache != NULL);
diff --git a/src/class_linker.h b/src/class_linker.h
index 622eaea..a2f2770 100644
--- a/src/class_linker.h
+++ b/src/class_linker.h
@@ -18,7 +18,7 @@
 class ClassLinker {
  public:
   // Initializes the class linker.
-  static ClassLinker* Create(std::vector<DexFile*> boot_class_path);
+  static ClassLinker* Create(const std::vector<DexFile*>& boot_class_path);
 
   ~ClassLinker() {}
 
@@ -54,7 +54,7 @@
  private:
   ClassLinker() {}
 
-  void Init(std::vector<DexFile*> boot_class_path_);
+  void Init(const std::vector<DexFile*>& boot_class_path_);
 
   Class* CreatePrimitiveClass(const StringPiece& descriptor);
 
diff --git a/src/jni_internal.cc b/src/jni_internal.cc
index 74f8961..6c6f493 100644
--- a/src/jni_internal.cc
+++ b/src/jni_internal.cc
@@ -1,7 +1,13 @@
 // Copyright 2011 Google Inc. All Rights Reserved.
 
 #include "jni_internal.h"
+
+#include <vector>
+#include <utility>
+
 #include "logging.h"
+#include "runtime.h"
+#include "thread.h"
 
 namespace art {
 
@@ -18,4 +24,120 @@
   monitor_exit_ = &JniMonitorExit;
 }
 
+extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, void** p_env, void* vm_args) {
+  const JavaVMInitArgs* args = static_cast<JavaVMInitArgs*>(vm_args);
+  if (args->version < JNI_VERSION_1_2) {
+    return JNI_EVERSION;
+  }
+  Runtime::Options options;
+  for (int i = 0; i < args->nOptions; ++i) {
+    JavaVMOption* option = &args->options[i];
+    options.push_back(std::make_pair(option->optionString, option->extraInfo));
+  }
+  bool ignore_unrecognized = args->ignoreUnrecognized;
+  scoped_ptr<Runtime> runtime(Runtime::Create(options, ignore_unrecognized));
+  if (runtime == NULL) {
+    return JNI_ERR;
+  } else {
+    *p_env = reinterpret_cast<JNIEnv*>(Thread::Current()->GetJniEnv());
+    *p_vm = reinterpret_cast<JavaVM*>(runtime.release());
+    return JNI_OK;
+  }
+}
+
+extern "C" jint JNI_GetCreatedJavaVMs(JavaVM** vmBuf, jsize bufLen,
+                                      jsize* nVMs) {
+  Runtime* runtime = Runtime::Current();
+  if (runtime == NULL) {
+    *nVMs = 0;
+  } else {
+    *nVMs = 1;
+    vmBuf[0] = reinterpret_cast<JavaVM*>(runtime);
+  }
+  return JNI_OK;
+}
+
+// Historically unsupported.
+extern "C" jint JNI_GetDefaultJavaVMInitArgs(void* vm_args) {
+  return JNI_ERR;
+}
+
+jint JniInvoke::DestroyJavaVM(JavaVM* vm) {
+  if (vm == NULL) {
+    return JNI_ERR;
+  } else {
+    Runtime* runtime = reinterpret_cast<Runtime*>(vm);
+    delete runtime;
+    return JNI_OK;
+  }
+}
+
+jint JniInvoke::AttachCurrentThread(JavaVM* vm, JNIEnv** p_env,
+                                    void* thr_args) {
+  if (vm == NULL || p_env == NULL) {
+    return JNI_ERR;
+  }
+  Runtime* runtime = reinterpret_cast<Runtime*>(vm);
+  JniEnvironment** jni_env = reinterpret_cast<JniEnvironment**>(p_env);
+  const char* name = NULL;
+  if (thr_args != NULL) {
+    // TODO: check version
+    name = static_cast<JavaVMAttachArgs*>(thr_args)->name;
+    // TODO: thread group
+  }
+  bool success = runtime->AttachCurrentThread(name, jni_env);
+  if (!success) {
+    return JNI_ERR;
+  } else {
+    return JNI_OK;
+  }
+}
+
+jint JniInvoke::DetachCurrentThread(JavaVM* vm) {
+  if (vm == NULL) {
+    return JNI_ERR;
+  } else {
+    Runtime* runtime = reinterpret_cast<Runtime*>(vm);
+    runtime->DetachCurrentThread();
+    return JNI_OK;
+  }
+}
+
+jint JniInvoke::GetEnv(JavaVM *vm, void **env, jint version) {
+  if (version < JNI_VERSION_1_1 || version > JNI_VERSION_1_6) {
+    return JNI_EVERSION;
+  }
+  if (vm == NULL || env == NULL) {
+    return JNI_ERR;
+  }
+  Thread* thread = Thread::Current();
+  if (thread == NULL) {
+    *env = NULL;
+    return JNI_EDETACHED;
+  }
+  *env = thread->GetJniEnv();
+  return JNI_OK;
+}
+
+jint JniInvoke::AttachCurrentThreadAsDaemon(JavaVM* vm, JNIEnv** p_env,
+                                            void* thr_args) {
+  if (vm == NULL || p_env == NULL) {
+    return JNI_ERR;
+  }
+  Runtime* runtime = reinterpret_cast<Runtime*>(vm);
+  JniEnvironment** jni_env = reinterpret_cast<JniEnvironment**>(p_env);
+  const char* name = NULL;
+  if (thr_args != NULL) {
+    // TODO: check version
+    name = static_cast<JavaVMAttachArgs*>(thr_args)->name;
+    // TODO: thread group
+  }
+  bool success = runtime->AttachCurrentThreadAsDaemon(name, jni_env);
+  if (!success) {
+    return JNI_ERR;
+  } else {
+    return JNI_OK;
+  }
+}
+
 }  // namespace art
diff --git a/src/jni_internal.h b/src/jni_internal.h
index e1474ab..16bf96e 100644
--- a/src/jni_internal.h
+++ b/src/jni_internal.h
@@ -27,6 +27,29 @@
  private:
   void (*monitor_enter_)(JniEnvironment*, jobject);
   void (*monitor_exit_)(JniEnvironment*, jobject);
+
+  DISALLOW_COPY_AND_ASSIGN(JniEnvironment);
+};
+
+class JniInvoke {
+ public:
+  // Index 3
+  int DestroyJavaVM(JavaVM* vm);
+
+  // Index 4
+  int AttachCurrentThread(JavaVM* vm, JNIEnv** penv, void* thr_args);
+
+  // Index 5
+  int DetachCurrentThread(JavaVM* vm);
+
+  // Index 6
+  int GetEnv(JavaVM* vm, void** penv, int version);
+
+  // Index 7
+  int AttachCurrentThreadAsDaemon(JavaVM* vm, JNIEnv** penv, void* thr_args);
+
+ private:
+  DISALLOW_IMPLICIT_CONSTRUCTORS(JniInvoke);
 };
 
 }  // namespace art
diff --git a/src/main.cc b/src/main.cc
index 6f277d1..4afcc29 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -1,8 +1,261 @@
 // Copyright 2011 Google Inc. All Rights Reserved.
 
-#include <iostream>
+#include <cstring>
+#include <cstdio>
+#include <signal.h>
+#include <string>
 
+#include "jni.h"
+#include "logging.h"
+#include "scoped_ptr.h"
+
+// TODO: move this into a publicly accessible location
+template<typename T>
+class scoped_local_ref {
+ public:
+  scoped_local_ref(JNIEnv* env, T ref) : env_(env), ref_(ref) {}
+  ~scoped_local_ref() { reset(); }
+
+  T get() const { return ref_; }
+
+  void reset() {
+    if (ref_ != NULL) {
+      env_->DeleteLocalRef(ref_);
+      ref_ = NULL;
+    }
+  }
+
+  T release() {
+    T ref = ref_;
+    ref_ = NULL;
+    return ref;
+  }
+
+ private:
+    JNIEnv* env_;
+    T ref_;
+    DISALLOW_COPY_AND_ASSIGN(scoped_local_ref);
+  };
+
+// TODO: move this into the runtime.
+static void BlockSigpipe() {
+  sigset_t sigset;
+  if (sigemptyset(&sigset) == -1) {
+    PLOG(ERROR) << "sigemptyset failed";
+    return;
+  }
+  if (sigaddset(&sigset, SIGPIPE) == -1) {
+    PLOG(ERROR) << "sigaddset failed";
+    return;
+  }
+  if (sigprocmask(SIG_BLOCK, &sigset, NULL) == -1) {
+    PLOG(ERROR) << "sigprocmask failed";
+  }
+}
+
+// TODO: this code should be shared with other parts of the system
+// that create string arrays.
+//Create a String[] and populate it with the contents of argv.
+static jobjectArray CreateStringArray(JNIEnv* env, char** argv, int argc) {
+  // Find the String class.
+  scoped_local_ref<jclass> klass(env, env->FindClass("java/lang/String"));
+  if (env->ExceptionCheck()) {
+    fprintf(stderr, "Got exception while finding class String\n");
+    return NULL;
+  }
+  DCHECK(klass.get() != NULL);
+
+  // Create an array of String elements.
+  scoped_local_ref<jobjectArray> args(env, env->NewObjectArray(argc,
+                                                               klass.get(),
+                                                               NULL));
+  if (env->ExceptionCheck()) {
+    fprintf(stderr, "Got exception while creating String array\n");
+    return NULL;
+  }
+  DCHECK(args.get() != NULL);
+
+  // Allocate a string object for each argv element.
+  for (int i = 0; i < argc; ++i) {
+    scoped_local_ref<jstring> elt(env, env->NewStringUTF(argv[i]));
+    if (env->ExceptionCheck()) {
+      fprintf(stderr, "Got exception while allocating Strings\n");
+      return NULL;
+    }
+    DCHECK(elt.get() != NULL);
+    env->SetObjectArrayElement(args.get(), i, elt.get());
+  }
+
+  // Return a local reference to the newly created array.
+  return args.release();
+}
+
+// Determine whether or not the specified method is public.
+//
+// Returns JNI_TRUE on success, JNI_FALSE on failure.
+static bool IsMethodPublic(JNIEnv* env, jclass clazz, jmethodID method_id) {
+  scoped_local_ref<jobject> reflected(env, env->ToReflectedMethod(clazz,
+                                                                  method_id,
+                                                                  JNI_FALSE));
+  if (reflected.get() == NULL) {
+    fprintf(stderr, "Unable to get reflected method\n");
+    return false;
+  }
+  // We now have a Method instance.  We need to call its
+  // getModifiers() method.
+  scoped_local_ref<jclass> method(env,
+                                  env->FindClass("java/lang/reflect/Method"));
+  if (method.get() == NULL) {
+    fprintf(stderr, "Unable to find class Method\n");
+    return false;
+  }
+  jmethodID get_modifiers = env->GetMethodID(method.get(),
+                                             "getModifiers",
+                                             "()I");
+  if (get_modifiers == NULL) {
+    fprintf(stderr, "Unable to find reflect.Method.getModifiers\n");
+    return false;
+  }
+  static const int PUBLIC = 0x0001;   // java.lang.reflect.Modifiers.PUBLIC
+  int modifiers = env->CallIntMethod(method.get(), get_modifiers);
+  if ((modifiers & PUBLIC) == 0) {
+    return false;
+  }
+  return true;
+}
+
+static bool InvokeMain(JavaVM* vm, JNIEnv* env, int argc, char** argv) {
+  // We want to call main() with a String array with our arguments in
+  // it.  Create an array and populate it.  Note argv[0] is not
+  // included.
+  scoped_local_ref<jobjectArray> args(env, CreateStringArray(env,
+                                                             argv + 1,
+                                                             argc - 1));
+  if (args.get() == NULL) {
+    return false;
+  }
+
+  // Find [class].main(String[]).
+
+  // Convert "com.android.Blah" to "com/android/Blah".
+  std::string class_name = argv[0];
+  std::replace(class_name.begin(), class_name.end(), '.', '/');
+
+  scoped_local_ref<jclass> klass(env, env->FindClass(class_name.c_str()));
+  if (klass.get() == NULL) {
+    fprintf(stderr, "Unable to locate class '%s'\n", class_name.c_str());
+    return false;
+  }
+
+  jmethodID method = env->GetStaticMethodID(klass.get(),
+                                            "main",
+                                            "([Ljava/lang/String;)V");
+  if (method == NULL) {
+    fprintf(stderr, "Unable to find static main(String[]) in '%s'\n",
+            class_name.c_str());
+    return false;
+  }
+
+  // Make sure the method is public.  JNI doesn't prevent us from
+  // calling a private method, so we have to check it explicitly.
+  if (!IsMethodPublic(env, klass.get(), method)) {
+    fprintf(stderr, "Sorry, main() is not public\n");
+    return false;
+  }
+
+  // Invoke main().
+
+  env->CallStaticVoidMethod(klass.get(), method, args.get());
+  if (env->ExceptionCheck()) {
+    return false;
+  } else {
+    return true;
+  }
+}
+
+// Parse arguments.  Most of it just gets passed through to the VM.
+// The JNI spec defines a handful of standard arguments.
 int main(int argc, char** argv) {
-  std::cout << "hello, world" << std::endl;
-  return 0;
+  setvbuf(stdout, NULL, _IONBF, 0);
+
+  // Skip over argv[0].
+  argv++;
+  argc--;
+
+  // If we're adding any additional stuff, e.g. function hook specifiers,
+  // add them to the count here.
+  //
+  // We're over-allocating, because this includes the options to the VM
+  // plus the options to the program.
+  int option_count = argc;
+  scoped_array<JavaVMOption> options(new JavaVMOption[option_count]());
+
+  // Copy options over.  Everything up to the name of the class starts
+  // with a '-' (the function hook stuff is strictly internal).
+  //
+  // [Do we need to catch & handle "-jar" here?]
+  bool need_extra = false;
+  int curr_opt, arg_idx;
+  for (curr_opt = arg_idx = 0; arg_idx < argc; arg_idx++) {
+    if (argv[arg_idx][0] != '-' && !need_extra) {
+      break;
+    }
+    options[curr_opt++].optionString = argv[arg_idx];
+
+    // Some options require an additional argument.
+    need_extra = false;
+    if (strcmp(argv[arg_idx], "-classpath") == 0 ||
+        strcmp(argv[arg_idx], "-cp") == 0) {
+      // others?
+      need_extra = true;
+    }
+  }
+
+  if (need_extra) {
+    fprintf(stderr, "VM requires value after last option flag\n");
+    return EXIT_FAILURE;
+  }
+
+  // Make sure they provided a class name.  We do this after VM init
+  // so that things like "-Xrunjdwp:help" have the opportunity to emit
+  // a usage statement.
+  if (arg_idx == argc) {
+    fprintf(stderr, "Class name required\n");
+    return EXIT_FAILURE;
+  }
+
+  // insert additional internal options here
+
+  DCHECK_LE(curr_opt, option_count);
+
+  JavaVMInitArgs init_args;
+  init_args.version = JNI_VERSION_1_4;
+  init_args.options = options.get();
+  init_args.nOptions = curr_opt;
+  init_args.ignoreUnrecognized = JNI_FALSE;
+
+  BlockSigpipe();
+
+  // Start VM.  The current thread becomes the main thread of the VM.
+  JavaVM* vm = NULL;
+  JNIEnv* env = NULL;
+  if (JNI_CreateJavaVM(&vm, &env, &init_args) != JNI_OK) {
+    fprintf(stderr, "VM init failed (check log file)\n");
+    return EXIT_FAILURE;
+  }
+
+  bool success = InvokeMain(vm, env, argc - arg_idx, &argv[arg_idx]);
+
+  if (vm != NULL && vm->DetachCurrentThread() != JNI_OK) {
+    fprintf(stderr, "Warning: unable to detach main thread\n");
+    success = false;
+  }
+
+  if (vm != NULL && vm->DestroyJavaVM() != 0) {
+    fprintf(stderr, "Warning: VM did not shut down cleanly\n");
+    success = false;
+  }
+
+  int retval = success ? EXIT_SUCCESS : EXIT_FAILURE;
+  return retval;
 }
diff --git a/src/runtime.cc b/src/runtime.cc
index eadc50e..eee84cd 100644
--- a/src/runtime.cc
+++ b/src/runtime.cc
@@ -4,6 +4,7 @@
 
 #include <cstdio>
 #include <cstdlib>
+#include <vector>
 
 #include "class_linker.h"
 #include "heap.h"
@@ -11,6 +12,8 @@
 
 namespace art {
 
+Runtime* Runtime::instance_ = NULL;
+
 Runtime::~Runtime() {
   // TODO: use a smart pointer instead.
   delete class_linker_;
@@ -44,17 +47,27 @@
   // notreached
 }
 
-Runtime* Runtime::Create(std::vector<DexFile*> boot_class_path) {
+Runtime* Runtime::Create(const Options& options, bool ignore_unrecognized) {
+  // TODO: parse arguments
+  std::vector<DexFile*> boot_class_path;  // empty
+  return Runtime::Create(boot_class_path);
+}
+
+Runtime* Runtime::Create(const std::vector<DexFile*>& boot_class_path) {
+  // TODO: acquire a static mutex on Runtime to avoid racing.
+  if (Runtime::instance_ != NULL) {
+    return NULL;
+  }
   scoped_ptr<Runtime> runtime(new Runtime());
   bool success = runtime->Init(boot_class_path);
   if (!success) {
     return NULL;
   } else {
-    return runtime.release();
+    return Runtime::instance_ = runtime.release();
   }
 }
 
-bool Runtime::Init(std::vector<DexFile*> boot_class_path) {
+bool Runtime::Init(const std::vector<DexFile*>& boot_class_path) {
   thread_list_ = ThreadList::Create();
   Heap::Init(Heap::kStartupSize, Heap::kMaximumSize);
   Thread::Init();
@@ -64,7 +77,13 @@
   return true;
 }
 
-bool Runtime::AttachCurrentThread() {
+bool Runtime::AttachCurrentThread(const char* name, JniEnvironment** penv) {
+  return Thread::Attach() != NULL;
+}
+
+bool Runtime::AttachCurrentThreadAsDaemon(const char* name,
+                                          JniEnvironment** penv) {
+  // TODO: do something different for daemon threads.
   return Thread::Attach() != NULL;
 }
 
diff --git a/src/runtime.h b/src/runtime.h
index b57f1cf..9ff532c 100644
--- a/src/runtime.h
+++ b/src/runtime.h
@@ -14,12 +14,20 @@
 
 class ClassLinker;
 class Heap;
+class JniEnvironment;
 class ThreadList;
 
 class Runtime {
  public:
+  typedef std::vector<std::pair<const char*, void*> > Options;
+
   // Creates and initializes a new runtime.
-  static Runtime* Create(std::vector<DexFile*> boot_class_path);
+  static Runtime* Create(const Options& options, bool ignore_unrecognized);
+  static Runtime* Create(const std::vector<DexFile*>& boot_class_path);
+
+  static Runtime* Current() {
+    return instance_;
+  }
 
   // Compiles a dex file.
   static void Compile(const StringPiece& filename);
@@ -32,7 +40,8 @@
   static void Abort(const char* file, int line);
 
   // Attaches the current native thread to the runtime.
-  bool AttachCurrentThread();
+  bool AttachCurrentThread(const char* name, JniEnvironment** jni_env);
+  bool AttachCurrentThreadAsDaemon(const char* name, JniEnvironment** jni_env);
 
   // Detaches the current native thread from the runtime.
   bool DetachCurrentThread();
@@ -43,18 +52,27 @@
     return class_linker_;
   }
 
+  void SetVfprintfHook(void* hook);
+
+  void SetExitHook(void* hook);
+
+  void SetAbortHook(void* hook);
+
  private:
   static void PlatformAbort(const char*, int);
 
   Runtime() : class_linker_(NULL), thread_list_(NULL) {}
 
   // Initializes a new uninitialized runtime.
-  bool Init(std::vector<DexFile*> boot_class_path);
+  bool Init(const std::vector<DexFile*>& boot_class_path);
 
   ClassLinker* class_linker_;
 
   ThreadList* thread_list_;
 
+  // A pointer to the active runtime or NULL.
+  static Runtime* instance_;
+
   DISALLOW_COPY_AND_ASSIGN(Runtime);
 };
 
diff --git a/src/runtime_linux.cc b/src/runtime_linux.cc
index e150d8e..4aa90f0 100644
--- a/src/runtime_linux.cc
+++ b/src/runtime_linux.cc
@@ -22,7 +22,7 @@
   // Turn them into something human-readable with symbols.
   // TODO: in practice, we may find that we should use backtrace_symbols_fd
   // to avoid allocation, rather than use our own custom formatting.
-  art::scoped_ptr_malloc<char*> symbols(backtrace_symbols(frames, frame_count));
+  scoped_ptr_malloc<char*> symbols(backtrace_symbols(frames, frame_count));
   if (symbols == NULL) {
     PLOG(ERROR) << "backtrace_symbols failed";
     return;
diff --git a/src/scoped_ptr.h b/src/scoped_ptr.h
index a3b30d5..c76a64e 100644
--- a/src/scoped_ptr.h
+++ b/src/scoped_ptr.h
@@ -26,8 +26,6 @@
 #include <algorithm>
 #include <cstddef>
 
-namespace art {
-
 template <class C> class scoped_ptr;
 template <class C, class Free> class scoped_ptr_malloc;
 template <class C> class scoped_array;
@@ -375,6 +373,5 @@
 bool operator!=(C* p, const scoped_ptr_malloc<C, FP>& b) {
   return p != b.get();
 }
-}  // namespace art
 
 #endif  // ART_SRC_SCOPED_PTR_H_