1 /*
   2  * Copyright (c) 2014, 2015, Oracle and/or its affiliates.
   3  * All rights reserved. Use is subject to license terms.
   4  *
   5  * This file is available and licensed under the following license:
   6  *
   7  * Redistribution and use in source and binary forms, with or without
   8  * modification, are permitted provided that the following conditions
   9  * are met:
  10  *
  11  *  - Redistributions of source code must retain the above copyright
  12  *    notice, this list of conditions and the following disclaimer.
  13  *  - Redistributions in binary form must reproduce the above copyright
  14  *    notice, this list of conditions and the following disclaimer in
  15  *    the documentation and/or other materials provided with the distribution.
  16  *  - Neither the name of Oracle Corporation nor the names of its
  17  *    contributors may be used to endorse or promote products derived
  18  *    from this software without specific prior written permission.
  19  *
  20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31  */
  32 
  33 
  34 #include "JavaVirtualMachine.h"
  35 #include "Platform.h"
  36 #include "PlatformString.h"
  37 #include "FilePath.h"
  38 #include "Package.h"
  39 #include "Java.h"
  40 #include "Helpers.h"
  41 #include "Messages.h"
  42 #include "Macros.h"
  43 #include "PlatformThread.h"
  44 
  45 #include "jni.h"
  46 
  47 #include <map>
  48 #include <list>
  49 
  50 
  51 // Private typedef for function pointer casting
  52 #ifndef USE_JLI_LAUNCH
  53 #define LAUNCH_FUNC "JNI_CreateJavaVM"
  54 typedef jint (JNICALL *JVM_CREATE)(JavaVM ** jvm, JNIEnv ** env, void *);
  55 #else
  56 #define LAUNCH_FUNC "JLI_Launch"
  57 typedef int (JNICALL *JVM_CREATE)(int argc, char ** argv,
  58                                     int jargc, const char** jargv,
  59                                     int appclassc, const char** appclassv,
  60                                     const char* fullversion,
  61                                     const char* dotversion,
  62                                     const char* pname,
  63                                     const char* lname,
  64                                     jboolean javaargs,
  65                                     jboolean cpwildcard,
  66                                     jboolean javaw,
  67                                     jint ergo);
  68 #endif //USE_JLI_LAUNCH
  69 
  70 class JavaLibrary : public Library {
  71     JVM_CREATE FCreateProc;
  72 
  73     JavaLibrary(const TString &FileName);
  74 
  75 public:
  76     JavaLibrary() : Library() {
  77         FCreateProc = NULL;
  78     }
  79 
  80 #ifndef USE_JLI_LAUNCH
  81 bool JavaVMCreate(JavaVM** jvm, JNIEnv** env, void* jvmArgs) {
  82         bool result = true;
  83 
  84         if (FCreateProc == NULL) {
  85             FCreateProc = (JVM_CREATE)GetProcAddress(LAUNCH_FUNC);
  86         }
  87 
  88         if (FCreateProc == NULL) {
  89             Platform& platform = Platform::GetInstance();
  90             Messages& messages = Messages::GetInstance();
  91             platform.ShowMessage(messages.GetMessage(FAILED_LOCATING_JVM_ENTRY_POINT));
  92             return false;
  93         }
  94 
  95         if ((*FCreateProc)(jvm, env, jvmArgs) < 0) {
  96             Platform& platform = Platform::GetInstance();
  97             Messages& messages = Messages::GetInstance();
  98             platform.ShowMessage(messages.GetMessage(FAILED_CREATING_JVM));
  99             return false;
 100         }
 101 
 102         return result;
 103     }
 104 #else
 105     bool JavaVMCreate(size_t argc, char *argv[]) {
 106         if (FCreateProc == NULL) {
 107             FCreateProc = (JVM_CREATE)GetProcAddress(LAUNCH_FUNC);
 108         }
 109 
 110         if (FCreateProc == NULL) {
 111             Platform& platform = Platform::GetInstance();
 112             Messages& messages = Messages::GetInstance();
 113             platform.ShowMessage(messages.GetMessage(FAILED_LOCATING_JVM_ENTRY_POINT));
 114             return false;
 115         }
 116 
 117         return FCreateProc((int)argc, argv,
 118             0, NULL,
 119             0, NULL,
 120             "",
 121             "",
 122             "java",
 123             "java",
 124             false,
 125             false,
 126             false,
 127             0) == 0;
 128     }
 129 #endif //USE_JLI_LAUNCH
 130 };
 131 
 132 #ifndef USE_JLI_LAUNCH
 133 //debug hook to print JVM messages into console.
 134 static jint JNICALL vfprintfHook(FILE *fp, const char *format, va_list args) {
 135 #ifdef WINDOWS
 136    char buffer[20480];
 137    int len;
 138    HANDLE hConsole;
 139    DWORD wasWritten;
 140 
 141    len = _vsnprintf_s(buffer, sizeof(buffer), sizeof(buffer), format, args);
 142 
 143    if (len <= 0) {
 144         return len;
 145    }
 146 
 147    hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
 148 
 149    if (hConsole == INVALID_HANDLE_VALUE) {
 150         return false;
 151    }
 152 
 153    //JVM will always pass us ASCII
 154    WriteConsoleA(hConsole, buffer, strlen(buffer), &wasWritten, NULL);
 155 
 156    return (jint) len;
 157 #endif //WINDOWS
 158 #ifdef LINUX
 159    return 0;
 160 #endif //LINUX
 161 }
 162 #endif //USE_JLI_LAUNCH
 163 
 164 //--------------------------------------------------------------------------------------------------
 165 
 166 struct JavaOptionItem {
 167     TString name;
 168     TString value;
 169     void* extraInfo;
 170 };
 171 
 172 
 173 class JavaOptions {
 174 private:
 175     std::list<JavaOptionItem> FItems;
 176     JavaVMOption* FOptions;
 177 
 178 public:
 179     JavaOptions() {
 180         FOptions = NULL;
 181 
 182 #ifndef USE_JLI_LAUNCH
 183 #ifdef DEBUG
 184         Platform& platform = Platform::GetInstance();
 185 
 186         if (platform.GetDebugState() == dsNative) {
 187             AppendValue(_T("vfprintf"), _T(""), (void*)vfprintfHook);
 188         }
 189 #endif //DEBUG
 190 #endif //USE_JLI_LAUNCH
 191     }
 192 
 193     ~JavaOptions() {
 194         if (FOptions != NULL) {
 195             for (unsigned int index = 0; index < GetCount(); index++) {
 196                 delete[] FOptions[index].optionString;
 197             }
 198 
 199             delete[] FOptions;
 200         }
 201     }
 202 
 203     void AppendValue(const TString Key, TString Value) {
 204         AppendValue(Key, Value, NULL);
 205     }
 206 
 207     void AppendValue(const TString Key, TString Value, void* Extra) {
 208         JavaOptionItem item;
 209         item.name = Key;
 210         item.value = Value;
 211         item.extraInfo = Extra;
 212         FItems.push_back(item);
 213     }
 214 
 215     void AppendValues(TOrderedMap Values) {
 216         std::list<TString> orderedKeys = Helpers::GetOrderedKeysFromMap(Values);
 217         
 218         for (std::list<TString>::const_iterator iterator = orderedKeys.begin(); iterator != orderedKeys.end(); iterator++) {
 219             TString name = *iterator;
 220             TValueIndex value = Values[name];
 221             AppendValue(name, value.value);
 222         }
 223     }
 224     
 225     void ReplaceValue(const TString Key, TString Value) {
 226         for (std::list<JavaOptionItem>::iterator iterator = FItems.begin();
 227              iterator != FItems.end(); iterator++) {
 228             
 229             TString lkey = iterator->name;
 230             
 231             if (lkey == Key) {
 232                 JavaOptionItem item = *iterator;
 233                 item.value = Value;
 234                 iterator = FItems.erase(iterator);
 235                 FItems.insert(iterator, item);
 236                 break;
 237             }
 238         }
 239     }
 240 
 241 #ifndef USE_JLI_LAUNCH
 242     JavaVMOption* ToJavaOptions() {
 243         FOptions = new JavaVMOption[FItems.size()];
 244         memset(FOptions, 0, sizeof(JavaVMOption) * FItems.size());
 245         Macros& macros = Macros::GetInstance();
 246         unsigned int index = 0;
 247         
 248         for (std::list<JavaOptionItem>::const_iterator iterator = FItems.begin();
 249              iterator != FItems.end(); iterator++) {
 250             TString key = iterator->name;
 251             TString value = iterator->value;
 252             TString option = Helpers::NameValueToString(key, value);
 253             option = macros.ExpandMacros(option);
 254 #ifdef DEBUG
 255             printf("%s\n", PlatformString(option).c_str());
 256 #endif //DEBUG
 257             FOptions[index].optionString = PlatformString::duplicate(PlatformString(option).c_str());
 258             FOptions[index].extraInfo = iterator->extraInfo;
 259             index++;
 260         }
 261 
 262         return FOptions;
 263     }
 264 #else
 265     std::list<TString> ToList() {
 266         std::list<TString> result;
 267         Macros& macros = Macros::GetInstance();
 268         
 269         for (std::list<JavaOptionItem>::const_iterator iterator = FItems.begin();
 270              iterator != FItems.end(); iterator++) {
 271             TString key = iterator->name;
 272             TString value = iterator->value;
 273             TString option = Helpers::NameValueToString(key, value);
 274             option = macros.ExpandMacros(option);
 275             result.push_back(option);
 276         }
 277 
 278         return result;
 279     }
 280 #endif //USE_JLI_LAUNCH
 281 
 282     size_t GetCount() {
 283         return FItems.size();
 284     }
 285 };
 286 
 287 // jvmuserargs can have a trailing equals in the key. This needs to be removed to use
 288 // other parts of the launcher.
 289 TOrderedMap RemoveTrailingEquals(TOrderedMap Map) {
 290     TOrderedMap result;
 291 
 292     for (TOrderedMap::const_iterator iterator = Map.begin(); iterator != Map.end(); iterator++) {
 293         TString name = iterator->first;
 294         TValueIndex value = iterator->second;
 295 
 296         // If the last character of the key is an equals, then remove it. If there is no
 297         // equals then combine the two as a key.
 298         TString::iterator i = name.end();
 299         i--;
 300 
 301         if (*i == '=') {
 302             name = name.substr(0, name.size() - 1);
 303         }
 304         else {
 305             i = value.value.begin();
 306             
 307             if (*i == '=') {
 308                 value.value = value.value.substr(1, value.value.size() - 1);
 309             }
 310             else {
 311                 name = name + value.value;
 312                 value.value = _T("");
 313             }
 314         }
 315 
 316         result.insert(TOrderedMap::value_type(name, value));
 317     }
 318 
 319     return result;
 320 }
 321 
 322 //--------------------------------------------------------------------------------------------------
 323 
 324 JavaVirtualMachine::JavaVirtualMachine() {
 325 #ifndef USE_JLI_LAUNCH
 326     FEnv = NULL;
 327     FJvm = NULL;
 328 #endif //USE_JLI_LAUNCH
 329 }
 330 
 331 JavaVirtualMachine::~JavaVirtualMachine(void) {
 332 }
 333 
 334 bool JavaVirtualMachine::StartJVM() {
 335     Platform& platform = Platform::GetInstance();
 336     Package& package = Package::GetInstance();
 337 
 338     TString classpath = package.GetClassPath();
 339 
 340     JavaOptions options;
 341     options.AppendValue(_T("-Djava.class.path"), classpath);
 342     options.AppendValue(_T("-Djava.library.path"), package.GetPackageAppDirectory() + FilePath::PathSeparator() + package.GetPackageLauncherDirectory());
 343     options.AppendValue(_T("-Djava.launcher.path"), package.GetPackageLauncherDirectory());
 344     options.AppendValue(_T("-Dapp.preferences.id"), package.GetAppID());
 345     options.AppendValues(package.GetJVMArgs());
 346     options.AppendValues(RemoveTrailingEquals(package.GetJVMUserArgs()));
 347  
 348 #ifdef DEBUG
 349     if (package.Debugging() == dsJava) {
 350         options.AppendValue(_T("-Xdebug"), _T(""));
 351         options.AppendValue(_T("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=localhost:8000"), _T(""));
 352         platform.ShowMessage(_T("localhost:8000"));
 353     }
 354 #endif //DEBUG
 355 
 356     TString maxHeapSizeOption;
 357     TString minHeapSizeOption;
 358 
 359 
 360     if (package.GetMemoryState() == PackageBootFields::msAuto) {
 361         TPlatformNumber memorySize = package.GetMemorySize();
 362         TString memory = PlatformString((size_t)memorySize).toString() + _T("m");
 363         maxHeapSizeOption = TString(_T("-Xmx")) + memory;
 364         options.AppendValue(maxHeapSizeOption, _T(""));
 365 
 366         if (memorySize > 256)
 367             minHeapSizeOption = _T("-Xms256m");
 368         else
 369             minHeapSizeOption = _T("-Xms") + memory;
 370 
 371         options.AppendValue(minHeapSizeOption, _T(""));
 372     }
 373 
 374     TString mainClassName = package.GetMainClassName();
 375 
 376     if (mainClassName.empty() == true) {
 377         Messages& messages = Messages::GetInstance();
 378         platform.ShowMessage(messages.GetMessage(NO_MAIN_CLASS_SPECIFIED));
 379         return false;
 380     }
 381 
 382     JavaLibrary javaLibrary;
 383     javaLibrary.AddDependencies(platform.FilterOutRuntimeDependenciesForPlatform(platform.GetLibraryImports(package.GetJVMLibraryFileName())));
 384     javaLibrary.Load(package.GetJVMLibraryFileName());
 385     
 386 #ifndef USE_JLI_LAUNCH
 387     if (package.HasSplashScreen() == true) {
 388         options.AppendValue(TString(_T("-splash:")) + package.GetSplashScreenFileName(), _T(""));
 389     }
 390 
 391     // Set up the VM init args
 392     JavaVMInitArgs jvmArgs;
 393     memset(&jvmArgs, 0, sizeof(JavaVMInitArgs));
 394     jvmArgs.version = JNI_VERSION_1_6;
 395     jvmArgs.options = options.ToJavaOptions();
 396     jvmArgs.nOptions = (jint)options.GetCount();
 397     jvmArgs.ignoreUnrecognized = JNI_TRUE;
 398 
 399     if (javaLibrary.JavaVMCreate(&FJvm, &FEnv, &jvmArgs) == true) {
 400         try {
 401             JavaClass mainClass(FEnv, Helpers::ConvertIdToJavaPath(mainClassName));
 402             JavaStaticMethod mainMethod = mainClass.GetStaticMethod(_T("main"), _T("([Ljava/lang/String;)V"));
 403             std::list<TString> appargs = package.GetArgs();
 404             JavaStringArray largs(FEnv, appargs);
 405             
 406             package.FreeBootFields();
 407             
 408             mainMethod.CallVoidMethod(1, largs.GetData());
 409             return true;
 410         }
 411         catch (JavaException& exception) {
 412             platform.ShowMessage(PlatformString(exception.what()).toString());
 413             return false;
 414         }
 415     }
 416 
 417     return false;
 418 }
 419 #else
 420     // Initialize the arguments to JLI_Launch()
 421     //
 422     // On Mac OS X JLI_Launch spawns a new thread that actually starts the JVM. This
 423     // new thread simply re-runs main(argc, argv). Therefore we do not want
 424     // to add new args if we are still in the original main thread so we
 425     // will treat them as command line args provided by the user ...
 426     // Only propagate original set of args first time.
 427 
 428     options.AppendValue(Helpers::ConvertPathToId(mainClassName), _T(""));
 429 
 430     std::list<TString> vmargs;
 431     vmargs.push_back(package.GetCommandName());
 432 
 433     // Mac adds a ProcessSerialNumber to args when launched from .app
 434     // filter out the psn since they it's not expected in the app
 435     if (platform.IsMainThread() == false) {
 436         //TODO shows a splash screen, does not work on Windows, and it does not go away and
 437         // it hangs the process.
 438         if (package.HasSplashScreen() == true) {
 439             options.AppendValue(TString(_T("-splash:")) + package.GetSplashScreenFileName(), _T(""));
 440         }
 441 
 442         std::list<TString> loptions = options.ToList();
 443         vmargs.splice(vmargs.end(), loptions, loptions.begin(), loptions.end());
 444     }
 445 
 446     std::list<TString> largs = package.GetArgs();
 447     vmargs.splice(vmargs.end(), largs, largs.begin(), largs.end());
 448     size_t argc = vmargs.size();
 449     DynamicBuffer<char*> argv(argc+1);
 450     unsigned int index = 0;
 451 
 452     for (std::list<TString>::const_iterator iterator = vmargs.begin();
 453          iterator != vmargs.end(); iterator++) {
 454         TString item = *iterator;
 455         std::string arg = PlatformString(item).toStdString();
 456 #ifdef DEBUG
 457         printf("%i %s\n", index, arg.c_str());
 458 #endif //DEBUG
 459         argv[index] = PlatformString::duplicate(arg.c_str());
 460         index++;
 461     }
 462 
 463     argv[argc] = NULL;
 464 
 465     // On Mac we can only free the boot fields if the calling thread is not the main thread.
 466 #ifdef MAC
 467     if (platform.IsMainThread() == false) {
 468         package.FreeBootFields();
 469     }
 470 #endif //MAC
 471 
 472     if (javaLibrary.JavaVMCreate(argc, argv.GetData()) == true) {
 473         return true;
 474     }
 475 
 476     for (index = 0; index < argc; index++) {
 477         if (argv[index] != NULL) {
 478             delete[] argv[index];
 479         }
 480     }
 481 
 482     return false;
 483 }
 484 #endif //USE_JLI_LAUNCH
 485 
 486 void JavaVirtualMachine::ShutdownJVM() {
 487 #ifndef USE_JLI_LAUNCH
 488     if (FJvm != NULL) {
 489         // If application main() exits quickly but application is run on some other thread
 490         //  (e.g. Swing app performs invokeLater() in main and exits)
 491         // then if we return execution to tWinMain it will exit.
 492         // This will cause process to exit and application will not actually run.
 493         //
 494         // To avoid this we are trying to detach jvm from current thread (java.exe does the same)
 495         // Because we are doing this on the main JVM thread (i.e. one that was used to create JVM)
 496         // this call will spawn "Destroy Java VM" java thread that will shut JVM once there are
 497         // no non-daemon threads running, and then return control here.
 498         // I.e. this will happen when EDT and other app thread will exit.
 499         if (FJvm->DetachCurrentThread() != JNI_OK) {
 500             Platform& platform = Platform::GetInstance();
 501             platform.ShowMessage(_T("Detach failed."));
 502         }
 503 
 504         FJvm->DestroyJavaVM();
 505     }
 506 #endif //USE_JLI_LAUNCH
 507 }