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 jdk.tools.jlink.internal.plugins;
26
27 import java.io.File;
28 import java.io.IOException;
29 import java.lang.invoke.MethodType;
30 import java.nio.file.Files;
31 import java.util.ArrayList;
32 import java.util.EnumSet;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Set;
36 import java.util.TreeMap;
37 import java.util.TreeSet;
38 import java.util.stream.Collectors;
39 import java.util.stream.Stream;
40 import jdk.internal.misc.SharedSecrets;
41 import jdk.internal.misc.JavaLangInvokeAccess;
42 import jdk.tools.jlink.plugin.ResourcePoolEntry;
43 import jdk.tools.jlink.plugin.PluginException;
44 import jdk.tools.jlink.plugin.ResourcePool;
45 import jdk.tools.jlink.plugin.ResourcePoolBuilder;
46 import jdk.tools.jlink.plugin.Plugin;
47
48 /**
49 * Plugin to generate java.lang.invoke classes.
50 */
51 public final class GenerateJLIClassesPlugin implements Plugin {
52
53 private static final String NAME = "generate-jli-classes";
54
55 private static final String DESCRIPTION = PluginsResourceBundle.getDescription(NAME);
56
57 private static final String DIRECT_HOLDER = "java/lang/invoke/DirectMethodHandle$Holder";
58 private static final String DMH_INVOKE_VIRTUAL = "invokeVirtual";
59 private static final String DMH_INVOKE_STATIC = "invokeStatic";
60 private static final String DMH_INVOKE_SPECIAL = "invokeSpecial";
61 private static final String DMH_NEW_INVOKE_SPECIAL = "newInvokeSpecial";
62 private static final String DMH_INVOKE_INTERFACE = "invokeInterface";
63 private static final String DMH_INVOKE_STATIC_INIT = "invokeStaticInit";
64
65 private static final String DELEGATING_HOLDER = "java/lang/invoke/DelegatingMethodHandle$Holder";
66 private static final String BASIC_FORMS_HOLDER = "java/lang/invoke/LambdaForm$Holder";
67 private static final String INVOKERS_HOLDER = "java/lang/invoke/Invokers$Holder";
68
69 private static final JavaLangInvokeAccess JLIA
70 = SharedSecrets.getJavaLangInvokeAccess();
71
72 Set<String> speciesTypes;
73
74 Set<String> invokerTypes;
75
76 Map<String, Set<String>> dmhMethods;
77
78 public GenerateJLIClassesPlugin() {
79 }
80
81 @Override
82 public String getName() {
83 return NAME;
84 }
85
86 @Override
87 public String getDescription() {
88 return DESCRIPTION;
89 }
90
91 @Override
92 public Set<State> getState() {
93 return EnumSet.of(State.AUTO_ENABLED, State.FUNCTIONAL);
94 }
95
96 @Override
140 "L_I", "L_L", "L_V", "LD_L", "LF_L", "LI_I", "LII_L", "LLI_L",
141 "LL_V", "LL_L", "L3_L", "L4_L", "L5_L", "L6_L", "L7_L",
142 "L8_L", "L9_L", "L10_L", "L10I_L", "L10II_L", "L10IIL_L",
143 "L11_L", "L12_L", "L13_L", "L14_L", "L14I_L", "L14II_L")
144 );
145 }
146
147 // Map from DirectMethodHandle method type to internal ID
148 private static final Map<String, Integer> DMH_METHOD_TYPE_MAP =
149 Map.of(
150 DMH_INVOKE_VIRTUAL, 0,
151 DMH_INVOKE_STATIC, 1,
152 DMH_INVOKE_SPECIAL, 2,
153 DMH_NEW_INVOKE_SPECIAL, 3,
154 DMH_INVOKE_INTERFACE, 4,
155 DMH_INVOKE_STATIC_INIT, 5
156 );
157
158 @Override
159 public void configure(Map<String, String> config) {
160 String mainArgument = config.get(NAME);
161
162 // Start with the default configuration
163 Set<String> defaultBMHSpecies = defaultSpecies();
164 // Expand BMH species signatures
165 defaultBMHSpecies = defaultBMHSpecies.stream()
166 .map(type -> expandSignature(type))
167 .collect(Collectors.toSet());
168
169 Set<String> defaultInvokerTypes = defaultInvokers();
170 validateMethodTypes(defaultInvokerTypes);
171
172 Map<String, Set<String>> defaultDmhMethods = defaultDMHMethods();
173 for (Set<String> dmhMethodTypes : defaultDmhMethods.values()) {
174 validateMethodTypes(dmhMethodTypes);
175 }
176
177 // Extend the default configuration with the contents in the supplied
178 // input file
179 if (mainArgument == null || !mainArgument.startsWith("@")) {
180 speciesTypes = defaultBMHSpecies;
181 invokerTypes = defaultInvokerTypes;
182 dmhMethods = defaultDmhMethods;
183 } else {
184 File file = new File(mainArgument.substring(1));
185 if (file.exists()) {
186 // Use TreeSet/TreeMap to keep things sorted in a deterministic
187 // order to avoid scrambling the layout on small changes and to
188 // ease finding methods in the generated code
189 speciesTypes = new TreeSet<>(defaultBMHSpecies);
190 invokerTypes = new TreeSet<>(defaultInvokerTypes);
191 dmhMethods = new TreeMap<>();
192 for (Map.Entry<String, Set<String>> entry : defaultDmhMethods.entrySet()) {
193 dmhMethods.put(entry.getKey(), new TreeSet<>(entry.getValue()));
194 }
195 fileLines(file)
196 .map(line -> line.split(" "))
197 .forEach(parts -> {
198 switch (parts[0]) {
199 case "[BMH_RESOLVE]":
200 speciesTypes.add(expandSignature(parts[1]));
201 break;
202 case "[LF_RESOLVE]":
203 String methodType = parts[3];
204 validateMethodType(methodType);
205 if (parts[1].contains("Invokers")) {
206 invokerTypes.add(methodType);
207 } else if (parts[1].contains("DirectMethodHandle")) {
208 String dmh = parts[2];
209 // ignore getObject etc for now (generated
210 // by default)
211 if (DMH_METHOD_TYPE_MAP.containsKey(dmh)) {
212 addDMHMethodType(dmh, methodType);
213 }
214 }
215 break;
216 default: break; // ignore
217 }
218 });
219 }
220 }
221 }
222
223 private void addDMHMethodType(String dmh, String methodType) {
224 validateMethodType(methodType);
225 Set<String> methodTypes = dmhMethods.get(dmh);
226 if (methodTypes == null) {
227 methodTypes = new TreeSet<>();
228 dmhMethods.put(dmh, methodTypes);
229 }
230 methodTypes.add(methodType);
231 }
232
233 private Stream<String> fileLines(File file) {
234 try {
235 return Files.lines(file.toPath());
236 } catch (IOException io) {
237 throw new PluginException("Couldn't read file");
238 }
239 }
240
241 private void validateMethodTypes(Set<String> dmhMethodTypes) {
248 String[] typeParts = type.split("_");
249 // check return type (second part)
250 if (typeParts.length != 2 || typeParts[1].length() != 1
251 || "LJIFDV".indexOf(typeParts[1].charAt(0)) == -1) {
252 throw new PluginException(
253 "Method type signature must be of form [LJIFD]*_[LJIFDV]");
254 }
255 // expand and check arguments (first part)
256 expandSignature(typeParts[0]);
257 }
258
259 private static void requireBasicType(char c) {
260 if ("LIJFD".indexOf(c) < 0) {
261 throw new PluginException(
262 "Character " + c + " must correspond to a basic field type: LIJFD");
263 }
264 }
265
266 @Override
267 public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
268 // Copy all but DMH_ENTRY to out
269 in.transformAndCopy(entry -> {
270 // filter out placeholder entries
271 if (entry.path().equals(DIRECT_METHOD_HOLDER_ENTRY) ||
272 entry.path().equals(DELEGATING_METHOD_HOLDER_ENTRY) ||
273 entry.path().equals(INVOKERS_HOLDER_ENTRY) ||
274 entry.path().equals(BASIC_FORMS_HOLDER_ENTRY)) {
275 return null;
276 } else {
277 return entry;
278 }
279 }, out);
280 speciesTypes.forEach(types -> generateBMHClass(types, out));
281 generateHolderClasses(out);
282 return out.build();
283 }
284
285 @SuppressWarnings("unchecked")
286 private void generateBMHClass(String types, ResourcePoolBuilder out) {
287 try {
288 // Generate class
289 Map.Entry<String, byte[]> result =
290 JLIA.generateConcreteBMHClassBytes(types);
291 String className = result.getKey();
292 byte[] bytes = result.getValue();
293
294 // Add class to pool
295 ResourcePoolEntry ndata = ResourcePoolEntry.create(
296 "/java.base/" + className + ".class",
297 bytes);
298 out.add(ndata);
299 } catch (Exception ex) {
300 throw new PluginException(ex);
301 }
|
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 jdk.tools.jlink.internal.plugins;
26
27 import java.io.BufferedReader;
28 import java.io.File;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.InputStreamReader;
32 import java.lang.invoke.MethodType;
33 import java.lang.module.ModuleDescriptor;
34 import java.nio.file.Files;
35 import java.util.EnumSet;
36 import java.util.Map;
37 import java.util.Optional;
38 import java.util.Set;
39 import java.util.TreeMap;
40 import java.util.TreeSet;
41 import java.util.stream.Collectors;
42 import java.util.stream.Stream;
43 import jdk.internal.misc.SharedSecrets;
44 import jdk.internal.misc.JavaLangInvokeAccess;
45 import jdk.tools.jlink.plugin.ResourcePoolEntry;
46 import jdk.tools.jlink.plugin.PluginException;
47 import jdk.tools.jlink.plugin.ResourcePool;
48 import jdk.tools.jlink.plugin.ResourcePoolBuilder;
49 import jdk.tools.jlink.plugin.Plugin;
50
51 /**
52 * Plugin to generate java.lang.invoke classes.
53 */
54 public final class GenerateJLIClassesPlugin implements Plugin {
55
56 private static final String NAME = "generate-jli-classes";
57 private static final String IGNORE_VERSION = "ignore-version";
58
59 private static final String DESCRIPTION = PluginsResourceBundle.getDescription(NAME);
60 private static final String IGNORE_VERSION_WARNING = NAME + ".ignore.version.warn";
61 private static final String VERSION_MISMATCH_WARNING = NAME + ".version.mismatch.warn";
62
63 private static final String DEFAULT_TRACE_FILE = "default_jli_trace.txt";
64
65 private static final String DIRECT_HOLDER = "java/lang/invoke/DirectMethodHandle$Holder";
66 private static final String DMH_INVOKE_VIRTUAL = "invokeVirtual";
67 private static final String DMH_INVOKE_STATIC = "invokeStatic";
68 private static final String DMH_INVOKE_SPECIAL = "invokeSpecial";
69 private static final String DMH_NEW_INVOKE_SPECIAL = "newInvokeSpecial";
70 private static final String DMH_INVOKE_INTERFACE = "invokeInterface";
71 private static final String DMH_INVOKE_STATIC_INIT = "invokeStaticInit";
72
73 private static final String DELEGATING_HOLDER = "java/lang/invoke/DelegatingMethodHandle$Holder";
74 private static final String BASIC_FORMS_HOLDER = "java/lang/invoke/LambdaForm$Holder";
75 private static final String INVOKERS_HOLDER = "java/lang/invoke/Invokers$Holder";
76
77 private static final JavaLangInvokeAccess JLIA
78 = SharedSecrets.getJavaLangInvokeAccess();
79
80 Set<String> speciesTypes = Set.of();
81
82 Set<String> invokerTypes = Set.of();
83
84 Map<String, Set<String>> dmhMethods = Map.of();
85
86 String mainArgument;
87
88 boolean ignoreVersion;
89
90 public GenerateJLIClassesPlugin() {
91 }
92
93 @Override
94 public String getName() {
95 return NAME;
96 }
97
98 @Override
99 public String getDescription() {
100 return DESCRIPTION;
101 }
102
103 @Override
104 public Set<State> getState() {
105 return EnumSet.of(State.AUTO_ENABLED, State.FUNCTIONAL);
106 }
107
108 @Override
152 "L_I", "L_L", "L_V", "LD_L", "LF_L", "LI_I", "LII_L", "LLI_L",
153 "LL_V", "LL_L", "L3_L", "L4_L", "L5_L", "L6_L", "L7_L",
154 "L8_L", "L9_L", "L10_L", "L10I_L", "L10II_L", "L10IIL_L",
155 "L11_L", "L12_L", "L13_L", "L14_L", "L14I_L", "L14II_L")
156 );
157 }
158
159 // Map from DirectMethodHandle method type to internal ID
160 private static final Map<String, Integer> DMH_METHOD_TYPE_MAP =
161 Map.of(
162 DMH_INVOKE_VIRTUAL, 0,
163 DMH_INVOKE_STATIC, 1,
164 DMH_INVOKE_SPECIAL, 2,
165 DMH_NEW_INVOKE_SPECIAL, 3,
166 DMH_INVOKE_INTERFACE, 4,
167 DMH_INVOKE_STATIC_INIT, 5
168 );
169
170 @Override
171 public void configure(Map<String, String> config) {
172 mainArgument = config.get(NAME);
173 ignoreVersion = Boolean.parseBoolean(config.get(IGNORE_VERSION));
174 }
175
176 public void initialize(ResourcePool in) {
177 // Start with the default configuration
178 speciesTypes = defaultSpecies().stream()
179 .map(type -> expandSignature(type))
180 .collect(Collectors.toSet());
181
182 invokerTypes = defaultInvokers();
183 validateMethodTypes(invokerTypes);
184
185 dmhMethods = defaultDMHMethods();
186 for (Set<String> dmhMethodTypes : dmhMethods.values()) {
187 validateMethodTypes(dmhMethodTypes);
188 }
189
190 // Extend the default configuration with the contents in the supplied
191 // input file - if none was supplied we look for the default file
192 if (mainArgument == null || !mainArgument.startsWith("@")) {
193 try (InputStream traceFile =
194 this.getClass().getResourceAsStream(DEFAULT_TRACE_FILE)) {
195 if (traceFile != null) {
196 readTraceConfig(
197 new BufferedReader(
198 new InputStreamReader(traceFile)).lines());
199 }
200 } catch (Exception e) {
201 throw new PluginException("Couldn't read " + DEFAULT_TRACE_FILE, e);
202 }
203 } else {
204 File file = new File(mainArgument.substring(1));
205 if (file.exists()) {
206 readTraceConfig(fileLines(file));
207 }
208 }
209 }
210
211 private boolean checkVersion(Runtime.Version linkedVersion) {
212 Runtime.Version baseVersion = Runtime.version();
213 if (baseVersion.major() != linkedVersion.major() ||
214 baseVersion.minor() != linkedVersion.minor()) {
215 return false;
216 }
217 return true;
218 }
219
220 private Runtime.Version getLinkedVersion(ResourcePool in) {
221 ModuleDescriptor.Version version = in.moduleView()
222 .findModule("java.base")
223 .get()
224 .descriptor()
225 .version()
226 .orElseThrow(() -> new PluginException("No version defined in "
227 + "the java.base being linked"));
228 return Runtime.Version.parse(version.toString());
229 }
230
231 private void readTraceConfig(Stream<String> lines) {
232 // Use TreeSet/TreeMap to keep things sorted in a deterministic
233 // order to avoid scrambling the layout on small changes and to
234 // ease finding methods in the generated code
235 speciesTypes = new TreeSet<>(speciesTypes);
236 invokerTypes = new TreeSet<>(invokerTypes);
237 TreeMap<String, Set<String>> newDMHMethods = new TreeMap<>();
238 for (Map.Entry<String, Set<String>> entry : dmhMethods.entrySet()) {
239 newDMHMethods.put(entry.getKey(), new TreeSet<>(entry.getValue()));
240 }
241 dmhMethods = newDMHMethods;
242 lines.map(line -> line.split(" "))
243 .forEach(parts -> {
244 switch (parts[0]) {
245 case "[BMH_RESOLVE]":
246 speciesTypes.add(expandSignature(parts[1]));
247 break;
248 case "[LF_RESOLVE]":
249 String methodType = parts[3];
250 validateMethodType(methodType);
251 if (parts[1].contains("Invokers")) {
252 invokerTypes.add(methodType);
253 } else if (parts[1].contains("DirectMethodHandle")) {
254 String dmh = parts[2];
255 // ignore getObject etc for now (generated
256 // by default)
257 if (DMH_METHOD_TYPE_MAP.containsKey(dmh)) {
258 addDMHMethodType(dmh, methodType);
259 }
260 }
261 break;
262 default: break; // ignore
263 }
264 });
265 }
266
267 private void addDMHMethodType(String dmh, String methodType) {
268 validateMethodType(methodType);
269 Set<String> methodTypes = dmhMethods.get(dmh);
270 if (methodTypes == null) {
271 methodTypes = new TreeSet<>();
272 dmhMethods.put(dmh, methodTypes);
273 }
274 methodTypes.add(methodType);
275 }
276
277 private Stream<String> fileLines(File file) {
278 try {
279 return Files.lines(file.toPath());
280 } catch (IOException io) {
281 throw new PluginException("Couldn't read file");
282 }
283 }
284
285 private void validateMethodTypes(Set<String> dmhMethodTypes) {
292 String[] typeParts = type.split("_");
293 // check return type (second part)
294 if (typeParts.length != 2 || typeParts[1].length() != 1
295 || "LJIFDV".indexOf(typeParts[1].charAt(0)) == -1) {
296 throw new PluginException(
297 "Method type signature must be of form [LJIFD]*_[LJIFDV]");
298 }
299 // expand and check arguments (first part)
300 expandSignature(typeParts[0]);
301 }
302
303 private static void requireBasicType(char c) {
304 if ("LIJFD".indexOf(c) < 0) {
305 throw new PluginException(
306 "Character " + c + " must correspond to a basic field type: LIJFD");
307 }
308 }
309
310 @Override
311 public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
312 if (ignoreVersion) {
313 System.out.println(
314 PluginsResourceBundle
315 .getMessage(IGNORE_VERSION_WARNING));
316 } else if (!checkVersion(getLinkedVersion(in))) {
317 // The linked images are not version compatible
318 if (mainArgument != null) {
319 // Log a mismatch warning if an argument was specified
320 System.out.println(
321 PluginsResourceBundle
322 .getMessage(VERSION_MISMATCH_WARNING,
323 getLinkedVersion(in),
324 Runtime.version()));
325 }
326 in.transformAndCopy(entry -> entry, out);
327 return out.build();
328 }
329
330 initialize(in);
331 // Copy all but DMH_ENTRY to out
332 in.transformAndCopy(entry -> {
333 // filter out placeholder entries
334 if (entry.path().equals(DIRECT_METHOD_HOLDER_ENTRY) ||
335 entry.path().equals(DELEGATING_METHOD_HOLDER_ENTRY) ||
336 entry.path().equals(INVOKERS_HOLDER_ENTRY) ||
337 entry.path().equals(BASIC_FORMS_HOLDER_ENTRY)) {
338 return null;
339 } else {
340 return entry;
341 }
342 }, out);
343
344 // Generate BMH Species classes
345 speciesTypes.forEach(types -> generateBMHClass(types, out));
346
347 // Generate LambdaForm Holder classes
348 generateHolderClasses(out);
349
350 // Let it go
351 speciesTypes = null;
352 invokerTypes = null;
353 dmhMethods = null;
354
355 return out.build();
356 }
357
358 @SuppressWarnings("unchecked")
359 private void generateBMHClass(String types, ResourcePoolBuilder out) {
360 try {
361 // Generate class
362 Map.Entry<String, byte[]> result =
363 JLIA.generateConcreteBMHClassBytes(types);
364 String className = result.getKey();
365 byte[] bytes = result.getValue();
366
367 // Add class to pool
368 ResourcePoolEntry ndata = ResourcePoolEntry.create(
369 "/java.base/" + className + ".class",
370 bytes);
371 out.add(ndata);
372 } catch (Exception ex) {
373 throw new PluginException(ex);
374 }
|