/* * This file is part of JOP, the Java Optimized Processor * see <http://www.jopdesign.com/> * * Copyright (C) 2011, Stefan Hepp (stefan@stefant.org). * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.jopdesign.common.tools; import com.jopdesign.common.AppInfo; import com.jopdesign.common.ClassInfo; import com.jopdesign.common.EmptyAppEventHandler; import com.jopdesign.common.MethodCode; import com.jopdesign.common.MethodInfo; import com.jopdesign.common.logger.LogConfig; import com.jopdesign.common.misc.ClassInfoNotFoundException; import com.jopdesign.common.misc.MethodNotFoundException; import com.jopdesign.common.type.MemberID; import org.apache.bcel.generic.InstructionHandle; import org.apache.log4j.Logger; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * This class can load and store source-line and -file attributes of methods to a separate file. * This is needed when the source file of an instruction is not the same as the source file as the class. * * TODO there is a lot which can be done here: optionally store in custom method attributes, support for * flowfacts like loopbounds, .. * * File format is: * [<fqmethodname>] * <first-index>-<last-index>: <linenr> <fqsourceclass> * ... * <empty line> * * @author Stefan Hepp (stefan@stefant.org) */ public class SourceLineStorage extends EmptyAppEventHandler { private static class SourceLineEntry { private int start; private int end; private int line; private String className; private SourceLineEntry(int start, int end, int line, String className) { this.start = start; this.end = end; this.line = line; this.className = className; } public static List<SourceLineEntry> findSourceEntries(MethodCode code) { List<SourceLineEntry> entries = new ArrayList<SourceLineEntry>(); int start = -1; int line = 0; String className = null; InstructionHandle[] il = code.getInstructionList(true, false).getInstructionHandles(); for (int i = 0; i < il.length; i++) { InstructionHandle ih = il[i]; String src = code.getSourceClassAttribute(ih); if (start >= 0) { // we are currently processing an entry, check if we reached the end if (src != null || code.getLineNumberEntry(ih,false) != null) { // a new line entry starts at this line entries.add(new SourceLineEntry(start, i-1, line, className)); start = -1; } } // we have a new entry here if (src != null) { start = i; line = code.getLineNumber(ih); className = src; } } // need to process the leftovers if (start >= 0) { entries.add(new SourceLineEntry(start, il.length-1, line, className)); } return entries; } public void applyEntry(MethodCode code, InstructionHandle[] il) { ClassInfo cls = null; try { cls = AppInfo.getSingleton().getClassInfo(className,false); } catch (ClassInfoNotFoundException e) { logger.error("Could not find class " + className + ", skipping source line entry.", e); return; } if (cls == null) { logger.info("Could not find class " + className + ", skipping. Maybe no longer used?"); return; } code.setLineNumber(il[start], cls, line); for (int i = start+1; i <= end; i++) { code.clearLineNumber(il[i]); } } public static SourceLineEntry readEntry(String entry) { int p1 = entry.indexOf('-'); int p2 = entry.indexOf(':', p1+1); int p3 = entry.indexOf(' ', p2+2); int start = Integer.parseInt(entry.substring(0, p1)); int end = Integer.parseInt(entry.substring(p1+1, p2)); int line = Integer.parseInt(entry.substring(p2+2, p3)); String className = entry.substring(p3+1); return new SourceLineEntry(start, end, line, className); } public void writeEntry(PrintWriter writer) { writer.printf("%d-%d: %d %s", start, end, line, className); writer.println(); } } private static final Logger logger = Logger.getLogger(LogConfig.LOG_APPINFO+".SourceLineStorage"); private final File storage; private Map<MemberID, List<SourceLineEntry>> sourceLineMap; /** * Create a new storage manager. * @param storage the file where sourcefile annotations are kept. */ public SourceLineStorage(File storage) { // TODO we could optionally use a method attribute for storage too, but this would add constant-pool // entries which we need to remove later, so we stay with files for now.. this.storage = storage; } @Override public void onRegisterEventHandler(AppInfo appInfo) { try { readSourceInfos(); } catch (IOException e) { logger.error("Error reading source line file: "+e.getMessage(), e); } } @Override public void onCreateClass(ClassInfo classInfo, boolean loaded) { if (!loaded) return; try { if (AppInfo.getSingleton().getClassFile(classInfo).getTime() > storage.lastModified()) { // TODO we should skip loading for all classes here, but what to do about classes we have already loaded? logger.error("Classfile of "+classInfo+" is newer than source line file "+storage+ ", not loading source lines for this class!"); return; } for (MethodInfo method : classInfo.getMethods()) { if (!method.hasCode()) continue; List<SourceLineEntry> entries = sourceLineMap.get(method.getMemberID()); if (entries == null) continue; applySourceInfos(method, entries); } } catch (FileNotFoundException e) { logger.warn("Could not load source class file for timestamp check", e); } } /** * Store all source file and -line annotations of all classes to the storage file. */ public void storeSourceInfos() { PrintWriter writer; try { writer = new PrintWriter(new BufferedWriter(new FileWriter(storage, false))); } catch (IOException e) { logger.error("Error opening file "+storage+" for writing, not writing source line infos!", e); return; } for (ClassInfo cls : AppInfo.getSingleton().getClassInfos()) { for (MethodInfo method : cls.getMethods()) { if (!method.hasCode()) continue; writeSourceInfos(method.getCode(), writer); } } writer.close(); } /** * Load all source file and -line annotations for all classes from the storage file. */ public void loadSourceInfos() { if (sourceLineMap == null) { try { readSourceInfos(); } catch (IOException e) { logger.error("Error reading sourceline file "+storage, e); } } Map<MethodInfo, List<SourceLineEntry>> methodMap = new HashMap<MethodInfo, List<SourceLineEntry>>(sourceLineMap.size()); for (MemberID mID : sourceLineMap.keySet()) { try { MethodInfo method = AppInfo.getSingleton().getMethodInfo(mID); methodMap.put(method, sourceLineMap.get(mID)); } catch (MethodNotFoundException ignored) { logger.warn("No method for entry "+mID+" in " +storage+" found!"); } } for (MethodInfo method : methodMap.keySet()) { try { if (AppInfo.getSingleton().getClassFile(method.getClassInfo()).getTime() > storage.lastModified()) { logger.error("One or more class files are newer than annotation file "+storage+ ", not loading source line annotations!"); return; } } catch (FileNotFoundException e) { logger.error("Could not get class file for class "+method.getClassInfo()+", not loading source lines!", e); return; } } for (Map.Entry<MethodInfo,List<SourceLineEntry>> entry : methodMap.entrySet()) { MethodInfo method = entry.getKey(); applySourceInfos(method, entry.getValue()); } } private void applySourceInfos(MethodInfo method, List<SourceLineEntry> entries) { MethodCode code = method.getCode(); InstructionHandle[] il = code.getInstructionList(true, false).getInstructionHandles(); for (SourceLineEntry sle : entries) { sle.applyEntry(code, il); } } private void readSourceInfos() throws IOException { BufferedReader reader = new BufferedReader(new FileReader(storage)); sourceLineMap = new HashMap<MemberID, List<SourceLineEntry>>(); String entry; List<SourceLineEntry> entries = null; //noinspection NestedAssignment while ((entry = reader.readLine()) != null ) { entry = entry.trim(); if (entry.startsWith("[")) { int p1 = entry.indexOf(']'); String methodID = entry.substring(1, p1); entries = new ArrayList<SourceLineEntry>(); MemberID id = MemberID.parse(methodID); sourceLineMap.put(id, entries); } else if (!"".equals(entry)) { assert entries != null; entries.add(SourceLineEntry.readEntry(entry)); } } reader.close(); } private void writeSourceInfos(MethodCode code, PrintWriter writer) { List<SourceLineEntry> entries = SourceLineEntry.findSourceEntries(code); if (entries.isEmpty()) { return; } writer.println("["+code.getMethodInfo().getMemberID()+"]"); for (SourceLineEntry entry : entries) { entry.writeEntry(writer); } writer.println(); } }