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.unpack200.bytecode;
018
019import java.io.DataOutputStream;
020import java.io.IOException;
021
022import org.apache.commons.compress.harmony.unpack200.Segment;
023import org.apache.commons.compress.harmony.unpack200.bytecode.forms.ByteCodeForm;
024
025/**
026 * A bytecode class file entry.
027 */
028public class ByteCode extends ClassFileEntry {
029
030    private static ByteCode[] noArgByteCodes = new ByteCode[255];
031
032    public static ByteCode getByteCode(final int opcode) {
033        final int byteOpcode = 0xFF & opcode;
034        if (ByteCodeForm.get(byteOpcode).hasNoOperand()) {
035            if (null == noArgByteCodes[byteOpcode]) {
036                noArgByteCodes[byteOpcode] = new ByteCode(byteOpcode);
037            }
038            return noArgByteCodes[byteOpcode];
039        }
040        return new ByteCode(byteOpcode);
041    }
042
043    private final ByteCodeForm byteCodeForm;
044
045    private ClassFileEntry[] nested;
046    private int[][] nestedPositions;
047    private int[] rewrite;
048
049    private int byteCodeOffset = -1;
050    private int[] byteCodeTargets;
051
052    protected ByteCode(final int opcode) {
053        this(opcode, ClassFileEntry.NONE);
054    }
055
056    protected ByteCode(final int opcode, final ClassFileEntry[] nested) {
057        this.byteCodeForm = ByteCodeForm.get(opcode);
058        this.rewrite = byteCodeForm.getRewriteCopy();
059        this.nested = nested;
060    }
061
062    /**
063     * Some ByteCodes (in particular, those with labels need to be fixed up after all the bytecodes in the CodeAttribute have been added. (This can't be done
064     * beforehand because the CodeAttribute needs to be complete before targets can be assigned.)
065     *
066     * @param codeAttribute the code attribute
067     */
068    public void applyByteCodeTargetFixup(final CodeAttribute codeAttribute) {
069        getByteCodeForm().fixUpByteCodeTargets(this, codeAttribute);
070    }
071
072    @Override
073    protected void doWrite(final DataOutputStream dos) throws IOException {
074        for (final int element : rewrite) {
075            dos.writeByte(element);
076        }
077    }
078
079    @Override
080    public boolean equals(final Object obj) {
081        return this == obj;
082    }
083
084    public void extractOperands(final OperandManager operandManager, final Segment segment, final int codeLength) {
085        // Given an OperandTable, figure out which operands
086        // the receiver needs and stuff them in operands.
087        // Later on the operands can be rewritten (But that's
088        // later, not now).
089        final ByteCodeForm currentByteCodeForm = getByteCodeForm();
090        currentByteCodeForm.setByteCodeOperands(this, operandManager, codeLength);
091    }
092
093    protected ByteCodeForm getByteCodeForm() {
094        return byteCodeForm;
095    }
096
097    public int getByteCodeIndex() {
098        return byteCodeOffset;
099    }
100
101    public int[] getByteCodeTargets() {
102        return byteCodeTargets;
103    }
104
105    public int getLength() {
106        return rewrite.length;
107    }
108
109    public String getName() {
110        return getByteCodeForm().getName();
111    }
112
113    @Override
114    public ClassFileEntry[] getNestedClassFileEntries() {
115        return nested;
116    }
117
118    public int[] getNestedPosition(final int index) {
119        return getNestedPositions()[index];
120    }
121
122    public int[][] getNestedPositions() {
123        return nestedPositions;
124    }
125
126    public int getOpcode() {
127        return getByteCodeForm().getOpcode();
128    }
129
130    /**
131     * Some bytecodes (the ones with variable lengths) can't have a static rewrite array - they need the ability to update the array. This method permits their
132     * associated bytecode formst to query their rewrite array.
133     *
134     * Note that this should not be called from bytecodes which have a static rewrite; use the table in ByteCodeForm instead to specify those rewrites.
135     *
136     * @return Some bytecodes.
137     */
138    public int[] getRewrite() {
139        return rewrite;
140    }
141
142    @Override
143    public int hashCode() {
144        return objectHashCode();
145    }
146
147    /**
148     * This method will answer true if the receiver is a multi-bytecode instruction (such as aload0_putfield_super); otherwise, it will answer false.
149     *
150     * @return boolean true if multibytecode, false otherwise
151     */
152    public boolean hasMultipleByteCodes() {
153        return getByteCodeForm().hasMultipleByteCodes();
154    }
155
156    public boolean nestedMustStartClassPool() {
157        return byteCodeForm.nestedMustStartClassPool();
158    }
159
160    /*
161     * (non-Javadoc)
162     *
163     * @see org.apache.commons.compress.harmony.unpack200.bytecode.ClassFileEntry#resolve(org.apache.commons.compress.harmony
164     * .unpack200.bytecode.ClassConstantPool)
165     */
166    @Override
167    protected void resolve(final ClassConstantPool pool) {
168        super.resolve(pool);
169        if (nested.length > 0) {
170            // Update the bytecode rewrite array so that it points
171            // to the elements of the nested array.
172            for (int index = 0; index < nested.length; index++) {
173                final int argLength = getNestedPosition(index)[1];
174                switch (argLength) {
175
176                case 1:
177                    setOperandByte(pool.indexOf(nested[index]), getNestedPosition(index)[0]);
178                    break;
179
180                case 2:
181                    setOperand2Bytes(pool.indexOf(nested[index]), getNestedPosition(index)[0]);
182                    break;
183
184                default:
185                    throw new Error("Unhandled resolve " + this);
186                }
187            }
188        }
189    }
190
191    /**
192     * ByteCodes may need to know their position in the code array (in particular, label byte codes need to know where they are in order to calculate their
193     * targets). This method lets the CodeAttribute specify where the byte code is.
194     *
195     * Since there are no aload0+label instructions, this method doesn't worry about multioperation bytecodes.
196     *
197     * @param byteCodeOffset int position in code array.
198     */
199    public void setByteCodeIndex(final int byteCodeOffset) {
200        this.byteCodeOffset = byteCodeOffset;
201    }
202
203    /**
204     * Some ByteCodes (in particular, those with labels) have to keep track of byteCodeTargets. These are initially offsets in the CodeAttribute array relative
205     * to the byteCodeOffset, but later get fixed up to point to the absolute position in the CodeAttribute array. This method sets the targets.
206     *
207     * @param byteCodeTargets int index in array
208     */
209    public void setByteCodeTargets(final int[] byteCodeTargets) {
210        this.byteCodeTargets = byteCodeTargets;
211    }
212
213    public void setNested(final ClassFileEntry[] nested) {
214        this.nested = nested;
215    }
216
217    /**
218     * nestedPositions is an array of arrays of ints. Each subarray specifies a position of a nested element (from the nested[] array) and the length of that
219     * element.
220     *
221     * For instance, one might have a nested of: {CPClass java/lang/Foo, CPFloat 3.14} The nestedPositions would then be: {{0,2},{2,2}} In other words, when the
222     * bytecode is resolved, the CPClass will be resolved to an int and inserted at position 0 and 1 of the rewrite arguments (the first occurrences of -1). The
223     * CPFloat will be resolved to an int position and inserted at positions 2 and 3 of the rewrite arguments.
224     *
225     * @param nestedPositions Each subarray specifies a position of a nested element (from the nested[] array) and the length of that element.
226     */
227    public void setNestedPositions(final int[][] nestedPositions) {
228        this.nestedPositions = nestedPositions;
229    }
230
231    /**
232     * Given an int operand, set the rewrite bytes for that position and the one immediately following it to a high-byte, low-byte encoding of the operand.
233     *
234     * @param operand  int to set the rewrite bytes to
235     * @param position int position in the operands of the rewrite bytes. For a rewrite array of {100, -1, -1, -1} position 0 is the first -1, position 1 is the
236     *                 second -1, etc.
237     */
238    public void setOperand2Bytes(final int operand, final int position) {
239        final int firstOperandIndex = getByteCodeForm().firstOperandIndex();
240        final int byteCodeFormLength = getByteCodeForm().getRewrite().length;
241        if (firstOperandIndex < 1) {
242            // No operand rewriting permitted for this bytecode
243            throw new Error("Trying to rewrite " + this + " that has no rewrite");
244        }
245
246        if (firstOperandIndex + position + 1 > byteCodeFormLength) {
247            throw new Error("Trying to rewrite " + this + " with an int at position " + position + " but this won't fit in the rewrite array");
248        }
249
250        rewrite[firstOperandIndex + position] = (operand & 0xFF00) >> 8;
251        rewrite[firstOperandIndex + position + 1] = operand & 0xFF;
252    }
253
254    /**
255     * Given an int operand, treat it as a byte and set the rewrite byte for that position to that value. Mask of anything beyond 0xFF.
256     *
257     * @param operand  int to set the rewrite byte to (unsigned)
258     * @param position int position in the operands of the rewrite bytes. For a rewrite array of {100, -1, -1, -1} position 0 is the first -1, position 1 is the
259     *                 second -1, etc.
260     */
261    public void setOperandByte(final int operand, final int position) {
262        final int firstOperandIndex = getByteCodeForm().firstOperandIndex();
263        final int byteCodeFormLength = getByteCodeForm().operandLength();
264        if (firstOperandIndex < 1) {
265            // No operand rewriting permitted for this bytecode
266            throw new Error("Trying to rewrite " + this + " that has no rewrite");
267        }
268
269        if (firstOperandIndex + position > byteCodeFormLength) {
270            throw new Error("Trying to rewrite " + this + " with an byte at position " + position + " but this won't fit in the rewrite array");
271        }
272
273        rewrite[firstOperandIndex + position] = operand & 0xFF;
274    }
275
276    /**
277     * Given an array of ints which correspond to bytes in the operand of the bytecode, set the rewrite bytes of the operand to be the appropriate values. All
278     * values in operands[] will be masked with 0xFF so they fit into a byte.
279     *
280     * @param operands int[] rewrite operand bytes
281     */
282    public void setOperandBytes(final int[] operands) {
283        final int firstOperandIndex = getByteCodeForm().firstOperandIndex();
284        final int byteCodeFormLength = getByteCodeForm().operandLength();
285        if (firstOperandIndex < 1) {
286            // No operand rewriting permitted for this bytecode
287            throw new Error("Trying to rewrite " + this + " that has no rewrite");
288        }
289
290        if (byteCodeFormLength != operands.length) {
291            throw new Error("Trying to rewrite " + this + " with " + operands.length + " but bytecode has length " + byteCodeForm.operandLength());
292        }
293
294        for (int index = 0; index < byteCodeFormLength; index++) {
295            rewrite[index + firstOperandIndex] = operands[index] & 0xFF;
296        }
297    }
298
299    /**
300     * This is just like setOperandInt, but takes special care when the operand is less than 0 to make sure it's written correctly.
301     *
302     * @param operand  int to set the rewrite bytes to
303     * @param position int position of the operands in the rewrite bytes
304     */
305    public void setOperandSigned2Bytes(final int operand, final int position) {
306        if (operand >= 0) {
307            setOperand2Bytes(operand, position);
308        } else {
309            final int twosComplementOperand = 0x10000 + operand;
310            setOperand2Bytes(twosComplementOperand, position);
311        }
312    }
313
314    /**
315     * Some bytecodes (the ones with variable lengths) can't have a static rewrite array - they need the ability to update the array. This method permits that.
316     *
317     * Note that this should not be called from bytecodes which have a static rewrite; use the table in ByteCodeForm instead to specify those rewrites.
318     *
319     * @param rewrite Some bytecodes.
320     */
321    public void setRewrite(final int[] rewrite) {
322        this.rewrite = rewrite;
323    }
324
325    @Override
326    public String toString() {
327        return getByteCodeForm().getName();
328    }
329}