Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions api/src/main/java/javax/jdo/JDOHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Pattern;
import javax.jdo.spi.I18NHelper;
import javax.jdo.spi.JDOImplHelper;
import javax.jdo.spi.JDOImplHelper.StateInterrogationBooleanReturn;
Expand Down Expand Up @@ -741,14 +742,15 @@ protected static String getClassNameFromURL(URL url) throws IOException {
InputStream is = openStream(url);
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line = null;
Pattern splitRegex = Pattern.compile("\\s");
try {
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) {
continue;
}
// else assume first line of text is the PMF class name
String[] tokens = line.split("\\s");
String[] tokens = splitRegex.split(line);
String pmfClassName = tokens[0];
int indexOfComment = pmfClassName.indexOf("#");
if (indexOfComment == -1) {
Expand Down Expand Up @@ -971,12 +973,20 @@ public static PersistenceManagerFactory getPersistenceManagerFactory(
protected static PersistenceManagerFactory invokeGetPersistenceManagerFactoryOnImplementation(
String pmfClassName, Map<?, ?> overrides, Map<?, ?> properties, ClassLoader cl) {
try {
Class<?> implClass = forName(pmfClassName, true, cl);
// load without initializing: no code of the candidate class (which may be named by
// the first-match-wins services lookup) runs before the method has been verified
Class<?> implClass = forName(pmfClassName, false, cl);
Method m =
getMethod(
implClass,
"getPersistenceManagerFactory", // NOI18N
overrides != null ? new Class[] {Map.class, Map.class} : new Class[] {Map.class});
if (!PersistenceManagerFactory.class.isAssignableFrom(m.getReturnType())) {
// reject before invoking: otherwise the static initializers and method body of an
// arbitrary candidate class would run before the type check
throw new JDOFatalInternalException(
MSG.msg("EXC_GetPMFClassCastException", pmfClassName)); // NOI18N
}
PersistenceManagerFactory pmf =
(PersistenceManagerFactory)
invoke(
Expand Down Expand Up @@ -1589,7 +1599,13 @@ public static JDOEnhancer getEnhancer(ClassLoader loader) {
numberOfJDOEnhancers++;
try {
String enhancerClassName = getClassNameFromURL(urls.nextElement());
Class<?> enhancerClass = forName(enhancerClassName, true, ctrLoader);
// load without initializing and verify assignability before running any code of
// the candidate class named by the (first-match-wins) services file
Class<?> enhancerClass = forName(enhancerClassName, false, ctrLoader);
if (!JDOEnhancer.class.isAssignableFrom(enhancerClass)) {
throw new JDOFatalUserException(
MSG.msg("EXC_GetEnhancerClassNotAssignable", enhancerClassName)); // NOI18N
}
return (JDOEnhancer) enhancerClass.getDeclaredConstructor().newInstance();
} catch (Exception ex) {
// remember exceptions from failed enhancer invocations
Expand Down
3 changes: 3 additions & 0 deletions api/src/main/resources/javax/jdo/Bundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ EXC_GetEnhancerNoValidEnhancerAvailable=\
There are {0} services entries for the JDOEnhancer; \
there were no valid JDOEnhancer implementations found in the CLASSPATH. \
The file META-INF/services/javax.jdo.JDOEnhancer should name the implementation class.
EXC_GetEnhancerClassNotAssignable=The class "{0}" named in a \
META-INF/services/javax.jdo.JDOEnhancer resource does not implement \
javax.jdo.JDOEnhancer. The class was not instantiated.
MSG_EnhancerUsage=\
Usage: java -cp <class path> javax.jdo.Enhancer <options> <files>\n\
options:\n\
Expand Down
80 changes: 80 additions & 0 deletions api/src/test/java/javax/jdo/JDOHelperServiceValidationTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package javax.jdo;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Collections;
import java.util.Enumeration;
import javax.jdo.util.AbstractTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

/**
* Tests that service-file discovery verifies the candidate class before running any of its code: a
* class named by a META-INF/services/javax.jdo.JDOEnhancer resource that does not implement
* JDOEnhancer must be rejected without executing its static initializer or constructor.
*/
class JDOHelperServiceValidationTest extends AbstractTest {

/** Set by NotAnEnhancer's static and instance initializers; must remain false. */
static volatile boolean notAnEnhancerCodeRan = false;

/** A services-file candidate that is not a JDOEnhancer. */
public static class NotAnEnhancer {
static {
notAnEnhancerCodeRan = true;
}

public NotAnEnhancer() {
notAnEnhancerCodeRan = true;
}
}

@Test
void testGetEnhancerRejectsNonEnhancerWithoutRunningItsCode() throws IOException {
File services = File.createTempFile("javax.jdo.JDOEnhancer", ".services");
services.deleteOnExit();
Files.write(
services.toPath(), (NotAnEnhancer.class.getName() + "\n").getBytes(StandardCharsets.UTF_8));
final URL servicesURL = services.toURI().toURL();

// a loader whose only JDOEnhancer services entry names NotAnEnhancer
ClassLoader loader =
new ClassLoader(getClass().getClassLoader()) {
@Override
public Enumeration<URL> getResources(String name) throws IOException {
if ("META-INF/services/javax.jdo.JDOEnhancer".equals(name)) {
return Collections.enumeration(Collections.singletonList(servicesURL));
}
return super.getResources(name);
}
};

Assertions.assertThrows(
JDOFatalUserException.class,
() -> JDOHelper.getEnhancer(loader),
"getEnhancer with only a non-enhancer candidate should fail");
Assertions.assertFalse(
notAnEnhancerCodeRan,
"Code of the non-enhancer candidate must not run before the type check");
}
}
Loading