Skip to content

Commit

Permalink
[GR-21741] Fixes support for timezones.
Browse files Browse the repository at this point in the history
PullRequest: graal/5655
  • Loading branch information
Esteban Ginez committed Apr 7, 2020
2 parents e045810 + d999203 commit 03d7d0c
Show file tree
Hide file tree
Showing 8 changed files with 881 additions and 55 deletions.
1 change: 1 addition & 0 deletions sdk/src/org.graalvm.nativeimage/snapshot.sigtest
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,7 @@ CLSS public abstract interface !annotation org.graalvm.nativeimage.c.function.CL
anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[TYPE, METHOD])
intf java.lang.annotation.Annotation
meth public abstract !hasdefault boolean requireStatic()
meth public abstract !hasdefault java.lang.String[] dependsOn()
meth public abstract java.lang.String value()

CLSS public abstract interface !annotation org.graalvm.nativeimage.c.function.CMacroInfo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,11 @@
* @since 19.1.0
*/
boolean requireStatic() default false;

/**
* Specifies the name of the libraries this library depends on.
*
* @since 20.1.0
*/
String[] dependsOn() default {};
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,16 @@
import org.graalvm.nativeimage.c.function.CFunction;
import org.graalvm.nativeimage.c.function.CFunction.Transition;
import org.graalvm.nativeimage.c.function.CLibrary;
import org.graalvm.nativeimage.c.type.CCharPointer;
import org.graalvm.nativeimage.c.type.CCharPointerPointer;

