Skip to content

Commit 8e85bcb

Browse files
committed
Check undefined system properties
Many system properties are undefined in native image. Getting such properties with System.getProperty returns null. It may result unexpected behaviors. This patch checks the usage of such properties at native image build, and throw exception at run time if -R:+ReportUndefinedSystemPropertyError option is set.
1 parent 505ac8d commit 8e85bcb

File tree

3 files changed

+252
-48
lines changed

3 files changed

+252
-48
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SystemPropertiesSupport.java

Lines changed: 144 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,12 @@
2828
import java.util.HashMap;
2929
import java.util.Map;
3030
import java.util.Properties;
31+
import java.util.concurrent.ConcurrentHashMap;
32+
import java.util.function.BooleanSupplier;
3133
import java.util.function.Supplier;
3234

35+
import com.oracle.svm.core.option.RuntimeOptionKey;
36+
import org.graalvm.compiler.options.Option;
3337
import org.graalvm.nativeimage.ImageInfo;
3438
import org.graalvm.nativeimage.Platform;
3539
import org.graalvm.nativeimage.Platforms;
@@ -41,32 +45,109 @@
4145
* This class maintains the system properties at run time.
4246
*
4347
* Some of the standard system properties can just be taken from the image generator:
44-
* {@link #HOSTED_PROPERTIES}. Other important system properties need to be computed at run time.
48+
* Other important system properties need to be computed at run time.
4549
* However, we want to do the computation lazily to reduce the startup cost. For example, getting
4650
* the current working directory is quite expensive. We initialize such a property either when it is
4751
* explicitly accessed, or when all properties are accessed.
4852
*/
4953
public abstract class SystemPropertiesSupport {
5054

51-
/** System properties that are taken from the VM hosting the image generator. */
52-
private static final String[] HOSTED_PROPERTIES = {
53-
"java.version",
54-
ImageInfo.PROPERTY_IMAGE_KIND_KEY,
55-
/*
56-
* We do not support cross-compilation for now. Separator might also be cached
57-
* in other classes, so changing them would be tricky.
58-
*/
59-
"line.separator", "path.separator", "file.separator",
60-
/* For our convenience for now. */
61-
"file.encoding", "sun.jnu.encoding",
62-
"java.class.version",
63-
"java.specification.name",
64-
"java.specification.vendor",
65-
"java.specification.version",
66-
"java.vm.specification.name",
67-
"java.vm.specification.vendor",
68-
"java.vm.specification.version"
69-
};
55+
public static class SystemPropertyFeatureOptions{
56+
@Option(help = "Report the usage of undefined property as exception when it is not explicitly set)")//
57+
public static final RuntimeOptionKey<Boolean> ReportUndefinedSystemPropertyError = new RuntimeOptionKey<>(false);
58+
}
59+
60+
private enum InitKind {AsHosted, Runtime, BuildTime, Undefined}
61+
62+
private static class PropertyInfo {
63+
private String name;
64+
private BooleanSupplier onlyWith;
65+
private InitKind initKind;
66+
private Supplier<String> initValue;
67+
68+
public PropertyInfo(String name, BooleanSupplier onlyWith, InitKind InitKind, Supplier<String> initValue) {
69+
if (name == null) {
70+
throw new NullPointerException("Property name should never be null!");
71+
}
72+
this.name = name;
73+
this.onlyWith = onlyWith;
74+
this.initKind = InitKind;
75+
this.initValue = initValue;
76+
}
77+
78+
}
79+
80+
private static final Map<String, PropertyInfo> SYSTEM_PROPERTIES= new ConcurrentHashMap<>();
81+
82+
private static final JDK8OrEarlier jdk8OrEarlier = new JDK8OrEarlier();
83+
private static final JDK11OrLater jdk11OrLater = new JDK11OrLater();
84+
private static final BooleanSupplier always = ()-> true;
85+
private static final Supplier<String> STRING_SUPPLIER_SUPPLIER = () -> "";
86+
87+
static {
88+
//Set properties same as hosted
89+
addAsHostedSystemProperty("file.encoding", always);
90+
// https://github.com/openjdk/jdk/commit/d4941f14af150fb81db7cff5394192637be701dd
91+
addAsHostedSystemProperty("file.encoding.pkg", jdk8OrEarlier);
92+
addAsHostedSystemProperty("file.separator", always);
93+
addAsHostedSystemProperty("java.class.version", always);
94+
addAsHostedSystemProperty("java.runtime.version", always);
95+
addAsHostedSystemProperty("java.specification.name", always);
96+
addAsHostedSystemProperty("java.specification.vendor", always);
97+
addAsHostedSystemProperty("java.specification.version", always);
98+
addAsHostedSystemProperty("java.version", always);
99+
// https://openjdk.java.net/jeps/322
100+
addAsHostedSystemProperty("java.version.date", jdk11OrLater);
101+
addAsHostedSystemProperty("java.vendor.version", jdk11OrLater);
102+
addAsHostedSystemProperty("java.vm.specification.name", always);
103+
addAsHostedSystemProperty("java.vm.specification.vendor", always);
104+
addAsHostedSystemProperty("java.vm.specification.version", always);
105+
addAsHostedSystemProperty("line.separator", always);
106+
addAsHostedSystemProperty("path.separator", always);
107+
addAsHostedSystemProperty("sun.cpu.endian", always);
108+
addAsHostedSystemProperty("sun.cpu.isalist", always);
109+
addAsHostedSystemProperty("sun.jnu.encoding", always);
110+
addAsHostedSystemProperty("sun.io.unicode.encoding", always);
111+
addAsHostedSystemProperty(ImageInfo.PROPERTY_IMAGE_KIND_KEY, always);
112+
113+
//Undefined properties
114+
addUndefinedSystemProperty("awt.toolkit", always);
115+
addUndefinedSystemProperty("java.awt.graphicsenv", always);
116+
addUndefinedSystemProperty("java.awt.printerjob", always);
117+
addUndefinedSystemProperty("java.home", always);
118+
addUndefinedSystemProperty("sun.java.command", always);
119+
addUndefinedSystemProperty("sun.java.launcher", always);
120+
addUndefinedSystemProperty("sun.os.patch.level", always);
121+
addUndefinedSystemProperty("user.country", always);
122+
addUndefinedSystemProperty("user.language", always);
123+
addUndefinedSystemProperty("user.timezone", always);
124+
// http://hg.openjdk.java.net/jdk9/jdk9/hotspot/rev/3b241fb72b89
125+
addUndefinedSystemProperty("java.vm.compressedOopsMode", jdk11OrLater);
126+
// http://hg.openjdk.java.net/jdk9/jdk9/hotspot/rev/39c579b50006
127+
addUndefinedSystemProperty("jdk.debug", jdk11OrLater);
128+
129+
// Build time initialized properties
130+
addSystemProperty("java.class.path", always, InitKind.BuildTime, STRING_SUPPLIER_SUPPLIER);
131+
// http://hg.openjdk.java.net/jdk9/jdk9/jdk/rev/e336cbd8b15e
132+
addSystemProperty("java.endorsed.dirs", jdk8OrEarlier, InitKind.BuildTime, STRING_SUPPLIER_SUPPLIER);
133+
addSystemProperty("java.ext.dirs", jdk8OrEarlier, InitKind.BuildTime, STRING_SUPPLIER_SUPPLIER);
134+
addSystemProperty("java.runtime.name", always, InitKind.BuildTime, ()->"Substrate VM Runtime Environment");
135+
addSystemProperty("java.vendor", always, InitKind.BuildTime, ()->"Oracle Corporation");
136+
addSystemProperty("java.vendor.url.bug", always, InitKind.BuildTime, ()->"https://github.com/oracle/graal/issues");
137+
addSystemProperty("java.vm.info", always, InitKind.BuildTime, ()->"static mode");
138+
addSystemProperty("java.vm.name", always, InitKind.BuildTime, () ->"Substrate VM");
139+
addSystemProperty("java.vm.vendor", always, InitKind.BuildTime, ()->"Oracle Corporation");
140+
addSystemProperty("java.vendor.url", always, InitKind.BuildTime, ()->"https://www.graalvm.org/");
141+
String targetArch = System.getProperty("svm.targetArch");
142+
addSystemProperty("os.arch", always, InitKind.BuildTime, ()->targetArch != null ? targetArch : System.getProperty("os.arch"));
143+
String targetName = System.getProperty("svm.targetName");
144+
addSystemProperty("os.name", always, InitKind.BuildTime, ()->targetName != null ? targetName : System.getProperty("os.name"));
145+
addSystemProperty("sun.arch.data.model", always, InitKind.BuildTime, ()->Integer.toString(ConfigurationValues.getTarget().wordJavaKind.getBitCount()));
146+
// http://openjdk.java.net/jeps/261 http://hg.openjdk.java.net/jdk9/jdk9/hotspot/rev/c558850fac57
147+
addSystemProperty("sun.boot.class.path", jdk8OrEarlier, InitKind.BuildTime, STRING_SUPPLIER_SUPPLIER);
148+
addSystemProperty("sun.management.compiler", always, InitKind.BuildTime,()-> "GraalVM Compiler");
149+
addSystemProperty(ImageInfo.PROPERTY_IMAGE_CODE_KEY, always, InitKind.BuildTime, ()->ImageInfo.PROPERTY_IMAGE_CODE_VALUE_RUNTIME);
150+
}
70151

71152
/** System properties that are lazily computed at run time on first access. */
72153
private final Map<String, Supplier<String>> lazyRuntimeValues;
@@ -83,37 +164,43 @@ protected SystemPropertiesSupport() {
83164
savedProperties = new HashMap<>();
84165
readOnlySavedProperties = Collections.unmodifiableMap(savedProperties);
85166

86-
for (String key : HOSTED_PROPERTIES) {
87-
String value = System.getProperty(key);
88-
properties.put(key, value);
89-
savedProperties.put(key, value);
90-
}
91-
92-
initializeProperty("java.vm.name", "Substrate VM");
93-
initializeProperty("java.vm.vendor", "Oracle Corporation");
94-
initializeProperty("java.vendor", "Oracle Corporation");
95-
initializeProperty("java.vendor.url", "https://www.graalvm.org/");
167+
// Runtime initialized properties
168+
addSystemProperty("user.name", always, InitKind.Runtime, this::userName);
169+
addSystemProperty("user.home", always, InitKind.Runtime, this::userHome);
170+
addSystemProperty("user.dir", always, InitKind.Runtime, this::userDir);
171+
addSystemProperty("java.io.tmpdir", always, InitKind.Runtime, this::tmpdirValue);
172+
addSystemProperty("os.version", always, InitKind.Runtime, this::osVersionValue);
173+
addSystemProperty("java.vm.version", always, InitKind.Runtime, VM::getVersion);
96174

97-
initializeProperty("java.class.path", "");
98-
initializeProperty("java.endorsed.dirs", "");
99-
initializeProperty("java.ext.dirs", "");
100-
initializeProperty("java.library.path", "");
101-
initializeProperty("sun.arch.data.model", Integer.toString(ConfigurationValues.getTarget().wordJavaKind.getBitCount()));
175+
lazyRuntimeValues = new HashMap<>();
176+
SYSTEM_PROPERTIES.forEach((key, propertyInfo) -> {
177+
if (propertyInfo.onlyWith.getAsBoolean()) {
178+
switch (propertyInfo.initKind){
179+
case AsHosted:
180+
initializeProperty(key, System.getProperty(key));
181+
break;
182+
case BuildTime:
183+
initializeProperty(key, propertyInfo.initValue.get());
184+
break;
185+
case Undefined:
186+
break;
187+
case Runtime:
188+
lazyRuntimeValues.put(key, propertyInfo.initValue);
189+
}
190+
}
191+
});
192+
}
102193

103-
String targetName = System.getProperty("svm.targetName");
104-
String targetArch = System.getProperty("svm.targetArch");
105-
initializeProperty("os.name", targetName != null ? targetName : System.getProperty("os.name"));
106-
initializeProperty("os.arch", targetArch != null ? targetArch : System.getProperty("os.arch"));
194+
private static void addSystemProperty(String name, BooleanSupplier onlyWith, InitKind InitKind, Supplier<String> initValue){
195+
SYSTEM_PROPERTIES.put(name,new PropertyInfo(name, onlyWith, InitKind, initValue));
196+
}
107197

108-
initializeProperty(ImageInfo.PROPERTY_IMAGE_CODE_KEY, ImageInfo.PROPERTY_IMAGE_CODE_VALUE_RUNTIME);
198+
private static void addUndefinedSystemProperty(String name, BooleanSupplier onlyWith) {
199+
SYSTEM_PROPERTIES.put(name,new PropertyInfo(name, onlyWith, InitKind.Undefined, null));
200+
}
109201

110-
lazyRuntimeValues = new HashMap<>();
111-
lazyRuntimeValues.put("user.name", this::userName);
112-
lazyRuntimeValues.put("user.home", this::userHome);
113-
lazyRuntimeValues.put("user.dir", this::userDir);
114-
lazyRuntimeValues.put("java.io.tmpdir", this::tmpdirValue);
115-
lazyRuntimeValues.put("os.version", this::osVersionValue);
116-
lazyRuntimeValues.put("java.vm.version", VM::getVersion);
202+
private static void addAsHostedSystemProperty(String name, BooleanSupplier onlyWith){
203+
SYSTEM_PROPERTIES.put(name,new PropertyInfo(name, onlyWith, InitKind.AsHosted, null));
117204
}
118205

119206
private void ensureFullyInitialized() {
@@ -141,7 +228,16 @@ public Properties getProperties() {
141228

142229
protected String getProperty(String key) {
143230
initializeLazyValue(key);
144-
return properties.getProperty(key);
231+
String ret = properties.getProperty(key);
232+
if (ret == null && isUndefined(key) && SystemPropertyFeatureOptions.ReportUndefinedSystemPropertyError.getValue()) {
233+
throw new UndefinedSystemPropertyException("Java system property " + key + " is undefined in native image. Please explicitly assign the expected value via -D" + key + "=, or avoid using this property.");
234+
}
235+
return ret;
236+
}
237+
238+
public static boolean isUndefined(String key) {
239+
PropertyInfo propertyInfo = SYSTEM_PROPERTIES.get(key);
240+
return propertyInfo != null && propertyInfo.initKind == InitKind.Undefined;
145241
}
146242

147243
public void setProperties(Properties props) {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
3+
* Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved.
4+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5+
*
6+
* This code is free software; you can redistribute it and/or modify it
7+
* under the terms of the GNU General Public License version 2 only, as
8+
* published by the Free Software Foundation. Oracle designates this
9+
* particular file as subject to the "Classpath" exception as provided
10+
* by Oracle in the LICENSE file that accompanied this code.
11+
*
12+
* This code is distributed in the hope that it will be useful, but WITHOUT
13+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15+
* version 2 for more details (a copy is included in the LICENSE file that
16+
* accompanied this code).
17+
*
18+
* You should have received a copy of the GNU General Public License version
19+
* 2 along with this work; if not, write to the Free Software Foundation,
20+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21+
*
22+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23+
* or visit www.oracle.com if you need additional information or have any
24+
* questions.
25+
*/
26+
package com.oracle.svm.core.jdk;
27+
28+
public class UndefinedSystemPropertyException extends RuntimeException{
29+
public UndefinedSystemPropertyException(String message){
30+
super(message);
31+
}
32+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
3+
* Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved.
4+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5+
*
6+
* This code is free software; you can redistribute it and/or modify it
7+
* under the terms of the GNU General Public License version 2 only, as
8+
* published by the Free Software Foundation. Oracle designates this
9+
* particular file as subject to the "Classpath" exception as provided
10+
* by Oracle in the LICENSE file that accompanied this code.
11+
*
12+
* This code is distributed in the hope that it will be useful, but WITHOUT
13+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15+
* version 2 for more details (a copy is included in the LICENSE file that
16+
* accompanied this code).
17+
*
18+
* You should have received a copy of the GNU General Public License version
19+
* 2 along with this work; if not, write to the Free Software Foundation,
20+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21+
*
22+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23+
* or visit www.oracle.com if you need additional information or have any
24+
* questions.
25+
*/
26+
package com.oracle.svm.hosted;
27+
28+
import com.oracle.svm.core.annotate.AutomaticFeature;
29+
import com.oracle.svm.core.graal.GraalFeature;
30+
import com.oracle.svm.core.jdk.SystemPropertiesSupport;
31+
import jdk.vm.ci.meta.ResolvedJavaMethod;
32+
import org.graalvm.compiler.api.replacements.SnippetReflectionProvider;
33+
import org.graalvm.compiler.nodes.ValueNode;
34+
import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext;
35+
import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugin;
36+
import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugins;
37+
import org.graalvm.compiler.phases.util.Providers;
38+
39+
import java.util.Set;
40+
import java.util.concurrent.ConcurrentHashMap;
41+
42+
@AutomaticFeature
43+
public class SystemPropertyFeature implements GraalFeature {
44+
45+
private final Set<String> systemPropertyCalls = ConcurrentHashMap.newKeySet();
46+
47+
@Override
48+
public void afterImageWrite(AfterImageWriteAccess access) {
49+
if (!systemPropertyCalls.isEmpty()) {
50+
StringBuilder sb = new StringBuilder("\n\nNative image undefined system properties are used in the program:\n");
51+
for (String s : systemPropertyCalls) {
52+
sb.append(" ").append(s).append("\n");
53+
}
54+
sb.append("The returned value of calling System.getProperty with these keys will be null in native image.\n");
55+
sb.append("Please make sure the expected property values are specified with -D when running the native image program or just avoid using them.\n\n");
56+
System.out.println(sb.toString());
57+
}
58+
}
59+
60+
@Override
61+
public void registerInvocationPlugins(Providers providers, SnippetReflectionProvider snippetReflection, InvocationPlugins invocationPlugins, boolean analysis, boolean hosted) {
62+
InvocationPlugins.Registration r = new InvocationPlugins.Registration(invocationPlugins, System.class);
63+
r.register1("getProperty", String.class, new InvocationPlugin() {
64+
@Override
65+
public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode name) {
66+
if (name.isConstant()) {
67+
String propertyKey = snippetReflection.asObject(String.class, name.asJavaConstant());
68+
if (SystemPropertiesSupport.isUndefined(propertyKey)) {
69+
systemPropertyCalls.add("Undefined property: " + propertyKey + " is used at " + b.getCode().asStackTraceElement(b.bci()));
70+
}
71+
}
72+
return false;
73+
}
74+
});
75+
}
76+
}

0 commit comments

Comments
 (0)