--- old/make/launcher/Launcher-java.base.gmk 2015-08-21 15:30:11.000000000 -0700 +++ new/make/launcher/Launcher-java.base.gmk 2015-08-21 15:30:11.000000000 -0700 @@ -31,7 +31,7 @@ # into another dir and copy selectively so debuginfo for java.dll isn't # overwritten. $(eval $(call SetupLauncher,java, \ - -DEXPAND_CLASSPATH_WILDCARDS,,,user32.lib comctl32.lib, \ + -DEXPAND_CLASSPATH_WILDCARDS -DENABLE_ARG_FILES,,,user32.lib comctl32.lib, \ $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/jli_static.lib, $(JAVA_RC_FLAGS), \ $(JAVA_VERSION_INFO_RESOURCE), $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/java_objs,true)) @@ -44,7 +44,7 @@ ifeq ($(OPENJDK_TARGET_OS), windows) $(eval $(call SetupLauncher,javaw, \ - -DJAVAW -DEXPAND_CLASSPATH_WILDCARDS,,,user32.lib comctl32.lib, \ + -DJAVAW -DEXPAND_CLASSPATH_WILDCARDS -DENABLE_ARG_FILES,,,user32.lib comctl32.lib, \ $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/jli_static.lib, $(JAVA_RC_FLAGS), \ $(JAVA_VERSION_INFO_RESOURCE),,true)) endif --- old/make/lib/CoreLibraries.gmk 2015-08-21 15:30:12.000000000 -0700 +++ new/make/lib/CoreLibraries.gmk 2015-08-21 15:30:12.000000000 -0700 @@ -330,6 +330,13 @@ -export:JLI_CmdToArgs \ -export:JLI_GetStdArgc \ -export:JLI_GetStdArgs \ + -export:JLI_List_new \ + -export:JLI_List_add \ + -export:JLI_StringDup \ + -export:JLI_MemFree \ + -export:JLI_InitArgProcessing \ + -export:JLI_PreprocessArg \ + -export:JLI_GetAppArgIndex \ advapi32.lib \ comctl32.lib \ user32.lib, \ --- old/make/mapfiles/libjli/mapfile-vers 2015-08-21 15:30:13.000000000 -0700 +++ new/make/mapfiles/libjli/mapfile-vers 2015-08-21 15:30:13.000000000 -0700 @@ -36,6 +36,13 @@ JLI_ReportExceptionDescription; JLI_GetStdArgs; JLI_GetStdArgc; + JLI_List_new; + JLI_List_add; + JLI_StringDup; + JLI_MemFree; + JLI_InitArgProcessing; + JLI_PreprocessArg; + JLI_GetAppArgIndex; local: *; --- old/src/java.base/share/classes/sun/launcher/resources/launcher.properties 2015-08-21 15:30:14.000000000 -0700 +++ new/src/java.base/share/classes/sun/launcher/resources/launcher.properties 2015-08-21 15:30:14.000000000 -0700 @@ -24,8 +24,8 @@ # # Translators please note do not translate the options themselves -java.launcher.opt.header = Usage: {0} [-options] class [args...]\n\ -\ (to execute a class)\n or {0} [-options] -jar jarfile [args...]\n\ +java.launcher.opt.header = Usage: {0} [options] class [args...]\n\ +\ (to execute a class)\n or {0} [options] -jar jarfile [args...]\n\ \ (to execute a jar file)\n\ where options include:\n @@ -68,6 +68,8 @@ \ load Java programming language agent, see java.lang.instrument\n\ \ -splash:\n\ \ show splash screen with specified image\n\ +\ @ read options from the specified file\n\ + See http://www.oracle.com/technetwork/java/javase/documentation/index.html for more details. # Translators please note do not translate the options themselves @@ -102,7 +104,8 @@ \ -XshowSettings:properties\n\ \ show all property settings and continue\n\ \ -XshowSettings:locale\n\ -\ show all locale related settings and continue\n\n\ +\ show all locale related settings and continue\n\ +\ -Xdisable-@files disable further argument file expansion\n\n\ The -X options are non-standard and subject to change without notice.\n # Translators please note do not translate the options themselves --- old/src/java.base/share/native/launcher/defines.h 2015-08-21 15:30:15.000000000 -0700 +++ new/src/java.base/share/native/launcher/defines.h 2015-08-21 15:30:15.000000000 -0700 @@ -89,4 +89,9 @@ static const jint const_ergo_class = DEFAULT_POLICY; #endif /* NEVER_ACT_AS_SERVER_CLASS_MACHINE */ +#ifdef ENABLE_ARG_FILES +static const jboolean const_disable_argfile = JNI_FALSE; +#else +static const jboolean const_disable_argfile = JNI_TRUE; +#endif #endif /*_DEFINES_H */ --- old/src/java.base/share/native/launcher/main.c 2015-08-21 15:30:16.000000000 -0700 +++ new/src/java.base/share/native/launcher/main.c 2015-08-21 15:30:16.000000000 -0700 @@ -31,6 +31,7 @@ */ #include "defines.h" +#include "jli_util.h" #ifdef _MSC_VER #if _MSC_VER > 1400 && _MSC_VER < 1600 @@ -96,6 +97,9 @@ char** margv; const jboolean const_javaw = JNI_FALSE; #endif /* JAVAW */ + + JLI_InitArgProcessing(!HAS_JAVA_ARGS, const_disable_argfile); + #ifdef _WIN32 { int i = 0; @@ -119,8 +123,30 @@ margv[i] = NULL; } #else /* *NIXES */ - margc = argc; - margv = argv; + { + // accommodate the NULL at the end + JLI_List args = JLI_List_new(argc + 1); + int i = 0; + for (i = 0; i < argc; i++) { + JLI_List argsInFile = JLI_PreprocessArg(argv[i]); + if (NULL == argsInFile) { + JLI_List_add(args, JLI_StringDup(argv[i])); + } else { + int cnt, idx; + cnt = argsInFile->size; + for (idx = 0; idx < cnt; idx++) { + JLI_List_add(args, argsInFile->elements[idx]); + } + // Shallow free, we reuse the string to avoid copy + JLI_MemFree(argsInFile->elements); + JLI_MemFree(argsInFile); + } + } + margc = args->size; + // add the NULL pointer at argv[argc] + JLI_List_add(args, NULL); + margv = args->elements; + } #endif /* WIN32 */ return JLI_Launch(margc, margv, sizeof(const_jargs) / sizeof(char *), const_jargs, --- old/src/java.base/share/native/libjli/emessages.h 2015-08-21 15:30:18.000000000 -0700 +++ new/src/java.base/share/native/libjli/emessages.h 2015-08-21 15:30:17.000000000 -0700 @@ -71,6 +71,7 @@ #define CFG_ERROR7 "Error: no known VMs. (check for corrupt jvm.cfg file)" #define CFG_ERROR8 "Error: missing `%s' JVM at `%s'.\nPlease install or use the JRE or JDK that contains these missing components." #define CFG_ERROR9 "Error: could not determine JVM type." +#define CFG_ERROR10 "Error: Argument file size should not be larger than %lu." #define JRE_ERROR1 "Error: Could not find Java SE Runtime Environment." #define JRE_ERROR2 "Error: This Java instance does not support a %d-bit JVM.\nPlease install the desired version." --- old/src/java.base/share/native/libjli/java.c 2015-08-21 15:30:19.000000000 -0700 +++ new/src/java.base/share/native/libjli/java.c 2015-08-21 15:30:18.000000000 -0700 @@ -1972,6 +1972,7 @@ { if (!JLI_IsTraceLauncher()) return ; printf("Launcher state:\n"); + printf("\tFirst application arg index: %d\n", JLI_GetAppArgIndex()); printf("\tdebug:%s\n", (JLI_IsTraceLauncher() == JNI_TRUE) ? "on" : "off"); printf("\tjavargs:%s\n", (_is_java_args == JNI_TRUE) ? "on" : "off"); printf("\tprogram name:%s\n", GetProgramName()); --- old/src/java.base/share/native/libjli/jli_util.c 2015-08-21 15:30:20.000000000 -0700 +++ new/src/java.base/share/native/libjli/jli_util.c 2015-08-21 15:30:20.000000000 -0700 @@ -25,8 +25,7 @@ #include #include -#include - +#include #include "jli_util.h" /* @@ -97,6 +96,7 @@ va_start(vl, fmt); vprintf(fmt,vl); va_end(vl); + fflush(stdout); } void @@ -119,3 +119,122 @@ { return JLI_StrNCmp(s1, s2, JLI_StrLen(s2)); } + +JLI_List +JLI_List_new(size_t capacity) +{ + JLI_List l = (JLI_List) JLI_MemAlloc(sizeof(struct JLI_List_)); + l->capacity = capacity; + l->elements = (char **) JLI_MemAlloc(capacity * sizeof(l->elements[0])); + l->size = 0; + return l; +} + +void +JLI_List_free(JLI_List sl) +{ + if (sl) { + if (sl->elements) { + size_t i; + for (i = 0; i < sl->size; i++) + JLI_MemFree(sl->elements[i]); + JLI_MemFree(sl->elements); + } + JLI_MemFree(sl); + } +} + +void +JLI_List_ensureCapacity(JLI_List sl, size_t capacity) +{ + if (sl->capacity < capacity) { + while (sl->capacity < capacity) + sl->capacity *= 2; + sl->elements = JLI_MemRealloc(sl->elements, + sl->capacity * sizeof(sl->elements[0])); + } +} + +void +JLI_List_add(JLI_List sl, char *str) +{ + JLI_List_ensureCapacity(sl, sl->size+1); + sl->elements[sl->size++] = str; +} + +void +JLI_List_addSubstring(JLI_List sl, const char *beg, size_t len) +{ + char *str = (char *) JLI_MemAlloc(len+1); + memcpy(str, beg, len); + str[len] = '\0'; + JLI_List_ensureCapacity(sl, sl->size+1); + sl->elements[sl->size++] = str; +} + +char * +JLI_List_combine(JLI_List sl) +{ + size_t i; + size_t size; + char *str; + char *p; + for (i = 0, size = 1; i < sl->size; i++) + size += JLI_StrLen(sl->elements[i]); + + str = JLI_MemAlloc(size); + + for (i = 0, p = str; i < sl->size; i++) { + size_t len = JLI_StrLen(sl->elements[i]); + memcpy(p, sl->elements[i], len); + p += len; + } + *p = '\0'; + + return str; +} + +char * +JLI_List_join(JLI_List sl, char sep) +{ + size_t i; + size_t size; + char *str; + char *p; + for (i = 0, size = 1; i < sl->size; i++) + size += JLI_StrLen(sl->elements[i]) + 1; + + str = JLI_MemAlloc(size); + + for (i = 0, p = str; i < sl->size; i++) { + size_t len = JLI_StrLen(sl->elements[i]); + if (i > 0) *p++ = sep; + memcpy(p, sl->elements[i], len); + p += len; + } + *p = '\0'; + + return str; +} + +JLI_List +JLI_List_split(const char *str, char sep) +{ + const char *p, *q; + size_t len = JLI_StrLen(str); + int count; + JLI_List sl; + for (count = 1, p = str; p < str + len; p++) + count += (*p == sep); + sl = JLI_List_new(count); + for (p = str;;) { + for (q = p; q <= str + len; q++) { + if (*q == sep || *q == '\0') { + JLI_List_addSubstring(sl, p, q - p); + if (*q == '\0') + return sl; + p = q + 1; + } + } + } +} --- old/src/java.base/share/native/libjli/jli_util.h 2015-08-21 15:30:21.000000000 -0700 +++ new/src/java.base/share/native/libjli/jli_util.h 2015-08-21 15:30:21.000000000 -0700 @@ -29,7 +29,15 @@ #include #include #include -#include + +#ifndef NO_JNI + #include +#else + #define jboolean int + #define JNI_TRUE 1 + #define JNI_FALSE 0 +#endif + #define JLDEBUG_ENV_ENTRY "_JAVA_LAUNCHER_DEBUG" void *JLI_MemAlloc(size_t size); @@ -45,6 +53,7 @@ StdArg *JLI_GetStdArgs(); int JLI_GetStdArgc(); +int JLI_GetAppArgIndex(); #define JLI_StrLen(p1) strlen((p1)) #define JLI_StrChr(p1, p2) strchr((p1), (p2)) @@ -102,4 +111,29 @@ void JLI_SetTraceLauncher(); jboolean JLI_IsTraceLauncher(); +/* + * JLI_List - a dynamic list of char* + */ +struct JLI_List_ +{ + char **elements; + size_t size; + size_t capacity; +}; +typedef struct JLI_List_ *JLI_List; + +JLI_List JLI_List_new(size_t capacity); +void JLI_List_free(JLI_List l); +void JLI_List_ensureCapacity(JLI_List l, size_t capacity); +/* e must be JLI_MemFree-able */ +void JLI_List_add(JLI_List l, char *e); +/* a copy is made out of beg */ +void JLI_List_addSubstring(JLI_List l, const char *beg, size_t len); +char *JLI_List_combine(JLI_List sl); +char *JLI_List_join(JLI_List l, char sep); +JLI_List JLI_List_split(const char *str, char sep); + +void JLI_InitArgProcessing(jboolean isJava, jboolean disableArgFile); +JLI_List JLI_PreprocessArg(const char *arg); + #endif /* _JLI_UTIL_H */ --- old/src/java.base/share/native/libjli/wildcard.c 2015-08-21 15:30:22.000000000 -0700 +++ new/src/java.base/share/native/libjli/wildcard.c 2015-08-21 15:30:22.000000000 -0700 @@ -218,116 +218,6 @@ return JLI_StrCmp(s1, s2) == 0; } -/* - * FileList ADT - a dynamic list of C filenames - */ -struct FileList_ -{ - char **files; - int size; - int capacity; -}; -typedef struct FileList_ *FileList; - -static FileList -FileList_new(int capacity) -{ - FileList fl = NEW_(FileList); - fl->capacity = capacity; - fl->files = (char **) JLI_MemAlloc(capacity * sizeof(fl->files[0])); - fl->size = 0; - return fl; -} - - - -static void -FileList_free(FileList fl) -{ - if (fl) { - if (fl->files) { - int i; - for (i = 0; i < fl->size; i++) - JLI_MemFree(fl->files[i]); - JLI_MemFree(fl->files); - } - JLI_MemFree(fl); - } -} - -static void -FileList_ensureCapacity(FileList fl, int capacity) -{ - if (fl->capacity < capacity) { - while (fl->capacity < capacity) - fl->capacity *= 2; - fl->files = JLI_MemRealloc(fl->files, - fl->capacity * sizeof(fl->files[0])); - } -} - -static void -FileList_add(FileList fl, char *file) -{ - FileList_ensureCapacity(fl, fl->size+1); - fl->files[fl->size++] = file; -} - -static void -FileList_addSubstring(FileList fl, const char *beg, size_t len) -{ - char *filename = (char *) JLI_MemAlloc(len+1); - memcpy(filename, beg, len); - filename[len] = '\0'; - FileList_ensureCapacity(fl, fl->size+1); - fl->files[fl->size++] = filename; -} - -static char * -FileList_join(FileList fl, char sep) -{ - int i; - int size; - char *path; - char *p; - for (i = 0, size = 1; i < fl->size; i++) - size += (int)JLI_StrLen(fl->files[i]) + 1; - - path = JLI_MemAlloc(size); - - for (i = 0, p = path; i < fl->size; i++) { - int len = (int)JLI_StrLen(fl->files[i]); - if (i > 0) *p++ = sep; - memcpy(p, fl->files[i], len); - p += len; - } - *p = '\0'; - - return path; -} - -static FileList -FileList_split(const char *path, char sep) -{ - const char *p, *q; - size_t len = JLI_StrLen(path); - int count; - FileList fl; - for (count = 1, p = path; p < path + len; p++) - count += (*p == sep); - fl = FileList_new(count); - for (p = path;;) { - for (q = p; q <= path + len; q++) { - if (*q == sep || *q == '\0') { - FileList_addSubstring(fl, p, q - p); - if (*q == '\0') - return fl; - p = q + 1; - } - } - } -} - static int isJarFileName(const char *filename) { @@ -352,22 +242,22 @@ return filename; } -static FileList +static JLI_List wildcardFileList(const char *wildcard) { const char *basename; - FileList fl = FileList_new(16); + JLI_List fl = JLI_List_new(16); WildcardIterator it = WildcardIterator_for(wildcard); if (it == NULL) { - FileList_free(fl); + JLI_List_free(fl); return NULL; } while ((basename = WildcardIterator_next(it)) != NULL) if (isJarFileName(basename)) - FileList_add(fl, wildcardConcat(wildcard, basename)); + JLI_List_add(fl, wildcardConcat(wildcard, basename)); WildcardIterator_close(it); return fl; } @@ -383,25 +273,25 @@ } static void -FileList_expandWildcards(FileList fl) +FileList_expandWildcards(JLI_List fl) { - int i, j; + size_t i, j; for (i = 0; i < fl->size; i++) { - if (isWildcard(fl->files[i])) { - FileList expanded = wildcardFileList(fl->files[i]); + if (isWildcard(fl->elements[i])) { + JLI_List expanded = wildcardFileList(fl->elements[i]); if (expanded != NULL && expanded->size > 0) { - JLI_MemFree(fl->files[i]); - FileList_ensureCapacity(fl, fl->size + expanded->size); + JLI_MemFree(fl->elements[i]); + JLI_List_ensureCapacity(fl, fl->size + expanded->size); for (j = fl->size - 1; j >= i+1; j--) - fl->files[j+expanded->size-1] = fl->files[j]; + fl->elements[j+expanded->size-1] = fl->elements[j]; for (j = 0; j < expanded->size; j++) - fl->files[i+j] = expanded->files[j]; + fl->elements[i+j] = expanded->elements[j]; i += expanded->size - 1; fl->size += expanded->size - 1; /* fl expropriates expanded's elements. */ expanded->size = 0; } - FileList_free(expanded); + JLI_List_free(expanded); } } } @@ -410,14 +300,14 @@ JLI_WildcardExpandClasspath(const char *classpath) { char *expanded; - FileList fl; + JLI_List fl; if (JLI_StrChr(classpath, '*') == NULL) return classpath; - fl = FileList_split(classpath, PATH_SEPARATOR); + fl = JLI_List_split(classpath, PATH_SEPARATOR); FileList_expandWildcards(fl); - expanded = FileList_join(fl, PATH_SEPARATOR); - FileList_free(fl); + expanded = JLI_List_join(fl, PATH_SEPARATOR); + JLI_List_free(fl); if (getenv(JLDEBUG_ENV_ENTRY) != 0) printf("Expanded wildcards:\n" " before: \"%s\"\n" @@ -428,13 +318,13 @@ #ifdef DEBUG_WILDCARD static void -FileList_print(FileList fl) +FileList_print(JLI_List fl) { - int i; + size_t i; putchar('['); for (i = 0; i < fl->size; i++) { if (i > 0) printf(", "); - printf("\"%s\"",fl->files[i]); + printf("\"%s\"",fl->elements[i]); } putchar(']'); } --- old/src/java.base/windows/native/libjli/cmdtoargs.c 2015-08-21 15:30:23.000000000 -0700 +++ new/src/java.base/windows/native/libjli/cmdtoargs.c 2015-08-21 15:30:23.000000000 -0700 @@ -198,18 +198,37 @@ StdArg* argv = NULL; jboolean wildcard = JNI_FALSE; char* src = cmdline; + JLI_List argsInFile; // allocate arg buffer with sufficient space to receive the largest arg char* arg = JLI_StringDup(cmdline); do { src = next_arg(src, arg, &wildcard); - // resize to accommodate another Arg - argv = (StdArg*) JLI_MemRealloc(argv, (nargs+1) * sizeof(StdArg)); - argv[nargs].arg = JLI_StringDup(arg); - argv[nargs].has_wildcard = wildcard; + argsInFile = JLI_PreprocessArg(arg); + if (argsInFile != NULL) { + size_t cnt, i; + // resize to accommodate another Arg + cnt = argsInFile->size; + argv = (StdArg*) JLI_MemRealloc(argv, (nargs + cnt) * sizeof(StdArg)); + for (i = 0; i < cnt; i++) { + argv[nargs].arg = argsInFile->elements[i]; + // wildcard is not supported in argfile + argv[nargs].has_wildcard = JNI_FALSE; + nargs++; + } + // Shallow free, we reuse the string to avoid copy + JLI_MemFree(argsInFile->elements); + JLI_MemFree(argsInFile); + } else { + // resize to accommodate another Arg + argv = (StdArg*) JLI_MemRealloc(argv, (nargs+1) * sizeof(StdArg)); + argv[nargs].arg = JLI_StringDup(arg); + argv[nargs].has_wildcard = wildcard; + *arg = '\0'; + nargs++; + } *arg = '\0'; - nargs++; } while (src != NULL); JLI_MemFree(arg); --- old/test/tools/launcher/TestHelper.java 2015-08-21 15:30:24.000000000 -0700 +++ new/test/tools/launcher/TestHelper.java 2015-08-21 15:30:24.000000000 -0700 @@ -594,7 +594,7 @@ } boolean notContains(String str) { - for (String x : testOutput) { + for (String x : testOutput) { if (x.contains(str)) { appendError("string <" + str + "> found"); return false; @@ -604,7 +604,7 @@ } boolean matches(String stringToMatch) { - for (String x : testOutput) { + for (String x : testOutput) { if (x.matches(stringToMatch)) { return true; } --- /dev/null 2015-08-21 15:30:25.000000000 -0700 +++ new/src/java.base/share/native/libjli/args.c 2015-08-21 15:30:25.000000000 -0700 @@ -0,0 +1,532 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include + +#ifdef DEBUG_ARGFILE + #ifndef NO_JNI + #define NO_JNI + #endif + #define JLI_ReportMessage(p1, p2) printf((p1), (p2)) +#else + #include "java.h" +#endif + +#include "jli_util.h" +#include "emessages.h" + +#define MAX_ARGF_SIZE 0x7fffffffL + +static char* clone_substring(const char *begin, size_t len) { + char *rv = (char *) JLI_MemAlloc(len + 1); + memcpy(rv, begin, len); + rv[len] = '\0'; + return rv; +} + +enum STATE { + FIND_NEXT, + IN_COMMENT, + IN_QUOTE, + IN_ESCAPE, + SKIP_LEAD_WS, + IN_TOKEN +}; + +typedef struct { + enum STATE state; + const char* cptr; + const char* eob; + char quote_char; + JLI_List parts; +} __ctx_args; + +#define NOT_FOUND -1 +static int firstAppArgIndex = NOT_FOUND; + +static jboolean expectingNoDashArg = JNI_FALSE; +static size_t argsCount = 0; +static jboolean stopExpansion = JNI_FALSE; + +void JLI_InitArgProcessing(jboolean isJava, jboolean disableArgFile) { + // No expansion for relaunch + if (argsCount != 0) { + stopExpansion = JNI_TRUE; + argsCount = 0; + } else { + stopExpansion = disableArgFile; + } + + expectingNoDashArg = JNI_FALSE; + + // for tools, this value remains 0 all the time. + firstAppArgIndex = isJava ? NOT_FOUND : 0; +} + +int JLI_GetAppArgIndex() { + // Will be 0 for tools + return firstAppArgIndex; +} + +static void checkArg(const char *arg) { + size_t idx = 0; + argsCount++; + if (argsCount == 1) { + // ignore first argument, the application name + return; + } + + // All arguments arrive here must be a launcher argument, + // ie. by now, all argfile expansions must have been performed. + if (*arg++ == '-') { + expectingNoDashArg = JNI_FALSE; + if (JLI_StrCmp(arg, "cp") == 0 || + JLI_StrCmp(arg, "classpath") == 0) { + expectingNoDashArg = JNI_TRUE; + } else if (JLI_StrCmp(arg, "jar") == 0) { + // This is tricky, we do expect NoDashArg + // But that is considered main class to stop expansion + expectingNoDashArg = JNI_FALSE; + // We can not just update the idx here because if -jar @file + // still need expansion of @file to get the argument for -jar + } else if (JLI_StrCmp(arg, "Xdisable-@files") == 0) { + stopExpansion = JNI_TRUE; + } + } else { + if (!expectingNoDashArg) { + // this is main class, argsCount is index to next arg + idx = argsCount; + } + expectingNoDashArg = JNI_FALSE; + } + // only update on java mode and not yet found main class + if (firstAppArgIndex == -1 && idx != 0) { + firstAppArgIndex = (int) idx; + } +} + +/* + [\n\r] +------------+ +------------+ [\n\r] + +---------+ IN_COMMENT +<------+ | IN_ESCAPE +---------+ + | +------------+ | +------------+ | + | [#] ^ |[#] ^ | | + | +----------+ | [\\]| |[^\n\r] | + v | | | v | ++------------+ [^ \t\n\r\f] +------------+['"]> +------------+ | +| FIND_NEXT +-------------->+ IN_TOKEN +-----------+ IN_QUOTE + | ++------------+ +------------+ <[quote]+------------+ | + | ^ | | ^ ^ | + | | [ \t\n\r\f]| [\n\r]| | |[^ \t\n\r\f]v + | +--------------------------+-----------------------+ | +--------------+ + | ['"] | | SKIP_LEAD_WS | + +---------------------------------------------------------+ +--------------+ +*/ +static char* nextToken(__ctx_args *pctx) { + const char* nextc = pctx->cptr; + const char* const eob = pctx->eob; + const char* anchor = nextc; + char *token; + + for (; nextc < eob; nextc++) { + register char ch = *nextc; + + // Skip white space characters + if (pctx->state == FIND_NEXT || pctx->state == SKIP_LEAD_WS) { + while (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\f') { + nextc++; + if (nextc >= eob) { + return NULL; + } + ch = *nextc; + } + pctx->state = (pctx->state == FIND_NEXT) ? IN_TOKEN : IN_QUOTE; + anchor = nextc; + // Deal with escape sequences + } else if (pctx->state == IN_ESCAPE) { + // concatenation directive + if (ch == '\n' || ch == '\r') { + pctx->state = SKIP_LEAD_WS; + } else { + // escaped character + char* escaped = (char*) JLI_MemAlloc(2 * sizeof(char)); + escaped[1] = '\0'; + switch (ch) { + case 'n': + escaped[0] = '\n'; + break; + case 'r': + escaped[0] = '\r'; + break; + case 't': + escaped[0] = '\t'; + break; + case 'f': + escaped[0] = '\f'; + break; + default: + escaped[0] = ch; + break; + } + JLI_List_add(pctx->parts, escaped); + pctx->state = IN_QUOTE; + } + // anchor to next character + anchor = nextc + 1; + continue; + // ignore comment to EOL + } else if (pctx->state == IN_COMMENT) { + while (ch != '\n' && ch != '\r') { + nextc++; + if (nextc > eob) { + return NULL; + } + ch = *nextc; + } + pctx->state = FIND_NEXT; + continue; + } + + assert(pctx->state != IN_ESCAPE); + assert(pctx->state != FIND_NEXT); + assert(pctx->state != SKIP_LEAD_WS); + assert(pctx->state != IN_COMMENT); + + switch(ch) { + case ' ': + case '\t': + case '\f': + if (pctx->state == IN_QUOTE) { + continue; + } + // fall through + case '\n': + case '\r': + if (pctx->parts->size == 0) { + token = clone_substring(anchor, nextc - anchor); + } else { + JLI_List_addSubstring(pctx->parts, anchor, nextc - anchor); + token = JLI_List_combine(pctx->parts); + JLI_List_free(pctx->parts); + pctx->parts = JLI_List_new(4); + } + pctx->cptr = nextc + 1; + pctx->state = FIND_NEXT; + return token; + case '#': + if (pctx->state == IN_QUOTE) { + continue; + } + pctx->state = IN_COMMENT; + break; + case '\\': + if (pctx->state != IN_QUOTE) { + continue; + } + JLI_List_addSubstring(pctx->parts, anchor, nextc - anchor); + pctx->state = IN_ESCAPE; + break; + case '\'': + case '"': + if (pctx->state == IN_QUOTE && pctx->quote_char != ch) { + // not matching quote + continue; + } + // partial before quote + if (anchor != nextc) { + JLI_List_addSubstring(pctx->parts, anchor, nextc - anchor); + } + // anchor after quote character + anchor = nextc + 1; + if (pctx->state == IN_TOKEN) { + pctx->quote_char = ch; + pctx->state = IN_QUOTE; + } else { + pctx->state = IN_TOKEN; + } + break; + default: + break; + } + } + + assert(nextc == eob); + if (anchor != nextc) { + // not yet return until end of stream, we have part of a token. + JLI_List_addSubstring(pctx->parts, anchor, nextc - anchor); + } + return NULL; +} + +static JLI_List readArgFile(FILE *file) { + char buf[4096]; + JLI_List rv; + __ctx_args ctx; + size_t size; + char *token; + + ctx.state = FIND_NEXT; + ctx.parts = JLI_List_new(4); + + /* arbitrarily pick 8, seems to be a reasonable number of arguments */ + rv = JLI_List_new(8); + + while (!feof(file)) { + size = fread(buf, sizeof(char), sizeof(buf), file); + if (ferror(file)) { + JLI_List_free(rv); + return NULL; + } + + /* nextc is next character to read from the buffer + * eob is the end of input + * token is the copied token value, NULL if no a complete token + */ + ctx.cptr = buf; + ctx.eob = buf + size; + token = nextToken(&ctx); + while (token != NULL) { + checkArg(token); + JLI_List_add(rv, token); + token = nextToken(&ctx); + } + } + + // remaining partial token + if (ctx.state == IN_TOKEN || ctx.state == IN_QUOTE) { + if (ctx.parts->size != 0) { + JLI_List_add(rv, JLI_List_combine(ctx.parts)); + } + } + JLI_List_free(ctx.parts); + + return rv; +} + +/* + * if the arg represent a file, that is, prefix with a single '@', + * return a list of arguments from the file. + * otherwise, return NULL. + */ +static JLI_List expandArgFile(const char *arg) { + FILE *fptr; + struct stat st; + JLI_List rv; + + /* failed to access the file */ + if (stat(arg, &st) != 0) { + JLI_ReportMessage(CFG_ERROR6, arg); + exit(1); + } + + if (st.st_size > MAX_ARGF_SIZE) { + JLI_ReportMessage(CFG_ERROR10, MAX_ARGF_SIZE); + exit(1); + } + + fptr = fopen(arg, "r"); + /* arg file cannot be openned */ + if (fptr == NULL) { + JLI_ReportMessage(CFG_ERROR6, arg); + exit(1); + } + + rv = readArgFile(fptr); + fclose(fptr); + + /* error occurred reading the file */ + if (rv == NULL) { + JLI_ReportMessage(DLL_ERROR4, arg); + exit(1); + } + + return rv; +} + +JLI_List JLI_PreprocessArg(const char *arg) +{ + JLI_List rv; + + if (firstAppArgIndex > 0) { + // In user application arg, no more work. + return NULL; + } + + if (stopExpansion) { + // still looking for user application arg + checkArg(arg); + return NULL; + } + + if (arg[0] != '@') { + checkArg(arg); + return NULL; + } + + if (arg[1] == '\0') { + // @ by itself is an argument + checkArg(arg); + return NULL; + } + + arg++; + if (arg[0] == '@') { + // escaped @argument + rv = JLI_List_new(1); + checkArg(arg); + JLI_List_add(rv, JLI_StringDup(arg)); + } else { + rv = expandArgFile(arg); + } + return rv; +} + +#ifdef DEBUG_ARGFILE +/* + * Stand-alone sanity test, build with following command line + * $ CC -DDEBUG_ARGFILE -DNO_JNI -g args.c jli_util.c + */ + +void fail(char *expected, char *actual, size_t idx) { + printf("FAILED: Token[%lu] expected to be <%s>, got <%s>\n", idx, expected, actual); + exit(1); +} + +void test_case(char *case_data, char **tokens, size_t cnt_tokens) { + size_t actual_cnt; + char *token; + __ctx_args ctx; + + actual_cnt = 0; + + ctx.state = FIND_NEXT; + ctx.parts = JLI_List_new(4); + ctx.cptr = case_data; + ctx.eob = case_data + strlen(case_data); + + printf("Test case: <%s>, expected %lu tokens.\n", case_data, cnt_tokens); + + for (token = nextToken(&ctx); token != NULL; token = nextToken(&ctx)) { + // should not have more tokens than expected + if (actual_cnt >= cnt_tokens) { + printf("FAILED: Extra token detected: <%s>\n", token); + exit(2); + } + if (JLI_StrCmp(token, tokens[actual_cnt]) != 0) { + fail(tokens[actual_cnt], token, actual_cnt); + } + actual_cnt++; + } + + char* last = NULL; + if (ctx.parts->size != 0) { + last = JLI_List_combine(ctx.parts); + } + JLI_List_free(ctx.parts); + + if (actual_cnt >= cnt_tokens) { + // same number of tokens, should have nothing left to parse + if (last != NULL) { + if (*last != '#') { + printf("Leftover detected: %s", last); + exit(2); + } + } + } else { + if (JLI_StrCmp(last, tokens[actual_cnt]) != 0) { + fail(tokens[actual_cnt], last, actual_cnt); + } + actual_cnt++; + } + if (actual_cnt != cnt_tokens) { + printf("FAILED: Number of tokens not match, expected %lu, got %lu\n", + cnt_tokens, actual_cnt); + exit(3); + } + + printf("PASS\n"); +} + +#define DO_CASE(name) \ + test_case(name[0], name + 1, sizeof(name)/sizeof(char*) - 1) + +int main(int argc, char** argv) { + size_t i, j; + + char* case1[] = { "-version -cp \"c:\\\\java libs\\\\one.jar\" \n", + "-version", "-cp", "c:\\java libs\\one.jar" }; + DO_CASE(case1); + + // note the open quote at the end + char* case2[] = { "com.foo.Panda \"Furious 5\"\fand\t'Shi Fu' \"escape\tprison", + "com.foo.Panda", "Furious 5", "and", "Shi Fu", "escape\tprison"}; + DO_CASE(case2); + + char* escaped_chars[] = { "escaped chars testing \"\\a\\b\\c\\f\\n\\r\\t\\v\\9\\6\\23\\82\\28\\377\\477\\278\\287\"", + "escaped", "chars", "testing", "abc\f\n\r\tv96238228377477278287"}; + DO_CASE(escaped_chars); + + char* mixed_quote[] = { "\"mix 'single quote' in double\" 'mix \"double quote\" in single' partial\"quote me\"this", + "mix 'single quote' in double", "mix \"double quote\" in single", "partialquote methis"}; + DO_CASE(mixed_quote); + + char* comments[] = { "line one #comment\n'line #2' #rest are comment\r\n#comment on line 3\nline 4 #comment to eof", + "line", "one", "line #2", "line", "4"}; + DO_CASE(comments); + + char* open_quote[] = { "This is an \"open quote \n across line\n\t, note for WS.", + "This", "is", "an", "open quote ", "across", "line", ",", "note", "for", "WS." }; + DO_CASE(open_quote); + + char* escape_in_open_quote[] = { "Try \"this \\\\\\\\ escape\\n double quote \\\" in open quote", + "Try", "this \\\\ escape\n double quote \" in open quote" }; + DO_CASE(escape_in_open_quote); + + char* quote[] = { "'-Dmy.quote.single'='Property in single quote. Here a double quote\" Add some slashes \\\\/'", + "-Dmy.quote.single=Property in single quote. Here a double quote\" Add some slashes \\/" }; + DO_CASE(quote); + + char* multi[] = { "\"Open quote to \n new \"line \\\n\r third\\\n\r\\\tand\ffourth\"", + "Open quote to ", "new", "line third\tand\ffourth" }; + DO_CASE(multi); + + char* escape_quote[] = { "c:\\\"partial quote\"\\lib", + "c:\\partial quote\\lib" }; + DO_CASE(escape_quote); + + if (argc > 1) { + for (i = 0; i < argc; i++) { + JLI_List tokens = JLI_PreprocessArg(argv[i]); + if (NULL != tokens) { + for (j = 0; j < tokens->size; j++) { + printf("Token[%lu]: <%s>\n", (unsigned long) j, tokens->elements[j]); + } + } + } + } +} + +#endif // DEBUG_ARGFILE --- /dev/null 2015-08-21 15:30:26.000000000 -0700 +++ new/test/tools/launcher/ArgFileSyntax.java 2015-08-21 15:30:26.000000000 -0700 @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8027634 + * @summary Verify syntax of argument file + * @build TestHelper + * @run main ArgFileSyntax + */ +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ArgFileSyntax extends TestHelper { + private File createArgFile(List lines) throws IOException { + File argFile = new File("argfile"); + argFile.delete(); + createAFile(argFile, lines); + return argFile; + } + + private void verifyOutput(List args, TestResult tr) { + if (args.isEmpty()) { + return; + } + + int i = 1; + for (String x : args) { + tr.matches(".*argv\\[" + i + "\\] = " + Pattern.quote(x) + ".*"); + i++; + } + if (! tr.testStatus) { + System.out.println(tr); + throw new RuntimeException("test fails"); + } + } + + // arg file content, expected options + static String[] testCases[][] = { + { // empty file + {}, {} + }, + { // comments and # inside quote + { "# a couple of -X flags", + "-Xmx32m", + "-XshowSettings #inline comment", + "-Dpound.in.quote=\"This property contains #.\"", + "# add -version", + "-version", + "# trail comment" + }, + { "-Xmx32m", + "-XshowSettings", + "-Dpound.in.quote=This property contains #.", + "-version" + } + }, + { // open quote with continuation directive + // multiple options in a line + { "-cp \"c:\\\\java lib\\\\all;\\", + " c:\\\\lib\"", + "-Xmx32m -XshowSettings", + "-version" + }, + { "-cp", + "c:\\java lib\\all;c:\\lib", + "-Xmx32m", + "-XshowSettings", + "-version" + } + }, + { // no continuation on open quote + // multiple lines in a property + { "-cp \"c:\\\\open quote\\\\all;", + " # c:\\\\lib\"", + "-Dmultiple.lines=\"line 1\\nline 2\\n\\rline 3\"", + "-Dopen.quote=\"Open quote to EOL", + "-Dcontinue.with.leadingWS=\"Continue with\\", + " \\ leading WS.", + "-Dcontinue.without.leadingWS=\"Continue without \\", + " leading WS.", + "-Descape.seq=\"escaped chars: \\\"\\a\\b\\c\\f\\t\\v\\9\\6\\23\\82\\28\\377\\477\\278\\287\\n\"", + "-version" + }, + { "-cp", + "c:\\open quote\\all;", + "-Dmultiple.lines=line 1", + // line 2 and line 3 shoule be in output, but not as arg[x]= + "-Dopen.quote=Open quote to EOL", + "-Dcontinue.with.leadingWS=Continue with leading WS.", + "-Dcontinue.without.leadingWS=Continue without leading WS.", + // cannot verify \n and \r as that break output lines + "-Descape.seq=escaped chars: \"abc\f\tv96238228377477278287", + "-version" + } + }, + { // No need to escape if not in quote + // also quote part of a token + { "-cp c:\\\"partial quote\"\\all", + "-Xmx32m -XshowSettings", + "-version" + }, + { "-cp", + "c:\\partial quote\\all", + "-Xmx32m", + "-XshowSettings", + "-version" + } + }, + { // No recursive expansion + { "-Xmx32m", + "-cp", + " # @cpfile should remains @cpfile", + "@cpfile", + "-version" + }, + { "-Xmx32m", + "-cp", + "@cpfile", + "-version" + } + }, + { // Mix quotation + { "-Dsingle.in.double=\"Mix 'single' in double\"", + "-Ddouble.in.single='Mix \"double\" in single'", + "-Dsingle.in.single='Escape \\\'single\\\' in single'", + "-Ddouble.in.double=\"Escape \\\"double\\\" in double\"" + }, + { "-Dsingle.in.double=Mix 'single' in double", + "-Ddouble.in.single=Mix \"double\" in single", + "-Dsingle.in.single=Escape 'single' in single", + "-Ddouble.in.double=Escape \"double\" in double" + }, + }, + { // \t\f as whitespace and in escape + { "-Xmx32m\t-Xint\f-version", + "-Dcontinue.with.leadingws=\"Line1\\", + " \t\fcontinue with \\f and \\t" + }, + { "-Xmx32m", + "-Xint", + "-version", + "-Dcontinue.with.leadingws=Line1continue with \f and \t" + } + } + }; + + public List>> loadCases() { + List>> rv = new ArrayList<>(); + for (String[][] testCaseArray: testCases) { + List> testCase = new ArrayList<>(2); + testCase.add(Arrays.asList(testCaseArray[0])); + testCase.add(Arrays.asList(testCaseArray[1])); + rv.add(testCase); + } + + // long lines + String bag = "-Dgarbage="; + String ver = "-version"; + // a token 8192 long + char[] data = new char[8192 - bag.length()]; + Arrays.fill(data, 'O'); + List scratch = new ArrayList<>(); + scratch.add("-Xmx32m"); + scratch.add(bag + String.valueOf(data)); + scratch.add(ver); + rv.add(Collections.nCopies(2, scratch)); + + data = new char[8192 + 1024]; + Arrays.fill(data, 'O'); + scratch = new ArrayList<>(); + scratch.add(bag + String.valueOf(data)); + scratch.add(ver); + rv.add(Collections.nCopies(2, scratch)); + + return rv; + } + + // ensure the arguments in the file are read in correctly + private void verifyParsing(List lines, List args) throws IOException { + File argFile = createArgFile(lines); + String fname = "@" + argFile.getName(); + Map env = new HashMap<>(); + env.put(JLDEBUG_KEY, "true"); + + TestResult tr; + if (args.contains("-version")) { + tr = doExec(env, javaCmd, fname); + } else { + tr = doExec(env, javaCmd, fname, "-version"); + } + tr.checkPositive(); + verifyOutput(args, tr); + + String lastArg = args.contains("-version") ? "-Dlast.arg" : "-version"; + tr = doExec(env, javaCmd, "-Xint", fname, lastArg); + List scratch = new ArrayList<>(); + scratch.add("-Xint"); + scratch.addAll(args); + scratch.add(lastArg); + verifyOutput(scratch, tr); + + argFile.delete(); + } + + @Test + public void testSyntax() throws IOException { + List>> allcases = loadCases(); + for (List> test: allcases) { + verifyParsing(test.get(0), test.get(1)); + } + } + + @Test + public void badCases() throws IOException { + List lines = Arrays.asList( + "-Dno.escape=\"Forgot to escape backslash\\\" -version"); + File argFile = createArgFile(lines); + String fname = "@" + argFile.getName(); + Map env = new HashMap<>(); + env.put(JLDEBUG_KEY, "true"); + + TestResult tr = doExec(env, javaCmd, fname); + tr.contains("argv[1] = -Dno.escape=Forgot to escape backslash\" -version"); + tr.checkNegative(); + if (!tr.testStatus) { + System.out.println(tr); + throw new RuntimeException("test fails"); + } + argFile.delete(); + } + + public static void main(String... args) throws Exception { + ArgFileSyntax a = new ArgFileSyntax(); + a.run(args); + if (testExitValue > 0) { + System.out.println("Total of " + testExitValue + " failed"); + System.exit(1); + } else { + System.out.println("All tests pass"); + } + } +} --- /dev/null 2015-08-21 15:30:27.000000000 -0700 +++ new/test/tools/launcher/ArgsFileTest.java 2015-08-21 15:30:27.000000000 -0700 @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8027634 + * @summary Argument parsing from file + * @build TestHelper + * @run main ArgsFileTest + */ +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ArgsFileTest extends TestHelper { + private static File testJar = null; + private static Map env = new HashMap<>(); + + static void init() throws IOException { + if (testJar != null) { + return; + } + testJar = new File("test.jar"); + StringBuilder tsrc = new StringBuilder(); + tsrc.append("public static void main(String... args) {\n"); + tsrc.append(" for (String x : args) {\n"); + tsrc.append(" System.out.println(x);\n"); + tsrc.append(" }\n"); + tsrc.append("}\n"); + createJar(testJar, new File("Foo"), tsrc.toString()); + + env.put(JLDEBUG_KEY, "true"); + } + + private File createArgFile(String fname, List lines) throws IOException { + File argFile = new File(fname); + argFile.delete(); + createAFile(argFile, lines); + return argFile; + } + + private void verifyOptions(List args, TestResult tr) { + if (args.isEmpty()) { + return; + } + + int i = 1; + for (String x : args) { + tr.matches(".*argv\\[" + i + "\\] = " + Pattern.quote(x) + ".*"); + i++; + } + if (! tr.testStatus) { + System.out.println(tr); + throw new RuntimeException("test fails"); + } + } + + private void verifyUserArgs(List args, TestResult tr, int index) { + if (javaCmd != TestHelper.javaCmd) { + tr.contains("\tFirst application arg index: 1"); + } else { + tr.contains("\tFirst application arg index: " + index); + + for (String arg: args) { + tr.matches("^" + Pattern.quote(arg) + "$"); + } + } + + if (! tr.testStatus) { + System.out.println(tr); + throw new RuntimeException("test fails"); + } + } + + @Test + public void expandAll() throws IOException { + List lines = new ArrayList<>(); + lines.add("-Xmx32m"); + lines.add("-Xint"); + File argFile1 = createArgFile("argFile1", lines); + lines = new ArrayList<>(); + lines.add("-jar"); + lines.add("test.jar"); + lines.add("uarg1 @uarg2 @@uarg3 -uarg4 uarg5"); + File argFile2 = createArgFile("argFile2", lines); + + TestResult tr = doExec(env, javaCmd, "@argFile1", "@argFile2"); + + List appArgs = new ArrayList<>(); + appArgs.add("uarg1"); + appArgs.add("@uarg2"); + appArgs.add("@@uarg3"); + appArgs.add("-uarg4"); + appArgs.add("uarg5"); + + List options = new ArrayList<>(); + options.add("-Xmx32m"); + options.add("-Xint"); + options.add("-jar"); + options.add("test.jar"); + options.addAll(appArgs); + + verifyOptions(options, tr); + verifyUserArgs(appArgs, tr, 5); + argFile1.delete(); + argFile2.delete(); + + File cpFile = createArgFile("cpFile", Arrays.asList("-cp", "test.jar")); + List appCmd = new ArrayList<>(); + appCmd.add("Foo"); + appCmd.addAll(appArgs); + File appFile = createArgFile("appFile", appCmd); + + tr = doExec(env, javaCmd, "@cpFile", "@appFile"); + verifyOptions(Arrays.asList("-cp", "test.jar", "Foo", + "uarg1", "@uarg2", "@@uarg3", "-uarg4", "uarg5"), tr); + verifyUserArgs(appArgs, tr, 4); + cpFile.delete(); + appFile.delete(); + } + + @Test + public void escapeArg() throws IOException { + List lines = new ArrayList<>(); + lines.add("-Xmx32m"); + lines.add("-Xint"); + File argFile1 = createArgFile("argFile1", lines); + + TestResult tr = doExec(env, javaCmd, "-cp", "@@arg", "-cp", "@", + "-cp", "@@@cp", "@argFile1", "@@@@Main@@@@", "-version"); + List options = new ArrayList<>(); + options.add("-cp"); + options.add("@arg"); + options.add("-cp"); + options.add("@"); + options.add("-cp"); + options.add("@@cp"); + options.add("-Xmx32m"); + options.add("-Xint"); + options.add("@@@Main@@@@"); + options.add("-version"); + verifyOptions(options, tr); + verifyUserArgs(Collections.emptyList(), tr, options.size()); + argFile1.delete(); + } + + @Test + public void killSwitch() throws IOException { + List lines = new ArrayList<>(); + lines.add("-Xmx32m"); + lines.add("-Xint"); + File argFile1 = createArgFile("argFile1", lines); + lines = new ArrayList<>(); + lines.add("-jar"); + lines.add("test.jar"); + lines.add("uarg1 @uarg2 @@uarg3 -uarg4 uarg5"); + File argFile2 = createArgFile("argFile2", lines); + File argKill = createArgFile("argKill", + Collections.singletonList("-Xdisable-@files")); + + TestResult tr = doExec(env, javaCmd, "@argFile1", "-Xdisable-@files", "@argFile2"); + List options = new ArrayList<>(); + options.add("-Xmx32m"); + options.add("-Xint"); + options.add("-Xdisable-@files"); + options.add("@argFile2"); + verifyOptions(options, tr); + // Main class is @argFile2 + verifyUserArgs(Collections.emptyList(), tr, 5); + + // Specify in file is same as specify inline + tr = doExec(env, javaCmd, "@argFile1", "@argKill", "@argFile2"); + verifyOptions(options, tr); + // Main class is @argFile2 + verifyUserArgs(Collections.emptyList(), tr, 5); + + // multiple is fine, once on is on. + tr = doExec(env, javaCmd, "@argKill", "@argFile1", "-Xdisable-@files", "@argFile2"); + options = Arrays.asList("-Xdisable-@files", "@argFile1", + "-Xdisable-@files", "@argFile2"); + verifyOptions(options, tr); + verifyUserArgs(Collections.emptyList(), tr, 3); + + // after main class, becoming an user application argument + tr = doExec(env, javaCmd, "@argFile2", "@argKill"); + options = Arrays.asList("-jar", "test.jar", "uarg1", "@uarg2", "@@uarg3", + "-uarg4", "uarg5", "@argKill"); + verifyOptions(options, tr); + verifyUserArgs(Arrays.asList("uarg1", "@uarg2", "@@uarg3", + "-uarg4", "uarg5", "@argKill"), tr, 3); + + argFile1.delete(); + argFile2.delete(); + argKill.delete(); + } + + @Test + public void userApplication() throws IOException { + List lines = new ArrayList<>(); + lines.add("-Xmx32m"); + lines.add("-Xint"); + File vmArgs = createArgFile("vmArgs", lines); + File jarOpt = createArgFile("jarOpt", Arrays.asList("-jar")); + File cpOpt = createArgFile("cpOpt", Arrays.asList("-cp")); + File jarArg = createArgFile("jarArg", Arrays.asList("test.jar")); + File userArgs = createArgFile("userArgs", Arrays.asList("-opt", "arg", "--longopt")); + + TestResult tr = doExec(env, javaCmd, + "@vmArgs", "@jarOpt", "test.jar", "-opt", "arg", "--longopt"); + verifyOptions(Arrays.asList( + "-Xmx32m", "-Xint", "-jar", "test.jar", "-opt", "arg", "--longopt"), tr); + verifyUserArgs(Arrays.asList("-opt", "arg", "--longopt"), tr, 5); + + tr = doExec(env, javaCmd, "@jarOpt", "@jarArg", "@vmArgs"); + verifyOptions(Arrays.asList("-jar", "test.jar", "@vmArgs"), tr); + verifyUserArgs(Arrays.asList("@vmArgs"), tr, 3); + + tr = doExec(env, javaCmd, "-cp", "@jarArg", "@vmArgs", "Foo", "@userArgs"); + verifyOptions(Arrays.asList("-cp", "test.jar", "-Xmx32m", "-Xint", + "Foo", "@userArgs"), tr); + verifyUserArgs(Arrays.asList("@userArgs"), tr, 6); + + tr = doExec(env, javaCmd, "@cpOpt", "@jarArg", "@vmArgs", "Foo", "@userArgs"); + verifyOptions(Arrays.asList("-cp", "test.jar", "-Xmx32m", "-Xint", + "Foo", "@userArgs"), tr); + verifyUserArgs(Arrays.asList("@userArgs"), tr, 6); + + tr = doExec(env, javaCmd, "@cpOpt", "test.jar", "@vmArgs", "Foo", "@userArgs"); + verifyOptions(Arrays.asList("-cp", "test.jar", "-Xmx32m", "-Xint", + "Foo", "@userArgs"), tr); + verifyUserArgs(Arrays.asList("@userArgs"), tr, 6); + + vmArgs.delete(); + jarOpt.delete(); + cpOpt.delete(); + jarArg.delete(); + userArgs.delete(); + } + + // test with missing file + @Test + public void missingFileNegativeTest() throws IOException { + TestResult tr = doExec(javaCmd, "@" + "missing.cmd"); + tr.checkNegative(); + tr.contains("Error: could not open `missing.cmd'"); + if (!tr.testStatus) { + System.out.println(tr); + throw new RuntimeException("test fails"); + } + } + + public static void main(String... args) throws Exception { + init(); + ArgsFileTest a = new ArgsFileTest(); + a.run(args); + if (testExitValue > 0) { + System.out.println("Total of " + testExitValue + " failed"); + System.exit(1); + } else { + System.out.println("All tests pass"); + } + } +}