    001/*
    002 *  Licensed to the Apache Software Foundation (ASF) under one or more
    003 *  contributor license agreements.  See the NOTICE file distributed with
    004 *  this work for additional information regarding copyright ownership.
    005 *  The ASF licenses this file to You under the Apache License, Version 2.0
    006 *  (the "License"); you may not use this file except in compliance with
    007 *  the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 *  Unless required by applicable law or agreed to in writing, software
    012 *  distributed under the License is distributed on an "AS IS" BASIS,
    013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 *  See the License for the specific language governing permissions and
    015 *  limitations under the License.
    016 */
    017package org.apache.commons.compress.harmony.pack200;
    018
    019import java.io.IOException;
    020import java.io.OutputStream;
    021import java.util.ArrayList;
    022import java.util.HashMap;
    023import java.util.List;
    024import java.util.Map;
    025
    026import org.objectweb.asm.Label;
    027
    028/**
    029 * Bytecode bands (corresponds to the <code>bc_bands</code> set of bands in the pack200 specification)
    030 */
    031public class BcBands extends BandSet {
    032
    033    private final CpBands cpBands;
    034    private final Segment segment;
    035
    036    public BcBands(final CpBands cpBands, final Segment segment, final int effort) {
    037        super(effort, segment.getSegmentHeader());
    038        this.cpBands = cpBands;
    039        this.segment = segment;
    040    }
    041
    042    private final IntList bcCodes = new IntList();
    043    private final IntList bcCaseCount = new IntList();
    044    private final IntList bcCaseValue = new IntList();
    045    private final IntList bcByte = new IntList();
    046    private final IntList bcShort = new IntList();
    047    private final IntList bcLocal = new IntList();
    048    private final List bcLabel = new ArrayList();
    049    private final List bcIntref = new ArrayList();
    050    private final List bcFloatRef = new ArrayList();
    051    private final List bcLongRef = new ArrayList();
    052    private final List bcDoubleRef = new ArrayList();
    053    private final List bcStringRef = new ArrayList();
    054    private final List bcClassRef = new ArrayList();
    055    private final List bcFieldRef = new ArrayList();
    056    private final List bcMethodRef = new ArrayList();
    057    private final List bcIMethodRef = new ArrayList();
    058    private List bcThisField = new ArrayList();
    059    private final List bcSuperField = new ArrayList();
    060    private List bcThisMethod = new ArrayList();
    061    private List bcSuperMethod = new ArrayList();
    062    private List bcInitRef = new ArrayList();
    063
    064    private String currentClass;
    065    private String superClass;
    066    private String currentNewClass;
    067
    068    private static final int MULTIANEWARRAY = 197;
    069    private static final int ALOAD_0 = 42;
    070    private static final int WIDE = 196;
    071    private static final int INVOKEINTERFACE = 185;
    072    private static final int TABLESWITCH = 170;
    073    private static final int IINC = 132;
    074    private static final int LOOKUPSWITCH = 171;
    075    private static final int endMarker = 255;
    076
    077    private final IntList bciRenumbering = new IntList();
    078    private final Map labelsToOffsets = new HashMap();
    079    private int byteCodeOffset;
    080    private int renumberedOffset;
    081    private final IntList bcLabelRelativeOffsets = new IntList();
    082
    083    public void setCurrentClass(final String name, final String superName) {
    084        currentClass = name;
    085        superClass = superName;
    086    }
    087
    088    /**
    089     * All input classes for the segment have now been read in, so this method is called so that this class can
    090     * calculate/complete anything it could not do while classes were being read.
    091     */
    092    public void finaliseBands() {
    093        bcThisField = getIndexInClass(bcThisField);
    094        bcThisMethod = getIndexInClass(bcThisMethod);
    095        bcSuperMethod = getIndexInClass(bcSuperMethod);
    096        bcInitRef = getIndexInClassForConstructor(bcInitRef);
    097    }
    098
    099    @Override
    100    public void pack(final OutputStream out) throws IOException, Pack200Exception {
    101        PackingUtils.log("Writing byte code bands...");
    102        byte[] encodedBand = encodeBandInt("bcCodes", bcCodes.toArray(), Codec.BYTE1);
    103        out.write(encodedBand);
    104        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcCodes[" + bcCodes.size() + "]");
    105
    106        encodedBand = encodeBandInt("bcCaseCount", bcCaseCount.toArray(), Codec.UNSIGNED5);
    107        out.write(encodedBand);
    108        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcCaseCount[" + bcCaseCount.size() + "]");
    109
    110        encodedBand = encodeBandInt("bcCaseValue", bcCaseValue.toArray(), Codec.DELTA5);
    111        out.write(encodedBand);
    112        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcCaseValue[" + bcCaseValue.size() + "]");
    113
    114        encodedBand = encodeBandInt("bcByte", bcByte.toArray(), Codec.BYTE1);
    115        out.write(encodedBand);
    116        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcByte[" + bcByte.size() + "]");
    117
    118        encodedBand = encodeBandInt("bcShort", bcShort.toArray(), Codec.DELTA5);
    119        out.write(encodedBand);
    120        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcShort[" + bcShort.size() + "]");
    121
    122        encodedBand = encodeBandInt("bcLocal", bcLocal.toArray(), Codec.UNSIGNED5);
    123        out.write(encodedBand);
    124        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcLocal[" + bcLocal.size() + "]");
    125
    126        encodedBand = encodeBandInt("bcLabel", integerListToArray(bcLabel), Codec.BRANCH5);
    127        out.write(encodedBand);
    128        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcLabel[" + bcLabel.size() + "]");
    129
    130        encodedBand = encodeBandInt("bcIntref", cpEntryListToArray(bcIntref), Codec.DELTA5);
    131        out.write(encodedBand);
    132        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcIntref[" + bcIntref.size() + "]");
    133
    134        encodedBand = encodeBandInt("bcFloatRef", cpEntryListToArray(bcFloatRef), Codec.DELTA5);
    135        out.write(encodedBand);
    136        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcFloatRef[" + bcFloatRef.size() + "]");
    137
    138        encodedBand = encodeBandInt("bcLongRef", cpEntryListToArray(bcLongRef), Codec.DELTA5);
    139        out.write(encodedBand);
    140        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcLongRef[" + bcLongRef.size() + "]");
    141
    142        encodedBand = encodeBandInt("bcDoubleRef", cpEntryListToArray(bcDoubleRef), Codec.DELTA5);
    143        out.write(encodedBand);
    144        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcDoubleRef[" + bcDoubleRef.size() + "]");
    145
    146        encodedBand = encodeBandInt("bcStringRef", cpEntryListToArray(bcStringRef), Codec.DELTA5);
    147        out.write(encodedBand);
    148        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcStringRef[" + bcStringRef.size() + "]");
    149
    150        encodedBand = encodeBandInt("bcClassRef", cpEntryOrNullListToArray(bcClassRef), Codec.UNSIGNED5);
    151        out.write(encodedBand);
    152        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcClassRef[" + bcClassRef.size() + "]");
    153
    154        encodedBand = encodeBandInt("bcFieldRef", cpEntryListToArray(bcFieldRef), Codec.DELTA5);
    155        out.write(encodedBand);
    156        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcFieldRef[" + bcFieldRef.size() + "]");
    157
    158        encodedBand = encodeBandInt("bcMethodRef", cpEntryListToArray(bcMethodRef), Codec.UNSIGNED5);
    159        out.write(encodedBand);
    160        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcMethodRef[" + bcMethodRef.size() + "]");
    161
    162        encodedBand = encodeBandInt("bcIMethodRef", cpEntryListToArray(bcIMethodRef), Codec.DELTA5);
    163        out.write(encodedBand);
    164        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcIMethodRef[" + bcIMethodRef.size() + "]");
    165
    166        encodedBand = encodeBandInt("bcThisField", integerListToArray(bcThisField), Codec.UNSIGNED5);
    167        out.write(encodedBand);
    168        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcThisField[" + bcThisField.size() + "]");
    169
    170        encodedBand = encodeBandInt("bcSuperField", integerListToArray(bcSuperField), Codec.UNSIGNED5);
    171        out.write(encodedBand);
    172        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcSuperField[" + bcSuperField.size() + "]");
    173
    174        encodedBand = encodeBandInt("bcThisMethod", integerListToArray(bcThisMethod), Codec.UNSIGNED5);
    175        out.write(encodedBand);
    176        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcThisMethod[" + bcThisMethod.size() + "]");
    177
    178        encodedBand = encodeBandInt("bcSuperMethod", integerListToArray(bcSuperMethod), Codec.UNSIGNED5);
    179        out.write(encodedBand);
    180        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcSuperMethod[" + bcSuperMethod.size() + "]");
    181
    182        encodedBand = encodeBandInt("bcInitRef", integerListToArray(bcInitRef), Codec.UNSIGNED5);
    183        out.write(encodedBand);
    184        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcInitRef[" + bcInitRef.size() + "]");
    185
    186        // out.write(encodeBandInt(cpEntryintegerListToArray(bcEscRef),
    187        // Codec.UNSIGNED5));
    188        // out.write(encodeBandInt(integerListToArray(bcEscRefSize),
    189        // Codec.UNSIGNED5));
    190        // out.write(encodeBandInt(integerListToArray(bcEscSize),
    191        // Codec.UNSIGNED5));
    192        // out.write(encodeBandInt(integerListToArray(bcEscByte), Codec.BYTE1));
    193    }
    194
    195    private List getIndexInClass(final List cPMethodOrFieldList) {
    196        final List indices = new ArrayList(cPMethodOrFieldList.size());
    197        for (int i = 0; i < cPMethodOrFieldList.size(); i++) {
    198            final CPMethodOrField cpMF = (CPMethodOrField) cPMethodOrFieldList.get(i);
    199            indices.add(Integer.valueOf(cpMF.getIndexInClass()));
    200        }
    201        return indices;
    202    }
    203
    204    private List getIndexInClassForConstructor(final List cPMethodList) {
    205        final List indices = new ArrayList(cPMethodList.size());
    206        for (int i = 0; i < cPMethodList.size(); i++) {
    207            final CPMethodOrField cpMF = (CPMethodOrField) cPMethodList.get(i);
    208            indices.add(Integer.valueOf(cpMF.getIndexInClassForConstructor()));
    209        }
    210        return indices;
    211    }
    212
    213    public void visitEnd() {
    214        for (int i = 0; i < bciRenumbering.size(); i++) {
    215            if (bciRenumbering.get(i) == -1) {
    216                bciRenumbering.remove(i);
    217                bciRenumbering.add(i, ++renumberedOffset);
    218            }
    219        }
    220        if (renumberedOffset != 0) {
    221            if (renumberedOffset + 1 != bciRenumbering.size()) {
    222                throw new RuntimeException("Mistake made with renumbering");
    223            }
    224            for (int i = bcLabel.size() - 1; i >= 0; i--) {
    225                final Object label = bcLabel.get(i);
    226                if (label instanceof Integer) {
    227                    break;
    228                }
    229                if (label instanceof Label) {
    230                    bcLabel.remove(i);
    231                    final Integer offset = (Integer) labelsToOffsets.get(label);
    232                    final int relativeOffset = bcLabelRelativeOffsets.get(i);
    233                    bcLabel.add(i,
    234                        Integer.valueOf(bciRenumbering.get(offset.intValue()) - bciRenumbering.get(relativeOffset)));
    235                }
    236            }
    237            bcCodes.add(endMarker);
    238            segment.getClassBands().doBciRenumbering(bciRenumbering, labelsToOffsets);
    239            bciRenumbering.clear();
    240            labelsToOffsets.clear();
    241            byteCodeOffset = 0;
    242            renumberedOffset = 0;
    243        }
    244    }
    245
    246    public void visitLabel(final Label label) {
    247        labelsToOffsets.put(label, Integer.valueOf(byteCodeOffset));
    248    }
    249
    250    public void visitFieldInsn(int opcode, final String owner, final String name, final String desc) {
    251        byteCodeOffset += 3;
    252        updateRenumbering();
    253        boolean aload_0 = false;
    254        if (bcCodes.size() > 0 && (bcCodes.get(bcCodes.size() - 1)) == ALOAD_0) {
    255            bcCodes.remove(bcCodes.size() - 1);
    256            aload_0 = true;
    257        }
    258        final CPMethodOrField cpField = cpBands.getCPField(owner, name, desc);
    259        if (aload_0) {
    260            opcode += 7;
    261        }
    262        if (owner.equals(currentClass)) {
    263            opcode += 24; // change to getstatic_this, putstatic_this etc.
    264            bcThisField.add(cpField);
    265//        } else if (owner.equals(superClass)) {
    266//            opcode += 38; // change to getstatic_super etc.
    267//            bcSuperField.add(cpField);
    268        } else {
    269            if (aload_0) {
    270                opcode -= 7;
    271                bcCodes.add(ALOAD_0); // add aload_0 back in because
    272                // there's no special rewrite in
    273                // this case.
    274            }
    275            bcFieldRef.add(cpField);
    276        }
    277        aload_0 = false;
    278        bcCodes.add(opcode);
    279    }
    280
    281    private void updateRenumbering() {
    282        if (bciRenumbering.isEmpty()) {
    283            bciRenumbering.add(0);
    284        }
    285        renumberedOffset++;
    286        for (int i = bciRenumbering.size(); i < byteCodeOffset; i++) {
    287            bciRenumbering.add(-1);
    288        }
    289        bciRenumbering.add(renumberedOffset);
    290    }
    291
    292    public void visitIincInsn(final int var, final int increment) {
    293        if (var > 255 || increment > 255) {
    294            byteCodeOffset += 6;
    295            bcCodes.add(WIDE);
    296            bcCodes.add(IINC);
    297            bcLocal.add(var);
    298            bcShort.add(increment);
    299        } else {
    300            byteCodeOffset += 3;
    301            bcCodes.add(IINC);
    302            bcLocal.add(var);
    303            bcByte.add(increment & 0xFF);
    304        }
    305        updateRenumbering();
    306    }
    307
    308    public void visitInsn(final int opcode) {
    309        if (opcode >= 202) {
    310            throw new RuntimeException("Non-standard bytecode instructions not supported");
    311        }
    312        bcCodes.add(opcode);
    313        byteCodeOffset++;
    314        updateRenumbering();
    315    }
    316
    317    public void visitIntInsn(final int opcode, final int operand) {
    318        switch (opcode) {
    319        case 17: // sipush
    320            bcCodes.add(opcode);
    321            bcShort.add(operand);
    322            byteCodeOffset += 3;
    323            break;
    324        case 16: // bipush
    325        case 188: // newarray
    326            bcCodes.add(opcode);
    327            bcByte.add(operand & 0xFF);
    328            byteCodeOffset += 2;
    329        }
    330        updateRenumbering();
    331    }
    332
    333    public void visitJumpInsn(final int opcode, final Label label) {
    334        bcCodes.add(opcode);
    335        bcLabel.add(label);
    336        bcLabelRelativeOffsets.add(byteCodeOffset);
    337        byteCodeOffset += 3;
    338        updateRenumbering();
    339    }
    340
    341    public void visitLdcInsn(final Object cst) {
    342        final CPConstant constant = cpBands.getConstant(cst);
    343        if (segment.lastConstantHadWideIndex() || constant instanceof CPLong || constant instanceof CPDouble) {
    344            byteCodeOffset += 3;
    345            if (constant instanceof CPInt) {
    346                bcCodes.add(237); // ildc_w
    347                bcIntref.add(constant);
    348            } else if (constant instanceof CPFloat) {
    349                bcCodes.add(238); // fldc
    350                bcFloatRef.add(constant);
    351            } else if (constant instanceof CPLong) {
    352                bcCodes.add(20); // lldc2_w
    353                bcLongRef.add(constant);
    354            } else if (constant instanceof CPDouble) {
    355                bcCodes.add(239); // dldc2_w
    356                bcDoubleRef.add(constant);
    357            } else if (constant instanceof CPString) {
    358                bcCodes.add(19); // aldc
    359                bcStringRef.add(constant);
    360            } else if (constant instanceof CPClass) {
    361                bcCodes.add(236); // cldc
    362                bcClassRef.add(constant);
    363            } else {
    364                throw new RuntimeException("Constant should not be null");
    365            }
    366        } else {
    367            byteCodeOffset += 2;
    368            if (constant instanceof CPInt) {
    369                bcCodes.add(234); // ildc
    370                bcIntref.add(constant);
    371            } else if (constant instanceof CPFloat) {
    372                bcCodes.add(235); // fldc
    373                bcFloatRef.add(constant);
    374            } else if (constant instanceof CPString) {
    375                bcCodes.add(18); // aldc
    376                bcStringRef.add(constant);
    377            } else if (constant instanceof CPClass) {
    378                bcCodes.add(233); // cldc
    379                bcClassRef.add(constant);
    380            }
    381        }
    382        updateRenumbering();
    383    }
    384
    385    public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
    386        bcCodes.add(LOOKUPSWITCH);
    387        bcLabel.add(dflt);
    388        bcLabelRelativeOffsets.add(byteCodeOffset);
    389        bcCaseCount.add(keys.length);
    390        for (int i = 0; i < labels.length; i++) {
    391            bcCaseValue.add(keys[i]);
    392            bcLabel.add(labels[i]);
    393            bcLabelRelativeOffsets.add(byteCodeOffset);
    394        }
    395        final int padding = (byteCodeOffset + 1) % 4 == 0 ? 0 : 4 - ((byteCodeOffset + 1) % 4);
    396        byteCodeOffset += 1 + padding + 8 + 8 * keys.length;
    397        updateRenumbering();
    398    }
    399
    400    public void visitMethodInsn(int opcode, final String owner, final String name, final String desc) {
    401        byteCodeOffset += 3;
    402        switch (opcode) {
    403        case 182: // invokevirtual
    404        case 183: // invokespecial
    405        case 184: // invokestatic
    406            boolean aload_0 = false;
    407            if (bcCodes.size() > 0 && (bcCodes.get(bcCodes.size() - 1)) == (ALOAD_0)) {
    408                bcCodes.remove(bcCodes.size() - 1);
    409                aload_0 = true;
    410                opcode += 7;
    411            }
    412            if (owner.equals(currentClass)) {
    413                opcode += 24; // change to invokevirtual_this,
    414                // invokespecial_this etc.
    415
    416                if (name.equals("<init>") && opcode == 207) {
    417                    opcode = 230; // invokespecial_this_init
    418                    bcInitRef.add(cpBands.getCPMethod(owner, name, desc));
    419                } else {
    420                    bcThisMethod.add(cpBands.getCPMethod(owner, name, desc));
    421                }
    422            } else if (owner.equals(superClass)) { // TODO
    423                opcode += 38; // change to invokevirtual_super,
    424                // invokespecial_super etc.
    425                if (name.equals("<init>") && opcode == 221) {
    426                    opcode = 231; // invokespecial_super_init
    427                    bcInitRef.add(cpBands.getCPMethod(owner, name, desc));
    428                } else {
    429                    bcSuperMethod.add(cpBands.getCPMethod(owner, name, desc));
    430                }
    431            } else {
    432                if (aload_0) {
    433                    opcode -= 7;
    434                    bcCodes.add(ALOAD_0); // add aload_0 back in
    435                    // because there's no
    436                    // special rewrite in this
    437                    // case.
    438                }
    439                if (name.equals("<init>") && opcode == 183 && owner.equals(currentNewClass)) {
    440                    opcode = 232; // invokespecial_new_init
    441                    bcInitRef.add(cpBands.getCPMethod(owner, name, desc));
    442                } else {
    443                    bcMethodRef.add(cpBands.getCPMethod(owner, name, desc));
    444                }
    445            }
    446            bcCodes.add(opcode);
    447            break;
    448        case 185: // invokeinterface
    449            byteCodeOffset += 2;
    450            final CPMethodOrField cpIMethod = cpBands.getCPIMethod(owner, name, desc);
    451            bcIMethodRef.add(cpIMethod);
    452            bcCodes.add(INVOKEINTERFACE);
    453            break;
    454        }
    455        updateRenumbering();
    456    }
    457
    458    public void visitMultiANewArrayInsn(final String desc, final int dimensions) {
    459        byteCodeOffset += 4;
    460        updateRenumbering();
    461        bcCodes.add(MULTIANEWARRAY);
    462        bcClassRef.add(cpBands.getCPClass(desc));
    463        bcByte.add(dimensions & 0xFF);
    464    }
    465
    466    public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label[] labels) {
    467        bcCodes.add(TABLESWITCH);
    468        bcLabel.add(dflt);
    469        bcLabelRelativeOffsets.add(byteCodeOffset);
    470        bcCaseValue.add(min);
    471        final int count = labels.length;
    472        bcCaseCount.add(count);
    473        for (int i = 0; i < count; i++) {
    474            bcLabel.add(labels[i]);
    475            bcLabelRelativeOffsets.add(byteCodeOffset);
    476        }
    477        final int padding = byteCodeOffset % 4 == 0 ? 0 : 4 - (byteCodeOffset % 4);
    478        byteCodeOffset += (padding + 12 + 4 * labels.length);
    479        updateRenumbering();
    480    }
    481
    482    public void visitTypeInsn(final int opcode, final String type) {
    483        // NEW, ANEWARRAY, CHECKCAST or INSTANCEOF
    484        byteCodeOffset += 3;
    485        updateRenumbering();
    486        bcCodes.add(opcode);
    487        bcClassRef.add(cpBands.getCPClass(type));
    488        if (opcode == 187) { // NEW
    489            currentNewClass = type;
    490        }
    491    }
    492
    493    public void visitVarInsn(final int opcode, final int var) {
    494        // ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET
    495        if (var > 255) {
    496            byteCodeOffset += 4;
    497            bcCodes.add(WIDE);
    498            bcCodes.add(opcode);
    499            bcLocal.add(var);
    500        } else if (var > 3 || opcode == 169 /* RET */) {
    501            byteCodeOffset += 2;
    502            bcCodes.add(opcode);
    503            bcLocal.add(var);
    504        } else {
    505            byteCodeOffset += 1;
    506            switch (opcode) {
    507            case 21: // ILOAD
    508            case 54: // ISTORE
    509                bcCodes.add(opcode + 5 + var);
    510                break;
    511            case 22: // LLOAD
    512            case 55: // LSTORE
    513                bcCodes.add(opcode + 8 + var);
    514                break;
    515            case 23: // FLOAD
    516            case 56: // FSTORE
    517                bcCodes.add(opcode + 11 + var);
    518                break;
    519            case 24: // DLOAD
    520            case 57: // DSTORE
    521                bcCodes.add(opcode + 14 + var);
    522                break;
    523            case 25: // A_LOAD
    524            case 58: // A_STORE
    525                bcCodes.add(opcode + 17 + var);
    526                break;
    527            }
    528        }
    529        updateRenumbering();
    530    }
    531
    532}