1 /*
2  * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <assert.h>
29 #include <sys/stat.h>
30 #include <ctype.h>
31 
32 #ifdef DEBUG_ARGFILE
33   #ifndef NO_JNI
34     #define NO_JNI
35   #endif
36   #define JLI_ReportMessage(...) printf(__VA_ARGS__)
37   #define JDK_JAVA_OPTIONS "JDK_JAVA_OPTIONS"
IsWhiteSpaceOption(const char * name)38   int IsWhiteSpaceOption(const char* name) { return 1; }
39 #else
40   #include "java.h"
41   #include "jni.h"
42 #endif
43 
44 #include "jli_util.h"
45 #include "emessages.h"
46 
47 #define MAX_ARGF_SIZE 0x7fffffffL
48 
clone_substring(const char * begin,size_t len)49 static char* clone_substring(const char *begin, size_t len) {
50     char *rv = (char *) JLI_MemAlloc(len + 1);
51     memcpy(rv, begin, len);
52     rv[len] = '\0';
53     return rv;
54 }
55 
56 enum STATE {
57     FIND_NEXT,
58     IN_COMMENT,
59     IN_QUOTE,
60     IN_ESCAPE,
61     SKIP_LEAD_WS,
62     IN_TOKEN
63 };
64 
65 typedef struct {
66     enum STATE state;
67     const char* cptr;
68     const char* eob;
69     char quote_char;
70     JLI_List parts;
71 } __ctx_args;
72 
73 #define NOT_FOUND -1
74 static int firstAppArgIndex = NOT_FOUND;
75 
76 static jboolean expectingNoDashArg = JNI_FALSE;
77 // Initialize to 1, as the first argument is the app name and not preprocessed
78 static size_t argsCount = 1;
79 static jboolean stopExpansion = JNI_FALSE;
80 static jboolean relaunch = JNI_FALSE;
81 
82 /*
83  * Prototypes for internal functions.
84  */
85 static jboolean expand(JLI_List args, const char *str, const char *var_name);
86 
87 JNIEXPORT void JNICALL
JLI_InitArgProcessing(jboolean hasJavaArgs,jboolean disableArgFile)88 JLI_InitArgProcessing(jboolean hasJavaArgs, jboolean disableArgFile) {
89     // No expansion for relaunch
90     if (argsCount != 1) {
91         relaunch = JNI_TRUE;
92         stopExpansion = JNI_TRUE;
93         argsCount = 1;
94     } else {
95         stopExpansion = disableArgFile;
96     }
97 
98     expectingNoDashArg = JNI_FALSE;
99 
100     // for tools, this value remains 0 all the time.
101     firstAppArgIndex = hasJavaArgs ? 0: NOT_FOUND;
102 }
103 
104 JNIEXPORT int JNICALL
JLI_GetAppArgIndex()105 JLI_GetAppArgIndex() {
106     // Will be 0 for tools
107     return firstAppArgIndex;
108 }
109 
checkArg(const char * arg)110 static void checkArg(const char *arg) {
111     size_t idx = 0;
112     argsCount++;
113 
114     // All arguments arrive here must be a launcher argument,
115     // ie. by now, all argfile expansions must have been performed.
116     if (*arg == '-') {
117         expectingNoDashArg = JNI_FALSE;
118         if (IsWhiteSpaceOption(arg)) {
119             // expect an argument
120             expectingNoDashArg = JNI_TRUE;
121 
122             if (JLI_StrCmp(arg, "-jar") == 0 ||
123                 JLI_StrCmp(arg, "--module") == 0 ||
124                 JLI_StrCmp(arg, "-m") == 0) {
125                 // This is tricky, we do expect NoDashArg
126                 // But that is considered main class to stop expansion
127                 expectingNoDashArg = JNI_FALSE;
128                 // We can not just update the idx here because if -jar @file
129                 // still need expansion of @file to get the argument for -jar
130             }
131         } else if (JLI_StrCmp(arg, "--disable-@files") == 0) {
132             stopExpansion = JNI_TRUE;
133         } else if (JLI_StrCCmp(arg, "--module=") == 0) {
134             idx = argsCount;
135         }
136     } else {
137         if (!expectingNoDashArg) {
138             // this is main class, argsCount is index to next arg
139             idx = argsCount;
140         }
141         expectingNoDashArg = JNI_FALSE;
142     }
143     // only update on java mode and not yet found main class
144     if (firstAppArgIndex == NOT_FOUND && idx != 0) {
145         firstAppArgIndex = (int) idx;
146     }
147 }
148 
149 /*
150        [\n\r]   +------------+                        +------------+ [\n\r]
151       +---------+ IN_COMMENT +<------+                | IN_ESCAPE  +---------+
152       |         +------------+       |                +------------+         |
153       |    [#]       ^               |[#]                 ^     |            |
154       |   +----------+               |                [\\]|     |[^\n\r]     |
155       v   |                          |                    |     v            |
156 +------------+ [^ \t\n\r\f]  +------------+['"]>      +------------+         |
157 | FIND_NEXT  +-------------->+ IN_TOKEN   +-----------+ IN_QUOTE   +         |
158 +------------+               +------------+   <[quote]+------------+         |
159   |   ^                          |                       |  ^   ^            |
160   |   |               [ \t\n\r\f]|                 [\n\r]|  |   |[^ \t\n\r\f]v
161   |   +--------------------------+-----------------------+  |  +--------------+
162   |                       ['"]                              |  | SKIP_LEAD_WS |
163   +---------------------------------------------------------+  +--------------+
164 */
nextToken(__ctx_args * pctx)165 static char* nextToken(__ctx_args *pctx) {
166     const char* nextc = pctx->cptr;
167     const char* const eob = pctx->eob;
168     const char* anchor = nextc;
169     char *token;
170 
171     for (; nextc < eob; nextc++) {
172         register char ch = *nextc;
173 
174         // Skip white space characters
175         if (pctx->state == FIND_NEXT || pctx->state == SKIP_LEAD_WS) {
176             while (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\f') {
177                 nextc++;
178                 if (nextc >= eob) {
179                     return NULL;
180                 }
181                 ch = *nextc;
182             }
183             pctx->state = (pctx->state == FIND_NEXT) ? IN_TOKEN : IN_QUOTE;
184             anchor = nextc;
185         // Deal with escape sequences
186         } else if (pctx->state == IN_ESCAPE) {
187             // concatenation directive
188             if (ch == '\n' || ch == '\r') {
189                 pctx->state = SKIP_LEAD_WS;
190             } else {
191             // escaped character
192                 char* escaped = (char*) JLI_MemAlloc(2 * sizeof(char));
193                 escaped[1] = '\0';
194                 switch (ch) {
195                     case 'n':
196                         escaped[0] = '\n';
197                         break;
198                     case 'r':
199                         escaped[0] = '\r';
200                         break;
201                     case 't':
202                         escaped[0] = '\t';
203                         break;
204                     case 'f':
205                         escaped[0] = '\f';
206                         break;
207                     default:
208                         escaped[0] = ch;
209                         break;
210                 }
211                 JLI_List_add(pctx->parts, escaped);
212                 pctx->state = IN_QUOTE;
213             }
214             // anchor to next character
215             anchor = nextc + 1;
216             continue;
217         // ignore comment to EOL
218         } else if (pctx->state == IN_COMMENT) {
219             while (ch != '\n' && ch != '\r') {
220                 nextc++;
221                 if (nextc >= eob) {
222                     return NULL;
223                 }
224                 ch = *nextc;
225             }
226             anchor = nextc + 1;
227             pctx->state = FIND_NEXT;
228             continue;
229         }
230 
231         assert(pctx->state != IN_ESCAPE);
232         assert(pctx->state != FIND_NEXT);
233         assert(pctx->state != SKIP_LEAD_WS);
234         assert(pctx->state != IN_COMMENT);
235 
236         switch(ch) {
237             case ' ':
238             case '\t':
239             case '\f':
240                 if (pctx->state == IN_QUOTE) {
241                     continue;
242                 }
243                 // fall through
244             case '\n':
245             case '\r':
246                 if (pctx->parts->size == 0) {
247                     token = clone_substring(anchor, nextc - anchor);
248                 } else {
249                     JLI_List_addSubstring(pctx->parts, anchor, nextc - anchor);
250                     token = JLI_List_combine(pctx->parts);
251                     JLI_List_free(pctx->parts);
252                     pctx->parts = JLI_List_new(4);
253                 }
254                 pctx->cptr = nextc + 1;
255                 pctx->state = FIND_NEXT;
256                 return token;
257             case '#':
258                 if (pctx->state == IN_QUOTE) {
259                     continue;
260                 }
261                 pctx->state = IN_COMMENT;
262                 anchor = nextc + 1;
263                 break;
264             case '\\':
265                 if (pctx->state != IN_QUOTE) {
266                     continue;
267                 }
268                 JLI_List_addSubstring(pctx->parts, anchor, nextc - anchor);
269                 pctx->state = IN_ESCAPE;
270                 // anchor after backslash character
271                 anchor = nextc + 1;
272                 break;
273             case '\'':
274             case '"':
275                 if (pctx->state == IN_QUOTE && pctx->quote_char != ch) {
276                     // not matching quote
277                     continue;
278                 }
279                 // partial before quote
280                 if (anchor != nextc) {
281                     JLI_List_addSubstring(pctx->parts, anchor, nextc - anchor);
282                 }
283                 // anchor after quote character
284                 anchor = nextc + 1;
285                 if (pctx->state == IN_TOKEN) {
286                     pctx->quote_char = ch;
287                     pctx->state = IN_QUOTE;
288                 } else {
289                     pctx->state = IN_TOKEN;
290                 }
291                 break;
292             default:
293                 break;
294         }
295     }
296 
297     assert(nextc == eob);
298     // Only need partial token, not comment or whitespaces
299     if (pctx->state == IN_TOKEN || pctx->state == IN_QUOTE) {
300         if (anchor < nextc) {
301             // not yet return until end of stream, we have part of a token.
302             JLI_List_addSubstring(pctx->parts, anchor, nextc - anchor);
303         }
304     }
305     return NULL;
306 }
307 
readArgFile(FILE * file)308 static JLI_List readArgFile(FILE *file) {
309     char buf[4096];
310     JLI_List rv;
311     __ctx_args ctx;
312     size_t size;
313     char *token;
314 
315     ctx.state = FIND_NEXT;
316     ctx.parts = JLI_List_new(4);
317     // initialize to avoid -Werror=maybe-uninitialized issues from gcc 7.3 onwards.
318     ctx.quote_char = '"';
319 
320     /* arbitrarily pick 8, seems to be a reasonable number of arguments */
321     rv = JLI_List_new(8);
322 
323     while (!feof(file)) {
324         size = fread(buf, sizeof(char), sizeof(buf), file);
325         if (ferror(file)) {
326             JLI_List_free(rv);
327             return NULL;
328         }
329 
330         /* nextc is next character to read from the buffer
331          * eob is the end of input
332          * token is the copied token value, NULL if no a complete token
333          */
334         ctx.cptr = buf;
335         ctx.eob = buf + size;
336         token = nextToken(&ctx);
337         while (token != NULL) {
338             checkArg(token);
339             JLI_List_add(rv, token);
340             token = nextToken(&ctx);
341         }
342     }
343 
344     // remaining partial token
345     if (ctx.state == IN_TOKEN || ctx.state == IN_QUOTE) {
346         if (ctx.parts->size != 0) {
347             token = JLI_List_combine(ctx.parts);
348             checkArg(token);
349             JLI_List_add(rv, token);
350         }
351     }
352     JLI_List_free(ctx.parts);
353 
354     return rv;
355 }
356 
357 /*
358  * if the arg represent a file, that is, prefix with a single '@',
359  * return a list of arguments from the file.
360  * otherwise, return NULL.
361  */
expandArgFile(const char * arg)362 static JLI_List expandArgFile(const char *arg) {
363     FILE *fptr;
364     struct stat st;
365     JLI_List rv;
366 
367     /* failed to access the file */
368     if (stat(arg, &st) != 0) {
369         JLI_ReportMessage(CFG_ERROR6, arg);
370         exit(1);
371     }
372 
373     if (st.st_size > MAX_ARGF_SIZE) {
374         JLI_ReportMessage(CFG_ERROR10, MAX_ARGF_SIZE);
375         exit(1);
376     }
377 
378     fptr = fopen(arg, "r");
379     /* arg file cannot be openned */
380     if (fptr == NULL) {
381         JLI_ReportMessage(CFG_ERROR6, arg);
382         exit(1);
383     }
384 
385     rv = readArgFile(fptr);
386     fclose(fptr);
387 
388     /* error occurred reading the file */
389     if (rv == NULL) {
390         JLI_ReportMessage(DLL_ERROR4, arg);
391         exit(1);
392     }
393 
394     return rv;
395 }
396 
397 /*
398  * expand a string into a list of words separated by whitespace.
399  */
expandArg(const char * arg)400 static JLI_List expandArg(const char *arg) {
401     JLI_List rv;
402 
403     /* arbitrarily pick 8, seems to be a reasonable number of arguments */
404     rv = JLI_List_new(8);
405 
406     expand(rv, arg, NULL);
407 
408     return rv;
409 }
410 
411 JNIEXPORT JLI_List JNICALL
JLI_PreprocessArg(const char * arg,jboolean expandSourceOpt)412 JLI_PreprocessArg(const char *arg, jboolean expandSourceOpt) {
413     JLI_List rv;
414 
415     if (firstAppArgIndex > 0) {
416         // In user application arg, no more work.
417         return NULL;
418     }
419 
420     if (stopExpansion) {
421         // still looking for user application arg
422         checkArg(arg);
423         return NULL;
424     }
425 
426     if (expandSourceOpt
427             && JLI_StrCCmp(arg, "--source") == 0
428             && JLI_StrChr(arg, ' ') != NULL) {
429         return expandArg(arg);
430     }
431 
432     if (arg[0] != '@') {
433         checkArg(arg);
434         return NULL;
435     }
436 
437     if (arg[1] == '\0') {
438         // @ by itself is an argument
439         checkArg(arg);
440         return NULL;
441     }
442 
443     arg++;
444     if (arg[0] == '@') {
445         // escaped @argument
446         rv = JLI_List_new(1);
447         checkArg(arg);
448         JLI_List_add(rv, JLI_StringDup(arg));
449     } else {
450         rv = expandArgFile(arg);
451     }
452     return rv;
453 }
454 
isTerminalOpt(char * arg)455 int isTerminalOpt(char *arg) {
456     return JLI_StrCmp(arg, "-jar") == 0 ||
457            JLI_StrCmp(arg, "-m") == 0 ||
458            JLI_StrCmp(arg, "--module") == 0 ||
459            JLI_StrCCmp(arg, "--module=") == 0 ||
460            JLI_StrCmp(arg, "--dry-run") == 0 ||
461            JLI_StrCmp(arg, "-h") == 0 ||
462            JLI_StrCmp(arg, "-?") == 0 ||
463            JLI_StrCmp(arg, "-help") == 0 ||
464            JLI_StrCmp(arg, "--help") == 0 ||
465            JLI_StrCmp(arg, "-X") == 0 ||
466            JLI_StrCmp(arg, "--help-extra") == 0 ||
467            JLI_StrCmp(arg, "-version") == 0 ||
468            JLI_StrCmp(arg, "--version") == 0 ||
469            JLI_StrCmp(arg, "-fullversion") == 0 ||
470            JLI_StrCmp(arg, "--full-version") == 0;
471 }
472 
473 JNIEXPORT jboolean JNICALL
JLI_AddArgsFromEnvVar(JLI_List args,const char * var_name)474 JLI_AddArgsFromEnvVar(JLI_List args, const char *var_name) {
475     char *env = getenv(var_name);
476 
477     if (firstAppArgIndex == 0) {
478         // Not 'java', return
479         return JNI_FALSE;
480     }
481 
482     if (relaunch) {
483         return JNI_FALSE;
484     }
485 
486     if (NULL == env) {
487         return JNI_FALSE;
488     }
489 
490     JLI_ReportMessage(ARG_INFO_ENVVAR, var_name, env);
491     return expand(args, env, var_name);
492 }
493 
494 /*
495  * Expand a string into a list of args.
496  * If the string is the result of looking up an environment variable,
497  * var_name should be set to the name of that environment variable,
498  * for use if needed in error messages.
499  */
500 
expand(JLI_List args,const char * str,const char * var_name)501 static jboolean expand(JLI_List args, const char *str, const char *var_name) {
502     jboolean inEnvVar = (var_name != NULL);
503 
504     char *p, *arg;
505     char quote;
506     JLI_List argsInFile;
507 
508     // This is retained until the process terminates as it is saved as the args
509     p = JLI_MemAlloc(JLI_StrLen(str) + 1);
510     while (*str != '\0') {
511         while (*str != '\0' && isspace(*str)) {
512             str++;
513         }
514 
515         // Trailing space
516         if (*str == '\0') {
517             break;
518         }
519 
520         arg = p;
521         while (*str != '\0' && !isspace(*str)) {
522             if (inEnvVar && (*str == '"' || *str == '\'')) {
523                 quote = *str++;
524                 while (*str != quote && *str != '\0') {
525                     *p++ = *str++;
526                 }
527 
528                 if (*str == '\0') {
529                     JLI_ReportMessage(ARG_ERROR8, var_name);
530                     exit(1);
531                 }
532                 str++;
533             } else {
534                 *p++ = *str++;
535             }
536         }
537 
538         *p++ = '\0';
539 
540         argsInFile = JLI_PreprocessArg(arg, JNI_FALSE);
541 
542         if (NULL == argsInFile) {
543             if (isTerminalOpt(arg)) {
544                 if (inEnvVar) {
545                     JLI_ReportMessage(ARG_ERROR9, arg, var_name);
546                 } else {
547                     JLI_ReportMessage(ARG_ERROR15, arg);
548                 }
549                 exit(1);
550             }
551             JLI_List_add(args, arg);
552         } else {
553             size_t cnt, idx;
554             char *argFile = arg;
555             cnt = argsInFile->size;
556             for (idx = 0; idx < cnt; idx++) {
557                 arg = argsInFile->elements[idx];
558                 if (isTerminalOpt(arg)) {
559                     if (inEnvVar) {
560                         JLI_ReportMessage(ARG_ERROR10, arg, argFile, var_name);
561                     } else {
562                         JLI_ReportMessage(ARG_ERROR16, arg, argFile);
563                     }
564                     exit(1);
565                 }
566                 JLI_List_add(args, arg);
567             }
568             // Shallow free, we reuse the string to avoid copy
569             JLI_MemFree(argsInFile->elements);
570             JLI_MemFree(argsInFile);
571         }
572         /*
573          * Check if main-class is specified after argument being checked. It
574          * must always appear after expansion, as a main-class could be specified
575          * indirectly into environment variable via an @argfile, and it must be
576          * caught now.
577          */
578         if (firstAppArgIndex != NOT_FOUND) {
579             if (inEnvVar) {
580                 JLI_ReportMessage(ARG_ERROR11, var_name);
581             } else {
582                 JLI_ReportMessage(ARG_ERROR17);
583             }
584             exit(1);
585         }
586 
587         assert (*str == '\0' || isspace(*str));
588     }
589 
590     return JNI_TRUE;
591 }
592 
593 #ifdef DEBUG_ARGFILE
594 /*
595  * Stand-alone sanity test, build with following command line
596  * $ CC -DDEBUG_ARGFILE -DNO_JNI -g args.c jli_util.c
597  */
598 
fail(char * expected,char * actual,size_t idx)599 void fail(char *expected, char *actual, size_t idx) {
600     printf("FAILED: Token[%lu] expected to be <%s>, got <%s>\n", idx, expected, actual);
601     exit(1);
602 }
603 
test_case(char * case_data,char ** tokens,size_t cnt_tokens)604 void test_case(char *case_data, char **tokens, size_t cnt_tokens) {
605     size_t actual_cnt;
606     char *token;
607     __ctx_args ctx;
608 
609     actual_cnt = 0;
610 
611     ctx.state = FIND_NEXT;
612     ctx.parts = JLI_List_new(4);
613     ctx.cptr = case_data;
614     ctx.eob = case_data + strlen(case_data);
615 
616     printf("Test case: <%s>, expected %lu tokens.\n", case_data, cnt_tokens);
617 
618     for (token = nextToken(&ctx); token != NULL; token = nextToken(&ctx)) {
619         // should not have more tokens than expected
620         if (actual_cnt >= cnt_tokens) {
621             printf("FAILED: Extra token detected: <%s>\n", token);
622             exit(2);
623         }
624         if (JLI_StrCmp(token, tokens[actual_cnt]) != 0) {
625             fail(tokens[actual_cnt], token, actual_cnt);
626         }
627         actual_cnt++;
628     }
629 
630     char* last = NULL;
631     if (ctx.parts->size != 0) {
632         last = JLI_List_combine(ctx.parts);
633     }
634     JLI_List_free(ctx.parts);
635 
636     if (actual_cnt >= cnt_tokens) {
637         // same number of tokens, should have nothing left to parse
638         if (last != NULL) {
639             if (*last != '#') {
640                 printf("Leftover detected: %s", last);
641                 exit(2);
642             }
643         }
644     } else {
645         if (JLI_StrCmp(last, tokens[actual_cnt]) != 0) {
646             fail(tokens[actual_cnt], last, actual_cnt);
647         }
648         actual_cnt++;
649     }
650     if (actual_cnt != cnt_tokens) {
651         printf("FAILED: Number of tokens not match, expected %lu, got %lu\n",
652             cnt_tokens, actual_cnt);
653         exit(3);
654     }
655 
656     printf("PASS\n");
657 }
658 
659 #define DO_CASE(name) \
660     test_case(name[0], name + 1, sizeof(name)/sizeof(char*) - 1)
661 
main(int argc,char ** argv)662 int main(int argc, char** argv) {
663     size_t i, j;
664 
665     char* case1[] = { "-version -cp \"c:\\\\java libs\\\\one.jar\" \n",
666         "-version", "-cp", "c:\\java libs\\one.jar" };
667     DO_CASE(case1);
668 
669     // note the open quote at the end
670     char* case2[] = { "com.foo.Panda \"Furious 5\"\fand\t'Shi Fu' \"escape\tprison",
671         "com.foo.Panda", "Furious 5", "and", "Shi Fu", "escape\tprison"};
672     DO_CASE(case2);
673 
674     char* escaped_chars[] = { "escaped chars testing \"\\a\\b\\c\\f\\n\\r\\t\\v\\9\\6\\23\\82\\28\\377\\477\\278\\287\"",
675         "escaped", "chars", "testing", "abc\f\n\r\tv96238228377477278287"};
676     DO_CASE(escaped_chars);
677 
678     char* mixed_quote[]  = { "\"mix 'single quote' in double\" 'mix \"double quote\" in single' partial\"quote me\"this",
679         "mix 'single quote' in double", "mix \"double quote\" in single", "partialquote methis"};
680     DO_CASE(mixed_quote);
681 
682     char* comments[]  = { "line one #comment\n'line #2' #rest are comment\r\n#comment on line 3\nline 4 #comment to eof",
683         "line", "one", "line #2", "line", "4"};
684     DO_CASE(comments);
685 
686     char* open_quote[] = { "This is an \"open quote \n    across line\n\t, note for WS.",
687         "This", "is", "an", "open quote ", "across", "line", ",", "note", "for", "WS." };
688     DO_CASE(open_quote);
689 
690     char* escape_in_open_quote[] = { "Try \"this \\\\\\\\ escape\\n double quote \\\" in open quote",
691         "Try", "this \\\\ escape\n double quote \" in open quote" };
692     DO_CASE(escape_in_open_quote);
693 
694     char* quote[] = { "'-Dmy.quote.single'='Property in single quote. Here a double quote\" Add some slashes \\\\/'",
695         "-Dmy.quote.single=Property in single quote. Here a double quote\" Add some slashes \\/" };
696     DO_CASE(quote);
697 
698     char* multi[] = { "\"Open quote to \n  new \"line \\\n\r   third\\\n\r\\\tand\ffourth\"",
699         "Open quote to ", "new", "line third\tand\ffourth" };
700     DO_CASE(multi);
701 
702     char* escape_quote[] = { "c:\\\"partial quote\"\\lib",
703         "c:\\partial quote\\lib" };
704     DO_CASE(escape_quote);
705 
706     if (argc > 1) {
707         for (i = 0; i < argc; i++) {
708             JLI_List tokens = JLI_PreprocessArg(argv[i], JNI_FALSE);
709             if (NULL != tokens) {
710                 for (j = 0; j < tokens->size; j++) {
711                     printf("Token[%lu]: <%s>\n", (unsigned long) j, tokens->elements[j]);
712                 }
713             }
714         }
715     }
716 }
717 
718 #endif // DEBUG_ARGFILE
719