View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.compress.harmony.pack200;
20  
21  import java.io.IOException;
22  import java.io.OutputStream;
23  import java.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.stream.Collectors;
28  
29  import org.objectweb.asm.Label;
30  
31  /**
32   * Bytecode bands (corresponds to the {@code bc_bands} set of bands in the pack200 specification)
33   */
34  public class BcBands extends BandSet {
35  
36      private static final int MULTIANEWARRAY = 197;
37      private static final int ALOAD_0 = 42;
38  
39      private static final int WIDE = 196;
40  
41      private static final int INVOKEINTERFACE = 185;
42      private static final int TABLESWITCH = 170;
43      private static final int IINC = 132;
44      private static final int LOOKUPSWITCH = 171;
45      private static final int endMarker = 255;
46      private final CpBands cpBands;
47  
48      private final Segment segment;
49      private final IntList bcCodes = new IntList();
50      private final IntList bcCaseCount = new IntList();
51      private final IntList bcCaseValue = new IntList();
52      private final IntList bcByte = new IntList();
53      private final IntList bcShort = new IntList();
54      private final IntList bcLocal = new IntList();
55  
56      // This List holds Integers and Labels
57      private final List bcLabel = new ArrayList();
58      private final List<CPInt> bcIntref = new ArrayList<>();
59      private final List<CPFloat> bcFloatRef = new ArrayList<>();
60      private final List<CPLong> bcLongRef = new ArrayList<>();
61      private final List<CPDouble> bcDoubleRef = new ArrayList<>();
62      private final List<CPString> bcStringRef = new ArrayList<>();
63      private final List<CPClass> bcClassRef = new ArrayList<>();
64      private final List<CPMethodOrField> bcFieldRef = new ArrayList<>();
65  
66      private final List<CPMethodOrField> bcMethodRef = new ArrayList<>();
67      private final List<CPMethodOrField> bcIMethodRef = new ArrayList<>();
68  
69      // This List holds Integers and CPMethodOrField
70      private List bcThisField = new ArrayList<>();
71  
72      private final List bcSuperField = new ArrayList<>();
73      private List bcThisMethod = new ArrayList<>();
74      private List bcSuperMethod = new ArrayList<>();
75      private List bcInitRef = new ArrayList<>();
76      private String currentClass;
77      private String superClass;
78      private String currentNewClass;
79      private final IntList bciRenumbering = new IntList();
80  
81      private final Map<Label, Integer> labelsToOffsets = new HashMap<>();
82      private int byteCodeOffset;
83      private int renumberedOffset;
84      private final IntList bcLabelRelativeOffsets = new IntList();
85  
86      public BcBands(final CpBands cpBands, final Segment segment, final int effort) {
87          super(effort, segment.getSegmentHeader());
88          this.cpBands = cpBands;
89          this.segment = segment;
90      }
91  
92      /**
93       * 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
94       * while classes were being read.
95       */
96      public void finaliseBands() {
97          bcThisField = getIndexInClass(bcThisField);
98          bcThisMethod = getIndexInClass(bcThisMethod);
99          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 }