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    public BcBands(final CpBands cpBands, final Segment segment, final int effort) {
084        super(effort, segment.getSegmentHeader());
085        this.cpBands = cpBands;
086        this.segment = segment;
087    }
088
089    /**
090     * All input classes for the segment have now been read in, so this method is called so that this class can
091     * calculate/complete anything it could not do while classes were being read.
092     */
093    public void finaliseBands() {
094        bcThisField = getIndexInClass(bcThisField);
095        bcThisMethod = getIndexInClass(bcThisMethod);
096        bcSuperMethod = getIndexInClass(bcSuperMethod);
097        bcInitRef = getIndexInClassForConstructor(bcInitRef);
098    }
099
100    private List<Integer> getIndexInClass(final List<CPMethodOrField> cPMethodOrFieldList) {
101        return cPMethodOrFieldList.stream().collect(Collectors.mapping(CPMethodOrField::getIndexInClass, Collectors.toList()));
102    }
103
104    private List<Integer> getIndexInClassForConstructor(final List<CPMethodOrField> cPMethodList) {
105        return cPMethodList.stream().collect(Collectors.mapping(CPMethodOrField::getIndexInClassForConstructor, Collectors.toList()));
106    }
107
108    @Override
109    public void pack(final OutputStream out) throws IOException, Pack200Exception {
110        PackingUtils.log("Writing byte code bands...");
111        byte[] encodedBand = encodeBandInt("bcCodes", bcCodes.toArray(), Codec.BYTE1);
112        out.write(encodedBand);
113        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcCodes[" + bcCodes.size() + "]");
114
115        encodedBand = encodeBandInt("bcCaseCount", bcCaseCount.toArray(), Codec.UNSIGNED5);
116        out.write(encodedBand);
117        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcCaseCount[" + bcCaseCount.size() + "]");
118
119        encodedBand = encodeBandInt("bcCaseValue", bcCaseValue.toArray(), Codec.DELTA5);
120        out.write(encodedBand);
121        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcCaseValue[" + bcCaseValue.size() + "]");
122
123        encodedBand = encodeBandInt("bcByte", bcByte.toArray(), Codec.BYTE1);
124        out.write(encodedBand);
125        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcByte[" + bcByte.size() + "]");
126
127        encodedBand = encodeBandInt("bcShort", bcShort.toArray(), Codec.DELTA5);
128        out.write(encodedBand);
129        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcShort[" + bcShort.size() + "]");
130
131        encodedBand = encodeBandInt("bcLocal", bcLocal.toArray(), Codec.UNSIGNED5);
132        out.write(encodedBand);
133        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcLocal[" + bcLocal.size() + "]");
134
135        encodedBand = encodeBandInt("bcLabel", integerListToArray(bcLabel), Codec.BRANCH5);
136        out.write(encodedBand);
137        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcLabel[" + bcLabel.size() + "]");
138
139        encodedBand = encodeBandInt("bcIntref", cpEntryListToArray(bcIntref), Codec.DELTA5);
140        out.write(encodedBand);
141        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcIntref[" + bcIntref.size() + "]");
142
143        encodedBand = encodeBandInt("bcFloatRef", cpEntryListToArray(bcFloatRef), Codec.DELTA5);
144        out.write(encodedBand);
145        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcFloatRef[" + bcFloatRef.size() + "]");
146
147        encodedBand = encodeBandInt("bcLongRef", cpEntryListToArray(bcLongRef), Codec.DELTA5);
148        out.write(encodedBand);
149        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcLongRef[" + bcLongRef.size() + "]");
150
151        encodedBand = encodeBandInt("bcDoubleRef", cpEntryListToArray(bcDoubleRef), Codec.DELTA5);
152        out.write(encodedBand);
153        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcDoubleRef[" + bcDoubleRef.size() + "]");
154
155        encodedBand = encodeBandInt("bcStringRef", cpEntryListToArray(bcStringRef), Codec.DELTA5);
156        out.write(encodedBand);
157        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcStringRef[" + bcStringRef.size() + "]");
158
159        encodedBand = encodeBandInt("bcClassRef", cpEntryOrNullListToArray(bcClassRef), Codec.UNSIGNED5);
160        out.write(encodedBand);
161        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcClassRef[" + bcClassRef.size() + "]");
162
163        encodedBand = encodeBandInt("bcFieldRef", cpEntryListToArray(bcFieldRef), Codec.DELTA5);
164        out.write(encodedBand);
165        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcFieldRef[" + bcFieldRef.size() + "]");
166
167        encodedBand = encodeBandInt("bcMethodRef", cpEntryListToArray(bcMethodRef), Codec.UNSIGNED5);
168        out.write(encodedBand);
169        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcMethodRef[" + bcMethodRef.size() + "]");
170
171        encodedBand = encodeBandInt("bcIMethodRef", cpEntryListToArray(bcIMethodRef), Codec.DELTA5);
172        out.write(encodedBand);
173        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcIMethodRef[" + bcIMethodRef.size() + "]");
174
175        encodedBand = encodeBandInt("bcThisField", integerListToArray(bcThisField), Codec.UNSIGNED5);
176        out.write(encodedBand);
177        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcThisField[" + bcThisField.size() + "]");
178
179        encodedBand = encodeBandInt("bcSuperField", integerListToArray(bcSuperField), Codec.UNSIGNED5);
180        out.write(encodedBand);
181        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcSuperField[" + bcSuperField.size() + "]");
182
183        encodedBand = encodeBandInt("bcThisMethod", integerListToArray(bcThisMethod), Codec.UNSIGNED5);
184        out.write(encodedBand);
185        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcThisMethod[" + bcThisMethod.size() + "]");
186
187        encodedBand = encodeBandInt("bcSuperMethod", integerListToArray(bcSuperMethod), Codec.UNSIGNED5);
188        out.write(encodedBand);
189        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcSuperMethod[" + bcSuperMethod.size() + "]");
190
191        encodedBand = encodeBandInt("bcInitRef", integerListToArray(bcInitRef), Codec.UNSIGNED5);
192        out.write(encodedBand);
193        PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcInitRef[" + bcInitRef.size() + "]");
194
195        // out.write(encodeBandInt(cpEntryintegerListToArray(bcEscRef),
196        // Codec.UNSIGNED5));
197        // out.write(encodeBandInt(integerListToArray(bcEscRefSize),
198        // Codec.UNSIGNED5));
199        // out.write(encodeBandInt(integerListToArray(bcEscSize),
200        // Codec.UNSIGNED5));
201        // out.write(encodeBandInt(integerListToArray(bcEscByte), Codec.BYTE1));
202    }
203
204    public void setCurrentClass(final String name, final String superName) {
205        currentClass = name;
206        superClass = superName;
207    }
208
209    private void updateRenumbering() {
210        if (bciRenumbering.isEmpty()) {
211            bciRenumbering.add(0);
212        }
213        renumberedOffset++;
214        for (int i = bciRenumbering.size(); i < byteCodeOffset; i++) {
215            bciRenumbering.add(-1);
216        }
217        bciRenumbering.add(renumberedOffset);
218    }
219
220    public void visitEnd() {
221        for (int i = 0; i < bciRenumbering.size(); i++) {
222            if (bciRenumbering.get(i) == -1) {
223                bciRenumbering.remove(i);
224                bciRenumbering.add(i, ++renumberedOffset);
225            }
226        }
227        if (renumberedOffset != 0) {
228            if (renumberedOffset + 1 != bciRenumbering.size()) {
229                throw new IllegalStateException("Mistake made with renumbering");
230            }
231            for (int i = bcLabel.size() - 1; i >= 0; i--) {
232                final Object label = bcLabel.get(i);
233                if (label instanceof Integer) {
234                    break;
235                }
236                if (label instanceof Label) {
237                    bcLabel.remove(i);
238                    final Integer offset = labelsToOffsets.get(label);
239                    final int relativeOffset = bcLabelRelativeOffsets.get(i);
240                    bcLabel.add(i,
241                        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}