/*
 * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.tools.jlink.internal.plugins;

import java.util.Map;

import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.tools.jlink.plugin.Plugin;
import jdk.tools.jlink.plugin.ResourcePool;
import jdk.tools.jlink.plugin.ResourcePoolBuilder;
import jdk.tools.jlink.plugin.ResourcePoolEntry;

Base plugin to update a static field in java.lang.VersionProps Fields to be updated must not be final such that values are not constant replaced at compile time and initialization code is generated. We assume that the initialization code only has ldcs, method calls and field instructions.
/** * Base plugin to update a static field in java.lang.VersionProps * * Fields to be updated must not be final such that values are not constant * replaced at compile time and initialization code is generated. * We assume that the initialization code only has ldcs, method calls and * field instructions. */
abstract class VersionPropsPlugin implements Plugin { private static final String VERSION_PROPS_CLASS = "/java.base/java/lang/VersionProps.class"; private final String name; private final String field; private String value;
Params:
  • field – The name of the java.lang.VersionProps field to be redefined
  • option – The option name
/** * @param field The name of the java.lang.VersionProps field to be redefined * @param option The option name */
protected VersionPropsPlugin(String field, String option) { this.field = field; this.name = option; }
Shorthand constructor for when the option name can be derived from the name of the field.
Params:
  • field – The name of the java.lang.VersionProps field to be redefined
/** * Shorthand constructor for when the option name can be derived from the * name of the field. * * @param field The name of the java.lang.VersionProps field to be redefined */
protected VersionPropsPlugin(String field) { this(field, field.toLowerCase().replace('_', '-')); } @Override public String getName() { return name; } @Override public String getDescription() { return PluginsResourceBundle.getDescription(name); } @Override public Category getType() { return Category.TRANSFORMER; } @Override public boolean hasArguments() { return true; } @Override public boolean hasRawArgument() { return true; } @Override public String getArgumentsDescription() { return PluginsResourceBundle.getArgument(name); } @Override public void configure(Map<String, String> config) { var v = config.get(name); if (v == null) throw new AssertionError(); value = v; } private boolean redefined = false; private byte[] redefine(byte[] classFile) { var cr = new ClassReader(classFile); var cw = new ClassWriter(0); cr.accept(new ClassVisitor(Opcodes.ASM7, cw) { @Override public MethodVisitor visitMethod(int access, String name, String desc, String sig, String[] xs) { if (name.equals("<clinit>")) return new MethodVisitor(Opcodes.ASM7, super.visitMethod(access, name, desc, sig, xs)) { private Object pendingLDC = null; private void flushPendingLDC() { if (pendingLDC != null) { super.visitLdcInsn(pendingLDC); pendingLDC = null; } } @Override public void visitLdcInsn(Object value) { flushPendingLDC(); pendingLDC = value; } @Override public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { flushPendingLDC(); super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); } @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { if (opcode == Opcodes.PUTSTATIC && name.equals(field)) { // assert that there is a pending ldc // for the old value if (pendingLDC == null) { throw new AssertionError("No load " + "instruction found for field " + field + " in static initializer of " + VERSION_PROPS_CLASS); } // forget about it pendingLDC = null; // and add an ldc for the new value super.visitLdcInsn(value); redefined = true; } else { flushPendingLDC(); } super.visitFieldInsn(opcode, owner, name, desc); } }; else return super.visitMethod(access, name, desc, sig, xs); } }, 0); return cw.toByteArray(); } @Override public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) { in.transformAndCopy(res -> { if (res.type().equals(ResourcePoolEntry.Type.CLASS_OR_RESOURCE)) { if (res.path().equals(VERSION_PROPS_CLASS)) { return res.copyWithContent(redefine(res.contentBytes())); } } return res; }, out); if (!redefined) throw new AssertionError(field); return out.build(); } }