/******************************************************************************* * 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.update; 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.Queue; import java.util.Set; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import rubah.bytecode.transformers.UpdatableClassInfoGatherer; import rubah.framework.Clazz; import rubah.framework.Field; import rubah.framework.Method; import rubah.framework.Namespace; import rubah.framework.Type; import rubah.runtime.Version; import rubah.tools.Comparator; import rubah.tools.UpdateClassGenerator; import rubah.update.change.Change; import rubah.update.change.ChangeSet; import rubah.update.change.ChangeType; import rubah.update.change.ClassChange; public class ProgramUpdate { private Map<Clazz, ClassUpdate> v0Updates = new HashMap<Clazz, ClassUpdate>(); private Map<Clazz, ClassUpdate> v1Updates = new HashMap<Clazz, ClassUpdate>(); private Set<Clazz> redefined = new HashSet<Clazz>(); private Set<Clazz> converted = new HashSet<Clazz>(); private Set<Clazz> updated = new HashSet<Clazz>(); private Version v1; private boolean lazy = false; private HashSet<Clazz> border = new HashSet<Clazz>(); public ProgramUpdate() { // Empty constructor } public static interface Callback { public void foundUpdatableClass(Clazz c1); } public ProgramUpdate( Callback c, UpdateClass updateClass, final Version version, boolean v0v0) { this.v1 = version; Map<Clazz, ClassChange> changes = new Comparator(version.getPrevious(), version).computeChanges(v0v0); for (Entry<Clazz, ClassChange> entry : changes.entrySet()) { Clazz c1 = entry.getKey(); Clazz c0 = entry.getValue().getOriginal(); this.addClassUpdate(new ClassUpdate(c0, c1, entry.getValue())); c.foundUpdatableClass(c1); } // Propagate updates to subclasses if (version.getPrevious() != null) { this.propagateSubclasses(c, version.getPrevious().getNamespace(), changes); } // Find classes that are converted even though not updated // TODO: move this up, passing it to the comparator to map v0 to v1 names according to conversion method arguments if (updateClass != null) { this.findConverted(updateClass, version.getPrevious().getNamespace()); } this.computeNonUpdatableBorder(); } private void computeNonUpdatableBorder() { for (Clazz c : this.updated) { Clazz parent = c.getParent(); while (parent != null) { this.border.add(parent); parent = parent.getParent(); } } } private void findConverted(UpdateClass updateClass, final Namespace n0) { new ClassReader(updateClass.getBytes()).accept( new ClassVisitor(Opcodes.ASM5) { @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { Clazz c0; if (name.equals(UpdateClassGenerator.METHOD_NAME)) { String c0Name = Type.getArgumentTypes(desc)[0].getClassName(); c0Name = c0Name.replaceFirst(UpdateClassGenerator.V0_PREFFIX + "\\.", ""); c0 = n0.getClass(Type.getObjectType(c0Name.replace('.', '/'))); String c1Name = Type.getArgumentTypes(desc)[1].getClassName(); c1Name = c1Name.replaceFirst(UpdateClassGenerator.V1_PREFFIX + "\\.", ""); ProgramUpdate.this.converted.add(c0); } else if (name.equals(UpdateClassGenerator.METHOD_NAME_STATIC)) { String c1Name = Type.getArgumentTypes(desc)[0].getClassName(); c1Name = c1Name.replaceFirst(UpdateClassGenerator.V1_PREFFIX + "\\.", ""); c0 = n0.getClass(Type.getObjectType(c1Name.replace('.', '/'))); ProgramUpdate.this.converted.add(c0); } return null; } }, ClassReader.SKIP_CODE); } private void propagateSubclasses(Callback c, Namespace n0, Map<Clazz, ClassChange> changes) { Map<Clazz, List<Clazz>> subclasses = UpdatableClassInfoGatherer.computeSubClasses(n0); Map<Clazz, Set<Clazz>> interfaces = UpdatableClassInfoGatherer.computeInterfaces(n0); for (Entry<Clazz, ClassChange> entry : changes.entrySet()) { if (entry.getValue().getChangeSet().isJVMSupported()) { // No changes or class redefined continue; } Clazz c1 = entry.getKey(); Clazz c0 = this.getV0(c1); if (c0 == null) { // New class continue; } Queue<Clazz> classesToProcess = new LinkedList<Clazz>(); classesToProcess.addAll(subclasses.get(c0)); classesToProcess.addAll(interfaces.get(c0)); while (!classesToProcess.isEmpty()) { Clazz sub0 = classesToProcess.poll(); Clazz sub1 = this.getV1(sub0); if (sub1 != null && !this.isUpdated(sub0)) { ClassChange classChange = new ClassChange( new ChangeSet(entry.getValue().getChangeSet().isJVMSupported() ? ChangeType.METHOD_BODY_CHANGE : ChangeType.CHANGED_SUPERTYPE), sub0, new HashMap<Field, Change<Field>>(), new HashMap<Method, Change<Method>>()); c.foundUpdatableClass(sub1); this.addClassUpdate(new ClassUpdate(sub0, sub1, classChange)); } classesToProcess.addAll(subclasses.get(sub0)); classesToProcess.addAll(interfaces.get(sub0)); } } } private void addClassUpdate(ClassUpdate update) { this.v0Updates.put(update.getV0(), update); this.v1Updates.put(update.getV1(), update); ChangeSet cs = update.getClassChanges().getChangeSet(); if (cs.isEmpty()) { return; } Clazz c0 = update.getV0(); if (c0 == null) { // New class, skip return; } else if (cs.isJVMSupported()) { // Change supported by JVM this.redefined.add(c0); } else { this.updated.add(c0); } } public Clazz getV1(Clazz c0) { if (c0.isArray()) { Clazz ret = this.getV1(c0.getArrayType()); if (ret == null) return null; return this.v1.getNamespace().getClass(ret.getASMType(), ret.getDimensions()); } ClassUpdate update = this.v0Updates.get(c0); if (update == null) { return null; } return update.getV1(); } public Clazz getV0(Clazz c1) { if (c1.isArray()) { Clazz ret = this.getV0(c1.getArrayType()); if (ret == null) return null; return this.v1.getPrevious().getNamespace().getClass(ret.getASMType(), c1.getDimensions()); } ClassUpdate update = this.v1Updates.get(c1); if (update == null) { return null; } return update.getV0(); } public boolean isConverted(Clazz c0) { return this.isUpdated(c0) || this.converted.contains(c0); } public boolean isRedefined(Clazz c0) { return this.redefined.contains(c0); } public boolean isUpdated(Clazz c0) { return this.updated.contains(c0); } public ClassChange getChanges(Clazz c0) { return this.v0Updates.get(c0).getClassChanges(); } public boolean isLazy() { return lazy; } public void setLazy(boolean isLazy) { this.lazy = isLazy; } public HashSet<Clazz> getBorder() { return border; } }