Skip to content

Commit 96563c1

Browse files
committed
Throw missing registration errors for JNI calls
1 parent 6335fa6 commit 96563c1

File tree

19 files changed

+639
-132
lines changed

19 files changed

+639
-132
lines changed

compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/util/SignatureUtil.java

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,30 +42,50 @@ private SignatureUtil() {
4242
* @return the parsed return type descriptor
4343
*/
4444
public static String parseSignature(String signature, List<String> parameters) {
45-
if (signature.length() == 0) {
46-
throw new IllegalArgumentException("Signature cannot be empty");
45+
return parseSignatureInternal(signature, parameters, true, false);
46+
}
47+
48+
/*
49+
* If throwOnInvalidFormat is not set, returns null if signature parsing failed.
50+
*/
51+
private static String parseSignatureInternal(String signature, List<String> parameters, boolean throwOnInvalidFormat, boolean acceptMissingReturnType) {
52+
if (signature.isEmpty()) {
53+
return throwOrReturn(throwOnInvalidFormat, null, "Signature cannot be empty");
4754
}
4855
if (signature.charAt(0) == '(') {
4956
int cur = 1;
5057
while (cur < signature.length() && signature.charAt(cur) != ')') {
51-
int nextCur = parseSignature(signature, cur);
52-
parameters.add(signature.substring(cur, nextCur));
58+
int nextCur = parseParameterSignature(signature, cur, throwOnInvalidFormat);
59+
if (nextCur == -1) {
60+
assert !throwOnInvalidFormat;
61+
return null;
62+
}
63+
if (parameters != null) {
64+
parameters.add(signature.substring(cur, nextCur));
65+
}
5366
cur = nextCur;
5467
}
5568

5669
cur++;
57-
int nextCur = parseSignature(signature, cur);
70+
if (acceptMissingReturnType && cur == signature.length()) {
71+
return "";
72+
}
73+
int nextCur = parseParameterSignature(signature, cur, throwOnInvalidFormat);
74+
if (nextCur == -1) {
75+
assert !throwOnInvalidFormat;
76+
return null;
77+
}
5878
String returnType = signature.substring(cur, nextCur);
5979
if (nextCur != signature.length()) {
60-
throw new IllegalArgumentException("Extra characters at end of signature: " + signature);
80+
return throwOrReturn(throwOnInvalidFormat, null, "Extra characters at end of signature: " + signature);
6181
}
6282
return returnType;
6383
} else {
64-
throw new IllegalArgumentException("Signature must start with a '(': " + signature);
84+
return throwOrReturn(throwOnInvalidFormat, null, "Signature must start with a '(': " + signature);
6585
}
6686
}
6787

68-
private static int parseSignature(String signature, int start) {
88+
private static int parseParameterSignature(String signature, int start, boolean throwOnInvalidFormat) {
6989
try {
7090
int cur = start;
7191
char first;
@@ -78,7 +98,7 @@ private static int parseSignature(String signature, int start) {
7898
case 'L':
7999
while (signature.charAt(cur) != ';') {
80100
if (signature.charAt(cur) == '.') {
81-
throw new IllegalArgumentException("Class name in signature contains '.' at index " + cur + ": " + signature);
101+
return throwOrReturn(throwOnInvalidFormat, -1, "Class name in signature contains '.' at index " + cur + ": " + signature);
82102
}
83103
cur++;
84104
}
@@ -95,11 +115,32 @@ private static int parseSignature(String signature, int start) {
95115
case 'Z':
96116
break;
97117
default:
98-
throw new IllegalArgumentException("Invalid character '" + signature.charAt(cur - 1) + "' at index " + (cur - 1) + " in signature: " + signature);
118+
return throwOrReturn(throwOnInvalidFormat, -1, "Invalid character '" + signature.charAt(cur - 1) + "' at index " + (cur - 1) + " in signature: " + signature);
99119
}
100120
return cur;
101121
} catch (StringIndexOutOfBoundsException e) {
102-
throw new IllegalArgumentException("Truncated signature: " + signature);
122+
return throwOrReturn(throwOnInvalidFormat, -1, "Truncated signature: " + signature);
123+
}
124+
}
125+
126+
/**
127+
* Checks if the given signature can be succesfully parsed by
128+
* {@link #parseSignature(String, List)}.
129+
*
130+
* @param signature the signature to check
131+
* @param acceptMissingReturnType whether a signature without a return type is considered to be
132+
* valid
133+
* @return whether the signature can be successfully parsed
134+
*/
135+
public static boolean isSignatureValid(String signature, boolean acceptMissingReturnType) {
136+
return parseSignatureInternal(signature, null, false, acceptMissingReturnType) != null;
137+
}
138+
139+
private static <T> T throwOrReturn(boolean shouldThrow, T returnValue, String errorMessage) {
140+
if (shouldThrow) {
141+
throw new IllegalArgumentException(errorMessage);
142+
} else {
143+
return returnValue;
103144
}
104145
}
105146
}

sdk/src/org.graalvm.nativeimage/snapshot.sigtest

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,15 @@ meth public java.lang.String getElementName()
233233
supr java.lang.Error
234234
hfds declaringClass,elementName,elementType,parameterTypes,serialVersionUID
235235

236+
CLSS public final org.graalvm.nativeimage.MissingJNIRegistrationError
237+
cons public init(java.lang.String,java.lang.Class<?>,java.lang.Class<?>,java.lang.String,java.lang.String)
238+
meth public java.lang.Class<?> getDeclaringClass()
239+
meth public java.lang.Class<?> getElementType()
240+
meth public java.lang.String getSignature()
241+
meth public java.lang.String getElementName()
242+
supr java.lang.Error
243+
hfds declaringClass,elementName,elementType,signature,serialVersionUID
244+
236245
CLSS public abstract interface org.graalvm.nativeimage.ObjectHandle
237246
intf org.graalvm.word.ComparableWord
238247

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* Copyright (c) 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 org.graalvm.nativeimage;
42+
43+
import java.io.Serial;
44+
import java.lang.reflect.Constructor;
45+
import java.lang.reflect.Field;
46+
import java.lang.reflect.Method;
47+
48+
/**
49+
* This exception is thrown when a JNI query tries to access an element that was not
50+
* <a href= "https://www.graalvm.org/latest/reference-manual/native-image/metadata/#jni">registered
51+
* for JNI access</a> in the program. When an element is not registered, the exception will be
52+
* thrown both for elements that exist and elements that do not exist on the given classpath.
53+
* <p/>
54+
* The purpose of this exception is to easily discover unregistered elements and to assure that all
55+
* JNI operations for registered elements have the expected behaviour.
56+
* <p/>
57+
* Queries will succeed (or throw the expected error) if the element was registered for JNI access.
58+
* If that is not the case, a {@link MissingJNIRegistrationError} will be thrown.
59+
* <p/>
60+
* The exception thrown by the JNI query is a <em>pending</em> exception that needs to be explicitly
61+
* checked by the calling native code.
62+
* </ol>
63+
* Examples:
64+
* <p/>
65+
* Registration: {@code "fields": [{"name": "registeredField"}, {"name":
66+
* "registeredNonexistentField"}]}<br>
67+
* {@code GetFieldID(declaringClass, "registeredField")} will return the expected field.<br>
68+
* {@code GetFieldID(declaringClass, "registeredNonexistentField")} will throw a
69+
* {@link NoSuchFieldError}.<br>
70+
* {@code GetFieldID(declaringClass, "unregisteredField")} will throw a
71+
* {@link MissingJNIRegistrationError}.<br>
72+
* {@code GetFieldID(declaringClass, "unregisteredNonexistentField")} will throw a
73+
* {@link MissingJNIRegistrationError}.<br>
74+
*
75+
* @since 24.1
76+
*/
77+
public final class MissingJNIRegistrationError extends Error {
78+
@Serial private static final long serialVersionUID = -8940056537864516986L;
79+
80+
private final Class<?> elementType;
81+
82+
private final Class<?> declaringClass;
83+
84+
private final String elementName;
85+
86+
private final String signature;
87+
88+
/**
89+
* @since 24.1
90+
*/
91+
public MissingJNIRegistrationError(String message, Class<?> elementType, Class<?> declaringClass, String elementName, String signature) {
92+
super(message);
93+
this.elementType = elementType;
94+
this.declaringClass = declaringClass;
95+
this.elementName = elementName;
96+
this.signature = signature;
97+
}
98+
99+
/**
100+
* @return The type of the element trying to be queried ({@link Class}, {@link Method},
101+
* {@link Field} or {@link Constructor}).
102+
* @since 23.0
103+
*/
104+
public Class<?> getElementType() {
105+
return elementType;
106+
}
107+
108+
/**
109+
* @return The class on which the missing query was tried, or null on static queries.
110+
* @since 23.0
111+
*/
112+
public Class<?> getDeclaringClass() {
113+
return declaringClass;
114+
}
115+
116+
/**
117+
* @return The name of the queried element.
118+
* @since 23.0
119+
*/
120+
public String getElementName() {
121+
return elementName;
122+
}
123+
124+
/**
125+
* @return The signature passed to the query, or null if the query doesn't take a signature as
126+
* argument.
127+
* @since 23.0
128+
*/
129+
public String getSignature() {
130+
return signature;
131+
}
132+
}

sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ReflectionRegistry.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -55,4 +55,11 @@ default void register(ConfigurationCondition condition, Class<?>... classes) {
5555

5656
void register(ConfigurationCondition condition, boolean finalIsWritable, Field... fields);
5757

58+
void registerClassLookup(ConfigurationCondition condition, String typeName);
59+
60+
void registerFieldLookup(ConfigurationCondition condition, Class<?> declaringClass, String fieldName);
61+
62+
void registerMethodLookup(ConfigurationCondition condition, Class<?> declaringClass, String methodName, Class<?>... parameterTypes);
63+
64+
void registerConstructorLookup(ConfigurationCondition condition, Class<?> declaringClass, Class<?>... parameterTypes);
5865
}

sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeReflectionSupport.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -67,12 +67,4 @@ public interface RuntimeReflectionSupport extends ReflectionRegistry {
6767
void registerAllSignersQuery(ConfigurationCondition condition, Class<?> clazz);
6868

6969
void registerClassLookupException(ConfigurationCondition condition, String typeName, Throwable t);
70-
71-
void registerClassLookup(ConfigurationCondition condition, String typeName);
72-
73-
void registerFieldLookup(ConfigurationCondition condition, Class<?> declaringClass, String fieldName);
74-
75-
void registerMethodLookup(ConfigurationCondition condition, Class<?> declaringClass, String methodName, Class<?>... parameterTypes);
76-
77-
void registerConstructorLookup(ConfigurationCondition condition, Class<?> declaringClass, Class<?>... parameterTypes);
7870
}

substratevm/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ This changelog summarizes major changes to GraalVM Native Image.
2727
* (GR-18214) In-place compacting garbage collection for the Serial GC old generation with `-H:+CompactingOldGen`.
2828
* (GR-52844) Add `Os` a new optimization mode to configure the optimizer in a way to get the smallest code size.
2929
* (GR-49770) Add support for glob patterns in resource-config files in addition to regexp. The Tracing agent now prints entries in the glob format.
30+
* (GR-46386) Throw missing registration errors for JNI queries when the query was not included in the reachability metadata.
3031

3132
## GraalVM for JDK 22 (Internal Version 24.0.0)
3233
* (GR-48304) Red Hat added support for the JFR event ThreadAllocationStatistics.

0 commit comments

Comments
 (0)