8173308: JAVAC_OPTIONS should be updated to align with JAVA_OPTIONS
authorksrini
Wed, 15 Feb 2017 18:07:28 -0800
changeset 43874 d49715456f51
parent 43873 705d732d3715
child 43875 d0e60aa1f18b
8173308: JAVAC_OPTIONS should be updated to align with JAVA_OPTIONS Reviewed-by: jjg
langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/main/CommandLine.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Main.java
langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties
langtools/test/tools/javac/main/EnvVariableTest.java
langtools/test/tools/javac/modules/EnvVarTest.java
langtools/test/tools/javac/modules/InheritRuntimeEnvironmentTest.java
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/main/CommandLine.java	Wed Feb 15 16:18:18 2017 -0800
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/main/CommandLine.java	Wed Feb 15 18:07:28 2017 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2017, 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
@@ -29,8 +29,9 @@
 import java.io.Reader;
 import java.nio.file.Files;
 import java.nio.file.Paths;
-
-import com.sun.tools.javac.util.ListBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 
 /**
  * Various utility methods for processing Java tool command line arguments.
@@ -55,28 +56,80 @@
      * @throws IOException if there is a problem reading any of the @files
      */
     public static String[] parse(String[] args) throws IOException {
-        ListBuffer<String> newArgs = new ListBuffer<>();
+        List<String> newArgs = new ArrayList<>();
+        appendParsedCommandArgs(newArgs, Arrays.asList(args));
+        return newArgs.toArray(new String[newArgs.size()]);
+    }
+
+    private static void appendParsedCommandArgs(List<String> newArgs, List<String> args) throws IOException {
         for (String arg : args) {
             if (arg.length() > 1 && arg.charAt(0) == '@') {
                 arg = arg.substring(1);
                 if (arg.charAt(0) == '@') {
-                    newArgs.append(arg);
+                    newArgs.add(arg);
                 } else {
                     loadCmdFile(arg, newArgs);
                 }
             } else {
-                newArgs.append(arg);
+                newArgs.add(arg);
             }
         }
-        return newArgs.toList().toArray(new String[newArgs.length()]);
     }
 
-    private static void loadCmdFile(String name, ListBuffer<String> args) throws IOException {
+    /**
+     * Process the given environment variable and appends any Win32-style
+     * command files for the specified command line arguments and return
+     * the resulting arguments. A command file argument
+     * is of the form '@file' where 'file' is the name of the file whose
+     * contents are to be parsed for additional arguments. The contents of
+     * the command file are parsed using StreamTokenizer and the original
+     * '@file' argument replaced with the resulting tokens. Recursive command
+     * files are not supported. The '@' character itself can be quoted with
+     * the sequence '@@'.
+     * @param envVariable the env variable to process
+     * @param args the arguments that may contain @files
+     * @return the arguments, with environment variable's content and expansion of @files
+     * @throws IOException if there is a problem reading any of the @files
+     * @throws com.sun.tools.javac.main.CommandLine.UnmatchedQuote
+     */
+    public static List<String> parse(String envVariable, List<String> args)
+            throws IOException, UnmatchedQuote {
+
+        List<String> inArgs = new ArrayList<>();
+        appendParsedEnvVariables(inArgs, envVariable);
+        inArgs.addAll(args);
+        List<String> newArgs = new ArrayList<>();
+        appendParsedCommandArgs(newArgs, inArgs);
+        return newArgs;
+    }
+
+    /**
+     * Process the given environment variable and appends any Win32-style
+     * command files for the specified command line arguments and return
+     * the resulting arguments. A command file argument
+     * is of the form '@file' where 'file' is the name of the file whose
+     * contents are to be parsed for additional arguments. The contents of
+     * the command file are parsed using StreamTokenizer and the original
+     * '@file' argument replaced with the resulting tokens. Recursive command
+     * files are not supported. The '@' character itself can be quoted with
+     * the sequence '@@'.
+     * @param envVariable the env variable to process
+     * @param args the arguments that may contain @files
+     * @return the arguments, with environment variable's content and expansion of @files
+     * @throws IOException if there is a problem reading any of the @files
+     * @throws com.sun.tools.javac.main.CommandLine.UnmatchedQuote
+     */
+    public static String[] parse(String envVariable, String[] args) throws IOException, UnmatchedQuote {
+        List<String> out = parse(envVariable, Arrays.asList(args));
+        return out.toArray(new String[out.size()]);
+    }
+
+    private static void loadCmdFile(String name, List<String> args) throws IOException {
         try (Reader r = Files.newBufferedReader(Paths.get(name))) {
             Tokenizer t = new Tokenizer(r);
             String s;
             while ((s = t.nextToken()) != null) {
-                args.append(s);
+                args.add(s);
             }
         }
     }
@@ -188,4 +241,75 @@
             }
         }
     }
