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