1 /* 2 * Copyright (c) 2015, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /** 25 * @test 26 * @bug 8027634 8210810 8240629 27 * @summary Verify syntax of argument file 28 * @build TestHelper 29 * @run main ArgFileSyntax 30 */ 31 import java.io.File; 32 import java.io.IOException; 33 import java.util.ArrayList; 34 import java.util.Arrays; 35 import java.util.Collections; 36 import java.util.HashMap; 37 import java.util.List; 38 import java.util.Map; 39 import java.util.regex.Pattern; 40 41 public class ArgFileSyntax extends TestHelper { 42 // Buffer size in args.c readArgFile() method 43 private static final int ARG_FILE_PARSER_BUF_SIZE = 4096; 44 45 private File createArgFile(List<String> lines) throws IOException { 46 File argFile = new File("argfile"); 47 argFile.delete(); 48 createAFile(argFile, lines); 49 return argFile; 50 } 51 52 private void verifyOutput(List<String> args, TestResult tr) { 53 if (args.isEmpty()) { 54 return; 55 } 56 57 int i = 1; 58 for (String x : args) { 59 tr.matches(".*argv\\[" + i + "\\] = " + Pattern.quote(x) + ".*"); 60 i++; 61 } 62 if (! tr.testStatus) { 63 System.out.println(tr); 64 throw new RuntimeException("test fails"); 65 } 66 } 67 68 // arg file content, expected options 69 static String[] testCases[][] = { 70 { // empty file 71 {}, {} 72 }, 73 { // comments and # inside quote 74 { "# a couple of -X flags", 75 "-Xmx32m", 76 "-XshowSettings #inline comment", 77 "-Dpound.in.quote=\"This property contains #.\"", 78 "# add -version", 79 "-version", 80 "# trail comment" 81 }, 82 { "-Xmx32m", 83 "-XshowSettings", 84 "-Dpound.in.quote=This property contains #.", 85 "-version" 86 } 87 }, 88 { // open quote with continuation directive 89 // multiple options in a line 90 { "-cp \"c:\\\\java lib\\\\all;\\", 91 " c:\\\\lib\"", 92 "-Xmx32m -XshowSettings", 93 "-version" 94 }, 95 { "-cp", 96 "c:\\java lib\\all;c:\\lib", 97 "-Xmx32m", 98 "-XshowSettings", 99 "-version" 100 } 101 }, 102 { // no continuation on open quote 103 // multiple lines in a property 104 { "-cp \"c:\\\\open quote\\\\all;", 105 " # c:\\\\lib\"", 106 "-Dmultiple.lines=\"line 1\\nline 2\\n\\rline 3\"", 107 "-Dopen.quote=\"Open quote to EOL", 108 "-Dcontinue.with.leadingWS=\"Continue with\\", 109 " \\ leading WS.", 110 "-Dcontinue.without.leadingWS=\"Continue without \\", 111 " leading WS.", 112 "-Descape.seq=\"escaped chars: \\\"\\a\\b\\c\\f\\t\\v\\9\\6\\23\\82\\28\\377\\477\\278\\287\\n\"", 113 "-version" 114 }, 115 { "-cp", 116 "c:\\open quote\\all;", 117 "-Dmultiple.lines=line 1", 118 // line 2 and line 3 shoule be in output, but not as arg[x]= 119 "-Dopen.quote=Open quote to EOL", 120 "-Dcontinue.with.leadingWS=Continue with leading WS.", 121 "-Dcontinue.without.leadingWS=Continue without leading WS.", 122 // cannot verify \n and \r as that break output lines 123 "-Descape.seq=escaped chars: \"abc\f\tv96238228377477278287", 124 "-version" 125 } 126 }, 127 { // No need to escape if not in quote 128 // also quote part of a token 129 { "-cp c:\\\"partial quote\"\\all", 130 "-Xmx32m -XshowSettings", 131 "-version" 132 }, 133 { "-cp", 134 "c:\\partial quote\\all", 135 "-Xmx32m", 136 "-XshowSettings", 137 "-version" 138 } 139 }, 140 { // No recursive expansion 141 { "-Xmx32m", 142 "-cp", 143 " # @cpfile should remains @cpfile", 144 "@cpfile", 145 "-version" 146 }, 147 { "-Xmx32m", 148 "-cp", 149 "@cpfile", 150 "-version" 151 } 152 }, 153 { // Mix quotation 154 { "-Dsingle.in.double=\"Mix 'single' in double\"", 155 "-Ddouble.in.single='Mix \"double\" in single'", 156 "-Dsingle.in.single='Escape \\\'single\\\' in single'", 157 "-Ddouble.in.double=\"Escape \\\"double\\\" in double\"" 158 }, 159 { "-Dsingle.in.double=Mix 'single' in double", 160 "-Ddouble.in.single=Mix \"double\" in single", 161 "-Dsingle.in.single=Escape 'single' in single", 162 "-Ddouble.in.double=Escape \"double\" in double" 163 }, 164 }, 165 { // \t\f as whitespace and in escape 166 { "-Xmx32m\t-Xint\f-version", 167 "-Dcontinue.with.leadingws=\"Line1\\", 168 " \t\fcontinue with \\f<ff> and \\t<tab>" 169 }, 170 { "-Xmx32m", 171 "-Xint", 172 "-version", 173 "-Dcontinue.with.leadingws=Line1continue with \f<ff> and \t<tab>" 174 } 175 } 176 }; 177 178 public List<List<List<String>>> loadCases() { 179 List<List<List<String>>> rv = new ArrayList<>(); 180 for (String[][] testCaseArray: testCases) { 181 List<List<String>> testCase = new ArrayList<>(2); 182 testCase.add(Arrays.asList(testCaseArray[0])); 183 testCase.add(Arrays.asList(testCaseArray[1])); 184 rv.add(testCase); 185 } 186 187 // long lines 188 String bag = "-Dgarbage="; 189 String ver = "-version"; 190 // a token 8192 long 191 char[] data = new char[2*ARG_FILE_PARSER_BUF_SIZE - bag.length()]; 192 Arrays.fill(data, 'O'); 193 List<String> scratch = new ArrayList<>(); 194 scratch.add("-Xmx32m"); 195 scratch.add(bag + String.valueOf(data)); 196 scratch.add(ver); 197 rv.add(Collections.nCopies(2, scratch)); 198 199 data = new char[2*ARG_FILE_PARSER_BUF_SIZE + 1024]; 200 Arrays.fill(data, 'O'); 201 scratch = new ArrayList<>(); 202 scratch.add(bag + String.valueOf(data)); 203 scratch.add(ver); 204 rv.add(Collections.nCopies(2, scratch)); 205 206 // 8210810: position escaping character at boundary 207 // reserve space for quote and backslash 208 data = new char[ARG_FILE_PARSER_BUF_SIZE - bag.length() - 2]; 209 Arrays.fill(data, 'O'); 210 scratch = new ArrayList<>(); 211 String filling = String.valueOf(data); 212 scratch.add(bag + "'" + filling + "\\\\aaa\\\\'"); 213 scratch.add(ver); 214 rv.add(List.of(scratch, List.of(bag + filling + "\\aaa\\", ver))); 215 216 // 8240629: position comment at boundary 217 final int bytesPerLine = 128; 218 data = new char[bytesPerLine]; 219 data[0] = '#'; 220 Arrays.fill(data, 1, data.length, '0'); 221 filling = String.valueOf(data, 0, bytesPerLine - System.lineSeparator().length()) ; 222 int lines = ARG_FILE_PARSER_BUF_SIZE / bytesPerLine; 223 int need = ARG_FILE_PARSER_BUF_SIZE % bytesPerLine; 224 if (need == 0) { 225 lines -= 1; 226 need = filling.length(); 227 } else { 228 need -= System.lineSeparator().length(); 229 } 230 // Construct args 231 scratch = new ArrayList<>(); 232 while (lines > 0) { 233 scratch.add(filling); 234 lines--; 235 } 236 237 Arrays.fill(data, '#'); 238 // Comment end before, at, after boundary 239 for (int count = need - 1; count <= need + 1 ; count++) { 240 String commentAtBoundary = String.valueOf(data, 0, count); 241 List<String> content = new ArrayList<>(); 242 content.addAll(scratch); 243 content.add(commentAtBoundary); 244 content.add("-Dfoo=bar"); 245 rv.add(List.of(content, List.of("-Dfoo=bar"))); 246 } 247 return rv; 248 } 249 250 // ensure the arguments in the file are read in correctly 251 private void verifyParsing(List<String> lines, List<String> args) throws IOException { 252 File argFile = createArgFile(lines); 253 String fname = "@" + argFile.getName(); 254 Map<String, String> env = new HashMap<>(); 255 env.put(JLDEBUG_KEY, "true"); 256 257 TestResult tr; 258 if (args.contains("-version")) { 259 tr = doExec(env, javaCmd, fname); 260 } else { 261 tr = doExec(env, javaCmd, fname, "-version"); 262 } 263 tr.checkPositive(); 264 verifyOutput(args, tr); 265 266 String lastArg = args.contains("-version") ? "-Dlast.arg" : "-version"; 267 tr = doExec(env, javaCmd, "-Xint", fname, lastArg); 268 List<String> scratch = new ArrayList<>(); 269 scratch.add("-Xint"); 270 scratch.addAll(args); 271 scratch.add(lastArg); 272 verifyOutput(scratch, tr); 273 274 argFile.delete(); 275 } 276 277 @Test 278 public void testSyntax() throws IOException { 279 List<List<List<String>>> allcases = loadCases(); 280 for (List<List<String>> test: allcases) { 281 verifyParsing(test.get(0), test.get(1)); 282 } 283 } 284 285 @Test 286 public void badCases() throws IOException { 287 List<String> lines = Arrays.asList( 288 "-Dno.escape=\"Forgot to escape backslash\\\" -version"); 289 File argFile = createArgFile(lines); 290 String fname = "@" + argFile.getName(); 291 Map<String, String> env = new HashMap<>(); 292 env.put(JLDEBUG_KEY, "true"); 293 294 TestResult tr = doExec(env, javaCmd, fname); 295 tr.contains("argv[1] = -Dno.escape=Forgot to escape backslash\" -version"); 296 tr.checkNegative(); 297 if (!tr.testStatus) { 298 System.out.println(tr); 299 throw new RuntimeException("test fails"); 300 } 301 argFile.delete(); 302 } 303 304 public static void main(String... args) throws Exception { 305 ArgFileSyntax a = new ArgFileSyntax(); 306 a.run(args); 307 if (testExitValue > 0) { 308 System.out.println("Total of " + testExitValue + " failed"); 309 System.exit(1); 310 } else { 311 System.out.println("All tests pass"); 312 } 313 } 314 }