Skip to content

Commit 2a23658

Browse files
committed
Add java version variants of entitlements checker (elastic#116878)
As each version of Java is released, there may be additional methods we want to instrument for entitlements. Since new methods won't exist in the base version of Java that Elasticsearch is compiled with, we need to hava different classes and compilation for each version. This commit adds a scaffolding for adding the classes for new versions of Java. Unfortunately it requires several classes in different locations. But hopefully these are infrequent enough that the boilerplate is ok. We could consider adding a helper Gradle task to templatize the new classes in the future if it is too cumbersome. Note that the example for Java23 does not have anything meaningful in it yet, it's only meant as an example until we find go through classes and methods that were added after Java 21.
1 parent 8602384 commit 2a23658

File tree

10 files changed

+201
-44
lines changed

10 files changed

+201
-44
lines changed

build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/MrjarPlugin.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020
import org.gradle.api.plugins.JavaPluginExtension;
2121
import org.gradle.api.tasks.SourceSet;
2222
import org.gradle.api.tasks.SourceSetContainer;
23+
import org.gradle.api.tasks.TaskProvider;
2324
import org.gradle.api.tasks.compile.CompileOptions;
2425
import org.gradle.api.tasks.compile.JavaCompile;
26+
import org.gradle.api.tasks.javadoc.Javadoc;
2527
import org.gradle.api.tasks.testing.Test;
28+
import org.gradle.external.javadoc.CoreJavadocOptions;
2629
import org.gradle.jvm.tasks.Jar;
2730
import org.gradle.jvm.toolchain.JavaLanguageVersion;
2831
import org.gradle.jvm.toolchain.JavaToolchainService;
@@ -41,7 +44,6 @@
4144
import java.util.regex.Matcher;
4245
import java.util.regex.Pattern;
4346
import java.util.stream.Stream;
44-
4547
import javax.inject.Inject;
4648

4749
import static de.thetaphi.forbiddenapis.gradle.ForbiddenApisPlugin.FORBIDDEN_APIS_TASK_NAME;
@@ -79,6 +81,7 @@ public void apply(Project project) {
7981
String mainSourceSetName = SourceSet.MAIN_SOURCE_SET_NAME + javaVersion;
8082
SourceSet mainSourceSet = addSourceSet(project, javaExtension, mainSourceSetName, mainSourceSets, javaVersion);
8183
configureSourceSetInJar(project, mainSourceSet, javaVersion);
84+
addJar(project, mainSourceSet, javaVersion);
8285
mainSourceSets.add(mainSourceSetName);
8386
testSourceSets.add(mainSourceSetName);
8487

@@ -142,6 +145,29 @@ private SourceSet addSourceSet(
142145
return sourceSet;
143146
}
144147

148+
private void addJar(Project project, SourceSet sourceSet, int javaVersion) {
149+
project.getConfigurations().register("java" + javaVersion);
150+
TaskProvider<Jar> jarTask = project.getTasks().register("java" + javaVersion + "Jar", Jar.class, task -> {
151+
task.from(sourceSet.getOutput());
152+
});
153+
project.getArtifacts().add("java" + javaVersion, jarTask);
154+
}
155+
156+
private void configurePreviewFeatures(Project project, SourceSet sourceSet, int javaVersion) {
157+
project.getTasks().withType(JavaCompile.class).named(sourceSet.getCompileJavaTaskName()).configure(compileTask -> {
158+
CompileOptions compileOptions = compileTask.getOptions();
159+
compileOptions.getCompilerArgs().add("--enable-preview");
160+
compileOptions.getCompilerArgs().add("-Xlint:-preview");
161+
162+
compileTask.doLast(t -> { stripPreviewFromFiles(compileTask.getDestinationDirectory().getAsFile().get().toPath()); });
163+
});
164+
project.getTasks().withType(Javadoc.class).named(name -> name.equals(sourceSet.getJavadocTaskName())).configureEach(javadocTask -> {
165+
CoreJavadocOptions options = (CoreJavadocOptions) javadocTask.getOptions();
166+
options.addBooleanOption("-enable-preview", true);
167+
options.addStringOption("-release", String.valueOf(javaVersion));
168+
});
169+
}
170+
145171
private void configureSourceSetInJar(Project project, SourceSet sourceSet, int javaVersion) {
146172
var jarTask = project.getTasks().withType(Jar.class).named(JavaPlugin.JAR_TASK_NAME);
147173
jarTask.configure(task -> task.into("META-INF/versions/" + javaVersion, copySpec -> copySpec.from(sourceSet.getOutput())));

libs/entitlement/asm-provider/src/main/java/org/elasticsearch/entitlement/instrumentation/impl/InstrumenterImpl.java

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,22 @@
3636
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
3737

3838
public class InstrumenterImpl implements Instrumenter {
39+
40+
private static final String checkerClassDescriptor;
41+
private static final String handleClass;
42+
static {
43+
int javaVersion = Runtime.version().feature();
44+
final String classNamePrefix;
45+
if (javaVersion >= 23) {
46+
classNamePrefix = "Java23";
47+
} else {
48+
classNamePrefix = "";
49+
}
50+
String checkerClass = "org/elasticsearch/entitlement/bridge/" + classNamePrefix + "EntitlementChecker";
51+
handleClass = checkerClass + "Handle";
52+
checkerClassDescriptor = Type.getObjectType(checkerClass).getDescriptor();
53+
}
54+
3955
/**
4056
* To avoid class name collisions during testing without an agent to replace classes in-place.
4157
*/
@@ -269,13 +285,7 @@ private void invokeInstrumentationMethod() {
269285
}
270286

271287
protected void pushEntitlementChecker(MethodVisitor mv) {
272-
mv.visitMethodInsn(
273-
INVOKESTATIC,
274-
"org/elasticsearch/entitlement/bridge/EntitlementCheckerHandle",
275-
"instance",
276-
"()Lorg/elasticsearch/entitlement/bridge/EntitlementChecker;",
277-
false
278-
);
288+
mv.visitMethodInsn(INVOKESTATIC, handleClass, "instance", "()" + checkerClassDescriptor, false);
279289
}
280290

281291
public record ClassFileInfo(String fileName, byte[] bytecodes) {}

libs/entitlement/bridge/build.gradle

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,18 @@
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
99

10+
import org.elasticsearch.gradle.internal.precommit.CheckForbiddenApisTask
11+
1012
apply plugin: 'elasticsearch.build'
13+
apply plugin: 'elasticsearch.mrjar'
1114

12-
configurations {
13-
bridgeJar {
14-
canBeConsumed = true
15-
canBeResolved = false
15+
tasks.named('jar').configure {
16+
// guarding for intellij
17+
if (sourceSets.findByName("main23")) {
18+
from sourceSets.main23.output
1619
}
1720
}
1821

19-
artifacts {
20-
bridgeJar(jar)
21-
}
22-
23-
tasks.named('forbiddenApisMain').configure {
22+
tasks.withType(CheckForbiddenApisTask).configureEach {
2423
replaceSignatureFiles 'jdk-signatures'
2524
}

libs/entitlement/bridge/src/main/java/org/elasticsearch/entitlement/bridge/EntitlementCheckerHandle.java

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@
99

1010
package org.elasticsearch.entitlement.bridge;
1111

12-
import java.lang.reflect.InvocationTargetException;
13-
import java.lang.reflect.Method;
14-
1512
/**
1613
* Makes the {@link EntitlementChecker} available to injected bytecode.
1714
*/
@@ -35,27 +32,7 @@ private static class Holder {
3532
* The {@code EntitlementInitialization} class is what actually instantiates it and makes it available;
3633
* here, we copy it into a static final variable for maximum performance.
3734
*/
38-
private static final EntitlementChecker instance;
39-
static {
40-
String initClazz = "org.elasticsearch.entitlement.initialization.EntitlementInitialization";
41-
final Class<?> clazz;
42-
try {
43-
clazz = ClassLoader.getSystemClassLoader().loadClass(initClazz);
44-
} catch (ClassNotFoundException e) {
45-
throw new AssertionError("java.base cannot find entitlement initialziation", e);
46-
}
47-
final Method checkerMethod;
48-
try {
49-
checkerMethod = clazz.getMethod("checker");
50-
} catch (NoSuchMethodException e) {
51-
throw new AssertionError("EntitlementInitialization is missing checker() method", e);
52-
}
53-
try {
54-
instance = (EntitlementChecker) checkerMethod.invoke(null);
55-
} catch (IllegalAccessException | InvocationTargetException e) {
56-
throw new AssertionError(e);
57-
}
58-
}
35+
private static final EntitlementChecker instance = HandleLoader.load(EntitlementChecker.class);
5936
}
6037

6138
// no construction
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.entitlement.bridge;
11+
12+
import java.lang.reflect.InvocationTargetException;
13+
import java.lang.reflect.Method;
14+
15+
class HandleLoader {
16+
17+
static <T extends EntitlementChecker> T load(Class<T> checkerClass) {
18+
String initClassName = "org.elasticsearch.entitlement.initialization.EntitlementInitialization";
19+
final Class<?> initClazz;
20+
try {
21+
initClazz = ClassLoader.getSystemClassLoader().loadClass(initClassName);
22+
} catch (ClassNotFoundException e) {
23+
throw new AssertionError("java.base cannot find entitlement initialization", e);
24+
}
25+
final Method checkerMethod;
26+
try {
27+
checkerMethod = initClazz.getMethod("checker");
28+
} catch (NoSuchMethodException e) {
29+
throw new AssertionError("EntitlementInitialization is missing checker() method", e);
30+
}
31+
try {
32+
return checkerClass.cast(checkerMethod.invoke(null));
33+
} catch (IllegalAccessException | InvocationTargetException e) {
34+
throw new AssertionError(e);
35+
}
36+
}
37+
38+
// no instance
39+
private HandleLoader() {}
40+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.entitlement.bridge;
11+
12+
public interface Java23EntitlementChecker extends EntitlementChecker {}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.entitlement.bridge;
11+
12+
/**
13+
* Java23 variant of {@link EntitlementChecker} handle holder.
14+
*/
15+
public class Java23EntitlementCheckerHandle {
16+
17+
public static Java23EntitlementChecker instance() {
18+
return Holder.instance;
19+
}
20+
21+
private static class Holder {
22+
private static final Java23EntitlementChecker instance = HandleLoader.load(Java23EntitlementChecker.class);
23+
}
24+
25+
// no construction
26+
private Java23EntitlementCheckerHandle() {}
27+
}

libs/entitlement/build.gradle

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66
* your election, the "Elastic License 2.0", the "GNU Affero General Public
77
* License v3.0 only", or the "Server Side Public License, v 1".
88
*/
9+
10+
import org.elasticsearch.gradle.internal.precommit.CheckForbiddenApisTask
11+
912
apply plugin: 'elasticsearch.build'
1013
apply plugin: 'elasticsearch.publish'
11-
1214
apply plugin: 'elasticsearch.embedded-providers'
15+
apply plugin: 'elasticsearch.mrjar'
1316

1417
embeddedProviders {
1518
impl 'entitlement', project(':libs:entitlement:asm-provider')
@@ -23,8 +26,13 @@ dependencies {
2326
testImplementation(project(":test:framework")) {
2427
exclude group: 'org.elasticsearch', module: 'entitlement'
2528
}
29+
30+
// guarding for intellij
31+
if (sourceSets.findByName("main23")) {
32+
main23CompileOnly project(path: ':libs:entitlement:bridge', configuration: 'java23')
33+
}
2634
}
2735

28-
tasks.named('forbiddenApisMain').configure {
36+
tasks.withType(CheckForbiddenApisTask).configureEach {
2937
replaceSignatureFiles 'jdk-signatures'
3038
}

libs/entitlement/src/main/java/org/elasticsearch/entitlement/initialization/EntitlementInitialization.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import java.lang.instrument.Instrumentation;
2828
import java.lang.module.ModuleFinder;
2929
import java.lang.module.ModuleReference;
30+
import java.lang.reflect.Constructor;
31+
import java.lang.reflect.InvocationTargetException;
3032
import java.nio.file.Files;
3133
import java.nio.file.Path;
3234
import java.nio.file.StandardOpenOption;
@@ -59,7 +61,7 @@ public static EntitlementChecker checker() {
5961

6062
// Note: referenced by agent reflectively
6163
public static void initialize(Instrumentation inst) throws Exception {
62-
manager = new ElasticsearchEntitlementChecker(createPolicyManager());
64+
manager = initChecker();
6365

6466
Map<MethodKey, CheckerMethod> methodMap = INSTRUMENTER_FACTORY.lookupMethodsToInstrument(
6567
"org.elasticsearch.entitlement.bridge.EntitlementChecker"
@@ -137,6 +139,36 @@ private static Set<String> getModuleNames(Path pluginRoot, boolean isModular) {
137139
return Set.of(ALL_UNNAMED);
138140
}
139141

142+
private static ElasticsearchEntitlementChecker initChecker() throws IOException {
143+
final PolicyManager policyManager = createPolicyManager();
144+
145+
int javaVersion = Runtime.version().feature();
146+
final String classNamePrefix;
147+
if (javaVersion >= 23) {
148+
classNamePrefix = "Java23";
149+
} else {
150+
classNamePrefix = "";
151+
}
152+
final String className = "org.elasticsearch.entitlement.runtime.api." + classNamePrefix + "ElasticsearchEntitlementChecker";
153+
Class<?> clazz;
154+
try {
155+
clazz = Class.forName(className);
156+
} catch (ClassNotFoundException e) {
157+
throw new AssertionError("entitlement lib cannot find entitlement impl", e);
158+
}
159+
Constructor<?> constructor;
160+
try {
161+
constructor = clazz.getConstructor(PolicyManager.class);
162+
} catch (NoSuchMethodException e) {
163+
throw new AssertionError("entitlement impl is missing no arg constructor", e);
164+
}
165+
try {
166+
return (ElasticsearchEntitlementChecker) constructor.newInstance(policyManager);
167+
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
168+
throw new AssertionError(e);
169+
}
170+
}
171+
140172
private static String internalName(Class<?> c) {
141173
return c.getName().replace('.', '/');
142174
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.entitlement.runtime.api;
11+
12+
import org.elasticsearch.entitlement.bridge.Java23EntitlementChecker;
13+
import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
14+
15+
public class Java23ElasticsearchEntitlementChecker extends ElasticsearchEntitlementChecker implements Java23EntitlementChecker {
16+
17+
public Java23ElasticsearchEntitlementChecker(PolicyManager policyManager) {
18+
super(policyManager);
19+
}
20+
21+
@Override
22+
public void check$java_lang_System$exit(Class<?> callerClass, int status) {
23+
// TODO: this is just an example, we shouldn't really override a method implemented in the superclass
24+
super.check$java_lang_System$exit(callerClass, status);
25+
}
26+
}

0 commit comments

Comments
 (0)