Skip to content

Commit 86f7b49

Browse files
committed
[GR-36601] Fix native-image resource URL handling.
PullRequest: graal/10958
2 parents df6066a + 2971e8a commit 86f7b49

File tree

6 files changed

+148
-76
lines changed

6 files changed

+148
-76
lines changed

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

Lines changed: 26 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@
2929
import java.io.InputStream;
3030
import java.net.MalformedURLException;
3131
import java.net.URL;
32-
import java.net.URLConnection;
33-
import java.net.URLStreamHandler;
3432
import java.nio.charset.StandardCharsets;
3533
import java.util.ArrayList;
3634
import java.util.Collections;
@@ -47,7 +45,6 @@
4745
import com.oracle.svm.core.annotate.AutomaticFeature;
4846
import com.oracle.svm.core.jdk.resources.NativeImageResourcePath;
4947
import com.oracle.svm.core.jdk.resources.ResourceStorageEntry;
50-
import com.oracle.svm.core.jdk.resources.ResourceURLConnection;
5148
import com.oracle.svm.core.util.ImageHeapMap;
5249
import com.oracle.svm.core.util.VMError;
5350

@@ -81,29 +78,11 @@ public EconomicMap<Pair<String, String>, ResourceStorageEntry> resources() {
8178
}
8279

8380
public static byte[] inputStreamToByteArray(InputStream is) {
84-
// TODO: Replace this with is.readAllBytes() once Java 8 support is removed
85-
byte[] arr = new byte[4096];
86-
int pos = 0;
8781
try {
88-
for (;;) {
89-
if (pos == arr.length) {
90-
byte[] tmp = new byte[arr.length * 2];
91-
System.arraycopy(arr, 0, tmp, 0, arr.length);
92-
arr = tmp;
93-
}
94-
int len = is.read(arr, pos, arr.length - pos);
95-
if (len == -1) {
96-
break;
97-
}
98-
pos += len;
99-
}
82+
return is.readAllBytes();
10083
} catch (IOException ex) {
10184
throw VMError.shouldNotReachHere(ex);
10285
}
103-
104-
byte[] data = new byte[pos];
105-
System.arraycopy(arr, 0, data, 0, pos);
106-
return data;
10786
}
10887

