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