Skip to content

Commit 40cb431

Browse files
archiecobbsjonathan-gibbons
authored andcommitted
8298943: Missing escapes for single quote marks in compiler.properties
Reviewed-by: jjg
1 parent 9194e91 commit 40cb431

File tree

2 files changed

+131
-3
lines changed

2 files changed

+131
-3
lines changed

src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties

+3-3
Original file line numberDiff line numberDiff line change
@@ -3337,7 +3337,7 @@ compiler.err.dc.no.content=\
33373337
no content
33383338

33393339
compiler.err.dc.no.tag.name=\
3340-
no tag name after '@'
3340+
no tag name after ''@''
33413341

33423342
compiler.err.dc.no.url=\
33433343
no URL
@@ -3601,7 +3601,7 @@ compiler.warn.leaks.not.accessible.unexported=\
36013601
{0} {1} in module {2} is not exported
36023602
# 0: kind name, 1: symbol, 2: symbol
36033603
compiler.warn.leaks.not.accessible.not.required.transitive=\
3604-
{0} {1} in module {2} is not indirectly exported using 'requires transitive'
3604+
{0} {1} in module {2} is not indirectly exported using ''requires transitive''
36053605
# 0: kind name, 1: symbol, 2: symbol
36063606
compiler.warn.leaks.not.accessible.unexported.qualified=\
36073607
{0} {1} in module {2} may not be visible to all clients that require this module
@@ -3663,7 +3663,7 @@ compiler.err.sealed.class.must.have.subclasses=\
36633663
# 0: symbol
36643664
compiler.err.cant.inherit.from.sealed=\
36653665
class is not allowed to extend sealed class: {0} \
3666-
(as it is not listed in its 'permits' clause)
3666+
(as it is not listed in its ''permits'' clause)
36673667

36683668
# 0: symbol
36693669
compiler.err.class.in.unnamed.module.cant.extend.sealed.in.diff.package=\

test/langtools/tools/javac/diags/CheckResourceKeys.java

