Skip to content

Commit 6ac0320

Browse files
committed
[GR-59338] Make js.locale option stable and allowed in sandboxed mode.
PullRequest: js/3300
2 parents df18e59 + 919b09f commit 6ac0320

File tree

4 files changed

+129
-4
lines changed

4 files changed

+129
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ See [release calendar](https://www.graalvm.org/release-calendar/) for release da
1414
* Added option `js.stack-trace-api` that enables/disables `Error.captureStackTrace`, `Error.prepareStackTrace` and `Error.stackTraceLimit`. These non-standard extensions are disabled by default (unless `js.v8-compat` or `js.nashorn-compat` is used).
1515
* Made option `js.webassembly` stable.
1616
* Made options `js.load`, `js.print`, and `js.graal-builtin` stable and allowed in `SandboxPolicy.UNTRUSTED`.
17+
* Made option `js.locale` stable and allowed in `SandboxPolicy.UNTRUSTED`. Its value, if non-empty, must be a well-formed Unicode BCP 47 locale identifier and is now validated.
1718
* Added an experimental `java.util.concurrent.Executor` that can be used to post tasks into the event loop thread in `graal-nodejs`. It is available as `require('node:graal').eventLoopExecutor`.
1819
* Implemented the `TextDecoder` and `TextEncoder` APIs of the [WHATWG Encoding Standard](https://encoding.spec.whatwg.org/). They are available behind the experimental option (`--js.text-encoding`).
1920

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package com.oracle.truffle.js.test.polyglot;
42+
43+
import static org.junit.Assert.assertThrows;
44+
45+
import java.util.List;
46+
import java.util.Locale;
47+
48+
import org.graalvm.polyglot.Context;
49+
import org.graalvm.polyglot.Value;
50+
import org.junit.Assert;
51+
import org.junit.Test;
52+
import org.junit.runner.RunWith;
53+
import org.junit.runners.Parameterized;
54+
import org.junit.runners.Parameterized.Parameter;
55+
import org.junit.runners.Parameterized.Parameters;
56+
57+
import com.oracle.truffle.js.lang.JavaScriptLanguage;
58+
import com.oracle.truffle.js.runtime.JSContextOptions;
59+
import com.oracle.truffle.js.runtime.util.UTS35Validator;
60+
61+
@RunWith(Parameterized.class)
62+
public class LocaleTest {
63+
64+
@Parameters(name = "{0}")
65+
66+
public static List<String> localeIds() {
67+
return List.of("",
68+
// well-formed
69+
"und",
70+
"cs",
71+
"cs-CZ",
72+
"de-AT",
73+
"gsw-u-sd-chzh",
74+
"en-t-jp",
75+
"en-x-private",
76+
"x-abc",
77+
"und-x-abc",
78+
"i-enochian",
79+
"und-x-i-enochian",
80+
"tr",
81+
"lt",
82+
// ill-formed
83+
"de-", // Empty subtag
84+
"de_AT", // Invalid subtag
85+
"en-US-u-unsupported-xyz", // Incomplete extension 'u'
86+
"en-u-ca-unsupported", // Invalid subtag: unsupported
87+
"root" // not well-formed but accepted by setLanguageTag
88+
);
89+
}
90+
91+
@Parameter(value = 0) public String localeId;
92+
93+
@Test
94+
public void testInput() {
95+
boolean wellFormed = localeId.isEmpty() || UTS35Validator.isWellFormedUnicodeBCP47LocaleIdentifier(localeId);
96+
if (wellFormed) {
97+
// Should accept a superset of well-formed Unicode BCP 47 Locale Identifiers.
98+
Locale locale = !localeId.isEmpty() ? new Locale.Builder().setLanguageTag(localeId).build() : Locale.getDefault();
99+
100+
try (Context context = Context.newBuilder(JavaScriptLanguage.ID).option(JSContextOptions.LOCALE_NAME, localeId).build()) {
101+
String upperCase = "HI\u00ccJ\u0303";
102+
Value result = context.eval("js", "'" + upperCase + "'.toLocaleLowerCase()");
103+
Assert.assertEquals(upperCase.toLowerCase(locale), result.asString());
104+
}
105+
} else {
106+
assertThrows(IllegalArgumentException.class, () -> {
107+
try (Context context = Context.newBuilder(JavaScriptLanguage.ID).option(JSContextOptions.LOCALE_NAME, localeId).build()) {
108+
context.initialize(JavaScriptLanguage.ID);
109+
}
110+
});
111+
}
112+
}
113+
}

graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/JSContextOptions.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
import com.oracle.truffle.api.Option;
6868
import com.oracle.truffle.api.TruffleOptionDescriptors;
6969
import com.oracle.truffle.js.lang.JavaScriptLanguage;
70+
import com.oracle.truffle.js.runtime.util.UTS35Validator;
7071

7172
/**
7273
* Defines, and provides access to, all JS (context and language) options.
@@ -439,8 +440,18 @@ public Map<String, String> apply(String value) {
439440
@CompilationFinal private boolean validateRegExpLiterals;
440441

441442
public static final String LOCALE_NAME = JS_OPTION_PREFIX + "locale";
442-
@Option(name = LOCALE_NAME, category = OptionCategory.EXPERT, usageSyntax = "<locale>", help = "Use a specific default locale for locale-sensitive operations.") //
443-
public static final OptionKey<String> LOCALE = new OptionKey<>("");
443+
@Option(name = LOCALE_NAME, category = OptionCategory.EXPERT, stability = OptionStability.STABLE, sandbox = SandboxPolicy.UNTRUSTED, usageSyntax = "<locale>", //
444+
help = "Set the default locale (Unicode BCP 47 locale identifier) to be used for locale-sensitive operations. If empty, Locale.getDefault() will be used.") //
445+
public static final OptionKey<String> LOCALE = new OptionKey<>("", new OptionType<>("Locale", new Function<>() {
446+
@Override
447+
public String apply(String tag) {
448+
if (tag.isEmpty() || UTS35Validator.isWellFormedUnicodeBCP47LocaleIdentifier(tag)) {
449+
return tag;
450+
} else {
451+
throw new IllegalArgumentException(String.format("Language tag is not well-formed: %s", tag));
452+
}
453+
}
454+
}));
444455

445456
public static final String FUNCTION_CONSTRUCTOR_CACHE_SIZE_NAME = JS_OPTION_PREFIX + "function-constructor-cache-size";
446457
@Option(name = FUNCTION_CONSTRUCTOR_CACHE_SIZE_NAME, category = OptionCategory.EXPERT, usageSyntax = "<int>", help = "Maximum size of the parsing cache used by the Function constructor to avoid re-parsing known sources.") //

graal-js/src/com.oracle.truffle.js/src/com/oracle/truffle/js/runtime/util/UTS35Validator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,15 +189,15 @@ private static String unicodeLocaleID() {
189189

190190
private static String extensions() {
191191
// extensions = unicode_locale_extensions | transformed_extensions | other_extensions
192-
return group(unicodeLocaleExtensions() + "|" + transformedExtentensions() + "|" + otherExtensions());
192+
return group(unicodeLocaleExtensions() + "|" + transformedExtensions() + "|" + otherExtensions());
193193
}
194194

195195
private static String unicodeLocaleExtensions() {
196196
// unicode_locale_extensions = sep [uU] ((sep keyword)+ |(sep attribute)+ (sep keyword)*)
197197
return group(sep() + "[uU]" + group(group(sep() + keyword()) + "+|" + group(sep() + attribute()) + "+" + group(sep() + keyword()) + "*"));
198198
}
199199

200-
private static String transformedExtentensions() {
200+
private static String transformedExtensions() {
201201
// transformed_extensions = sep [tT] ((sep tlang (sep tfield)*) | (sep tfield)+)
202202
return group(sep() + "[tT]" + group(group(sep() + tLang() + group(sep() + tField()) + "*") + "|" + group(sep() + tField()) + "+"));
203203
}

0 commit comments

Comments
 (0)