/******************************************************************************* * Copyright 2014, * Luis Pina <luis@luispina.me>, * Michael Hicks <mwh@cs.umd.edu> * * This file is part of Rubah. * * Rubah 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. * * Rubah 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 Rubah. If not, see <http://www.gnu.org/licenses/>. *******************************************************************************/ package rubah.tools; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.apache.commons.io.IOUtils; import org.javatuples.Pair; import org.javatuples.Quartet; import org.javatuples.Quintet; import org.javatuples.Sextet; import org.javatuples.Tuple; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Opcodes; import rubah.bytecode.transformers.ClassNameGatherer; import rubah.bytecode.transformers.DecreaseClassMethodsProtection; import rubah.bytecode.transformers.UpdatableClassInfoGatherer; import rubah.framework.Clazz; import rubah.framework.DelegatingNamespace; import rubah.framework.Field; import rubah.framework.Method; import rubah.framework.Namespace; import rubah.framework.Type; import rubah.runtime.Version; import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter; import com.beust.jcommander.ParameterException; import com.beust.jcommander.converters.FileConverter; public class UpdatableJarAnalyzer extends ReadWriteTool implements Opcodes { private static class ArgParser { @Parameter( converter=FileConverter.class, description="Input jar file", names={"-i","--in-jar"}, required=true) private File injar; @Parameter( converter=FileConverter.class, description="Output file", required=true, names={"-o","--out"}) private File outFile; @Parameter( description="Updatable packages (empty for all classes in jar)", required=false, variableArity=true, names={"-p","--packages"}) private List<String> packages = new LinkedList<String>(); } private List<String> packages; private Namespace namespace = new Namespace(); private ClassVisitor visitor; public static void main(String[] args) throws IOException { ArgParser parser = new ArgParser(); JCommander argParser = new JCommander(parser); try { argParser.parse(args); } catch (ParameterException e) { System.out.println(e.getMessage()); argParser.usage(); System.exit(1); } UpdatableJarAnalyzer analyzer = new UpdatableJarAnalyzer(); analyzer.inFile = parser.injar; analyzer.outFile = parser.outFile; analyzer.packages = new LinkedList<String>(); for (String pack : parser.packages) { analyzer.packages.add(pack.replace('.', File.separatorChar)); } analyzer.processJar(); } @Override protected void foundClassFile(String name, InputStream inputStream) throws IOException { boolean process = true; if (!this.packages.isEmpty()) { process = false; for (String pack : this.packages) { if (name.startsWith(pack)) { process = true; break; } } } if (!process) { return; } new ClassReader(inputStream).accept(this.visitor, 0); } @Override public void processJar() throws IOException { ClassNameGatherer nameGatherer = new ClassNameGatherer(); this.visitor = nameGatherer; super.processJar(); Namespace newNamespace = new DelegatingNamespace(this.namespace, nameGatherer.getClassNames()); Version version = new Version(newNamespace); UpdatableClassInfoGatherer infoGatherer = new UpdatableClassInfoGatherer(version); this.visitor = new DecreaseClassMethodsProtection(infoGatherer); super.processJar(); infoGatherer.computeOverloads(); this.writeOutFile( new VersionDescriptor( newNamespace, infoGatherer.getOverloads())); } private void writeOutFile(VersionDescriptor descriptor) throws IOException { ObjectOutputStream outStream = new ObjectOutputStream(new FileOutputStream(this.outFile)); AnalysisData data = new AnalysisData(); for (Clazz c : descriptor.namespace.getDefinedClasses()) { data.updatableClasses.add(new ClassData(c)); } for (Entry<Pair<Clazz, Method>, Integer> entry : descriptor.overloads.entrySet()) { data.overloads.put( new Pair<ClassData, MethodData>( new ClassData(entry.getKey().getValue0()), new MethodData(entry.getKey().getValue1())), entry.getValue()); } outStream.writeObject(data); outStream.close(); } public static class VersionDescriptor { public final Namespace namespace; public final Map<Pair<Clazz, Method>, Integer> overloads; public VersionDescriptor(Namespace namespace, Map<Pair<Clazz, Method>, Integer> overloads) { this.namespace = namespace; this.overloads = overloads; } } public static VersionDescriptor readFile(File inJar, Namespace namespace) throws FileNotFoundException, IOException { return readFile(IOUtils.toByteArray(new FileInputStream(inJar)), namespace); } public static VersionDescriptor readFile(byte[] updateDescriptor, Namespace namespace) throws IOException { ObjectInputStream outStream = new ObjectInputStream(new ByteArrayInputStream(updateDescriptor)); Set<Clazz> classes = new HashSet<Clazz>(); Map<Pair<Clazz, Method>, Integer> overloads = new HashMap<Pair<Clazz,Method>, Integer>(); AnalysisData data = null; try { data = (AnalysisData) outStream.readObject(); } catch (ClassNotFoundException e) { throw new Error(e); } finally { outStream.close(); } HashSet<String> classNames = new HashSet<String>(); for (ClassData cd : data.updatableClasses) { classNames.add(Type.getType(cd.tuple.getValue0()).getClassName()); } Namespace newNamespace = new DelegatingNamespace(namespace, classNames); for (ClassData cd : data.updatableClasses) { Clazz c = cd.toClass(newNamespace); for (MethodData md : cd.tuple.getValue3()) { List<Clazz> args = new LinkedList<Clazz>(); for (String argFqn : md.tuple.getValue3()) { args.add(newNamespace.getClass(Type.getType(argFqn))); } Method m = new Method( md.tuple.getValue0(), md.tuple.getValue1(), newNamespace.getClass(Type.getType(md.tuple.getValue2())), args); Integer overload = data.overloads.get(new Pair<ClassData, MethodData>(cd, md)); if (overload != null) { overloads.put(new Pair<Clazz, Method>(c, m), overload); } } classes.add(c); } return new VersionDescriptor(newNamespace, overloads); } private static abstract class ElementData<T extends Tuple> implements Serializable { protected T tuple; @Override public int hashCode() { return this.tuple.hashCode(); } @Override public boolean equals(Object obj) { if (obj instanceof ElementData) { return this.tuple.equals(((ElementData) obj).tuple); } return false; } @Override public String toString() { return this.tuple.toString(); } } public static class ClassData extends ElementData<Sextet<String, String, Set<FieldData>, Set<MethodData>, Boolean, Set<String>>> { public ClassData(Clazz c) { String fqn = c.getASMType().getDescriptor(); String parentFqn = null; if (c.getParent() != null) { parentFqn = c.getParent().getASMType().getDescriptor(); } Set<String> interfaces = new HashSet<String>(); for (Clazz iface : c.getInterfaces()) { interfaces.add(iface.getASMType().getDescriptor()); } Set<FieldData> fields = new HashSet<FieldData>(); for (Field f : c.getFields()) { fields.add(new FieldData(f)); } Set<MethodData> methods = new HashSet<MethodData>(); for (Method m : c.getMethods()) { methods.add(new MethodData(m)); } this.tuple = new Sextet<String, String, Set<FieldData>, Set<MethodData>, Boolean, Set<String>>( fqn, parentFqn, fields, methods, c.isInterface(), interfaces); } public Clazz toClass(Namespace namespace) { Clazz ret = namespace.getClass(Type.getType(this.tuple.getValue0()), true); ret.setInterface(this.tuple.getValue4()); if (this.tuple.getValue1() != null) { ret.setParent(namespace.getClass(Type.getType(this.tuple.getValue1()))); } for (String iface : this.tuple.getValue5()) { ret.getInterfaces().add(namespace.getClass(Type.getType(iface))); } for (FieldData fd : this.tuple.getValue2()) { Field f = new Field( fd.tuple.getValue0(), fd.tuple.getValue1(), namespace.getClass(Type.getType(fd.tuple.getValue2())), fd.tuple.getValue3()); ret.getFields().add(f); } for (MethodData md : this.tuple.getValue3()) { List<Clazz> args = new LinkedList<Clazz>(); for (String argFqn : md.tuple.getValue3()) { args.add(namespace.getClass(Type.getType(argFqn))); } Method m = new Method( md.tuple.getValue0(), md.tuple.getValue1(), namespace.getClass(Type.getType(md.tuple.getValue2())), args); m.setBodyMD5(md.tuple.getValue4()); ret.addMethod(m); } return ret; } } public static class FieldData extends ElementData<Quartet<Integer, String, String, Boolean>> { public FieldData(Field f) { this.tuple = new Quartet<Integer, String, String, Boolean>( f.getAccess(), f.getName(), f.getType().getASMType().getDescriptor(), f.isConstant()); } } public static class MethodData extends ElementData<Quintet<Integer, String, String, List<String>, String>> { public MethodData(Method m) { List<String> args = new LinkedList<String>(); for (Clazz arg : m.getArgTypes()) { args.add(arg.getASMType().getDescriptor()); } this.tuple = new Quintet<Integer, String, String, List<String>, String>( m.getAccess(), m.getName(), m.getRetType().getASMType().getDescriptor(), args, m.getBodyMD5()); } } private static class AnalysisData implements Serializable { private Set<ClassData> updatableClasses = new HashSet<ClassData>(); private Map<Pair<ClassData, MethodData>, Integer> overloads = new HashMap<Pair<ClassData,MethodData>, Integer>(); } }