+128
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
import java.io.*;
3434
import java.util.*;
35+
import java.util.regex.*;
3536
import javax.tools.*;
3637
import com.sun.tools.classfile.*;
3738
import com.sun.tools.javac.code.Lint.LintCategory;
@@ -47,6 +48,8 @@ public class CheckResourceKeys {
4748
* look for keys in resource bundles that are no longer required
4849
* -findmissingkeys
4950
* look for keys in resource bundles that are missing
51+
* -checkformats
52+
* validate MessageFormat patterns in resource bundles
5053
*
5154
* @throws Exception if invoked by jtreg and errors occur
5255
*/
@@ -71,16 +74,19 @@ static boolean is_jtreg() {
7174
boolean run(String... args) throws Exception {
7275
boolean findDeadKeys = false;
7376
boolean findMissingKeys = false;
77+
boolean checkFormats = false;
7478

7579
if (args.length == 0) {
7680
if (is_jtreg()) {
7781
findDeadKeys = true;
7882
findMissingKeys = true;
83+
checkFormats = true;
7984
} else {
8085
System.err.println("Usage: java CheckResourceKeys <options>");
8186
System.err.println("where options include");
8287
System.err.println(" -finddeadkeys find keys in resource bundles which are no longer required");
8388
System.err.println(" -findmissingkeys find keys in resource bundles that are required but missing");
89+
System.err.println(" -checkformats validate MessageFormat patterns in resource bundles");
8490
return true;
8591
}
8692
} else {
@@ -89,6 +95,8 @@ boolean run(String... args) throws Exception {
8995
findDeadKeys = true;
9096
else if (arg.equalsIgnoreCase("-findmissingkeys"))
9197
findMissingKeys = true;
98+
else if (arg.equalsIgnoreCase("-checkformats"))
99+
checkFormats = true;
92100
else
93101
error("bad option: " + arg);
94102
}
@@ -106,6 +114,9 @@ else if (arg.equalsIgnoreCase("-findmissingkeys"))
106114
if (findMissingKeys)
107115
findMissingKeys(codeStrings, resourceKeys);
108116

117+
if (checkFormats)
118+
checkFormats(getMessageFormatBundles());
119+
109120
return (errors == 0);
110121
}
111122

@@ -312,6 +323,109 @@ void findMissingKeys(Set<String> codeStrings, Set<String> resourceKeys) {
312323
"locn."
313324
));
314325

326+
void checkFormats(List<ResourceBundle> messageFormatBundles) {
327+
for (ResourceBundle bundle : messageFormatBundles) {
328+
for (String key : bundle.keySet()) {
329+
final String pattern = bundle.getString(key);
330+
try {
331+
validateMessageFormatPattern(pattern);
332+
} catch (IllegalArgumentException e) {
333+
error("Invalid MessageFormat pattern for resource \""
334+
+ key + "\": " + e.getMessage());
335+
}
336+
}
337+
}
338+
}
339+
340+
/**
341+
* Do some basic validation of a {@link java.text.MessageFormat} format string.
342+
*
343+
* <p>
344+
* This checks for balanced braces and unnecessary quoting.
345+
* Code cut, pasted, &amp; simplified from {@link java.text.MessageFormat#applyPattern}.
346+
*
347+
* @throws IllegalArgumentException if {@code pattern} is invalid
348+
* @throws IllegalArgumentException if {@code pattern} is null
349+
*/
350+
public static void validateMessageFormatPattern(String pattern) {
351+
352+
// Check for null
353+
if (pattern == null)
354+
throw new IllegalArgumentException("null pattern");
355+
356+
// Replicate the quirky lexical analysis of MessageFormat's parsing algorithm
357+
final int SEG_RAW = 0;
358+
final int SEG_INDEX = 1;
359+
final int SEG_TYPE = 2;
360+
final int SEG_MODIFIER = 3;
361+
int part = SEG_RAW;
362+
int braceStack = 0;
363+
int quotedStartPos = -1;
364+
for (int i = 0; i < pattern.length(); i++) {
365+
final char ch = pattern.charAt(i);
366+
if (part == SEG_RAW) {
367+
if (ch == '\'') {
368+
if (i + 1 < pattern.length() && pattern.charAt(i + 1) == '\'')
369+
i++;
370+
else if (quotedStartPos == -1)
371+
quotedStartPos = i;
372+
else {
373+
validateMessageFormatQuoted(pattern.substring(quotedStartPos + 1, i));
374+
quotedStartPos = -1;
375+
}
376+
} else if (ch == '{' && quotedStartPos == -1)
377+
part = SEG_INDEX;
378+
continue;
379+
}
380+
if (quotedStartPos != -1) {
381+
if (ch == '\'') {
382+
validateMessageFormatQuoted(pattern.substring(quotedStartPos + 1, i));
383+
quotedStartPos = -1;
384+
}
385+
continue;
386+
}
387+
switch (ch) {
388+
case ',':
389+
if (part < SEG_MODIFIER)
390+
part++;
391+
break;
392+
case '{':
393+
braceStack++;
394+
break;
395+
case '}':
396+
if (braceStack == 0)
397+
part = SEG_RAW;
398+
else
399+
braceStack--;
400+
break;
401+
case '\'':
402+
quotedStartPos = i;
403+
break;
404+
default:
405+
break;
406+
}
407+
}
408+
if (part != SEG_RAW)
409+
throw new IllegalArgumentException("unmatched braces");
410+
if (quotedStartPos != -1)
411+
throw new IllegalArgumentException("unmatched quote starting at offset " + quotedStartPos);
412+
}
413+
414+
/**
415+
* Validate the content of a quoted substring in a {@link java.text.MessageFormat} pattern.
416+
*
417+
* <p>
418+
* We expect this content to contain at least one special character. Otherwise,
419+
* it was probably meant to be something in single quotes but somebody forgot
420+
* to escape the single quotes by doulbing them; and even if intentional,
421+
* it's still bogus because the single quotes are just going to get discarded
422+
* and so they were unnecessary in the first place.
423+
*/
424+
static void validateMessageFormatQuoted(String quoted) {
425+
if (quoted.matches("[^'{},]+"))
426+
throw new IllegalArgumentException("unescaped single quotes around \"" + quoted + "\"");
427+
}
428+
315429
/**
316430
* Look for a resource that ends in this string fragment.
317431
*/
@@ -405,6 +519,20 @@ Set<String> getResourceKeys() {
405519
return results;
406520
}
407521

522+
/**
523+
* Get resource bundles containing MessageFormat strings.
524+
*/
525+
List<ResourceBundle> getMessageFormatBundles() {
526+
Module jdk_compiler = ModuleLayer.boot().findModule("jdk.compiler").get();
527+
List<ResourceBundle> results = new ArrayList<>();
528+
for (String name : new String[]{"compiler", "launcher"}) {
529+
ResourceBundle b =
530+
ResourceBundle.getBundle("com.sun.tools.javac.resources." + name, jdk_compiler);
531+
results.add(b);
532+
}
533+
return results;
534+
}
535+
408536
/**
409537
* Report an error.
410538
*/

0 commit comments

Comments
 (0)