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}