8143642: Nashorn shebang argument handling is broken
authormhaupt
Thu, 26 Nov 2015 12:01:41 +0100
changeset 34448 4549273285cb
parent 34447 ec4c069f9436
child 34449 d4f99108d903
8143642: Nashorn shebang argument handling is broken Reviewed-by: hannesw, sundar
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/Shell.java
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/Shell.java	Tue Nov 24 10:19:34 2015 +0100
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/Shell.java	Thu Nov 26 12:01:41 2015 +0100
@@ -36,10 +36,12 @@
 import java.io.OutputStream;
 import java.io.PrintStream;
 import java.io.PrintWriter;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.ResourceBundle;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.stream.Collectors;
+
 import jdk.nashorn.api.scripting.NashornException;
 import jdk.nashorn.internal.codegen.Compiler;
 import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
@@ -201,7 +203,8 @@
         // parse options
         if (args != null) {
             try {
-                options.process(args);
+                final String[] prepArgs = preprocessArgs(args);
+                options.process(prepArgs);
             } catch (final IllegalArgumentException e) {
                 werr.println(bundle.getString("shell.usage"));
                 options.displayHelp(e);
@@ -232,6 +235,74 @@
     }
 
     /**
+     * Preprocess the command line arguments passed in by the shell. This checks, for each of the arguments, whether it
+     * can be a file name, and if so, whether the file exists. If the file exists and begins with a shebang line, and
+     * the arguments on that line are a prefix of {@code args} with the file removed, it is assumed that a script file
+     * being executed via shebang was found, and it is moved to the appropriate position in the argument list. The first
+     * such match is used.
+     * <p>
+     * This method canonicalizes the command line arguments to the form {@code <options> <scripts> -- <arguments>},
+     * where the last of the {@code scripts} is the one being run in shebang fashion.
+     * <p>
+     * @implNote Example:<ul>
+     * <li>Shebang line in {@code script.js}: {@code #!/path/to/jjs --language=es6 other.js -- arg1}</li>
+     * <li>Command line: {@code ./script.js arg2}</li>
+     * <li>{@code args} array passed to Nashorn: {@code --language=es6,other.js,--,arg1,./script.js,arg2}</li>
+     * <li>Required canonicalized arguments array: {@code --language=es6,other.js,./script.js,--,arg1,arg2}</li>
+     * </ul>
+     *
+     * @param args the command line arguments as passed into Nashorn.
+     * @return a properly ordered argument list
+     */
+    private static String[] preprocessArgs(final String[] args) {
+        final List<String> largs = new ArrayList<>();
+        Collections.addAll(largs, args);
+        final List<String> pa = new ArrayList<>();
+        String scriptFile = null;
+        boolean found = false;
+        for (int i = 0; i < args.length; ++i) {
+            final String a = args[i];
+            final Path p = Paths.get(a);
+            if (!found && (!a.startsWith("-") || a.length() == 1) && Files.exists(p)) {
+                String l = "";
+                try (final BufferedReader r = Files.newBufferedReader(p)) {
+                    l = r.readLine();
+                } catch (IOException ioe) {
+                    // ignore
+                }
+                if (l.startsWith("#!")) {
+                    List<String> shebangArgs = Arrays.asList(l.split(" "));
+                    shebangArgs = shebangArgs.subList(1, shebangArgs.size()); // remove #! part
+                    final int ssize = shebangArgs.size();
+                    final List<String> filteredArgs = largs.stream().filter(x -> !x.equals(a)).collect(Collectors.toList());
+                    if (filteredArgs.size() >= ssize && shebangArgs.equals(filteredArgs.subList(0, ssize))) {
+                        scriptFile = a;
+                        found = true;
+                        continue;
+                    }
+                }
+            }
+            pa.add(a);
+        }
+        if (scriptFile != null) {
+            // Insert the found script file name either before a -- argument, or at the end of the options list, before
+            // any other arguments, with an extra --.
+            int argidx = pa.indexOf("--");
+            if (argidx == -1) {
+                for (String s : pa) {
+                    ++argidx;
+                    if (s.charAt(0) != '-') {
+                        pa.add(argidx, "--");
+                        break;
+                    }
+                }
+            }
+            pa.add(argidx, scriptFile);
+        }
+        return pa.stream().toArray(String[]::new);
+    }
+
+    /**
      * Compiles the given script files in the command line
      * This is called only when using the --compile-only flag
      *