+
+    @SuppressWarnings("fallthrough")
+    private static void appendParsedEnvVariables(List<String> newArgs, String envVariable)
+            throws UnmatchedQuote {
+
+        if (envVariable == null) {
+            return;
+        }
+        String in = System.getenv(envVariable);
+        if (in == null || in.trim().isEmpty()) {
+            return;
+        }
+
+        final char NUL = (char)0;
+        final int len = in.length();
+
+        int pos = 0;
+        StringBuilder sb = new StringBuilder();
+        char quote = NUL;
+        char ch;
+
+        loop:
+        while (pos < len) {
+            ch = in.charAt(pos);
+            switch (ch) {
+                case '\"': case '\'':
+                    if (quote == NUL) {
+                        quote = ch;
+                    } else if (quote == ch) {
+                        quote = NUL;
+                    } else {
+                        sb.append(ch);
+                    }
+                    pos++;
+                    break;
+                case '\f': case '\n': case '\r': case '\t': case ' ':
+                    if (quote == NUL) {
+                        newArgs.add(sb.toString());
+                        sb.setLength(0);
+                        while (ch == '\f' || ch == '\n' || ch == '\r' || ch == '\t' || ch == ' ') {
+                            pos++;
+                            if (pos >= len) {
+                                break loop;
+                            }
+                            ch = in.charAt(pos);
+                        }
+                        break;
+                    }
+                    // fall through
+                default:
+                    sb.append(ch);
+                    pos++;
+            }
+        }
+        if (sb.length() != 0) {
+            newArgs.add(sb.toString());
+        }
+        if (quote != NUL) {
+            throw new UnmatchedQuote(envVariable);
+        }
+    }
+
+    public static class UnmatchedQuote extends Exception {
+        private static final long serialVersionUID = 0;
+
+        public final String variableName;
+
+        UnmatchedQuote(String variable) {
+            this.variableName = variable;
+        }
+    }
 }
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Main.java	Wed Feb 15 16:18:18 2017 -0800
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Main.java	Wed Feb 15 18:07:28 2017 -0800
@@ -43,6 +43,7 @@
 import com.sun.tools.javac.file.BaseFileManager;
 import com.sun.tools.javac.file.JavacFileManager;
 import com.sun.tools.javac.jvm.Target;
+import com.sun.tools.javac.main.CommandLine.UnmatchedQuote;
 import com.sun.tools.javac.platform.PlatformDescription;
 import com.sun.tools.javac.processing.AnnotationProcessingError;
 import com.sun.tools.javac.util.*;
@@ -80,6 +81,7 @@
      */
     boolean apiMode;
 
+    private static final String ENV_OPT_NAME = "JDK_JAVAC_OPTIONS";
 
     /** Result codes.
      */
@@ -201,19 +203,12 @@
             return Result.CMDERR;
         }
 
