| /* |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| import org.apache.bcel.Constants; |
| import org.apache.bcel.classfile.ClassParser; |
| import org.apache.bcel.classfile.Constant; |
| import org.apache.bcel.classfile.ConstantFieldref; |
| import org.apache.bcel.classfile.ConstantMethodref; |
| import org.apache.bcel.classfile.ConstantNameAndType; |
| import org.apache.bcel.classfile.ConstantUtf8; |
| import org.apache.bcel.classfile.JavaClass; |
| import org.apache.bcel.classfile.Method; |
| import org.apache.bcel.generic.CPInstruction; |
| import org.apache.bcel.generic.ConstantPoolGen; |
| import org.apache.bcel.generic.InstructionConstants; |
| import org.apache.bcel.generic.InstructionHandle; |
| import org.apache.bcel.generic.InstructionList; |
| import org.apache.bcel.generic.MethodGen; |
| import org.apache.bcel.generic.TargetLostException; |
| import org.apache.bcel.util.InstructionFinder; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.DataInputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.FilenameFilter; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| |
| /** |
| * make JDK 1.5 classfiles (version 49.0) compatible with JDK 1.3 (version 47.0) |
| * by making the following changes: |
| * |
| * 1. replace references to java.lang.StringBuilder with java.lang.StringBuffer |
| * 2. replace Integer.valueOf(int) with macromedia.asc.util.Boxing.valueOf(int) |
| * 3. reorder this$outer and super() code snippets in inner class constructors, |
| * to please the 1.3 verifier. |
| * 4. replace java.lang.AssertionError with java.lang.Error |
| * |
| * NOTE TO MAINTAINERS: |
| * additional class reference changes can be added by adding entries in the strings |
| * table. these are simple substitutions. Note that usually both the classname |
| * reference and signatures of its methods must be changed. |
| * |
| * To adjust bytecode, we use BCEL's InstructionFinder, which lets us do regexp |
| * matching on instruction sequences. Once we find a match, we make the necessary |
| * changes to a method's existing InstructionList. |
| */ |
| public class Downgrader implements Constants |
| { |
| static HashMap<String,String> strings = new HashMap<String,String>(); |
| |
| static |
| { |
| // replace StringBuilder with StringBuffer |
| strings.put("java/lang/StringBuilder", "java/lang/StringBuffer"); |
| strings.put("(Ljava/lang/String;)Ljava/lang/StringBuilder;", "(Ljava/lang/String;)Ljava/lang/StringBuffer;"); |
| strings.put("(Ljava/lang/Object;)Ljava/lang/StringBuilder;", "(Ljava/lang/Object;)Ljava/lang/StringBuffer;"); |
| strings.put("(I)Ljava/lang/StringBuilder;", "(I)Ljava/lang/StringBuffer;"); |
| strings.put("(C)Ljava/lang/StringBuilder;", "(C)Ljava/lang/StringBuffer;"); |
| strings.put("(Z)Ljava/lang/StringBuilder;", "(Z)Ljava/lang/StringBuffer;"); |
| strings.put("(D)Ljava/lang/StringBuilder;", "(D)Ljava/lang/StringBuffer;"); |
| strings.put("(J)Ljava/lang/StringBuilder;", "(J)Ljava/lang/StringBuffer;"); |
| |
| // replace java.lang.AssertionError with java/lang/Error |
| strings.put("java/lang/AssertionError", "java/lang/Error"); |
| |
| //strings.put("java/lang/Iterable", "macromedia/asc/util/Iterable"); |
| } |
| |
| ConstantPoolGen cpool; |
| |
| void downgrade(String filename) throws IOException |
| { |
| byte[] b = new byte[(int) new File(filename).length()]; |
| InputStream in = new FileInputStream(filename); |
| new DataInputStream(in).readFully(b); |
| in.close(); |
| |
| ClassParser parser = new ClassParser(new ByteArrayInputStream(b), filename); |
| JavaClass jc = parser.parse(); |
| boolean changed; |
| changed = downgrade(jc); |
| |
| if (changed) |
| { |
| b = jc.getBytes(); |
| |
| FileOutputStream out = new FileOutputStream(filename); |
| out.write(b); |
| out.close(); |
| } |
| } |
| |
| private boolean downgrade(JavaClass jc) |
| { |
| if (jc.getMajor() <= 47) |
| return false; |
| |
| Method[] methods = jc.getMethods(); |
| cpool = new ConstantPoolGen(jc.getConstantPool().getConstantPool()); |
| |
| jc.setMajor(47); |
| |
| for (int i=1, n=cpool.getSize(); i < n; i++) |
| { |
| Constant constant = cpool.getConstant(i); |
| switch (constant.getTag()) |
| { |
| case Constants.CONSTANT_Utf8: |
| ConstantUtf8 cstring = (ConstantUtf8)constant; |
| String bytes = cstring.getBytes(); |
| String newbytes = (String) strings.get(bytes); |
| if (newbytes != null) |
| cstring.setBytes(newbytes); |
| break; |
| case Constants.CONSTANT_Double: |
| case Constants.CONSTANT_Long: |
| i++; |
| break; |
| case Constants.CONSTANT_Methodref: |
| ConstantMethodref cmethod = (ConstantMethodref) constant; |
| if (cmethod.getClass(cpool.getConstantPool()).equals("java.lang.Integer")) |
| { |
| ConstantNameAndType cnt = (ConstantNameAndType) cpool.getConstant(cmethod.getNameAndTypeIndex()); |
| |
| if (cnt.getName(cpool.getConstantPool()).equals("valueOf") && |
| cnt.getSignature(cpool.getConstantPool()).equals("(I)Ljava/lang/Integer;")) |
| { |
| cmethod.setClassIndex(cpool.addClass("macromedia.asc.util.Boxing")); |
| } |
| } |
| |
| else if (cmethod.getClass(cpool.getConstantPool()).equals("java.lang.Double")) |
| { |
| ConstantNameAndType cnt = (ConstantNameAndType) cpool.getConstant(cmethod.getNameAndTypeIndex()); |
| |
| if (cnt.getName(cpool.getConstantPool()).equals("valueOf") && |
| cnt.getSignature(cpool.getConstantPool()).equals("(D)Ljava/lang/Double;")) |
| { |
| cmethod.setClassIndex(cpool.addClass("macromedia.asc.util.Boxing")); |
| } |
| } |
| break; |
| } |
| } |
| |
| for (int i=0, n = methods.length; i < n; i++) |
| { |
| if (!(methods[i].isAbstract() || methods[i].isNative())) |
| { |
| MethodGen mg = new MethodGen(methods[i], jc.getClassName(), cpool); |
| |
| boolean changed = change1(mg); |
| |
| if (methods[i].getName().equals("<init>")) |
| changed |= change2(mg); |
| |
| // changed |= change3(mg); |
| |
| if (changed) |
| { |
| mg.setMaxStack(); |
| methods[i] = mg.getMethod(); |
| } |
| } |
| } |
| |
| jc.setConstantPool(cpool.getFinalConstantPool()); |
| return true; |
| } |
| |
| private boolean change2(MethodGen mg) |
| { |
| InstructionList il = mg.getInstructionList(); |
| InstructionFinder f = new InstructionFinder(il); |
| String pat = "ALOAD_0 ALOAD_1 PUTFIELD ALOAD_0 INVOKESPECIAL"; |
| |
| boolean changed = false; |
| for(Iterator j = f.search(pat, new IsInnerConstructor()); j.hasNext(); ) |
| { |
| InstructionHandle[] match = (InstructionHandle[]) j.next(); |
| |
| // replace |
| // ALOAD_0 [0] |
| // ALOAD_1 [1] |
| // PUTFIELD [2] |
| // ALOAD_0 [3] |
| // INVOKESPECIAL [4] |
| // with |
| // ALOAD_0 [0] |
| // INVOKESPECIAL [4] |
| // ALOAD_0 [3] |
| // ALOAD_1 [1] |
| // PUTFIELD [2] |
| |
| changed = true; |
| try |
| { |
| InstructionList il2 = new InstructionList(); |
| il2.append(match[3].getInstruction()); |
| il2.append(match[4].getInstruction()); |
| il.delete(match[3], match[4]); |
| il.insert(match[0], il2); |
| } |
| catch (TargetLostException e) |
| { |
| System.err.println("ERROR IN "+mg); |
| e.printStackTrace(); |
| } |
| } |
| return changed; |
| } |
| |
| class IsInnerConstructor implements InstructionFinder.CodeConstraint |
| { |
| public boolean checkCode(InstructionHandle[] match) |
| { |
| InstructionHandle ih = match[2]; |
| CPInstruction putfield = (CPInstruction) ih.getInstruction(); |
| Constant cc = cpool.getConstant(putfield.getIndex()); |
| if (cc.getTag() != CONSTANT_Fieldref) |
| return false; |
| ConstantFieldref cfield = (ConstantFieldref) cc; |
| ConstantNameAndType cnt = (ConstantNameAndType) cpool.getConstant(cfield.getNameAndTypeIndex()); |
| if (!cnt.getName(cpool.getConstantPool()).equals("this$0")) |
| return false; |
| return true; |
| } |
| } |
| |
| /* private boolean change3(MethodGen mg) |
| { |
| InstructionList il = mg.getInstructionList(); |
| InstructionFinder f = new InstructionFinder(il); |
| String pat = "INVOKESTATIC"; |
| |
| boolean changed = false; |
| for(Iterator j = f.search(pat, new IsIntegerValueOf()); j.hasNext(); ) |
| { |
| InstructionHandle[] match = (InstructionHandle[]) j.next(); |
| |
| // replace |
| // (expr) |
| // INVOKESTATIC Integer.valueOf(int):Integer |
| // with |
| // (expr) |
| // NEW Integer |
| // DUP_X1 |
| // SWAP |
| // INVOKESPECIAL Integer(int) |
| |
| changed = true; |
| try |
| { |
| InstructionFactory factory = new InstructionFactory(cpool); |
| InstructionList il2 = new InstructionList(); |
| |
| il2.append(factory.createNew("java.lang.Integer")); |
| il2.append(InstructionConstants.DUP_X1); |
| il2.append(InstructionConstants.SWAP); |
| il2.append(factory.createInvoke("java.lang.Integer", "Integer", Type.VOID, new Type[] { Type.INT }, INVOKESPECIAL)); |
| |
| il.append(match[0], il2); |
| il.delete(match[0]); |
| System.out.println(il); |
| } |
| catch (TargetLostException e) |
| { |
| e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. |
| } |
| } |
| return changed; |
| } |
| |
| class IsIntegerValueOf implements InstructionFinder.CodeConstraint |
| { |
| public boolean checkCode(InstructionHandle[] match) |
| { |
| InstructionHandle ih = match[0]; |
| CPInstruction invokestatic = (CPInstruction) ih.getInstruction(); |
| Constant cc = cpool.getConstant(invokestatic.getIndex()); |
| if (cc.getTag() != CONSTANT_Methodref) |
| return false; |
| ConstantMethodref cmethod = (ConstantMethodref) cc; |
| if (cmethod.getClass(cpool.getConstantPool()).equals("java.lang.Integer")) |
| { |
| ConstantNameAndType cnt = (ConstantNameAndType) cpool.getConstant(cmethod.getNameAndTypeIndex()); |
| if (cnt.getName(cpool.getConstantPool()).equals("valueOf") && |
| cnt.getSignature(cpool.getConstantPool()).equals("(I)Ljava/lang/Integer;")) |
| { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
| */ |
| |
| private boolean change1(MethodGen mg) |
| { |
| InstructionList il = mg.getInstructionList(); |
| InstructionFinder f = new InstructionFinder(il); |
| String pat = "[LDC_W|LDC] INVOKEVIRTUAL IFNE ICONST_1 GOTO ICONST_0"; |
| |
| boolean changed = false; |
| for(Iterator j = f.search(pat, new IsLdcClass()); j.hasNext(); ) |
| { |
| InstructionHandle[] match = (InstructionHandle[]) j.next(); |
| |
| // replace |
| // LDC_W <class> |
| // INVOKEVIRTUAL Class.desiredAssertionStatus |
| // IFNE |
| // ICONST_1 |
| // goto |
| // ICONST_0 |
| // with |
| // ICONST_1 |
| |
| changed = true; |
| match[0].setInstruction(InstructionConstants.ICONST_1); |
| try |
| { |
| il.delete(match[1], match[5]); |
| // System.out.println(il); |
| } |
| catch (TargetLostException e) |
| { |
| e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. |
| } |
| } |
| return changed; |
| } |
| |
| class IsLdcClass implements InstructionFinder.CodeConstraint |
| { |
| public boolean checkCode(InstructionHandle[] match) |
| { |
| InstructionHandle ih = match[0]; |
| CPInstruction ldc_w = (CPInstruction) ih.getInstruction(); |
| Constant cc = cpool.getConstant(ldc_w.getIndex()); |
| if (cc.getTag() != CONSTANT_Class) |
| return false; |
| |
| ih = match[1]; |
| CPInstruction invokevirtual = (CPInstruction) ih.getInstruction(); |
| ConstantMethodref cm = (ConstantMethodref) cpool.getConstant(invokevirtual.getIndex()); |
| ConstantNameAndType cnt = (ConstantNameAndType) cpool.getConstant(cm.getNameAndTypeIndex()); |
| if (!cnt.getName(cpool.getConstantPool()).equals("desiredAssertionStatus")) |
| return false; |
| return true; |
| } |
| } |
| |
| public static void main(String[] args) |
| { |
| for (int i = 0, length = args.length; i < length; i++) |
| { |
| String dirname = args[i]; |
| |
| File dir = new File(dirname); |
| try |
| { |
| downgradeDir(dir); |
| } |
| catch (Throwable e) |
| { |
| e.printStackTrace(); |
| System.exit(1); |
| } |
| } |
| } |
| |
| static void downgradeDir(File dir) throws IOException |
| { |
| System.out.println(dir); |
| File[] files = dir.listFiles(new FilenameFilter() |
| { |
| public boolean accept(File dir, String name) |
| { |
| return name.endsWith(".class") || new File(dir,name).isDirectory(); |
| } |
| }); |
| |
| for (int i=0, n=files.length; i < n; i++) |
| { |
| if (files[i].isDirectory()) |
| downgradeDir(files[i]); |
| else |
| new Downgrader().downgrade(files[i].getPath()); |
| } |
| } |
| } |