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