@CLibrary(value = "libchelper", requireStatic = true)
@CLibrary(value = "libchelper", requireStatic = true, dependsOn = "java")
public class LibCHelper {
@CFunction(transition = Transition.NO_TRANSITION)
public static native CCharPointerPointer getEnviron();

@CFunction(transition = Transition.TO_NATIVE)
// Checkstyle: stop
public static native CCharPointer SVM_FindJavaTZmd(CCharPointer tzMappings, int length);
// Checkstyle: start
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,84 +24,165 @@
*/
package com.oracle.svm.core.jdk;

import static java.util.stream.Collectors.toMap;

import java.util.Arrays;
import java.util.Map;
import java.util.TimeZone;

import org.graalvm.compiler.options.Option;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.nativeimage.ImageSingletons;

import com.oracle.svm.core.LibCHelper;
import com.oracle.svm.core.OS;
import com.oracle.svm.core.annotate.Alias;
import com.oracle.svm.core.annotate.AutomaticFeature;
import com.oracle.svm.core.annotate.RecomputeFieldValue;
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.util.VMError;
import org.graalvm.collections.EconomicMap;
import org.graalvm.compiler.options.Option;
import org.graalvm.compiler.options.OptionKey;

import com.oracle.svm.core.annotate.TargetClass;
import com.oracle.svm.core.option.HostedOptionKey;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.PinnedObject;
import org.graalvm.nativeimage.c.type.CCharPointer;
import org.graalvm.nativeimage.c.type.CTypeConversion;
import org.graalvm.nativeimage.hosted.Feature;
import org.graalvm.word.WordFactory;

final class TimeZoneSupport {

final Map<String, TimeZone> zones;
TimeZone defaultZone;

TimeZoneSupport(Map<String, TimeZone> zones, TimeZone defaultZone) {
this.zones = zones;
this.defaultZone = defaultZone;
}

public static TimeZoneSupport instance() {
return ImageSingletons.lookup(TimeZoneSupport.class);
}
}
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.TimeZone;

/**
* The following classes aim to provide full support for time zones for native-image. This
* substitution is necessary due to the reliance on JAVA_HOME in the JDK.
*
* In summary in the JDK time zone data is extracted from the underlying platform via native
* methods, later the JDK code processes that data to create time zone objects exposed via JAVA
* APIs.
*
* Luckily JAVA_HOME is only really necessary for the Windows operating system. Posix operating
* systems rely on system calls independent of JAVA_HOME (except for null checks done in the JNI
* function wrapping the native time zone JDK functions). Thus for Posix operating systems this
* implementation, simply, by-passes the null check(in native image JAVA_HOME is null) and calls
* into the original native functions by substituting the JNI method
* TimeZone.getSystemTimeZoneID(String).
*
* In Windows, the JRE contains a special file called tzmappings, (see
* <a href="https://docs.oracle.com/javase/9/troubleshoot/time-zone-settings-jre.htm#JSTGD359">time
* zones in the jre</a>), representing the mapping between Windows and Java time zones. The
* tzmappings file is read and parsed in the native JDK code. Thus for windows,
* {@link TimeZoneFeature} reads such file and stores it in the image heap at build-time. At
* run-time the contents of the file are passed to a custom implementation of the JDK time zones
* logic. The only difference in the custom implementation is reading and parsing time zone mappings
* from a buffer as opposed to a file.
*/
@TargetClass(java.util.TimeZone.class)
@SuppressWarnings("unused")
final class Target_java_util_TimeZone {

@Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) private static TimeZone defaultTimeZone;

@Substitute
private static TimeZone getDefaultRef() {
return TimeZoneSupport.instance().defaultZone;
private static String getSystemTimeZoneID(String javaHome) {
CCharPointer tzMappingsPtr = WordFactory.nullPointer();
int contentLen = 0;
PinnedObject pinnedContent = null;
try {
if (ImageSingletons.contains(TimeZoneSupport.class)) {
byte[] content = ImageSingletons.lookup(TimeZoneSupport.class).getTzMappingsContent();
contentLen = content.length;
pinnedContent = PinnedObject.create(content);
tzMappingsPtr = pinnedContent.addressOfArrayElement(0);
}
CCharPointer tzId = LibCHelper.SVM_FindJavaTZmd(tzMappingsPtr, contentLen);
return CTypeConversion.toJavaString(tzId);
} finally {
if (pinnedContent != null) {
pinnedContent.close();
}
}
}
}

@Substitute
private static void setDefault(TimeZone zone) {
TimeZoneSupport.instance().defaultZone = zone;
/**
* Holds time zone mapping data.
*/
final class TimeZoneSupport {
final byte[] tzMappingsContent;

TimeZoneSupport(final byte[] content) {
this.tzMappingsContent = content;
}

@Substitute
public static TimeZone getTimeZone(String id) {
return TimeZoneSupport.instance().zones.getOrDefault(id, TimeZoneSupport.instance().zones.get("GMT"));
public byte[] getTzMappingsContent() {
return tzMappingsContent;
}
}

/**
* Reads time zone mappings data and stores in the image heap, if necessary.
*/
@AutomaticFeature
final class TimeZoneFeature implements Feature {
static class Options {
private static final TimeZone defaultZone = TimeZone.getDefault();

@Option(help = "When true, all time zones will be pre-initialized in the image.")//
public static final HostedOptionKey<Boolean> IncludeAllTimeZones = new HostedOptionKey<>(false);
public static final HostedOptionKey<Boolean> IncludeAllTimeZones = new HostedOptionKey<Boolean>(false) {
@Override
protected void onValueUpdate(EconomicMap<OptionKey<?>, Object> values, Boolean oldValue, Boolean newValue) {
super.onValueUpdate(values, oldValue, newValue);
printWarning();
}
};

@Option(help = "The time zones, in addition to the default zone of the host, that will be pre-initialized in the image.")//
public static final HostedOptionKey<String[]> IncludeTimeZones = new HostedOptionKey<>(new String[]{"GMT", "UTC", defaultZone.getID()});
public static final HostedOptionKey<String> IncludeTimeZones = new HostedOptionKey<String>("") {
@Override
protected void onValueUpdate(EconomicMap<OptionKey<?>, Object> values, String oldValue, String newValue) {
super.onValueUpdate(values, oldValue, newValue);
printWarning();
}
};

private static void printWarning() {
// Checkstyle: stop
System.err.println("-H:IncludeAllTimeZones and -H:IncludeTimeZones are now deprecated. Native-image includes all timezones" +
"by default.");
// Checkstyle: resume
}
}

private static byte[] cleanCR(byte[] buffer) {
byte[] scratch = new byte[buffer.length];
int copied = 0;
for (byte b : buffer) {
if (b == '\r') {
continue;
}
scratch[copied++] = b;
}
byte[] content = new byte[copied];
System.arraycopy(scratch, 0, content, 0, copied);
return content;
}

@Override
public void afterRegistration(AfterRegistrationAccess access) {
final String[] supportedZoneIDs;
if (Options.IncludeAllTimeZones.getValue()) {
supportedZoneIDs = TimeZone.getAvailableIDs();
} else {
supportedZoneIDs = Options.IncludeTimeZones.getValue();

if (OS.getCurrent() != OS.WINDOWS) {
return;
}

// read tzmappings on windows
Path tzMappingsPath = Paths.get(System.getProperty("java.home"), "lib", "tzmappings");
try {
byte[] buffer = Files.readAllBytes(tzMappingsPath);
// tzmappings has windows line endings on windows??
byte[] content = cleanCR(buffer);
ImageSingletons.add(TimeZoneSupport.class, new TimeZoneSupport(content));
} catch (IOException e) {
VMError.shouldNotReachHere("Failed to read time zone mappings. The time zone mappings should be part" +
"of your JDK usually found: " + tzMappingsPath.toAbsolutePath(), e);
}
Map<String, TimeZone> supportedZones = Arrays.stream(supportedZoneIDs)
.map(TimeZone::getTimeZone)
.collect(toMap(TimeZone::getID, tz -> tz, (tz1, tz2) -> tz1));
ImageSingletons.add(TimeZoneSupport.class, new TimeZoneSupport(supportedZones, Options.defaultZone));
}
}

/**
* This whole file should be eventually removed: GR-11844.
*/
public class TimeZoneSubstitutions {
class TimeZoneSubstitutions {
}
Loading

0 comments on commit 03d7d0c

Please sign in to comment.