blob: 91a86d6822d5c3a440cf27367cfd2596d607c040 [file] [log] [blame]
/*
*
* 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());
}
}
}