-        // prefix argv with contents of _JAVAC_OPTIONS if set
-        String envOpt = System.getenv("_JAVAC_OPTIONS");
-        if (envOpt != null && !envOpt.trim().isEmpty()) {
-            String[] envv = envOpt.split("\\s+");
-            String[] result = new String[envv.length + argv.length];
-            System.arraycopy(envv, 0, result, 0, envv.length);
-            System.arraycopy(argv, 0, result, envv.length, argv.length);
-            argv = result;
-        }
-
-        // expand @-files
+        // prefix argv with contents of environment variable and expand @-files
         try {
-            argv = CommandLine.parse(argv);
+            argv = CommandLine.parse(ENV_OPT_NAME, argv);
+        } catch (UnmatchedQuote ex) {
+            error("err.unmatched.quote", ex.variableName);
+            return Result.CMDERR;
         } catch (FileNotFoundException | NoSuchFileException e) {
             warning("err.file.not.found", e.getMessage());
             return Result.SYSERR;
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties	Wed Feb 15 16:18:18 2017 -0800
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties	Wed Feb 15 18:07:28 2017 -0800
@@ -377,6 +377,9 @@
 javac.err.repeated.value.for.patch.module=\
     --patch-module specified more than once for {0}
 
+javac.err.unmatched.quote=\
+    unmatched quote in environment variable %s
+
 ## messages
 
 javac.msg.usage.header=\
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/main/EnvVariableTest.java	Wed Feb 15 18:07:28 2017 -0800
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2017, 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 8173308
+ * @summary Check JDK_JAVA_OPTIONS parsing behavior
+ * @library /tools/lib
+ * @modules jdk.compiler/com.sun.tools.javac.main
+ * @build toolbox.ToolBox toolbox.TestRunner
+ * @run main EnvVariableTest
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.file.Path;
+
+import toolbox.*;
+
+import com.sun.tools.javac.main.CommandLine;
+
+public class EnvVariableTest extends TestRunner {
+    final String testClasses;
+    final ToolBox tb;
+    final Path javaExePath;
+    final ExecTask task;
+    final PrintStream ostream;
+    final ByteArrayOutputStream baos;
+
+    public EnvVariableTest() {
+        super(System.err);
+        ostream = System.err;
+        baos = new ByteArrayOutputStream();
+        testClasses = System.getProperty("test.classes");
+        tb = new ToolBox();
+        javaExePath = tb.getJDKTool("java");
+        task = new ExecTask(tb, javaExePath);
+    }
+
+    public static void main(String... args) throws Exception {
+        EnvVariableTest t = new EnvVariableTest();
+        t.runTests();
+    }
+
+    @Test
+    public void testDoubleQuote() throws Exception {
+        // white space quoted with double quotes
+        test("-version -cp \"c:\\\\java libs\\\\one.jar\" \n",
+                "-version", "-cp", "c:\\\\java libs\\\\one.jar");
+    }
+
+    @Test
+    public void testSingleQuote() throws Exception {
+        // white space quoted with single quotes
+        test("-version -cp \'c:\\\\java libs\\\\one.jar\' \n",
+                "-version", "-cp", "c:\\\\java libs\\\\one.jar");
+    }
+
+    @Test
+    public void testEscapeCharacters() throws Exception {
+        // escaped characters
+        test("escaped chars testing \"\\a\\b\\c\\f\\n\\r\\t\\v\\9\\6\\23\\82\\28\\377\\477\\278\\287\"",
+                "escaped", "chars", "testing", "\\a\\b\\c\\f\\n\\r\\t\\v\\9\\6\\23\\82\\28\\377\\477\\278\\287");
+    }
+
+    @Test
+    public void testMixedQuotes() throws Exception {
+        // more mixing of quote types
+        test("\"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");
+    }
+
+    @Test
+    public void testWhiteSpaces() throws Exception {
+        // whitespace tests
+        test("line one #comment\n'line #2' #rest are comment\r\n#comment on line 3\fline 4 #comment to eof",
+                "line", "one", "#comment", "line #2", "#rest", "are", "comment", "#comment", "on", "line",
+                "3", "line", "4", "#comment", "to", "eof");
+    }
+
+    @Test
+    public void testMismatchedDoubleQuote() throws Exception {
+        // mismatched quote
+        test("This is an \"open quote \n    across line\n\t, note for WS.",
+                "Exception: JDK_JAVAC_OPTIONS");
+    }
+
+    @Test
+    public void testMismatchedSingleQuote() throws Exception {
+        // mismatched quote
+        test("This is an \'open quote \n    across line\n\t, note for WS.",
+                "Exception: JDK_JAVAC_OPTIONS");
+    }
+
+    void test(String full, String... expectedArgs) throws Exception {
+        task.envVar("JDK_JAVAC_OPTIONS", full);
+        task.args("--add-exports", "jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
+                "-cp", testClasses, "EnvVariableTest$Tester");
+        Task.Result tr = task.run(Task.Expect.SUCCESS);
+        String expected = Tester.arrayToString(expectedArgs);
+        String in = tr.getOutput(Task.OutputKind.STDOUT);
+        System.err.println("Matching...");
+        System.err.println("Obtained: " + in);
+        System.err.println("Expected: " + expected);
+        if (in.contains(expected)) {
+            System.err.println("....OK");
+            return;
+        }
+        throw new Exception("Expected strings not found");
+    }
+
+    /**
+     * A tester class that is invoked to invoke the CommandLine class, and
+     * print the result.
+     */
+    public static class Tester {
+        private static final String[] EMPTY_ARRAY = new String[0];
+        static String arrayToString(String... args) {
+            return String.join(", ", args);
+        }
+        public static void main(String... args) throws IOException {
+            try {
+                String[] argv = CommandLine.parse("JDK_JAVAC_OPTIONS", EMPTY_ARRAY);
+                System.out.print(arrayToString(argv));
+            } catch (CommandLine.UnmatchedQuote ex) {
+                System.out.print("Exception: " + ex.variableName);
+            }
+        }
+    }
+}
--- a/langtools/test/tools/javac/modules/EnvVarTest.java	Wed Feb 15 16:18:18 2017 -0800
+++ b/langtools/test/tools/javac/modules/EnvVarTest.java	Wed Feb 15 18:07:28 2017 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2017, 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
@@ -24,7 +24,7 @@
 /*
  * @test
  * @bug 8156962
- * @summary Tests use of _JAVAC_OPTIONS env variable
+ * @summary Tests use of JDK_JAVAC_OPTIONS env variable
  * @library /tools/lib
  * @modules jdk.compiler/com.sun.tools.javac.api
  *          jdk.compiler/com.sun.tools.javac.main
@@ -71,7 +71,7 @@
 
         tb.out.println("test that addExports can be provided with env variable");
         new JavacTask(tb, Mode.EXEC)
-                .envVar("_JAVAC_OPTIONS", "--add-exports jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED")
+                .envVar("JDK_JAVAC_OPTIONS", "--add-exports jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED")
                 .outdir(classes)
                 .files(findJavaFiles(src))
                 .run(Expect.SUCCESS)
@@ -83,7 +83,7 @@
                 "--add-exports jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED");
 
         new JavacTask(tb, Mode.EXEC)
-                .envVar("_JAVAC_OPTIONS", "@" + atFile)
+                .envVar("JDK_JAVAC_OPTIONS", "@" + atFile)
                 .outdir(classes)
                 .files(findJavaFiles(src))
                 .run(Expect.SUCCESS)
--- a/langtools/test/tools/javac/modules/InheritRuntimeEnvironmentTest.java	Wed Feb 15 16:18:18 2017 -0800
+++ b/langtools/test/tools/javac/modules/InheritRuntimeEnvironmentTest.java	Wed Feb 15 18:07:28 2017 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2017, 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
@@ -238,7 +238,7 @@
                 Arrays.asList("--add-exports", "jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED");
         List<Path> files = Arrays.asList(findJavaFiles(src));
 
-        String envName = "_JAVAC_OPTIONS";
+        String envName = "JDK_JAVAC_OPTIONS";
         String envValue = String.join(" ", testOpts);
 
         out.println("  javac:");