10988
private static void addEntry(String moduleName, String resourceName, boolean isDirectory, byte[] data) {
@@ -159,19 +138,13 @@ public static ResourceStorageEntry get(String moduleName, String resourceName) {
159138
return singleton().resources.get(Pair.create(moduleName, resourceName));
160139
}
161140

162-
private static URL createURL(String resourceName, int index) {
141+
private static URL createURL(String moduleName, String resourceName, int index) {
163142
try {
164-
return new URL(JavaNetSubstitutions.RESOURCE_PROTOCOL, null, -1, resourceName,
165-
new URLStreamHandler() {
166-
@Override
167-
protected URLConnection openConnection(URL url) {
168-
return new ResourceURLConnection(url, index);
169-
}
170-
});
143+
String refPart = index != 0 ? '#' + Integer.toString(index) : "";
144+
return new URL(JavaNetSubstitutions.RESOURCE_PROTOCOL, moduleName, -1, '/' + resourceName + refPart);
171145
} catch (MalformedURLException ex) {
172146
throw new IllegalStateException(ex);
173147
}
174-
175148
}
176149

177150
public static URL createURL(String resourceName) {
@@ -213,18 +186,34 @@ public static Enumeration<URL> createURLs(String moduleName, String resourceName
213186
if (resourceName == null) {
214187
return null;
215188
}
216-
217189
String canonicalResourceName = toCanonicalForm(resourceName);
218-
ResourceStorageEntry entry = Resources.get(moduleName, canonicalResourceName);
219-
if (entry == null) {
190+
191+
List<URL> resourcesURLs = new ArrayList<>();
192+
193+
/* If moduleName was unspecified we have to consider all modules in the image */
194+
if (moduleName == null) {
195+
for (Module module : BootModuleLayerSupport.instance().getBootLayer().modules()) {
196+
ResourceStorageEntry entry = Resources.get(module.getName(), canonicalResourceName);
197+
addURLEntries(resourcesURLs, entry, module.getName(), canonicalResourceName);
198+
}
199+
}
200+
ResourceStorageEntry explicitEntry = Resources.get(moduleName, canonicalResourceName);
201+
addURLEntries(resourcesURLs, explicitEntry, moduleName, canonicalResourceName);
202+
203+
if (resourcesURLs.isEmpty()) {
220204
return Collections.emptyEnumeration();
221205
}
206+
return Collections.enumeration(resourcesURLs);
207+
}
208+
209+
private static void addURLEntries(List<URL> resourcesURLs, ResourceStorageEntry entry, String moduleName, String canonicalResourceName) {
210+
if (entry == null) {
211+
return;
212+
}
222213
int numberOfResources = entry.getData().size();
223-
List<URL> resourcesURLs = new ArrayList<>(numberOfResources);
224214
for (int index = 0; index < numberOfResources; index++) {
225-
resourcesURLs.add(createURL(canonicalResourceName, index));
215+
resourcesURLs.add(createURL(moduleName, canonicalResourceName, index));
226216
}
227-
return Collections.enumeration(resourcesURLs);
228217
}
229218
}
230219

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/resources/ResourceURLConnection.java

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,26 +33,15 @@
3333
import java.net.URLConnection;
3434
import java.util.List;
3535

36+
import com.oracle.svm.core.jdk.JavaNetSubstitutions;
3637
import com.oracle.svm.core.jdk.Resources;
3738

3839
public class ResourceURLConnection extends URLConnection {
3940

40-
private final URL url;
41-
private final int index;
4241
private byte[] data;
4342

4443
public ResourceURLConnection(URL url) {
45-
this(url, 0);
46-
}
47-
48-
public ResourceURLConnection(URL url, int index) {
4944
super(url);
50-
this.url = url;
51-
this.index = index;
52-
}
53-
54-
private static String resolveName(String resourceName) {
55-
return resourceName.startsWith("/") ? resourceName.substring(1) : resourceName;
5645
}
5746

5847
@Override
@@ -62,10 +51,25 @@ public void connect() {
6251
}
6352
connected = true;
6453

65-
String resourceName = resolveName(url.getPath());
66-
ResourceStorageEntry entry = Resources.get(Resources.toCanonicalForm(resourceName));
54+
String urlHost = url.getHost();
55+
String hostNameOrNull = urlHost != null && !urlHost.isEmpty() ? urlHost : null;
56+
String urlPath = url.getPath();
57+
if (urlPath.isEmpty()) {
58+
throw new IllegalArgumentException("Empty URL path not allowed in " + JavaNetSubstitutions.RESOURCE_PROTOCOL + " URL");
59+
}
60+
String resourceName = urlPath.substring(1);
61+
ResourceStorageEntry entry = Resources.get(hostNameOrNull, Resources.toCanonicalForm(resourceName));
6762
if (entry != null) {
6863
List<byte[]> bytes = entry.getData();
64+
String urlRef = url.getRef();
65+
int index = 0;
66+
if (urlRef != null) {
67+
try {
68+
index = Integer.valueOf(urlRef);
69+
} catch (NumberFormatException e) {
70+
throw new IllegalArgumentException("URL anchor '#" + urlRef + "' not allowed in " + JavaNetSubstitutions.RESOURCE_PROTOCOL + " URL");
71+
}
72+
}
6973
if (index < bytes.size()) {
7074
this.data = bytes.get(index);
7175
} else {
@@ -96,5 +100,4 @@ public long getContentLengthLong() {
96100
connect();
97101
return data != null ? data.length : -1L;
98102
}
99-
100103
}

substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/native-image.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ Args= \
55
--features=com.oracle.svm.test.AbstractServiceLoaderTest$TestFeature \
66
--features=com.oracle.svm.test.NoProviderConstructorServiceLoaderTest$TestFeature \
77
--features=com.oracle.svm.test.NativeImageResourceFileSystemProviderTest$TestFeature \
8+
--add-opens=java.base/java.lang=ALL-UNNAMED \
89
-H:+AllowVMInspection \
910
--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.containers=ALL-UNNAMED
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright (c) 2022, 2022, 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.test;
26+
27+
import java.lang.annotation.ElementType;
28+
import java.lang.annotation.Retention;
29+
import java.lang.annotation.RetentionPolicy;
30+
import java.lang.annotation.Target;
31+
32+
/**
33+
* Specifies packages concealed in JDK modules used by a test. The mx unit test runner will ensure
34+
* the packages are exported to the module containing annotated test class.
35+
*/
36+
@Retention(RetentionPolicy.RUNTIME)
37+
@Target(ElementType.TYPE)
38+
public @interface AddExports {
39+
/**
40+
* The qualified name of the concealed package in {@code <module>/<package>} format (e.g.,
41+
* "jdk.internal.vm.ci/jdk.vm.ci.code").
42+
*/
43+
String[] value() default "";
44+
}

substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/NativeImageResourceFileSystemProviderTest.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import java.io.IOException;
2929
import java.io.InputStream;
30+
import java.net.MalformedURLException;
3031
import java.net.URI;
3132
import java.net.URISyntaxException;
3233
import java.net.URL;
@@ -48,11 +49,15 @@
4849
import java.nio.file.attribute.FileTime;
4950
import java.nio.file.spi.FileSystemProvider;
5051
import java.util.Collections;
52+
import java.util.Enumeration;
5153
import java.util.HashMap;
5254
import java.util.HashSet;
5355
import java.util.Iterator;
56+
import java.util.List;
5457
import java.util.Map;
5558
import java.util.Set;
59+
import java.util.stream.Collectors;
60+
import java.util.stream.StreamSupport;
5661

5762
import org.graalvm.nativeimage.ImageSingletons;
5863
import org.graalvm.nativeimage.hosted.Feature;
@@ -62,6 +67,7 @@
6267

6368
import com.oracle.svm.core.configure.ResourcesRegistry;
6469

70+
@AddExports("java.base/java.lang")
6571
public class NativeImageResourceFileSystemProviderTest {
6672

6773
private static final String RESOURCE_DIR = "/resources";
@@ -79,6 +85,11 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
7985
// Remove leading / for the resource patterns
8086
registry.addResources(ConfigurationCondition.alwaysTrue(), RESOURCE_FILE_1.substring(1));
8187
registry.addResources(ConfigurationCondition.alwaysTrue(), RESOURCE_FILE_2.substring(1));
88+
89+
/** Needed for {@link #testURLExternalFormEquivalence()} */
90+
for (Module module : ModuleLayer.boot().modules()) {
91+
registry.addResources(ConfigurationCondition.alwaysTrue(), module.getName() + ":" + "module-info.class");
92+
}
8293
}
8394
}
8495

@@ -572,4 +583,52 @@ public void writingFileAttributes() {
572583
// 3. Closing file system.
573584
closeFileSystem(fileSystem);
574585
}
586+
587+
@Test
588+
public void moduleResourceURLAccess() {
589+
URL url = Class.class.getResource("uniName.dat");
590+
Assert.assertNotNull("URL for resource java.base/java/lang/uniName.dat must not be null", url);
591+
try (InputStream in = url.openStream()) {
592+
try {
593+
Assert.assertNotEquals("uniName.dat does not seem to contain valid data", in.read(), 0);
594+
} catch (IOException e) {
595+
Assert.fail("IOException in in.read(): " + e.getMessage());
596+
}
597+
} catch (IOException e) {
598+
Assert.fail("IOException in url.openStream(): " + e.getMessage());
599+
}
600+
}
601+
602+
@Test
603+
public void testURLExternalFormEquivalence() {
604+
Enumeration<URL> urlEnumeration = null;
605+
try {
606+
urlEnumeration = ClassLoader.getSystemResources("module-info.class");
607+
} catch (IOException e) {
608+
Assert.fail("IOException in ClassLoader.getSystemResources(\"module-info.class\"): " + e.getMessage());
609+
}
610+
611+
Assert.assertNotNull(urlEnumeration);
612+
Enumeration<URL> finalVar = urlEnumeration;
613+
Iterable<URL> urlIterable = () -> finalVar.asIterator();
614+
List<URL> urlList = StreamSupport.stream(urlIterable.spliterator(), false).collect(Collectors.toList());
615+
Assert.assertTrue("ClassLoader.getSystemResources(\"module-info.class\") must return many module-info.class URLs",
616+
urlList.size() > 3);
617+
618+
URL thirdEntry = urlList.get(2);
619+
String thirdEntryExternalForm = thirdEntry.toExternalForm();
620+
URL thirdEntryFromExternalForm = null;
621+
try {
622+
thirdEntryFromExternalForm = new URL(thirdEntryExternalForm);
623+
} catch (MalformedURLException e) {
624+
Assert.fail("Creating a new URL from the ExternalForm of another has to work: " + e.getMessage());
625+
}
626+
627+
try {
628+
boolean compareResult = compareTwoURLs(thirdEntry, thirdEntryFromExternalForm);
629+
Assert.assertTrue("Contents of original URL and one created from originals ExternalForm must be the same", compareResult);
630+
} catch (IOException e) {
631+
Assert.fail("Contents of original URL and one created from originals ExternalForm must be the same: " + e);
632+
}
633+
}
575634
}

substratevm/src/com.oracle.svm.truffle.tck/src/com/oracle/svm/truffle/tck/resources/jre.json

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -172,30 +172,6 @@
172172
]}
173173
]
174174
},
175-
{
176-
"name" : "com.oracle.svm.core.jdk.Resources",
177-
"methods" : [
178-
{ "name" : "createURL" ,
179-
"parameterTypes" : [
180-
"java.lang.String"
181-
]},
182-
{ "name" : "createURL" ,
183-
"parameterTypes" : [
184-
"java.lang.String",
185-
"int"
186-
]}
187-
]
188-
},
189-
{
190-
"name" : "com.oracle.svm.core.jdk.ResourcesHelper",
191-
"methods" : [
192-
{ "name" : "urlToResource" ,
193-
"parameterTypes" : [
194-
"java.lang.String",
195-
"java.net.URL"
196-
]}
197-
]
198-
},
199175
{
200176
"name" : "com.oracle.svm.core.handles.ObjectHandlesImpl",
201177
"allDeclaredMethods" : true,

0 commit comments

Comments
 (0)