Skip to content

Commit 6448b58

Browse files
Automatic merge of master into galahad
2 parents 1319571 + eca7e0b commit 6448b58

File tree

8 files changed

+221
-22
lines changed

8 files changed

+221
-22
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,8 +1441,8 @@ public enum ReportingMode {
14411441
public static final HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> Preserve = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build());
14421442

14431443
@Option(help = "Ignore classes or packages (comma separated) from the ones included with '-H:Preserve'. This can be used to workaround potential issues related to '-H:Preserve'.", type = OptionType.Debug) //
1444-
public static final HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> IgnorePreserveForClasses = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Strings.build());
1445-
1444+
public static final HostedOptionKey<AccumulatingLocatableMultiOptionValue.Strings> IgnorePreserveForClasses = new HostedOptionKey<>(
1445+
AccumulatingLocatableMultiOptionValue.Strings.buildWithCommaDelimiter());
14461446
@Option(help = "Force include include all public types and methods that can be reached using normal Java access rules.")//
14471447
public static final HostedOptionKey<Boolean> UseBaseLayerInclusionPolicy = new HostedOptionKey<>(false);
14481448

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright (c) 2025, 2025, 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. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.oracle.svm.core.util;
26+
27+
import com.oracle.svm.core.OS;
28+
29+
import java.util.HashSet;
30+
import java.util.Locale;
31+
import java.util.Map;
32+
import java.util.Set;
33+
34+
public class EnvVariableUtils {
35+
private static final Set<String> REQUIRED_ENV_VARIABLE_KEYS_COMMON = Set.of(
36+
"PATH",
37+
"PWD",
38+
"HOME",
39+
"LANG",
40+
"LANGUAGE");
41+
42+
private static final Set<String> REQUIRED_ENV_VARIABLE_KEYS_WINDOWS = Set.of(
43+
"TEMP",
44+
"INCLUDE",
45+
"LIB");
46+
47+
private static final Set<String> REQUIRED_ENV_VARIABLE_KEYS = getRequiredEnvVariableKeys();
48+
49+
private static Set<String> getRequiredEnvVariableKeys() {
50+
Set<String> requiredEnvVariableKeys = new HashSet<>(REQUIRED_ENV_VARIABLE_KEYS_COMMON);
51+
if (OS.WINDOWS.isCurrent()) {
52+
requiredEnvVariableKeys.addAll(REQUIRED_ENV_VARIABLE_KEYS_WINDOWS);
53+
}
54+
return requiredEnvVariableKeys;
55+
}
56+
57+
public record EnvironmentVariable(String key, String value) {
58+
public EnvironmentVariable {
59+
key = mapKey(key);
60+
}
61+
62+
public static EnvironmentVariable of(Map.Entry<String, String> entry) {
63+
return new EnvironmentVariable(entry.getKey(), entry.getValue());
64+
}
65+
66+
public static EnvironmentVariable of(String s) {
67+
String[] envVarArr = s.split("=", 2);
68+
return new EnvironmentVariable(envVarArr[0], envVarArr[1]);
69+
}
70+
71+
@Override
72+
public String toString() {
73+
return key + "=" + value;
74+
}
75+
76+
public boolean keyEquals(String otherKey) {
77+
return key.equals(mapKey(otherKey));
78+
}
79+
80+
public boolean isKeyRequired() {
81+
return isKeyRequiredCondition(key);
82+
}
83+
84+
public static boolean isKeyRequired(String key) {
85+
mapKey(key);
86+
return isKeyRequiredCondition(key);
87+
}
88+
89+
private static String mapKey(String key) {
90+
if (OS.WINDOWS.isCurrent()) {
91+
return key.toUpperCase(Locale.ROOT);
92+
}
93+
return key;
94+
}
95+
96+
private static boolean isKeyRequiredCondition(String key) {
97+
// LC_* are locale vars that override LANG for specific categories (or all, with LC_ALL)
98+
return REQUIRED_ENV_VARIABLE_KEYS.contains(key) || key.startsWith("LC_");
99+
}
100+
}
101+
}

substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/NativeImage.java

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
*/
2525
package com.oracle.svm.driver;
2626

27+
import static com.oracle.svm.core.util.EnvVariableUtils.EnvironmentVariable;
28+
2729
import java.io.BufferedReader;
2830
import java.io.File;
2931
import java.io.IOException;
@@ -1979,19 +1981,9 @@ private static void deprecatedSanitizeJVMEnvironment(Map<String, String> environ
19791981
}
19801982

19811983
private static void sanitizeJVMEnvironment(Map<String, String> environment, Map<String, String> imageBuilderEnvironment) {
1982-
Set<String> requiredKeys = new HashSet<>(List.of("PATH", "PWD", "HOME", "LANG", "LANGUAGE"));
1983-
Function<String, String> keyMapper;
1984-
if (OS.WINDOWS.isCurrent()) {
1985-
requiredKeys.addAll(List.of("TEMP", "INCLUDE", "LIB"));
1986-
keyMapper = s -> s.toUpperCase(Locale.ROOT);
1987-
} else {
1988-
keyMapper = Function.identity();
1989-
}
19901984
Map<String, String> restrictedEnvironment = new HashMap<>();
19911985
environment.forEach((key, val) -> {
1992-
String mappedKey = keyMapper.apply(key);
1993-
// LC_* are locale vars that override LANG for specific categories (or all, with LC_ALL)
1994-
if (requiredKeys.contains(mappedKey) || mappedKey.startsWith("LC_")) {
1986+
if (EnvironmentVariable.isKeyRequired(key)) {
19951987
restrictedEnvironment.put(key, val);
19961988
}
19971989
});
@@ -2000,8 +1992,9 @@ private static void sanitizeJVMEnvironment(Map<String, String> environment, Map<
20001992
if (entry.getValue() != null) {
20011993
restrictedEnvironment.put(entry.getKey(), entry.getValue());
20021994
} else {
1995+
EnvironmentVariable imageBuilderEnvironmentVariable = EnvironmentVariable.of(entry);
20031996
environment.forEach((key, val) -> {
2004-
if (keyMapper.apply(key).equals(keyMapper.apply(entry.getKey()))) {
1997+
if (imageBuilderEnvironmentVariable.keyEquals(key)) {
20051998
/*
20061999
* Record key as it was given by -E<key-name> (by using `entry.getKey()`
20072000
* instead of `key`) to allow creating bundles on Windows that will also

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/PreserveOptionsSupport.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,23 @@
2525
package com.oracle.svm.hosted.image;
2626

2727
import static com.oracle.graal.pointsto.api.PointstoOptions.UseConservativeUnsafeAccess;
28+
import static com.oracle.svm.core.SubstrateOptions.EnableURLProtocols;
2829
import static com.oracle.svm.core.SubstrateOptions.Preserve;
30+
import static com.oracle.svm.core.jdk.JRTSupport.Options.AllowJRTFileSystem;
31+
import static com.oracle.svm.hosted.SecurityServicesFeature.Options.AdditionalSecurityProviders;
32+
import static com.oracle.svm.hosted.jdk.localization.LocalizationFeature.Options.AddAllCharsets;
33+
import static com.oracle.svm.hosted.jdk.localization.LocalizationFeature.Options.IncludeAllLocales;
2934

3035
import java.lang.reflect.Constructor;
3136
import java.lang.reflect.Field;
3237
import java.lang.reflect.Method;
3338
import java.lang.reflect.Modifier;
39+
import java.security.Provider;
40+
import java.security.Security;
3441
import java.util.Arrays;
3542
import java.util.Comparator;
3643
import java.util.Set;
44+
import java.util.StringJoiner;
3745
import java.util.stream.Stream;
3846

3947
import org.graalvm.collections.EconomicMap;
@@ -139,9 +147,25 @@ public static void parsePreserveOption(EconomicMap<OptionKey<?>, Object> hostedV
139147
SubstrateOptionsParser.commandArgument(UseConservativeUnsafeAccess, "-"));
140148
}
141149
UseConservativeUnsafeAccess.update(hostedValues, true);
150+
151+
AddAllCharsets.update(hostedValues, true);
152+
IncludeAllLocales.update(hostedValues, true);
153+
AllowJRTFileSystem.update(hostedValues, true);
154+
EnableURLProtocols.update(hostedValues, "http,https,ftp,jar,file,mailto,jrt,jmod");
155+
AdditionalSecurityProviders.update(hostedValues, getSecurityProvidersCSV());
142156
}
143157
}
144158

159+
private static String getSecurityProvidersCSV() {
160+
StringJoiner joiner = new StringJoiner(",");
161+
for (Provider provider : Security.getProviders()) {
162+
Class<? extends Provider> aClass = provider.getClass();
163+
String typeName = aClass.getTypeName();
164+
joiner.add(typeName);
165+
}
166+
return joiner.toString();
167+
}
168+
145169
public static void registerPreservedClasses(NativeImageClassLoaderSupport classLoaderSupport) {
146170
var classesOrPackagesToIgnore = SubstrateOptions.IgnorePreserveForClasses.getValue().valuesAsSet();
147171
var classesToPreserve = classLoaderSupport.getClassesToPreserve()
@@ -221,7 +245,9 @@ public static void registerPreservedClasses(NativeImageClassLoaderSupport classL
221245
});
222246

223247
for (String className : classLoaderSupport.getClassNamesToPreserve()) {
224-
reflection.registerClassLookup(always, className);
248+
if (!classesOrPackagesToIgnore.contains(className)) {
249+
reflection.registerClassLookup(always, className);
250+
}
225251
}
226252
}
227253

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LayerArchiveSupport.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
*/
2525
package com.oracle.svm.hosted.imagelayer;
2626

27+
import static com.oracle.svm.core.util.EnvVariableUtils.EnvironmentVariable;
28+
2729
import java.io.IOException;
2830
import java.io.OutputStream;
2931
import java.nio.file.Files;
@@ -37,6 +39,7 @@
3739
import java.util.Properties;
3840

3941
import com.oracle.svm.core.OS;
42+
import com.oracle.svm.core.SharedConstants;
4043
import com.oracle.svm.core.SubstrateUtil;
4144
import com.oracle.svm.core.util.ArchiveSupport;
4245
import com.oracle.svm.core.util.UserError;
@@ -49,6 +52,7 @@ public class LayerArchiveSupport {
4952
private static final int LAYER_FILE_FORMAT_VERSION_MINOR = 1;
5053

5154
private static final String BUILDER_ARGUMENTS_FILE_NAME = "builder-arguments.txt";
55+
private static final String ENV_VARIABLES_FILE_NAME = "env-variables.txt";
5256
private static final String SNAPSHOT_FILE_NAME = "layer-snapshot.lsb";
5357
private static final String SNAPSHOT_GRAPHS_FILE_NAME = "layer-snapshot-graphs.big";
5458
private static final String LAYER_INFO_MESSAGE_PREFIX = "Native Image Layers";
@@ -121,6 +125,18 @@ protected Path getBuilderArgumentsFilePath() {
121125
return layerDir.resolve(BUILDER_ARGUMENTS_FILE_NAME);
122126
}
123127

128+
protected Path getEnvVariablesFilePath() {
129+
return layerDir.resolve(ENV_VARIABLES_FILE_NAME);
130+
}
131+
132+
protected List<EnvironmentVariable> parseEnvVariables() {
133+
return System.getenv().entrySet().stream()
134+
.map(EnvironmentVariable::of)
135+
.filter(envVar -> !envVar.isKeyRequired())
136+
.filter(envVar -> !envVar.keyEquals(SharedConstants.DRIVER_TEMP_DIR_ENV_VARIABLE))
137+
.toList();
138+
}
139+
124140
public final class LayerProperties {
125141

126142
private static final String PROPERTY_KEY_LAYER_FILE_VERSION_MAJOR = "LayerFileVersionMajor";

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/LoadLayerArchiveSupport.java

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@
2424
*/
2525
package com.oracle.svm.hosted.imagelayer;
2626

27+
import static com.oracle.svm.core.util.EnvVariableUtils.EnvironmentVariable;
28+
2729
import java.io.IOException;
2830
import java.nio.file.Files;
2931
import java.nio.file.Path;
3032
import java.util.ArrayList;
3133
import java.util.HashMap;
34+
import java.util.HashSet;
3235
import java.util.List;
3336
import java.util.Map;
3437
import java.util.Set;
@@ -42,6 +45,7 @@
4245
import com.oracle.svm.core.option.OptionOrigin;
4346
import com.oracle.svm.core.option.SubstrateOptionsParser;
4447
import com.oracle.svm.core.util.ArchiveSupport;
48+
import com.oracle.svm.core.util.EnvVariableUtils;
4549
import com.oracle.svm.core.util.UserError;
4650
import com.oracle.svm.core.util.VMError;
4751
import com.oracle.svm.hosted.NativeImageClassLoaderSupport;
@@ -69,6 +73,16 @@ private void loadBuilderArgumentsFile() {
6973
}
7074
}
7175

76+
private List<EnvironmentVariable> loadEnvironmentVariablesFile() {
77+
List<EnvironmentVariable> envVariables = new ArrayList<>();
78+
try (Stream<String> lines = Files.lines(getEnvVariablesFilePath())) {
79+
lines.map(EnvironmentVariable::of).forEach(envVariables::add);
80+
return envVariables;
81+
} catch (IOException e) {
82+
throw UserError.abort("Unable to load environment variables from file " + getEnvVariablesFilePath());
83+
}
84+
}
85+
7286
@Override
7387
protected void validateLayerFile() {
7488
super.validateLayerFile();
@@ -81,14 +95,15 @@ protected void validateLayerFile() {
8195
public void verifyCompatibility(NativeImageClassLoaderSupport classLoaderSupport, Map<String, OptionLayerVerificationRequests> allRequests, boolean strict, boolean verbose) {
8296
Function<String, String> filterFunction = argument -> splitArgumentOrigin(argument).argument;
8397
boolean violationsFound = false;
84-
violationsFound |= verifyCompatibility(builderArguments, classLoaderSupport.getHostedOptionParser().getArguments(), filterFunction, allRequests, strict, verbose, true);
85-
violationsFound |= verifyCompatibility(builderArguments, classLoaderSupport.getHostedOptionParser().getArguments(), filterFunction, allRequests, strict, verbose, false);
98+
violationsFound |= verifyBuilderArgumentsCompatibility(builderArguments, classLoaderSupport.getHostedOptionParser().getArguments(), filterFunction, allRequests, strict, verbose, true);
99+
violationsFound |= verifyBuilderArgumentsCompatibility(builderArguments, classLoaderSupport.getHostedOptionParser().getArguments(), filterFunction, allRequests, strict, verbose, false);
100+
violationsFound |= verifyEnvironmentVariablesCompatibility(loadEnvironmentVariablesFile(), parseEnvVariables(), strict, verbose);
86101
if (violationsFound && verbose) {
87102
UserError.abort("Verbose LayerOptionVerification failed.");
88103
}
89104
}
90105

91-
private static boolean verifyCompatibility(List<String> previousArgs, List<String> currentArgs, Function<String, String> filterFunction,
106+
private static boolean verifyBuilderArgumentsCompatibility(List<String> previousArgs, List<String> currentArgs, Function<String, String> filterFunction,
92107
Map<String, OptionLayerVerificationRequests> allRequests, boolean strict, boolean verbose, boolean positional) {
93108

94109
List<String> left;
@@ -203,6 +218,41 @@ private static boolean verifyCompatibility(List<String> previousArgs, List<Strin
203218
return violationsFound;
204219
}
205220

221+
/**
222+
* Verifies that the user-specified environment variables in the previous layered image build
223+
* are a subset of those used in the current layered image build. The environment variables
224+
* obtained via {@link System#getenv()} were previously filtered to include only the
225+
* user-specified variables for this verification. This filtering was performed in
226+
* {@link LayerArchiveSupport#parseEnvVariables()}, which removed system-dependent variables
227+
* defined in {@link EnvVariableUtils}. These excluded variables are required for image builds
228+
* but are not considered by this verification, since mismatches between these don't affect
229+
* layer compatibility.
230+
*/
231+
private static boolean verifyEnvironmentVariablesCompatibility(List<EnvironmentVariable> previousEnvVars, List<EnvironmentVariable> currentEnvVars, boolean strict, boolean verbose) {
232+
Set<EnvironmentVariable> currentEnvVarsSet = new HashSet<>(currentEnvVars);
233+
boolean violationsFound = false;
234+
235+
for (EnvironmentVariable previousEnvVar : previousEnvVars) {
236+
if (currentEnvVarsSet.contains(previousEnvVar)) {
237+
continue;
238+
}
239+
240+
violationsFound = true;
241+
String message = "Current layered image build must set environment variable " + previousEnvVar + " as it was set in the previous layer build.";
242+
if (verbose) {
243+
LogUtils.info("Error: ", message);
244+
} else {
245+
if (strict) {
246+
throw UserError.abort(message);
247+
} else {
248+
LogUtils.warning(message);
249+
}
250+
}
251+
}
252+
253+
return violationsFound;
254+
}
255+
206256
record ArgumentOrigin(boolean booleanOption, String argument, String origin) {
207257

208258
record NameValue(String name, String value) {

substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/imagelayer/WriteLayerArchiveSupport.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
*/
2525
package com.oracle.svm.hosted.imagelayer;
2626

27+
import static com.oracle.svm.core.util.EnvVariableUtils.EnvironmentVariable;
28+
2729
import java.io.IOException;
2830
import java.nio.file.Files;
2931
import java.nio.file.Path;
@@ -76,13 +78,24 @@ private void writeBuilderArgumentsFile() {
7678
}
7779
}
7880

81+
private void writeEnvVariablesFile() {
82+
try {
83+
Files.write(getEnvVariablesFilePath(), parseEnvVariables().stream().map(EnvironmentVariable::toString).toList());
84+
} catch (IOException e) {
85+
throw UserError.abort("Unable to write environment variables to file " + getEnvVariablesFilePath());
86+
}
87+
}
88+
7989
public void write() {
8090
try (JarOutputStream jarOutStream = new JarOutputStream(Files.newOutputStream(layerFile), archiveSupport.createManifest())) {
8191
// disable compression for significant (un)archiving speedup at the cost of file size
8292
jarOutStream.setLevel(0);
8393
// write builder arguments file and add to jar
8494
writeBuilderArgumentsFile();
8595
archiveSupport.addFileToJar(layerDir, getBuilderArgumentsFilePath(), layerFile, jarOutStream);
96+
// write environment variables file and add to jar
97+
writeEnvVariablesFile();
98+
archiveSupport.addFileToJar(layerDir, getEnvVariablesFilePath(), layerFile, jarOutStream);
8699
// copy the layer snapshot file and its graphs file to the jar
87100
archiveSupport.addFileToJar(layerDir, getSnapshotPath(), layerFile, jarOutStream);
88101
archiveSupport.addFileToJar(layerDir, getSnapshotGraphsPath(), layerFile, jarOutStream);

0 commit comments

Comments
 (0)