Home | History | Annotate | Download | only in stackmap
      1 /*
      2  * Javassist, a Java-bytecode translator toolkit.
      3  * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
      4  *
      5  * The contents of this file are subject to the Mozilla Public License Version
      6  * 1.1 (the "License"); you may not use this file except in compliance with
      7  * the License.  Alternatively, the contents of this file may be used under
      8  * the terms of the GNU Lesser General Public License Version 2.1 or later.
      9  *
     10  * Software distributed under the License is distributed on an "AS IS" basis,
     11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
     12  * for the specific language governing rights and limitations under the
     13  * License.
     14  */
     15 
     16 package javassist.bytecode.stackmap;
     17 
     18 import javassist.ClassPool;
     19 import javassist.bytecode.*;
     20 
     21 /**
     22  * Stack map maker.
     23  */
     24 public class MapMaker extends Tracer {
     25     /*
     26     public static void main(String[] args) throws Exception {
     27         boolean useMain2 = args[0].equals("0");
     28         if (useMain2 && args.length > 1) {
     29             main2(args);
     30             return;
     31         }
     32 
     33         for (int i = 0; i < args.length; i++)
     34             main1(args[i]);
     35     }
     36 
     37     public static void main1(String className) throws Exception {
     38         ClassPool cp = ClassPool.getDefault();
     39         //javassist.CtClass cc = cp.get(className);
     40         javassist.CtClass cc = cp.makeClass(new java.io.FileInputStream(className));
     41         System.out.println(className);
     42         ClassFile cf = cc.getClassFile();
     43         java.util.List minfos = cf.getMethods();
     44         for (int i = 0; i < minfos.size(); i++) {
     45             MethodInfo minfo = (MethodInfo)minfos.get(i);
     46             CodeAttribute ca = minfo.getCodeAttribute();
     47             if (ca != null)
     48                 ca.setAttribute(make(cp, minfo));
     49         }
     50 
     51         cc.writeFile("tmp");
     52     }
     53 
     54     public static void main2(String[] args) throws Exception {
     55         ClassPool cp = ClassPool.getDefault();
     56         //javassist.CtClass cc = cp.get(args[1]);
     57         javassist.CtClass cc = cp.makeClass(new java.io.FileInputStream(args[1]));
     58         MethodInfo minfo;
     59         if (args[2].equals("_init_"))
     60             minfo = cc.getDeclaredConstructors()[0].getMethodInfo();
     61             // minfo = cc.getClassInitializer().getMethodInfo();
     62         else
     63             minfo = cc.getDeclaredMethod(args[2]).getMethodInfo();
     64 
     65         CodeAttribute ca = minfo.getCodeAttribute();
     66         if (ca == null) {
     67             System.out.println("abstarct method");
     68             return;
     69         }
     70 
     71         TypedBlock[] blocks = TypedBlock.makeBlocks(minfo, ca, false);
     72         MapMaker mm = new MapMaker(cp, minfo, ca);
     73         mm.make(blocks, ca.getCode());
     74         for (int i = 0; i < blocks.length; i++)
     75             System.out.println(blocks[i]);
     76     }
     77     */
     78 
     79     /**
     80      * Computes the stack map table of the given method and returns it.
     81      * It returns null if the given method does not have to have a
     82      * stack map table.
     83      */
     84     public static StackMapTable make(ClassPool classes, MethodInfo minfo)
     85         throws BadBytecode
     86     {
     87         CodeAttribute ca = minfo.getCodeAttribute();
     88         if (ca == null)
     89             return null;
     90 
     91         TypedBlock[] blocks = TypedBlock.makeBlocks(minfo, ca, true);
     92         if (blocks == null)
     93             return null;
     94 
     95         MapMaker mm = new MapMaker(classes, minfo, ca);
     96         mm.make(blocks, ca.getCode());
     97         return mm.toStackMap(blocks);
     98     }
     99 
    100     /**
    101      * Computes the stack map table for J2ME.
    102      * It returns null if the given method does not have to have a
    103      * stack map table.
    104      */
    105     public static StackMap make2(ClassPool classes, MethodInfo minfo)
    106         throws BadBytecode
    107     {
    108         CodeAttribute ca = minfo.getCodeAttribute();
    109         if (ca == null)
    110             return null;
    111 
    112         TypedBlock[] blocks = TypedBlock.makeBlocks(minfo, ca, true);
    113         if (blocks == null)
    114             return null;
    115 
    116         MapMaker mm = new MapMaker(classes, minfo, ca);
    117         mm.make(blocks, ca.getCode());
    118         return mm.toStackMap2(minfo.getConstPool(), blocks);
    119     }
    120 
    121     public MapMaker(ClassPool classes, MethodInfo minfo, CodeAttribute ca) {
    122         super(classes, minfo.getConstPool(),
    123               ca.getMaxStack(), ca.getMaxLocals(),
    124               TypedBlock.getRetType(minfo.getDescriptor()));
    125     }
    126 
    127     protected MapMaker(MapMaker old, boolean copyStack) {
    128         super(old, copyStack);
    129     }
    130 
    131     /**
    132      * Runs an analyzer (Phase 1 and 2).
    133      */
    134     void make(TypedBlock[] blocks, byte[] code)
    135         throws BadBytecode
    136     {
    137         TypedBlock first = blocks[0];
    138         fixParamTypes(first);
    139         TypeData[] srcTypes = first.localsTypes;
    140         copyFrom(srcTypes.length, srcTypes, this.localsTypes);
    141         make(code, first);
    142 
    143         int n = blocks.length;
    144         for (int i = 0; i < n; i++)
    145             evalExpected(blocks[i]);
    146     }
    147 
    148     /*
    149      * If a parameter type is String but it is used only as Object
    150      * within the method body, this MapMaker class will report its type
    151      * is Object.  To avoid this, fixParamTypes calls TypeData.setType()
    152      * on each parameter type.
    153      */
    154     private void fixParamTypes(TypedBlock first) throws BadBytecode {
    155         TypeData[] types = first.localsTypes;
    156         int n = types.length;
    157         for (int i = 0; i < n; i++) {
    158             TypeData t = types[i];
    159             if (t instanceof TypeData.ClassName) {
    160                 /* Skip the following statement if t.isNullType() is true
    161                  * although a parameter type is never null type.
    162                  */
    163                 TypeData.setType(t, t.getName(), classPool);
    164             }
    165         }
    166     }
    167 
    168     // Phase 1
    169 
    170     private void make(byte[] code, TypedBlock tb)
    171         throws BadBytecode
    172     {
    173         BasicBlock.Catch handlers = tb.toCatch;
    174         while (handlers != null) {
    175             traceException(code, handlers);
    176             handlers = handlers.next;
    177         }
    178 
    179         int pos = tb.position;
    180         int end = pos + tb.length;
    181         while (pos < end)
    182             pos += doOpcode(pos, code);
    183 
    184         if (tb.exit != null) {
    185             for (int i = 0; i < tb.exit.length; i++) {
    186                 TypedBlock e = (TypedBlock)tb.exit[i];
    187                 if (e.alreadySet())
    188                     mergeMap(e, true);
    189                 else {
    190                     recordStackMap(e);
    191                     MapMaker maker = new MapMaker(this, true);
    192                     maker.make(code, e);
    193                 }
    194             }
    195         }
    196     }
    197 
    198     private void traceException(byte[] code, TypedBlock.Catch handler)
    199         throws BadBytecode
    200     {
    201         TypedBlock tb = (TypedBlock)handler.body;
    202         if (tb.alreadySet())
    203             mergeMap(tb, false);
    204         else {
    205             recordStackMap(tb, handler.typeIndex);
    206             MapMaker maker = new MapMaker(this, false);
    207 
    208             /* the following code is equivalent to maker.copyFrom(this)
    209              * except stackTypes are not copied.
    210              */
    211             maker.stackTypes[0] = tb.stackTypes[0].getSelf();
    212             maker.stackTop = 1;
    213             maker.make(code, tb);
    214         }
    215     }
    216 
    217     private void mergeMap(TypedBlock dest, boolean mergeStack) {
    218         boolean[] inputs = dest.inputs;
    219         int n = inputs.length;
    220         for (int i = 0; i < n; i++)
    221             if (inputs[i])
    222                 merge(localsTypes[i], dest.localsTypes[i]);
    223 
    224         if (mergeStack) {
    225             n = stackTop;
    226             for (int i = 0; i < n; i++)
    227                 merge(stackTypes[i], dest.stackTypes[i]);
    228         }
    229     }
    230 
    231     private void merge(TypeData td, TypeData target) {
    232         boolean tdIsObj = false;
    233         boolean targetIsObj = false;
    234         // td or target is null if it is TOP.
    235         if (td != TOP && td.isObjectType())
    236             tdIsObj = true;
    237 
    238         if (target != TOP && target.isObjectType())
    239             targetIsObj = true;
    240 
    241         if (tdIsObj && targetIsObj)
    242             target.merge(td);
    243     }
    244 
    245     private void recordStackMap(TypedBlock target)
    246         throws BadBytecode
    247     {
    248         TypeData[] tStackTypes = new TypeData[stackTypes.length];
    249         int st = stackTop;
    250         copyFrom(st, stackTypes, tStackTypes);
    251         recordStackMap0(target, st, tStackTypes);
    252     }
    253 
    254     private void recordStackMap(TypedBlock target, int exceptionType)
    255         throws BadBytecode
    256     {
    257         String type;
    258         if (exceptionType == 0)
    259             type = "java.lang.Throwable";
    260         else
    261             type = cpool.getClassInfo(exceptionType);
    262 
    263         TypeData[] tStackTypes = new TypeData[stackTypes.length];
    264         tStackTypes[0] = new TypeData.ClassName(type);
    265 
    266         recordStackMap0(target, 1, tStackTypes);
    267     }
    268 
    269     private void recordStackMap0(TypedBlock target, int st, TypeData[] tStackTypes)
    270         throws BadBytecode
    271     {
    272         int n = localsTypes.length;
    273         TypeData[] tLocalsTypes = new TypeData[n];
    274         int k = copyFrom(n, localsTypes, tLocalsTypes);
    275 
    276         boolean[] inputs = target.inputs;
    277         for (int i = 0; i < n; i++)
    278             if (!inputs[i])
    279                 tLocalsTypes[i] = TOP;
    280 
    281         target.setStackMap(st, tStackTypes, k, tLocalsTypes);
    282     }
    283 
    284     // Phase 2
    285 
    286     void evalExpected(TypedBlock target) throws BadBytecode {
    287         ClassPool cp = classPool;
    288         evalExpected(cp, target.stackTop, target.stackTypes);
    289         TypeData[] types = target.localsTypes;
    290         if (types != null)  // unless this block is dead code
    291             evalExpected(cp, types.length, types);
    292     }
    293 
    294     private static void evalExpected(ClassPool cp, int n, TypeData[] types)
    295         throws BadBytecode
    296     {
    297         for (int i = 0; i < n; i++) {
    298             TypeData td = types[i];
    299             if (td != null)
    300                 td.evalExpectedType(cp);
    301         }
    302     }
    303 
    304     // Phase 3
    305 
    306     public StackMapTable toStackMap(TypedBlock[] blocks) {
    307         StackMapTable.Writer writer = new StackMapTable.Writer(32);
    308         int n = blocks.length;
    309         TypedBlock prev = blocks[0];
    310         int offsetDelta = prev.length;
    311         if (prev.incoming > 0) {     // the first instruction is a branch target.
    312             writer.sameFrame(0);
    313             offsetDelta--;
    314         }
    315 
    316         for (int i = 1; i < n; i++) {
    317             TypedBlock bb = blocks[i];
    318             if (isTarget(bb, blocks[i - 1])) {
    319                 bb.resetNumLocals();
    320                 int diffL = stackMapDiff(prev.numLocals, prev.localsTypes,
    321                                          bb.numLocals, bb.localsTypes);
    322                 toStackMapBody(writer, bb, diffL, offsetDelta, prev);
    323                 offsetDelta = bb.length - 1;
    324                 prev = bb;
    325             }
    326             else
    327                 offsetDelta += bb.length;
    328         }
    329 
    330         return writer.toStackMapTable(cpool);
    331     }
    332 
    333     /**
    334      * Returns true if cur is a branch target.
    335      */
    336     private boolean isTarget(TypedBlock cur, TypedBlock prev) {
    337         int in = cur.incoming;
    338         if (in > 1)
    339             return true;
    340         else if (in < 1)
    341             return false;
    342 
    343         return prev.stop;
    344     }
    345 
    346     private void toStackMapBody(StackMapTable.Writer writer, TypedBlock bb,
    347                                 int diffL, int offsetDelta, TypedBlock prev) {
    348         // if diffL is -100, two TypeData arrays do not share
    349         // any elements.
    350 
    351         int stackTop = bb.stackTop;
    352         if (stackTop == 0) {
    353             if (diffL == 0) {
    354                 writer.sameFrame(offsetDelta);
    355                 return;
    356             }
    357             else if (0 > diffL && diffL >= -3) {
    358                 writer.chopFrame(offsetDelta, -diffL);
    359                 return;
    360             }
    361             else if (0 < diffL && diffL <= 3) {
    362                 int[] data = new int[diffL];
    363                 int[] tags = fillStackMap(bb.numLocals - prev.numLocals,
    364                                           prev.numLocals, data,
    365                                           bb.localsTypes);
    366                 writer.appendFrame(offsetDelta, tags, data);
    367                 return;
    368             }
    369         }
    370         else if (stackTop == 1 && diffL == 0) {
    371             TypeData td = bb.stackTypes[0];
    372             if (td == TOP)
    373                 writer.sameLocals(offsetDelta, StackMapTable.TOP, 0);
    374             else
    375                 writer.sameLocals(offsetDelta, td.getTypeTag(),
    376                                   td.getTypeData(cpool));
    377             return;
    378         }
    379         else if (stackTop == 2 && diffL == 0) {
    380             TypeData td = bb.stackTypes[0];
    381             if (td != TOP && td.is2WordType()) {
    382                 // bb.stackTypes[1] must be TOP.
    383                 writer.sameLocals(offsetDelta, td.getTypeTag(),
    384                                   td.getTypeData(cpool));
    385                 return;
    386             }
    387         }
    388 
    389         int[] sdata = new int[stackTop];
    390         int[] stags = fillStackMap(stackTop, 0, sdata, bb.stackTypes);
    391         int[] ldata = new int[bb.numLocals];
    392         int[] ltags = fillStackMap(bb.numLocals, 0, ldata, bb.localsTypes);
    393         writer.fullFrame(offsetDelta, ltags, ldata, stags, sdata);
    394     }
    395 
    396     private int[] fillStackMap(int num, int offset, int[] data, TypeData[] types) {
    397         int realNum = diffSize(types, offset, offset + num);
    398         ConstPool cp = cpool;
    399         int[] tags = new int[realNum];
    400         int j = 0;
    401         for (int i = 0; i < num; i++) {
    402             TypeData td = types[offset + i];
    403             if (td == TOP) {
    404                 tags[j] = StackMapTable.TOP;
    405                 data[j] = 0;
    406             }
    407             else {
    408                 tags[j] = td.getTypeTag();
    409                 data[j] = td.getTypeData(cp);
    410                 if (td.is2WordType())
    411                     i++;
    412             }
    413 
    414             j++;
    415         }
    416 
    417         return tags;
    418     }
    419 
    420     private static int stackMapDiff(int oldTdLen, TypeData[] oldTd,
    421                                     int newTdLen, TypeData[] newTd)
    422     {
    423         int diff = newTdLen - oldTdLen;
    424         int len;
    425         if (diff > 0)
    426             len = oldTdLen;
    427         else
    428             len = newTdLen;
    429 
    430         if (stackMapEq(oldTd, newTd, len))
    431             if (diff > 0)
    432                 return diffSize(newTd, len, newTdLen);
    433             else
    434                 return -diffSize(oldTd, len, oldTdLen);
    435         else
    436             return -100;
    437     }
    438 
    439     private static boolean stackMapEq(TypeData[] oldTd, TypeData[] newTd, int len) {
    440         for (int i = 0; i < len; i++) {
    441             TypeData td = oldTd[i];
    442             if (td == TOP) {        // the next element to LONG/DOUBLE is TOP.
    443                 if (newTd[i] != TOP)
    444                     return false;
    445             }
    446             else
    447                 if (!oldTd[i].equals(newTd[i]))
    448                     return false;
    449         }
    450 
    451         return true;
    452     }
    453 
    454     private static int diffSize(TypeData[] types, int offset, int len) {
    455         int num = 0;
    456         while (offset < len) {
    457             TypeData td = types[offset++];
    458             num++;
    459             if (td != TOP && td.is2WordType())
    460                 offset++;
    461         }
    462 
    463         return num;
    464     }
    465 
    466     // Phase 3 for J2ME
    467 
    468     public StackMap toStackMap2(ConstPool cp, TypedBlock[] blocks) {
    469         StackMap.Writer writer = new StackMap.Writer();
    470         int n = blocks.length;      // should be > 0
    471         boolean[] effective = new boolean[n];
    472         TypedBlock prev = blocks[0];
    473 
    474         // Is the first instruction a branch target?
    475         effective[0] = prev.incoming > 0;
    476 
    477         int num = effective[0] ? 1 : 0;
    478         for (int i = 1; i < n; i++) {
    479             TypedBlock bb = blocks[i];
    480             if (effective[i] = isTarget(bb, blocks[i - 1])) {
    481                 bb.resetNumLocals();
    482                 prev = bb;
    483                 num++;
    484             }
    485         }
    486 
    487         if (num == 0)
    488             return null;
    489 
    490         writer.write16bit(num);
    491         for (int i = 0; i < n; i++)
    492             if (effective[i])
    493                 writeStackFrame(writer, cp, blocks[i].position, blocks[i]);
    494 
    495         return writer.toStackMap(cp);
    496     }
    497 
    498     private void writeStackFrame(StackMap.Writer writer, ConstPool cp, int offset, TypedBlock tb) {
    499         writer.write16bit(offset);
    500         writeVerifyTypeInfo(writer, cp, tb.localsTypes, tb.numLocals);
    501         writeVerifyTypeInfo(writer, cp, tb.stackTypes, tb.stackTop);
    502     }
    503 
    504     private void writeVerifyTypeInfo(StackMap.Writer writer, ConstPool cp, TypeData[] types, int num) {
    505         int numDWord = 0;
    506         for (int i = 0; i < num; i++) {
    507             TypeData td = types[i];
    508             if (td != null && td.is2WordType()) {
    509                 numDWord++;
    510                 i++;
    511             }
    512         }
    513 
    514         writer.write16bit(num - numDWord);
    515         for (int i = 0; i < num; i++) {
    516             TypeData td = types[i];
    517             if (td == TOP)
    518                 writer.writeVerifyTypeInfo(StackMap.TOP, 0);
    519             else {
    520                 writer.writeVerifyTypeInfo(td.getTypeTag(), td.getTypeData(cp));
    521                 if (td.is2WordType())
    522                     i++;
    523             }
    524         }
    525     }
    526 }
    527