8189953: FileHandler constructor throws NoSuchFileException with absolute path
authordfuchs
Thu, 09 Nov 2017 20:51:37 +0000
changeset 47725 a4fb389ca61a
parent 47724 6b374b7fdc3d
child 47726 a85bb15efb57
8189953: FileHandler constructor throws NoSuchFileException with absolute path Reviewed-by: mchung
src/java.logging/share/classes/java/util/logging/FileHandler.java
test/jdk/java/util/logging/FileHandlerPatternGeneration.java
--- a/src/java.logging/share/classes/java/util/logging/FileHandler.java	Thu Nov 09 14:38:54 2017 +0530
+++ b/src/java.logging/share/classes/java/util/logging/FileHandler.java	Thu Nov 09 20:51:37 2017 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 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
@@ -616,79 +616,96 @@
      * @throws IOException
      */
     private File generate(String pattern, int generation, int unique)
-            throws IOException {
-        File file = null;
-        String word = "";
-        int ix = 0;
+            throws IOException
+    {
+        return generate(pattern, count, generation, unique);
+    }
+
+    // The static method here is provided for whitebox testing of the algorithm.
+    static File generate(String pat, int count, int generation, int unique)
+            throws IOException
+    {
+        Path path = Paths.get(pat);
+        Path result = null;
         boolean sawg = false;
         boolean sawu = false;
-        while (ix < pattern.length()) {
-            char ch = pattern.charAt(ix);
-            ix++;
-            char ch2 = 0;
-            if (ix < pattern.length()) {
-                ch2 = Character.toLowerCase(pattern.charAt(ix));
+        StringBuilder word = new StringBuilder();
+        Path prev = null;
+        for (Path elem : path) {
+            if (prev != null) {
+                prev = prev.resolveSibling(word.toString());
+                result = result == null ? prev : result.resolve(prev);
             }
-            if (ch == '/') {
-                if (file == null) {
-                    file = new File(word);
-                } else {
-                    file = new File(file, word);
+            String pattern = elem.toString();
+            int ix = 0;
+            word.setLength(0);
+            while (ix < pattern.length()) {
+                char ch = pattern.charAt(ix);
+                ix++;
+                char ch2 = 0;
+                if (ix < pattern.length()) {
+                    ch2 = Character.toLowerCase(pattern.charAt(ix));
                 }
-                word = "";
-                continue;
-            } else  if (ch == '%') {
-                if (ch2 == 't') {
-                    String tmpDir = System.getProperty("java.io.tmpdir");
-                    if (tmpDir == null) {
-                        tmpDir = System.getProperty("user.home");
+                if (ch == '%') {
+                    if (ch2 == 't') {
+                        String tmpDir = System.getProperty("java.io.tmpdir");
+                        if (tmpDir == null) {
+                            tmpDir = System.getProperty("user.home");
+                        }
+                        result = Paths.get(tmpDir);
+                        ix++;
+                        word.setLength(0);
+                        continue;
+                    } else if (ch2 == 'h') {
+                        result = Paths.get(System.getProperty("user.home"));
+                        if (jdk.internal.misc.VM.isSetUID()) {
+                            // Ok, we are in a set UID program.  For safety's sake
+                            // we disallow attempts to open files relative to %h.
+                            throw new IOException("can't use %h in set UID program");
+                        }
+                        ix++;
+                        word.setLength(0);
+                        continue;
+                    } else if (ch2 == 'g') {
+                        word = word.append(generation);
+                        sawg = true;
+                        ix++;
+                        continue;
+                    } else if (ch2 == 'u') {
+                        word = word.append(unique);
+                        sawu = true;
+                        ix++;
+                        continue;
+                    } else if (ch2 == '%') {
+                        word = word.append('%');
+                        ix++;
+                        continue;
                     }
-                    file = new File(tmpDir);
-                    ix++;
-                    word = "";
-                    continue;
-                } else if (ch2 == 'h') {
-                    file = new File(System.getProperty("user.home"));
-                    if (jdk.internal.misc.VM.isSetUID()) {
-                        // Ok, we are in a set UID program.  For safety's sake
-                        // we disallow attempts to open files relative to %h.
-                        throw new IOException("can't use %h in set UID program");
-                    }
-                    ix++;
-                    word = "";
-                    continue;
-                } else if (ch2 == 'g') {
-                    word = word + generation;
-                    sawg = true;
-                    ix++;
-                    continue;
-                } else if (ch2 == 'u') {
-                    word = word + unique;
-                    sawu = true;
-                    ix++;
-                    continue;
-                } else if (ch2 == '%') {
-                    word = word + "%";
-                    ix++;
-                    continue;
                 }
+                word = word.append(ch);
             }
-            word = word + ch;
+            prev = elem;
         }
+
         if (count > 1 && !sawg) {
-            word = word + "." + generation;
+            word = word.append('.').append(generation);
         }
         if (unique > 0 && !sawu) {
-            word = word + "." + unique;
+            word = word.append('.').append(unique);
         }
         if (word.length() > 0) {
-            if (file == null) {
-                file = new File(word);
-            } else {
-                file = new File(file, word);
-            }
+            String n = word.toString();
+            Path p = prev == null ? Paths.get(n) : prev.resolveSibling(n);
+            result = result == null ? p : result.resolve(p);
+        } else if (result == null) {
+            result = Paths.get("");
         }
-        return file;
+
+        if (path.getRoot() == null) {
+            return result.toFile();
+        } else {
+            return path.getRoot().resolve(result).toFile();
+        }
     }
 
     /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/util/logging/FileHandlerPatternGeneration.java	Thu Nov 09 20:51:37 2017 +0000
@@ -0,0 +1,232 @@
+/*
+ * 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.
+ */
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.*;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.FileHandler;
+import java.util.logging.LogManager;
+
+/**
+ * @test
+ * @bug 8189953
+ * @summary tests the pattern generation algorithm
+ * @modules java.logging/java.util.logging:open
+ * @run main/othervm FileHandlerPatternGeneration
+ * @author danielfuchs
+ */
+public class FileHandlerPatternGeneration {
+
+    /**
+     * An array of strings where the elements at even indices are the input
+     * to give to FileHandler::generate(pattern, count, generation, unique),
+     * and the elements at the next odd index are a partially computed expected
+     * output, where %t, %h, %u, %g and file separator still need to be replaced.
+     * The final expected output is obtained by passing the partially computed
+     * output to FileHandlerPatternGeneration::generateExpected
+     * <p>
+     * The test verifies that {@code
+     *    FileHandler.generate(PATTERN[i], c, g, u).toString()
+     * }
+     * is equal to {@code
+     *    FileHandlerPatternGeneration.generateExpected(PATTERN[i],
+     *                                                  PATTERN[i+1],
+     *                                                  c, g, u)
+     * }
+     */
+    static final String[] PATTERNS = {
+            "C:/Workspace/hoge.log",         "C:/Workspace/hoge.log",
+            "C:/Workspace%g/hoge.log",       "C:/Workspace%g/hoge.log",
+            "C:/%uWorkspace/hoge.log",       "C:/%uWorkspace/hoge.log",
+            "C:/%uWorkspace%g/hoge.log",     "C:/%uWorkspace%g/hoge.log",
+            "C:/Workspace/%ghoge.log",       "C:/Workspace/%ghoge.log",
+            "C:/Workspace/%ghoge%u.log",     "C:/Workspace/%ghoge%u.log",
+            "C:/Workspace-%g/hoge.log",      "C:/Workspace-%g/hoge.log",
+            "C:/Work%hspace/hoge.log",       "%h/space/hoge.log",
+            "C:/Works%tpace%g/hoge.log",     "%t/pace%g/hoge.log",
+            "C:/%uWork%hspace/hoge.log",     "%h/space/hoge.log",
+            "C:/%uWorkspace%g/%thoge.log",   "%t/hoge.log",
+            "C:/Workspace/%g%h%%hoge.log",   "%h/%%hoge.log",
+            "C:/Work%h%%hspace/hoge.log",    "%h/%%hspace/hoge.log",
+            "C:/Works%t%%hpace%g/hoge.log",  "%t/%%hpace%g/hoge.log",
+            "C:/%uWork%h%%tspace/hoge.log",  "%h/%%tspace/hoge.log",
+            "C:/%uWorkspace%g/%t%%hoge.log", "%t/%%hoge.log",
+            "C:/Workspace/%g%h%%hoge.log",   "%h/%%hoge.log",
+            "ahaha",                         "ahaha",
+            "ahaha/ahabe",                   "ahaha/ahabe",
+            "../ahaha/ahabe",                "../ahaha/ahabe",
+            "/x%ty/w/hoge.log",              "%t/y/w/hoge.log",
+            "/x/%ty/w/hoge.log",             "%t/y/w/hoge.log",
+            "/x%t/y/w/hoge.log",             "%t/y/w/hoge.log",
+            "/x/%t/y/w/hoge.log",            "%t/y/w/hoge.log",
+            "%ty/w/hoge.log",                "%t/y/w/hoge.log",
+            "%t/y/w/hoge.log",               "%t/y/w/hoge.log",
+            "/x%hy/w/hoge.log",              "%h/y/w/hoge.log",
+            "/x/%hy/w/hoge.log",             "%h/y/w/hoge.log",
+            "/x%h/y/w/hoge.log",             "%h/y/w/hoge.log",
+            "/x/%h/y/w/hoge.log",            "%h/y/w/hoge.log",
+            "%hy/w/hoge.log",                "%h/y/w/hoge.log",
+            "%h/y/w/hoge.log",               "%h/y/w/hoge.log",
+            "ahaha-%u-%g",                   "ahaha-%u-%g",
+            "ahaha-%g/ahabe-%u",             "ahaha-%g/ahabe-%u",
+            "../ahaha-%u/ahabe",             "../ahaha-%u/ahabe",
+            "/x%ty/w/hoge-%g.log",           "%t/y/w/hoge-%g.log",
+            "/x/%ty/w/hoge-%u.log",          "%t/y/w/hoge-%u.log",
+            "%u-%g/x%t/y/w/hoge.log",        "%t/y/w/hoge.log",
+            "/x/%g%t%u/y/w/hoge.log",        "%t/%u/y/w/hoge.log",
+            "%ty/w-%g/hoge.log",             "%t/y/w-%g/hoge.log",
+            "%t/y/w-%u/hoge.log",            "%t/y/w-%u/hoge.log",
+            "/x%hy/%u-%g-w/hoge.log",        "%h/y/%u-%g-w/hoge.log",
+            "/x/%hy/w-%u-%g/hoge.log",       "%h/y/w-%u-%g/hoge.log",
+            "/x%h/y/w/%u-%ghoge.log",        "%h/y/w/%u-%ghoge.log",
+            "/x/%h/y/w/hoge-%u-%g.log",      "%h/y/w/hoge-%u-%g.log",
+            "%hy/w/%u-%g-hoge.log",          "%h/y/w/%u-%g-hoge.log",
+            "%h/y/w/hoge-%u-%g.log",         "%h/y/w/hoge-%u-%g.log",
+            "/x/y/z/hoge-%u.log",            "/x/y/z/hoge-%u.log",
+    };
+
+    // the (count, generation, unique) parameters to pass to
+    // FileHandler.generate(pattern, count, generation, unique)
+    static final int[][] GENERATIONS = {
+        {0, 0, 0},
+        {0, 1, 0},
+        {0, 1, 1},
+        {1, 1, 0},
+        {1, 1, 1},
+        {1, 1, 2},
+        {1, 2, 3},
+        {3, 4, 0},
+        {3, 4, 1},
+        {3, 4, 2},
+        {3, 0, 5},
+        {3, 1, 5},
+        {3, 2, 5},
+    };
+
+    static final Class<FileHandler> FILE_HANDLER_CLASS = FileHandler.class;
+    static final Method GENERATE;
+    static final String USER_HOME;
+    static final String TMP;
+    static {
+        Method generate;
+        try {
+           generate = FILE_HANDLER_CLASS.getDeclaredMethod("generate",
+                                                            String.class,
+                                                            int.class,
+                                                            int.class,
+                                                            int.class);
+           generate.setAccessible(true);
+        } catch (Exception e) {
+            throw new ExceptionInInitializerError(e);
+        }
+        GENERATE = generate;
+        USER_HOME = System.getProperty("user.home");
+        TMP = System.getProperty("java.io.tmpdir", USER_HOME);
+    }
+
+    public static void main(String... args) throws Throwable {
+
+        for (int i=0; i < PATTERNS.length; i+=2) {
+            String s = PATTERNS[i];
+            String partial = PATTERNS[i+1];
+            System.out.println("generate: " + s);
+            for (int[] gen : GENERATIONS) {
+                String expected = generateExpected(s, partial, gen[0], gen[1], gen[2]);
+                String output = generate(s, gen[0], gen[1], gen[2]).toString();
+                System.out.println("\t" + Arrays.toString(gen)+ ": " + output);
+                if (!expected.equals(output)) {
+                    throw new RuntimeException("test failed for \""
+                            + s +"\" " + Arrays.toString(gen) + ": "
+                            + "\n\tgenerated: \"" + output +"\""
+                            + "\n\t expected: \"" + expected +"\"");
+                }
+            }
+        }
+
+    }
+
+    // Strip the trailing separator from the string, if present
+    static String stripTrailingSeparator(String s) {
+        if (s.endsWith("/")) {
+            return s.substring(0, s.length() -1);
+        } else if (s.endsWith(File.separator)) {
+            return s.substring(0, s.length() - File.separator.length());
+        } else {
+            return s;
+        }
+    }
+
+    /**
+     * Compute the final expected output from a partially computed output found
+     * at PATTERNS[i+1]
+     * @param s           The pattern string, found at PATTERN[i]
+     *                    (with i % 2 == 0)
+     * @param partial     The partially computed output, found at PATTERN[i+1]
+     * @param count       The count parameter given to FileHandler::generate
+     * @param generation  The generation parameter given to FileHandler::generate
+     * @param unique      The unique parameter given to FileHandler::generate
+     * @return  The expected output that FileHandler.generate(s, count, gen, unique)
+     *          should produce.
+     */
+    static String generateExpected(String s, String partial,
+                                   int count, int generation, int unique)
+    {
+        boolean sawu = s.replace("%%", "$$$$").contains("%u");
+        boolean sawg = s.replace("%%", "$$$$").contains("%g");
+        String result = partial.replace("%%", "$$$$");
+        String tmp = stripTrailingSeparator(TMP);
+        String home = stripTrailingSeparator(USER_HOME);
+        result = result.replace("%h", home);
+        result = result.replace("%t", tmp);
+        result = result.replace("%g", String.valueOf(generation));
+        result = result.replace("%u", String.valueOf(unique));
+        result = result.replace("$$$$", "%");
+        result = result.replace("/", File.separator);
+        if (count > 1 && !sawg) {
+            result = result + "." + generation;
+        }
+        if (unique > 0 && !sawu) {
+            result = result + "." + unique;
+        }
+        return result;
+    }
+
+    // Calls FileHandler.generate(s, count, generation, unique) through reflection
+    static File generate(String s, int count, int generation, int unique)
+            throws Throwable
+    {
+        try {
+            return (File) GENERATE.invoke(null, s, count, generation, unique);
+        } catch (InvocationTargetException e) {
+            throw e.getCause();
+        }
+    }
+}