1 /*
  2  * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 package org.openjdk.asmtools;
 24 
 25 import java.lang.reflect.Method;
 26 import java.lang.reflect.InvocationTargetException;
 27 import java.util.ArrayList;
 28 import java.nio.file.Path;
 29 import java.nio.file.InvalidPathException;
 30 import java.nio.file.Files;
 31 import java.io.PrintWriter;
 32 import java.io.IOException;
 33 import org.openjdk.asmtools.jdis.uEscWriter;
 34 
 35 /**
 36  * An entry point to AsmTools for use with the jtreg '@run driver' command.
 37  * Unlike 'Main', never invokes 'System.exit' (which crashes jtreg).
 38  *
 39  * Also adjusts file paths:
 40  *
 41  * - For jasm and jcoder, source files are expected to appear in ${test.src},
 42  *   and output is sent to ${test.classes}
 43  *
 44  * - For other tools, class files are expected to appear in ${test.classes},
 45  *   and output is sent to the scratch working directory
 46  *
 47  * Example jtreg usage:
 48  *
 49  * @library /test/lib
 50  * @build org.openjdk.asmtools.* org.openjdk.asmtools.jasm.*
 51  * @run driver org.openjdk.asmtools.JtregDriver jasm -strict TestFile.jasm
 52  */
 53 public class JtregDriver {
 54 
 55     public static void main(String... args) throws Throwable {
 56         // run AltLoaderMain from a class loader that prefers the modified AsmTools classes
 57         ClassLoader loader = JtregDriver.class.getClassLoader();
 58         Path file = Path.of(loader.getResource("org/openjdk/asmtools/JtregDriver.class").toURI());
 59         Path root = file.getParent().getParent().getParent().getParent();
 60         ClassLoader altLoader = new AsmToolsClassLoader(root);
 61         Class<?> altMain = altLoader.loadClass(AltLoaderMain.class.getName());
 62         try {
 63             altMain.getMethod("main", String[].class).invoke(null, (Object) args);
 64         } catch (InvocationTargetException e) {
 65             throw e.getCause();
 66         }
 67     }
 68 
 69     public static class AltLoaderMain {
 70 
 71         public static void main(String... args) throws IOException {
 72             if (args.length == 0) {
 73                 throw new IllegalArgumentException("Missing asmtools command");
 74             }
 75             String cmd = args[0];
 76             if (!cmd.equals("jasm") && !cmd.equals("jdis") && !cmd.equals("jcoder")
 77                     && !cmd.equals("jdec") && !cmd.equals("jcdec")) {
 78                 throw new IllegalArgumentException("Unrecognized asmtools command: " + cmd);
 79             }
 80             boolean isAssembler = cmd.equals("jasm") || cmd.equals("jcoder");
 81             String srcDir = System.getProperty("test.src", ".");
 82             String clsDir = System.getProperty("test.classes", ".");
 83             String fileDir = isAssembler ? srcDir : clsDir;
 84 
 85             ArrayList<String> toolArgList = new ArrayList<String>();
 86 
 87             if (isAssembler) {
 88                 Path destPath = Path.of(clsDir);
 89                 if (!Files.exists(destPath)) {
 90                     // jtreg creates classes dir on demand, might not have happened yet
 91                     Files.createDirectories(destPath);
 92                 }
 93                 toolArgList.add("-d");
 94                 toolArgList.add(clsDir);
 95             }
 96 
 97             boolean isOptionArg = false; // marks an argument to a previous option
 98             for (int i = 1; i < args.length; i++) {
 99                 String arg = args[i];
100                 if (isOptionArg) {
101                     isOptionArg = false; // reset for next
102                 } else {
103                     if (arg.equals("-d")) {
104                         isOptionArg = true;
105                     } else if (!arg.startsWith("-") && !arg.startsWith("/")) {
106                         // adjust filename
107                         arg = Path.of(fileDir, arg).toString();
108                     }
109                 }
110                 toolArgList.add(arg);
111             }
112 
113             String[] toolArgs = toolArgList.toArray(new String[0]);
114             boolean success = switch (cmd) {
115                 case "jasm" -> {
116                     PrintWriter out = new PrintWriter(System.out);
117                     yield new org.openjdk.asmtools.jasm.Main(out, "jasm").compile(toolArgs);
118                 }
119                 case "jdis" -> {
120                     PrintWriter out = new PrintWriter(new uEscWriter(System.out));
121                     PrintWriter err = new PrintWriter(System.err);
122                     yield new org.openjdk.asmtools.jdis.Main(out, err, "jdis").disasm(toolArgs);
123                 }
124                 case "jcoder" -> {
125                     PrintWriter out = new PrintWriter(System.out);
126                     yield new org.openjdk.asmtools.jcoder.Main(out, "jcoder").compile(toolArgs);
127                 }
128                 case "jdec" -> {
129                     PrintWriter out = new PrintWriter(new uEscWriter(System.out));
130                     PrintWriter err = new PrintWriter(System.err);
131                     yield new org.openjdk.asmtools.jdec.Main(out, err, "jdec").decode(toolArgs);
132                 }
133                 case "jcdec" -> {
134                     PrintWriter out = new PrintWriter(new uEscWriter(System.out));
135                     yield new org.openjdk.asmtools.jcdec.Main(out, "jcdec").decode(toolArgs);
136                 }
137                 default -> throw new AssertionError();
138             };
139             if (!success) {
140                 throw new RuntimeException("asmtools execution failed");
141             }
142         }
143 
144     }
145 
146     /**
147      * Class loader for the AsmTools classes. Allows classes colocated with
148      * JtregDriver to have priority over versions of the classes loaded by
149      * jtreg (which includes its own copy of AsmTools).
150      */
151     private static class AsmToolsClassLoader extends ClassLoader {
152         private final Path root;
153         private final String separator;
154 
155         public AsmToolsClassLoader(Path root) {
156             super(AsmToolsClassLoader.class.getClassLoader());
157             this.root = root;
158             this.separator = root.getFileSystem().getSeparator();
159         }
160 
161         protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
162             if (name.startsWith("org.openjdk.asmtools.")) {
163                 Class<?> result = findClass(name);
164                 if (resolve) resolveClass(result);
165                 return result;
166             } else {
167                 return super.loadClass(name, resolve);
168             }
169         }
170 
171         protected Class<?> findClass(String name) throws ClassNotFoundException {
172             String filename = name.replace(".",separator) + ".class";
173             try {
174                 Path classFile = root.resolve(filename);
175                 byte[] bytes = Files.readAllBytes(classFile);
176                 return defineClass(name, bytes, 0, bytes.length);
177             } catch (InvalidPathException | IOException e) {
178                 throw new ClassNotFoundException("can't read class " + filename, e);
179             }
180         }
181 
182     }
183 
184 }