/* * Copyright (c) 2008, 2015, 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 com.sun.btrace.compiler; import com.sun.btrace.org.objectweb.asm.ClassReader; import com.sun.btrace.org.objectweb.asm.ClassWriter; import com.sun.btrace.org.objectweb.asm.Opcodes; import com.sun.btrace.org.objectweb.asm.tree.ClassNode; import javax.annotation.processing.Processor; import com.sun.source.util.JavacTask; import com.sun.btrace.util.Messages; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.io.Writer; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.tools.Diagnostic; import javax.tools.Diagnostic.Kind; import javax.tools.DiagnosticCollector; import javax.tools.FileObject; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; /** * Compiler for a BTrace program. Note that a BTrace * program is a Java program that is specially annotated * and can *not* use many Java constructs (essentially java--). * We use JSR 199 API to compile BTrace program but validate * the program (for BTrace safety rules) using JSR 269 and * javac's Tree API. * * @author A. Sundararajan */ public class Compiler { // JSR 199 compiler private JavaCompiler compiler; private StandardJavaFileManager stdManager; // null means no preprocessing isf done. public List<String> includeDirs; public Compiler(String includePath) { if (includePath != null) { includeDirs = new ArrayList<>(); String[] paths = includePath.split(File.pathSeparator); includeDirs.addAll(Arrays.asList(paths)); } this.compiler = ToolProvider.getSystemJavaCompiler(); this.stdManager = compiler.getStandardFileManager(null, null, null); } public Compiler() { this(null); } private static void usage(String msg) { System.err.println(msg); System.exit(1); } private static void usage() { usage(Messages.get("btracec.usage")); } // simple test main @SuppressWarnings("DefaultCharset") public static void main(String[] args) throws Exception { if (args.length == 0) { usage(); } String classPath = "."; String outputDir = "."; String includePath = null; boolean trusted = false; int count = 0; boolean classPathDefined = false; boolean outputDirDefined = false; boolean includePathDefined = false; boolean trustedDefined = false; for (;;) { if (args[count].charAt(0) == '-') { if (args.length <= count + 1) { usage(); } if ((args[count].equals("-cp") || args[count].equals("-classpath")) && !classPathDefined) { classPath = args[++count]; classPathDefined = true; } else if (args[count].equals("-d") && !outputDirDefined) { outputDir = args[++count]; outputDirDefined = true; } else if (args[count].equals("-I") && !includePathDefined) { includePath = args[++count]; includePathDefined = true; } else if ((args[count].equals("-unsafe") || args[count].equals("-trusted")) && !trustedDefined) { trusted = true; trustedDefined = true; } else { usage(); } count++; if (count >= args.length) { break; } } else { break; } } if (args.length <= count) { usage(); } File[] files = new File[args.length - count]; for (int i = 0; i < files.length; i++) { files[i] = new File(args[i + count]); if (!files[i].exists()) { usage("File not found: " + files[i]); } } Compiler compiler = new Compiler(includePath); classPath += File.pathSeparator + System.getProperty("java.class.path"); Map<String, byte[]> classes = compiler.compile(files, new PrintWriter(System.err), ".", classPath); if (classes != null) { // write .class files. for (Map.Entry<String, byte[]> c : classes.entrySet()) { String name = c.getKey().replace(".", File.separator); int index = name.lastIndexOf(File.separatorChar); String dir = outputDir + File.separator; if (index != -1) { dir += name.substring(0, index); } new File(dir).mkdirs(); String file; if (index != -1) { file = name.substring(index + 1); } else { file = name; } file += ".class"; File out = new File(dir, file); try (FileOutputStream fos = new FileOutputStream(out)) { fos.write(c.getValue()); } } } else { // fail System.exit(1); } } public Map<String, byte[]> compile(String fileName, String source, Writer err, String sourcePath, String classPath) { // create a new memory JavaFileManager MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager, includeDirs); // prepare the compilation unit List<JavaFileObject> compUnits = new ArrayList<>(1); compUnits.add(MemoryJavaFileManager.makeStringSource(fileName, source, includeDirs)); return compile(manager, compUnits, err, sourcePath, classPath); } public Map<String, byte[]> compile(File file, Writer err, String sourcePath, String classPath) { File[] files = new File[1]; files[0] = file; return compile(files, err, sourcePath, classPath); } public Map<String, byte[]> compile(File[] files, Writer err, String sourcePath, String classPath) { Iterable<? extends JavaFileObject> compUnits = stdManager.getJavaFileObjects(files); List<JavaFileObject> preprocessedCompUnits = new ArrayList<>(); try { for (JavaFileObject jfo : compUnits) { preprocessedCompUnits.add(MemoryJavaFileManager.preprocessedFileObject(jfo, includeDirs)); } } catch (IOException ioExp) { throw new RuntimeException(ioExp); } return compile(preprocessedCompUnits, err, sourcePath, classPath); } public Map<String, byte[]> compile( Iterable<? extends JavaFileObject> compUnits, Writer err, String sourcePath, String classPath) { // create a new memory JavaFileManager MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager, includeDirs); return compile(manager, compUnits, err, sourcePath, classPath); } private Map<String, byte[]> compile(MemoryJavaFileManager manager, Iterable<? extends JavaFileObject> compUnits, Writer err, String sourcePath, final String classPath) { // to collect errors, warnings etc. DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>(); // javac options List<String> options = new ArrayList<>(); options.add("-Xlint:all"); options.add("-g:lines"); options.add("-deprecation"); options.add("-source"); options.add("1.7"); options.add("-target"); options.add("1.7"); if (sourcePath != null) { options.add("-sourcepath"); options.add(sourcePath); } if (classPath != null) { options.add("-classpath"); options.add(classPath); } // create a compilation task JavacTask task = (JavacTask) compiler.getTask(err, manager, diagnostics, options, null, compUnits); Verifier btraceVerifier = new Verifier(); task.setTaskListener(btraceVerifier); // we add BTrace Verifier as a (JSR 269) Processor List<Processor> processors = new ArrayList<>(1); processors.add(btraceVerifier); task.setProcessors(processors); final PrintWriter perr = (err instanceof PrintWriter) ? (PrintWriter) err : new PrintWriter(err); // print dignostics messages in case of failures. if (task.call() == false || containsErrors(diagnostics)) { for (Diagnostic diagnostic : diagnostics.getDiagnostics()) { printDiagnostic(diagnostic, perr); } perr.flush(); return null; } // collect .class bytes of all compiled classes try { Map<String, byte[]> classBytes = manager.getClassBytes(); List<String> classNames = btraceVerifier.getClassNames(); Map<String, byte[]> result = new HashMap<>(); for (String name : classNames) { if (classBytes.containsKey(name)) { dump(name + "_before", classBytes.get(name)); ClassReader cr = new ClassReader(classBytes.get(name)); ClassWriter cw = new CompilerClassWriter(classPath, perr); cr.accept(new Postprocessor(cw), ClassReader.EXPAND_FRAMES + ClassReader.SKIP_DEBUG); result.put(name, cw.toByteArray()); dump(name + "_after", cw.toByteArray()); } } return result; } finally { try { manager.close(); } catch (IOException exp) { } } } private void printDiagnostic(Diagnostic diagnostic, final PrintWriter perr) { perr.println(diagnostic); } /** Checks if the list of diagnostic messages contains at least one error. Certain * {@link JavacTask} implementations may return success error code even though errors were * reported. */ private boolean containsErrors(DiagnosticCollector<?> diagnostics) { for (Diagnostic<?> diagnostic : diagnostics.getDiagnostics()) { if (diagnostic.getKind() == Kind.ERROR) { return true; } } return false; } private void dump(String name, byte[] code) { // OutputStream os = null; // try { // name = name.replace(".", "/") + ".class"; // File f = new File("/tmp/" + name); // if (!f.exists()) { // f.getParentFile().createNewFile(); // } // os = new FileOutputStream(f); // os.write(code); // } catch (IOException e) { // // } finally { // if (os != null) { // try { // os.close(); // } catch (IOException e) {} // } // } } }