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