View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  package org.apache.commons.compress.harmony.pack200;
18  
19  import java.io.IOException;
20  import java.io.OutputStream;
21  import java.util.ArrayList;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.stream.Collectors;
26  
27  import org.objectweb.asm.Label;
28  
29  /**
30   * Bytecode bands (corresponds to the {@code bc_bands} set of bands in the pack200 specification)
31   */
32  public class BcBands extends BandSet {
33  
34      private static final int MULTIANEWARRAY = 197;
35      private static final int ALOAD_0 = 42;
36  
37      private static final int WIDE = 196;
38  
39      private static final int INVOKEINTERFACE = 185;
40      private static final int TABLESWITCH = 170;
41      private static final int IINC = 132;
42      private static final int LOOKUPSWITCH = 171;
43      private static final int endMarker = 255;
44      private final CpBands cpBands;
45  
46      private final Segment segment;
47      private final IntList bcCodes = new IntList();
48      private final IntList bcCaseCount = new IntList();
49      private final IntList bcCaseValue = new IntList();
50      private final IntList bcByte = new IntList();
51      private final IntList bcShort = new IntList();
52      private final IntList bcLocal = new IntList();
53  
54      // Integers and Labels
55      private final List bcLabel = new ArrayList();
56      private final List<CPInt> bcIntref = new ArrayList<>();
57      private final List<CPFloat> bcFloatRef = new ArrayList<>();
58      private final List<CPLong> bcLongRef = new ArrayList<>();
59      private final List<CPDouble> bcDoubleRef = new ArrayList<>();
60      private final List<CPString> bcStringRef = new ArrayList<>();
61      private final List<CPClass> bcClassRef = new ArrayList<>();
62      private final List<CPMethodOrField> bcFieldRef = new ArrayList<>();
63  
64      private final List<CPMethodOrField> bcMethodRef = new ArrayList<>();
65      private final List<CPMethodOrField> bcIMethodRef = new ArrayList<>();
66  
67      // Integers and CPMethodOrField
68      private List bcThisField = new ArrayList<>();
69  
70      private final List bcSuperField = new ArrayList<>();
71      private List bcThisMethod = new ArrayList<>();
72      private List bcSuperMethod = new ArrayList<>();
73      private List bcInitRef = new ArrayList<>();
74      private String currentClass;
75      private String superClass;
76      private String currentNewClass;
77      private final IntList bciRenumbering = new IntList();
78  
79      private final Map<Label, Integer> labelsToOffsets = new HashMap<>();
80      private int byteCodeOffset;
81      private int renumberedOffset;
82      private final IntList bcLabelRelativeOffsets = new IntList();
83  
84      public BcBands(final CpBands cpBands, final Segment segment, final int effort) {
85          super(effort, segment.getSegmentHeader());
86          this.cpBands = cpBands;
87          this.segment = segment;
88      }
89  
90      /**
91       * 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
92       * while classes were being read.
93       */
94      public void finaliseBands() {
95          bcThisField = getIndexInClass(bcThisField);
96          bcThisMethod = getIndexInClass(bcThisMethod);
97          bcSuperMethod = getIndexInClass(bcSuperMethod);
98          bcInitRef = getIndexInClassForConstructor(bcInitRef);
99      }
100 
101     private List<Integer> getIndexInClass(final List<CPMethodOrField> cPMethodOrFieldList) {
102         return cPMethodOrFieldList.stream().collect(Collectors.mapping(CPMethodOrField::getIndexInClass, Collectors.toList()));
103     }
104 
105     private List<Integer> getIndexInClassForConstructor(final List<CPMethodOrField> cPMethodList) {
106         return cPMethodList.stream().collect(Collectors.mapping(CPMethodOrField::getIndexInClassForConstructor, Collectors.toList()));
107     }
108 
109     @Override
110     public void pack(final OutputStream out) throws IOException, Pack200Exception {
111         PackingUtils.log("Writing byte code bands...");
112         byte[] encodedBand = encodeBandInt("bcCodes", bcCodes.toArray(), Codec.BYTE1);
113         out.write(encodedBand);
114         PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcCodes[" + bcCodes.size() + "]");
115 
116         encodedBand = encodeBandInt("bcCaseCount", bcCaseCount.toArray(), Codec.UNSIGNED5);
117         out.write(encodedBand);
118         PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcCaseCount[" + bcCaseCount.size() + "]");
119 
120         encodedBand = encodeBandInt("bcCaseValue", bcCaseValue.toArray(), Codec.DELTA5);
121         out.write(encodedBand);
122         PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcCaseValue[" + bcCaseValue.size() + "]");
123 
124         encodedBand = encodeBandInt("bcByte", bcByte.toArray(), Codec.BYTE1);
125         out.write(encodedBand);
126         PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcByte[" + bcByte.size() + "]");
127 
128         encodedBand = encodeBandInt("bcShort", bcShort.toArray(), Codec.DELTA5);
129         out.write(encodedBand);
130         PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcShort[" + bcShort.size() + "]");
131 
132         encodedBand = encodeBandInt("bcLocal", bcLocal.toArray(), Codec.UNSIGNED5);
133         out.write(encodedBand);
134         PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcLocal[" + bcLocal.size() + "]");
135 
136         encodedBand = encodeBandInt("bcLabel", integerListToArray(bcLabel), Codec.BRANCH5);
137         out.write(encodedBand);
138         PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcLabel[" + bcLabel.size() + "]");
139 
140         encodedBand = encodeBandInt("bcIntref", cpEntryListToArray(bcIntref), Codec.DELTA5);
141         out.write(encodedBand);
142         PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcIntref[" + bcIntref.size() + "]");
143 
144         encodedBand = encodeBandInt("bcFloatRef", cpEntryListToArray(bcFloatRef), Codec.DELTA5);
145         out.write(encodedBand);
146         PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcFloatRef[" + bcFloatRef.size() + "]");
147 
148         encodedBand = encodeBandInt("bcLongRef", cpEntryListToArray(bcLongRef), Codec.DELTA5);
149         out.write(encodedBand);
150         PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcLongRef[" + bcLongRef.size() + "]");
151 
152         encodedBand = encodeBandInt("bcDoubleRef", cpEntryListToArray(bcDoubleRef), Codec.DELTA5);
153         out.write(encodedBand);
154         PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcDoubleRef[" + bcDoubleRef.size() + "]");
155 
156         encodedBand = encodeBandInt("bcStringRef", cpEntryListToArray(bcStringRef), Codec.DELTA5);
157         out.write(encodedBand);
158         PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcStringRef[" + bcStringRef.size() + "]");
159 
160         encodedBand = encodeBandInt("bcClassRef", cpEntryOrNullListToArray(bcClassRef), Codec.UNSIGNED5);
161         out.write(encodedBand);
162         PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcClassRef[" + bcClassRef.size() + "]");
163 
164         encodedBand = encodeBandInt("bcFieldRef", cpEntryListToArray(bcFieldRef), Codec.DELTA5);
165         out.write(encodedBand);
166         PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcFieldRef[" + bcFieldRef.size() + "]");
167 
168         encodedBand = encodeBandInt("bcMethodRef", cpEntryListToArray(bcMethodRef), Codec.UNSIGNED5);
169         out.write(encodedBand);
170         PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcMethodRef[" + bcMethodRef.size() + "]");
171 
172         encodedBand = encodeBandInt("bcIMethodRef", cpEntryListToArray(bcIMethodRef), Codec.DELTA5);
173         out.write(encodedBand);
174         PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcIMethodRef[" + bcIMethodRef.size() + "]");
175 
176         encodedBand = encodeBandInt("bcThisField", integerListToArray(bcThisField), Codec.UNSIGNED5);
177         out.write(encodedBand);
178         PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcThisField[" + bcThisField.size() + "]");
179 
180         encodedBand = encodeBandInt("bcSuperField", integerListToArray(bcSuperField), Codec.UNSIGNED5);
181         out.write(encodedBand);
182         PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcSuperField[" + bcSuperField.size() + "]");
183 
184         encodedBand = encodeBandInt("bcThisMethod", integerListToArray(bcThisMethod), Codec.UNSIGNED5);
185         out.write(encodedBand);
186         PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcThisMethod[" + bcThisMethod.size() + "]");
187 
188         encodedBand = encodeBandInt("bcSuperMethod", integerListToArray(bcSuperMethod), Codec.UNSIGNED5);
189         out.write(encodedBand);
190         PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcSuperMethod[" + bcSuperMethod.size() + "]");
191 
192         encodedBand = encodeBandInt("bcInitRef", integerListToArray(bcInitRef), Codec.UNSIGNED5);
193         out.write(encodedBand);
194         PackingUtils.log("Wrote " + encodedBand.length + " bytes from bcInitRef[" + bcInitRef.size() + "]");
195 
196         // out.write(encodeBandInt(cpEntryintegerListToArray(bcEscRef),
197         // Codec.UNSIGNED5));
198         // out.write(encodeBandInt(integerListToArray(bcEscRefSize),
199         // Codec.UNSIGNED5));
200         // out.write(encodeBandInt(integerListToArray(bcEscSize),
201         // Codec.UNSIGNED5));
202         // out.write(encodeBandInt(integerListToArray(bcEscByte), Codec.BYTE1));
203     }
204 
205     public void setCurrentClass(final String name, final String superName) {
206         currentClass = name;
207         superClass = superName;
208     }
209 
210     private void updateRenumbering() {
211         if (bciRenumbering.isEmpty()) {
212             bciRenumbering.add(0);
213         }
214         renumberedOffset++;
215         for (int i = bciRenumbering.size(); i < byteCodeOffset; i++) {
216             bciRenumbering.add(-1);
217         }
218         bciRenumbering.add(renumberedOffset);
219     }
220 
221     public void visitEnd() {
222         for (int i = 0; i < bciRenumbering.size(); i++) {
223             if (bciRenumbering.get(i) == -1) {
224                 bciRenumbering.remove(i);
225                 bciRenumbering.add(i, ++renumberedOffset);
226             }
227         }
228         if (renumberedOffset != 0) {
229             if (renumberedOffset + 1 != bciRenumbering.size()) {
230                 throw new IllegalStateException("Mistake made with renumbering");
231             }
232             for (int i = bcLabel.size() - 1; i >= 0; i--) {
233                 final Object label = bcLabel.get(i);
234                 if (label instanceof Integer) {
235                     break;
236                 }
237                 if (label instanceof Label) {
238                     bcLabel.remove(i);
239                     final Integer offset = labelsToOffsets.get(label);
240                     final int relativeOffset = bcLabelRelativeOffsets.get(i);
241                     bcLabel.add(i, Integer.valueOf(bciRenumbering.get(offset.intValue()) - bciRenumbering.get(relativeOffset)));
242                 }
243             }
244             bcCodes.add(endMarker);
245             segment.getClassBands().doBciRenumbering(bciRenumbering, labelsToOffsets);
246             bciRenumbering.clear();
247             labelsToOffsets.clear();
248             byteCodeOffset = 0;
249             renumberedOffset = 0;
250         }
251     }
252 
253     public void visitFieldInsn(int opcode, final String owner, final String name, final String desc) {
254         byteCodeOffset += 3;
255         updateRenumbering();
256         boolean aload_0 = false;
257         if (bcCodes.size() > 0 && bcCodes.get(bcCodes.size() - 1) == ALOAD_0) {
258             bcCodes.remove(bcCodes.size() - 1);
259             aload_0 = true;
260         }
261         final CPMethodOrField cpField = cpBands.getCPField(owner, name, desc);
262         if (aload_0) {
263             opcode += 7;
264         }
265         if (owner.equals(currentClass)) {
266             opcode += 24; // change to getstatic_this, putstatic_this etc.
267             bcThisField.add(cpField);
268 //        } else if (owner.equals(superClass)) {
269 //            opcode += 38; // change to getstatic_super etc.
270 //            bcSuperField.add(cpField);
271         } else {
272             if (aload_0) {
273                 opcode -= 7;
274                 bcCodes.add(ALOAD_0); // add aload_0 back in because
275                 // there's no special rewrite in
276                 // this case.
277             }
278             bcFieldRef.add(cpField);
279         }
280         aload_0 = false;
281         bcCodes.add(opcode);
282     }
283 
284     public void visitIincInsn(final int var, final int increment) {
285         if (var > 255 || increment > 255) {
286             byteCodeOffset += 6;
287             bcCodes.add(WIDE);
288             bcCodes.add(IINC);
289             bcLocal.add(var);
290             bcShort.add(increment);
291         } else {
292             byteCodeOffset += 3;
293             bcCodes.add(IINC);
294             bcLocal.add(var);
295             bcByte.add(increment & 0xFF);
296         }
297         updateRenumbering();
298     }
299 
300     public void visitInsn(final int opcode) {
301         if (opcode >= 202) {
302             throw new IllegalArgumentException("Non-standard bytecode instructions not supported");
303         }
304         bcCodes.add(opcode);
305         byteCodeOffset++;
306         updateRenumbering();
307     }
308 
309     public void visitIntInsn(final int opcode, final int operand) {
310         switch (opcode) {
311         case 17: // sipush
312             bcCodes.add(opcode);
313             bcShort.add(operand);
314             byteCodeOffset += 3;
315             break;
316         case 16: // bipush
317         case 188: // newarray
318             bcCodes.add(opcode);
319             bcByte.add(operand & 0xFF);
320             byteCodeOffset += 2;
321         }
322         updateRenumbering();
323     }
324 
325     public void visitJumpInsn(final int opcode, final Label label) {
326         bcCodes.add(opcode);
327         bcLabel.add(label);
328         bcLabelRelativeOffsets.add(byteCodeOffset);
329         byteCodeOffset += 3;
330         updateRenumbering();
331     }
332 
333     public void visitLabel(final Label label) {
334         labelsToOffsets.put(label, Integer.valueOf(byteCodeOffset));
335     }
336 
337     public void visitLdcInsn(final Object cst) {
338         final CPConstant<?> constant = cpBands.getConstant(cst);
339         if (segment.lastConstantHadWideIndex() || constant instanceof CPLong || constant instanceof CPDouble) {
340             byteCodeOffset += 3;
341             if (constant instanceof CPInt) {
342                 bcCodes.add(237); // ildc_w
343                 bcIntref.add((CPInt) constant);
344             } else if (constant instanceof CPFloat) {
345                 bcCodes.add(238); // fldc
346                 bcFloatRef.add((CPFloat) constant);
347             } else if (constant instanceof CPLong) {
348                 bcCodes.add(20); // lldc2_w
349                 bcLongRef.add((CPLong) constant);
350             } else if (constant instanceof CPDouble) {
351                 bcCodes.add(239); // dldc2_w
352                 bcDoubleRef.add((CPDouble) constant);
353             } else if (constant instanceof CPString) {
354                 bcCodes.add(19); // aldc
355                 bcStringRef.add((CPString) constant);
356             } else if (constant instanceof CPClass) {
357                 bcCodes.add(236); // cldc
358                 bcClassRef.add((CPClass) constant);
359             } else {
360                 throw new IllegalArgumentException("Constant should not be null");
361             }
362         } else {
363             byteCodeOffset += 2;
364             if (constant instanceof CPInt) {
365                 bcCodes.add(234); // ildc
366                 bcIntref.add((CPInt) constant);
367             } else if (constant instanceof CPFloat) {
368                 bcCodes.add(235); // fldc
369                 bcFloatRef.add((CPFloat) constant);
370             } else if (constant instanceof CPString) {
371                 bcCodes.add(18); // aldc
372                 bcStringRef.add((CPString) constant);
373             } else if (constant instanceof CPClass) {
374                 bcCodes.add(233); // cldc
375                 bcClassRef.add((CPClass) constant);
376             }
377         }
378         updateRenumbering();
379     }
380 
381     public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
382         bcCodes.add(LOOKUPSWITCH);
383         bcLabel.add(dflt);
384         bcLabelRelativeOffsets.add(byteCodeOffset);
385         bcCaseCount.add(keys.length);
386         for (int i = 0; i < labels.length; i++) {
387             bcCaseValue.add(keys[i]);
388             bcLabel.add(labels[i]);
389             bcLabelRelativeOffsets.add(byteCodeOffset);
390         }
391         final int padding = (byteCodeOffset + 1) % 4 == 0 ? 0 : 4 - (byteCodeOffset + 1) % 4;
392         byteCodeOffset += 1 + padding + 8 + 8 * keys.length;
393         updateRenumbering();
394     }
395 
396     public void visitMethodInsn(int opcode, final String owner, final String name, final String desc) {
397         byteCodeOffset += 3;
398         switch (opcode) {
399         case 182: // invokevirtual
400         case 183: // invokespecial
401         case 184: // invokestatic
402             boolean aload_0 = false;
403             if (bcCodes.size() > 0 && bcCodes.get(bcCodes.size() - 1) == ALOAD_0) {
404                 bcCodes.remove(bcCodes.size() - 1);
405                 aload_0 = true;
406                 opcode += 7;
407             }
408             if (owner.equals(currentClass)) {
409                 opcode += 24; // change to invokevirtual_this,
410                 // invokespecial_this etc.
411 
412                 if (name.equals("<init>") && opcode == 207) {
413                     opcode = 230; // invokespecial_this_init
414                     bcInitRef.add(cpBands.getCPMethod(owner, name, desc));
415                 } else {
416                     bcThisMethod.add(cpBands.getCPMethod(owner, name, desc));
417                 }
418             } else if (owner.equals(superClass)) { // TODO
419                 opcode += 38; // change to invokevirtual_super,
420                 // invokespecial_super etc.
421                 if (name.equals("<init>") && opcode == 221) {
422                     opcode = 231; // invokespecial_super_init
423                     bcInitRef.add(cpBands.getCPMethod(owner, name, desc));
424                 } else {
425                     bcSuperMethod.add(cpBands.getCPMethod(owner, name, desc));
426                 }
427             } else {
428                 if (aload_0) {
429                     opcode -= 7;
430                     bcCodes.add(ALOAD_0); // add aload_0 back in
431                     // because there's no
432                     // special rewrite in this
433                     // case.
434                 }
435                 if (name.equals("<init>") && opcode == 183 && owner.equals(currentNewClass)) {
436                     opcode = 232; // invokespecial_new_init
437                     bcInitRef.add(cpBands.getCPMethod(owner, name, desc));
438                 } else {
439                     bcMethodRef.add(cpBands.getCPMethod(owner, name, desc));
440                 }
441             }
442             bcCodes.add(opcode);
443             break;
444         case 185: // invokeinterface
445             byteCodeOffset += 2;
446             final CPMethodOrField cpIMethod = cpBands.getCPIMethod(owner, name, desc);
447             bcIMethodRef.add(cpIMethod);
448             bcCodes.add(INVOKEINTERFACE);
449             break;
450         }
451         updateRenumbering();
452     }
453 
454     public void visitMultiANewArrayInsn(final String desc, final int dimensions) {
455         byteCodeOffset += 4;
456         updateRenumbering();
457         bcCodes.add(MULTIANEWARRAY);
458         bcClassRef.add(cpBands.getCPClass(desc));
459         bcByte.add(dimensions & 0xFF);
460     }
461 
462     public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label... labels) {
463         bcCodes.add(TABLESWITCH);
464         bcLabel.add(dflt);
465         bcLabelRelativeOffsets.add(byteCodeOffset);
466         bcCaseValue.add(min);
467         final int count = labels.length;
468         bcCaseCount.add(count);
469         for (int i = 0; i < count; i++) {
470             bcLabel.add(labels[i]);
471             bcLabelRelativeOffsets.add(byteCodeOffset);
472         }
473         final int padding = byteCodeOffset % 4 == 0 ? 0 : 4 - byteCodeOffset % 4;
474         byteCodeOffset += padding + 12 + 4 * labels.length;
475         updateRenumbering();
476     }
477 
478     public void visitTypeInsn(final int opcode, final String type) {
479         // NEW, ANEWARRAY, CHECKCAST or INSTANCEOF
480         byteCodeOffset += 3;
481         updateRenumbering();
482         bcCodes.add(opcode);
483         bcClassRef.add(cpBands.getCPClass(type));
484         if (opcode == 187) { // NEW
485             currentNewClass = type;
486         }
487     }
488 
489     public void visitVarInsn(final int opcode, final int var) {
490         // ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET
491         if (var > 255) {
492             byteCodeOffset += 4;
493             bcCodes.add(WIDE);
494             bcCodes.add(opcode);
495             bcLocal.add(var);
496         } else if (var > 3 || opcode == 169 /* RET */) {
497             byteCodeOffset += 2;
498             bcCodes.add(opcode);
499             bcLocal.add(var);
500         } else {
501             byteCodeOffset += 1;
502             switch (opcode) {
503             case 21: // ILOAD
504             case 54: // ISTORE
505                 bcCodes.add(opcode + 5 + var);
506                 break;
507             case 22: // LLOAD
508             case 55: // LSTORE
509                 bcCodes.add(opcode + 8 + var);
510                 break;
511             case 23: // FLOAD
512             case 56: // FSTORE
513                 bcCodes.add(opcode + 11 + var);
514                 break;
515             case 24: // DLOAD
516             case 57: // DSTORE
517                 bcCodes.add(opcode + 14 + var);
518                 break;
519             case 25: // A_LOAD
520             case 58: // A_STORE
521                 bcCodes.add(opcode + 17 + var);
522                 break;
523             }
524         }
525         updateRenumbering();
526     }
527 
528 }