diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2012-04-01 00:00:00 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2013-03-01 09:33:00 -0800 |
commit | 4b279859aaa01ed58457a5cee3335069985ea53c (patch) | |
tree | 1c2bc735be22147da2a67ff3c3822662b7899289 /dexgen | |
download | dalvik2-master.tar.gz |
Change-Id: Ifeff1cc0867b7a1459e6d83cb354883f134fda74
Diffstat (limited to 'dexgen')
218 files changed, 42094 insertions, 0 deletions
diff --git a/dexgen/Android.mk b/dexgen/Android.mk new file mode 100644 index 0000000..59d623a --- /dev/null +++ b/dexgen/Android.mk @@ -0,0 +1,26 @@ +# Copyright (C) 2010 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SDK_VERSION := 4 +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_MODULE := dexgen +LOCAL_MODULE_TAGS := optional + +include $(BUILD_STATIC_JAVA_LIBRARY) + +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/dexgen/README.txt b/dexgen/README.txt new file mode 100644 index 0000000..a542f04 --- /dev/null +++ b/dexgen/README.txt @@ -0,0 +1,3 @@ +Home of dexgen, the dex code generator project. It provides API for +creating dex classes in runtime which is needed e.g. for class mocking. +This solution is based on the dx tool and uses its classes extensively. diff --git a/dexgen/src/com/android/dexgen/dex/code/ArrayData.java b/dexgen/src/com/android/dexgen/dex/code/ArrayData.java new file mode 100644 index 0000000..d89a93f --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/ArrayData.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.code.SourcePosition; +import com.android.dexgen.rop.cst.*; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; + +import java.util.ArrayList; + +/** + * Pseudo-instruction which holds fill array data. + */ +public final class ArrayData extends VariableSizeInsn { + /** + * {@code non-null;} address representing the instruction that uses this + * instance + */ + private final CodeAddress user; + + /** {@code non-null;} initial values to be filled into an array */ + private final ArrayList<Constant> values; + + /** non-null: type of constant that initializes the array */ + private final Constant arrayType; + + /** Width of the init value element */ + private final int elemWidth; + + /** Length of the init list */ + private final int initLength; + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + * @param user {@code non-null;} address representing the instruction that + * uses this instance + * @param values {@code non-null;} initial values to be filled into an array + */ + public ArrayData(SourcePosition position, CodeAddress user, + ArrayList<Constant> values, + Constant arrayType) { + super(position, RegisterSpecList.EMPTY); + + if (user == null) { + throw new NullPointerException("user == null"); + } + + if (values == null) { + throw new NullPointerException("values == null"); + } + + int sz = values.size(); + + if (sz <= 0) { + throw new IllegalArgumentException("Illegal number of init values"); + } + + this.arrayType = arrayType; + + if (arrayType == CstType.BYTE_ARRAY || + arrayType == CstType.BOOLEAN_ARRAY) { + elemWidth = 1; + } else if (arrayType == CstType.SHORT_ARRAY || + arrayType == CstType.CHAR_ARRAY) { + elemWidth = 2; + } else if (arrayType == CstType.INT_ARRAY || + arrayType == CstType.FLOAT_ARRAY) { + elemWidth = 4; + } else if (arrayType == CstType.LONG_ARRAY || + arrayType == CstType.DOUBLE_ARRAY) { + elemWidth = 8; + } else { + throw new IllegalArgumentException("Unexpected constant type"); + } + this.user = user; + this.values = values; + initLength = values.size(); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + int sz = initLength; + // Note: the unit here is 16-bit + return 4 + ((sz * elemWidth) + 1) / 2; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out) { + int sz = values.size(); + + out.writeShort(0x300 | DalvOps.NOP); + out.writeShort(elemWidth); + out.writeInt(initLength); + + + // For speed reasons, replicate the for loop in each case + switch (elemWidth) { + case 1: { + for (int i = 0; i < sz; i++) { + Constant cst = values.get(i); + out.writeByte((byte) ((CstLiteral32) cst).getIntBits()); + } + break; + } + case 2: { + for (int i = 0; i < sz; i++) { + Constant cst = values.get(i); + out.writeShort((short) ((CstLiteral32) cst).getIntBits()); + } + break; + } + case 4: { + for (int i = 0; i < sz; i++) { + Constant cst = values.get(i); + out.writeInt(((CstLiteral32) cst).getIntBits()); + } + break; + } + case 8: { + for (int i = 0; i < sz; i++) { + Constant cst = values.get(i); + out.writeLong(((CstLiteral64) cst).getLongBits()); + } + break; + } + default: + break; + } + + // Pad one byte to make the size of data table multiples of 16-bits + if (elemWidth == 1 && (sz % 2 != 0)) { + out.writeByte(0x00); + } + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new ArrayData(getPosition(), user, values, arrayType); + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + StringBuffer sb = new StringBuffer(100); + + int sz = values.size(); + for (int i = 0; i < sz; i++) { + sb.append("\n "); + sb.append(i); + sb.append(": "); + sb.append(values.get(i).toHuman()); + } + + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + int baseAddress = user.getAddress(); + StringBuffer sb = new StringBuffer(100); + int sz = values.size(); + + sb.append("array-data // for fill-array-data @ "); + sb.append(Hex.u2(baseAddress)); + + for (int i = 0; i < sz; i++) { + sb.append("\n "); + sb.append(i); + sb.append(": "); + sb.append(values.get(i).toHuman()); + } + + return sb.toString(); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/BlockAddresses.java b/dexgen/src/com/android/dexgen/dex/code/BlockAddresses.java new file mode 100644 index 0000000..fa8096e --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/BlockAddresses.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.BasicBlock; +import com.android.dexgen.rop.code.BasicBlockList; +import com.android.dexgen.rop.code.Insn; +import com.android.dexgen.rop.code.RopMethod; +import com.android.dexgen.rop.code.SourcePosition; + +/** + * Container for the set of {@link CodeAddress} instances associated with + * the blocks of a particular method. Each block has a corresponding + * start address, end address, and last instruction address. + */ +public final class BlockAddresses { + /** {@code non-null;} array containing addresses for the start of each basic + * block (indexed by basic block label) */ + private final CodeAddress[] starts; + + /** {@code non-null;} array containing addresses for the final instruction + * of each basic block (indexed by basic block label) */ + private final CodeAddress[] lasts; + + /** {@code non-null;} array containing addresses for the end (just past the + * final instruction) of each basic block (indexed by basic block + * label) */ + private final CodeAddress[] ends; + + /** + * Constructs an instance. + * + * @param method {@code non-null;} the method to have block addresses for + */ + public BlockAddresses(RopMethod method) { + BasicBlockList blocks = method.getBlocks(); + int maxLabel = blocks.getMaxLabel(); + + this.starts = new CodeAddress[maxLabel]; + this.lasts = new CodeAddress[maxLabel]; + this.ends = new CodeAddress[maxLabel]; + + setupArrays(method); + } + + /** + * Gets the instance for the start of the given block. + * + * @param block {@code non-null;} the block in question + * @return {@code non-null;} the appropriate instance + */ + public CodeAddress getStart(BasicBlock block) { + return starts[block.getLabel()]; + } + + /** + * Gets the instance for the start of the block with the given label. + * + * @param label {@code non-null;} the label of the block in question + * @return {@code non-null;} the appropriate instance + */ + public CodeAddress getStart(int label) { + return starts[label]; + } + + /** + * Gets the instance for the final instruction of the given block. + * + * @param block {@code non-null;} the block in question + * @return {@code non-null;} the appropriate instance + */ + public CodeAddress getLast(BasicBlock block) { + return lasts[block.getLabel()]; + } + + /** + * Gets the instance for the final instruction of the block with + * the given label. + * + * @param label {@code non-null;} the label of the block in question + * @return {@code non-null;} the appropriate instance + */ + public CodeAddress getLast(int label) { + return lasts[label]; + } + + /** + * Gets the instance for the end (address after the final instruction) + * of the given block. + * + * @param block {@code non-null;} the block in question + * @return {@code non-null;} the appropriate instance + */ + public CodeAddress getEnd(BasicBlock block) { + return ends[block.getLabel()]; + } + + /** + * Gets the instance for the end (address after the final instruction) + * of the block with the given label. + * + * @param label {@code non-null;} the label of the block in question + * @return {@code non-null;} the appropriate instance + */ + public CodeAddress getEnd(int label) { + return ends[label]; + } + + /** + * Sets up the address arrays. + */ + private void setupArrays(RopMethod method) { + BasicBlockList blocks = method.getBlocks(); + int sz = blocks.size(); + + for (int i = 0; i < sz; i++) { + BasicBlock one = blocks.get(i); + int label = one.getLabel(); + Insn insn = one.getInsns().get(0); + + starts[label] = new CodeAddress(insn.getPosition()); + + SourcePosition pos = one.getLastInsn().getPosition(); + + lasts[label] = new CodeAddress(pos); + ends[label] = new CodeAddress(pos); + } + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/CatchBuilder.java b/dexgen/src/com/android/dexgen/dex/code/CatchBuilder.java new file mode 100644 index 0000000..adb119a --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/CatchBuilder.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.type.Type; + +import java.util.HashSet; + +/** + * Interface for the construction of {@link CatchTable} instances. + */ +public interface CatchBuilder { + /** + * Builds and returns the catch table for this instance. + * + * @return {@code non-null;} the constructed table + */ + public CatchTable build(); + + /** + * Gets whether this instance has any catches at all (either typed + * or catch-all). + * + * @return whether this instance has any catches at all + */ + public boolean hasAnyCatches(); + + /** + * Gets the set of catch types associated with this instance. + * + * @return {@code non-null;} the set of catch types + */ + public HashSet<Type> getCatchTypes(); +} diff --git a/dexgen/src/com/android/dexgen/dex/code/CatchHandlerList.java b/dexgen/src/com/android/dexgen/dex/code/CatchHandlerList.java new file mode 100644 index 0000000..54200ed --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/CatchHandlerList.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.util.FixedSizeList; +import com.android.dexgen.util.Hex; + +/** + * Ordered list of (exception type, handler address) entries. + */ +public final class CatchHandlerList extends FixedSizeList + implements Comparable<CatchHandlerList> { + /** {@code non-null;} empty instance */ + public static final CatchHandlerList EMPTY = new CatchHandlerList(0); + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size {@code >= 0;} the size of the list + */ + public CatchHandlerList(int size) { + super(size); + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public Entry get(int n) { + return (Entry) get0(n); + } + + /** {@inheritDoc} */ + public String toHuman() { + return toHuman("", ""); + } + + /** + * Get the human form of this instance, prefixed on each line + * with the string. + * + * @param prefix {@code non-null;} the prefix for every line + * @param header {@code non-null;} the header for the first line (after the + * first prefix) + * @return {@code non-null;} the human form + */ + public String toHuman(String prefix, String header) { + StringBuilder sb = new StringBuilder(100); + int size = size(); + + sb.append(prefix); + sb.append(header); + sb.append("catch "); + + for (int i = 0; i < size; i++) { + Entry entry = get(i); + + if (i != 0) { + sb.append(",\n"); + sb.append(prefix); + sb.append(" "); + } + + if ((i == (size - 1)) && catchesAll()) { + sb.append("<any>"); + } else { + sb.append(entry.getExceptionType().toHuman()); + } + + sb.append(" -> "); + sb.append(Hex.u2or4(entry.getHandler())); + } + + return sb.toString(); + } + + /** + * Returns whether or not this instance ends with a "catch-all" + * handler. + * + * @return {@code true} if this instance ends with a "catch-all" + * handler or {@code false} if not + */ + public boolean catchesAll() { + int size = size(); + + if (size == 0) { + return false; + } + + Entry last = get(size - 1); + return last.getExceptionType().equals(CstType.OBJECT); + } + + /** + * Sets the entry at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param exceptionType {@code non-null;} type of exception handled + * @param handler {@code >= 0;} exception handler address + */ + public void set(int n, CstType exceptionType, int handler) { + set0(n, new Entry(exceptionType, handler)); + } + + /** + * Sets the entry at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param entry {@code non-null;} the entry to set at {@code n} + */ + public void set(int n, Entry entry) { + set0(n, entry); + } + + /** {@inheritDoc} */ + public int compareTo(CatchHandlerList other) { + if (this == other) { + // Easy out. + return 0; + } + + int thisSize = size(); + int otherSize = other.size(); + int checkSize = Math.min(thisSize, otherSize); + + for (int i = 0; i < checkSize; i++) { + Entry thisEntry = get(i); + Entry otherEntry = other.get(i); + int compare = thisEntry.compareTo(otherEntry); + if (compare != 0) { + return compare; + } + } + + if (thisSize < otherSize) { + return -1; + } else if (thisSize > otherSize) { + return 1; + } + + return 0; + } + + /** + * Entry in the list. + */ + public static class Entry implements Comparable<Entry> { + /** {@code non-null;} type of exception handled */ + private final CstType exceptionType; + + /** {@code >= 0;} exception handler address */ + private final int handler; + + /** + * Constructs an instance. + * + * @param exceptionType {@code non-null;} type of exception handled + * @param handler {@code >= 0;} exception handler address + */ + public Entry(CstType exceptionType, int handler) { + if (handler < 0) { + throw new IllegalArgumentException("handler < 0"); + } + + if (exceptionType == null) { + throw new NullPointerException("exceptionType == null"); + } + + this.handler = handler; + this.exceptionType = exceptionType; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return (handler * 31) + exceptionType.hashCode(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (other instanceof Entry) { + return (compareTo((Entry) other) == 0); + } + + return false; + } + + /** {@inheritDoc} */ + public int compareTo(Entry other) { + if (handler < other.handler) { + return -1; + } else if (handler > other.handler) { + return 1; + } + + return exceptionType.compareTo(other.exceptionType); + } + + /** + * Gets the exception type handled. + * + * @return {@code non-null;} the exception type + */ + public CstType getExceptionType() { + return exceptionType; + } + + /** + * Gets the handler address. + * + * @return {@code >= 0;} the handler address + */ + public int getHandler() { + return handler; + } + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/CatchTable.java b/dexgen/src/com/android/dexgen/dex/code/CatchTable.java new file mode 100644 index 0000000..4de0da0 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/CatchTable.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.util.FixedSizeList; + +/** + * Table of catch entries. Each entry includes a range of code + * addresses for which it is valid and an associated {@link + * CatchHandlerList}. + */ +public final class CatchTable extends FixedSizeList + implements Comparable<CatchTable> { + /** {@code non-null;} empty instance */ + public static final CatchTable EMPTY = new CatchTable(0); + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size {@code >= 0;} the size of the table + */ + public CatchTable(int size) { + super(size); + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public Entry get(int n) { + return (Entry) get0(n); + } + + /** + * Sets the entry at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param entry {@code non-null;} the entry to set at {@code n} + */ + public void set(int n, Entry entry) { + set0(n, entry); + } + + /** {@inheritDoc} */ + public int compareTo(CatchTable other) { + if (this == other) { + // Easy out. + return 0; + } + + int thisSize = size(); + int otherSize = other.size(); + int checkSize = Math.min(thisSize, otherSize); + + for (int i = 0; i < checkSize; i++) { + Entry thisEntry = get(i); + Entry otherEntry = other.get(i); + int compare = thisEntry.compareTo(otherEntry); + if (compare != 0) { + return compare; + } + } + + if (thisSize < otherSize) { + return -1; + } else if (thisSize > otherSize) { + return 1; + } + + return 0; + } + + /** + * Entry in a catch list. + */ + public static class Entry implements Comparable<Entry> { + /** {@code >= 0;} start address */ + private final int start; + + /** {@code > start;} end address (exclusive) */ + private final int end; + + /** {@code non-null;} list of catch handlers */ + private final CatchHandlerList handlers; + + /** + * Constructs an instance. + * + * @param start {@code >= 0;} start address + * @param end {@code > start;} end address (exclusive) + * @param handlers {@code non-null;} list of catch handlers + */ + public Entry(int start, int end, CatchHandlerList handlers) { + if (start < 0) { + throw new IllegalArgumentException("start < 0"); + } + + if (end <= start) { + throw new IllegalArgumentException("end <= start"); + } + + if (handlers.isMutable()) { + throw new IllegalArgumentException("handlers.isMutable()"); + } + + this.start = start; + this.end = end; + this.handlers = handlers; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + int hash = (start * 31) + end; + hash = (hash * 31) + handlers.hashCode(); + return hash; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (other instanceof Entry) { + return (compareTo((Entry) other) == 0); + } + + return false; + } + + /** {@inheritDoc} */ + public int compareTo(Entry other) { + if (start < other.start) { + return -1; + } else if (start > other.start) { + return 1; + } + + if (end < other.end) { + return -1; + } else if (end > other.end) { + return 1; + } + + return handlers.compareTo(other.handlers); + } + + /** + * Gets the start address. + * + * @return {@code >= 0;} the start address + */ + public int getStart() { + return start; + } + + /** + * Gets the end address (exclusive). + * + * @return {@code > start;} the end address (exclusive) + */ + public int getEnd() { + return end; + } + + /** + * Gets the handlers. + * + * @return {@code non-null;} the handlers + */ + public CatchHandlerList getHandlers() { + return handlers; + } + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/CodeAddress.java b/dexgen/src/com/android/dexgen/dex/code/CodeAddress.java new file mode 100644 index 0000000..b9600ee --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/CodeAddress.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.code.SourcePosition; + +/** + * Pseudo-instruction which is used to track an address within a code + * array. Instances are used for such things as branch targets and + * exception handler ranges. Its code size is zero, and so instances + * do not in general directly wind up in any output (either + * human-oriented or binary file). + */ +public final class CodeAddress extends ZeroSizeInsn { + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + */ + public CodeAddress(SourcePosition position) { + super(position); + } + + /** {@inheritDoc} */ + @Override + public final DalvInsn withRegisters(RegisterSpecList registers) { + return new CodeAddress(getPosition()); + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return null; + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + return "code-address"; + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/CstInsn.java b/dexgen/src/com/android/dexgen/dex/code/CstInsn.java new file mode 100644 index 0000000..901266b --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/CstInsn.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.code.SourcePosition; +import com.android.dexgen.rop.cst.Constant; + +/** + * Instruction which has a single constant argument in addition + * to all the normal instruction information. + */ +public final class CstInsn extends FixedSizeInsn { + /** {@code non-null;} the constant argument for this instruction */ + private final Constant constant; + + /** + * {@code >= -1;} the constant pool index for {@link #constant}, or + * {@code -1} if not yet set + */ + private int index; + + /** + * {@code >= -1;} the constant pool index for the class reference in + * {@link #constant} if any, or {@code -1} if not yet set + */ + private int classIndex; + + /** + * Constructs an instance. The output address of this instance is + * initially unknown ({@code -1}) as is the constant pool index. + * + * @param opcode the opcode; one of the constants from {@link Dops} + * @param position {@code non-null;} source position + * @param registers {@code non-null;} register list, including a + * result register if appropriate (that is, registers may be either + * ins or outs) + * @param constant {@code non-null;} constant argument + */ + public CstInsn(Dop opcode, SourcePosition position, + RegisterSpecList registers, Constant constant) { + super(opcode, position, registers); + + if (constant == null) { + throw new NullPointerException("constant == null"); + } + + this.constant = constant; + this.index = -1; + this.classIndex = -1; + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withOpcode(Dop opcode) { + CstInsn result = + new CstInsn(opcode, getPosition(), getRegisters(), constant); + + if (index >= 0) { + result.setIndex(index); + } + + if (classIndex >= 0) { + result.setClassIndex(classIndex); + } + + return result; + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + CstInsn result = + new CstInsn(getOpcode(), getPosition(), registers, constant); + + if (index >= 0) { + result.setIndex(index); + } + + if (classIndex >= 0) { + result.setClassIndex(classIndex); + } + + return result; + } + + /** + * Gets the constant argument. + * + * @return {@code non-null;} the constant argument + */ + public Constant getConstant() { + return constant; + } + + /** + * Gets the constant's index. It is only valid to call this after + * {@link #setIndex} has been called. + * + * @return {@code >= 0;} the constant pool index + */ + public int getIndex() { + if (index < 0) { + throw new RuntimeException("index not yet set for " + constant); + } + + return index; + } + + /** + * Returns whether the constant's index has been set for this instance. + * + * @see #setIndex + * + * @return {@code true} iff the index has been set + */ + public boolean hasIndex() { + return (index >= 0); + } + + /** + * Sets the constant's index. It is only valid to call this method once + * per instance. + * + * @param index {@code >= 0;} the constant pool index + */ + public void setIndex(int index) { + if (index < 0) { + throw new IllegalArgumentException("index < 0"); + } + + if (this.index >= 0) { + throw new RuntimeException("index already set"); + } + + this.index = index; + } + + /** + * Gets the constant's class index. It is only valid to call this after + * {@link #setClassIndex} has been called. + * + * @return {@code >= 0;} the constant's class's constant pool index + */ + public int getClassIndex() { + if (classIndex < 0) { + throw new RuntimeException("class index not yet set"); + } + + return classIndex; + } + + /** + * Returns whether the constant's class index has been set for this + * instance. + * + * @see #setClassIndex + * + * @return {@code true} iff the index has been set + */ + public boolean hasClassIndex() { + return (classIndex >= 0); + } + + /** + * Sets the constant's class index. This is the constant pool index + * for the class referred to by this instance's constant. Only + * reference constants have a class, so it is only on instances + * with reference constants that this method should ever be + * called. It is only valid to call this method once per instance. + * + * @param index {@code >= 0;} the constant's class's constant pool index + */ + public void setClassIndex(int index) { + if (index < 0) { + throw new IllegalArgumentException("index < 0"); + } + + if (this.classIndex >= 0) { + throw new RuntimeException("class index already set"); + } + + this.classIndex = index; + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return constant.toHuman(); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/DalvCode.java b/dexgen/src/com/android/dexgen/dex/code/DalvCode.java new file mode 100644 index 0000000..2df49ed --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/DalvCode.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.type.Type; + +import java.util.HashSet; + +/** + * Container for all the pieces of a concrete method. Each instance + * corresponds to a {@code code} structure in a {@code .dex} file. + */ +public final class DalvCode { + /** + * how much position info to preserve; one of the static + * constants in {@link PositionList} + */ + private final int positionInfo; + + /** + * {@code null-ok;} the instruction list, ready for final processing; + * nulled out in {@link #finishProcessingIfNecessary} + */ + private OutputFinisher unprocessedInsns; + + /** + * {@code non-null;} unprocessed catch table; + * nulled out in {@link #finishProcessingIfNecessary} + */ + private CatchBuilder unprocessedCatches; + + /** + * {@code null-ok;} catch table; set in + * {@link #finishProcessingIfNecessary} + */ + private CatchTable catches; + + /** + * {@code null-ok;} source positions list; set in + * {@link #finishProcessingIfNecessary} + */ + private PositionList positions; + + /** + * {@code null-ok;} local variable list; set in + * {@link #finishProcessingIfNecessary} + */ + private LocalList locals; + + /** + * {@code null-ok;} the processed instruction list; set in + * {@link #finishProcessingIfNecessary} + */ + private DalvInsnList insns; + + /** + * Constructs an instance. + * + * @param positionInfo how much position info to preserve; one of the + * static constants in {@link PositionList} + * @param unprocessedInsns {@code non-null;} the instruction list, ready + * for final processing + * @param unprocessedCatches {@code non-null;} unprocessed catch + * (exception handler) table + */ + public DalvCode(int positionInfo, OutputFinisher unprocessedInsns, + CatchBuilder unprocessedCatches) { + if (unprocessedInsns == null) { + throw new NullPointerException("unprocessedInsns == null"); + } + + if (unprocessedCatches == null) { + throw new NullPointerException("unprocessedCatches == null"); + } + + this.positionInfo = positionInfo; + this.unprocessedInsns = unprocessedInsns; + this.unprocessedCatches = unprocessedCatches; + this.catches = null; + this.positions = null; + this.locals = null; + this.insns = null; + } + + /** + * Finish up processing of the method. + */ + private void finishProcessingIfNecessary() { + if (insns != null) { + return; + } + + insns = unprocessedInsns.finishProcessingAndGetList(); + positions = PositionList.make(insns, positionInfo); + locals = LocalList.make(insns); + catches = unprocessedCatches.build(); + + // Let them be gc'ed. + unprocessedInsns = null; + unprocessedCatches = null; + } + + /** + * Assign indices in all instructions that need them, using the + * given callback to perform lookups. This must be called before + * {@link #getInsns}. + * + * @param callback {@code non-null;} callback object + */ + public void assignIndices(AssignIndicesCallback callback) { + unprocessedInsns.assignIndices(callback); + } + + /** + * Gets whether this instance has any position data to represent. + * + * @return {@code true} iff this instance has any position + * data to represent + */ + public boolean hasPositions() { + return (positionInfo != PositionList.NONE) + && unprocessedInsns.hasAnyPositionInfo(); + } + + /** + * Gets whether this instance has any local variable data to represent. + * + * @return {@code true} iff this instance has any local variable + * data to represent + */ + public boolean hasLocals() { + return unprocessedInsns.hasAnyLocalInfo(); + } + + /** + * Gets whether this instance has any catches at all (either typed + * or catch-all). + * + * @return whether this instance has any catches at all + */ + public boolean hasAnyCatches() { + return unprocessedCatches.hasAnyCatches(); + } + + /** + * Gets the set of catch types handled anywhere in the code. + * + * @return {@code non-null;} the set of catch types + */ + public HashSet<Type> getCatchTypes() { + return unprocessedCatches.getCatchTypes(); + } + + /** + * Gets the set of all constants referred to by instructions in + * the code. + * + * @return {@code non-null;} the set of constants + */ + public HashSet<Constant> getInsnConstants() { + return unprocessedInsns.getAllConstants(); + } + + /** + * Gets the list of instructions. + * + * @return {@code non-null;} the instruction list + */ + public DalvInsnList getInsns() { + finishProcessingIfNecessary(); + return insns; + } + + /** + * Gets the catch (exception handler) table. + * + * @return {@code non-null;} the catch table + */ + public CatchTable getCatches() { + finishProcessingIfNecessary(); + return catches; + } + + /** + * Gets the source positions list. + * + * @return {@code non-null;} the source positions list + */ + public PositionList getPositions() { + finishProcessingIfNecessary(); + return positions; + } + + /** + * Gets the source positions list. + * + * @return {@code non-null;} the source positions list + */ + public LocalList getLocals() { + finishProcessingIfNecessary(); + return locals; + } + + /** + * Class used as a callback for {@link #assignIndices}. + */ + public static interface AssignIndicesCallback { + /** + * Gets the index for the given constant. + * + * @param cst {@code non-null;} the constant + * @return {@code >= -1;} the index or {@code -1} if the constant + * shouldn't actually be reified with an index + */ + public int getIndex(Constant cst); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/DalvInsn.java b/dexgen/src/com/android/dexgen/dex/code/DalvInsn.java new file mode 100644 index 0000000..95b5feb --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/DalvInsn.java @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.RegisterSpec; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.code.SourcePosition; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; +import com.android.dexgen.util.TwoColumnOutput; + +/** + * Base class for Dalvik instructions. + */ +public abstract class DalvInsn { + /** + * the actual output address of this instance, if known, or + * {@code -1} if not + */ + private int address; + + /** the opcode; one of the constants from {@link Dops} */ + private final Dop opcode; + + /** {@code non-null;} source position */ + private final SourcePosition position; + + /** {@code non-null;} list of register arguments */ + private final RegisterSpecList registers; + + /** + * Makes a move instruction, appropriate and ideal for the given arguments. + * + * @param position {@code non-null;} source position information + * @param dest {@code non-null;} destination register + * @param src {@code non-null;} source register + * @return {@code non-null;} an appropriately-constructed instance + */ + public static SimpleInsn makeMove(SourcePosition position, + RegisterSpec dest, RegisterSpec src) { + boolean category1 = dest.getCategory() == 1; + boolean reference = dest.getType().isReference(); + int destReg = dest.getReg(); + int srcReg = src.getReg(); + Dop opcode; + + if ((srcReg | destReg) < 16) { + opcode = reference ? Dops.MOVE_OBJECT : + (category1 ? Dops.MOVE : Dops.MOVE_WIDE); + } else if (destReg < 256) { + opcode = reference ? Dops.MOVE_OBJECT_FROM16 : + (category1 ? Dops.MOVE_FROM16 : Dops.MOVE_WIDE_FROM16); + } else { + opcode = reference ? Dops.MOVE_OBJECT_16 : + (category1 ? Dops.MOVE_16 : Dops.MOVE_WIDE_16); + } + + return new SimpleInsn(opcode, position, + RegisterSpecList.make(dest, src)); + } + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * <p><b>Note:</b> In the unlikely event that an instruction takes + * absolutely no registers (e.g., a {@code nop} or a + * no-argument no-result static method call), then the given + * register list may be passed as {@link + * RegisterSpecList#EMPTY}.</p> + * + * @param opcode the opcode; one of the constants from {@link Dops} + * @param position {@code non-null;} source position + * @param registers {@code non-null;} register list, including a + * result register if appropriate (that is, registers may be either + * ins and outs) + */ + public DalvInsn(Dop opcode, SourcePosition position, + RegisterSpecList registers) { + if (opcode == null) { + throw new NullPointerException("opcode == null"); + } + + if (position == null) { + throw new NullPointerException("position == null"); + } + + if (registers == null) { + throw new NullPointerException("registers == null"); + } + + this.address = -1; + this.opcode = opcode; + this.position = position; + this.registers = registers; + } + + /** {@inheritDoc} */ + @Override + public final String toString() { + StringBuffer sb = new StringBuffer(100); + + sb.append(identifierString()); + sb.append(' '); + sb.append(position); + + sb.append(": "); + sb.append(opcode.getName()); + + boolean needComma = false; + if (registers.size() != 0) { + sb.append(registers.toHuman(" ", ", ", null)); + needComma = true; + } + + String extra = argString(); + if (extra != null) { + if (needComma) { + sb.append(','); + } + sb.append(' '); + sb.append(extra); + } + + return sb.toString(); + } + + /** + * Gets whether the address of this instruction is known. + * + * @see #getAddress + * @see #setAddress + */ + public final boolean hasAddress() { + return (address >= 0); + } + + /** + * Gets the output address of this instruction, if it is known. This throws + * a {@code RuntimeException} if it has not yet been set. + * + * @see #setAddress + * + * @return {@code >= 0;} the output address + */ + public final int getAddress() { + if (address < 0) { + throw new RuntimeException("address not yet known"); + } + + return address; + } + + /** + * Gets the opcode. + * + * @return {@code non-null;} the opcode + */ + public final Dop getOpcode() { + return opcode; + } + + /** + * Gets the source position. + * + * @return {@code non-null;} the source position + */ + public final SourcePosition getPosition() { + return position; + } + + /** + * Gets the register list for this instruction. + * + * @return {@code non-null;} the registers + */ + public final RegisterSpecList getRegisters() { + return registers; + } + + /** + * Returns whether this instance's opcode uses a result register. + * This method is a convenient shorthand for + * {@code getOpcode().hasResult()}. + * + * @return {@code true} iff this opcode uses a result register + */ + public final boolean hasResult() { + return opcode.hasResult(); + } + + /** + * Gets the minimum distinct registers required for this instruction. + * This assumes that the result (if any) can share registers with the + * sources (if any), that each source register is unique, and that + * (to be explicit here) category-2 values take up two consecutive + * registers. + * + * @return {@code >= 0;} the minimum distinct register requirement + */ + public final int getMinimumRegisterRequirement() { + boolean hasResult = hasResult(); + int regSz = registers.size(); + int resultRequirement = hasResult ? registers.get(0).getCategory() : 0; + int sourceRequirement = 0; + + for (int i = hasResult ? 1 : 0; i < regSz; i++) { + sourceRequirement += registers.get(i).getCategory(); + } + + return Math.max(sourceRequirement, resultRequirement); + } + + /** + * Gets the instruction prefix required, if any, to use in a high + * register transformed version of this instance. + * + * @see #hrVersion + * + * @return {@code null-ok;} the prefix, if any + */ + public DalvInsn hrPrefix() { + RegisterSpecList regs = registers; + int sz = regs.size(); + + if (hasResult()) { + if (sz == 1) { + return null; + } + regs = regs.withoutFirst(); + } else if (sz == 0) { + return null; + } + + return new HighRegisterPrefix(position, regs); + } + + /** + * Gets the instruction suffix required, if any, to use in a high + * register transformed version of this instance. + * + * @see #hrVersion + * + * @return {@code null-ok;} the suffix, if any + */ + public DalvInsn hrSuffix() { + if (hasResult()) { + RegisterSpec r = registers.get(0); + return makeMove(position, r, r.withReg(0)); + } else { + return null; + } + } + + /** + * Gets the instruction that is equivalent to this one, except that + * uses sequential registers starting at {@code 0} (storing + * the result, if any, in register {@code 0} as well). The + * sequence of instructions from {@link #hrPrefix} and {@link + * #hrSuffix} (if non-null) surrounding the result of a call to + * this method are the high register transformation of this + * instance, and it is guaranteed that the number of low registers + * used will be the number returned by {@link + * #getMinimumRegisterRequirement}. + * + * @return {@code non-null;} the replacement + */ + public DalvInsn hrVersion() { + RegisterSpecList regs = + registers.withSequentialRegisters(0, hasResult()); + return withRegisters(regs); + } + + /** + * Gets the short identifier for this instruction. This is its + * address, if assigned, or its identity hashcode if not. + * + * @return {@code non-null;} the identifier + */ + public final String identifierString() { + if (address != -1) { + return String.format("%04x", address); + } + + return Hex.u4(System.identityHashCode(this)); + } + + /** + * Returns the string form of this instance suitable for inclusion in + * a human-oriented listing dump. This method will return {@code null} + * if this instance should not appear in a listing. + * + * @param prefix {@code non-null;} prefix before the address; each follow-on + * line will be indented to match as well + * @param width {@code >= 0;} the width of the output or {@code 0} for + * unlimited width + * @param noteIndices whether to include an explicit notation of + * constant pool indices + * @return {@code null-ok;} the string form or {@code null} if this + * instance should not appear in a listing + */ + public final String listingString(String prefix, int width, + boolean noteIndices) { + String insnPerSe = listingString0(noteIndices); + + if (insnPerSe == null) { + return null; + } + + String addr = prefix + identifierString() + ": "; + int w1 = addr.length(); + int w2 = (width == 0) ? insnPerSe.length() : (width - w1); + + return TwoColumnOutput.toString(addr, w1, "", insnPerSe, w2); + } + + /** + * Sets the output address. + * + * @param address {@code >= 0;} the output address + */ + public final void setAddress(int address) { + if (address < 0) { + throw new IllegalArgumentException("address < 0"); + } + + this.address = address; + } + + /** + * Gets the address immediately after this instance. This is only + * calculable if this instance's address is known, and it is equal + * to the address plus the length of the instruction format of this + * instance's opcode. + * + * @return {@code >= 0;} the next address + */ + public final int getNextAddress() { + return getAddress() + codeSize(); + } + + /** + * Gets the size of this instruction, in 16-bit code units. + * + * @return {@code >= 0;} the code size of this instruction + */ + public abstract int codeSize(); + + /** + * Writes this instance to the given output. This method should + * never annotate the output. + * + * @param out {@code non-null;} where to write to + */ + public abstract void writeTo(AnnotatedOutput out); + + /** + * Returns an instance that is just like this one, except that its + * opcode is replaced by the one given, and its address is reset. + * + * @param opcode {@code non-null;} the new opcode + * @return {@code non-null;} an appropriately-constructed instance + */ + public abstract DalvInsn withOpcode(Dop opcode); + + /** + * Returns an instance that is just like this one, except that all + * register references have been offset by the given delta, and its + * address is reset. + * + * @param delta the amount to offset register references by + * @return {@code non-null;} an appropriately-constructed instance + */ + public abstract DalvInsn withRegisterOffset(int delta); + + /** + * Returns an instance that is just like this one, except that the + * register list is replaced by the given one, and its address is + * reset. + * + * @param registers {@code non-null;} new register list + * @return {@code non-null;} an appropriately-constructed instance + */ + public abstract DalvInsn withRegisters(RegisterSpecList registers); + + /** + * Gets the string form for any arguments to this instance. Subclasses + * must override this. + * + * @return {@code null-ok;} the string version of any arguments or + * {@code null} if there are none + */ + protected abstract String argString(); + + /** + * Helper for {@link #listingString}, which returns the string + * form of this instance suitable for inclusion in a + * human-oriented listing dump, not including the instruction + * address and without respect for any output formatting. This + * method should return {@code null} if this instance should + * not appear in a listing. + * + * @param noteIndices whether to include an explicit notation of + * constant pool indices + * @return {@code null-ok;} the listing string + */ + protected abstract String listingString0(boolean noteIndices); +} diff --git a/dexgen/src/com/android/dexgen/dex/code/DalvInsnList.java b/dexgen/src/com/android/dexgen/dex/code/DalvInsnList.java new file mode 100644 index 0000000..15f82c1 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/DalvInsnList.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstBaseMethodRef; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.ExceptionWithContext; +import com.android.dexgen.util.FixedSizeList; +import com.android.dexgen.util.IndentingWriter; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.ArrayList; + +/** + * List of {@link DalvInsn} instances. + */ +public final class DalvInsnList extends FixedSizeList { + + /** + * The amount of register space, in register units, required for this + * code block. This may be greater than the largest observed register+ + * category because the method this code block exists in may + * specify arguments that are unused by the method. + */ + private final int regCount; + + /** + * Constructs and returns an immutable instance whose elements are + * identical to the ones in the given list, in the same order. + * + * @param list {@code non-null;} the list to use for elements + * @param regCount count, in register-units, of the number of registers + * this code block requires. + * @return {@code non-null;} an appropriately-constructed instance of this + * class + */ + public static DalvInsnList makeImmutable(ArrayList<DalvInsn> list, + int regCount) { + int size = list.size(); + DalvInsnList result = new DalvInsnList(size, regCount); + + for (int i = 0; i < size; i++) { + result.set(i, list.get(i)); + } + + result.setImmutable(); + return result; + } + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + */ + public DalvInsnList(int size, int regCount) { + super(size); + this.regCount = regCount; + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public DalvInsn get(int n) { + return (DalvInsn) get0(n); + } + + /** + * Sets the instruction at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param insn {@code non-null;} the instruction to set at {@code n} + */ + public void set(int n, DalvInsn insn) { + set0(n, insn); + } + + /** + * Gets the size of this instance, in 16-bit code units. This will only + * return a meaningful result if the instructions in this instance all + * have valid addresses. + * + * @return {@code >= 0;} the size + */ + public int codeSize() { + int sz = size(); + + if (sz == 0) { + return 0; + } + + DalvInsn last = get(sz - 1); + return last.getNextAddress(); + } + + /** + * Writes all the instructions in this instance to the given output + * destination. + * + * @param out {@code non-null;} where to write to + */ + public void writeTo(AnnotatedOutput out) { + int startCursor = out.getCursor(); + int sz = size(); + + if (out.annotates()) { + boolean verbose = out.isVerbose(); + + for (int i = 0; i < sz; i++) { + DalvInsn insn = (DalvInsn) get0(i); + int codeBytes = insn.codeSize() * 2; + String s; + + if ((codeBytes != 0) || verbose) { + s = insn.listingString(" ", out.getAnnotationWidth(), + true); + } else { + s = null; + } + + if (s != null) { + out.annotate(codeBytes, s); + } else if (codeBytes != 0) { + out.annotate(codeBytes, ""); + } + } + } + + for (int i = 0; i < sz; i++) { + DalvInsn insn = (DalvInsn) get0(i); + try { + insn.writeTo(out); + } catch (RuntimeException ex) { + throw ExceptionWithContext.withContext(ex, + "...while writing " + insn); + } + } + + // Sanity check of the amount written. + int written = (out.getCursor() - startCursor) / 2; + if (written != codeSize()) { + throw new RuntimeException("write length mismatch; expected " + + codeSize() + " but actually wrote " + written); + } + } + + /** + * Gets the minimum required register count implied by this + * instance. This includes any unused parameters that could + * potentially be at the top of the register space. + * @return {@code >= 0;} the required registers size + */ + public int getRegistersSize() { + return regCount; + } + + /** + * Gets the size of the outgoing arguments area required by this + * method. This is equal to the largest argument word count of any + * method referred to by this instance. + * + * @return {@code >= 0;} the required outgoing arguments size + */ + public int getOutsSize() { + int sz = size(); + int result = 0; + + for (int i = 0; i < sz; i++) { + DalvInsn insn = (DalvInsn) get0(i); + + if (!(insn instanceof CstInsn)) { + continue; + } + + Constant cst = ((CstInsn) insn).getConstant(); + + if (!(cst instanceof CstBaseMethodRef)) { + continue; + } + + boolean isStatic = + (insn.getOpcode().getFamily() == DalvOps.INVOKE_STATIC); + int count = + ((CstBaseMethodRef) cst).getParameterWordCount(isStatic); + + if (count > result) { + result = count; + } + } + + return result; + } + + /** + * Does a human-friendly dump of this instance. + * + * @param out {@code non-null;} where to dump + * @param prefix {@code non-null;} prefix to attach to each line of output + * @param verbose whether to be verbose; verbose output includes + * lines for zero-size instructions and explicit constant pool indices + */ + public void debugPrint(Writer out, String prefix, boolean verbose) { + IndentingWriter iw = new IndentingWriter(out, 0, prefix); + int sz = size(); + + try { + for (int i = 0; i < sz; i++) { + DalvInsn insn = (DalvInsn) get0(i); + String s; + + if ((insn.codeSize() != 0) || verbose) { + s = insn.listingString("", 0, verbose); + } else { + s = null; + } + + if (s != null) { + iw.write(s); + } + } + + iw.flush(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + /** + * Does a human-friendly dump of this instance. + * + * @param out {@code non-null;} where to dump + * @param prefix {@code non-null;} prefix to attach to each line of output + * @param verbose whether to be verbose; verbose output includes + * lines for zero-size instructions + */ + public void debugPrint(OutputStream out, String prefix, boolean verbose) { + Writer w = new OutputStreamWriter(out); + debugPrint(w, prefix, verbose); + + try { + w.flush(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/DalvOps.java b/dexgen/src/com/android/dexgen/dex/code/DalvOps.java new file mode 100644 index 0000000..1d051ea --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/DalvOps.java @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +/** + * All the Dalvik opcode value constants. See the related spec + * document for the meaning and instruction format of each opcode. + */ +public final class DalvOps { + /** pseudo-opcode used for nonstandard format "instructions" */ + public static final int SPECIAL_FORMAT = -1; + + /** minimum valid opcode value */ + public static final int MIN_VALUE = -1; + + /** maximum valid opcode value */ + public static final int MAX_VALUE = 0xff; + + // BEGIN(opcodes); GENERATED AUTOMATICALLY BY opcode-gen + public static final int NOP = 0x00; + public static final int MOVE = 0x01; + public static final int MOVE_FROM16 = 0x02; + public static final int MOVE_16 = 0x03; + public static final int MOVE_WIDE = 0x04; + public static final int MOVE_WIDE_FROM16 = 0x05; + public static final int MOVE_WIDE_16 = 0x06; + public static final int MOVE_OBJECT = 0x07; + public static final int MOVE_OBJECT_FROM16 = 0x08; + public static final int MOVE_OBJECT_16 = 0x09; + public static final int MOVE_RESULT = 0x0a; + public static final int MOVE_RESULT_WIDE = 0x0b; + public static final int MOVE_RESULT_OBJECT = 0x0c; + public static final int MOVE_EXCEPTION = 0x0d; + public static final int RETURN_VOID = 0x0e; + public static final int RETURN = 0x0f; + public static final int RETURN_WIDE = 0x10; + public static final int RETURN_OBJECT = 0x11; + public static final int CONST_4 = 0x12; + public static final int CONST_16 = 0x13; + public static final int CONST = 0x14; + public static final int CONST_HIGH16 = 0x15; + public static final int CONST_WIDE_16 = 0x16; + public static final int CONST_WIDE_32 = 0x17; + public static final int CONST_WIDE = 0x18; + public static final int CONST_WIDE_HIGH16 = 0x19; + public static final int CONST_STRING = 0x1a; + public static final int CONST_STRING_JUMBO = 0x1b; + public static final int CONST_CLASS = 0x1c; + public static final int MONITOR_ENTER = 0x1d; + public static final int MONITOR_EXIT = 0x1e; + public static final int CHECK_CAST = 0x1f; + public static final int INSTANCE_OF = 0x20; + public static final int ARRAY_LENGTH = 0x21; + public static final int NEW_INSTANCE = 0x22; + public static final int NEW_ARRAY = 0x23; + public static final int FILLED_NEW_ARRAY = 0x24; + public static final int FILLED_NEW_ARRAY_RANGE = 0x25; + public static final int FILL_ARRAY_DATA = 0x26; + public static final int THROW = 0x27; + public static final int GOTO = 0x28; + public static final int GOTO_16 = 0x29; + public static final int GOTO_32 = 0x2a; + public static final int PACKED_SWITCH = 0x2b; + public static final int SPARSE_SWITCH = 0x2c; + public static final int CMPL_FLOAT = 0x2d; + public static final int CMPG_FLOAT = 0x2e; + public static final int CMPL_DOUBLE = 0x2f; + public static final int CMPG_DOUBLE = 0x30; + public static final int CMP_LONG = 0x31; + public static final int IF_EQ = 0x32; + public static final int IF_NE = 0x33; + public static final int IF_LT = 0x34; + public static final int IF_GE = 0x35; + public static final int IF_GT = 0x36; + public static final int IF_LE = 0x37; + public static final int IF_EQZ = 0x38; + public static final int IF_NEZ = 0x39; + public static final int IF_LTZ = 0x3a; + public static final int IF_GEZ = 0x3b; + public static final int IF_GTZ = 0x3c; + public static final int IF_LEZ = 0x3d; + public static final int UNUSED_3E = 0x3e; + public static final int UNUSED_3F = 0x3f; + public static final int UNUSED_40 = 0x40; + public static final int UNUSED_41 = 0x41; + public static final int UNUSED_42 = 0x42; + public static final int UNUSED_43 = 0x43; + public static final int AGET = 0x44; + public static final int AGET_WIDE = 0x45; + public static final int AGET_OBJECT = 0x46; + public static final int AGET_BOOLEAN = 0x47; + public static final int AGET_BYTE = 0x48; + public static final int AGET_CHAR = 0x49; + public static final int AGET_SHORT = 0x4a; + public static final int APUT = 0x4b; + public static final int APUT_WIDE = 0x4c; + public static final int APUT_OBJECT = 0x4d; + public static final int APUT_BOOLEAN = 0x4e; + public static final int APUT_BYTE = 0x4f; + public static final int APUT_CHAR = 0x50; + public static final int APUT_SHORT = 0x51; + public static final int IGET = 0x52; + public static final int IGET_WIDE = 0x53; + public static final int IGET_OBJECT = 0x54; + public static final int IGET_BOOLEAN = 0x55; + public static final int IGET_BYTE = 0x56; + public static final int IGET_CHAR = 0x57; + public static final int IGET_SHORT = 0x58; + public static final int IPUT = 0x59; + public static final int IPUT_WIDE = 0x5a; + public static final int IPUT_OBJECT = 0x5b; + public static final int IPUT_BOOLEAN = 0x5c; + public static final int IPUT_BYTE = 0x5d; + public static final int IPUT_CHAR = 0x5e; + public static final int IPUT_SHORT = 0x5f; + public static final int SGET = 0x60; + public static final int SGET_WIDE = 0x61; + public static final int SGET_OBJECT = 0x62; + public static final int SGET_BOOLEAN = 0x63; + public static final int SGET_BYTE = 0x64; + public static final int SGET_CHAR = 0x65; + public static final int SGET_SHORT = 0x66; + public static final int SPUT = 0x67; + public static final int SPUT_WIDE = 0x68; + public static final int SPUT_OBJECT = 0x69; + public static final int SPUT_BOOLEAN = 0x6a; + public static final int SPUT_BYTE = 0x6b; + public static final int SPUT_CHAR = 0x6c; + public static final int SPUT_SHORT = 0x6d; + public static final int INVOKE_VIRTUAL = 0x6e; + public static final int INVOKE_SUPER = 0x6f; + public static final int INVOKE_DIRECT = 0x70; + public static final int INVOKE_STATIC = 0x71; + public static final int INVOKE_INTERFACE = 0x72; + public static final int UNUSED_73 = 0x73; + public static final int INVOKE_VIRTUAL_RANGE = 0x74; + public static final int INVOKE_SUPER_RANGE = 0x75; + public static final int INVOKE_DIRECT_RANGE = 0x76; + public static final int INVOKE_STATIC_RANGE = 0x77; + public static final int INVOKE_INTERFACE_RANGE = 0x78; + public static final int UNUSED_79 = 0x79; + public static final int UNUSED_7A = 0x7a; + public static final int NEG_INT = 0x7b; + public static final int NOT_INT = 0x7c; + public static final int NEG_LONG = 0x7d; + public static final int NOT_LONG = 0x7e; + public static final int NEG_FLOAT = 0x7f; + public static final int NEG_DOUBLE = 0x80; + public static final int INT_TO_LONG = 0x81; + public static final int INT_TO_FLOAT = 0x82; + public static final int INT_TO_DOUBLE = 0x83; + public static final int LONG_TO_INT = 0x84; + public static final int LONG_TO_FLOAT = 0x85; + public static final int LONG_TO_DOUBLE = 0x86; + public static final int FLOAT_TO_INT = 0x87; + public static final int FLOAT_TO_LONG = 0x88; + public static final int FLOAT_TO_DOUBLE = 0x89; + public static final int DOUBLE_TO_INT = 0x8a; + public static final int DOUBLE_TO_LONG = 0x8b; + public static final int DOUBLE_TO_FLOAT = 0x8c; + public static final int INT_TO_BYTE = 0x8d; + public static final int INT_TO_CHAR = 0x8e; + public static final int INT_TO_SHORT = 0x8f; + public static final int ADD_INT = 0x90; + public static final int SUB_INT = 0x91; + public static final int MUL_INT = 0x92; + public static final int DIV_INT = 0x93; + public static final int REM_INT = 0x94; + public static final int AND_INT = 0x95; + public static final int OR_INT = 0x96; + public static final int XOR_INT = 0x97; + public static final int SHL_INT = 0x98; + public static final int SHR_INT = 0x99; + public static final int USHR_INT = 0x9a; + public static final int ADD_LONG = 0x9b; + public static final int SUB_LONG = 0x9c; + public static final int MUL_LONG = 0x9d; + public static final int DIV_LONG = 0x9e; + public static final int REM_LONG = 0x9f; + public static final int AND_LONG = 0xa0; + public static final int OR_LONG = 0xa1; + public static final int XOR_LONG = 0xa2; + public static final int SHL_LONG = 0xa3; + public static final int SHR_LONG = 0xa4; + public static final int USHR_LONG = 0xa5; + public static final int ADD_FLOAT = 0xa6; + public static final int SUB_FLOAT = 0xa7; + public static final int MUL_FLOAT = 0xa8; + public static final int DIV_FLOAT = 0xa9; + public static final int REM_FLOAT = 0xaa; + public static final int ADD_DOUBLE = 0xab; + public static final int SUB_DOUBLE = 0xac; + public static final int MUL_DOUBLE = 0xad; + public static final int DIV_DOUBLE = 0xae; + public static final int REM_DOUBLE = 0xaf; + public static final int ADD_INT_2ADDR = 0xb0; + public static final int SUB_INT_2ADDR = 0xb1; + public static final int MUL_INT_2ADDR = 0xb2; + public static final int DIV_INT_2ADDR = 0xb3; + public static final int REM_INT_2ADDR = 0xb4; + public static final int AND_INT_2ADDR = 0xb5; + public static final int OR_INT_2ADDR = 0xb6; + public static final int XOR_INT_2ADDR = 0xb7; + public static final int SHL_INT_2ADDR = 0xb8; + public static final int SHR_INT_2ADDR = 0xb9; + public static final int USHR_INT_2ADDR = 0xba; + public static final int ADD_LONG_2ADDR = 0xbb; + public static final int SUB_LONG_2ADDR = 0xbc; + public static final int MUL_LONG_2ADDR = 0xbd; + public static final int DIV_LONG_2ADDR = 0xbe; + public static final int REM_LONG_2ADDR = 0xbf; + public static final int AND_LONG_2ADDR = 0xc0; + public static final int OR_LONG_2ADDR = 0xc1; + public static final int XOR_LONG_2ADDR = 0xc2; + public static final int SHL_LONG_2ADDR = 0xc3; + public static final int SHR_LONG_2ADDR = 0xc4; + public static final int USHR_LONG_2ADDR = 0xc5; + public static final int ADD_FLOAT_2ADDR = 0xc6; + public static final int SUB_FLOAT_2ADDR = 0xc7; + public static final int MUL_FLOAT_2ADDR = 0xc8; + public static final int DIV_FLOAT_2ADDR = 0xc9; + public static final int REM_FLOAT_2ADDR = 0xca; + public static final int ADD_DOUBLE_2ADDR = 0xcb; + public static final int SUB_DOUBLE_2ADDR = 0xcc; + public static final int MUL_DOUBLE_2ADDR = 0xcd; + public static final int DIV_DOUBLE_2ADDR = 0xce; + public static final int REM_DOUBLE_2ADDR = 0xcf; + public static final int ADD_INT_LIT16 = 0xd0; + public static final int RSUB_INT = 0xd1; + public static final int MUL_INT_LIT16 = 0xd2; + public static final int DIV_INT_LIT16 = 0xd3; + public static final int REM_INT_LIT16 = 0xd4; + public static final int AND_INT_LIT16 = 0xd5; + public static final int OR_INT_LIT16 = 0xd6; + public static final int XOR_INT_LIT16 = 0xd7; + public static final int ADD_INT_LIT8 = 0xd8; + public static final int RSUB_INT_LIT8 = 0xd9; + public static final int MUL_INT_LIT8 = 0xda; + public static final int DIV_INT_LIT8 = 0xdb; + public static final int REM_INT_LIT8 = 0xdc; + public static final int AND_INT_LIT8 = 0xdd; + public static final int OR_INT_LIT8 = 0xde; + public static final int XOR_INT_LIT8 = 0xdf; + public static final int SHL_INT_LIT8 = 0xe0; + public static final int SHR_INT_LIT8 = 0xe1; + public static final int USHR_INT_LIT8 = 0xe2; + public static final int UNUSED_E3 = 0xe3; + public static final int UNUSED_E4 = 0xe4; + public static final int UNUSED_E5 = 0xe5; + public static final int UNUSED_E6 = 0xe6; + public static final int UNUSED_E7 = 0xe7; + public static final int UNUSED_E8 = 0xe8; + public static final int UNUSED_E9 = 0xe9; + public static final int UNUSED_EA = 0xea; + public static final int UNUSED_EB = 0xeb; + public static final int UNUSED_EC = 0xec; + public static final int UNUSED_ED = 0xed; + public static final int UNUSED_EE = 0xee; + public static final int UNUSED_EF = 0xef; + public static final int UNUSED_F0 = 0xf0; + public static final int UNUSED_F1 = 0xf1; + public static final int UNUSED_F2 = 0xf2; + public static final int UNUSED_F3 = 0xf3; + public static final int UNUSED_F4 = 0xf4; + public static final int UNUSED_F5 = 0xf5; + public static final int UNUSED_F6 = 0xf6; + public static final int UNUSED_F7 = 0xf7; + public static final int UNUSED_F8 = 0xf8; + public static final int UNUSED_F9 = 0xf9; + public static final int UNUSED_FA = 0xfa; + public static final int UNUSED_FB = 0xfb; + public static final int UNUSED_FC = 0xfc; + public static final int UNUSED_FD = 0xfd; + public static final int UNUSED_FE = 0xfe; + public static final int UNUSED_FF = 0xff; + // END(opcodes) + + /** + * This class is uninstantiable. + */ + private DalvOps() { + // This space intentionally left blank. + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/Dop.java b/dexgen/src/com/android/dexgen/dex/code/Dop.java new file mode 100644 index 0000000..dc788f1 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/Dop.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +/** + * Representation of an opcode. + */ +public final class Dop { + /** DalvOps.MIN_VALUE..DalvOps.MAX_VALUE; the opcode value itself */ + private final int opcode; + + /** DalvOps.MIN_VALUE..DalvOps.MAX_VALUE; the opcode family */ + private final int family; + + /** {@code non-null;} the instruction format */ + private final InsnFormat format; + + /** whether this opcode uses a result register */ + private final boolean hasResult; + + /** {@code non-null;} the name */ + private final String name; + + /** + * Constructs an instance. + * + * @param opcode {@code DalvOps.MIN_VALUE..DalvOps.MAX_VALUE;} the opcode + * value itself + * @param family {@code DalvOps.MIN_VALUE..DalvOps.MAX_VALUE;} the opcode family + * @param format {@code non-null;} the instruction format + * @param hasResult whether the opcode has a result register; if so it + * is always the first register + * @param name {@code non-null;} the name + */ + public Dop(int opcode, int family, InsnFormat format, + boolean hasResult, String name) { + if ((opcode < DalvOps.MIN_VALUE) || (opcode > DalvOps.MAX_VALUE)) { + throw new IllegalArgumentException("bogus opcode"); + } + + if ((family < DalvOps.MIN_VALUE) || (family > DalvOps.MAX_VALUE)) { + throw new IllegalArgumentException("bogus family"); + } + + if (format == null) { + throw new NullPointerException("format == null"); + } + + if (name == null) { + throw new NullPointerException("name == null"); + } + + this.opcode = opcode; + this.family = family; + this.format = format; + this.hasResult = hasResult; + this.name = name; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return name; + } + + /** + * Gets the opcode value. + * + * @return {@code DalvOps.MIN_VALUE..DalvOps.MAX_VALUE;} the opcode value + */ + public int getOpcode() { + return opcode; + } + + /** + * Gets the opcode family. The opcode family is the unmarked (no + * "/...") opcode that has equivalent semantics to this one. + * + * @return {@code DalvOps.MIN_VALUE..DalvOps.MAX_VALUE;} the opcode family + */ + public int getFamily() { + return family; + } + + /** + * Gets the instruction format. + * + * @return {@code non-null;} the instruction format + */ + public InsnFormat getFormat() { + return format; + } + + /** + * Returns whether this opcode uses a result register. + * + * @return {@code true} iff this opcode uses a result register + */ + public boolean hasResult() { + return hasResult; + } + + /** + * Gets the opcode name. + * + * @return {@code non-null;} the opcode name + */ + public String getName() { + return name; + } + + /** + * Gets the opcode for the opposite test of this instance. This is only + * valid for opcodes which are in fact tests. + * + * @return {@code non-null;} the opposite test + */ + public Dop getOppositeTest() { + switch (opcode) { + case DalvOps.IF_EQ: return Dops.IF_NE; + case DalvOps.IF_NE: return Dops.IF_EQ; + case DalvOps.IF_LT: return Dops.IF_GE; + case DalvOps.IF_GE: return Dops.IF_LT; + case DalvOps.IF_GT: return Dops.IF_LE; + case DalvOps.IF_LE: return Dops.IF_GT; + case DalvOps.IF_EQZ: return Dops.IF_NEZ; + case DalvOps.IF_NEZ: return Dops.IF_EQZ; + case DalvOps.IF_LTZ: return Dops.IF_GEZ; + case DalvOps.IF_GEZ: return Dops.IF_LTZ; + case DalvOps.IF_GTZ: return Dops.IF_LEZ; + case DalvOps.IF_LEZ: return Dops.IF_GTZ; + } + + throw new IllegalArgumentException("bogus opcode: " + this); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/Dops.java b/dexgen/src/com/android/dexgen/dex/code/Dops.java new file mode 100644 index 0000000..afd21e3 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/Dops.java @@ -0,0 +1,1231 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.dex.code.form.Form10t; +import com.android.dexgen.dex.code.form.Form10x; +import com.android.dexgen.dex.code.form.Form11n; +import com.android.dexgen.dex.code.form.Form11x; +import com.android.dexgen.dex.code.form.Form12x; +import com.android.dexgen.dex.code.form.Form20t; +import com.android.dexgen.dex.code.form.Form21c; +import com.android.dexgen.dex.code.form.Form21h; +import com.android.dexgen.dex.code.form.Form21s; +import com.android.dexgen.dex.code.form.Form21t; +import com.android.dexgen.dex.code.form.Form22b; +import com.android.dexgen.dex.code.form.Form22c; +import com.android.dexgen.dex.code.form.Form22s; +import com.android.dexgen.dex.code.form.Form22t; +import com.android.dexgen.dex.code.form.Form22x; +import com.android.dexgen.dex.code.form.Form23x; +import com.android.dexgen.dex.code.form.Form30t; +import com.android.dexgen.dex.code.form.Form31c; +import com.android.dexgen.dex.code.form.Form31i; +import com.android.dexgen.dex.code.form.Form31t; +import com.android.dexgen.dex.code.form.Form32x; +import com.android.dexgen.dex.code.form.Form35c; +import com.android.dexgen.dex.code.form.Form3rc; +import com.android.dexgen.dex.code.form.Form51l; +import com.android.dexgen.dex.code.form.SpecialFormat; + +/** + * Standard instances of {@link Dop} and utility methods for getting + * them. + */ +public final class Dops { + /** {@code non-null;} array containing all the standard instances */ + private static final Dop[] DOPS; + + /** + * pseudo-opcode used for nonstandard formatted "instructions" + * (which are mostly not actually instructions, though they do + * appear in instruction lists) + */ + public static final Dop SPECIAL_FORMAT = + new Dop(DalvOps.SPECIAL_FORMAT, DalvOps.SPECIAL_FORMAT, + SpecialFormat.THE_ONE, false, "<special>"); + + // BEGIN(dops); GENERATED AUTOMATICALLY BY opcode-gen + public static final Dop NOP = + new Dop(DalvOps.NOP, DalvOps.NOP, + Form10x.THE_ONE, false, "nop"); + + public static final Dop MOVE = + new Dop(DalvOps.MOVE, DalvOps.MOVE, + Form12x.THE_ONE, true, "move"); + + public static final Dop MOVE_FROM16 = + new Dop(DalvOps.MOVE_FROM16, DalvOps.MOVE, + Form22x.THE_ONE, true, "move/from16"); + + public static final Dop MOVE_16 = + new Dop(DalvOps.MOVE_16, DalvOps.MOVE, + Form32x.THE_ONE, true, "move/16"); + + public static final Dop MOVE_WIDE = + new Dop(DalvOps.MOVE_WIDE, DalvOps.MOVE_WIDE, + Form12x.THE_ONE, true, "move-wide"); + + public static final Dop MOVE_WIDE_FROM16 = + new Dop(DalvOps.MOVE_WIDE_FROM16, DalvOps.MOVE_WIDE, + Form22x.THE_ONE, true, "move-wide/from16"); + + public static final Dop MOVE_WIDE_16 = + new Dop(DalvOps.MOVE_WIDE_16, DalvOps.MOVE_WIDE, + Form32x.THE_ONE, true, "move-wide/16"); + + public static final Dop MOVE_OBJECT = + new Dop(DalvOps.MOVE_OBJECT, DalvOps.MOVE_OBJECT, + Form12x.THE_ONE, true, "move-object"); + + public static final Dop MOVE_OBJECT_FROM16 = + new Dop(DalvOps.MOVE_OBJECT_FROM16, DalvOps.MOVE_OBJECT, + Form22x.THE_ONE, true, "move-object/from16"); + + public static final Dop MOVE_OBJECT_16 = + new Dop(DalvOps.MOVE_OBJECT_16, DalvOps.MOVE_OBJECT, + Form32x.THE_ONE, true, "move-object/16"); + + public static final Dop MOVE_RESULT = + new Dop(DalvOps.MOVE_RESULT, DalvOps.MOVE_RESULT, + Form11x.THE_ONE, true, "move-result"); + + public static final Dop MOVE_RESULT_WIDE = + new Dop(DalvOps.MOVE_RESULT_WIDE, DalvOps.MOVE_RESULT_WIDE, + Form11x.THE_ONE, true, "move-result-wide"); + + public static final Dop MOVE_RESULT_OBJECT = + new Dop(DalvOps.MOVE_RESULT_OBJECT, DalvOps.MOVE_RESULT_OBJECT, + Form11x.THE_ONE, true, "move-result-object"); + + public static final Dop MOVE_EXCEPTION = + new Dop(DalvOps.MOVE_EXCEPTION, DalvOps.MOVE_EXCEPTION, + Form11x.THE_ONE, true, "move-exception"); + + public static final Dop RETURN_VOID = + new Dop(DalvOps.RETURN_VOID, DalvOps.RETURN_VOID, + Form10x.THE_ONE, false, "return-void"); + + public static final Dop RETURN = + new Dop(DalvOps.RETURN, DalvOps.RETURN, + Form11x.THE_ONE, false, "return"); + + public static final Dop RETURN_WIDE = + new Dop(DalvOps.RETURN_WIDE, DalvOps.RETURN_WIDE, + Form11x.THE_ONE, false, "return-wide"); + + public static final Dop RETURN_OBJECT = + new Dop(DalvOps.RETURN_OBJECT, DalvOps.RETURN_OBJECT, + Form11x.THE_ONE, false, "return-object"); + + public static final Dop CONST_4 = + new Dop(DalvOps.CONST_4, DalvOps.CONST, + Form11n.THE_ONE, true, "const/4"); + + public static final Dop CONST_16 = + new Dop(DalvOps.CONST_16, DalvOps.CONST, + Form21s.THE_ONE, true, "const/16"); + + public static final Dop CONST = + new Dop(DalvOps.CONST, DalvOps.CONST, + Form31i.THE_ONE, true, "const"); + + public static final Dop CONST_HIGH16 = + new Dop(DalvOps.CONST_HIGH16, DalvOps.CONST, + Form21h.THE_ONE, true, "const/high16"); + + public static final Dop CONST_WIDE_16 = + new Dop(DalvOps.CONST_WIDE_16, DalvOps.CONST_WIDE, + Form21s.THE_ONE, true, "const-wide/16"); + + public static final Dop CONST_WIDE_32 = + new Dop(DalvOps.CONST_WIDE_32, DalvOps.CONST_WIDE, + Form31i.THE_ONE, true, "const-wide/32"); + + public static final Dop CONST_WIDE = + new Dop(DalvOps.CONST_WIDE, DalvOps.CONST_WIDE, + Form51l.THE_ONE, true, "const-wide"); + + public static final Dop CONST_WIDE_HIGH16 = + new Dop(DalvOps.CONST_WIDE_HIGH16, DalvOps.CONST_WIDE, + Form21h.THE_ONE, true, "const-wide/high16"); + + public static final Dop CONST_STRING = + new Dop(DalvOps.CONST_STRING, DalvOps.CONST_STRING, + Form21c.THE_ONE, true, "const-string"); + + public static final Dop CONST_STRING_JUMBO = + new Dop(DalvOps.CONST_STRING_JUMBO, DalvOps.CONST_STRING, + Form31c.THE_ONE, true, "const-string/jumbo"); + + public static final Dop CONST_CLASS = + new Dop(DalvOps.CONST_CLASS, DalvOps.CONST_CLASS, + Form21c.THE_ONE, true, "const-class"); + + public static final Dop MONITOR_ENTER = + new Dop(DalvOps.MONITOR_ENTER, DalvOps.MONITOR_ENTER, + Form11x.THE_ONE, false, "monitor-enter"); + + public static final Dop MONITOR_EXIT = + new Dop(DalvOps.MONITOR_EXIT, DalvOps.MONITOR_EXIT, + Form11x.THE_ONE, false, "monitor-exit"); + + public static final Dop CHECK_CAST = + new Dop(DalvOps.CHECK_CAST, DalvOps.CHECK_CAST, + Form21c.THE_ONE, true, "check-cast"); + + public static final Dop INSTANCE_OF = + new Dop(DalvOps.INSTANCE_OF, DalvOps.INSTANCE_OF, + Form22c.THE_ONE, true, "instance-of"); + + public static final Dop ARRAY_LENGTH = + new Dop(DalvOps.ARRAY_LENGTH, DalvOps.ARRAY_LENGTH, + Form12x.THE_ONE, true, "array-length"); + + public static final Dop NEW_INSTANCE = + new Dop(DalvOps.NEW_INSTANCE, DalvOps.NEW_INSTANCE, + Form21c.THE_ONE, true, "new-instance"); + + public static final Dop NEW_ARRAY = + new Dop(DalvOps.NEW_ARRAY, DalvOps.NEW_ARRAY, + Form22c.THE_ONE, true, "new-array"); + + public static final Dop FILLED_NEW_ARRAY = + new Dop(DalvOps.FILLED_NEW_ARRAY, DalvOps.FILLED_NEW_ARRAY, + Form35c.THE_ONE, false, "filled-new-array"); + + public static final Dop FILLED_NEW_ARRAY_RANGE = + new Dop(DalvOps.FILLED_NEW_ARRAY_RANGE, DalvOps.FILLED_NEW_ARRAY, + Form3rc.THE_ONE, false, "filled-new-array/range"); + + public static final Dop FILL_ARRAY_DATA = + new Dop(DalvOps.FILL_ARRAY_DATA, DalvOps.FILL_ARRAY_DATA, + Form31t.THE_ONE, false, "fill-array-data"); + + public static final Dop THROW = + new Dop(DalvOps.THROW, DalvOps.THROW, + Form11x.THE_ONE, false, "throw"); + + public static final Dop GOTO = + new Dop(DalvOps.GOTO, DalvOps.GOTO, + Form10t.THE_ONE, false, "goto"); + + public static final Dop GOTO_16 = + new Dop(DalvOps.GOTO_16, DalvOps.GOTO, + Form20t.THE_ONE, false, "goto/16"); + + public static final Dop GOTO_32 = + new Dop(DalvOps.GOTO_32, DalvOps.GOTO, + Form30t.THE_ONE, false, "goto/32"); + + public static final Dop PACKED_SWITCH = + new Dop(DalvOps.PACKED_SWITCH, DalvOps.PACKED_SWITCH, + Form31t.THE_ONE, false, "packed-switch"); + + public static final Dop SPARSE_SWITCH = + new Dop(DalvOps.SPARSE_SWITCH, DalvOps.SPARSE_SWITCH, + Form31t.THE_ONE, false, "sparse-switch"); + + public static final Dop CMPL_FLOAT = + new Dop(DalvOps.CMPL_FLOAT, DalvOps.CMPL_FLOAT, + Form23x.THE_ONE, true, "cmpl-float"); + + public static final Dop CMPG_FLOAT = + new Dop(DalvOps.CMPG_FLOAT, DalvOps.CMPG_FLOAT, + Form23x.THE_ONE, true, "cmpg-float"); + + public static final Dop CMPL_DOUBLE = + new Dop(DalvOps.CMPL_DOUBLE, DalvOps.CMPL_DOUBLE, + Form23x.THE_ONE, true, "cmpl-double"); + + public static final Dop CMPG_DOUBLE = + new Dop(DalvOps.CMPG_DOUBLE, DalvOps.CMPG_DOUBLE, + Form23x.THE_ONE, true, "cmpg-double"); + + public static final Dop CMP_LONG = + new Dop(DalvOps.CMP_LONG, DalvOps.CMP_LONG, + Form23x.THE_ONE, true, "cmp-long"); + + public static final Dop IF_EQ = + new Dop(DalvOps.IF_EQ, DalvOps.IF_EQ, + Form22t.THE_ONE, false, "if-eq"); + + public static final Dop IF_NE = + new Dop(DalvOps.IF_NE, DalvOps.IF_NE, + Form22t.THE_ONE, false, "if-ne"); + + public static final Dop IF_LT = + new Dop(DalvOps.IF_LT, DalvOps.IF_LT, + Form22t.THE_ONE, false, "if-lt"); + + public static final Dop IF_GE = + new Dop(DalvOps.IF_GE, DalvOps.IF_GE, + Form22t.THE_ONE, false, "if-ge"); + + public static final Dop IF_GT = + new Dop(DalvOps.IF_GT, DalvOps.IF_GT, + Form22t.THE_ONE, false, "if-gt"); + + public static final Dop IF_LE = + new Dop(DalvOps.IF_LE, DalvOps.IF_LE, + Form22t.THE_ONE, false, "if-le"); + + public static final Dop IF_EQZ = + new Dop(DalvOps.IF_EQZ, DalvOps.IF_EQZ, + Form21t.THE_ONE, false, "if-eqz"); + + public static final Dop IF_NEZ = + new Dop(DalvOps.IF_NEZ, DalvOps.IF_NEZ, + Form21t.THE_ONE, false, "if-nez"); + + public static final Dop IF_LTZ = + new Dop(DalvOps.IF_LTZ, DalvOps.IF_LTZ, + Form21t.THE_ONE, false, "if-ltz"); + + public static final Dop IF_GEZ = + new Dop(DalvOps.IF_GEZ, DalvOps.IF_GEZ, + Form21t.THE_ONE, false, "if-gez"); + + public static final Dop IF_GTZ = + new Dop(DalvOps.IF_GTZ, DalvOps.IF_GTZ, + Form21t.THE_ONE, false, "if-gtz"); + + public static final Dop IF_LEZ = + new Dop(DalvOps.IF_LEZ, DalvOps.IF_LEZ, + Form21t.THE_ONE, false, "if-lez"); + + public static final Dop AGET = + new Dop(DalvOps.AGET, DalvOps.AGET, + Form23x.THE_ONE, true, "aget"); + + public static final Dop AGET_WIDE = + new Dop(DalvOps.AGET_WIDE, DalvOps.AGET_WIDE, + Form23x.THE_ONE, true, "aget-wide"); + + public static final Dop AGET_OBJECT = + new Dop(DalvOps.AGET_OBJECT, DalvOps.AGET_OBJECT, + Form23x.THE_ONE, true, "aget-object"); + + public static final Dop AGET_BOOLEAN = + new Dop(DalvOps.AGET_BOOLEAN, DalvOps.AGET_BOOLEAN, + Form23x.THE_ONE, true, "aget-boolean"); + + public static final Dop AGET_BYTE = + new Dop(DalvOps.AGET_BYTE, DalvOps.AGET_BYTE, + Form23x.THE_ONE, true, "aget-byte"); + + public static final Dop AGET_CHAR = + new Dop(DalvOps.AGET_CHAR, DalvOps.AGET_CHAR, + Form23x.THE_ONE, true, "aget-char"); + + public static final Dop AGET_SHORT = + new Dop(DalvOps.AGET_SHORT, DalvOps.AGET_SHORT, + Form23x.THE_ONE, true, "aget-short"); + + public static final Dop APUT = + new Dop(DalvOps.APUT, DalvOps.APUT, + Form23x.THE_ONE, false, "aput"); + + public static final Dop APUT_WIDE = + new Dop(DalvOps.APUT_WIDE, DalvOps.APUT_WIDE, + Form23x.THE_ONE, false, "aput-wide"); + + public static final Dop APUT_OBJECT = + new Dop(DalvOps.APUT_OBJECT, DalvOps.APUT_OBJECT, + Form23x.THE_ONE, false, "aput-object"); + + public static final Dop APUT_BOOLEAN = + new Dop(DalvOps.APUT_BOOLEAN, DalvOps.APUT_BOOLEAN, + Form23x.THE_ONE, false, "aput-boolean"); + + public static final Dop APUT_BYTE = + new Dop(DalvOps.APUT_BYTE, DalvOps.APUT_BYTE, + Form23x.THE_ONE, false, "aput-byte"); + + public static final Dop APUT_CHAR = + new Dop(DalvOps.APUT_CHAR, DalvOps.APUT_CHAR, + Form23x.THE_ONE, false, "aput-char"); + + public static final Dop APUT_SHORT = + new Dop(DalvOps.APUT_SHORT, DalvOps.APUT_SHORT, + Form23x.THE_ONE, false, "aput-short"); + + public static final Dop IGET = + new Dop(DalvOps.IGET, DalvOps.IGET, + Form22c.THE_ONE, true, "iget"); + + public static final Dop IGET_WIDE = + new Dop(DalvOps.IGET_WIDE, DalvOps.IGET_WIDE, + Form22c.THE_ONE, true, "iget-wide"); + + public static final Dop IGET_OBJECT = + new Dop(DalvOps.IGET_OBJECT, DalvOps.IGET_OBJECT, + Form22c.THE_ONE, true, "iget-object"); + + public static final Dop IGET_BOOLEAN = + new Dop(DalvOps.IGET_BOOLEAN, DalvOps.IGET_BOOLEAN, + Form22c.THE_ONE, true, "iget-boolean"); + + public static final Dop IGET_BYTE = + new Dop(DalvOps.IGET_BYTE, DalvOps.IGET_BYTE, + Form22c.THE_ONE, true, "iget-byte"); + + public static final Dop IGET_CHAR = + new Dop(DalvOps.IGET_CHAR, DalvOps.IGET_CHAR, + Form22c.THE_ONE, true, "iget-char"); + + public static final Dop IGET_SHORT = + new Dop(DalvOps.IGET_SHORT, DalvOps.IGET_SHORT, + Form22c.THE_ONE, true, "iget-short"); + + public static final Dop IPUT = + new Dop(DalvOps.IPUT, DalvOps.IPUT, + Form22c.THE_ONE, false, "iput"); + + public static final Dop IPUT_WIDE = + new Dop(DalvOps.IPUT_WIDE, DalvOps.IPUT_WIDE, + Form22c.THE_ONE, false, "iput-wide"); + + public static final Dop IPUT_OBJECT = + new Dop(DalvOps.IPUT_OBJECT, DalvOps.IPUT_OBJECT, + Form22c.THE_ONE, false, "iput-object"); + + public static final Dop IPUT_BOOLEAN = + new Dop(DalvOps.IPUT_BOOLEAN, DalvOps.IPUT_BOOLEAN, + Form22c.THE_ONE, false, "iput-boolean"); + + public static final Dop IPUT_BYTE = + new Dop(DalvOps.IPUT_BYTE, DalvOps.IPUT_BYTE, + Form22c.THE_ONE, false, "iput-byte"); + + public static final Dop IPUT_CHAR = + new Dop(DalvOps.IPUT_CHAR, DalvOps.IPUT_CHAR, + Form22c.THE_ONE, false, "iput-char"); + + public static final Dop IPUT_SHORT = + new Dop(DalvOps.IPUT_SHORT, DalvOps.IPUT_SHORT, + Form22c.THE_ONE, false, "iput-short"); + + public static final Dop SGET = + new Dop(DalvOps.SGET, DalvOps.SGET, + Form21c.THE_ONE, true, "sget"); + + public static final Dop SGET_WIDE = + new Dop(DalvOps.SGET_WIDE, DalvOps.SGET_WIDE, + Form21c.THE_ONE, true, "sget-wide"); + + public static final Dop SGET_OBJECT = + new Dop(DalvOps.SGET_OBJECT, DalvOps.SGET_OBJECT, + Form21c.THE_ONE, true, "sget-object"); + + public static final Dop SGET_BOOLEAN = + new Dop(DalvOps.SGET_BOOLEAN, DalvOps.SGET_BOOLEAN, + Form21c.THE_ONE, true, "sget-boolean"); + + public static final Dop SGET_BYTE = + new Dop(DalvOps.SGET_BYTE, DalvOps.SGET_BYTE, + Form21c.THE_ONE, true, "sget-byte"); + + public static final Dop SGET_CHAR = + new Dop(DalvOps.SGET_CHAR, DalvOps.SGET_CHAR, + Form21c.THE_ONE, true, "sget-char"); + + public static final Dop SGET_SHORT = + new Dop(DalvOps.SGET_SHORT, DalvOps.SGET_SHORT, + Form21c.THE_ONE, true, "sget-short"); + + public static final Dop SPUT = + new Dop(DalvOps.SPUT, DalvOps.SPUT, + Form21c.THE_ONE, false, "sput"); + + public static final Dop SPUT_WIDE = + new Dop(DalvOps.SPUT_WIDE, DalvOps.SPUT_WIDE, + Form21c.THE_ONE, false, "sput-wide"); + + public static final Dop SPUT_OBJECT = + new Dop(DalvOps.SPUT_OBJECT, DalvOps.SPUT_OBJECT, + Form21c.THE_ONE, false, "sput-object"); + + public static final Dop SPUT_BOOLEAN = + new Dop(DalvOps.SPUT_BOOLEAN, DalvOps.SPUT_BOOLEAN, + Form21c.THE_ONE, false, "sput-boolean"); + + public static final Dop SPUT_BYTE = + new Dop(DalvOps.SPUT_BYTE, DalvOps.SPUT_BYTE, + Form21c.THE_ONE, false, "sput-byte"); + + public static final Dop SPUT_CHAR = + new Dop(DalvOps.SPUT_CHAR, DalvOps.SPUT_CHAR, + Form21c.THE_ONE, false, "sput-char"); + + public static final Dop SPUT_SHORT = + new Dop(DalvOps.SPUT_SHORT, DalvOps.SPUT_SHORT, + Form21c.THE_ONE, false, "sput-short"); + + public static final Dop INVOKE_VIRTUAL = + new Dop(DalvOps.INVOKE_VIRTUAL, DalvOps.INVOKE_VIRTUAL, + Form35c.THE_ONE, false, "invoke-virtual"); + + public static final Dop INVOKE_SUPER = + new Dop(DalvOps.INVOKE_SUPER, DalvOps.INVOKE_SUPER, + Form35c.THE_ONE, false, "invoke-super"); + + public static final Dop INVOKE_DIRECT = + new Dop(DalvOps.INVOKE_DIRECT, DalvOps.INVOKE_DIRECT, + Form35c.THE_ONE, false, "invoke-direct"); + + public static final Dop INVOKE_STATIC = + new Dop(DalvOps.INVOKE_STATIC, DalvOps.INVOKE_STATIC, + Form35c.THE_ONE, false, "invoke-static"); + + public static final Dop INVOKE_INTERFACE = + new Dop(DalvOps.INVOKE_INTERFACE, DalvOps.INVOKE_INTERFACE, + Form35c.THE_ONE, false, "invoke-interface"); + + public static final Dop INVOKE_VIRTUAL_RANGE = + new Dop(DalvOps.INVOKE_VIRTUAL_RANGE, DalvOps.INVOKE_VIRTUAL, + Form3rc.THE_ONE, false, "invoke-virtual/range"); + + public static final Dop INVOKE_SUPER_RANGE = + new Dop(DalvOps.INVOKE_SUPER_RANGE, DalvOps.INVOKE_SUPER, + Form3rc.THE_ONE, false, "invoke-super/range"); + + public static final Dop INVOKE_DIRECT_RANGE = + new Dop(DalvOps.INVOKE_DIRECT_RANGE, DalvOps.INVOKE_DIRECT, + Form3rc.THE_ONE, false, "invoke-direct/range"); + + public static final Dop INVOKE_STATIC_RANGE = + new Dop(DalvOps.INVOKE_STATIC_RANGE, DalvOps.INVOKE_STATIC, + Form3rc.THE_ONE, false, "invoke-static/range"); + + public static final Dop INVOKE_INTERFACE_RANGE = + new Dop(DalvOps.INVOKE_INTERFACE_RANGE, DalvOps.INVOKE_INTERFACE, + Form3rc.THE_ONE, false, "invoke-interface/range"); + + public static final Dop NEG_INT = + new Dop(DalvOps.NEG_INT, DalvOps.NEG_INT, + Form12x.THE_ONE, true, "neg-int"); + + public static final Dop NOT_INT = + new Dop(DalvOps.NOT_INT, DalvOps.NOT_INT, + Form12x.THE_ONE, true, "not-int"); + + public static final Dop NEG_LONG = + new Dop(DalvOps.NEG_LONG, DalvOps.NEG_LONG, + Form12x.THE_ONE, true, "neg-long"); + + public static final Dop NOT_LONG = + new Dop(DalvOps.NOT_LONG, DalvOps.NOT_LONG, + Form12x.THE_ONE, true, "not-long"); + + public static final Dop NEG_FLOAT = + new Dop(DalvOps.NEG_FLOAT, DalvOps.NEG_FLOAT, + Form12x.THE_ONE, true, "neg-float"); + + public static final Dop NEG_DOUBLE = + new Dop(DalvOps.NEG_DOUBLE, DalvOps.NEG_DOUBLE, + Form12x.THE_ONE, true, "neg-double"); + + public static final Dop INT_TO_LONG = + new Dop(DalvOps.INT_TO_LONG, DalvOps.INT_TO_LONG, + Form12x.THE_ONE, true, "int-to-long"); + + public static final Dop INT_TO_FLOAT = + new Dop(DalvOps.INT_TO_FLOAT, DalvOps.INT_TO_FLOAT, + Form12x.THE_ONE, true, "int-to-float"); + + public static final Dop INT_TO_DOUBLE = + new Dop(DalvOps.INT_TO_DOUBLE, DalvOps.INT_TO_DOUBLE, + Form12x.THE_ONE, true, "int-to-double"); + + public static final Dop LONG_TO_INT = + new Dop(DalvOps.LONG_TO_INT, DalvOps.LONG_TO_INT, + Form12x.THE_ONE, true, "long-to-int"); + + public static final Dop LONG_TO_FLOAT = + new Dop(DalvOps.LONG_TO_FLOAT, DalvOps.LONG_TO_FLOAT, + Form12x.THE_ONE, true, "long-to-float"); + + public static final Dop LONG_TO_DOUBLE = + new Dop(DalvOps.LONG_TO_DOUBLE, DalvOps.LONG_TO_DOUBLE, + Form12x.THE_ONE, true, "long-to-double"); + + public static final Dop FLOAT_TO_INT = + new Dop(DalvOps.FLOAT_TO_INT, DalvOps.FLOAT_TO_INT, + Form12x.THE_ONE, true, "float-to-int"); + + public static final Dop FLOAT_TO_LONG = + new Dop(DalvOps.FLOAT_TO_LONG, DalvOps.FLOAT_TO_LONG, + Form12x.THE_ONE, true, "float-to-long"); + + public static final Dop FLOAT_TO_DOUBLE = + new Dop(DalvOps.FLOAT_TO_DOUBLE, DalvOps.FLOAT_TO_DOUBLE, + Form12x.THE_ONE, true, "float-to-double"); + + public static final Dop DOUBLE_TO_INT = + new Dop(DalvOps.DOUBLE_TO_INT, DalvOps.DOUBLE_TO_INT, + Form12x.THE_ONE, true, "double-to-int"); + + public static final Dop DOUBLE_TO_LONG = + new Dop(DalvOps.DOUBLE_TO_LONG, DalvOps.DOUBLE_TO_LONG, + Form12x.THE_ONE, true, "double-to-long"); + + public static final Dop DOUBLE_TO_FLOAT = + new Dop(DalvOps.DOUBLE_TO_FLOAT, DalvOps.DOUBLE_TO_FLOAT, + Form12x.THE_ONE, true, "double-to-float"); + + public static final Dop INT_TO_BYTE = + new Dop(DalvOps.INT_TO_BYTE, DalvOps.INT_TO_BYTE, + Form12x.THE_ONE, true, "int-to-byte"); + + public static final Dop INT_TO_CHAR = + new Dop(DalvOps.INT_TO_CHAR, DalvOps.INT_TO_CHAR, + Form12x.THE_ONE, true, "int-to-char"); + + public static final Dop INT_TO_SHORT = + new Dop(DalvOps.INT_TO_SHORT, DalvOps.INT_TO_SHORT, + Form12x.THE_ONE, true, "int-to-short"); + + public static final Dop ADD_INT = + new Dop(DalvOps.ADD_INT, DalvOps.ADD_INT, + Form23x.THE_ONE, true, "add-int"); + + public static final Dop SUB_INT = + new Dop(DalvOps.SUB_INT, DalvOps.SUB_INT, + Form23x.THE_ONE, true, "sub-int"); + + public static final Dop MUL_INT = + new Dop(DalvOps.MUL_INT, DalvOps.MUL_INT, + Form23x.THE_ONE, true, "mul-int"); + + public static final Dop DIV_INT = + new Dop(DalvOps.DIV_INT, DalvOps.DIV_INT, + Form23x.THE_ONE, true, "div-int"); + + public static final Dop REM_INT = + new Dop(DalvOps.REM_INT, DalvOps.REM_INT, + Form23x.THE_ONE, true, "rem-int"); + + public static final Dop AND_INT = + new Dop(DalvOps.AND_INT, DalvOps.AND_INT, + Form23x.THE_ONE, true, "and-int"); + + public static final Dop OR_INT = + new Dop(DalvOps.OR_INT, DalvOps.OR_INT, + Form23x.THE_ONE, true, "or-int"); + + public static final Dop XOR_INT = + new Dop(DalvOps.XOR_INT, DalvOps.XOR_INT, + Form23x.THE_ONE, true, "xor-int"); + + public static final Dop SHL_INT = + new Dop(DalvOps.SHL_INT, DalvOps.SHL_INT, + Form23x.THE_ONE, true, "shl-int"); + + public static final Dop SHR_INT = + new Dop(DalvOps.SHR_INT, DalvOps.SHR_INT, + Form23x.THE_ONE, true, "shr-int"); + + public static final Dop USHR_INT = + new Dop(DalvOps.USHR_INT, DalvOps.USHR_INT, + Form23x.THE_ONE, true, "ushr-int"); + + public static final Dop ADD_LONG = + new Dop(DalvOps.ADD_LONG, DalvOps.ADD_LONG, + Form23x.THE_ONE, true, "add-long"); + + public static final Dop SUB_LONG = + new Dop(DalvOps.SUB_LONG, DalvOps.SUB_LONG, + Form23x.THE_ONE, true, "sub-long"); + + public static final Dop MUL_LONG = + new Dop(DalvOps.MUL_LONG, DalvOps.MUL_LONG, + Form23x.THE_ONE, true, "mul-long"); + + public static final Dop DIV_LONG = + new Dop(DalvOps.DIV_LONG, DalvOps.DIV_LONG, + Form23x.THE_ONE, true, "div-long"); + + public static final Dop REM_LONG = + new Dop(DalvOps.REM_LONG, DalvOps.REM_LONG, + Form23x.THE_ONE, true, "rem-long"); + + public static final Dop AND_LONG = + new Dop(DalvOps.AND_LONG, DalvOps.AND_LONG, + Form23x.THE_ONE, true, "and-long"); + + public static final Dop OR_LONG = + new Dop(DalvOps.OR_LONG, DalvOps.OR_LONG, + Form23x.THE_ONE, true, "or-long"); + + public static final Dop XOR_LONG = + new Dop(DalvOps.XOR_LONG, DalvOps.XOR_LONG, + Form23x.THE_ONE, true, "xor-long"); + + public static final Dop SHL_LONG = + new Dop(DalvOps.SHL_LONG, DalvOps.SHL_LONG, + Form23x.THE_ONE, true, "shl-long"); + + public static final Dop SHR_LONG = + new Dop(DalvOps.SHR_LONG, DalvOps.SHR_LONG, + Form23x.THE_ONE, true, "shr-long"); + + public static final Dop USHR_LONG = + new Dop(DalvOps.USHR_LONG, DalvOps.USHR_LONG, + Form23x.THE_ONE, true, "ushr-long"); + + public static final Dop ADD_FLOAT = + new Dop(DalvOps.ADD_FLOAT, DalvOps.ADD_FLOAT, + Form23x.THE_ONE, true, "add-float"); + + public static final Dop SUB_FLOAT = + new Dop(DalvOps.SUB_FLOAT, DalvOps.SUB_FLOAT, + Form23x.THE_ONE, true, "sub-float"); + + public static final Dop MUL_FLOAT = + new Dop(DalvOps.MUL_FLOAT, DalvOps.MUL_FLOAT, + Form23x.THE_ONE, true, "mul-float"); + + public static final Dop DIV_FLOAT = + new Dop(DalvOps.DIV_FLOAT, DalvOps.DIV_FLOAT, + Form23x.THE_ONE, true, "div-float"); + + public static final Dop REM_FLOAT = + new Dop(DalvOps.REM_FLOAT, DalvOps.REM_FLOAT, + Form23x.THE_ONE, true, "rem-float"); + + public static final Dop ADD_DOUBLE = + new Dop(DalvOps.ADD_DOUBLE, DalvOps.ADD_DOUBLE, + Form23x.THE_ONE, true, "add-double"); + + public static final Dop SUB_DOUBLE = + new Dop(DalvOps.SUB_DOUBLE, DalvOps.SUB_DOUBLE, + Form23x.THE_ONE, true, "sub-double"); + + public static final Dop MUL_DOUBLE = + new Dop(DalvOps.MUL_DOUBLE, DalvOps.MUL_DOUBLE, + Form23x.THE_ONE, true, "mul-double"); + + public static final Dop DIV_DOUBLE = + new Dop(DalvOps.DIV_DOUBLE, DalvOps.DIV_DOUBLE, + Form23x.THE_ONE, true, "div-double"); + + public static final Dop REM_DOUBLE = + new Dop(DalvOps.REM_DOUBLE, DalvOps.REM_DOUBLE, + Form23x.THE_ONE, true, "rem-double"); + + public static final Dop ADD_INT_2ADDR = + new Dop(DalvOps.ADD_INT_2ADDR, DalvOps.ADD_INT, + Form12x.THE_ONE, true, "add-int/2addr"); + + public static final Dop SUB_INT_2ADDR = + new Dop(DalvOps.SUB_INT_2ADDR, DalvOps.SUB_INT, + Form12x.THE_ONE, true, "sub-int/2addr"); + + public static final Dop MUL_INT_2ADDR = + new Dop(DalvOps.MUL_INT_2ADDR, DalvOps.MUL_INT, + Form12x.THE_ONE, true, "mul-int/2addr"); + + public static final Dop DIV_INT_2ADDR = + new Dop(DalvOps.DIV_INT_2ADDR, DalvOps.DIV_INT, + Form12x.THE_ONE, true, "div-int/2addr"); + + public static final Dop REM_INT_2ADDR = + new Dop(DalvOps.REM_INT_2ADDR, DalvOps.REM_INT, + Form12x.THE_ONE, true, "rem-int/2addr"); + + public static final Dop AND_INT_2ADDR = + new Dop(DalvOps.AND_INT_2ADDR, DalvOps.AND_INT, + Form12x.THE_ONE, true, "and-int/2addr"); + + public static final Dop OR_INT_2ADDR = + new Dop(DalvOps.OR_INT_2ADDR, DalvOps.OR_INT, + Form12x.THE_ONE, true, "or-int/2addr"); + + public static final Dop XOR_INT_2ADDR = + new Dop(DalvOps.XOR_INT_2ADDR, DalvOps.XOR_INT, + Form12x.THE_ONE, true, "xor-int/2addr"); + + public static final Dop SHL_INT_2ADDR = + new Dop(DalvOps.SHL_INT_2ADDR, DalvOps.SHL_INT, + Form12x.THE_ONE, true, "shl-int/2addr"); + + public static final Dop SHR_INT_2ADDR = + new Dop(DalvOps.SHR_INT_2ADDR, DalvOps.SHR_INT, + Form12x.THE_ONE, true, "shr-int/2addr"); + + public static final Dop USHR_INT_2ADDR = + new Dop(DalvOps.USHR_INT_2ADDR, DalvOps.USHR_INT, + Form12x.THE_ONE, true, "ushr-int/2addr"); + + public static final Dop ADD_LONG_2ADDR = + new Dop(DalvOps.ADD_LONG_2ADDR, DalvOps.ADD_LONG, + Form12x.THE_ONE, true, "add-long/2addr"); + + public static final Dop SUB_LONG_2ADDR = + new Dop(DalvOps.SUB_LONG_2ADDR, DalvOps.SUB_LONG, + Form12x.THE_ONE, true, "sub-long/2addr"); + + public static final Dop MUL_LONG_2ADDR = + new Dop(DalvOps.MUL_LONG_2ADDR, DalvOps.MUL_LONG, + Form12x.THE_ONE, true, "mul-long/2addr"); + + public static final Dop DIV_LONG_2ADDR = + new Dop(DalvOps.DIV_LONG_2ADDR, DalvOps.DIV_LONG, + Form12x.THE_ONE, true, "div-long/2addr"); + + public static final Dop REM_LONG_2ADDR = + new Dop(DalvOps.REM_LONG_2ADDR, DalvOps.REM_LONG, + Form12x.THE_ONE, true, "rem-long/2addr"); + + public static final Dop AND_LONG_2ADDR = + new Dop(DalvOps.AND_LONG_2ADDR, DalvOps.AND_LONG, + Form12x.THE_ONE, true, "and-long/2addr"); + + public static final Dop OR_LONG_2ADDR = + new Dop(DalvOps.OR_LONG_2ADDR, DalvOps.OR_LONG, + Form12x.THE_ONE, true, "or-long/2addr"); + + public static final Dop XOR_LONG_2ADDR = + new Dop(DalvOps.XOR_LONG_2ADDR, DalvOps.XOR_LONG, + Form12x.THE_ONE, true, "xor-long/2addr"); + + public static final Dop SHL_LONG_2ADDR = + new Dop(DalvOps.SHL_LONG_2ADDR, DalvOps.SHL_LONG, + Form12x.THE_ONE, true, "shl-long/2addr"); + + public static final Dop SHR_LONG_2ADDR = + new Dop(DalvOps.SHR_LONG_2ADDR, DalvOps.SHR_LONG, + Form12x.THE_ONE, true, "shr-long/2addr"); + + public static final Dop USHR_LONG_2ADDR = + new Dop(DalvOps.USHR_LONG_2ADDR, DalvOps.USHR_LONG, + Form12x.THE_ONE, true, "ushr-long/2addr"); + + public static final Dop ADD_FLOAT_2ADDR = + new Dop(DalvOps.ADD_FLOAT_2ADDR, DalvOps.ADD_FLOAT, + Form12x.THE_ONE, true, "add-float/2addr"); + + public static final Dop SUB_FLOAT_2ADDR = + new Dop(DalvOps.SUB_FLOAT_2ADDR, DalvOps.SUB_FLOAT, + Form12x.THE_ONE, true, "sub-float/2addr"); + + public static final Dop MUL_FLOAT_2ADDR = + new Dop(DalvOps.MUL_FLOAT_2ADDR, DalvOps.MUL_FLOAT, + Form12x.THE_ONE, true, "mul-float/2addr"); + + public static final Dop DIV_FLOAT_2ADDR = + new Dop(DalvOps.DIV_FLOAT_2ADDR, DalvOps.DIV_FLOAT, + Form12x.THE_ONE, true, "div-float/2addr"); + + public static final Dop REM_FLOAT_2ADDR = + new Dop(DalvOps.REM_FLOAT_2ADDR, DalvOps.REM_FLOAT, + Form12x.THE_ONE, true, "rem-float/2addr"); + + public static final Dop ADD_DOUBLE_2ADDR = + new Dop(DalvOps.ADD_DOUBLE_2ADDR, DalvOps.ADD_DOUBLE, + Form12x.THE_ONE, true, "add-double/2addr"); + + public static final Dop SUB_DOUBLE_2ADDR = + new Dop(DalvOps.SUB_DOUBLE_2ADDR, DalvOps.SUB_DOUBLE, + Form12x.THE_ONE, true, "sub-double/2addr"); + + public static final Dop MUL_DOUBLE_2ADDR = + new Dop(DalvOps.MUL_DOUBLE_2ADDR, DalvOps.MUL_DOUBLE, + Form12x.THE_ONE, true, "mul-double/2addr"); + + public static final Dop DIV_DOUBLE_2ADDR = + new Dop(DalvOps.DIV_DOUBLE_2ADDR, DalvOps.DIV_DOUBLE, + Form12x.THE_ONE, true, "div-double/2addr"); + + public static final Dop REM_DOUBLE_2ADDR = + new Dop(DalvOps.REM_DOUBLE_2ADDR, DalvOps.REM_DOUBLE, + Form12x.THE_ONE, true, "rem-double/2addr"); + + public static final Dop ADD_INT_LIT16 = + new Dop(DalvOps.ADD_INT_LIT16, DalvOps.ADD_INT, + Form22s.THE_ONE, true, "add-int/lit16"); + + public static final Dop RSUB_INT = + new Dop(DalvOps.RSUB_INT, DalvOps.RSUB_INT, + Form22s.THE_ONE, true, "rsub-int"); + + public static final Dop MUL_INT_LIT16 = + new Dop(DalvOps.MUL_INT_LIT16, DalvOps.MUL_INT, + Form22s.THE_ONE, true, "mul-int/lit16"); + + public static final Dop DIV_INT_LIT16 = + new Dop(DalvOps.DIV_INT_LIT16, DalvOps.DIV_INT, + Form22s.THE_ONE, true, "div-int/lit16"); + + public static final Dop REM_INT_LIT16 = + new Dop(DalvOps.REM_INT_LIT16, DalvOps.REM_INT, + Form22s.THE_ONE, true, "rem-int/lit16"); + + public static final Dop AND_INT_LIT16 = + new Dop(DalvOps.AND_INT_LIT16, DalvOps.AND_INT, + Form22s.THE_ONE, true, "and-int/lit16"); + + public static final Dop OR_INT_LIT16 = + new Dop(DalvOps.OR_INT_LIT16, DalvOps.OR_INT, + Form22s.THE_ONE, true, "or-int/lit16"); + + public static final Dop XOR_INT_LIT16 = + new Dop(DalvOps.XOR_INT_LIT16, DalvOps.XOR_INT, + Form22s.THE_ONE, true, "xor-int/lit16"); + + public static final Dop ADD_INT_LIT8 = + new Dop(DalvOps.ADD_INT_LIT8, DalvOps.ADD_INT, + Form22b.THE_ONE, true, "add-int/lit8"); + + public static final Dop RSUB_INT_LIT8 = + new Dop(DalvOps.RSUB_INT_LIT8, DalvOps.RSUB_INT, + Form22b.THE_ONE, true, "rsub-int/lit8"); + + public static final Dop MUL_INT_LIT8 = + new Dop(DalvOps.MUL_INT_LIT8, DalvOps.MUL_INT, + Form22b.THE_ONE, true, "mul-int/lit8"); + + public static final Dop DIV_INT_LIT8 = + new Dop(DalvOps.DIV_INT_LIT8, DalvOps.DIV_INT, + Form22b.THE_ONE, true, "div-int/lit8"); + + public static final Dop REM_INT_LIT8 = + new Dop(DalvOps.REM_INT_LIT8, DalvOps.REM_INT, + Form22b.THE_ONE, true, "rem-int/lit8"); + + public static final Dop AND_INT_LIT8 = + new Dop(DalvOps.AND_INT_LIT8, DalvOps.AND_INT, + Form22b.THE_ONE, true, "and-int/lit8"); + + public static final Dop OR_INT_LIT8 = + new Dop(DalvOps.OR_INT_LIT8, DalvOps.OR_INT, + Form22b.THE_ONE, true, "or-int/lit8"); + + public static final Dop XOR_INT_LIT8 = + new Dop(DalvOps.XOR_INT_LIT8, DalvOps.XOR_INT, + Form22b.THE_ONE, true, "xor-int/lit8"); + + public static final Dop SHL_INT_LIT8 = + new Dop(DalvOps.SHL_INT_LIT8, DalvOps.SHL_INT, + Form22b.THE_ONE, true, "shl-int/lit8"); + + public static final Dop SHR_INT_LIT8 = + new Dop(DalvOps.SHR_INT_LIT8, DalvOps.SHR_INT, + Form22b.THE_ONE, true, "shr-int/lit8"); + + public static final Dop USHR_INT_LIT8 = + new Dop(DalvOps.USHR_INT_LIT8, DalvOps.USHR_INT, + Form22b.THE_ONE, true, "ushr-int/lit8"); + + // END(dops) + + // Static initialization. + static { + DOPS = new Dop[DalvOps.MAX_VALUE - DalvOps.MIN_VALUE + 1]; + + set(SPECIAL_FORMAT); + + // BEGIN(dops-init); GENERATED AUTOMATICALLY BY opcode-gen + set(NOP); + set(MOVE); + set(MOVE_FROM16); + set(MOVE_16); + set(MOVE_WIDE); + set(MOVE_WIDE_FROM16); + set(MOVE_WIDE_16); + set(MOVE_OBJECT); + set(MOVE_OBJECT_FROM16); + set(MOVE_OBJECT_16); + set(MOVE_RESULT); + set(MOVE_RESULT_WIDE); + set(MOVE_RESULT_OBJECT); + set(MOVE_EXCEPTION); + set(RETURN_VOID); + set(RETURN); + set(RETURN_WIDE); + set(RETURN_OBJECT); + set(CONST_4); + set(CONST_16); + set(CONST); + set(CONST_HIGH16); + set(CONST_WIDE_16); + set(CONST_WIDE_32); + set(CONST_WIDE); + set(CONST_WIDE_HIGH16); + set(CONST_STRING); + set(CONST_STRING_JUMBO); + set(CONST_CLASS); + set(MONITOR_ENTER); + set(MONITOR_EXIT); + set(CHECK_CAST); + set(INSTANCE_OF); + set(ARRAY_LENGTH); + set(NEW_INSTANCE); + set(NEW_ARRAY); + set(FILLED_NEW_ARRAY); + set(FILLED_NEW_ARRAY_RANGE); + set(FILL_ARRAY_DATA); + set(THROW); + set(GOTO); + set(GOTO_16); + set(GOTO_32); + set(PACKED_SWITCH); + set(SPARSE_SWITCH); + set(CMPL_FLOAT); + set(CMPG_FLOAT); + set(CMPL_DOUBLE); + set(CMPG_DOUBLE); + set(CMP_LONG); + set(IF_EQ); + set(IF_NE); + set(IF_LT); + set(IF_GE); + set(IF_GT); + set(IF_LE); + set(IF_EQZ); + set(IF_NEZ); + set(IF_LTZ); + set(IF_GEZ); + set(IF_GTZ); + set(IF_LEZ); + set(AGET); + set(AGET_WIDE); + set(AGET_OBJECT); + set(AGET_BOOLEAN); + set(AGET_BYTE); + set(AGET_CHAR); + set(AGET_SHORT); + set(APUT); + set(APUT_WIDE); + set(APUT_OBJECT); + set(APUT_BOOLEAN); + set(APUT_BYTE); + set(APUT_CHAR); + set(APUT_SHORT); + set(IGET); + set(IGET_WIDE); + set(IGET_OBJECT); + set(IGET_BOOLEAN); + set(IGET_BYTE); + set(IGET_CHAR); + set(IGET_SHORT); + set(IPUT); + set(IPUT_WIDE); + set(IPUT_OBJECT); + set(IPUT_BOOLEAN); + set(IPUT_BYTE); + set(IPUT_CHAR); + set(IPUT_SHORT); + set(SGET); + set(SGET_WIDE); + set(SGET_OBJECT); + set(SGET_BOOLEAN); + set(SGET_BYTE); + set(SGET_CHAR); + set(SGET_SHORT); + set(SPUT); + set(SPUT_WIDE); + set(SPUT_OBJECT); + set(SPUT_BOOLEAN); + set(SPUT_BYTE); + set(SPUT_CHAR); + set(SPUT_SHORT); + set(INVOKE_VIRTUAL); + set(INVOKE_SUPER); + set(INVOKE_DIRECT); + set(INVOKE_STATIC); + set(INVOKE_INTERFACE); + set(INVOKE_VIRTUAL_RANGE); + set(INVOKE_SUPER_RANGE); + set(INVOKE_DIRECT_RANGE); + set(INVOKE_STATIC_RANGE); + set(INVOKE_INTERFACE_RANGE); + set(NEG_INT); + set(NOT_INT); + set(NEG_LONG); + set(NOT_LONG); + set(NEG_FLOAT); + set(NEG_DOUBLE); + set(INT_TO_LONG); + set(INT_TO_FLOAT); + set(INT_TO_DOUBLE); + set(LONG_TO_INT); + set(LONG_TO_FLOAT); + set(LONG_TO_DOUBLE); + set(FLOAT_TO_INT); + set(FLOAT_TO_LONG); + set(FLOAT_TO_DOUBLE); + set(DOUBLE_TO_INT); + set(DOUBLE_TO_LONG); + set(DOUBLE_TO_FLOAT); + set(INT_TO_BYTE); + set(INT_TO_CHAR); + set(INT_TO_SHORT); + set(ADD_INT); + set(SUB_INT); + set(MUL_INT); + set(DIV_INT); + set(REM_INT); + set(AND_INT); + set(OR_INT); + set(XOR_INT); + set(SHL_INT); + set(SHR_INT); + set(USHR_INT); + set(ADD_LONG); + set(SUB_LONG); + set(MUL_LONG); + set(DIV_LONG); + set(REM_LONG); + set(AND_LONG); + set(OR_LONG); + set(XOR_LONG); + set(SHL_LONG); + set(SHR_LONG); + set(USHR_LONG); + set(ADD_FLOAT); + set(SUB_FLOAT); + set(MUL_FLOAT); + set(DIV_FLOAT); + set(REM_FLOAT); + set(ADD_DOUBLE); + set(SUB_DOUBLE); + set(MUL_DOUBLE); + set(DIV_DOUBLE); + set(REM_DOUBLE); + set(ADD_INT_2ADDR); + set(SUB_INT_2ADDR); + set(MUL_INT_2ADDR); + set(DIV_INT_2ADDR); + set(REM_INT_2ADDR); + set(AND_INT_2ADDR); + set(OR_INT_2ADDR); + set(XOR_INT_2ADDR); + set(SHL_INT_2ADDR); + set(SHR_INT_2ADDR); + set(USHR_INT_2ADDR); + set(ADD_LONG_2ADDR); + set(SUB_LONG_2ADDR); + set(MUL_LONG_2ADDR); + set(DIV_LONG_2ADDR); + set(REM_LONG_2ADDR); + set(AND_LONG_2ADDR); + set(OR_LONG_2ADDR); + set(XOR_LONG_2ADDR); + set(SHL_LONG_2ADDR); + set(SHR_LONG_2ADDR); + set(USHR_LONG_2ADDR); + set(ADD_FLOAT_2ADDR); + set(SUB_FLOAT_2ADDR); + set(MUL_FLOAT_2ADDR); + set(DIV_FLOAT_2ADDR); + set(REM_FLOAT_2ADDR); + set(ADD_DOUBLE_2ADDR); + set(SUB_DOUBLE_2ADDR); + set(MUL_DOUBLE_2ADDR); + set(DIV_DOUBLE_2ADDR); + set(REM_DOUBLE_2ADDR); + set(ADD_INT_LIT16); + set(RSUB_INT); + set(MUL_INT_LIT16); + set(DIV_INT_LIT16); + set(REM_INT_LIT16); + set(AND_INT_LIT16); + set(OR_INT_LIT16); + set(XOR_INT_LIT16); + set(ADD_INT_LIT8); + set(RSUB_INT_LIT8); + set(MUL_INT_LIT8); + set(DIV_INT_LIT8); + set(REM_INT_LIT8); + set(AND_INT_LIT8); + set(OR_INT_LIT8); + set(XOR_INT_LIT8); + set(SHL_INT_LIT8); + set(SHR_INT_LIT8); + set(USHR_INT_LIT8); + // END(dops-init) + } + + /** + * This class is uninstantiable. + */ + private Dops() { + // This space intentionally left blank. + } + + /** + * Gets the {@link Dop} for the given opcode value. + * + * @param opcode {@code DalvOps.MIN_VALUE..DalvOps.MAX_VALUE;} the opcode value + * @return {@code non-null;} the associated opcode instance + */ + public static Dop get(int opcode) { + int idx = opcode - DalvOps.MIN_VALUE; + + try { + Dop result = DOPS[idx]; + if (result != null) { + return result; + } + } catch (ArrayIndexOutOfBoundsException ex) { + // Fall through. + } + + throw new IllegalArgumentException("bogus opcode"); + } + + /** + * Gets the {@link Dop} with the given family/format combination, if + * any. + * + * @param family {@code DalvOps.MIN_VALUE..DalvOps.MAX_VALUE;} the opcode family + * @param format {@code non-null;} the opcode's instruction format + * @return {@code null-ok;} the corresponding opcode, or {@code null} if + * there is none + */ + public static Dop getOrNull(int family, InsnFormat format) { + if (format == null) { + throw new NullPointerException("format == null"); + } + + int len = DOPS.length; + + // TODO: Linear search is bad. + for (int i = 0; i < len; i++) { + Dop dop = DOPS[i]; + if ((dop != null) && + (dop.getFamily() == family) && + (dop.getFormat() == format)) { + return dop; + } + } + + return null; + } + + /** + * Puts the given opcode into the table of all ops. + * + * @param opcode {@code non-null;} the opcode + */ + private static void set(Dop opcode) { + int idx = opcode.getOpcode() - DalvOps.MIN_VALUE; + DOPS[idx] = opcode; + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/FixedSizeInsn.java b/dexgen/src/com/android/dexgen/dex/code/FixedSizeInsn.java new file mode 100644 index 0000000..28d8986 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/FixedSizeInsn.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.code.SourcePosition; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Base class for instructions which are of a fixed code size and which use {@link InsnFormat} methods to write themselves. This + * includes most — but not all — instructions. + */ +public abstract class FixedSizeInsn extends DalvInsn { + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * <p><b>Note:</b> In the unlikely event that an instruction takes + * absolutely no registers (e.g., a {@code nop} or a + * no-argument no-result * static method call), then the given + * register list may be passed as {@link + * RegisterSpecList#EMPTY}.</p> + * + * @param opcode the opcode; one of the constants from {@link Dops} + * @param position {@code non-null;} source position + * @param registers {@code non-null;} register list, including a + * result register if appropriate (that is, registers may be either + * ins or outs) + */ + public FixedSizeInsn(Dop opcode, SourcePosition position, + RegisterSpecList registers) { + super(opcode, position, registers); + } + + /** {@inheritDoc} */ + @Override + public final int codeSize() { + return getOpcode().getFormat().codeSize(); + } + + /** {@inheritDoc} */ + @Override + public final void writeTo(AnnotatedOutput out) { + getOpcode().getFormat().writeTo(out, this); + } + + /** {@inheritDoc} */ + @Override + public final DalvInsn withRegisterOffset(int delta) { + return withRegisters(getRegisters().withOffset(delta)); + } + + /** {@inheritDoc} */ + @Override + protected final String listingString0(boolean noteIndices) { + return getOpcode().getFormat().listingString(this, noteIndices); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/HighRegisterPrefix.java b/dexgen/src/com/android/dexgen/dex/code/HighRegisterPrefix.java new file mode 100644 index 0000000..08794ff --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/HighRegisterPrefix.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.RegisterSpec; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.code.SourcePosition; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Combination instruction which turns into a variable number of + * {@code move*} instructions to move a set of registers into + * registers starting at {@code 0} sequentially. This is used + * in translating an instruction whose register requirements cannot + * be met using a straightforward choice of a single opcode. + */ +public final class HighRegisterPrefix extends VariableSizeInsn { + /** {@code null-ok;} cached instructions, if constructed */ + private SimpleInsn[] insns; + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + * @param registers {@code non-null;} source registers + */ + public HighRegisterPrefix(SourcePosition position, + RegisterSpecList registers) { + super(position, registers); + + if (registers.size() == 0) { + throw new IllegalArgumentException("registers.size() == 0"); + } + + insns = null; + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + int result = 0; + + calculateInsnsIfNecessary(); + + for (SimpleInsn insn : insns) { + result += insn.codeSize(); + } + + return result; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out) { + calculateInsnsIfNecessary(); + + for (SimpleInsn insn : insns) { + insn.writeTo(out); + } + } + + /** + * Helper for {@link #codeSize} and {@link #writeTo} which sets up + * {@link #insns} if not already done. + */ + private void calculateInsnsIfNecessary() { + if (insns != null) { + return; + } + + RegisterSpecList registers = getRegisters(); + int sz = registers.size(); + + insns = new SimpleInsn[sz]; + + for (int i = 0, outAt = 0; i < sz; i++) { + RegisterSpec src = registers.get(i); + insns[i] = moveInsnFor(src, outAt); + outAt += src.getCategory(); + } + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new HighRegisterPrefix(getPosition(), registers); + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return null; + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + RegisterSpecList registers = getRegisters(); + int sz = registers.size(); + StringBuffer sb = new StringBuffer(100); + + for (int i = 0, outAt = 0; i < sz; i++) { + RegisterSpec src = registers.get(i); + SimpleInsn insn = moveInsnFor(src, outAt); + + if (i != 0) { + sb.append('\n'); + } + + sb.append(insn.listingString0(noteIndices)); + + outAt += src.getCategory(); + } + + return sb.toString(); + } + + /** + * Returns the proper move instruction for the given source spec + * and destination index. + * + * @param src {@code non-null;} the source register spec + * @param destIndex {@code >= 0;} the destination register index + * @return {@code non-null;} the appropriate move instruction + */ + private static SimpleInsn moveInsnFor(RegisterSpec src, int destIndex) { + return DalvInsn.makeMove(SourcePosition.NO_INFO, + RegisterSpec.make(destIndex, src.getType()), + src); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/InsnFormat.java b/dexgen/src/com/android/dexgen/dex/code/InsnFormat.java new file mode 100644 index 0000000..bd5b60d --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/InsnFormat.java @@ -0,0 +1,578 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstInteger; +import com.android.dexgen.rop.cst.CstKnownNull; +import com.android.dexgen.rop.cst.CstLiteral64; +import com.android.dexgen.rop.cst.CstLiteralBits; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; + +/** + * Base class for all instruction format handlers. Instruction format + * handlers know how to translate {@link DalvInsn} instances into + * streams of code words, as well as human-oriented listing strings + * representing such translations. + */ +public abstract class InsnFormat { + /** + * Returns the string form, suitable for inclusion in a listing + * dump, of the given instruction. The instruction must be of this + * instance's format for proper operation. + * + * @param insn {@code non-null;} the instruction + * @param noteIndices whether to include an explicit notation of + * constant pool indices + * @return {@code non-null;} the string form + */ + public final String listingString(DalvInsn insn, boolean noteIndices) { + String op = insn.getOpcode().getName(); + String arg = insnArgString(insn); + String comment = insnCommentString(insn, noteIndices); + StringBuilder sb = new StringBuilder(100); + + sb.append(op); + + if (arg.length() != 0) { + sb.append(' '); + sb.append(arg); + } + + if (comment.length() != 0) { + sb.append(" // "); + sb.append(comment); + } + + return sb.toString(); + } + + /** + * Returns the string form of the arguments to the given instruction. + * The instruction must be of this instance's format. If the instruction + * has no arguments, then the result should be {@code ""}, not + * {@code null}. + * + * <p>Subclasses must override this method.</p> + * + * @param insn {@code non-null;} the instruction + * @return {@code non-null;} the string form + */ + public abstract String insnArgString(DalvInsn insn); + + /** + * Returns the associated comment for the given instruction, if any. + * The instruction must be of this instance's format. If the instruction + * has no comment, then the result should be {@code ""}, not + * {@code null}. + * + * <p>Subclasses must override this method.</p> + * + * @param insn {@code non-null;} the instruction + * @param noteIndices whether to include an explicit notation of + * constant pool indices + * @return {@code non-null;} the string form + */ + public abstract String insnCommentString(DalvInsn insn, + boolean noteIndices); + + /** + * Gets the code size of instructions that use this format. The + * size is a number of 16-bit code units, not bytes. This should + * throw an exception if this format is of variable size. + * + * @return {@code >= 0;} the instruction length in 16-bit code units + */ + public abstract int codeSize(); + + /** + * Returns whether or not the given instruction's arguments will + * fit in this instance's format. This includes such things as + * counting register arguments, checking register ranges, and + * making sure that additional arguments are of appropriate types + * and are in-range. If this format has a branch target but the + * instruction's branch offset is unknown, this method will simply + * not check the offset. + * + * <p>Subclasses must override this method.</p> + * + * @param insn {@code non-null;} the instruction to check + * @return {@code true} iff the instruction's arguments are + * appropriate for this instance, or {@code false} if not + */ + public abstract boolean isCompatible(DalvInsn insn); + + /** + * Returns whether or not the given instruction's branch offset will + * fit in this instance's format. This always returns {@code false} + * for formats that don't include a branch offset. + * + * <p>The default implementation of this method always returns + * {@code false}. Subclasses must override this method if they + * include branch offsets.</p> + * + * @param insn {@code non-null;} the instruction to check + * @return {@code true} iff the instruction's branch offset is + * appropriate for this instance, or {@code false} if not + */ + public boolean branchFits(TargetInsn insn) { + return false; + } + + /** + * Returns the next instruction format to try to match an instruction + * with, presuming that this instance isn't compatible, if any. + * + * <p>Subclasses must override this method.</p> + * + * @return {@code null-ok;} the next format to try, or {@code null} if + * there are no suitable alternatives + */ + public abstract InsnFormat nextUp(); + + /** + * Writes the code units for the given instruction to the given + * output destination. The instruction must be of this instance's format. + * + * <p>Subclasses must override this method.</p> + * + * @param out {@code non-null;} the output destination to write to + * @param insn {@code non-null;} the instruction to write + */ + public abstract void writeTo(AnnotatedOutput out, DalvInsn insn); + + /** + * Helper method to return a register list string. + * + * @param list {@code non-null;} the list of registers + * @return {@code non-null;} the string form + */ + protected static String regListString(RegisterSpecList list) { + int sz = list.size(); + StringBuffer sb = new StringBuffer(sz * 5 + 2); + + sb.append('{'); + + for (int i = 0; i < sz; i++) { + if (i != 0) { + sb.append(", "); + } + sb.append(list.get(i).regString()); + } + + sb.append('}'); + + return sb.toString(); + } + + /** + * Helper method to return a literal bits argument string. + * + * @param value the value + * @return {@code non-null;} the string form + */ + protected static String literalBitsString(CstLiteralBits value) { + StringBuffer sb = new StringBuffer(100); + + sb.append('#'); + + if (value instanceof CstKnownNull) { + sb.append("null"); + } else { + sb.append(value.typeName()); + sb.append(' '); + sb.append(value.toHuman()); + } + + return sb.toString(); + } + + /** + * Helper method to return a literal bits comment string. + * + * @param value the value + * @param width the width of the constant, in bits (used for displaying + * the uninterpreted bits; one of: {@code 4 8 16 32 64} + * @return {@code non-null;} the comment + */ + protected static String literalBitsComment(CstLiteralBits value, + int width) { + StringBuffer sb = new StringBuffer(20); + + sb.append("#"); + + long bits; + + if (value instanceof CstLiteral64) { + bits = ((CstLiteral64) value).getLongBits(); + } else { + bits = value.getIntBits(); + } + + switch (width) { + case 4: sb.append(Hex.uNibble((int) bits)); break; + case 8: sb.append(Hex.u1((int) bits)); break; + case 16: sb.append(Hex.u2((int) bits)); break; + case 32: sb.append(Hex.u4((int) bits)); break; + case 64: sb.append(Hex.u8(bits)); break; + default: { + throw new RuntimeException("shouldn't happen"); + } + } + + return sb.toString(); + } + + /** + * Helper method to return a branch address string. + * + * @param insn {@code non-null;} the instruction in question + * @return {@code non-null;} the string form of the instruction's branch target + */ + protected static String branchString(DalvInsn insn) { + TargetInsn ti = (TargetInsn) insn; + int address = ti.getTargetAddress(); + + return (address == (char) address) ? Hex.u2(address) : Hex.u4(address); + } + + /** + * Helper method to return the comment for a branch. + * + * @param insn {@code non-null;} the instruction in question + * @return {@code non-null;} the comment + */ + protected static String branchComment(DalvInsn insn) { + TargetInsn ti = (TargetInsn) insn; + int offset = ti.getTargetOffset(); + + return (offset == (short) offset) ? Hex.s2(offset) : Hex.s4(offset); + } + + /** + * Helper method to return a constant string. + * + * @param insn {@code non-null;} a constant-bearing instruction + * @return {@code non-null;} the string form of the contained constant + */ + protected static String cstString(DalvInsn insn) { + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + return cst.toHuman(); + } + + /** + * Helper method to return an instruction comment for a constant. + * + * @param insn {@code non-null;} a constant-bearing instruction + * @return {@code non-null;} comment string representing the constant + */ + protected static String cstComment(DalvInsn insn) { + CstInsn ci = (CstInsn) insn; + + if (! ci.hasIndex()) { + return ""; + } + + StringBuilder sb = new StringBuilder(20); + int index = ci.getIndex(); + + sb.append(ci.getConstant().typeName()); + sb.append('@'); + + if (index < 65536) { + sb.append(Hex.u2(index)); + } else { + sb.append(Hex.u4(index)); + } + + return sb.toString(); + } + + /** + * Helper method to determine if a signed int value fits in a nibble. + * + * @param value the value in question + * @return {@code true} iff it's in the range -8..+7 + */ + protected static boolean signedFitsInNibble(int value) { + return (value >= -8) && (value <= 7); + } + + /** + * Helper method to determine if an unsigned int value fits in a nibble. + * + * @param value the value in question + * @return {@code true} iff it's in the range 0..0xf + */ + protected static boolean unsignedFitsInNibble(int value) { + return value == (value & 0xf); + } + + /** + * Helper method to determine if a signed int value fits in a byte. + * + * @param value the value in question + * @return {@code true} iff it's in the range -0x80..+0x7f + */ + protected static boolean signedFitsInByte(int value) { + return (byte) value == value; + } + + /** + * Helper method to determine if an unsigned int value fits in a byte. + * + * @param value the value in question + * @return {@code true} iff it's in the range 0..0xff + */ + protected static boolean unsignedFitsInByte(int value) { + return value == (value & 0xff); + } + + /** + * Helper method to determine if a signed int value fits in a short. + * + * @param value the value in question + * @return {@code true} iff it's in the range -0x8000..+0x7fff + */ + protected static boolean signedFitsInShort(int value) { + return (short) value == value; + } + + /** + * Helper method to determine if an unsigned int value fits in a short. + * + * @param value the value in question + * @return {@code true} iff it's in the range 0..0xffff + */ + protected static boolean unsignedFitsInShort(int value) { + return value == (value & 0xffff); + } + + /** + * Helper method to determine if a signed int value fits in three bytes. + * + * @param value the value in question + * @return {@code true} iff it's in the range -0x800000..+0x7fffff + */ + protected static boolean signedFitsIn3Bytes(int value) { + return value == ((value << 8) >> 8); + } + + /** + * Helper method to extract the callout-argument index from an + * appropriate instruction. + * + * @param insn {@code non-null;} the instruction + * @return {@code >= 0;} the callout argument index + */ + protected static int argIndex(DalvInsn insn) { + int arg = ((CstInteger) ((CstInsn) insn).getConstant()).getValue(); + + if (arg < 0) { + throw new IllegalArgumentException("bogus insn"); + } + + return arg; + } + + /** + * Helper method to combine an opcode and a second byte of data into + * the appropriate form for emitting into a code buffer. + * + * @param insn {@code non-null;} the instruction containing the opcode + * @param arg {@code 0..255;} arbitrary other byte value + * @return combined value + */ + protected static short opcodeUnit(DalvInsn insn, int arg) { + if ((arg & 0xff) != arg) { + throw new IllegalArgumentException("arg out of range 0..255"); + } + + int opcode = insn.getOpcode().getOpcode(); + + if ((opcode & 0xff) != opcode) { + throw new IllegalArgumentException("opcode out of range 0..255"); + } + + return (short) (opcode | (arg << 8)); + } + + /** + * Helper method to combine two bytes into a code unit. + * + * @param low {@code 0..255;} low byte + * @param high {@code 0..255;} high byte + * @return combined value + */ + protected static short codeUnit(int low, int high) { + if ((low & 0xff) != low) { + throw new IllegalArgumentException("low out of range 0..255"); + } + + if ((high & 0xff) != high) { + throw new IllegalArgumentException("high out of range 0..255"); + } + + return (short) (low | (high << 8)); + } + + /** + * Helper method to combine four nibbles into a code unit. + * + * @param n0 {@code 0..15;} low nibble + * @param n1 {@code 0..15;} medium-low nibble + * @param n2 {@code 0..15;} medium-high nibble + * @param n3 {@code 0..15;} high nibble + * @return combined value + */ + protected static short codeUnit(int n0, int n1, int n2, int n3) { + if ((n0 & 0xf) != n0) { + throw new IllegalArgumentException("n0 out of range 0..15"); + } + + if ((n1 & 0xf) != n1) { + throw new IllegalArgumentException("n1 out of range 0..15"); + } + + if ((n2 & 0xf) != n2) { + throw new IllegalArgumentException("n2 out of range 0..15"); + } + + if ((n3 & 0xf) != n3) { + throw new IllegalArgumentException("n3 out of range 0..15"); + } + + return (short) (n0 | (n1 << 4) | (n2 << 8) | (n3 << 12)); + } + + /** + * Helper method to combine two nibbles into a byte. + * + * @param low {@code 0..15;} low nibble + * @param high {@code 0..15;} high nibble + * @return {@code 0..255;} combined value + */ + protected static int makeByte(int low, int high) { + if ((low & 0xf) != low) { + throw new IllegalArgumentException("low out of range 0..15"); + } + + if ((high & 0xf) != high) { + throw new IllegalArgumentException("high out of range 0..15"); + } + + return low | (high << 4); + } + + /** + * Writes one code unit to the given output destination. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + */ + protected static void write(AnnotatedOutput out, short c0) { + out.writeShort(c0); + } + + /** + * Writes two code units to the given output destination. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + * @param c1 code unit to write + */ + protected static void write(AnnotatedOutput out, short c0, short c1) { + out.writeShort(c0); + out.writeShort(c1); + } + + /** + * Writes three code units to the given output destination. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + * @param c1 code unit to write + * @param c2 code unit to write + */ + protected static void write(AnnotatedOutput out, short c0, short c1, + short c2) { + out.writeShort(c0); + out.writeShort(c1); + out.writeShort(c2); + } + + /** + * Writes four code units to the given output destination. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + * @param c1 code unit to write + * @param c2 code unit to write + * @param c3 code unit to write + */ + protected static void write(AnnotatedOutput out, short c0, short c1, + short c2, short c3) { + out.writeShort(c0); + out.writeShort(c1); + out.writeShort(c2); + out.writeShort(c3); + } + + /** + * Writes five code units to the given output destination. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + * @param c1 code unit to write + * @param c2 code unit to write + * @param c3 code unit to write + * @param c4 code unit to write + */ + protected static void write(AnnotatedOutput out, short c0, short c1, + short c2, short c3, short c4) { + out.writeShort(c0); + out.writeShort(c1); + out.writeShort(c2); + out.writeShort(c3); + out.writeShort(c4); + } + + /** + * Writes six code units to the given output destination. + * + * @param out {@code non-null;} where to write to + * @param c0 code unit to write + * @param c1 code unit to write + * @param c2 code unit to write + * @param c3 code unit to write + * @param c4 code unit to write + * @param c5 code unit to write + */ + protected static void write(AnnotatedOutput out, short c0, short c1, + short c2, short c3, short c4, short c5) { + out.writeShort(c0); + out.writeShort(c1); + out.writeShort(c2); + out.writeShort(c3); + out.writeShort(c4); + out.writeShort(c5); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/LocalEnd.java b/dexgen/src/com/android/dexgen/dex/code/LocalEnd.java new file mode 100644 index 0000000..130b08b --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/LocalEnd.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.RegisterSpec; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.code.SourcePosition; + +/** + * Pseudo-instruction which is used to explicitly end the mapping of a + * register to a named local variable. That is, an instance of this + * class in an instruction stream indicates that starting with the + * subsequent instruction, the indicated variable is no longer valid. + */ +public final class LocalEnd extends ZeroSizeInsn { + /** + * {@code non-null;} register spec representing the local variable ended + * by this instance. <b>Note:</b> Technically, only the register + * number needs to be recorded here as the rest of the information + * is implicit in the ambient local variable state, but other code + * will check the other info for consistency. + */ + private final RegisterSpec local; + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + * @param local {@code non-null;} register spec representing the local + * variable introduced by this instance + */ + public LocalEnd(SourcePosition position, RegisterSpec local) { + super(position); + + if (local == null) { + throw new NullPointerException("local == null"); + } + + this.local = local; + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisterOffset(int delta) { + return new LocalEnd(getPosition(), local.withOffset(delta)); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new LocalEnd(getPosition(), local); + } + + /** + * Gets the register spec representing the local variable ended + * by this instance. + * + * @return {@code non-null;} the register spec + */ + public RegisterSpec getLocal() { + return local; + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return local.toString(); + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + return "local-end " + LocalStart.localString(local); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/LocalList.java b/dexgen/src/com/android/dexgen/dex/code/LocalList.java new file mode 100644 index 0000000..c1c3921 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/LocalList.java @@ -0,0 +1,948 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.RegisterSpec; +import com.android.dexgen.rop.code.RegisterSpecSet; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.util.FixedSizeList; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * List of local variables. Each local variable entry indicates a + * range of code which it is valid for, a register number, a name, + * and a type. + */ +public final class LocalList extends FixedSizeList { + /** {@code non-null;} empty instance */ + public static final LocalList EMPTY = new LocalList(0); + + /** whether to run the self-check code */ + private static final boolean DEBUG = false; + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size {@code >= 0;} the size of the list + */ + public LocalList(int size) { + super(size); + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public Entry get(int n) { + return (Entry) get0(n); + } + + /** + * Sets the entry at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param entry {@code non-null;} the entry to set at {@code n} + */ + public void set(int n, Entry entry) { + set0(n, entry); + } + + /** + * Does a human-friendly dump of this instance. + * + * @param out {@code non-null;} where to dump + * @param prefix {@code non-null;} prefix to attach to each line of output + */ + public void debugPrint(PrintStream out, String prefix) { + int sz = size(); + + for (int i = 0; i < sz; i++) { + out.print(prefix); + out.println(get(i)); + } + } + + /** + * Disposition of a local entry. + */ + public static enum Disposition { + /** local started (introduced) */ + START, + + /** local ended without being replaced */ + END_SIMPLY, + + /** local ended because it was directly replaced */ + END_REPLACED, + + /** local ended because it was moved to a different register */ + END_MOVED, + + /** + * local ended because the previous local clobbered this one + * (because it is category-2) + */ + END_CLOBBERED_BY_PREV, + + /** + * local ended because the next local clobbered this one + * (because this one is a category-2) + */ + END_CLOBBERED_BY_NEXT; + } + + /** + * Entry in a local list. + */ + public static class Entry implements Comparable<Entry> { + /** {@code >= 0;} address */ + private final int address; + + /** {@code non-null;} disposition of the local */ + private final Disposition disposition; + + /** {@code non-null;} register spec representing the variable */ + private final RegisterSpec spec; + + /** {@code non-null;} variable type (derived from {@code spec}) */ + private final CstType type; + + /** + * Constructs an instance. + * + * @param address {@code >= 0;} address + * @param disposition {@code non-null;} disposition of the local + * @param spec {@code non-null;} register spec representing + * the variable + */ + public Entry(int address, Disposition disposition, RegisterSpec spec) { + if (address < 0) { + throw new IllegalArgumentException("address < 0"); + } + + if (disposition == null) { + throw new NullPointerException("disposition == null"); + } + + try { + if (spec.getLocalItem() == null) { + throw new NullPointerException( + "spec.getLocalItem() == null"); + } + } catch (NullPointerException ex) { + // Elucidate the exception. + throw new NullPointerException("spec == null"); + } + + this.address = address; + this.disposition = disposition; + this.spec = spec; + this.type = CstType.intern(spec.getType()); + } + + /** {@inheritDoc} */ + public String toString() { + return Integer.toHexString(address) + " " + disposition + " " + + spec; + } + + /** {@inheritDoc} */ + public boolean equals(Object other) { + if (!(other instanceof Entry)) { + return false; + } + + return (compareTo((Entry) other) == 0); + } + + /** + * Compares by (in priority order) address, end then start + * disposition (variants of end are all consistered + * equivalent), and spec. + * + * @param other {@code non-null;} entry to compare to + * @return {@code -1..1;} standard result of comparison + */ + public int compareTo(Entry other) { + if (address < other.address) { + return -1; + } else if (address > other.address) { + return 1; + } + + boolean thisIsStart = isStart(); + boolean otherIsStart = other.isStart(); + + if (thisIsStart != otherIsStart) { + return thisIsStart ? 1 : -1; + } + + return spec.compareTo(other.spec); + } + + /** + * Gets the address. + * + * @return {@code >= 0;} the address + */ + public int getAddress() { + return address; + } + + /** + * Gets the disposition. + * + * @return {@code non-null;} the disposition + */ + public Disposition getDisposition() { + return disposition; + } + + /** + * Gets whether this is a local start. This is just shorthand for + * {@code getDisposition() == Disposition.START}. + * + * @return {@code true} iff this is a start + */ + public boolean isStart() { + return disposition == Disposition.START; + } + + /** + * Gets the variable name. + * + * @return {@code null-ok;} the variable name + */ + public CstUtf8 getName() { + return spec.getLocalItem().getName(); + } + + /** + * Gets the variable signature. + * + * @return {@code null-ok;} the variable signature + */ + public CstUtf8 getSignature() { + return spec.getLocalItem().getSignature(); + } + + /** + * Gets the variable's type. + * + * @return {@code non-null;} the type + */ + public CstType getType() { + return type; + } + + /** + * Gets the number of the register holding the variable. + * + * @return {@code >= 0;} the number of the register holding + * the variable + */ + public int getRegister() { + return spec.getReg(); + } + + /** + * Gets the RegisterSpec of the register holding the variable. + * + * @return {@code non-null;} RegisterSpec of the holding register. + */ + public RegisterSpec getRegisterSpec() { + return spec; + } + + /** + * Returns whether or not this instance matches the given spec. + * + * @param otherSpec {@code non-null;} the spec in question + * @return {@code true} iff this instance matches + * {@code spec} + */ + public boolean matches(RegisterSpec otherSpec) { + return spec.equalsUsingSimpleType(otherSpec); + } + + /** + * Returns whether or not this instance matches the spec in + * the given instance. + * + * @param other {@code non-null;} another entry + * @return {@code true} iff this instance's spec matches + * {@code other} + */ + public boolean matches(Entry other) { + return matches(other.spec); + } + + /** + * Returns an instance just like this one but with the disposition + * set as given. + * + * @param disposition {@code non-null;} the new disposition + * @return {@code non-null;} an appropriately-constructed instance + */ + public Entry withDisposition(Disposition disposition) { + if (disposition == this.disposition) { + return this; + } + + return new Entry(address, disposition, spec); + } + } + + /** + * Constructs an instance for the given method, based on the given + * block order and intermediate local information. + * + * @param insns {@code non-null;} instructions to convert + * @return {@code non-null;} the constructed list + */ + public static LocalList make(DalvInsnList insns) { + int sz = insns.size(); + + /* + * Go through the insn list, looking for all the local + * variable pseudoinstructions, splitting out LocalSnapshots + * into separate per-variable starts, adding explicit ends + * wherever a variable is replaced or moved, and collecting + * these and all the other local variable "activity" + * together into an output list (without the other insns). + * + * Note: As of this writing, this method won't be handed any + * insn lists that contain local ends, but I (danfuzz) expect + * that to change at some point, when we start feeding that + * info explicitly into the rop layer rather than only trying + * to infer it. So, given that expectation, this code is + * written to deal with them. + */ + + MakeState state = new MakeState(sz); + + for (int i = 0; i < sz; i++) { + DalvInsn insn = insns.get(i); + + if (insn instanceof LocalSnapshot) { + RegisterSpecSet snapshot = + ((LocalSnapshot) insn).getLocals(); + state.snapshot(insn.getAddress(), snapshot); + } else if (insn instanceof LocalStart) { + RegisterSpec local = ((LocalStart) insn).getLocal(); + state.startLocal(insn.getAddress(), local); + } else if (insn instanceof LocalEnd) { + RegisterSpec local = ((LocalEnd) insn).getLocal(); + state.endLocal(insn.getAddress(), local); + } + } + + LocalList result = state.finish(); + + if (DEBUG) { + debugVerify(result); + } + + return result; + } + + /** + * Debugging helper that verifies the constraint that a list doesn't + * contain any redundant local starts and that local ends that are + * due to replacements are properly annotated. + */ + private static void debugVerify(LocalList locals) { + try { + debugVerify0(locals); + } catch (RuntimeException ex) { + int sz = locals.size(); + for (int i = 0; i < sz; i++) { + System.err.println(locals.get(i)); + } + throw ex; + } + + } + + /** + * Helper for {@link #debugVerify} which does most of the work. + */ + private static void debugVerify0(LocalList locals) { + int sz = locals.size(); + Entry[] active = new Entry[65536]; + + for (int i = 0; i < sz; i++) { + Entry e = locals.get(i); + int reg = e.getRegister(); + + if (e.isStart()) { + Entry already = active[reg]; + + if ((already != null) && e.matches(already)) { + throw new RuntimeException("redundant start at " + + Integer.toHexString(e.getAddress()) + ": got " + + e + "; had " + already); + } + + active[reg] = e; + } else { + if (active[reg] == null) { + throw new RuntimeException("redundant end at " + + Integer.toHexString(e.getAddress())); + } + + int addr = e.getAddress(); + boolean foundStart = false; + + for (int j = i + 1; j < sz; j++) { + Entry test = locals.get(j); + if (test.getAddress() != addr) { + break; + } + if (test.getRegisterSpec().getReg() == reg) { + if (test.isStart()) { + if (e.getDisposition() + != Disposition.END_REPLACED) { + throw new RuntimeException( + "improperly marked end at " + + Integer.toHexString(addr)); + } + foundStart = true; + } else { + throw new RuntimeException( + "redundant end at " + + Integer.toHexString(addr)); + } + } + } + + if (!foundStart && + (e.getDisposition() == Disposition.END_REPLACED)) { + throw new RuntimeException( + "improper end replacement claim at " + + Integer.toHexString(addr)); + } + + active[reg] = null; + } + } + } + + /** + * Intermediate state when constructing a local list. + */ + public static class MakeState { + /** {@code non-null;} result being collected */ + private final ArrayList<Entry> result; + + /** + * {@code >= 0;} running count of nulled result entries, to help with + * sizing the final list + */ + private int nullResultCount; + + /** {@code null-ok;} current register mappings */ + private RegisterSpecSet regs; + + /** {@code null-ok;} result indices where local ends are stored */ + private int[] endIndices; + + /** {@code >= 0;} last address seen */ + private int lastAddress; + + /** + * Constructs an instance. + */ + public MakeState(int initialSize) { + result = new ArrayList<Entry>(initialSize); + nullResultCount = 0; + regs = null; + endIndices = null; + lastAddress = 0; + } + + /** + * Checks the address and other vitals as a prerequisite to + * further processing. + * + * @param address {@code >= 0;} address about to be processed + * @param reg {@code >= 0;} register number about to be processed + */ + private void aboutToProcess(int address, int reg) { + boolean first = (endIndices == null); + + if ((address == lastAddress) && !first) { + return; + } + + if (address < lastAddress) { + throw new RuntimeException("shouldn't happen"); + } + + if (first || (reg >= endIndices.length)) { + /* + * This is the first allocation of the state set and + * index array, or we need to grow. (The latter doesn't + * happen much; in fact, we have only ever observed + * it happening in test cases, never in "real" code.) + */ + int newSz = reg + 1; + RegisterSpecSet newRegs = new RegisterSpecSet(newSz); + int[] newEnds = new int[newSz]; + Arrays.fill(newEnds, -1); + + if (!first) { + newRegs.putAll(regs); + System.arraycopy(endIndices, 0, newEnds, 0, + endIndices.length); + } + + regs = newRegs; + endIndices = newEnds; + } + } + + /** + * Sets the local state at the given address to the given snapshot. + * The first call on this instance must be to this method, so that + * the register state can be properly sized. + * + * @param address {@code >= 0;} the address + * @param specs {@code non-null;} spec set representing the locals + */ + public void snapshot(int address, RegisterSpecSet specs) { + if (DEBUG) { + System.err.printf("%04x snapshot %s\n", address, specs); + } + + int sz = specs.getMaxSize(); + aboutToProcess(address, sz - 1); + + for (int i = 0; i < sz; i++) { + RegisterSpec oldSpec = regs.get(i); + RegisterSpec newSpec = filterSpec(specs.get(i)); + + if (oldSpec == null) { + if (newSpec != null) { + startLocal(address, newSpec); + } + } else if (newSpec == null) { + endLocal(address, oldSpec); + } else if (! newSpec.equalsUsingSimpleType(oldSpec)) { + endLocal(address, oldSpec); + startLocal(address, newSpec); + } + } + + if (DEBUG) { + System.err.printf("%04x snapshot done\n", address); + } + } + + /** + * Starts a local at the given address. + * + * @param address {@code >= 0;} the address + * @param startedLocal {@code non-null;} spec representing the + * started local + */ + public void startLocal(int address, RegisterSpec startedLocal) { + if (DEBUG) { + System.err.printf("%04x start %s\n", address, startedLocal); + } + + int regNum = startedLocal.getReg(); + + startedLocal = filterSpec(startedLocal); + aboutToProcess(address, regNum); + + RegisterSpec existingLocal = regs.get(regNum); + + if (startedLocal.equalsUsingSimpleType(existingLocal)) { + // Silently ignore a redundant start. + return; + } + + RegisterSpec movedLocal = regs.findMatchingLocal(startedLocal); + if (movedLocal != null) { + /* + * The same variable was moved from one register to another. + * So add an end for its old location. + */ + addOrUpdateEnd(address, Disposition.END_MOVED, movedLocal); + } + + int endAt = endIndices[regNum]; + + if (existingLocal != null) { + /* + * There is an existing (but non-matching) local. + * Add an explicit end for it. + */ + add(address, Disposition.END_REPLACED, existingLocal); + } else if (endAt >= 0) { + /* + * Look for an end local for the same register at the + * same address. If found, then update it or delete + * it, depending on whether or not it represents the + * same variable as the one being started. + */ + Entry endEntry = result.get(endAt); + if (endEntry.getAddress() == address) { + if (endEntry.matches(startedLocal)) { + /* + * There was already an end local for the same + * variable at the same address. This turns + * out to be superfluous, as we are starting + * up the exact same local. This situation can + * happen when a single local variable got + * somehow "split up" during intermediate + * processing. In any case, rather than represent + * the end-then-start, just remove the old end. + */ + result.set(endAt, null); + nullResultCount++; + regs.put(startedLocal); + endIndices[regNum] = -1; + return; + } else { + /* + * There was a different variable ended at the + * same address. Update it to indicate that + * it was ended due to a replacement (rather than + * ending for no particular reason). + */ + endEntry = endEntry.withDisposition( + Disposition.END_REPLACED); + result.set(endAt, endEntry); + } + } + } + + /* + * The code above didn't find and remove an unnecessary + * local end, so we now have to add one or more entries to + * the output to capture the transition. + */ + + /* + * If the local just below (in the register set at reg-1) + * is of category-2, then it is ended by this new start. + */ + if (regNum > 0) { + RegisterSpec justBelow = regs.get(regNum - 1); + if ((justBelow != null) && justBelow.isCategory2()) { + addOrUpdateEnd(address, + Disposition.END_CLOBBERED_BY_NEXT, + justBelow); + } + } + + /* + * Similarly, if this local is category-2, then the local + * just above (if any) is ended by the start now being + * emitted. + */ + if (startedLocal.isCategory2()) { + RegisterSpec justAbove = regs.get(regNum + 1); + if (justAbove != null) { + addOrUpdateEnd(address, + Disposition.END_CLOBBERED_BY_PREV, + justAbove); + } + } + + /* + * TODO: Add an end for the same local in a different reg, + * if any (that is, if the local migrates from vX to vY, + * we should note that as a local end in vX). + */ + + add(address, Disposition.START, startedLocal); + } + + /** + * Ends a local at the given address, using the disposition + * {@code END_SIMPLY}. + * + * @param address {@code >= 0;} the address + * @param endedLocal {@code non-null;} spec representing the + * local being ended + */ + public void endLocal(int address, RegisterSpec endedLocal) { + endLocal(address, endedLocal, Disposition.END_SIMPLY); + } + + /** + * Ends a local at the given address. + * + * @param address {@code >= 0;} the address + * @param endedLocal {@code non-null;} spec representing the + * local being ended + * @param disposition reason for the end + */ + public void endLocal(int address, RegisterSpec endedLocal, + Disposition disposition) { + if (DEBUG) { + System.err.printf("%04x end %s\n", address, endedLocal); + } + + int regNum = endedLocal.getReg(); + + endedLocal = filterSpec(endedLocal); + aboutToProcess(address, regNum); + + int endAt = endIndices[regNum]; + + if (endAt >= 0) { + /* + * The local in the given register is already ended. + * Silently return without adding anything to the result. + */ + return; + } + + // Check for start and end at the same address. + if (checkForEmptyRange(address, endedLocal)) { + return; + } + + add(address, disposition, endedLocal); + } + + /** + * Helper for {@link #endLocal}, which handles the cases where + * and end local is issued at the same address as a start local + * for the same register. If this case is found, then this + * method will remove the start (as the local was never actually + * active), update the {@link #endIndices} to be accurate, and + * if needed update the newly-active end to reflect an altered + * disposition. + * + * @param address {@code >= 0;} the address + * @param endedLocal {@code non-null;} spec representing the + * local being ended + * @return {@code true} iff this method found the case in question + * and adjusted things accordingly + */ + private boolean checkForEmptyRange(int address, + RegisterSpec endedLocal) { + int at = result.size() - 1; + Entry entry; + + // Look for a previous entry at the same address. + for (/*at*/; at >= 0; at--) { + entry = result.get(at); + + if (entry == null) { + continue; + } + + if (entry.getAddress() != address) { + // We didn't find any match at the same address. + return false; + } + + if (entry.matches(endedLocal)) { + break; + } + } + + /* + * In fact, we found that the endedLocal had started at the + * same address, so do all the requisite cleanup. + */ + + regs.remove(endedLocal); + result.set(at, null); + nullResultCount++; + + int regNum = endedLocal.getReg(); + boolean found = false; + entry = null; + + // Now look back further to update where the register ended. + for (at--; at >= 0; at--) { + entry = result.get(at); + + if (entry == null) { + continue; + } + + if (entry.getRegisterSpec().getReg() == regNum) { + found = true; + break; + } + } + + if (found) { + // We found an end for the same register. + endIndices[regNum] = at; + + if (entry.getAddress() == address) { + /* + * It's still the same address, so update the + * disposition. + */ + result.set(at, + entry.withDisposition(Disposition.END_SIMPLY)); + } + } + + return true; + } + + /** + * Converts a given spec into the form acceptable for use in a + * local list. This, in particular, transforms the "known + * null" type into simply {@code Object}. This method needs to + * be called for any spec that is on its way into a locals + * list. + * + * <p>This isn't necessarily the cleanest way to achieve the + * goal of not representing known nulls in a locals list, but + * it gets the job done.</p> + * + * @param orig {@code null-ok;} the original spec + * @return {@code null-ok;} an appropriately modified spec, or the + * original if nothing needs to be done + */ + private static RegisterSpec filterSpec(RegisterSpec orig) { + if ((orig != null) && (orig.getType() == Type.KNOWN_NULL)) { + return orig.withType(Type.OBJECT); + } + + return orig; + } + + /** + * Adds an entry to the result, updating the adjunct tables + * accordingly. + * + * @param address {@code >= 0;} the address + * @param disposition {@code non-null;} the disposition + * @param spec {@code non-null;} spec representing the local + */ + private void add(int address, Disposition disposition, + RegisterSpec spec) { + int regNum = spec.getReg(); + + result.add(new Entry(address, disposition, spec)); + + if (disposition == Disposition.START) { + regs.put(spec); + endIndices[regNum] = -1; + } else { + regs.remove(spec); + endIndices[regNum] = result.size() - 1; + } + } + + /** + * Adds or updates an end local (changing its disposition). If + * this would cause an empty range for a local, this instead + * removes the local entirely. + * + * @param address {@code >= 0;} the address + * @param disposition {@code non-null;} the disposition + * @param spec {@code non-null;} spec representing the local + */ + private void addOrUpdateEnd(int address, Disposition disposition, + RegisterSpec spec) { + if (disposition == Disposition.START) { + throw new RuntimeException("shouldn't happen"); + } + + int regNum = spec.getReg(); + int endAt = endIndices[regNum]; + + if (endAt >= 0) { + // There is a previous end. + Entry endEntry = result.get(endAt); + if ((endEntry.getAddress() == address) && + endEntry.getRegisterSpec().equals(spec)) { + /* + * The end is for the right address and variable, so + * update it. + */ + result.set(endAt, endEntry.withDisposition(disposition)); + regs.remove(spec); // TODO: Is this line superfluous? + return; + } + } + + endLocal(address, spec, disposition); + } + + /** + * Finishes processing altogether and gets the result. + * + * @return {@code non-null;} the result list + */ + public LocalList finish() { + aboutToProcess(Integer.MAX_VALUE, 0); + + int resultSz = result.size(); + int finalSz = resultSz - nullResultCount; + + if (finalSz == 0) { + return EMPTY; + } + + /* + * Collect an array of only the non-null entries, and then + * sort it to get a consistent order for everything: Local + * ends and starts for a given address could come in any + * order, but we want ends before starts as well as + * registers in order (within ends or starts). + */ + + Entry[] resultArr = new Entry[finalSz]; + + if (resultSz == finalSz) { + result.toArray(resultArr); + } else { + int at = 0; + for (Entry e : result) { + if (e != null) { + resultArr[at++] = e; + } + } + } + + Arrays.sort(resultArr); + + LocalList resultList = new LocalList(finalSz); + + for (int i = 0; i < finalSz; i++) { + resultList.set(i, resultArr[i]); + } + + resultList.setImmutable(); + return resultList; + } + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/LocalSnapshot.java b/dexgen/src/com/android/dexgen/dex/code/LocalSnapshot.java new file mode 100644 index 0000000..81b78c9 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/LocalSnapshot.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.RegisterSpec; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.code.RegisterSpecSet; +import com.android.dexgen.rop.code.SourcePosition; + +/** + * Pseudo-instruction which is used to hold a snapshot of the + * state of local variable name mappings that exists immediately after + * the instance in an instruction array. + */ +public final class LocalSnapshot extends ZeroSizeInsn { + /** {@code non-null;} local state associated with this instance */ + private final RegisterSpecSet locals; + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + * @param locals {@code non-null;} associated local variable state + */ + public LocalSnapshot(SourcePosition position, RegisterSpecSet locals) { + super(position); + + if (locals == null) { + throw new NullPointerException("locals == null"); + } + + this.locals = locals; + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisterOffset(int delta) { + return new LocalSnapshot(getPosition(), locals.withOffset(delta)); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new LocalSnapshot(getPosition(), locals); + } + + /** + * Gets the local state associated with this instance. + * + * @return {@code non-null;} the state + */ + public RegisterSpecSet getLocals() { + return locals; + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return locals.toString(); + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + int sz = locals.size(); + int max = locals.getMaxSize(); + StringBuffer sb = new StringBuffer(100 + sz * 40); + + sb.append("local-snapshot"); + + for (int i = 0; i < max; i++) { + RegisterSpec spec = locals.get(i); + if (spec != null) { + sb.append("\n "); + sb.append(LocalStart.localString(spec)); + } + } + + return sb.toString(); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/LocalStart.java b/dexgen/src/com/android/dexgen/dex/code/LocalStart.java new file mode 100644 index 0000000..ba426e8 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/LocalStart.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.RegisterSpec; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.code.SourcePosition; + +/** + * Pseudo-instruction which is used to introduce a new local variable. That + * is, an instance of this class in an instruction stream indicates that + * starting with the subsequent instruction, the indicated variable + * is bound. + */ +public final class LocalStart extends ZeroSizeInsn { + /** + * {@code non-null;} register spec representing the local variable introduced + * by this instance + */ + private final RegisterSpec local; + + /** + * Returns the local variable listing string for a single register spec. + * + * @param spec {@code non-null;} the spec to convert + * @return {@code non-null;} the string form + */ + public static String localString(RegisterSpec spec) { + return spec.regString() + ' ' + spec.getLocalItem().toString() + ": " + + spec.getTypeBearer().toHuman(); + } + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + * @param local {@code non-null;} register spec representing the local + * variable introduced by this instance + */ + public LocalStart(SourcePosition position, RegisterSpec local) { + super(position); + + if (local == null) { + throw new NullPointerException("local == null"); + } + + this.local = local; + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisterOffset(int delta) { + return new LocalStart(getPosition(), local.withOffset(delta)); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new LocalStart(getPosition(), local); + } + + /** + * Gets the register spec representing the local variable introduced + * by this instance. + * + * @return {@code non-null;} the register spec + */ + public RegisterSpec getLocal() { + return local; + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return local.toString(); + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + return "local-start " + localString(local); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/OddSpacer.java b/dexgen/src/com/android/dexgen/dex/code/OddSpacer.java new file mode 100644 index 0000000..8e2bab8 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/OddSpacer.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.code.SourcePosition; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Pseudo-instruction which either turns into a {@code nop} or + * nothingness, in order to make the subsequent instruction have an + * even address. This is used to align (subsequent) instructions that + * require it. + */ +public final class OddSpacer extends VariableSizeInsn { + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + */ + public OddSpacer(SourcePosition position) { + super(position, RegisterSpecList.EMPTY); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return (getAddress() & 1); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out) { + if (codeSize() != 0) { + out.writeShort(InsnFormat.codeUnit(DalvOps.NOP, 0)); + } + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new OddSpacer(getPosition()); + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return null; + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + if (codeSize() == 0) { + return null; + } + + return "nop // spacer"; + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/OutputCollector.java b/dexgen/src/com/android/dexgen/dex/code/OutputCollector.java new file mode 100644 index 0000000..7f970eb --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/OutputCollector.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import java.util.ArrayList; + +/** + * Destination for {@link DalvInsn} instances being output. This class + * receives and collects instructions in two pieces — a primary + * list and a suffix (generally consisting of adjunct data referred to + * by the primary list, such as switch case tables) — which it + * merges and emits back out in the form of a {@link DalvInsnList} + * instance. + */ +public final class OutputCollector { + /** + * {@code non-null;} the associated finisher (which holds the instruction + * list in-progress) + */ + private final OutputFinisher finisher; + + /** + * {@code null-ok;} suffix for the output, or {@code null} if the suffix + * has been appended to the main output (by {@link #appendSuffixToOutput}) + */ + private ArrayList<DalvInsn> suffix; + + /** + * Constructs an instance. + * + * @param initialCapacity {@code >= 0;} initial capacity of the output list + * @param suffixInitialCapacity {@code >= 0;} initial capacity of the output + * suffix + * @param regCount {@code >= 0;} register count for the method + */ + public OutputCollector(int initialCapacity, int suffixInitialCapacity, + int regCount) { + this.finisher = new OutputFinisher(initialCapacity, regCount); + this.suffix = new ArrayList<DalvInsn>(suffixInitialCapacity); + } + + /** + * Adds an instruction to the output. + * + * @param insn {@code non-null;} the instruction to add + */ + public void add(DalvInsn insn) { + finisher.add(insn); + } + + /** + * Reverses a branch which is buried a given number of instructions + * backward in the output. It is illegal to call this unless the + * indicated instruction really is a reversible branch. + * + * @param which how many instructions back to find the branch; + * {@code 0} is the most recently added instruction, + * {@code 1} is the instruction before that, etc. + * @param newTarget {@code non-null;} the new target for the reversed branch + */ + public void reverseBranch(int which, CodeAddress newTarget) { + finisher.reverseBranch(which, newTarget); + } + + /** + * Adds an instruction to the output suffix. + * + * @param insn {@code non-null;} the instruction to add + */ + public void addSuffix(DalvInsn insn) { + suffix.add(insn); + } + + /** + * Gets the results of all the calls on this instance, in the form of + * an {@link OutputFinisher}. + * + * @return {@code non-null;} the output finisher + * @throws UnsupportedOperationException if this method has + * already been called + */ + public OutputFinisher getFinisher() { + if (suffix == null) { + throw new UnsupportedOperationException("already processed"); + } + + appendSuffixToOutput(); + return finisher; + } + + /** + * Helper for {@link #getFinisher}, which appends the suffix to + * the primary output. + */ + private void appendSuffixToOutput() { + int size = suffix.size(); + + for (int i = 0; i < size; i++) { + finisher.add(suffix.get(i)); + } + + suffix = null; + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/OutputFinisher.java b/dexgen/src/com/android/dexgen/dex/code/OutputFinisher.java new file mode 100644 index 0000000..98c7e4e --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/OutputFinisher.java @@ -0,0 +1,737 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.LocalItem; +import com.android.dexgen.rop.code.RegisterSpec; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.code.RegisterSpecSet; +import com.android.dexgen.rop.code.SourcePosition; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstMemberRef; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.rop.type.Type; + +import java.util.ArrayList; +import java.util.HashSet; + +/** + * Processor for instruction lists, which takes a "first cut" of + * instruction selection as a basis and produces a "final cut" in the + * form of a {@link DalvInsnList} instance. + */ +public final class OutputFinisher { + /** + * {@code >= 0;} register count for the method, not including any extra + * "reserved" registers needed to translate "difficult" instructions + */ + private final int unreservedRegCount; + + /** {@code non-null;} the list of instructions, per se */ + private ArrayList<DalvInsn> insns; + + /** whether any instruction has position info */ + private boolean hasAnyPositionInfo; + + /** whether any instruction has local variable info */ + private boolean hasAnyLocalInfo; + + /** + * {@code >= 0;} the count of reserved registers (low-numbered + * registers used when expanding instructions that can't be + * represented simply); becomes valid after a call to {@link + * #massageInstructions} + */ + private int reservedCount; + + /** + * Constructs an instance. It initially contains no instructions. + * + * @param regCount {@code >= 0;} register count for the method + * @param initialCapacity {@code >= 0;} initial capacity of the instructions + * list + */ + public OutputFinisher(int initialCapacity, int regCount) { + this.unreservedRegCount = regCount; + this.insns = new ArrayList<DalvInsn>(initialCapacity); + this.reservedCount = -1; + this.hasAnyPositionInfo = false; + this.hasAnyLocalInfo = false; + } + + /** + * Returns whether any of the instructions added to this instance + * come with position info. + * + * @return whether any of the instructions added to this instance + * come with position info + */ + public boolean hasAnyPositionInfo() { + return hasAnyPositionInfo; + } + + /** + * Returns whether this instance has any local variable information. + * + * @return whether this instance has any local variable information + */ + public boolean hasAnyLocalInfo() { + return hasAnyLocalInfo; + } + + /** + * Helper for {@link #add} which scrutinizes a single + * instruction for local variable information. + * + * @param insn {@code non-null;} instruction to scrutinize + * @return {@code true} iff the instruction refers to any + * named locals + */ + private static boolean hasLocalInfo(DalvInsn insn) { + if (insn instanceof LocalSnapshot) { + RegisterSpecSet specs = ((LocalSnapshot) insn).getLocals(); + int size = specs.size(); + for (int i = 0; i < size; i++) { + if (hasLocalInfo(specs.get(i))) { + return true; + } + } + } else if (insn instanceof LocalStart) { + RegisterSpec spec = ((LocalStart) insn).getLocal(); + if (hasLocalInfo(spec)) { + return true; + } + } + + return false; + } + + /** + * Helper for {@link #hasAnyLocalInfo} which scrutinizes a single + * register spec. + * + * @param spec {@code non-null;} spec to scrutinize + * @return {@code true} iff the spec refers to any + * named locals + */ + private static boolean hasLocalInfo(RegisterSpec spec) { + return (spec != null) + && (spec.getLocalItem().getName() != null); + } + + /** + * Returns the set of all constants referred to by instructions added + * to this instance. + * + * @return {@code non-null;} the set of constants + */ + public HashSet<Constant> getAllConstants() { + HashSet<Constant> result = new HashSet<Constant>(20); + + for (DalvInsn insn : insns) { + addConstants(result, insn); + } + + return result; + } + + /** + * Helper for {@link #getAllConstants} which adds all the info for + * a single instruction. + * + * @param result {@code non-null;} result set to add to + * @param insn {@code non-null;} instruction to scrutinize + */ + private static void addConstants(HashSet<Constant> result, + DalvInsn insn) { + if (insn instanceof CstInsn) { + Constant cst = ((CstInsn) insn).getConstant(); + result.add(cst); + } else if (insn instanceof LocalSnapshot) { + RegisterSpecSet specs = ((LocalSnapshot) insn).getLocals(); + int size = specs.size(); + for (int i = 0; i < size; i++) { + addConstants(result, specs.get(i)); + } + } else if (insn instanceof LocalStart) { + RegisterSpec spec = ((LocalStart) insn).getLocal(); + addConstants(result, spec); + } + } + + /** + * Helper for {@link #getAllConstants} which adds all the info for + * a single {@code RegisterSpec}. + * + * @param result {@code non-null;} result set to add to + * @param spec {@code null-ok;} register spec to add + */ + private static void addConstants(HashSet<Constant> result, + RegisterSpec spec) { + if (spec == null) { + return; + } + + LocalItem local = spec.getLocalItem(); + CstUtf8 name = local.getName(); + CstUtf8 signature = local.getSignature(); + Type type = spec.getType(); + + if (type != Type.KNOWN_NULL) { + result.add(CstType.intern(type)); + } + + if (name != null) { + result.add(name); + } + + if (signature != null) { + result.add(signature); + } + } + + /** + * Adds an instruction to the output. + * + * @param insn {@code non-null;} the instruction to add + */ + public void add(DalvInsn insn) { + insns.add(insn); + updateInfo(insn); + } + + /** + * Inserts an instruction in the output at the given offset. + * + * @param at {@code >= 0;} what index to insert at + * @param insn {@code non-null;} the instruction to insert + */ + public void insert(int at, DalvInsn insn) { + insns.add(at, insn); + updateInfo(insn); + } + + /** + * Helper for {@link #add} and {@link #insert}, + * which updates the position and local info flags. + * + * @param insn {@code non-null;} an instruction that was just introduced + */ + private void updateInfo(DalvInsn insn) { + if (! hasAnyPositionInfo) { + SourcePosition pos = insn.getPosition(); + if (pos.getLine() >= 0) { + hasAnyPositionInfo = true; + } + } + + if (! hasAnyLocalInfo) { + if (hasLocalInfo(insn)) { + hasAnyLocalInfo = true; + } + } + } + + /** + * Reverses a branch which is buried a given number of instructions + * backward in the output. It is illegal to call this unless the + * indicated instruction really is a reversible branch. + * + * @param which how many instructions back to find the branch; + * {@code 0} is the most recently added instruction, + * {@code 1} is the instruction before that, etc. + * @param newTarget {@code non-null;} the new target for the reversed branch + */ + public void reverseBranch(int which, CodeAddress newTarget) { + int size = insns.size(); + int index = size - which - 1; + TargetInsn targetInsn; + + try { + targetInsn = (TargetInsn) insns.get(index); + } catch (IndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("too few instructions"); + } catch (ClassCastException ex) { + // Translate the exception. + throw new IllegalArgumentException("non-reversible instruction"); + } + + /* + * No need to call this.set(), since the format and other info + * are the same. + */ + insns.set(index, targetInsn.withNewTargetAndReversed(newTarget)); + } + + /** + * Assigns indices in all instructions that need them, using the + * given callback to perform lookups. This should be called before + * calling {@link #finishProcessingAndGetList}. + * + * @param callback {@code non-null;} callback object + */ + public void assignIndices(DalvCode.AssignIndicesCallback callback) { + for (DalvInsn insn : insns) { + if (insn instanceof CstInsn) { + assignIndices((CstInsn) insn, callback); + } + } + } + + /** + * Helper for {@link #assignIndices} which does assignment for one + * instruction. + * + * @param insn {@code non-null;} the instruction + * @param callback {@code non-null;} the callback + */ + private static void assignIndices(CstInsn insn, + DalvCode.AssignIndicesCallback callback) { + Constant cst = insn.getConstant(); + int index = callback.getIndex(cst); + + if (index >= 0) { + insn.setIndex(index); + } + + if (cst instanceof CstMemberRef) { + CstMemberRef member = (CstMemberRef) cst; + CstType definer = member.getDefiningClass(); + index = callback.getIndex(definer); + if (index >= 0) { + insn.setClassIndex(index); + } + } + } + + /** + * Does final processing on this instance and gets the output as + * a {@link DalvInsnList}. Final processing consists of: + * + * <ul> + * <li>optionally renumbering registers (to make room as needed for + * expanded instructions)</li> + * <li>picking a final opcode for each instruction</li> + * <li>rewriting instructions, because of register number, + * constant pool index, or branch target size issues</li> + * <li>assigning final addresses</li> + * </ul> + * + * <p><b>Note:</b> This method may only be called once per instance + * of this class.</p> + * + * @return {@code non-null;} the output list + * @throws UnsupportedOperationException if this method has + * already been called + */ + public DalvInsnList finishProcessingAndGetList() { + if (reservedCount >= 0) { + throw new UnsupportedOperationException("already processed"); + } + + InsnFormat[] formats = makeFormatsArray(); + reserveRegisters(formats); + massageInstructions(formats); + assignAddressesAndFixBranches(); + + return DalvInsnList.makeImmutable(insns, + reservedCount + unreservedRegCount); + } + + /** + * Helper for {@link #finishProcessingAndGetList}, which extracts + * the format out of each instruction into a separate array, to be + * further manipulated as things progress. + * + * @return {@code non-null;} the array of formats + */ + private InsnFormat[] makeFormatsArray() { + int size = insns.size(); + InsnFormat[] result = new InsnFormat[size]; + + for (int i = 0; i < size; i++) { + result[i] = insns.get(i).getOpcode().getFormat(); + } + + return result; + } + + /** + * Helper for {@link #finishProcessingAndGetList}, which figures + * out how many reserved registers are required and then reserving + * them. It also updates the given {@code formats} array so + * as to avoid extra work when constructing the massaged + * instruction list. + * + * @param formats {@code non-null;} array of per-instruction format selections + */ + private void reserveRegisters(InsnFormat[] formats) { + int oldReservedCount = (reservedCount < 0) ? 0 : reservedCount; + + /* + * Call calculateReservedCount() and then perform register + * reservation, repeatedly until no new reservations happen. + */ + for (;;) { + int newReservedCount = calculateReservedCount(formats); + if (oldReservedCount >= newReservedCount) { + break; + } + + int reservedDifference = newReservedCount - oldReservedCount; + int size = insns.size(); + + for (int i = 0; i < size; i++) { + /* + * CodeAddress instance identity is used to link + * TargetInsns to their targets, so it is + * inappropriate to make replacements, and they don't + * have registers in any case. Hence, the instanceof + * test below. + */ + DalvInsn insn = insns.get(i); + if (!(insn instanceof CodeAddress)) { + /* + * No need to call this.set() since the format and + * other info are the same. + */ + insns.set(i, insn.withRegisterOffset(reservedDifference)); + } + } + + oldReservedCount = newReservedCount; + } + + reservedCount = oldReservedCount; + } + + /** + * Helper for {@link #reserveRegisters}, which does one + * pass over the instructions, calculating the number of + * registers that need to be reserved. It also updates the + * {@code formats} list to help avoid extra work in future + * register reservation passes. + * + * @param formats {@code non-null;} array of per-instruction format selections + * @return {@code >= 0;} the count of reserved registers + */ + private int calculateReservedCount(InsnFormat[] formats) { + int size = insns.size(); + + /* + * Potential new value of reservedCount, which gets updated in the + * following loop. It starts out with the existing reservedCount + * and gets increased if it turns out that additional registers + * need to be reserved. + */ + int newReservedCount = reservedCount; + + for (int i = 0; i < size; i++) { + DalvInsn insn = insns.get(i); + InsnFormat originalFormat = formats[i]; + InsnFormat newFormat = findFormatForInsn(insn, originalFormat); + + if (originalFormat == newFormat) { + continue; + } + + if (newFormat == null) { + /* + * The instruction will need to be expanded, so reserve + * registers for it. + */ + int reserve = insn.getMinimumRegisterRequirement(); + if (reserve > newReservedCount) { + newReservedCount = reserve; + } + } + + formats[i] = newFormat; + } + + return newReservedCount; + } + + /** + * Attempts to fit the given instruction into a format, returning + * either a format that the instruction fits into or {@code null} + * to indicate that the instruction will need to be expanded. This + * fitting process starts with the given format as a first "best + * guess" and then pessimizes from there if necessary. + * + * @param insn {@code non-null;} the instruction in question + * @param format {@code null-ok;} the current guess as to the best instruction + * format to use; {@code null} means that no simple format fits + * @return {@code null-ok;} a possibly-different format, which is either a + * good fit or {@code null} to indicate that no simple format + * fits + */ + private InsnFormat findFormatForInsn(DalvInsn insn, InsnFormat format) { + if (format == null) { + // The instruction is already known not to fit any simple format. + return format; + } + + if (format.isCompatible(insn)) { + // The instruction already fits in the current best-known format. + return format; + } + + Dop dop = insn.getOpcode(); + int family = dop.getFamily(); + + for (;;) { + format = format.nextUp(); + if ((format == null) || + (format.isCompatible(insn) && + (Dops.getOrNull(family, format) != null))) { + break; + } + } + + return format; + } + + /** + * Helper for {@link #finishProcessingAndGetList}, which goes + * through each instruction in the output, making sure its opcode + * can accomodate its arguments. In cases where the opcode is + * unable to do so, this replaces the instruction with a larger + * instruction with identical semantics that <i>will</i> work. + * + * <p>This method may also reserve a number of low-numbered + * registers, renumbering the instructions' original registers, in + * order to have register space available in which to move + * very-high registers when expanding instructions into + * multi-instruction sequences. This expansion is done when no + * simple instruction format can be found for a given instruction that + * is able to accomodate that instruction's registers.</p> + * + * <p>This method ignores issues of branch target size, since + * final addresses aren't known at the point that this method is + * called.</p> + * + * @param formats {@code non-null;} array of per-instruction format selections + */ + private void massageInstructions(InsnFormat[] formats) { + if (reservedCount == 0) { + /* + * The easy common case: No registers were reserved, so we + * merely need to replace any instructions whose format changed + * during the reservation pass, but all instructions will stay + * at their original indices, and the instruction list doesn't + * grow. + */ + int size = insns.size(); + + for (int i = 0; i < size; i++) { + DalvInsn insn = insns.get(i); + Dop dop = insn.getOpcode(); + InsnFormat format = formats[i]; + + if (format != dop.getFormat()) { + dop = Dops.getOrNull(dop.getFamily(), format); + insns.set(i, insn.withOpcode(dop)); + } + } + } else { + /* + * The difficult uncommon case: Some instructions have to be + * expanded to deal with high registers. + */ + insns = performExpansion(formats); + } + } + + /** + * Helper for {@link #massageInstructions}, which constructs a + * replacement list, where each {link DalvInsn} instance that + * couldn't be represented simply (due to register representation + * problems) is expanded into a series of instances that together + * perform the proper function. + * + * @param formats {@code non-null;} array of per-instruction format selections + * @return {@code non-null;} the replacement list + */ + private ArrayList<DalvInsn> performExpansion(InsnFormat[] formats) { + int size = insns.size(); + ArrayList<DalvInsn> result = new ArrayList<DalvInsn>(size * 2); + + for (int i = 0; i < size; i++) { + DalvInsn insn = insns.get(i); + Dop dop = insn.getOpcode(); + InsnFormat originalFormat = dop.getFormat(); + InsnFormat currentFormat = formats[i]; + DalvInsn prefix; + DalvInsn suffix; + + if (currentFormat != null) { + // No expansion is necessary. + prefix = null; + suffix = null; + } else { + // Expansion is required. + prefix = insn.hrPrefix(); + suffix = insn.hrSuffix(); + + /* + * Get the initial guess as to the hr version, but then + * let findFormatForInsn() pick a better format, if any. + */ + insn = insn.hrVersion(); + originalFormat = insn.getOpcode().getFormat(); + currentFormat = findFormatForInsn(insn, originalFormat); + } + + if (prefix != null) { + result.add(prefix); + } + + if (currentFormat != originalFormat) { + dop = Dops.getOrNull(dop.getFamily(), currentFormat); + insn = insn.withOpcode(dop); + } + result.add(insn); + + if (suffix != null) { + result.add(suffix); + } + } + + return result; + } + + /** + * Helper for {@link #finishProcessingAndGetList}, which assigns + * addresses to each instruction, possibly rewriting branches to + * fix ones that wouldn't otherwise be able to reach their + * targets. + */ + private void assignAddressesAndFixBranches() { + for (;;) { + assignAddresses(); + if (!fixBranches()) { + break; + } + } + } + + /** + * Helper for {@link #assignAddressesAndFixBranches}, which + * assigns an address to each instruction, in order. + */ + private void assignAddresses() { + int address = 0; + int size = insns.size(); + + for (int i = 0; i < size; i++) { + DalvInsn insn = insns.get(i); + insn.setAddress(address); + address += insn.codeSize(); + } + } + + /** + * Helper for {@link #assignAddressesAndFixBranches}, which checks + * the branch target size requirement of each branch instruction + * to make sure it fits. For instructions that don't fit, this + * rewrites them to use a {@code goto} of some sort. In the + * case of a conditional branch that doesn't fit, the sense of the + * test is reversed in order to branch around a {@code goto} + * to the original target. + * + * @return whether any branches had to be fixed + */ + private boolean fixBranches() { + int size = insns.size(); + boolean anyFixed = false; + + for (int i = 0; i < size; i++) { + DalvInsn insn = insns.get(i); + if (!(insn instanceof TargetInsn)) { + // This loop only needs to inspect TargetInsns. + continue; + } + + Dop dop = insn.getOpcode(); + InsnFormat format = dop.getFormat(); + TargetInsn target = (TargetInsn) insn; + + if (format.branchFits(target)) { + continue; + } + + if (dop.getFamily() == DalvOps.GOTO) { + // It is a goto; widen it if possible. + InsnFormat newFormat = findFormatForInsn(insn, format); + if (newFormat == null) { + /* + * The branch is already maximally large. This should + * only be possible if a method somehow manages to have + * more than 2^31 code units. + */ + throw new UnsupportedOperationException("method too long"); + } + dop = Dops.getOrNull(dop.getFamily(), newFormat); + insn = insn.withOpcode(dop); + insns.set(i, insn); + } else { + /* + * It is a conditional: Reverse its sense, and arrange for + * it to branch around an absolute goto to the original + * branch target. + * + * Note: An invariant of the list being processed is + * that every TargetInsn is followed by a CodeAddress. + * Hence, it is always safe to get the next element + * after a TargetInsn and cast it to CodeAddress, as + * is happening a few lines down. + * + * Also note: Size gets incremented by one here, as we + * have -- in the net -- added one additional element + * to the list, so we increment i to match. The added + * and changed elements will be inspected by a repeat + * call to this method after this invocation returns. + */ + CodeAddress newTarget; + try { + newTarget = (CodeAddress) insns.get(i + 1); + } catch (IndexOutOfBoundsException ex) { + // The TargetInsn / CodeAddress invariant was violated. + throw new IllegalStateException( + "unpaired TargetInsn (dangling)"); + } catch (ClassCastException ex) { + // The TargetInsn / CodeAddress invariant was violated. + throw new IllegalStateException("unpaired TargetInsn"); + } + TargetInsn gotoInsn = + new TargetInsn(Dops.GOTO, target.getPosition(), + RegisterSpecList.EMPTY, target.getTarget()); + insns.set(i, gotoInsn); + insns.add(i, target.withNewTargetAndReversed(newTarget)); + size++; + i++; + } + + anyFixed = true; + } + + return anyFixed; + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/PositionList.java b/dexgen/src/com/android/dexgen/dex/code/PositionList.java new file mode 100644 index 0000000..8b52f26 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/PositionList.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.SourcePosition; +import com.android.dexgen.util.FixedSizeList; + +/** + * List of source position entries. This class includes a utility + * method to extract an instance out of a {@link DalvInsnList}. + */ +public final class PositionList extends FixedSizeList { + /** {@code non-null;} empty instance */ + public static final PositionList EMPTY = new PositionList(0); + + /** + * constant for {@link #make} to indicate that no actual position + * information should be returned + */ + public static final int NONE = 1; + + /** + * constant for {@link #make} to indicate that only line number + * transitions should be returned + */ + public static final int LINES = 2; + + /** + * constant for {@link #make} to indicate that only "important" position + * information should be returned. This includes block starts and + * instructions that might throw. + */ + public static final int IMPORTANT = 3; + + /** + * Extracts and returns the source position information out of an + * instruction list. + * + * @param insns {@code non-null;} instructions to convert + * @param howMuch how much information should be included; one of the + * static constants defined by this class + * @return {@code non-null;} the positions list + */ + public static PositionList make(DalvInsnList insns, int howMuch) { + switch (howMuch) { + case NONE: { + return EMPTY; + } + case LINES: + case IMPORTANT: { + // Valid. + break; + } + default: { + throw new IllegalArgumentException("bogus howMuch"); + } + } + + SourcePosition noInfo = SourcePosition.NO_INFO; + SourcePosition cur = noInfo; + int sz = insns.size(); + PositionList.Entry[] arr = new PositionList.Entry[sz]; + boolean lastWasTarget = false; + int at = 0; + + for (int i = 0; i < sz; i++) { + DalvInsn insn = insns.get(i); + + if (insn instanceof CodeAddress) { + lastWasTarget = true;; + continue; + } + + SourcePosition pos = insn.getPosition(); + + if (pos.equals(noInfo) || pos.sameLine(cur)) { + continue; + } + + if ((howMuch == IMPORTANT) && !lastWasTarget) { + continue; + } + + cur = pos; + arr[at] = new PositionList.Entry(insn.getAddress(), pos); + at++; + + lastWasTarget = false; + } + + PositionList result = new PositionList(at); + for (int i = 0; i < at; i++) { + result.set(i, arr[i]); + } + + result.setImmutable(); + return result; + } + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size {@code >= 0;} the size of the list + */ + public PositionList(int size) { + super(size); + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public Entry get(int n) { + return (Entry) get0(n); + } + + /** + * Sets the entry at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param entry {@code non-null;} the entry to set at {@code n} + */ + public void set(int n, Entry entry) { + set0(n, entry); + } + + /** + * Entry in a position list. + */ + public static class Entry { + /** {@code >= 0;} address of this entry */ + private final int address; + + /** {@code non-null;} corresponding source position information */ + private final SourcePosition position; + + /** + * Constructs an instance. + * + * @param address {@code >= 0;} address of this entry + * @param position {@code non-null;} corresponding source position information + */ + public Entry (int address, SourcePosition position) { + if (address < 0) { + throw new IllegalArgumentException("address < 0"); + } + + if (position == null) { + throw new NullPointerException("position == null"); + } + + this.address = address; + this.position = position; + } + + /** + * Gets the address. + * + * @return {@code >= 0;} the address + */ + public int getAddress() { + return address; + } + + /** + * Gets the source position information. + * + * @return {@code non-null;} the position information + */ + public SourcePosition getPosition() { + return position; + } + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/RopToDop.java b/dexgen/src/com/android/dexgen/dex/code/RopToDop.java new file mode 100644 index 0000000..03d1de8 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/RopToDop.java @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.Insn; +import com.android.dexgen.rop.code.RegOps; +import com.android.dexgen.rop.code.RegisterSpec; +import com.android.dexgen.rop.code.Rop; +import com.android.dexgen.rop.code.Rops; +import com.android.dexgen.rop.code.ThrowingCstInsn; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstFieldRef; +import com.android.dexgen.rop.cst.CstString; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.type.Type; + +import java.util.HashMap; + +/** + * Translator from rop-level {@link Insn} instances to corresponding + * {@link Dop} instances. + */ +public final class RopToDop { + /** {@code non-null;} map from all the common rops to dalvik opcodes */ + private static final HashMap<Rop, Dop> MAP; + + /** + * This class is uninstantiable. + */ + private RopToDop() { + // This space intentionally left blank. + } + + static { + /* + * Note: The choices made here are to pick the optimistically + * smallest Dalvik opcode, and leave it to later processing to + * pessimize. + */ + MAP = new HashMap<Rop, Dop>(400); + MAP.put(Rops.NOP, Dops.NOP); + MAP.put(Rops.MOVE_INT, Dops.MOVE); + MAP.put(Rops.MOVE_LONG, Dops.MOVE_WIDE); + MAP.put(Rops.MOVE_FLOAT, Dops.MOVE); + MAP.put(Rops.MOVE_DOUBLE, Dops.MOVE_WIDE); + MAP.put(Rops.MOVE_OBJECT, Dops.MOVE_OBJECT); + MAP.put(Rops.MOVE_PARAM_INT, Dops.MOVE); + MAP.put(Rops.MOVE_PARAM_LONG, Dops.MOVE_WIDE); + MAP.put(Rops.MOVE_PARAM_FLOAT, Dops.MOVE); + MAP.put(Rops.MOVE_PARAM_DOUBLE, Dops.MOVE_WIDE); + MAP.put(Rops.MOVE_PARAM_OBJECT, Dops.MOVE_OBJECT); + + /* + * Note: No entry for MOVE_EXCEPTION, since it varies by + * exception type. (That is, there is no unique instance to + * add to the map.) + */ + + MAP.put(Rops.CONST_INT, Dops.CONST_4); + MAP.put(Rops.CONST_LONG, Dops.CONST_WIDE_16); + MAP.put(Rops.CONST_FLOAT, Dops.CONST_4); + MAP.put(Rops.CONST_DOUBLE, Dops.CONST_WIDE_16); + + /* + * Note: No entry for CONST_OBJECT, since it needs to turn + * into either CONST_STRING or CONST_CLASS. + */ + + /* + * TODO: I think the only case of this is for null, and + * const/4 should cover that. + */ + MAP.put(Rops.CONST_OBJECT_NOTHROW, Dops.CONST_4); + + MAP.put(Rops.GOTO, Dops.GOTO); + MAP.put(Rops.IF_EQZ_INT, Dops.IF_EQZ); + MAP.put(Rops.IF_NEZ_INT, Dops.IF_NEZ); + MAP.put(Rops.IF_LTZ_INT, Dops.IF_LTZ); + MAP.put(Rops.IF_GEZ_INT, Dops.IF_GEZ); + MAP.put(Rops.IF_LEZ_INT, Dops.IF_LEZ); + MAP.put(Rops.IF_GTZ_INT, Dops.IF_GTZ); + MAP.put(Rops.IF_EQZ_OBJECT, Dops.IF_EQZ); + MAP.put(Rops.IF_NEZ_OBJECT, Dops.IF_NEZ); + MAP.put(Rops.IF_EQ_INT, Dops.IF_EQ); + MAP.put(Rops.IF_NE_INT, Dops.IF_NE); + MAP.put(Rops.IF_LT_INT, Dops.IF_LT); + MAP.put(Rops.IF_GE_INT, Dops.IF_GE); + MAP.put(Rops.IF_LE_INT, Dops.IF_LE); + MAP.put(Rops.IF_GT_INT, Dops.IF_GT); + MAP.put(Rops.IF_EQ_OBJECT, Dops.IF_EQ); + MAP.put(Rops.IF_NE_OBJECT, Dops.IF_NE); + MAP.put(Rops.SWITCH, Dops.SPARSE_SWITCH); + MAP.put(Rops.ADD_INT, Dops.ADD_INT_2ADDR); + MAP.put(Rops.ADD_LONG, Dops.ADD_LONG_2ADDR); + MAP.put(Rops.ADD_FLOAT, Dops.ADD_FLOAT_2ADDR); + MAP.put(Rops.ADD_DOUBLE, Dops.ADD_DOUBLE_2ADDR); + MAP.put(Rops.SUB_INT, Dops.SUB_INT_2ADDR); + MAP.put(Rops.SUB_LONG, Dops.SUB_LONG_2ADDR); + MAP.put(Rops.SUB_FLOAT, Dops.SUB_FLOAT_2ADDR); + MAP.put(Rops.SUB_DOUBLE, Dops.SUB_DOUBLE_2ADDR); + MAP.put(Rops.MUL_INT, Dops.MUL_INT_2ADDR); + MAP.put(Rops.MUL_LONG, Dops.MUL_LONG_2ADDR); + MAP.put(Rops.MUL_FLOAT, Dops.MUL_FLOAT_2ADDR); + MAP.put(Rops.MUL_DOUBLE, Dops.MUL_DOUBLE_2ADDR); + MAP.put(Rops.DIV_INT, Dops.DIV_INT_2ADDR); + MAP.put(Rops.DIV_LONG, Dops.DIV_LONG_2ADDR); + MAP.put(Rops.DIV_FLOAT, Dops.DIV_FLOAT_2ADDR); + MAP.put(Rops.DIV_DOUBLE, Dops.DIV_DOUBLE_2ADDR); + MAP.put(Rops.REM_INT, Dops.REM_INT_2ADDR); + MAP.put(Rops.REM_LONG, Dops.REM_LONG_2ADDR); + MAP.put(Rops.REM_FLOAT, Dops.REM_FLOAT_2ADDR); + MAP.put(Rops.REM_DOUBLE, Dops.REM_DOUBLE_2ADDR); + MAP.put(Rops.NEG_INT, Dops.NEG_INT); + MAP.put(Rops.NEG_LONG, Dops.NEG_LONG); + MAP.put(Rops.NEG_FLOAT, Dops.NEG_FLOAT); + MAP.put(Rops.NEG_DOUBLE, Dops.NEG_DOUBLE); + MAP.put(Rops.AND_INT, Dops.AND_INT_2ADDR); + MAP.put(Rops.AND_LONG, Dops.AND_LONG_2ADDR); + MAP.put(Rops.OR_INT, Dops.OR_INT_2ADDR); + MAP.put(Rops.OR_LONG, Dops.OR_LONG_2ADDR); + MAP.put(Rops.XOR_INT, Dops.XOR_INT_2ADDR); + MAP.put(Rops.XOR_LONG, Dops.XOR_LONG_2ADDR); + MAP.put(Rops.SHL_INT, Dops.SHL_INT_2ADDR); + MAP.put(Rops.SHL_LONG, Dops.SHL_LONG_2ADDR); + MAP.put(Rops.SHR_INT, Dops.SHR_INT_2ADDR); + MAP.put(Rops.SHR_LONG, Dops.SHR_LONG_2ADDR); + MAP.put(Rops.USHR_INT, Dops.USHR_INT_2ADDR); + MAP.put(Rops.USHR_LONG, Dops.USHR_LONG_2ADDR); + MAP.put(Rops.NOT_INT, Dops.NOT_INT); + MAP.put(Rops.NOT_LONG, Dops.NOT_LONG); + + MAP.put(Rops.ADD_CONST_INT, Dops.ADD_INT_LIT8); + // Note: No dalvik ops for other types of add_const. + + /* + * Note: No dalvik ops for any type of sub_const; there's a + * *reverse* sub (constant - reg) for ints, though, but that + * should end up getting handled at optimization time. + */ + + MAP.put(Rops.MUL_CONST_INT, Dops.MUL_INT_LIT8); + // Note: No dalvik ops for other types of mul_const. + + MAP.put(Rops.DIV_CONST_INT, Dops.DIV_INT_LIT8); + // Note: No dalvik ops for other types of div_const. + + MAP.put(Rops.REM_CONST_INT, Dops.REM_INT_LIT8); + // Note: No dalvik ops for other types of rem_const. + + MAP.put(Rops.AND_CONST_INT, Dops.AND_INT_LIT8); + // Note: No dalvik op for and_const_long. + + MAP.put(Rops.OR_CONST_INT, Dops.OR_INT_LIT8); + // Note: No dalvik op for or_const_long. + + MAP.put(Rops.XOR_CONST_INT, Dops.XOR_INT_LIT8); + // Note: No dalvik op for xor_const_long. + + MAP.put(Rops.SHL_CONST_INT, Dops.SHL_INT_LIT8); + // Note: No dalvik op for shl_const_long. + + MAP.put(Rops.SHR_CONST_INT, Dops.SHR_INT_LIT8); + // Note: No dalvik op for shr_const_long. + + MAP.put(Rops.USHR_CONST_INT, Dops.USHR_INT_LIT8); + // Note: No dalvik op for shr_const_long. + + MAP.put(Rops.CMPL_LONG, Dops.CMP_LONG); + MAP.put(Rops.CMPL_FLOAT, Dops.CMPL_FLOAT); + MAP.put(Rops.CMPL_DOUBLE, Dops.CMPL_DOUBLE); + MAP.put(Rops.CMPG_FLOAT, Dops.CMPG_FLOAT); + MAP.put(Rops.CMPG_DOUBLE, Dops.CMPG_DOUBLE); + MAP.put(Rops.CONV_L2I, Dops.LONG_TO_INT); + MAP.put(Rops.CONV_F2I, Dops.FLOAT_TO_INT); + MAP.put(Rops.CONV_D2I, Dops.DOUBLE_TO_INT); + MAP.put(Rops.CONV_I2L, Dops.INT_TO_LONG); + MAP.put(Rops.CONV_F2L, Dops.FLOAT_TO_LONG); + MAP.put(Rops.CONV_D2L, Dops.DOUBLE_TO_LONG); + MAP.put(Rops.CONV_I2F, Dops.INT_TO_FLOAT); + MAP.put(Rops.CONV_L2F, Dops.LONG_TO_FLOAT); + MAP.put(Rops.CONV_D2F, Dops.DOUBLE_TO_FLOAT); + MAP.put(Rops.CONV_I2D, Dops.INT_TO_DOUBLE); + MAP.put(Rops.CONV_L2D, Dops.LONG_TO_DOUBLE); + MAP.put(Rops.CONV_F2D, Dops.FLOAT_TO_DOUBLE); + MAP.put(Rops.TO_BYTE, Dops.INT_TO_BYTE); + MAP.put(Rops.TO_CHAR, Dops.INT_TO_CHAR); + MAP.put(Rops.TO_SHORT, Dops.INT_TO_SHORT); + MAP.put(Rops.RETURN_VOID, Dops.RETURN_VOID); + MAP.put(Rops.RETURN_INT, Dops.RETURN); + MAP.put(Rops.RETURN_LONG, Dops.RETURN_WIDE); + MAP.put(Rops.RETURN_FLOAT, Dops.RETURN); + MAP.put(Rops.RETURN_DOUBLE, Dops.RETURN_WIDE); + MAP.put(Rops.RETURN_OBJECT, Dops.RETURN_OBJECT); + MAP.put(Rops.ARRAY_LENGTH, Dops.ARRAY_LENGTH); + MAP.put(Rops.THROW, Dops.THROW); + MAP.put(Rops.MONITOR_ENTER, Dops.MONITOR_ENTER); + MAP.put(Rops.MONITOR_EXIT, Dops.MONITOR_EXIT); + MAP.put(Rops.AGET_INT, Dops.AGET); + MAP.put(Rops.AGET_LONG, Dops.AGET_WIDE); + MAP.put(Rops.AGET_FLOAT, Dops.AGET); + MAP.put(Rops.AGET_DOUBLE, Dops.AGET_WIDE); + MAP.put(Rops.AGET_OBJECT, Dops.AGET_OBJECT); + MAP.put(Rops.AGET_BOOLEAN, Dops.AGET_BOOLEAN); + MAP.put(Rops.AGET_BYTE, Dops.AGET_BYTE); + MAP.put(Rops.AGET_CHAR, Dops.AGET_CHAR); + MAP.put(Rops.AGET_SHORT, Dops.AGET_SHORT); + MAP.put(Rops.APUT_INT, Dops.APUT); + MAP.put(Rops.APUT_LONG, Dops.APUT_WIDE); + MAP.put(Rops.APUT_FLOAT, Dops.APUT); + MAP.put(Rops.APUT_DOUBLE, Dops.APUT_WIDE); + MAP.put(Rops.APUT_OBJECT, Dops.APUT_OBJECT); + MAP.put(Rops.APUT_BOOLEAN, Dops.APUT_BOOLEAN); + MAP.put(Rops.APUT_BYTE, Dops.APUT_BYTE); + MAP.put(Rops.APUT_CHAR, Dops.APUT_CHAR); + MAP.put(Rops.APUT_SHORT, Dops.APUT_SHORT); + MAP.put(Rops.NEW_INSTANCE, Dops.NEW_INSTANCE); + MAP.put(Rops.CHECK_CAST, Dops.CHECK_CAST); + MAP.put(Rops.INSTANCE_OF, Dops.INSTANCE_OF); + + MAP.put(Rops.GET_FIELD_LONG, Dops.IGET_WIDE); + MAP.put(Rops.GET_FIELD_FLOAT, Dops.IGET); + MAP.put(Rops.GET_FIELD_DOUBLE, Dops.IGET_WIDE); + MAP.put(Rops.GET_FIELD_OBJECT, Dops.IGET_OBJECT); + /* + * Note: No map entries for get_field_* for non-long integral types, + * since they need to be handled specially (see dopFor() below). + */ + + MAP.put(Rops.GET_STATIC_LONG, Dops.SGET_WIDE); + MAP.put(Rops.GET_STATIC_FLOAT, Dops.SGET); + MAP.put(Rops.GET_STATIC_DOUBLE, Dops.SGET_WIDE); + MAP.put(Rops.GET_STATIC_OBJECT, Dops.SGET_OBJECT); + /* + * Note: No map entries for get_static* for non-long integral types, + * since they need to be handled specially (see dopFor() below). + */ + + MAP.put(Rops.PUT_FIELD_LONG, Dops.IPUT_WIDE); + MAP.put(Rops.PUT_FIELD_FLOAT, Dops.IPUT); + MAP.put(Rops.PUT_FIELD_DOUBLE, Dops.IPUT_WIDE); + MAP.put(Rops.PUT_FIELD_OBJECT, Dops.IPUT_OBJECT); + /* + * Note: No map entries for put_field_* for non-long integral types, + * since they need to be handled specially (see dopFor() below). + */ + + MAP.put(Rops.PUT_STATIC_LONG, Dops.SPUT_WIDE); + MAP.put(Rops.PUT_STATIC_FLOAT, Dops.SPUT); + MAP.put(Rops.PUT_STATIC_DOUBLE, Dops.SPUT_WIDE); + MAP.put(Rops.PUT_STATIC_OBJECT, Dops.SPUT_OBJECT); + /* + * Note: No map entries for put_static* for non-long integral types, + * since they need to be handled specially (see dopFor() below). + */ + + /* + * Note: No map entries for invoke*, new_array, and + * filled_new_array, since they need to be handled specially + * (see dopFor() below). + */ + } + + /** + * Returns the dalvik opcode appropriate for the given register-based + * instruction. + * + * @param insn {@code non-null;} the original instruction + * @return the corresponding dalvik opcode; one of the constants in + * {@link Dops} + */ + public static Dop dopFor(Insn insn) { + Rop rop = insn.getOpcode(); + + /* + * First, just try looking up the rop in the MAP of easy + * cases. + */ + Dop result = MAP.get(rop); + if (result != null) { + return result; + } + + /* + * There was no easy case for the rop, so look up the opcode, and + * do something special for each: + * + * The move_exception, new_array, filled_new_array, and + * invoke* opcodes won't be found in MAP, since they'll each + * have different source and/or result register types / lists. + * + * The get* and put* opcodes for (non-long) integral types + * aren't in the map, since the type signatures aren't + * sufficient to distinguish between the types (the salient + * source or result will always be just "int"). + * + * And const instruction need to distinguish between strings and + * classes. + */ + + switch (rop.getOpcode()) { + case RegOps.MOVE_EXCEPTION: return Dops.MOVE_EXCEPTION; + case RegOps.INVOKE_STATIC: return Dops.INVOKE_STATIC; + case RegOps.INVOKE_VIRTUAL: return Dops.INVOKE_VIRTUAL; + case RegOps.INVOKE_SUPER: return Dops.INVOKE_SUPER; + case RegOps.INVOKE_DIRECT: return Dops.INVOKE_DIRECT; + case RegOps.INVOKE_INTERFACE: return Dops.INVOKE_INTERFACE; + case RegOps.NEW_ARRAY: return Dops.NEW_ARRAY; + case RegOps.FILLED_NEW_ARRAY: return Dops.FILLED_NEW_ARRAY; + case RegOps.FILL_ARRAY_DATA: return Dops.FILL_ARRAY_DATA; + case RegOps.MOVE_RESULT: { + RegisterSpec resultReg = insn.getResult(); + + if (resultReg == null) { + return Dops.NOP; + } else { + switch (resultReg.getBasicType()) { + case Type.BT_INT: + case Type.BT_FLOAT: + case Type.BT_BOOLEAN: + case Type.BT_BYTE: + case Type.BT_CHAR: + case Type.BT_SHORT: + return Dops.MOVE_RESULT; + case Type.BT_LONG: + case Type.BT_DOUBLE: + return Dops.MOVE_RESULT_WIDE; + case Type.BT_OBJECT: + return Dops.MOVE_RESULT_OBJECT; + default: { + throw new RuntimeException("Unexpected basic type"); + } + } + } + } + + case RegOps.GET_FIELD: { + CstFieldRef ref = + (CstFieldRef) ((ThrowingCstInsn) insn).getConstant(); + int basicType = ref.getBasicType(); + switch (basicType) { + case Type.BT_BOOLEAN: return Dops.IGET_BOOLEAN; + case Type.BT_BYTE: return Dops.IGET_BYTE; + case Type.BT_CHAR: return Dops.IGET_CHAR; + case Type.BT_SHORT: return Dops.IGET_SHORT; + case Type.BT_INT: return Dops.IGET; + } + break; + } + case RegOps.PUT_FIELD: { + CstFieldRef ref = + (CstFieldRef) ((ThrowingCstInsn) insn).getConstant(); + int basicType = ref.getBasicType(); + switch (basicType) { + case Type.BT_BOOLEAN: return Dops.IPUT_BOOLEAN; + case Type.BT_BYTE: return Dops.IPUT_BYTE; + case Type.BT_CHAR: return Dops.IPUT_CHAR; + case Type.BT_SHORT: return Dops.IPUT_SHORT; + case Type.BT_INT: return Dops.IPUT; + } + break; + } + case RegOps.GET_STATIC: { + CstFieldRef ref = + (CstFieldRef) ((ThrowingCstInsn) insn).getConstant(); + int basicType = ref.getBasicType(); + switch (basicType) { + case Type.BT_BOOLEAN: return Dops.SGET_BOOLEAN; + case Type.BT_BYTE: return Dops.SGET_BYTE; + case Type.BT_CHAR: return Dops.SGET_CHAR; + case Type.BT_SHORT: return Dops.SGET_SHORT; + case Type.BT_INT: return Dops.SGET; + } + break; + } + case RegOps.PUT_STATIC: { + CstFieldRef ref = + (CstFieldRef) ((ThrowingCstInsn) insn).getConstant(); + int basicType = ref.getBasicType(); + switch (basicType) { + case Type.BT_BOOLEAN: return Dops.SPUT_BOOLEAN; + case Type.BT_BYTE: return Dops.SPUT_BYTE; + case Type.BT_CHAR: return Dops.SPUT_CHAR; + case Type.BT_SHORT: return Dops.SPUT_SHORT; + case Type.BT_INT: return Dops.SPUT; + } + break; + } + case RegOps.CONST: { + Constant cst = ((ThrowingCstInsn) insn).getConstant(); + if (cst instanceof CstType) { + return Dops.CONST_CLASS; + } else if (cst instanceof CstString) { + return Dops.CONST_STRING; + } + break; + } + } + + throw new RuntimeException("unknown rop: " + rop); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/RopTranslator.java b/dexgen/src/com/android/dexgen/dex/code/RopTranslator.java new file mode 100644 index 0000000..ad05f2b --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/RopTranslator.java @@ -0,0 +1,872 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.BasicBlock; +import com.android.dexgen.rop.code.BasicBlockList; +import com.android.dexgen.rop.code.FillArrayDataInsn; +import com.android.dexgen.rop.code.Insn; +import com.android.dexgen.rop.code.LocalVariableInfo; +import com.android.dexgen.rop.code.PlainCstInsn; +import com.android.dexgen.rop.code.PlainInsn; +import com.android.dexgen.rop.code.RegOps; +import com.android.dexgen.rop.code.RegisterSpec; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.code.RegisterSpecSet; +import com.android.dexgen.rop.code.Rop; +import com.android.dexgen.rop.code.RopMethod; +import com.android.dexgen.rop.code.SourcePosition; +import com.android.dexgen.rop.code.SwitchInsn; +import com.android.dexgen.rop.code.ThrowingCstInsn; +import com.android.dexgen.rop.code.ThrowingInsn; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstInteger; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.util.Bits; +import com.android.dexgen.util.IntList; + +import java.util.ArrayList; + +/** + * Translator from {@link RopMethod} to {@link DalvCode}. The {@link + * #translate} method is the thing to call on this class. + */ +public final class RopTranslator { + /** {@code non-null;} method to translate */ + private final RopMethod method; + + /** + * how much position info to preserve; one of the static + * constants in {@link PositionList} + */ + private final int positionInfo; + + /** {@code null-ok;} local variable info to use */ + private final LocalVariableInfo locals; + + /** {@code non-null;} container for all the address objects for the method */ + private final BlockAddresses addresses; + + /** {@code non-null;} list of output instructions in-progress */ + private final OutputCollector output; + + /** {@code non-null;} visitor to use during translation */ + private final TranslationVisitor translationVisitor; + + /** {@code >= 0;} register count for the method */ + private final int regCount; + + /** {@code null-ok;} block output order; becomes non-null in {@link #pickOrder} */ + private int[] order; + + /** size, in register units, of all the parameters to this method */ + private final int paramSize; + + /** + * true if the parameters to this method happen to be in proper order + * at the end of the frame (as the optimizer emits them) + */ + private boolean paramsAreInOrder; + + /** + * Translates a {@link RopMethod}. This may modify the given + * input. + * + * @param method {@code non-null;} the original method + * @param positionInfo how much position info to preserve; one of the + * static constants in {@link PositionList} + * @param locals {@code null-ok;} local variable information to use + * @param paramSize size, in register units, of all the parameters to + * this method + * @return {@code non-null;} the translated version + */ + public static DalvCode translate(RopMethod method, int positionInfo, + LocalVariableInfo locals, int paramSize) { + RopTranslator translator = + new RopTranslator(method, positionInfo, locals, + paramSize); + return translator.translateAndGetResult(); + } + + /** + * Constructs an instance. This method is private. Use {@link #translate}. + * + * @param method {@code non-null;} the original method + * @param positionInfo how much position info to preserve; one of the + * static constants in {@link PositionList} + * @param locals {@code null-ok;} local variable information to use + * @param paramSize size, in register units, of all the parameters to + * this method + */ + private RopTranslator(RopMethod method, int positionInfo, + LocalVariableInfo locals, int paramSize) { + this.method = method; + this.positionInfo = positionInfo; + this.locals = locals; + this.addresses = new BlockAddresses(method); + this.paramSize = paramSize; + this.order = null; + this.paramsAreInOrder = calculateParamsAreInOrder(method, paramSize); + + BasicBlockList blocks = method.getBlocks(); + int bsz = blocks.size(); + + /* + * Max possible instructions includes three code address + * objects per basic block (to the first and last instruction, + * and just past the end of the block), and the possibility of + * an extra goto at the end of each basic block. + */ + int maxInsns = (bsz * 3) + blocks.getInstructionCount(); + + if (locals != null) { + /* + * If we're tracking locals, then there's could be another + * extra instruction per block (for the locals state at the + * start of the block) as well as one for each interblock + * local introduction. + */ + maxInsns += bsz + locals.getAssignmentCount(); + } + + /* + * If params are not in order, we will need register space + * for them before this is all over... + */ + this.regCount = blocks.getRegCount() + + (paramsAreInOrder ? 0 : this.paramSize); + + this.output = new OutputCollector(maxInsns, bsz * 3, regCount); + + if (locals != null) { + this.translationVisitor = + new LocalVariableAwareTranslationVisitor(output, locals); + } else { + this.translationVisitor = new TranslationVisitor(output); + } + } + + /** + * Checks to see if the move-param instructions that occur in this + * method happen to slot the params in an order at the top of the + * stack frame that matches dalvik's calling conventions. This will + * alway result in "true" for methods that have run through the + * SSA optimizer. + * + * @param paramSize size, in register units, of all the parameters + * to this method + */ + private static boolean calculateParamsAreInOrder(RopMethod method, + final int paramSize) { + final boolean[] paramsAreInOrder = { true }; + final int initialRegCount = method.getBlocks().getRegCount(); + + /* + * We almost could just check the first block here, but the + * {@code cf} layer will put in a second move-param in a + * subsequent block in the case of synchronized methods. + */ + method.getBlocks().forEachInsn(new Insn.BaseVisitor() { + public void visitPlainCstInsn(PlainCstInsn insn) { + if (insn.getOpcode().getOpcode()== RegOps.MOVE_PARAM) { + int param = + ((CstInteger) insn.getConstant()).getValue(); + + paramsAreInOrder[0] = paramsAreInOrder[0] + && ((initialRegCount - paramSize + param) + == insn.getResult().getReg()); + } + } + }); + + return paramsAreInOrder[0]; + } + + /** + * Does the translation and returns the result. + * + * @return {@code non-null;} the result + */ + private DalvCode translateAndGetResult() { + pickOrder(); + outputInstructions(); + + StdCatchBuilder catches = + new StdCatchBuilder(method, order, addresses); + + return new DalvCode(positionInfo, output.getFinisher(), catches); + } + + /** + * Performs initial creation of output instructions based on the + * original blocks. + */ + private void outputInstructions() { + BasicBlockList blocks = method.getBlocks(); + int[] order = this.order; + int len = order.length; + + // Process the blocks in output order. + for (int i = 0; i < len; i++) { + int nextI = i + 1; + int nextLabel = (nextI == order.length) ? -1 : order[nextI]; + outputBlock(blocks.labelToBlock(order[i]), nextLabel); + } + } + + /** + * Helper for {@link #outputInstructions}, which does the processing + * and output of one block. + * + * @param block {@code non-null;} the block to process and output + * @param nextLabel {@code >= -1;} the next block that will be processed, or + * {@code -1} if there is no next block + */ + private void outputBlock(BasicBlock block, int nextLabel) { + // Append the code address for this block. + CodeAddress startAddress = addresses.getStart(block); + output.add(startAddress); + + // Append the local variable state for the block. + if (locals != null) { + RegisterSpecSet starts = locals.getStarts(block); + output.add(new LocalSnapshot(startAddress.getPosition(), + starts)); + } + + /* + * Choose and append an output instruction for each original + * instruction. + */ + translationVisitor.setBlock(block, addresses.getLast(block)); + block.getInsns().forEach(translationVisitor); + + // Insert the block end code address. + output.add(addresses.getEnd(block)); + + // Set up for end-of-block activities. + + int succ = block.getPrimarySuccessor(); + Insn lastInsn = block.getLastInsn(); + + /* + * Check for (and possibly correct for) a non-optimal choice of + * which block will get output next. + */ + + if ((succ >= 0) && (succ != nextLabel)) { + /* + * The block has a "primary successor" and that primary + * successor isn't the next block to be output. + */ + Rop lastRop = lastInsn.getOpcode(); + if ((lastRop.getBranchingness() == Rop.BRANCH_IF) && + (block.getSecondarySuccessor() == nextLabel)) { + /* + * The block ends with an "if" of some sort, and its + * secondary successor (the "then") is in fact the + * next block to output. So, reverse the sense of + * the test, so that we can just emit the next block + * without an interstitial goto. + */ + output.reverseBranch(1, addresses.getStart(succ)); + } else { + /* + * Our only recourse is to add a goto here to get the + * flow to be correct. + */ + TargetInsn insn = + new TargetInsn(Dops.GOTO, lastInsn.getPosition(), + RegisterSpecList.EMPTY, + addresses.getStart(succ)); + output.add(insn); + } + } + } + + /** + * Picks an order for the blocks by doing "trace" analysis. + */ + private void pickOrder() { + BasicBlockList blocks = method.getBlocks(); + int sz = blocks.size(); + int maxLabel = blocks.getMaxLabel(); + int[] workSet = Bits.makeBitSet(maxLabel); + int[] tracebackSet = Bits.makeBitSet(maxLabel); + + for (int i = 0; i < sz; i++) { + BasicBlock one = blocks.get(i); + Bits.set(workSet, one.getLabel()); + } + + int[] order = new int[sz]; + int at = 0; + + /* + * Starting with the designated "first label" (that is, the + * first block of the method), add that label to the order, + * and then pick its first as-yet unordered successor to + * immediately follow it, giving top priority to the primary + * (aka default) successor (if any). Keep following successors + * until the trace runs out of possibilities. Then, continue + * by finding an unordered chain containing the first as-yet + * unordered block, and adding it to the order, and so on. + */ + for (int label = method.getFirstLabel(); + label != -1; + label = Bits.findFirst(workSet, 0)) { + + /* + * Attempt to trace backward from the chosen block to an + * as-yet unordered predecessor which lists the chosen + * block as its primary successor, and so on, until we + * fail to find such an unordered predecessor. Start the + * trace with that block. Note that the first block in the + * method has no predecessors, so in that case this loop + * will simply terminate with zero iterations and without + * picking a new starter block. + */ + traceBack: + for (;;) { + IntList preds = method.labelToPredecessors(label); + int psz = preds.size(); + + for (int i = 0; i < psz; i++) { + int predLabel = preds.get(i); + + if (Bits.get(tracebackSet, predLabel)) { + /* + * We found a predecessor loop; stop tracing back + * from here. + */ + break; + } + + if (!Bits.get(workSet, predLabel)) { + // This one's already ordered. + continue; + } + + BasicBlock pred = blocks.labelToBlock(predLabel); + if (pred.getPrimarySuccessor() == label) { + // Found one! + label = predLabel; + Bits.set(tracebackSet, label); + continue traceBack; + } + } + + // Failed to find a better block to start the trace. + break; + } + + /* + * Trace a path from the chosen block to one of its + * unordered successors (hopefully the primary), and so + * on, until we run out of unordered successors. + */ + while (label != -1) { + Bits.clear(workSet, label); + Bits.clear(tracebackSet, label); + order[at] = label; + at++; + + BasicBlock one = blocks.labelToBlock(label); + BasicBlock preferredBlock = blocks.preferredSuccessorOf(one); + + if (preferredBlock == null) { + break; + } + + int preferred = preferredBlock.getLabel(); + int primary = one.getPrimarySuccessor(); + + if (Bits.get(workSet, preferred)) { + /* + * Order the current block's preferred successor + * next, as it has yet to be scheduled. + */ + label = preferred; + } else if ((primary != preferred) && (primary >= 0) + && Bits.get(workSet, primary)) { + /* + * The primary is available, so use that. + */ + label = primary; + } else { + /* + * There's no obvious candidate, so pick the first + * one that's available, if any. + */ + IntList successors = one.getSuccessors(); + int ssz = successors.size(); + label = -1; + for (int i = 0; i < ssz; i++) { + int candidate = successors.get(i); + if (Bits.get(workSet, candidate)) { + label = candidate; + break; + } + } + } + } + } + + if (at != sz) { + // There was a duplicate block label. + throw new RuntimeException("shouldn't happen"); + } + + this.order = order; + } + + /** + * Gets the complete register list (result and sources) out of a + * given rop instruction. For insns that are commutative, have + * two register sources, and have a source equal to the result, + * place that source first. + * + * @param insn {@code non-null;} instruction in question + * @return {@code non-null;} the instruction's complete register list + */ + private static RegisterSpecList getRegs(Insn insn) { + return getRegs(insn, insn.getResult()); + } + + /** + * Gets the complete register list (result and sources) out of a + * given rop instruction. For insns that are commutative, have + * two register sources, and have a source equal to the result, + * place that source first. + * + * @param insn {@code non-null;} instruction in question + * @param resultReg {@code null-ok;} the real result to use (ignore the insn's) + * @return {@code non-null;} the instruction's complete register list + */ + private static RegisterSpecList getRegs(Insn insn, + RegisterSpec resultReg) { + RegisterSpecList regs = insn.getSources(); + + if (insn.getOpcode().isCommutative() + && (regs.size() == 2) + && (resultReg.getReg() == regs.get(1).getReg())) { + + /* + * For commutative ops which have two register sources, + * if the second source is the same register as the result, + * swap the sources so that an opcode of form 12x can be selected + * instead of one of form 23x + */ + + regs = RegisterSpecList.make(regs.get(1), regs.get(0)); + } + + if (resultReg == null) { + return regs; + } + + return regs.withFirst(resultReg); + } + + /** + * Instruction visitor class for doing the instruction translation per se. + */ + private class TranslationVisitor implements Insn.Visitor { + /** {@code non-null;} list of output instructions in-progress */ + private final OutputCollector output; + + /** {@code non-null;} basic block being worked on */ + private BasicBlock block; + + /** + * {@code null-ok;} code address for the salient last instruction of the + * block (used before switches and throwing instructions) + */ + private CodeAddress lastAddress; + + /** + * Constructs an instance. + * + * @param output {@code non-null;} destination for instruction output + */ + public TranslationVisitor(OutputCollector output) { + this.output = output; + } + + /** + * Sets the block currently being worked on. + * + * @param block {@code non-null;} the block + * @param lastAddress {@code non-null;} code address for the salient + * last instruction of the block + */ + public void setBlock(BasicBlock block, CodeAddress lastAddress) { + this.block = block; + this.lastAddress = lastAddress; + } + + /** {@inheritDoc} */ + public void visitPlainInsn(PlainInsn insn) { + Rop rop = insn.getOpcode(); + if (rop.getOpcode() == RegOps.MARK_LOCAL) { + /* + * Ignore these. They're dealt with by + * the LocalVariableAwareTranslationVisitor + */ + return; + } + if (rop.getOpcode() == RegOps.MOVE_RESULT_PSEUDO) { + // These get skipped + return; + } + + SourcePosition pos = insn.getPosition(); + Dop opcode = RopToDop.dopFor(insn); + DalvInsn di; + + switch (rop.getBranchingness()) { + case Rop.BRANCH_NONE: + case Rop.BRANCH_RETURN: + case Rop.BRANCH_THROW: { + di = new SimpleInsn(opcode, pos, getRegs(insn)); + break; + } + case Rop.BRANCH_GOTO: { + /* + * Code in the main translation loop will emit a + * goto if necessary (if the branch isn't to the + * immediately subsequent block). + */ + return; + } + case Rop.BRANCH_IF: { + int target = block.getSuccessors().get(1); + di = new TargetInsn(opcode, pos, getRegs(insn), + addresses.getStart(target)); + break; + } + default: { + throw new RuntimeException("shouldn't happen"); + } + } + + addOutput(di); + } + + /** {@inheritDoc} */ + public void visitPlainCstInsn(PlainCstInsn insn) { + SourcePosition pos = insn.getPosition(); + Dop opcode = RopToDop.dopFor(insn); + Rop rop = insn.getOpcode(); + int ropOpcode = rop.getOpcode(); + DalvInsn di; + + if (rop.getBranchingness() != Rop.BRANCH_NONE) { + throw new RuntimeException("shouldn't happen"); + } + + if (ropOpcode == RegOps.MOVE_PARAM) { + if (!paramsAreInOrder) { + /* + * Parameters are not in order at the top of the reg space. + * We need to add moves. + */ + + RegisterSpec dest = insn.getResult(); + int param = + ((CstInteger) insn.getConstant()).getValue(); + RegisterSpec source = + RegisterSpec.make(regCount - paramSize + param, + dest.getType()); + di = new SimpleInsn(opcode, pos, + RegisterSpecList.make(dest, source)); + addOutput(di); + } + } else { + // No moves required for the parameters + RegisterSpecList regs = getRegs(insn); + di = new CstInsn(opcode, pos, regs, insn.getConstant()); + addOutput(di); + } + } + + /** {@inheritDoc} */ + public void visitSwitchInsn(SwitchInsn insn) { + SourcePosition pos = insn.getPosition(); + IntList cases = insn.getCases(); + IntList successors = block.getSuccessors(); + int casesSz = cases.size(); + int succSz = successors.size(); + int primarySuccessor = block.getPrimarySuccessor(); + + /* + * Check the assumptions that the number of cases is one + * less than the number of successors and that the last + * successor in the list is the primary (in this case, the + * default). This test is here to guard against forgetting + * to change this code if the way switch instructions are + * constructed also gets changed. + */ + if ((casesSz != (succSz - 1)) || + (primarySuccessor != successors.get(casesSz))) { + throw new RuntimeException("shouldn't happen"); + } + + CodeAddress[] switchTargets = new CodeAddress[casesSz]; + + for (int i = 0; i < casesSz; i++) { + int label = successors.get(i); + switchTargets[i] = addresses.getStart(label); + } + + CodeAddress dataAddress = new CodeAddress(pos); + SwitchData dataInsn = + new SwitchData(pos, lastAddress, cases, switchTargets); + Dop opcode = dataInsn.isPacked() ? + Dops.PACKED_SWITCH : Dops.SPARSE_SWITCH; + TargetInsn switchInsn = + new TargetInsn(opcode, pos, getRegs(insn), dataAddress); + + addOutput(lastAddress); + addOutput(switchInsn); + + addOutputSuffix(new OddSpacer(pos)); + addOutputSuffix(dataAddress); + addOutputSuffix(dataInsn); + } + + /** + * Looks forward to the current block's primary successor, returning + * the RegisterSpec of the result of the move-result-pseudo at the + * top of that block or null if none. + * + * @return {@code null-ok;} result of move-result-pseudo at the beginning of + * primary successor + */ + private RegisterSpec getNextMoveResultPseudo() + { + int label = block.getPrimarySuccessor(); + + if (label < 0) { + return null; + } + + Insn insn + = method.getBlocks().labelToBlock(label).getInsns().get(0); + + if (insn.getOpcode().getOpcode() != RegOps.MOVE_RESULT_PSEUDO) { + return null; + } else { + return insn.getResult(); + } + } + + /** {@inheritDoc} */ + public void visitThrowingCstInsn(ThrowingCstInsn insn) { + SourcePosition pos = insn.getPosition(); + Dop opcode = RopToDop.dopFor(insn); + Rop rop = insn.getOpcode(); + Constant cst = insn.getConstant(); + + if (rop.getBranchingness() != Rop.BRANCH_THROW) { + throw new RuntimeException("shouldn't happen"); + } + + addOutput(lastAddress); + + if (rop.isCallLike()) { + RegisterSpecList regs = insn.getSources(); + DalvInsn di = new CstInsn(opcode, pos, regs, cst); + + addOutput(di); + } else { + RegisterSpec realResult = getNextMoveResultPseudo(); + + RegisterSpecList regs = getRegs(insn, realResult); + DalvInsn di; + + boolean hasResult = opcode.hasResult() + || (rop.getOpcode() == RegOps.CHECK_CAST); + + if (hasResult != (realResult != null)) { + throw new RuntimeException( + "Insn with result/move-result-pseudo mismatch " + + insn); + } + + if ((rop.getOpcode() == RegOps.NEW_ARRAY) && + (opcode.getOpcode() != DalvOps.NEW_ARRAY)) { + /* + * It's a type-specific new-array-<primitive>, and + * so it should be turned into a SimpleInsn (no + * constant ref as it's implicit). + */ + di = new SimpleInsn(opcode, pos, regs); + } else { + /* + * This is the general case for constant-bearing + * instructions. + */ + di = new CstInsn(opcode, pos, regs, cst); + } + + addOutput(di); + } + } + + /** {@inheritDoc} */ + public void visitThrowingInsn(ThrowingInsn insn) { + SourcePosition pos = insn.getPosition(); + Dop opcode = RopToDop.dopFor(insn); + Rop rop = insn.getOpcode(); + RegisterSpec realResult; + + if (rop.getBranchingness() != Rop.BRANCH_THROW) { + throw new RuntimeException("shouldn't happen"); + } + + realResult = getNextMoveResultPseudo(); + + if (opcode.hasResult() != (realResult != null)) { + throw new RuntimeException( + "Insn with result/move-result-pseudo mismatch" + insn); + } + + addOutput(lastAddress); + + DalvInsn di = new SimpleInsn(opcode, pos, + getRegs(insn, realResult)); + + addOutput(di); + } + + /** {@inheritDoc} */ + public void visitFillArrayDataInsn(FillArrayDataInsn insn) { + SourcePosition pos = insn.getPosition(); + Constant cst = insn.getConstant(); + ArrayList<Constant> values = insn.getInitValues(); + Rop rop = insn.getOpcode(); + + if (rop.getBranchingness() != Rop.BRANCH_NONE) { + throw new RuntimeException("shouldn't happen"); + } + CodeAddress dataAddress = new CodeAddress(pos); + ArrayData dataInsn = + new ArrayData(pos, lastAddress, values, cst); + + TargetInsn fillArrayDataInsn = + new TargetInsn(Dops.FILL_ARRAY_DATA, pos, getRegs(insn), + dataAddress); + + addOutput(lastAddress); + addOutput(fillArrayDataInsn); + + addOutputSuffix(new OddSpacer(pos)); + addOutputSuffix(dataAddress); + addOutputSuffix(dataInsn); + } + + /** + * Adds to the output. + * + * @param insn {@code non-null;} instruction to add + */ + protected void addOutput(DalvInsn insn) { + output.add(insn); + } + + /** + * Adds to the output suffix. + * + * @param insn {@code non-null;} instruction to add + */ + protected void addOutputSuffix(DalvInsn insn) { + output.addSuffix(insn); + } + } + + /** + * Instruction visitor class for doing instruction translation with + * local variable tracking + */ + private class LocalVariableAwareTranslationVisitor + extends TranslationVisitor { + /** {@code non-null;} local variable info */ + private LocalVariableInfo locals; + + /** + * Constructs an instance. + * + * @param output {@code non-null;} destination for instruction output + * @param locals {@code non-null;} the local variable info + */ + public LocalVariableAwareTranslationVisitor(OutputCollector output, + LocalVariableInfo locals) { + super(output); + this.locals = locals; + } + + /** {@inheritDoc} */ + @Override + public void visitPlainInsn(PlainInsn insn) { + super.visitPlainInsn(insn); + addIntroductionIfNecessary(insn); + } + + /** {@inheritDoc} */ + @Override + public void visitPlainCstInsn(PlainCstInsn insn) { + super.visitPlainCstInsn(insn); + addIntroductionIfNecessary(insn); + } + + /** {@inheritDoc} */ + @Override + public void visitSwitchInsn(SwitchInsn insn) { + super.visitSwitchInsn(insn); + addIntroductionIfNecessary(insn); + } + + /** {@inheritDoc} */ + @Override + public void visitThrowingCstInsn(ThrowingCstInsn insn) { + super.visitThrowingCstInsn(insn); + addIntroductionIfNecessary(insn); + } + + /** {@inheritDoc} */ + @Override + public void visitThrowingInsn(ThrowingInsn insn) { + super.visitThrowingInsn(insn); + addIntroductionIfNecessary(insn); + } + + /** + * Adds a {@link LocalStart} to the output if the given + * instruction in fact introduces a local variable. + * + * @param insn {@code non-null;} instruction in question + */ + public void addIntroductionIfNecessary(Insn insn) { + RegisterSpec spec = locals.getAssignment(insn); + + if (spec != null) { + addOutput(new LocalStart(insn.getPosition(), spec)); + } + } + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/SimpleInsn.java b/dexgen/src/com/android/dexgen/dex/code/SimpleInsn.java new file mode 100644 index 0000000..abef242 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/SimpleInsn.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.code.SourcePosition; + +/** + * Instruction which has no extra info beyond the basics provided for in + * the base class. + */ +public final class SimpleInsn extends FixedSizeInsn { + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param opcode the opcode; one of the constants from {@link Dops} + * @param position {@code non-null;} source position + * @param registers {@code non-null;} register list, including a + * result register if appropriate (that is, registers may be either + * ins or outs) + */ + public SimpleInsn(Dop opcode, SourcePosition position, + RegisterSpecList registers) { + super(opcode, position, registers); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withOpcode(Dop opcode) { + return new SimpleInsn(opcode, getPosition(), getRegisters()); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new SimpleInsn(getOpcode(), getPosition(), registers); + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + return null; + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/StdCatchBuilder.java b/dexgen/src/com/android/dexgen/dex/code/StdCatchBuilder.java new file mode 100644 index 0000000..ba149e7 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/StdCatchBuilder.java @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.BasicBlock; +import com.android.dexgen.rop.code.BasicBlockList; +import com.android.dexgen.rop.code.RopMethod; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.rop.type.TypeList; +import com.android.dexgen.util.IntList; + +import java.util.ArrayList; +import java.util.HashSet; + +/** + * Constructor of {@link CatchTable} instances from {@link RopMethod} + * and associated data. + */ +public final class StdCatchBuilder implements CatchBuilder { + /** the maximum range of a single catch handler, in code units */ + private static final int MAX_CATCH_RANGE = 65535; + + /** {@code non-null;} method to build the list for */ + private final RopMethod method; + + /** {@code non-null;} block output order */ + private final int[] order; + + /** {@code non-null;} address objects for each block */ + private final BlockAddresses addresses; + + /** + * Constructs an instance. It merely holds onto its parameters for + * a subsequent call to {@link #build}. + * + * @param method {@code non-null;} method to build the list for + * @param order {@code non-null;} block output order + * @param addresses {@code non-null;} address objects for each block + */ + public StdCatchBuilder(RopMethod method, int[] order, + BlockAddresses addresses) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + if (order == null) { + throw new NullPointerException("order == null"); + } + + if (addresses == null) { + throw new NullPointerException("addresses == null"); + } + + this.method = method; + this.order = order; + this.addresses = addresses; + } + + /** {@inheritDoc} */ + public CatchTable build() { + return build(method, order, addresses); + } + + /** {@inheritDoc} */ + public boolean hasAnyCatches() { + BasicBlockList blocks = method.getBlocks(); + int size = blocks.size(); + + for (int i = 0; i < size; i++) { + BasicBlock block = blocks.get(i); + TypeList catches = block.getLastInsn().getCatches(); + if (catches.size() != 0) { + return true; + } + } + + return false; + } + + /** {@inheritDoc} */ + public HashSet<Type> getCatchTypes() { + HashSet<Type> result = new HashSet<Type>(20); + BasicBlockList blocks = method.getBlocks(); + int size = blocks.size(); + + for (int i = 0; i < size; i++) { + BasicBlock block = blocks.get(i); + TypeList catches = block.getLastInsn().getCatches(); + int catchSize = catches.size(); + + for (int j = 0; j < catchSize; j++) { + result.add(catches.getType(j)); + } + } + + return result; + } + + /** + * Builds and returns the catch table for a given method. + * + * @param method {@code non-null;} method to build the list for + * @param order {@code non-null;} block output order + * @param addresses {@code non-null;} address objects for each block + * @return {@code non-null;} the constructed table + */ + public static CatchTable build(RopMethod method, int[] order, + BlockAddresses addresses) { + int len = order.length; + BasicBlockList blocks = method.getBlocks(); + ArrayList<CatchTable.Entry> resultList = + new ArrayList<CatchTable.Entry>(len); + CatchHandlerList currentHandlers = CatchHandlerList.EMPTY; + BasicBlock currentStartBlock = null; + BasicBlock currentEndBlock = null; + + for (int i = 0; i < len; i++) { + BasicBlock block = blocks.labelToBlock(order[i]); + + if (!block.canThrow()) { + /* + * There is no need to concern ourselves with the + * placement of blocks that can't throw with respect + * to the blocks that *can* throw. + */ + continue; + } + + CatchHandlerList handlers = handlersFor(block, addresses); + + if (currentHandlers.size() == 0) { + // This is the start of a new catch range. + currentStartBlock = block; + currentEndBlock = block; + currentHandlers = handlers; + continue; + } + + if (currentHandlers.equals(handlers) + && rangeIsValid(currentStartBlock, block, addresses)) { + /* + * The block we are looking at now has the same handlers + * as the block that started the currently open catch + * range, and adding it to the currently open range won't + * cause it to be too long. + */ + currentEndBlock = block; + continue; + } + + /* + * The block we are looking at now has incompatible handlers, + * so we need to finish off the last entry and start a new + * one. Note: We only emit an entry if it has associated handlers. + */ + if (currentHandlers.size() != 0) { + CatchTable.Entry entry = + makeEntry(currentStartBlock, currentEndBlock, + currentHandlers, addresses); + resultList.add(entry); + } + + currentStartBlock = block; + currentEndBlock = block; + currentHandlers = handlers; + } + + if (currentHandlers.size() != 0) { + // Emit an entry for the range that was left hanging. + CatchTable.Entry entry = + makeEntry(currentStartBlock, currentEndBlock, + currentHandlers, addresses); + resultList.add(entry); + } + + // Construct the final result. + + int resultSz = resultList.size(); + + if (resultSz == 0) { + return CatchTable.EMPTY; + } + + CatchTable result = new CatchTable(resultSz); + + for (int i = 0; i < resultSz; i++) { + result.set(i, resultList.get(i)); + } + + result.setImmutable(); + return result; + } + + /** + * Makes the {@link CatchHandlerList} for the given basic block. + * + * @param block {@code non-null;} block to get entries for + * @param addresses {@code non-null;} address objects for each block + * @return {@code non-null;} array of entries + */ + private static CatchHandlerList handlersFor(BasicBlock block, + BlockAddresses addresses) { + IntList successors = block.getSuccessors(); + int succSize = successors.size(); + int primary = block.getPrimarySuccessor(); + TypeList catches = block.getLastInsn().getCatches(); + int catchSize = catches.size(); + + if (catchSize == 0) { + return CatchHandlerList.EMPTY; + } + + if (((primary == -1) && (succSize != catchSize)) + || ((primary != -1) && + ((succSize != (catchSize + 1)) + || (primary != successors.get(catchSize))))) { + /* + * Blocks that throw are supposed to list their primary + * successor -- if any -- last in the successors list, but + * that constraint appears to be violated here. + */ + throw new RuntimeException( + "shouldn't happen: weird successors list"); + } + + /* + * Reduce the effective catchSize if we spot a catch-all that + * isn't at the end. + */ + for (int i = 0; i < catchSize; i++) { + Type type = catches.getType(i); + if (type.equals(Type.OBJECT)) { + catchSize = i + 1; + break; + } + } + + CatchHandlerList result = new CatchHandlerList(catchSize); + + for (int i = 0; i < catchSize; i++) { + CstType oneType = new CstType(catches.getType(i)); + CodeAddress oneHandler = addresses.getStart(successors.get(i)); + result.set(i, oneType, oneHandler.getAddress()); + } + + result.setImmutable(); + return result; + } + + /** + * Makes a {@link CatchTable#Entry} for the given block range and + * handlers. + * + * @param start {@code non-null;} the start block for the range (inclusive) + * @param end {@code non-null;} the start block for the range (also inclusive) + * @param handlers {@code non-null;} the handlers for the range + * @param addresses {@code non-null;} address objects for each block + */ + private static CatchTable.Entry makeEntry(BasicBlock start, + BasicBlock end, CatchHandlerList handlers, + BlockAddresses addresses) { + /* + * We start at the *last* instruction of the start block, since + * that's the instruction that can throw... + */ + CodeAddress startAddress = addresses.getLast(start); + + // ...And we end *after* the last instruction of the end block. + CodeAddress endAddress = addresses.getEnd(end); + + return new CatchTable.Entry(startAddress.getAddress(), + endAddress.getAddress(), handlers); + } + + /** + * Gets whether the address range for the given two blocks is valid + * for a catch handler. This is true as long as the covered range is + * under 65536 code units. + * + * @param start {@code non-null;} the start block for the range (inclusive) + * @param end {@code non-null;} the start block for the range (also inclusive) + * @param addresses {@code non-null;} address objects for each block + * @return {@code true} if the range is valid as a catch range + */ + private static boolean rangeIsValid(BasicBlock start, BasicBlock end, + BlockAddresses addresses) { + if (start == null) { + throw new NullPointerException("start == null"); + } + + if (end == null) { + throw new NullPointerException("end == null"); + } + + // See above about selection of instructions. + int startAddress = addresses.getLast(start).getAddress(); + int endAddress = addresses.getEnd(end).getAddress(); + + return (endAddress - startAddress) <= MAX_CATCH_RANGE; + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/SwitchData.java b/dexgen/src/com/android/dexgen/dex/code/SwitchData.java new file mode 100644 index 0000000..a7d8465 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/SwitchData.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.code.SourcePosition; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; +import com.android.dexgen.util.IntList; + +/** + * Pseudo-instruction which holds switch data. The switch data is + * a map of values to target addresses, and this class writes the data + * in either a "packed" or "sparse" form. + */ +public final class SwitchData extends VariableSizeInsn { + /** + * {@code non-null;} address representing the instruction that uses this + * instance + */ + private final CodeAddress user; + + /** {@code non-null;} sorted list of switch cases (keys) */ + private final IntList cases; + + /** + * {@code non-null;} corresponding list of code addresses; the branch + * target for each case + */ + private final CodeAddress[] targets; + + /** whether the output table will be packed (vs. sparse) */ + private final boolean packed; + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + * @param user {@code non-null;} address representing the instruction that + * uses this instance + * @param cases {@code non-null;} sorted list of switch cases (keys) + * @param targets {@code non-null;} corresponding list of code addresses; the + * branch target for each case + */ + public SwitchData(SourcePosition position, CodeAddress user, + IntList cases, CodeAddress[] targets) { + super(position, RegisterSpecList.EMPTY); + + if (user == null) { + throw new NullPointerException("user == null"); + } + + if (cases == null) { + throw new NullPointerException("cases == null"); + } + + if (targets == null) { + throw new NullPointerException("targets == null"); + } + + int sz = cases.size(); + + if (sz != targets.length) { + throw new IllegalArgumentException("cases / targets mismatch"); + } + + if (sz > 65535) { + throw new IllegalArgumentException("too many cases"); + } + + this.user = user; + this.cases = cases; + this.targets = targets; + this.packed = shouldPack(cases); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return packed ? (int) packedCodeSize(cases) : + (int) sparseCodeSize(cases); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out) { + int baseAddress = user.getAddress(); + int defaultTarget = Dops.PACKED_SWITCH.getFormat().codeSize(); + int sz = targets.length; + + if (packed) { + int firstCase = (sz == 0) ? 0 : cases.get(0); + int lastCase = (sz == 0) ? 0 : cases.get(sz - 1); + int outSz = lastCase - firstCase + 1; + + out.writeShort(0x100 | DalvOps.NOP); + out.writeShort(outSz); + out.writeInt(firstCase); + + int caseAt = 0; + for (int i = 0; i < outSz; i++) { + int outCase = firstCase + i; + int oneCase = cases.get(caseAt); + int relTarget; + + if (oneCase > outCase) { + relTarget = defaultTarget; + } else { + relTarget = targets[caseAt].getAddress() - baseAddress; + caseAt++; + } + + out.writeInt(relTarget); + } + } else { + out.writeShort(0x200 | DalvOps.NOP); + out.writeShort(sz); + + for (int i = 0; i < sz; i++) { + out.writeInt(cases.get(i)); + } + + for (int i = 0; i < sz; i++) { + int relTarget = targets[i].getAddress() - baseAddress; + out.writeInt(relTarget); + } + } + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new SwitchData(getPosition(), user, cases, targets); + } + + /** + * Returns whether or not this instance's data will be output as packed. + * + * @return {@code true} iff the data is to be packed + */ + public boolean isPacked() { + return packed; + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + StringBuffer sb = new StringBuffer(100); + + int sz = targets.length; + for (int i = 0; i < sz; i++) { + sb.append("\n "); + sb.append(cases.get(i)); + sb.append(": "); + sb.append(targets[i]); + } + + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + protected String listingString0(boolean noteIndices) { + int baseAddress = user.getAddress(); + StringBuffer sb = new StringBuffer(100); + int sz = targets.length; + + sb.append(packed ? "packed" : "sparse"); + sb.append("-switch-data // for switch @ "); + sb.append(Hex.u2(baseAddress)); + + for (int i = 0; i < sz; i++) { + int absTarget = targets[i].getAddress(); + int relTarget = absTarget - baseAddress; + sb.append("\n "); + sb.append(cases.get(i)); + sb.append(": "); + sb.append(Hex.u4(absTarget)); + sb.append(" // "); + sb.append(Hex.s4(relTarget)); + } + + return sb.toString(); + } + + /** + * Gets the size of a packed table for the given cases, in 16-bit code + * units. + * + * @param cases {@code non-null;} sorted list of cases + * @return {@code >= -1;} the packed table size or {@code -1} if the + * cases couldn't possibly be represented as a packed table + */ + private static long packedCodeSize(IntList cases) { + int sz = cases.size(); + long low = cases.get(0); + long high = cases.get(sz - 1); + long result = ((high - low + 1)) * 2 + 4; + + return (result <= 0x7fffffff) ? result : -1; + } + + /** + * Gets the size of a sparse table for the given cases, in 16-bit code + * units. + * + * @param cases {@code non-null;} sorted list of cases + * @return {@code > 0;} the sparse table size + */ + private static long sparseCodeSize(IntList cases) { + int sz = cases.size(); + + return (sz * 4L) + 2; + } + + /** + * Determines whether the given list of cases warrant being packed. + * + * @param cases {@code non-null;} sorted list of cases + * @return {@code true} iff the table encoding the cases + * should be packed + */ + private static boolean shouldPack(IntList cases) { + int sz = cases.size(); + + if (sz < 2) { + return true; + } + + long packedSize = packedCodeSize(cases); + long sparseSize = sparseCodeSize(cases); + + /* + * We pick the packed representation if it is possible and + * would be as small or smaller than 5/4 of the sparse + * representation. That is, we accept some size overhead on + * the packed representation, since that format is faster to + * execute at runtime. + */ + return (packedSize >= 0) && (packedSize <= ((sparseSize * 5) / 4)); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/TargetInsn.java b/dexgen/src/com/android/dexgen/dex/code/TargetInsn.java new file mode 100644 index 0000000..8e02255 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/TargetInsn.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.code.SourcePosition; + +/** + * Instruction which has a single branch target. + */ +public final class TargetInsn extends FixedSizeInsn { + /** {@code non-null;} the branch target */ + private CodeAddress target; + + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}), and the target is initially + * {@code null}. + * + * @param opcode the opcode; one of the constants from {@link Dops} + * @param position {@code non-null;} source position + * @param registers {@code non-null;} register list, including a + * result register if appropriate (that is, registers may be either + * ins or outs) + * @param target {@code non-null;} the branch target + */ + public TargetInsn(Dop opcode, SourcePosition position, + RegisterSpecList registers, CodeAddress target) { + super(opcode, position, registers); + + if (target == null) { + throw new NullPointerException("target == null"); + } + + this.target = target; + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withOpcode(Dop opcode) { + return new TargetInsn(opcode, getPosition(), getRegisters(), target); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisters(RegisterSpecList registers) { + return new TargetInsn(getOpcode(), getPosition(), registers, target); + } + + /** + * Returns an instance that is just like this one, except that its + * opcode has the opposite sense (as a test; e.g. a + * {@code lt} test becomes a {@code ge}), and its branch + * target is replaced by the one given, and all set-once values + * associated with the class (such as its address) are reset. + * + * @param target {@code non-null;} the new branch target + * @return {@code non-null;} an appropriately-constructed instance + */ + public TargetInsn withNewTargetAndReversed(CodeAddress target) { + Dop opcode = getOpcode().getOppositeTest(); + + return new TargetInsn(opcode, getPosition(), getRegisters(), target); + } + + /** + * Gets the unique branch target of this instruction. + * + * @return {@code non-null;} the branch target + */ + public CodeAddress getTarget() { + return target; + } + + /** + * Gets the target address of this instruction. This is only valid + * to call if the target instruction has been assigned an address, + * and it is merely a convenient shorthand for + * {@code getTarget().getAddress()}. + * + * @return {@code >= 0;} the target address + */ + public int getTargetAddress() { + return target.getAddress(); + } + + /** + * Gets the branch offset of this instruction. This is only valid to + * call if both this and the target instruction each has been assigned + * an address, and it is merely a convenient shorthand for + * {@code getTargetAddress() - getAddress()}. + * + * @return the branch offset + */ + public int getTargetOffset() { + return target.getAddress() - getAddress(); + } + + /** + * Returns whether the target offset is known. + * + * @return {@code true} if the target offset is known or + * {@code false} if not + */ + public boolean hasTargetOffset() { + return hasAddress() && target.hasAddress(); + } + + /** {@inheritDoc} */ + @Override + protected String argString() { + if (target == null) { + return "????"; + } + + return target.identifierString(); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/VariableSizeInsn.java b/dexgen/src/com/android/dexgen/dex/code/VariableSizeInsn.java new file mode 100644 index 0000000..baa62a3 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/VariableSizeInsn.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.code.SourcePosition; + +/** + * Pseudo-instruction base class for variable-sized instructions. + */ +public abstract class VariableSizeInsn extends DalvInsn { + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + * @param registers {@code non-null;} source registers + */ + public VariableSizeInsn(SourcePosition position, + RegisterSpecList registers) { + super(Dops.SPECIAL_FORMAT, position, registers); + } + + /** {@inheritDoc} */ + @Override + public final DalvInsn withOpcode(Dop opcode) { + throw new RuntimeException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public final DalvInsn withRegisterOffset(int delta) { + return withRegisters(getRegisters().withOffset(delta)); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/ZeroSizeInsn.java b/dexgen/src/com/android/dexgen/dex/code/ZeroSizeInsn.java new file mode 100644 index 0000000..3c8c94a --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/ZeroSizeInsn.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code; + +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.code.SourcePosition; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Pseudo-instruction base class for zero-size (no code emitted) + * instructions, which are generally used for tracking metainformation + * about the code they are adjacent to. + */ +public abstract class ZeroSizeInsn extends DalvInsn { + /** + * Constructs an instance. The output address of this instance is initially + * unknown ({@code -1}). + * + * @param position {@code non-null;} source position + */ + public ZeroSizeInsn(SourcePosition position) { + super(Dops.SPECIAL_FORMAT, position, RegisterSpecList.EMPTY); + } + + /** {@inheritDoc} */ + @Override + public final int codeSize() { + return 0; + } + + /** {@inheritDoc} */ + @Override + public final void writeTo(AnnotatedOutput out) { + // Nothing to do here, for this class. + } + + /** {@inheritDoc} */ + @Override + public final DalvInsn withOpcode(Dop opcode) { + throw new RuntimeException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public DalvInsn withRegisterOffset(int delta) { + return withRegisters(getRegisters().withOffset(delta)); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form10t.java b/dexgen/src/com/android/dexgen/dex/code/form/Form10t.java new file mode 100644 index 0000000..4784fc3 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form10t.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.dex.code.TargetInsn; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 10t}. See the instruction format spec + * for details. + */ +public final class Form10t extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form10t(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form10t() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + return branchString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + return branchComment(insn); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 1; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!((insn instanceof TargetInsn) && + (insn.getRegisters().size() == 0))) { + return false; + } + + TargetInsn ti = (TargetInsn) insn; + return ti.hasTargetOffset() ? branchFits(ti) : true; + } + + /** {@inheritDoc} */ + @Override + public boolean branchFits(TargetInsn insn) { + int offset = insn.getTargetOffset(); + + // Note: A zero offset would fit, but it is prohibited by the spec. + return (offset != 0) && signedFitsInByte(offset); + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return Form20t.THE_ONE; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + int offset = ((TargetInsn) insn).getTargetOffset(); + + write(out, opcodeUnit(insn, (offset & 0xff))); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form10x.java b/dexgen/src/com/android/dexgen/dex/code/form/Form10x.java new file mode 100644 index 0000000..63c861c --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form10x.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.dex.code.SimpleInsn; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 10x}. See the instruction format spec + * for details. + */ +public final class Form10x extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form10x(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form10x() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + // This format has no arguments. + return ""; + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + // This format has no comment. + return ""; + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 1; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + return (insn instanceof SimpleInsn) && + (insn.getRegisters().size() == 0); + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return null; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + write(out, opcodeUnit(insn, 0)); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form11n.java b/dexgen/src/com/android/dexgen/dex/code/form/Form11n.java new file mode 100644 index 0000000..511d7d1 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form11n.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.CstInsn; +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstLiteralBits; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 11n}. See the instruction format spec + * for details. + */ +public final class Form11n extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form11n(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form11n() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return regs.get(0).regString() + ", " + literalBitsString(value); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + return literalBitsComment(value, 4); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 1; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + + if (!((insn instanceof CstInsn) && + (regs.size() == 1) && + unsignedFitsInNibble(regs.get(0).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + if (!(cst instanceof CstLiteralBits)) { + return false; + } + + CstLiteralBits cb = (CstLiteralBits) cst; + + return cb.fitsInInt() && signedFitsInNibble(cb.getIntBits()); + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return Form21s.THE_ONE; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int value = + ((CstLiteralBits) ((CstInsn) insn).getConstant()).getIntBits(); + + write(out, + opcodeUnit(insn, makeByte(regs.get(0).getReg(), value & 0xf))); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form11x.java b/dexgen/src/com/android/dexgen/dex/code/form/Form11x.java new file mode 100644 index 0000000..8bf9bba --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form11x.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.dex.code.SimpleInsn; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 11x}. See the instruction format spec + * for details. + */ +public final class Form11x extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form11x(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form11x() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + // This format has no comment. + return ""; + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 1; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return (insn instanceof SimpleInsn) && + (regs.size() == 1) && + unsignedFitsInByte(regs.get(0).getReg()); + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return null; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + write(out, opcodeUnit(insn, regs.get(0).getReg())); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form12x.java b/dexgen/src/com/android/dexgen/dex/code/form/Form12x.java new file mode 100644 index 0000000..d55a66a --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form12x.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.HighRegisterPrefix; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.dex.code.SimpleInsn; +import com.android.dexgen.rop.code.RegisterSpec; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 12x}. See the instruction format spec + * for details. + */ +public final class Form12x extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form12x(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form12x() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int sz = regs.size(); + + /* + * The (sz - 2) and (sz - 1) below makes this code work for + * both the two- and three-register ops. (See "case 3" in + * isCompatible(), below.) + */ + + return regs.get(sz - 2).regString() + ", " + + regs.get(sz - 1).regString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + // This format has no comment. + return ""; + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 1; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!(insn instanceof SimpleInsn)) { + return false; + } + + RegisterSpecList regs = insn.getRegisters(); + RegisterSpec rs1; + RegisterSpec rs2; + + switch (regs.size()) { + case 2: { + rs1 = regs.get(0); + rs2 = regs.get(1); + break; + } + case 3: { + /* + * This format is allowed for ops that are effectively + * 3-arg but where the first two args are identical. + */ + rs1 = regs.get(1); + rs2 = regs.get(2); + if (rs1.getReg() != regs.get(0).getReg()) { + return false; + } + break; + } + default: { + return false; + } + } + + return unsignedFitsInNibble(rs1.getReg()) && + unsignedFitsInNibble(rs2.getReg()); + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return Form22x.THE_ONE; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int sz = regs.size(); + + /* + * The (sz - 2) and (sz - 1) below makes this code work for + * both the two- and three-register ops. (See "case 3" in + * isCompatible(), above.) + */ + + write(out, opcodeUnit(insn, + makeByte(regs.get(sz - 2).getReg(), + regs.get(sz - 1).getReg()))); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form20t.java b/dexgen/src/com/android/dexgen/dex/code/form/Form20t.java new file mode 100644 index 0000000..2760606 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form20t.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.dex.code.TargetInsn; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 20t}. See the instruction format spec + * for details. + */ +public final class Form20t extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form20t(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form20t() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + return branchString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + return branchComment(insn); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!((insn instanceof TargetInsn) && + (insn.getRegisters().size() == 0))) { + return false; + } + + TargetInsn ti = (TargetInsn) insn; + return ti.hasTargetOffset() ? branchFits(ti) : true; + } + + /** {@inheritDoc} */ + @Override + public boolean branchFits(TargetInsn insn) { + int offset = insn.getTargetOffset(); + + // Note: A zero offset would fit, but it is prohibited by the spec. + return (offset != 0) && signedFitsInShort(offset); + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return Form30t.THE_ONE; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + int offset = ((TargetInsn) insn).getTargetOffset(); + + write(out, opcodeUnit(insn, 0), (short) offset); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form21c.java b/dexgen/src/com/android/dexgen/dex/code/form/Form21c.java new file mode 100644 index 0000000..33df3d6 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form21c.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.CstInsn; +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.rop.code.RegisterSpec; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstFieldRef; +import com.android.dexgen.rop.cst.CstString; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 21c}. See the instruction format spec + * for details. + */ +public final class Form21c extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form21c(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form21c() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + cstString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + if (noteIndices) { + return cstComment(insn); + } else { + return ""; + } + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!(insn instanceof CstInsn)) { + return false; + } + + RegisterSpecList regs = insn.getRegisters(); + RegisterSpec reg; + + switch (regs.size()) { + case 1: { + reg = regs.get(0); + break; + } + case 2: { + /* + * This format is allowed for ops that are effectively + * 2-arg but where the two args are identical. + */ + reg = regs.get(0); + if (reg.getReg() != regs.get(1).getReg()) { + return false; + } + break; + } + default: { + return false; + } + } + + if (!unsignedFitsInByte(reg.getReg())) { + return false; + } + + CstInsn ci = (CstInsn) insn; + int cpi = ci.getIndex(); + + if (! unsignedFitsInShort(cpi)) { + return false; + } + + Constant cst = ci.getConstant(); + return (cst instanceof CstType) || + (cst instanceof CstFieldRef) || + (cst instanceof CstString); + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return Form31c.THE_ONE; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int cpi = ((CstInsn) insn).getIndex(); + + write(out, + opcodeUnit(insn, regs.get(0).getReg()), + (short) cpi); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form21h.java b/dexgen/src/com/android/dexgen/dex/code/form/Form21h.java new file mode 100644 index 0000000..ee6ed3e --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form21h.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.CstInsn; +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstLiteralBits; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 21h}. See the instruction format spec + * for details. + */ +public final class Form21h extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form21h(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form21h() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return regs.get(0).regString() + ", " + literalBitsString(value); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return + literalBitsComment(value, + (regs.get(0).getCategory() == 1) ? 32 : 64); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + if (!((insn instanceof CstInsn) && + (regs.size() == 1) && + unsignedFitsInByte(regs.get(0).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + if (!(cst instanceof CstLiteralBits)) { + return false; + } + + CstLiteralBits cb = (CstLiteralBits) cst; + + // Where the high bits are depends on the category of the target. + if (regs.get(0).getCategory() == 1) { + int bits = cb.getIntBits(); + return ((bits & 0xffff) == 0); + } else { + long bits = cb.getLongBits(); + return ((bits & 0xffffffffffffL) == 0); + } + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return Form31i.THE_ONE; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits cb = (CstLiteralBits) ((CstInsn) insn).getConstant(); + short bits; + + // Where the high bits are depends on the category of the target. + if (regs.get(0).getCategory() == 1) { + bits = (short) (cb.getIntBits() >>> 16); + } else { + bits = (short) (cb.getLongBits() >>> 48); + } + + write(out, opcodeUnit(insn, regs.get(0).getReg()), bits); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form21s.java b/dexgen/src/com/android/dexgen/dex/code/form/Form21s.java new file mode 100644 index 0000000..4b853d0 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form21s.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.CstInsn; +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstLiteralBits; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 21s}. See the instruction format spec + * for details. + */ +public final class Form21s extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form21s(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form21s() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return regs.get(0).regString() + ", " + literalBitsString(value); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + return literalBitsComment(value, 16); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + if (!((insn instanceof CstInsn) && + (regs.size() == 1) && + unsignedFitsInByte(regs.get(0).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + if (!(cst instanceof CstLiteralBits)) { + return false; + } + + CstLiteralBits cb = (CstLiteralBits) cst; + + return cb.fitsInInt() && signedFitsInShort(cb.getIntBits()); + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return Form21h.THE_ONE; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int value = + ((CstLiteralBits) ((CstInsn) insn).getConstant()).getIntBits(); + + write(out, + opcodeUnit(insn, regs.get(0).getReg()), + (short) value); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form21t.java b/dexgen/src/com/android/dexgen/dex/code/form/Form21t.java new file mode 100644 index 0000000..61599f6 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form21t.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.dex.code.TargetInsn; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 21t}. See the instruction format spec + * for details. + */ +public final class Form21t extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form21t(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form21t() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + branchString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + return branchComment(insn); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + + if (!((insn instanceof TargetInsn) && + (regs.size() == 1) && + unsignedFitsInByte(regs.get(0).getReg()))) { + return false; + } + + TargetInsn ti = (TargetInsn) insn; + return ti.hasTargetOffset() ? branchFits(ti) : true; + } + + /** {@inheritDoc} */ + @Override + public boolean branchFits(TargetInsn insn) { + int offset = insn.getTargetOffset(); + + // Note: A zero offset would fit, but it is prohibited by the spec. + return (offset != 0) && signedFitsInShort(offset); + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return Form31t.THE_ONE; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int offset = ((TargetInsn) insn).getTargetOffset(); + + write(out, + opcodeUnit(insn, regs.get(0).getReg()), + (short) offset); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form22b.java b/dexgen/src/com/android/dexgen/dex/code/form/Form22b.java new file mode 100644 index 0000000..6c37d57 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form22b.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.CstInsn; +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstLiteralBits; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 22b}. See the instruction format spec + * for details. + */ +public final class Form22b extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form22b(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form22b() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return regs.get(0).regString() + ", " + regs.get(1).regString() + + ", " + literalBitsString(value); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + return literalBitsComment(value, 8); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + if (!((insn instanceof CstInsn) && + (regs.size() == 2) && + unsignedFitsInByte(regs.get(0).getReg()) && + unsignedFitsInByte(regs.get(1).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + if (!(cst instanceof CstLiteralBits)) { + return false; + } + + CstLiteralBits cb = (CstLiteralBits) cst; + + return cb.fitsInInt() && signedFitsInByte(cb.getIntBits()); + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return Form22s.THE_ONE; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int value = + ((CstLiteralBits) ((CstInsn) insn).getConstant()).getIntBits(); + + write(out, + opcodeUnit(insn, regs.get(0).getReg()), + codeUnit(regs.get(1).getReg(), value & 0xff)); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form22c.java b/dexgen/src/com/android/dexgen/dex/code/form/Form22c.java new file mode 100644 index 0000000..b089ec4 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form22c.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.CstInsn; +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstFieldRef; +import com.android.dexgen.rop.cst.CstString; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 22c}. See the instruction format spec + * for details. + */ +public final class Form22c extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form22c(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form22c() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + regs.get(1).regString() + + ", " + cstString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + if (noteIndices) { + return cstComment(insn); + } else { + return ""; + } + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + if (!((insn instanceof CstInsn) && + (regs.size() == 2) && + unsignedFitsInNibble(regs.get(0).getReg()) && + unsignedFitsInNibble(regs.get(1).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + int cpi = ci.getIndex(); + + if (! unsignedFitsInShort(cpi)) { + return false; + } + + Constant cst = ci.getConstant(); + return (cst instanceof CstType) || + (cst instanceof CstFieldRef); + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return null; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int cpi = ((CstInsn) insn).getIndex(); + + write(out, + opcodeUnit(insn, + makeByte(regs.get(0).getReg(), regs.get(1).getReg())), + (short) cpi); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form22s.java b/dexgen/src/com/android/dexgen/dex/code/form/Form22s.java new file mode 100644 index 0000000..0eca280 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form22s.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.CstInsn; +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstLiteralBits; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 22s}. See the instruction format spec + * for details. + */ +public final class Form22s extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form22s(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form22s() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return regs.get(0).regString() + ", " + regs.get(1).regString() + + ", " + literalBitsString(value); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + return literalBitsComment(value, 16); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + if (!((insn instanceof CstInsn) && + (regs.size() == 2) && + unsignedFitsInNibble(regs.get(0).getReg()) && + unsignedFitsInNibble(regs.get(1).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + if (!(cst instanceof CstLiteralBits)) { + return false; + } + + CstLiteralBits cb = (CstLiteralBits) cst; + + return cb.fitsInInt() && signedFitsInShort(cb.getIntBits()); + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return null; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int value = + ((CstLiteralBits) ((CstInsn) insn).getConstant()).getIntBits(); + + write(out, + opcodeUnit(insn, + makeByte(regs.get(0).getReg(), regs.get(1).getReg())), + (short) value); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form22t.java b/dexgen/src/com/android/dexgen/dex/code/form/Form22t.java new file mode 100644 index 0000000..707bb97 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form22t.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.dex.code.TargetInsn; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 22t}. See the instruction format spec + * for details. + */ +public final class Form22t extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form22t(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form22t() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + regs.get(1).regString() + + ", " + branchString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + return branchComment(insn); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + + if (!((insn instanceof TargetInsn) && + (regs.size() == 2) && + unsignedFitsInNibble(regs.get(0).getReg()) && + unsignedFitsInNibble(regs.get(1).getReg()))) { + return false; + } + + TargetInsn ti = (TargetInsn) insn; + return ti.hasTargetOffset() ? branchFits(ti) : true; + } + + /** {@inheritDoc} */ + @Override + public boolean branchFits(TargetInsn insn) { + int offset = insn.getTargetOffset(); + + // Note: A zero offset would fit, but it is prohibited by the spec. + return (offset != 0) && signedFitsInShort(offset); + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return null; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int offset = ((TargetInsn) insn).getTargetOffset(); + + write(out, + opcodeUnit(insn, + makeByte(regs.get(0).getReg(), regs.get(1).getReg())), + (short) offset); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form22x.java b/dexgen/src/com/android/dexgen/dex/code/form/Form22x.java new file mode 100644 index 0000000..bd6a8df --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form22x.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.dex.code.SimpleInsn; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 22x}. See the instruction format spec + * for details. + */ +public final class Form22x extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form22x(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form22x() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + regs.get(1).regString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + // This format has no comment. + return ""; + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + + return (insn instanceof SimpleInsn) && + (regs.size() == 2) && + unsignedFitsInByte(regs.get(0).getReg()) && + unsignedFitsInShort(regs.get(1).getReg()); + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return Form23x.THE_ONE; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + write(out, + opcodeUnit(insn, regs.get(0).getReg()), + (short) regs.get(1).getReg()); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form23x.java b/dexgen/src/com/android/dexgen/dex/code/form/Form23x.java new file mode 100644 index 0000000..eaf1cee --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form23x.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.dex.code.SimpleInsn; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 23x}. See the instruction format spec + * for details. + */ +public final class Form23x extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form23x(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form23x() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + regs.get(1).regString() + + ", " + regs.get(2).regString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + // This format has no comment. + return ""; + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 2; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + + return (insn instanceof SimpleInsn) && + (regs.size() == 3) && + unsignedFitsInByte(regs.get(0).getReg()) && + unsignedFitsInByte(regs.get(1).getReg()) && + unsignedFitsInByte(regs.get(2).getReg()); + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return Form32x.THE_ONE; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + write(out, + opcodeUnit(insn, regs.get(0).getReg()), + codeUnit(regs.get(1).getReg(), regs.get(2).getReg())); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form30t.java b/dexgen/src/com/android/dexgen/dex/code/form/Form30t.java new file mode 100644 index 0000000..0909ec8 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form30t.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.dex.code.TargetInsn; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 30t}. See the instruction format spec + * for details. + */ +public final class Form30t extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form30t(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form30t() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + return branchString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + return branchComment(insn); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 3; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!((insn instanceof TargetInsn) && + (insn.getRegisters().size() == 0))) { + return false; + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public boolean branchFits(TargetInsn insn) { + return true; + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return null; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + int offset = ((TargetInsn) insn).getTargetOffset(); + + write(out, opcodeUnit(insn, 0), + (short) offset, + (short) (offset >> 16)); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form31c.java b/dexgen/src/com/android/dexgen/dex/code/form/Form31c.java new file mode 100644 index 0000000..c87a451 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form31c.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.CstInsn; +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.rop.code.RegisterSpec; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstFieldRef; +import com.android.dexgen.rop.cst.CstString; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 31c}. See the instruction format spec + * for details. + */ +public final class Form31c extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form31c(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form31c() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + cstString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + if (noteIndices) { + return cstComment(insn); + } else { + return ""; + } + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 3; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!(insn instanceof CstInsn)) { + return false; + } + + RegisterSpecList regs = insn.getRegisters(); + RegisterSpec reg; + + switch (regs.size()) { + case 1: { + reg = regs.get(0); + break; + } + case 2: { + /* + * This format is allowed for ops that are effectively + * 2-arg but where the two args are identical. + */ + reg = regs.get(0); + if (reg.getReg() != regs.get(1).getReg()) { + return false; + } + break; + } + default: { + return false; + } + } + + if (!unsignedFitsInByte(reg.getReg())) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + return ((cst instanceof CstType) || + (cst instanceof CstFieldRef) || + (cst instanceof CstString)); + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return null; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int cpi = ((CstInsn) insn).getIndex(); + + write(out, + opcodeUnit(insn, regs.get(0).getReg()), + (short) cpi, + (short) (cpi >> 16)); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form31i.java b/dexgen/src/com/android/dexgen/dex/code/form/Form31i.java new file mode 100644 index 0000000..e74ea86 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form31i.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.CstInsn; +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstLiteralBits; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 31i}. See the instruction format spec + * for details. + */ +public final class Form31i extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form31i(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form31i() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return regs.get(0).regString() + ", " + literalBitsString(value); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + return literalBitsComment(value, 32); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 3; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + if (!((insn instanceof CstInsn) && + (regs.size() == 1) && + unsignedFitsInByte(regs.get(0).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + if (!(cst instanceof CstLiteralBits)) { + return false; + } + + return ((CstLiteralBits) cst).fitsInInt(); + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return Form51l.THE_ONE; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int value = + ((CstLiteralBits) ((CstInsn) insn).getConstant()).getIntBits(); + + write(out, + opcodeUnit(insn, regs.get(0).getReg()), + (short) value, + (short) (value >> 16)); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form31t.java b/dexgen/src/com/android/dexgen/dex/code/form/Form31t.java new file mode 100644 index 0000000..212f93b --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form31t.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.dex.code.TargetInsn; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 31t}. See the instruction format spec + * for details. + */ +public final class Form31t extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form31t(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form31t() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + branchString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + return branchComment(insn); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 3; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + + if (!((insn instanceof TargetInsn) && + (regs.size() == 1) && + unsignedFitsInByte(regs.get(0).getReg()))) { + return false; + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public boolean branchFits(TargetInsn insn) { + return true; + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return null; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int offset = ((TargetInsn) insn).getTargetOffset(); + + write(out, opcodeUnit(insn, regs.get(0).getReg()), + (short) offset, + (short) (offset >> 16)); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form32x.java b/dexgen/src/com/android/dexgen/dex/code/form/Form32x.java new file mode 100644 index 0000000..097fb65 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form32x.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.dex.code.SimpleInsn; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 32x}. See the instruction format spec + * for details. + */ +public final class Form32x extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form32x(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form32x() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return regs.get(0).regString() + ", " + regs.get(1).regString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + // This format has no comment. + return ""; + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 3; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + return (insn instanceof SimpleInsn) && + (regs.size() == 2) && + unsignedFitsInShort(regs.get(0).getReg()) && + unsignedFitsInShort(regs.get(1).getReg()); + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return null; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + + write(out, + opcodeUnit(insn, 0), + (short) regs.get(0).getReg(), + (short) regs.get(1).getReg()); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form35c.java b/dexgen/src/com/android/dexgen/dex/code/form/Form35c.java new file mode 100644 index 0000000..147aac1 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form35c.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.CstInsn; +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.rop.code.RegisterSpec; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstMethodRef; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 35c}. See the instruction format spec + * for details. + */ +public final class Form35c extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form35c(); + + /** Maximal number of operands */ + private static final int MAX_NUM_OPS = 5; + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form35c() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = explicitize(insn.getRegisters()); + return regListString(regs) + ", " + cstString(insn); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + if (noteIndices) { + return cstComment(insn); + } else { + return ""; + } + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 3; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!(insn instanceof CstInsn)) { + return false; + } + + CstInsn ci = (CstInsn) insn; + int cpi = ci.getIndex(); + + if (! unsignedFitsInShort(cpi)) { + return false; + } + + Constant cst = ci.getConstant(); + if (!((cst instanceof CstMethodRef) || + (cst instanceof CstType))) { + return false; + } + + RegisterSpecList regs = ci.getRegisters(); + return (wordCount(regs) >= 0); + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return Form3rc.THE_ONE; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + int cpi = ((CstInsn) insn).getIndex(); + RegisterSpecList regs = explicitize(insn.getRegisters()); + int sz = regs.size(); + int r0 = (sz > 0) ? regs.get(0).getReg() : 0; + int r1 = (sz > 1) ? regs.get(1).getReg() : 0; + int r2 = (sz > 2) ? regs.get(2).getReg() : 0; + int r3 = (sz > 3) ? regs.get(3).getReg() : 0; + int r4 = (sz > 4) ? regs.get(4).getReg() : 0; + + write(out, + opcodeUnit(insn, + makeByte(r4, sz)), // encode the fifth operand here + (short) cpi, + codeUnit(r0, r1, r2, r3)); + } + + /** + * Gets the number of words required for the given register list, where + * category-2 values count as two words. Return {@code -1} if the + * list requires more than five words or contains registers that need + * more than a nibble to identify them. + * + * @param regs {@code non-null;} the register list in question + * @return {@code >= -1;} the number of words required, or {@code -1} + * if the list couldn't possibly fit in this format + */ + private static int wordCount(RegisterSpecList regs) { + int sz = regs.size(); + + if (sz > MAX_NUM_OPS) { + // It can't possibly fit. + return -1; + } + + int result = 0; + + for (int i = 0; i < sz; i++) { + RegisterSpec one = regs.get(i); + result += one.getCategory(); + /* + * The check below adds (category - 1) to the register, to + * account for the fact that the second half of a + * category-2 register has to be represented explicitly in + * the result. + */ + if (!unsignedFitsInNibble(one.getReg() + one.getCategory() - 1)) { + return -1; + } + } + + return (result <= MAX_NUM_OPS) ? result : -1; + } + + /** + * Returns a register list which is equivalent to the given one, + * except that it splits category-2 registers into two explicit + * entries. This returns the original list if no modification is + * required + * + * @param orig {@code non-null;} the original list + * @return {@code non-null;} the list with the described transformation + */ + private static RegisterSpecList explicitize(RegisterSpecList orig) { + int wordCount = wordCount(orig); + int sz = orig.size(); + + if (wordCount == sz) { + return orig; + } + + RegisterSpecList result = new RegisterSpecList(wordCount); + int wordAt = 0; + + for (int i = 0; i < sz; i++) { + RegisterSpec one = orig.get(i); + result.set(wordAt, one); + if (one.getCategory() == 2) { + result.set(wordAt + 1, + RegisterSpec.make(one.getReg() + 1, Type.VOID)); + wordAt += 2; + } else { + wordAt++; + } + } + + result.setImmutable(); + return result; + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form3rc.java b/dexgen/src/com/android/dexgen/dex/code/form/Form3rc.java new file mode 100644 index 0000000..a061c6f --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form3rc.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.CstInsn; +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.rop.code.RegisterSpec; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstMethodRef; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 3rc}. See the instruction format spec + * for details. + */ +public final class Form3rc extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form3rc(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form3rc() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int size = regs.size(); + StringBuilder sb = new StringBuilder(30); + + sb.append("{"); + + switch (size) { + case 0: { + // Nothing to do. + break; + } + case 1: { + sb.append(regs.get(0).regString()); + break; + } + default: { + RegisterSpec lastReg = regs.get(size - 1); + if (lastReg.getCategory() == 2) { + /* + * Add one to properly represent a list-final + * category-2 register. + */ + lastReg = lastReg.withOffset(1); + } + + sb.append(regs.get(0).regString()); + sb.append(".."); + sb.append(lastReg.regString()); + } + } + + sb.append("}, "); + sb.append(cstString(insn)); + + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + if (noteIndices) { + return cstComment(insn); + } else { + return ""; + } + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 3; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + if (!(insn instanceof CstInsn)) { + return false; + } + + CstInsn ci = (CstInsn) insn; + int cpi = ci.getIndex(); + + if (! unsignedFitsInShort(cpi)) { + return false; + } + + Constant cst = ci.getConstant(); + if (!((cst instanceof CstMethodRef) || + (cst instanceof CstType))) { + return false; + } + + RegisterSpecList regs = ci.getRegisters(); + int sz = regs.size(); + + if (sz == 0) { + return true; + } + + int first = regs.get(0).getReg(); + int next = first; + + if (!unsignedFitsInShort(first)) { + return false; + } + + for (int i = 0; i < sz; i++) { + RegisterSpec one = regs.get(i); + if (one.getReg() != next) { + return false; + } + next += one.getCategory(); + } + + return unsignedFitsInByte(next - first); + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return null; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + int sz = regs.size(); + int cpi = ((CstInsn) insn).getIndex(); + int firstReg; + int count; + + if (sz == 0) { + firstReg = 0; + count = 0; + } else { + int lastReg = regs.get(sz - 1).getNextReg(); + firstReg = regs.get(0).getReg(); + count = lastReg - firstReg; + } + + write(out, + opcodeUnit(insn, count), + (short) cpi, + (short) firstReg); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/Form51l.java b/dexgen/src/com/android/dexgen/dex/code/form/Form51l.java new file mode 100644 index 0000000..537eaa9 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/Form51l.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.CstInsn; +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.rop.code.RegisterSpecList; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstLiteral64; +import com.android.dexgen.rop.cst.CstLiteralBits; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format {@code 51l}. See the instruction format spec + * for details. + */ +public final class Form51l extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new Form51l(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private Form51l() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + + return regs.get(0).regString() + ", " + literalBitsString(value); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + CstLiteralBits value = (CstLiteralBits) ((CstInsn) insn).getConstant(); + return literalBitsComment(value, 64); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + return 5; + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + if (!((insn instanceof CstInsn) && + (regs.size() == 1) && + unsignedFitsInByte(regs.get(0).getReg()))) { + return false; + } + + CstInsn ci = (CstInsn) insn; + Constant cst = ci.getConstant(); + + return (cst instanceof CstLiteral64); + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + return null; + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + RegisterSpecList regs = insn.getRegisters(); + long value = + ((CstLiteral64) ((CstInsn) insn).getConstant()).getLongBits(); + + write(out, + opcodeUnit(insn, regs.get(0).getReg()), + (short) value, + (short) (value >> 16), + (short) (value >> 32), + (short) (value >> 48)); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/code/form/SpecialFormat.java b/dexgen/src/com/android/dexgen/dex/code/form/SpecialFormat.java new file mode 100644 index 0000000..c75f18f --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/code/form/SpecialFormat.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.code.form; + +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.DalvOps; +import com.android.dexgen.dex.code.InsnFormat; +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Instruction format for nonstandard format instructions, which aren't + * generally real instructions but do end up appearing in instruction + * lists. Most of the overridden methods on this class end up throwing + * exceptions, as code should know (implicitly or explicitly) to avoid + * using this class. The one exception is {@link #isCompatible}, which + * always returns {@code true}. + */ +public final class SpecialFormat extends InsnFormat { + /** {@code non-null;} unique instance of this class */ + public static final InsnFormat THE_ONE = new SpecialFormat(); + + /** + * Constructs an instance. This class is not publicly + * instantiable. Use {@link #THE_ONE}. + */ + private SpecialFormat() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public String insnArgString(DalvInsn insn) { + throw new RuntimeException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public String insnCommentString(DalvInsn insn, boolean noteIndices) { + throw new RuntimeException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public int codeSize() { + throw new RuntimeException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public boolean isCompatible(DalvInsn insn) { + return true; + } + + /** {@inheritDoc} */ + @Override + public InsnFormat nextUp() { + throw new RuntimeException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(AnnotatedOutput out, DalvInsn insn) { + throw new RuntimeException("unsupported"); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/AnnotationItem.java b/dexgen/src/com/android/dexgen/dex/file/AnnotationItem.java new file mode 100644 index 0000000..a078bc0 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/AnnotationItem.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.annotation.Annotation; +import com.android.dexgen.rop.annotation.AnnotationVisibility; +import com.android.dexgen.rop.annotation.NameValuePair; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstAnnotation; +import com.android.dexgen.rop.cst.CstArray; +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.ByteArrayAnnotatedOutput; + +import java.util.Arrays; +import java.util.Comparator; + +/** + * Single annotation, which consists of a type and a set of name-value + * element pairs. + */ +public final class AnnotationItem extends OffsettedItem { + /** annotation visibility constant: visible at build time only */ + private static final int VISIBILITY_BUILD = 0; + + /** annotation visibility constant: visible at runtime */ + private static final int VISIBILITY_RUNTIME = 1; + + /** annotation visibility constant: visible at runtime only to system */ + private static final int VISIBILITY_SYSTEM = 2; + + /** the required alignment for instances of this class */ + private static final int ALIGNMENT = 1; + + /** {@code non-null;} unique instance of {@link #TypeIdSorter} */ + private static final TypeIdSorter TYPE_ID_SORTER = new TypeIdSorter(); + + /** {@code non-null;} the annotation to represent */ + private final Annotation annotation; + + /** + * {@code null-ok;} type reference for the annotation type; set during + * {@link #addContents} + */ + private TypeIdItem type; + + /** + * {@code null-ok;} encoded form, ready for writing to a file; set during + * {@link #place0} + */ + private byte[] encodedForm; + + /** + * Comparator that sorts (outer) instances by type id index. + */ + private static class TypeIdSorter implements Comparator<AnnotationItem> { + /** {@inheritDoc} */ + public int compare(AnnotationItem item1, AnnotationItem item2) { + int index1 = item1.type.getIndex(); + int index2 = item2.type.getIndex(); + + if (index1 < index2) { + return -1; + } else if (index1 > index2) { + return 1; + } + + return 0; + } + } + + /** + * Sorts an array of instances, in place, by type id index, + * ignoring all other aspects of the elements. This is only valid + * to use after type id indices are known. + * + * @param array {@code non-null;} array to sort + */ + public static void sortByTypeIdIndex(AnnotationItem[] array) { + Arrays.sort(array, TYPE_ID_SORTER); + } + + /** + * Constructs an instance. + * + * @param annotation {@code non-null;} annotation to represent + */ + public AnnotationItem(Annotation annotation) { + /* + * The write size isn't known up-front because (the variable-lengthed) + * leb128 type is used to represent some things. + */ + super(ALIGNMENT, -1); + + if (annotation == null) { + throw new NullPointerException("annotation == null"); + } + + this.annotation = annotation; + this.type = null; + this.encodedForm = null; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_ANNOTATION_ITEM; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return annotation.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(OffsettedItem other) { + AnnotationItem otherAnnotation = (AnnotationItem) other; + + return annotation.compareTo(otherAnnotation.annotation); + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return annotation.toHuman(); + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + type = file.getTypeIds().intern(annotation.getType()); + ValueEncoder.addContents(file, annotation); + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + // Encode the data and note the size. + + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(); + ValueEncoder encoder = new ValueEncoder(addedTo.getFile(), out); + + encoder.writeAnnotation(annotation, false); + encodedForm = out.toByteArray(); + + // Add one for the visibility byte in front of the encoded annotation. + setWriteSize(encodedForm.length + 1); + } + + /** + * Write a (listing file) annotation for this instance to the given + * output, that consumes no bytes of output. This is for annotating + * a reference to this instance at the point of the reference. + * + * @param out {@code non-null;} where to output to + * @param prefix {@code non-null;} prefix for each line of output + */ + public void annotateTo(AnnotatedOutput out, String prefix) { + out.annotate(0, prefix + "visibility: " + + annotation.getVisibility().toHuman()); + out.annotate(0, prefix + "type: " + annotation.getType().toHuman()); + + for (NameValuePair pair : annotation.getNameValuePairs()) { + CstUtf8 name = pair.getName(); + Constant value = pair.getValue(); + + out.annotate(0, prefix + name.toHuman() + ": " + + ValueEncoder.constantToHuman(value)); + } + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + AnnotationVisibility visibility = annotation.getVisibility(); + + if (annotates) { + out.annotate(0, offsetString() + " annotation"); + out.annotate(1, " visibility: VISBILITY_" + visibility); + } + + switch (visibility) { + case BUILD: out.writeByte(VISIBILITY_BUILD); break; + case RUNTIME: out.writeByte(VISIBILITY_RUNTIME); break; + case SYSTEM: out.writeByte(VISIBILITY_SYSTEM); break; + default: { + // EMBEDDED shouldn't appear at the top level. + throw new RuntimeException("shouldn't happen"); + } + } + + if (annotates) { + /* + * The output is to be annotated, so redo the work previously + * done by place0(), except this time annotations will actually + * get emitted. + */ + ValueEncoder encoder = new ValueEncoder(file, out); + encoder.writeAnnotation(annotation, true); + } else { + out.write(encodedForm); + } + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/AnnotationSetItem.java b/dexgen/src/com/android/dexgen/dex/file/AnnotationSetItem.java new file mode 100644 index 0000000..46ea2f0 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/AnnotationSetItem.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.annotation.Annotation; +import com.android.dexgen.rop.annotation.Annotations; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; + +/** + * Set of annotations, where no annotation type appears more than once. + */ +public final class AnnotationSetItem extends OffsettedItem { + /** the required alignment for instances of this class */ + private static final int ALIGNMENT = 4; + + /** the size of an entry int the set: one {@code uint} */ + private static final int ENTRY_WRITE_SIZE = 4; + + /** {@code non-null;} the set of annotations */ + private final Annotations annotations; + + /** + * {@code non-null;} set of annotations as individual items in an array. + * <b>Note:</b> The contents have to get sorted by type id before + * writing. + */ + private final AnnotationItem[] items; + + /** + * Constructs an instance. + * + * @param annotations {@code non-null;} set of annotations + */ + public AnnotationSetItem(Annotations annotations) { + super(ALIGNMENT, writeSize(annotations)); + + this.annotations = annotations; + this.items = new AnnotationItem[annotations.size()]; + + int at = 0; + for (Annotation a : annotations.getAnnotations()) { + items[at] = new AnnotationItem(a); + at++; + } + } + + /** + * Gets the write size for the given set. + * + * @param annotations {@code non-null;} the set + * @return {@code > 0;} the write size + */ + private static int writeSize(Annotations annotations) { + // This includes an int size at the start of the list. + + try { + return (annotations.size() * ENTRY_WRITE_SIZE) + 4; + } catch (NullPointerException ex) { + // Elucidate the exception. + throw new NullPointerException("list == null"); + } + } + + /** + * Gets the underlying annotations of this instance + * + * @return {@code non-null;} the annotations + */ + public Annotations getAnnotations() { + return annotations; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return annotations.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(OffsettedItem other) { + AnnotationSetItem otherSet = (AnnotationSetItem) other; + + return annotations.compareTo(otherSet.annotations); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_ANNOTATION_SET_ITEM; + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return annotations.toString(); + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + MixedItemSection byteData = file.getByteData(); + int size = items.length; + + for (int i = 0; i < size; i++) { + items[i] = byteData.intern(items[i]); + } + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + // Sort the array to be in type id index order. + AnnotationItem.sortByTypeIdIndex(items); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + int size = items.length; + + if (annotates) { + out.annotate(0, offsetString() + " annotation set"); + out.annotate(4, " size: " + Hex.u4(size)); + } + + out.writeInt(size); + + for (int i = 0; i < size; i++) { + AnnotationItem item = items[i]; + int offset = item.getAbsoluteOffset(); + + if (annotates) { + out.annotate(4, " entries[" + Integer.toHexString(i) + "]: " + + Hex.u4(offset)); + items[i].annotateTo(out, " "); + } + + out.writeInt(offset); + } + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/AnnotationSetRefItem.java b/dexgen/src/com/android/dexgen/dex/file/AnnotationSetRefItem.java new file mode 100644 index 0000000..b876ce0 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/AnnotationSetRefItem.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; + +/** + * Indirect reference to an {@link AnnotationSetItem}. + */ +public final class AnnotationSetRefItem extends OffsettedItem { + /** the required alignment for instances of this class */ + private static final int ALIGNMENT = 4; + + /** write size of this class, in bytes */ + private static final int WRITE_SIZE = 4; + + /** {@code non-null;} the annotation set to refer to */ + private AnnotationSetItem annotations; + + /** + * Constructs an instance. + * + * @param annotations {@code non-null;} the annotation set to refer to + */ + public AnnotationSetRefItem(AnnotationSetItem annotations) { + super(ALIGNMENT, WRITE_SIZE); + + if (annotations == null) { + throw new NullPointerException("annotations == null"); + } + + this.annotations = annotations; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_ANNOTATION_SET_REF_ITEM; + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + MixedItemSection wordData = file.getWordData(); + + annotations = wordData.intern(annotations); + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return annotations.toHuman(); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + int annotationsOff = annotations.getAbsoluteOffset(); + + if (out.annotates()) { + out.annotate(4, " annotations_off: " + Hex.u4(annotationsOff)); + } + + out.writeInt(annotationsOff); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/AnnotationUtils.java b/dexgen/src/com/android/dexgen/dex/file/AnnotationUtils.java new file mode 100644 index 0000000..111ba8a --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/AnnotationUtils.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.annotation.Annotation; +import com.android.dexgen.rop.annotation.NameValuePair; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstAnnotation; +import com.android.dexgen.rop.cst.CstArray; +import com.android.dexgen.rop.cst.CstInteger; +import com.android.dexgen.rop.cst.CstKnownNull; +import com.android.dexgen.rop.cst.CstMethodRef; +import com.android.dexgen.rop.cst.CstString; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.rop.type.TypeList; + +import java.util.ArrayList; + +import static com.android.dexgen.rop.annotation.AnnotationVisibility.*; + +/** + * Utility class for dealing with annotations. + */ +public final class AnnotationUtils { + /** {@code non-null;} type for {@code AnnotationDefault} annotations */ + private static final CstType ANNOTATION_DEFAULT_TYPE = + CstType.intern(Type.intern("Ldalvik/annotation/AnnotationDefault;")); + + /** {@code non-null;} type for {@code EnclosingClass} annotations */ + private static final CstType ENCLOSING_CLASS_TYPE = + CstType.intern(Type.intern("Ldalvik/annotation/EnclosingClass;")); + + /** {@code non-null;} type for {@code EnclosingMethod} annotations */ + private static final CstType ENCLOSING_METHOD_TYPE = + CstType.intern(Type.intern("Ldalvik/annotation/EnclosingMethod;")); + + /** {@code non-null;} type for {@code InnerClass} annotations */ + private static final CstType INNER_CLASS_TYPE = + CstType.intern(Type.intern("Ldalvik/annotation/InnerClass;")); + + /** {@code non-null;} type for {@code MemberClasses} annotations */ + private static final CstType MEMBER_CLASSES_TYPE = + CstType.intern(Type.intern("Ldalvik/annotation/MemberClasses;")); + + /** {@code non-null;} type for {@code Signature} annotations */ + private static final CstType SIGNATURE_TYPE = + CstType.intern(Type.intern("Ldalvik/annotation/Signature;")); + + /** {@code non-null;} type for {@code Throws} annotations */ + private static final CstType THROWS_TYPE = + CstType.intern(Type.intern("Ldalvik/annotation/Throws;")); + + /** {@code non-null;} the UTF-8 constant {@code "accessFlags"} */ + private static final CstUtf8 ACCESS_FLAGS_UTF = new CstUtf8("accessFlags"); + + /** {@code non-null;} the UTF-8 constant {@code "name"} */ + private static final CstUtf8 NAME_UTF = new CstUtf8("name"); + + /** {@code non-null;} the UTF-8 constant {@code "value"} */ + private static final CstUtf8 VALUE_UTF = new CstUtf8("value"); + + /** + * This class is uninstantiable. + */ + private AnnotationUtils() { + // This space intentionally left blank. + } + + /** + * Constructs a standard {@code AnnotationDefault} annotation. + * + * @param defaults {@code non-null;} the defaults, itself as an annotation + * @return {@code non-null;} the constructed annotation + */ + public static Annotation makeAnnotationDefault(Annotation defaults) { + Annotation result = new Annotation(ANNOTATION_DEFAULT_TYPE, SYSTEM); + + result.put(new NameValuePair(VALUE_UTF, new CstAnnotation(defaults))); + result.setImmutable(); + return result; + } + + /** + * Constructs a standard {@code EnclosingClass} annotation. + * + * @param clazz {@code non-null;} the enclosing class + * @return {@code non-null;} the annotation + */ + public static Annotation makeEnclosingClass(CstType clazz) { + Annotation result = new Annotation(ENCLOSING_CLASS_TYPE, SYSTEM); + + result.put(new NameValuePair(VALUE_UTF, clazz)); + result.setImmutable(); + return result; + } + + /** + * Constructs a standard {@code EnclosingMethod} annotation. + * + * @param method {@code non-null;} the enclosing method + * @return {@code non-null;} the annotation + */ + public static Annotation makeEnclosingMethod(CstMethodRef method) { + Annotation result = new Annotation(ENCLOSING_METHOD_TYPE, SYSTEM); + + result.put(new NameValuePair(VALUE_UTF, method)); + result.setImmutable(); + return result; + } + + /** + * Constructs a standard {@code InnerClass} annotation. + * + * @param name {@code null-ok;} the original name of the class, or + * {@code null} to represent an anonymous class + * @param accessFlags the original access flags + * @return {@code non-null;} the annotation + */ + public static Annotation makeInnerClass(CstUtf8 name, int accessFlags) { + Annotation result = new Annotation(INNER_CLASS_TYPE, SYSTEM); + Constant nameCst = + (name != null) ? new CstString(name) : CstKnownNull.THE_ONE; + + result.put(new NameValuePair(NAME_UTF, nameCst)); + result.put(new NameValuePair(ACCESS_FLAGS_UTF, + CstInteger.make(accessFlags))); + result.setImmutable(); + return result; + } + + /** + * Constructs a standard {@code MemberClasses} annotation. + * + * @param types {@code non-null;} the list of (the types of) the member classes + * @return {@code non-null;} the annotation + */ + public static Annotation makeMemberClasses(TypeList types) { + CstArray array = makeCstArray(types); + Annotation result = new Annotation(MEMBER_CLASSES_TYPE, SYSTEM); + result.put(new NameValuePair(VALUE_UTF, array)); + result.setImmutable(); + return result; + } + + /** + * Constructs a standard {@code Signature} annotation. + * + * @param signature {@code non-null;} the signature string + * @return {@code non-null;} the annotation + */ + public static Annotation makeSignature(CstUtf8 signature) { + Annotation result = new Annotation(SIGNATURE_TYPE, SYSTEM); + + /* + * Split the string into pieces that are likely to be common + * across many signatures and the rest of the file. + */ + + String raw = signature.getString(); + int rawLength = raw.length(); + ArrayList<String> pieces = new ArrayList<String>(20); + + for (int at = 0; at < rawLength; /*at*/) { + char c = raw.charAt(at); + int endAt = at + 1; + if (c == 'L') { + // Scan to ';' or '<'. Consume ';' but not '<'. + while (endAt < rawLength) { + c = raw.charAt(endAt); + if (c == ';') { + endAt++; + break; + } else if (c == '<') { + break; + } + endAt++; + } + } else { + // Scan to 'L' without consuming it. + while (endAt < rawLength) { + c = raw.charAt(endAt); + if (c == 'L') { + break; + } + endAt++; + } + } + + pieces.add(raw.substring(at, endAt)); + at = endAt; + } + + int size = pieces.size(); + CstArray.List list = new CstArray.List(size); + + for (int i = 0; i < size; i++) { + list.set(i, new CstString(pieces.get(i))); + } + + list.setImmutable(); + + result.put(new NameValuePair(VALUE_UTF, new CstArray(list))); + result.setImmutable(); + return result; + } + + /** + * Constructs a standard {@code Throws} annotation. + * + * @param types {@code non-null;} the list of thrown types + * @return {@code non-null;} the annotation + */ + public static Annotation makeThrows(TypeList types) { + CstArray array = makeCstArray(types); + Annotation result = new Annotation(THROWS_TYPE, SYSTEM); + result.put(new NameValuePair(VALUE_UTF, array)); + result.setImmutable(); + return result; + } + + /** + * Converts a {@link TypeList} to a {@link CstArray}. + * + * @param types {@code non-null;} the type list + * @return {@code non-null;} the corresponding array constant + */ + private static CstArray makeCstArray(TypeList types) { + int size = types.size(); + CstArray.List list = new CstArray.List(size); + + for (int i = 0; i < size; i++) { + list.set(i, CstType.intern(types.getType(i))); + } + + list.setImmutable(); + return new CstArray(list); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/AnnotationsDirectoryItem.java b/dexgen/src/com/android/dexgen/dex/file/AnnotationsDirectoryItem.java new file mode 100644 index 0000000..860c16d --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/AnnotationsDirectoryItem.java @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.annotation.Annotations; +import com.android.dexgen.rop.annotation.AnnotationsList; +import com.android.dexgen.rop.cst.CstFieldRef; +import com.android.dexgen.rop.cst.CstMethodRef; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; + +/** + * Per-class directory of annotations. + */ +public final class AnnotationsDirectoryItem extends OffsettedItem { + /** the required alignment for instances of this class */ + private static final int ALIGNMENT = 4; + + /** write size of this class's header, in bytes */ + private static final int HEADER_SIZE = 16; + + /** write size of a list element, in bytes */ + private static final int ELEMENT_SIZE = 8; + + /** {@code null-ok;} the class-level annotations, if any */ + private AnnotationSetItem classAnnotations; + + /** {@code null-ok;} the annotated fields, if any */ + private ArrayList<FieldAnnotationStruct> fieldAnnotations; + + /** {@code null-ok;} the annotated methods, if any */ + private ArrayList<MethodAnnotationStruct> methodAnnotations; + + /** {@code null-ok;} the annotated parameters, if any */ + private ArrayList<ParameterAnnotationStruct> parameterAnnotations; + + /** + * Constructs an empty instance. + */ + public AnnotationsDirectoryItem() { + super(ALIGNMENT, -1); + + classAnnotations = null; + fieldAnnotations = null; + methodAnnotations = null; + parameterAnnotations = null; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_ANNOTATIONS_DIRECTORY_ITEM; + } + + /** + * Returns whether this item is empty (has no contents). + * + * @return {@code true} if this item is empty, or {@code false} + * if not + */ + public boolean isEmpty() { + return (classAnnotations == null) && + (fieldAnnotations == null) && + (methodAnnotations == null) && + (parameterAnnotations == null); + } + + /** + * Returns whether this item is a candidate for interning. The only + * interning candidates are ones that <i>only</i> have a non-null + * set of class annotations, with no other lists. + * + * @return {@code true} if this is an interning candidate, or + * {@code false} if not + */ + public boolean isInternable() { + return (classAnnotations != null) && + (fieldAnnotations == null) && + (methodAnnotations == null) && + (parameterAnnotations == null); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + if (classAnnotations == null) { + return 0; + } + + return classAnnotations.hashCode(); + } + + /** + * {@inheritDoc} + * + * <p><b>Note:</b>: This throws an exception if this item is not + * internable.</p> + * + * @see #isInternable + */ + @Override + public int compareTo0(OffsettedItem other) { + if (! isInternable()) { + throw new UnsupportedOperationException("uninternable instance"); + } + + AnnotationsDirectoryItem otherDirectory = + (AnnotationsDirectoryItem) other; + return classAnnotations.compareTo(otherDirectory.classAnnotations); + } + + /** + * Sets the direct annotations on this instance. These are annotations + * made on the class, per se, as opposed to on one of its members. + * It is only valid to call this method at most once per instance. + * + * @param annotations {@code non-null;} annotations to set for this class + */ + public void setClassAnnotations(Annotations annotations) { + if (annotations == null) { + throw new NullPointerException("annotations == null"); + } + + if (classAnnotations != null) { + throw new UnsupportedOperationException( + "class annotations already set"); + } + + classAnnotations = new AnnotationSetItem(annotations); + } + + /** + * Adds a field annotations item to this instance. + * + * @param field {@code non-null;} field in question + * @param annotations {@code non-null;} associated annotations to add + */ + public void addFieldAnnotations(CstFieldRef field, + Annotations annotations) { + if (fieldAnnotations == null) { + fieldAnnotations = new ArrayList<FieldAnnotationStruct>(); + } + + fieldAnnotations.add(new FieldAnnotationStruct(field, + new AnnotationSetItem(annotations))); + } + + /** + * Adds a method annotations item to this instance. + * + * @param method {@code non-null;} method in question + * @param annotations {@code non-null;} associated annotations to add + */ + public void addMethodAnnotations(CstMethodRef method, + Annotations annotations) { + if (methodAnnotations == null) { + methodAnnotations = new ArrayList<MethodAnnotationStruct>(); + } + + methodAnnotations.add(new MethodAnnotationStruct(method, + new AnnotationSetItem(annotations))); + } + + /** + * Adds a parameter annotations item to this instance. + * + * @param method {@code non-null;} method in question + * @param list {@code non-null;} associated list of annotation sets to add + */ + public void addParameterAnnotations(CstMethodRef method, + AnnotationsList list) { + if (parameterAnnotations == null) { + parameterAnnotations = new ArrayList<ParameterAnnotationStruct>(); + } + + parameterAnnotations.add(new ParameterAnnotationStruct(method, list)); + } + + /** + * Gets the method annotations for a given method, if any. This is + * meant for use by debugging / dumping code. + * + * @param method {@code non-null;} the method + * @return {@code null-ok;} the method annotations, if any + */ + public Annotations getMethodAnnotations(CstMethodRef method) { + if (methodAnnotations == null) { + return null; + } + + for (MethodAnnotationStruct item : methodAnnotations) { + if (item.getMethod().equals(method)) { + return item.getAnnotations(); + } + } + + return null; + } + + /** + * Gets the parameter annotations for a given method, if any. This is + * meant for use by debugging / dumping code. + * + * @param method {@code non-null;} the method + * @return {@code null-ok;} the parameter annotations, if any + */ + public AnnotationsList getParameterAnnotations(CstMethodRef method) { + if (parameterAnnotations == null) { + return null; + } + + for (ParameterAnnotationStruct item : parameterAnnotations) { + if (item.getMethod().equals(method)) { + return item.getAnnotationsList(); + } + } + + return null; + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + MixedItemSection wordData = file.getWordData(); + + if (classAnnotations != null) { + classAnnotations = wordData.intern(classAnnotations); + } + + if (fieldAnnotations != null) { + for (FieldAnnotationStruct item : fieldAnnotations) { + item.addContents(file); + } + } + + if (methodAnnotations != null) { + for (MethodAnnotationStruct item : methodAnnotations) { + item.addContents(file); + } + } + + if (parameterAnnotations != null) { + for (ParameterAnnotationStruct item : parameterAnnotations) { + item.addContents(file); + } + } + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + throw new RuntimeException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + // We just need to set the write size here. + + int elementCount = listSize(fieldAnnotations) + + listSize(methodAnnotations) + listSize(parameterAnnotations); + setWriteSize(HEADER_SIZE + (elementCount * ELEMENT_SIZE)); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + int classOff = OffsettedItem.getAbsoluteOffsetOr0(classAnnotations); + int fieldsSize = listSize(fieldAnnotations); + int methodsSize = listSize(methodAnnotations); + int parametersSize = listSize(parameterAnnotations); + + if (annotates) { + out.annotate(0, offsetString() + " annotations directory"); + out.annotate(4, " class_annotations_off: " + Hex.u4(classOff)); + out.annotate(4, " fields_size: " + + Hex.u4(fieldsSize)); + out.annotate(4, " methods_size: " + + Hex.u4(methodsSize)); + out.annotate(4, " parameters_size: " + + Hex.u4(parametersSize)); + } + + out.writeInt(classOff); + out.writeInt(fieldsSize); + out.writeInt(methodsSize); + out.writeInt(parametersSize); + + if (fieldsSize != 0) { + Collections.sort(fieldAnnotations); + if (annotates) { + out.annotate(0, " fields:"); + } + for (FieldAnnotationStruct item : fieldAnnotations) { + item.writeTo(file, out); + } + } + + if (methodsSize != 0) { + Collections.sort(methodAnnotations); + if (annotates) { + out.annotate(0, " methods:"); + } + for (MethodAnnotationStruct item : methodAnnotations) { + item.writeTo(file, out); + } + } + + if (parametersSize != 0) { + Collections.sort(parameterAnnotations); + if (annotates) { + out.annotate(0, " parameters:"); + } + for (ParameterAnnotationStruct item : parameterAnnotations) { + item.writeTo(file, out); + } + } + } + + /** + * Gets the list size of the given list, or {@code 0} if given + * {@code null}. + * + * @param list {@code null-ok;} the list in question + * @return {@code >= 0;} its size + */ + private static int listSize(ArrayList<?> list) { + if (list == null) { + return 0; + } + + return list.size(); + } + + /** + * Prints out the contents of this instance, in a debugging-friendly + * way. This is meant to be called from {@link ClassDefItem#debugPrint}. + * + * @param out {@code non-null;} where to output to + */ + /*package*/ void debugPrint(PrintWriter out) { + if (classAnnotations != null) { + out.println(" class annotations: " + classAnnotations); + } + + if (fieldAnnotations != null) { + out.println(" field annotations:"); + for (FieldAnnotationStruct item : fieldAnnotations) { + out.println(" " + item.toHuman()); + } + } + + if (methodAnnotations != null) { + out.println(" method annotations:"); + for (MethodAnnotationStruct item : methodAnnotations) { + out.println(" " + item.toHuman()); + } + } + + if (parameterAnnotations != null) { + out.println(" parameter annotations:"); + for (ParameterAnnotationStruct item : parameterAnnotations) { + out.println(" " + item.toHuman()); + } + } + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/CatchStructs.java b/dexgen/src/com/android/dexgen/dex/file/CatchStructs.java new file mode 100644 index 0000000..df9a847 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/CatchStructs.java @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.dex.code.CatchHandlerList; +import com.android.dexgen.dex.code.CatchTable; +import com.android.dexgen.dex.code.DalvCode; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.ByteArrayAnnotatedOutput; +import com.android.dexgen.util.Hex; + +import java.io.PrintWriter; +import java.util.Map; +import java.util.TreeMap; + +/** + * List of exception handlers (tuples of covered range, catch type, + * handler address) for a particular piece of code. Instances of this + * class correspond to a {@code try_item[]} and a + * {@code catch_handler_item[]}. + */ +public final class CatchStructs { + /** + * the size of a {@code try_item}: a {@code uint} + * and two {@code ushort}s + */ + private static final int TRY_ITEM_WRITE_SIZE = 4 + (2 * 2); + + /** {@code non-null;} code that contains the catches */ + private final DalvCode code; + + /** + * {@code null-ok;} the underlying table; set in + * {@link #finishProcessingIfNecessary} + */ + private CatchTable table; + + /** + * {@code null-ok;} the encoded handler list, if calculated; set in + * {@link #encode} + */ + private byte[] encodedHandlers; + + /** + * length of the handlers header (encoded size), if known; used for + * annotation + */ + private int encodedHandlerHeaderSize; + + /** + * {@code null-ok;} map from handler lists to byte offsets, if calculated; set in + * {@link #encode} + */ + private TreeMap<CatchHandlerList, Integer> handlerOffsets; + + /** + * Constructs an instance. + * + * @param code {@code non-null;} code that contains the catches + */ + public CatchStructs(DalvCode code) { + this.code = code; + this.table = null; + this.encodedHandlers = null; + this.encodedHandlerHeaderSize = 0; + this.handlerOffsets = null; + } + + /** + * Finish processing the catches, if necessary. + */ + private void finishProcessingIfNecessary() { + if (table == null) { + table = code.getCatches(); + } + } + + /** + * Gets the size of the tries list, in entries. + * + * @return {@code >= 0;} the tries list size + */ + public int triesSize() { + finishProcessingIfNecessary(); + return table.size(); + } + + /** + * Does a human-friendly dump of this instance. + * + * @param out {@code non-null;} where to dump + * @param prefix {@code non-null;} prefix to attach to each line of output + */ + public void debugPrint(PrintWriter out, String prefix) { + annotateEntries(prefix, out, null); + } + + /** + * Encodes the handler lists. + * + * @param file {@code non-null;} file this instance is part of + */ + public void encode(DexFile file) { + finishProcessingIfNecessary(); + + TypeIdsSection typeIds = file.getTypeIds(); + int size = table.size(); + + handlerOffsets = new TreeMap<CatchHandlerList, Integer>(); + + /* + * First add a map entry for each unique list. The tree structure + * will ensure they are sorted when we reiterate later. + */ + for (int i = 0; i < size; i++) { + handlerOffsets.put(table.get(i).getHandlers(), null); + } + + if (handlerOffsets.size() > 65535) { + throw new UnsupportedOperationException( + "too many catch handlers"); + } + + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(); + + // Write out the handlers "header" consisting of its size in entries. + encodedHandlerHeaderSize = + out.writeUnsignedLeb128(handlerOffsets.size()); + + // Now write the lists out in order, noting the offset of each. + for (Map.Entry<CatchHandlerList, Integer> mapping : + handlerOffsets.entrySet()) { + CatchHandlerList list = mapping.getKey(); + int listSize = list.size(); + boolean catchesAll = list.catchesAll(); + + // Set the offset before we do any writing. + mapping.setValue(out.getCursor()); + + if (catchesAll) { + // A size <= 0 means that the list ends with a catch-all. + out.writeSignedLeb128(-(listSize - 1)); + listSize--; + } else { + out.writeSignedLeb128(listSize); + } + + for (int i = 0; i < listSize; i++) { + CatchHandlerList.Entry entry = list.get(i); + out.writeUnsignedLeb128( + typeIds.indexOf(entry.getExceptionType())); + out.writeUnsignedLeb128(entry.getHandler()); + } + + if (catchesAll) { + out.writeUnsignedLeb128(list.get(listSize).getHandler()); + } + } + + encodedHandlers = out.toByteArray(); + } + + /** + * Gets the write size of this instance, in bytes. + * + * @return {@code >= 0;} the write size + */ + public int writeSize() { + return (triesSize() * TRY_ITEM_WRITE_SIZE) + + + encodedHandlers.length; + } + + /** + * Writes this instance to the given stream. + * + * @param file {@code non-null;} file this instance is part of + * @param out {@code non-null;} where to write to + */ + public void writeTo(DexFile file, AnnotatedOutput out) { + finishProcessingIfNecessary(); + + if (out.annotates()) { + annotateEntries(" ", null, out); + } + + int tableSize = table.size(); + for (int i = 0; i < tableSize; i++) { + CatchTable.Entry one = table.get(i); + int start = one.getStart(); + int end = one.getEnd(); + int insnCount = end - start; + + if (insnCount >= 65536) { + throw new UnsupportedOperationException( + "bogus exception range: " + Hex.u4(start) + ".." + + Hex.u4(end)); + } + + out.writeInt(start); + out.writeShort(insnCount); + out.writeShort(handlerOffsets.get(one.getHandlers())); + } + + out.write(encodedHandlers); + } + + /** + * Helper method to annotate or simply print the exception handlers. + * Only one of {@code printTo} or {@code annotateTo} should + * be non-null. + * + * @param prefix {@code non-null;} prefix for each line + * @param printTo {@code null-ok;} where to print to + * @param annotateTo {@code null-ok;} where to consume bytes and annotate to + */ + private void annotateEntries(String prefix, PrintWriter printTo, + AnnotatedOutput annotateTo) { + finishProcessingIfNecessary(); + + boolean consume = (annotateTo != null); + int amt1 = consume ? 6 : 0; + int amt2 = consume ? 2 : 0; + int size = table.size(); + String subPrefix = prefix + " "; + + if (consume) { + annotateTo.annotate(0, prefix + "tries:"); + } else { + printTo.println(prefix + "tries:"); + } + + for (int i = 0; i < size; i++) { + CatchTable.Entry entry = table.get(i); + CatchHandlerList handlers = entry.getHandlers(); + String s1 = subPrefix + "try " + Hex.u2or4(entry.getStart()) + + ".." + Hex.u2or4(entry.getEnd()); + String s2 = handlers.toHuman(subPrefix, ""); + + if (consume) { + annotateTo.annotate(amt1, s1); + annotateTo.annotate(amt2, s2); + } else { + printTo.println(s1); + printTo.println(s2); + } + } + + if (! consume) { + // Only emit the handler lists if we are consuming bytes. + return; + } + + annotateTo.annotate(0, prefix + "handlers:"); + annotateTo.annotate(encodedHandlerHeaderSize, + subPrefix + "size: " + Hex.u2(handlerOffsets.size())); + + int lastOffset = 0; + CatchHandlerList lastList = null; + + for (Map.Entry<CatchHandlerList, Integer> mapping : + handlerOffsets.entrySet()) { + CatchHandlerList list = mapping.getKey(); + int offset = mapping.getValue(); + + if (lastList != null) { + annotateAndConsumeHandlers(lastList, lastOffset, + offset - lastOffset, subPrefix, printTo, annotateTo); + } + + lastList = list; + lastOffset = offset; + } + + annotateAndConsumeHandlers(lastList, lastOffset, + encodedHandlers.length - lastOffset, + subPrefix, printTo, annotateTo); + } + + /** + * Helper for {@link #annotateEntries} to annotate a catch handler list + * while consuming it. + * + * @param handlers {@code non-null;} handlers to annotate + * @param offset {@code >= 0;} the offset of this handler + * @param size {@code >= 1;} the number of bytes the handlers consume + * @param prefix {@code non-null;} prefix for each line + * @param printTo {@code null-ok;} where to print to + * @param annotateTo {@code non-null;} where to annotate to + */ + private static void annotateAndConsumeHandlers(CatchHandlerList handlers, + int offset, int size, String prefix, PrintWriter printTo, + AnnotatedOutput annotateTo) { + String s = handlers.toHuman(prefix, Hex.u2(offset) + ": "); + + if (printTo != null) { + printTo.println(s); + } + + annotateTo.annotate(size, s); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/ClassDataItem.java b/dexgen/src/com/android/dexgen/dex/file/ClassDataItem.java new file mode 100644 index 0000000..c46a4a5 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/ClassDataItem.java @@ -0,0 +1,429 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstArray; +import com.android.dexgen.rop.cst.CstLiteralBits; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.cst.Zeroes; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.ByteArrayAnnotatedOutput; +import com.android.dexgen.util.Hex; +import com.android.dexgen.util.Writers; + +import java.io.PrintWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.HashMap; + +/** + * Representation of all the parts of a Dalvik class that are generally + * "inflated" into an in-memory representation at runtime. Instances of + * this class are represented in a compact streamable form in a + * {@code dex} file, as opposed to a random-access form. + */ +public final class ClassDataItem extends OffsettedItem { + /** {@code non-null;} what class this data is for, just for listing generation */ + private final CstType thisClass; + + /** {@code non-null;} list of static fields */ + private final ArrayList<EncodedField> staticFields; + + /** {@code non-null;} list of initial values for static fields */ + private final HashMap<EncodedField, Constant> staticValues; + + /** {@code non-null;} list of instance fields */ + private final ArrayList<EncodedField> instanceFields; + + /** {@code non-null;} list of direct methods */ + private final ArrayList<EncodedMethod> directMethods; + + /** {@code non-null;} list of virtual methods */ + private final ArrayList<EncodedMethod> virtualMethods; + + /** {@code null-ok;} static initializer list; set in {@link #addContents} */ + private CstArray staticValuesConstant; + + /** + * {@code null-ok;} encoded form, ready for writing to a file; set during + * {@link #place0} + */ + private byte[] encodedForm; + + /** + * Constructs an instance. Its sets of members are initially + * empty. + * + * @param thisClass {@code non-null;} what class this data is for, just + * for listing generation + */ + public ClassDataItem(CstType thisClass) { + super(1, -1); + + if (thisClass == null) { + throw new NullPointerException("thisClass == null"); + } + + this.thisClass = thisClass; + this.staticFields = new ArrayList<EncodedField>(20); + this.staticValues = new HashMap<EncodedField, Constant>(40); + this.instanceFields = new ArrayList<EncodedField>(20); + this.directMethods = new ArrayList<EncodedMethod>(20); + this.virtualMethods = new ArrayList<EncodedMethod>(20); + this.staticValuesConstant = null; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_CLASS_DATA_ITEM; + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return toString(); + } + + /** + * Returns whether this instance is empty. + * + * @return {@code true} if this instance is empty or + * {@code false} if at least one element has been added to it + */ + public boolean isEmpty() { + return staticFields.isEmpty() && instanceFields.isEmpty() + && directMethods.isEmpty() && virtualMethods.isEmpty(); + } + + /** + * Adds a static field. + * + * @param field {@code non-null;} the field to add + * @param value {@code null-ok;} initial value for the field, if any + */ + public void addStaticField(EncodedField field, Constant value) { + if (field == null) { + throw new NullPointerException("field == null"); + } + + if (staticValuesConstant != null) { + throw new UnsupportedOperationException( + "static fields already sorted"); + } + + staticFields.add(field); + staticValues.put(field, value); + } + + /** + * Adds an instance field. + * + * @param field {@code non-null;} the field to add + */ + public void addInstanceField(EncodedField field) { + if (field == null) { + throw new NullPointerException("field == null"); + } + + instanceFields.add(field); + } + + /** + * Adds a direct ({@code static} and/or {@code private}) method. + * + * @param method {@code non-null;} the method to add + */ + public void addDirectMethod(EncodedMethod method) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + directMethods.add(method); + } + + /** + * Adds a virtual method. + * + * @param method {@code non-null;} the method to add + */ + public void addVirtualMethod(EncodedMethod method) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + virtualMethods.add(method); + } + + /** + * Gets all the methods in this class. The returned list is not linked + * in any way to the underlying lists contained in this instance, but + * the objects contained in the list are shared. + * + * @return {@code non-null;} list of all methods + */ + public ArrayList<EncodedMethod> getMethods() { + int sz = directMethods.size() + virtualMethods.size(); + ArrayList<EncodedMethod> result = new ArrayList<EncodedMethod>(sz); + + result.addAll(directMethods); + result.addAll(virtualMethods); + + return result; + } + + + /** + * Prints out the contents of this instance, in a debugging-friendly + * way. + * + * @param out {@code non-null;} where to output to + * @param verbose whether to be verbose with the output + */ + public void debugPrint(Writer out, boolean verbose) { + PrintWriter pw = Writers.printWriterFor(out); + + int sz = staticFields.size(); + for (int i = 0; i < sz; i++) { + pw.println(" sfields[" + i + "]: " + staticFields.get(i)); + } + + sz = instanceFields.size(); + for (int i = 0; i < sz; i++) { + pw.println(" ifields[" + i + "]: " + instanceFields.get(i)); + } + + sz = directMethods.size(); + for (int i = 0; i < sz; i++) { + pw.println(" dmeths[" + i + "]:"); + directMethods.get(i).debugPrint(pw, verbose); + } + + sz = virtualMethods.size(); + for (int i = 0; i < sz; i++) { + pw.println(" vmeths[" + i + "]:"); + virtualMethods.get(i).debugPrint(pw, verbose); + } + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + if (!staticFields.isEmpty()) { + getStaticValuesConstant(); // Force the fields to be sorted. + for (EncodedField field : staticFields) { + field.addContents(file); + } + } + + if (!instanceFields.isEmpty()) { + Collections.sort(instanceFields); + for (EncodedField field : instanceFields) { + field.addContents(file); + } + } + + if (!directMethods.isEmpty()) { + Collections.sort(directMethods); + for (EncodedMethod method : directMethods) { + method.addContents(file); + } + } + + if (!virtualMethods.isEmpty()) { + Collections.sort(virtualMethods); + for (EncodedMethod method : virtualMethods) { + method.addContents(file); + } + } + } + + /** + * Gets a {@link CstArray} corresponding to {@link #staticValues} if + * it contains any non-zero non-{@code null} values. + * + * @return {@code null-ok;} the corresponding constant or {@code null} if + * there are no values to encode + */ + public CstArray getStaticValuesConstant() { + if ((staticValuesConstant == null) && (staticFields.size() != 0)) { + staticValuesConstant = makeStaticValuesConstant(); + } + + return staticValuesConstant; + } + + /** + * Gets a {@link CstArray} corresponding to {@link #staticValues} if + * it contains any non-zero non-{@code null} values. + * + * @return {@code null-ok;} the corresponding constant or {@code null} if + * there are no values to encode + */ + private CstArray makeStaticValuesConstant() { + // First sort the statics into their final order. + Collections.sort(staticFields); + + /* + * Get the size of staticValues minus any trailing zeros/nulls (both + * nulls per se as well as instances of CstKnownNull). + */ + + int size = staticFields.size(); + while (size > 0) { + EncodedField field = staticFields.get(size - 1); + Constant cst = staticValues.get(field); + if (cst instanceof CstLiteralBits) { + // Note: CstKnownNull extends CstLiteralBits. + if (((CstLiteralBits) cst).getLongBits() != 0) { + break; + } + } else if (cst != null) { + break; + } + size--; + } + + if (size == 0) { + return null; + } + + // There is something worth encoding, so build up a result. + + CstArray.List list = new CstArray.List(size); + for (int i = 0; i < size; i++) { + EncodedField field = staticFields.get(i); + Constant cst = staticValues.get(field); + if (cst == null) { + cst = Zeroes.zeroFor(field.getRef().getType()); + } + list.set(i, cst); + } + list.setImmutable(); + + return new CstArray(list); + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + // Encode the data and note the size. + + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(); + + encodeOutput(addedTo.getFile(), out); + encodedForm = out.toByteArray(); + setWriteSize(encodedForm.length); + } + + /** + * Writes out the encoded form of this instance. + * + * @param file {@code non-null;} file this instance is part of + * @param out {@code non-null;} where to write to + */ + private void encodeOutput(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + + if (annotates) { + out.annotate(0, offsetString() + " class data for " + + thisClass.toHuman()); + } + + encodeSize(file, out, "static_fields", staticFields.size()); + encodeSize(file, out, "instance_fields", instanceFields.size()); + encodeSize(file, out, "direct_methods", directMethods.size()); + encodeSize(file, out, "virtual_methods", virtualMethods.size()); + + encodeList(file, out, "static_fields", staticFields); + encodeList(file, out, "instance_fields", instanceFields); + encodeList(file, out, "direct_methods", directMethods); + encodeList(file, out, "virtual_methods", virtualMethods); + + if (annotates) { + out.endAnnotation(); + } + } + + /** + * Helper for {@link #encodeOutput}, which writes out the given + * size value, annotating it as well (if annotations are enabled). + * + * @param file {@code non-null;} file this instance is part of + * @param out {@code non-null;} where to write to + * @param label {@code non-null;} the label for the purposes of annotation + * @param size {@code >= 0;} the size to write + */ + private static void encodeSize(DexFile file, AnnotatedOutput out, + String label, int size) { + if (out.annotates()) { + out.annotate(String.format(" %-21s %08x", label + "_size:", + size)); + } + + out.writeUnsignedLeb128(size); + } + + /** + * Helper for {@link #encodeOutput}, which writes out the given + * list. It also annotates the items (if any and if annotations + * are enabled). + * + * @param file {@code non-null;} file this instance is part of + * @param out {@code non-null;} where to write to + * @param label {@code non-null;} the label for the purposes of annotation + * @param list {@code non-null;} the list in question + */ + private static void encodeList(DexFile file, AnnotatedOutput out, + String label, ArrayList<? extends EncodedMember> list) { + int size = list.size(); + int lastIndex = 0; + + if (size == 0) { + return; + } + + if (out.annotates()) { + out.annotate(0, " " + label + ":"); + } + + for (int i = 0; i < size; i++) { + lastIndex = list.get(i).encode(file, out, lastIndex, i); + } + } + + /** {@inheritDoc} */ + @Override + public void writeTo0(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + + if (annotates) { + /* + * The output is to be annotated, so redo the work previously + * done by place0(), except this time annotations will actually + * get emitted. + */ + encodeOutput(file, out); + } else { + out.write(encodedForm); + } + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/ClassDefItem.java b/dexgen/src/com/android/dexgen/dex/file/ClassDefItem.java new file mode 100644 index 0000000..6177145 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/ClassDefItem.java @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.annotation.Annotations; +import com.android.dexgen.rop.annotation.AnnotationsList; +import com.android.dexgen.rop.code.AccessFlags; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstArray; +import com.android.dexgen.rop.cst.CstFieldRef; +import com.android.dexgen.rop.cst.CstMethodRef; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.rop.type.StdTypeList; +import com.android.dexgen.rop.type.TypeList; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; +import com.android.dexgen.util.Writers; + +import java.io.PrintWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.TreeSet; + +/** + * Representation of a Dalvik class, which is basically a set of + * members (fields or methods) along with a few more pieces of + * information. + */ +public final class ClassDefItem extends IndexedItem { + /** size of instances when written out to a file, in bytes */ + public static final int WRITE_SIZE = 32; + + /** {@code non-null;} type constant for this class */ + private final CstType thisClass; + + /** access flags */ + private final int accessFlags; + + /** + * {@code null-ok;} superclass or {@code null} if this class is a/the + * root class + */ + private final CstType superclass; + + /** {@code null-ok;} list of implemented interfaces */ + private TypeListItem interfaces; + + /** {@code null-ok;} source file name or {@code null} if unknown */ + private final CstUtf8 sourceFile; + + /** {@code non-null;} associated class data object */ + private final ClassDataItem classData; + + /** + * {@code null-ok;} item wrapper for the static values, initialized + * in {@link #addContents} + */ + private EncodedArrayItem staticValuesItem; + + /** {@code non-null;} annotations directory */ + private AnnotationsDirectoryItem annotationsDirectory; + + /** + * Constructs an instance. Its sets of members and annotations are + * initially empty. + * + * @param thisClass {@code non-null;} type constant for this class + * @param accessFlags access flags + * @param superclass {@code null-ok;} superclass or {@code null} if + * this class is a/the root class + * @param interfaces {@code non-null;} list of implemented interfaces + * @param sourceFile {@code null-ok;} source file name or + * {@code null} if unknown + */ + public ClassDefItem(CstType thisClass, int accessFlags, + CstType superclass, TypeList interfaces, CstUtf8 sourceFile) { + if (thisClass == null) { + throw new NullPointerException("thisClass == null"); + } + + /* + * TODO: Maybe check accessFlags and superclass, at + * least for easily-checked stuff? + */ + + if (interfaces == null) { + throw new NullPointerException("interfaces == null"); + } + + this.thisClass = thisClass; + this.accessFlags = accessFlags; + this.superclass = superclass; + this.interfaces = + (interfaces.size() == 0) ? null : new TypeListItem(interfaces); + this.sourceFile = sourceFile; + this.classData = new ClassDataItem(thisClass); + this.staticValuesItem = null; + this.annotationsDirectory = new AnnotationsDirectoryItem(); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_CLASS_DEF_ITEM; + } + + /** {@inheritDoc} */ + @Override + public int writeSize() { + return WRITE_SIZE; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + TypeIdsSection typeIds = file.getTypeIds(); + MixedItemSection byteData = file.getByteData(); + MixedItemSection wordData = file.getWordData(); + MixedItemSection typeLists = file.getTypeLists(); + StringIdsSection stringIds = file.getStringIds(); + + typeIds.intern(thisClass); + + if (!classData.isEmpty()) { + MixedItemSection classDataSection = file.getClassData(); + classDataSection.add(classData); + + CstArray staticValues = classData.getStaticValuesConstant(); + if (staticValues != null) { + staticValuesItem = + byteData.intern(new EncodedArrayItem(staticValues)); + } + } + + if (superclass != null) { + typeIds.intern(superclass); + } + + if (interfaces != null) { + interfaces = typeLists.intern(interfaces); + } + + if (sourceFile != null) { + stringIds.intern(sourceFile); + } + + if (! annotationsDirectory.isEmpty()) { + if (annotationsDirectory.isInternable()) { + annotationsDirectory = wordData.intern(annotationsDirectory); + } else { + wordData.add(annotationsDirectory); + } + } + } + + /** {@inheritDoc} */ + @Override + public void writeTo(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + TypeIdsSection typeIds = file.getTypeIds(); + int classIdx = typeIds.indexOf(thisClass); + int superIdx = (superclass == null) ? -1 : + typeIds.indexOf(superclass); + int interOff = OffsettedItem.getAbsoluteOffsetOr0(interfaces); + int annoOff = annotationsDirectory.isEmpty() ? 0 : + annotationsDirectory.getAbsoluteOffset(); + int sourceFileIdx = (sourceFile == null) ? -1 : + file.getStringIds().indexOf(sourceFile); + int dataOff = classData.isEmpty()? 0 : classData.getAbsoluteOffset(); + int staticValuesOff = + OffsettedItem.getAbsoluteOffsetOr0(staticValuesItem); + + if (annotates) { + out.annotate(0, indexString() + ' ' + thisClass.toHuman()); + out.annotate(4, " class_idx: " + Hex.u4(classIdx)); + out.annotate(4, " access_flags: " + + AccessFlags.classString(accessFlags)); + out.annotate(4, " superclass_idx: " + Hex.u4(superIdx) + + " // " + ((superclass == null) ? "<none>" : + superclass.toHuman())); + out.annotate(4, " interfaces_off: " + Hex.u4(interOff)); + if (interOff != 0) { + TypeList list = interfaces.getList(); + int sz = list.size(); + for (int i = 0; i < sz; i++) { + out.annotate(0, " " + list.getType(i).toHuman()); + } + } + out.annotate(4, " source_file_idx: " + Hex.u4(sourceFileIdx) + + " // " + ((sourceFile == null) ? "<none>" : + sourceFile.toHuman())); + out.annotate(4, " annotations_off: " + Hex.u4(annoOff)); + out.annotate(4, " class_data_off: " + Hex.u4(dataOff)); + out.annotate(4, " static_values_off: " + + Hex.u4(staticValuesOff)); + } + + out.writeInt(classIdx); + out.writeInt(accessFlags); + out.writeInt(superIdx); + out.writeInt(interOff); + out.writeInt(sourceFileIdx); + out.writeInt(annoOff); + out.writeInt(dataOff); + out.writeInt(staticValuesOff); + } + + /** + * Gets the constant corresponding to this class. + * + * @return {@code non-null;} the constant + */ + public CstType getThisClass() { + return thisClass; + } + + /** + * Gets the access flags. + * + * @return the access flags + */ + public int getAccessFlags() { + return accessFlags; + } + + /** + * Gets the superclass. + * + * @return {@code null-ok;} the superclass or {@code null} if + * this class is a/the root class + */ + public CstType getSuperclass() { + return superclass; + } + + /** + * Gets the list of interfaces implemented. + * + * @return {@code non-null;} the interfaces list + */ + public TypeList getInterfaces() { + if (interfaces == null) { + return StdTypeList.EMPTY; + } + + return interfaces.getList(); + } + + /** + * Gets the source file name. + * + * @return {@code null-ok;} the source file name or {@code null} if unknown + */ + public CstUtf8 getSourceFile() { + return sourceFile; + } + + /** + * Adds a static field. + * + * @param field {@code non-null;} the field to add + * @param value {@code null-ok;} initial value for the field, if any + */ + public void addStaticField(EncodedField field, Constant value) { + classData.addStaticField(field, value); + } + + /** + * Adds an instance field. + * + * @param field {@code non-null;} the field to add + */ + public void addInstanceField(EncodedField field) { + classData.addInstanceField(field); + } + + /** + * Adds a direct ({@code static} and/or {@code private}) method. + * + * @param method {@code non-null;} the method to add + */ + public void addDirectMethod(EncodedMethod method) { + classData.addDirectMethod(method); + } + + /** + * Adds a virtual method. + * + * @param method {@code non-null;} the method to add + */ + public void addVirtualMethod(EncodedMethod method) { + classData.addVirtualMethod(method); + } + + /** + * Gets all the methods in this class. The returned list is not linked + * in any way to the underlying lists contained in this instance, but + * the objects contained in the list are shared. + * + * @return {@code non-null;} list of all methods + */ + public ArrayList<EncodedMethod> getMethods() { + return classData.getMethods(); + } + + /** + * Sets the direct annotations on this class. These are annotations + * made on the class, per se, as opposed to on one of its members. + * It is only valid to call this method at most once per instance. + * + * @param annotations {@code non-null;} annotations to set for this class + */ + public void setClassAnnotations(Annotations annotations) { + annotationsDirectory.setClassAnnotations(annotations); + } + + /** + * Adds a field annotations item to this class. + * + * @param field {@code non-null;} field in question + * @param annotations {@code non-null;} associated annotations to add + */ + public void addFieldAnnotations(CstFieldRef field, + Annotations annotations) { + annotationsDirectory.addFieldAnnotations(field, annotations); + } + + /** + * Adds a method annotations item to this class. + * + * @param method {@code non-null;} method in question + * @param annotations {@code non-null;} associated annotations to add + */ + public void addMethodAnnotations(CstMethodRef method, + Annotations annotations) { + annotationsDirectory.addMethodAnnotations(method, annotations); + } + + /** + * Adds a parameter annotations item to this class. + * + * @param method {@code non-null;} method in question + * @param list {@code non-null;} associated list of annotation sets to add + */ + public void addParameterAnnotations(CstMethodRef method, + AnnotationsList list) { + annotationsDirectory.addParameterAnnotations(method, list); + } + + /** + * Gets the method annotations for a given method, if any. This is + * meant for use by debugging / dumping code. + * + * @param method {@code non-null;} the method + * @return {@code null-ok;} the method annotations, if any + */ + public Annotations getMethodAnnotations(CstMethodRef method) { + return annotationsDirectory.getMethodAnnotations(method); + } + + /** + * Gets the parameter annotations for a given method, if any. This is + * meant for use by debugging / dumping code. + * + * @param method {@code non-null;} the method + * @return {@code null-ok;} the parameter annotations, if any + */ + public AnnotationsList getParameterAnnotations(CstMethodRef method) { + return annotationsDirectory.getParameterAnnotations(method); + } + + /** + * Prints out the contents of this instance, in a debugging-friendly + * way. + * + * @param out {@code non-null;} where to output to + * @param verbose whether to be verbose with the output + */ + public void debugPrint(Writer out, boolean verbose) { + PrintWriter pw = Writers.printWriterFor(out); + + pw.println(getClass().getName() + " {"); + pw.println(" accessFlags: " + Hex.u2(accessFlags)); + pw.println(" superclass: " + superclass); + pw.println(" interfaces: " + + ((interfaces == null) ? "<none>" : interfaces)); + pw.println(" sourceFile: " + + ((sourceFile == null) ? "<none>" : sourceFile.toQuoted())); + + classData.debugPrint(out, verbose); + annotationsDirectory.debugPrint(pw); + + pw.println("}"); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/ClassDefsSection.java b/dexgen/src/com/android/dexgen/dex/file/ClassDefsSection.java new file mode 100644 index 0000000..a6392d4 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/ClassDefsSection.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.rop.type.TypeList; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.TreeMap; + +/** + * Class definitions list section of a {@code .dex} file. + */ +public final class ClassDefsSection extends UniformItemSection { + /** + * {@code non-null;} map from type constants for classes to {@link + * ClassDefItem} instances that define those classes + */ + private final TreeMap<Type, ClassDefItem> classDefs; + + /** {@code null-ok;} ordered list of classes; set in {@link #orderItems} */ + private ArrayList<ClassDefItem> orderedDefs; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param file {@code non-null;} file that this instance is part of + */ + public ClassDefsSection(DexFile file) { + super("class_defs", file, 4); + + classDefs = new TreeMap<Type, ClassDefItem>(); + orderedDefs = null; + } + + /** {@inheritDoc} */ + @Override + public Collection<? extends Item> items() { + if (orderedDefs != null) { + return orderedDefs; + } + + return classDefs.values(); + } + + /** {@inheritDoc} */ + @Override + public IndexedItem get(Constant cst) { + if (cst == null) { + throw new NullPointerException("cst == null"); + } + + throwIfNotPrepared(); + + Type type = ((CstType) cst).getClassType(); + IndexedItem result = classDefs.get(type); + + if (result == null) { + throw new IllegalArgumentException("not found"); + } + + return result; + } + + /** + * Writes the portion of the file header that refers to this instance. + * + * @param out {@code non-null;} where to write + */ + public void writeHeaderPart(AnnotatedOutput out) { + throwIfNotPrepared(); + + int sz = classDefs.size(); + int offset = (sz == 0) ? 0 : getFileOffset(); + + if (out.annotates()) { + out.annotate(4, "class_defs_size: " + Hex.u4(sz)); + out.annotate(4, "class_defs_off: " + Hex.u4(offset)); + } + + out.writeInt(sz); + out.writeInt(offset); + } + + /** + * Adds an element to this instance. It is illegal to attempt to add more + * than one class with the same name. + * + * @param clazz {@code non-null;} the class def to add + */ + public void add(ClassDefItem clazz) { + Type type; + + try { + type = clazz.getThisClass().getClassType(); + } catch (NullPointerException ex) { + // Elucidate the exception. + throw new NullPointerException("clazz == null"); + } + + throwIfPrepared(); + + if (classDefs.get(type) != null) { + throw new IllegalArgumentException("already added: " + type); + } + + classDefs.put(type, clazz); + } + + /** {@inheritDoc} */ + @Override + protected void orderItems() { + int sz = classDefs.size(); + int idx = 0; + + orderedDefs = new ArrayList<ClassDefItem>(sz); + + /* + * Iterate over all the classes, recursively assigning an + * index to each, implicitly skipping the ones that have + * already been assigned by the time this (top-level) + * iteration reaches them. + */ + for (Type type : classDefs.keySet()) { + idx = orderItems0(type, idx, sz - idx); + } + } + + /** + * Helper for {@link #orderItems}, which recursively assigns indices + * to classes. + * + * @param type {@code null-ok;} type ref to assign, if any + * @param idx {@code >= 0;} the next index to assign + * @param maxDepth maximum recursion depth; if negative, this will + * throw an exception indicating class definition circularity + * @return {@code >= 0;} the next index to assign + */ + private int orderItems0(Type type, int idx, int maxDepth) { + ClassDefItem c = classDefs.get(type); + + if ((c == null) || (c.hasIndex())) { + return idx; + } + + if (maxDepth < 0) { + throw new RuntimeException("class circularity with " + type); + } + + maxDepth--; + + CstType superclassCst = c.getSuperclass(); + if (superclassCst != null) { + Type superclass = superclassCst.getClassType(); + idx = orderItems0(superclass, idx, maxDepth); + } + + TypeList interfaces = c.getInterfaces(); + int sz = interfaces.size(); + for (int i = 0; i < sz; i++) { + idx = orderItems0(interfaces.getType(i), idx, maxDepth); + } + + c.setIndex(idx); + orderedDefs.add(c); + return idx + 1; + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/CodeItem.java b/dexgen/src/com/android/dexgen/dex/file/CodeItem.java new file mode 100644 index 0000000..1b305c7 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/CodeItem.java @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.dex.code.CatchTable; +import com.android.dexgen.dex.code.CstInsn; +import com.android.dexgen.dex.code.DalvCode; +import com.android.dexgen.dex.code.DalvInsn; +import com.android.dexgen.dex.code.DalvInsnList; +import com.android.dexgen.dex.code.LocalList; +import com.android.dexgen.dex.code.PositionList; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstMemberRef; +import com.android.dexgen.rop.cst.CstMethodRef; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.type.StdTypeList; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.rop.type.TypeList; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.ExceptionWithContext; +import com.android.dexgen.util.Hex; + +import java.io.PrintWriter; +import java.util.HashSet; + +/** + * Representation of all the parts needed for concrete methods in a + * {@code dex} file. + */ +public final class CodeItem extends OffsettedItem { + /** file alignment of this class, in bytes */ + private static final int ALIGNMENT = 4; + + /** write size of the header of this class, in bytes */ + private static final int HEADER_SIZE = 16; + + /** {@code non-null;} method that this code implements */ + private final CstMethodRef ref; + + /** {@code non-null;} the bytecode instructions and associated data */ + private final DalvCode code; + + /** {@code null-ok;} the catches, if needed; set in {@link #addContents} */ + private CatchStructs catches; + + /** whether this instance is for a {@code static} method */ + private final boolean isStatic; + + /** + * {@code non-null;} list of possibly-thrown exceptions; just used in + * generating debugging output (listings) + */ + private final TypeList throwsList; + + /** + * {@code null-ok;} the debug info or {@code null} if there is none; + * set in {@link #addContents} + */ + private DebugInfoItem debugInfo; + + /** + * Constructs an instance. + * + * @param ref {@code non-null;} method that this code implements + * @param code {@code non-null;} the underlying code + * @param isStatic whether this instance is for a {@code static} + * method + * @param throwsList {@code non-null;} list of possibly-thrown exceptions, + * just used in generating debugging output (listings) + */ + public CodeItem(CstMethodRef ref, DalvCode code, boolean isStatic, + TypeList throwsList) { + super(ALIGNMENT, -1); + + if (ref == null) { + throw new NullPointerException("ref == null"); + } + + if (code == null) { + throw new NullPointerException("code == null"); + } + + if (throwsList == null) { + throw new NullPointerException("throwsList == null"); + } + + this.ref = ref; + this.code = code; + this.isStatic = isStatic; + this.throwsList = throwsList; + this.catches = null; + this.debugInfo = null; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_CODE_ITEM; + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + MixedItemSection byteData = file.getByteData(); + TypeIdsSection typeIds = file.getTypeIds(); + + if (code.hasPositions() || code.hasLocals()) { + debugInfo = new DebugInfoItem(code, isStatic, ref); + byteData.add(debugInfo); + } + + if (code.hasAnyCatches()) { + for (Type type : code.getCatchTypes()) { + typeIds.intern(type); + } + catches = new CatchStructs(code); + } + + for (Constant c : code.getInsnConstants()) { + file.internIfAppropriate(c); + } + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "CodeItem{" + toHuman() + "}"; + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return ref.toHuman(); + } + + /** + * Gets the reference to the method this instance implements. + * + * @return {@code non-null;} the method reference + */ + public CstMethodRef getRef() { + return ref; + } + + /** + * Does a human-friendly dump of this instance. + * + * @param out {@code non-null;} where to dump + * @param prefix {@code non-null;} per-line prefix to use + * @param verbose whether to be verbose with the output + */ + public void debugPrint(PrintWriter out, String prefix, boolean verbose) { + out.println(ref.toHuman() + ":"); + + DalvInsnList insns = code.getInsns(); + out.println("regs: " + Hex.u2(getRegistersSize()) + + "; ins: " + Hex.u2(getInsSize()) + "; outs: " + + Hex.u2(getOutsSize())); + + insns.debugPrint(out, prefix, verbose); + + String prefix2 = prefix + " "; + + if (catches != null) { + out.print(prefix); + out.println("catches"); + catches.debugPrint(out, prefix2); + } + + if (debugInfo != null) { + out.print(prefix); + out.println("debug info"); + debugInfo.debugPrint(out, prefix2); + } + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + final DexFile file = addedTo.getFile(); + int catchesSize; + + /* + * In order to get the catches and insns, all the code's + * constants need to be assigned indices. + */ + code.assignIndices(new DalvCode.AssignIndicesCallback() { + public int getIndex(Constant cst) { + IndexedItem item = file.findItemOrNull(cst); + if (item == null) { + return -1; + } + return item.getIndex(); + } + }); + + if (catches != null) { + catches.encode(file); + catchesSize = catches.writeSize(); + } else { + catchesSize = 0; + } + + /* + * The write size includes the header, two bytes per code + * unit, post-code padding if necessary, and however much + * space the catches need. + */ + + int insnsSize = code.getInsns().codeSize(); + if ((insnsSize & 1) != 0) { + insnsSize++; + } + + setWriteSize(HEADER_SIZE + (insnsSize * 2) + catchesSize); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + int regSz = getRegistersSize(); + int outsSz = getOutsSize(); + int insSz = getInsSize(); + int insnsSz = code.getInsns().codeSize(); + boolean needPadding = (insnsSz & 1) != 0; + int triesSz = (catches == null) ? 0 : catches.triesSize(); + int debugOff = (debugInfo == null) ? 0 : debugInfo.getAbsoluteOffset(); + + if (annotates) { + out.annotate(0, offsetString() + ' ' + ref.toHuman()); + out.annotate(2, " registers_size: " + Hex.u2(regSz)); + out.annotate(2, " ins_size: " + Hex.u2(insSz)); + out.annotate(2, " outs_size: " + Hex.u2(outsSz)); + out.annotate(2, " tries_size: " + Hex.u2(triesSz)); + out.annotate(4, " debug_off: " + Hex.u4(debugOff)); + out.annotate(4, " insns_size: " + Hex.u4(insnsSz)); + + // This isn't represented directly here, but it is useful to see. + int size = throwsList.size(); + if (size != 0) { + out.annotate(0, " throws " + StdTypeList.toHuman(throwsList)); + } + } + + out.writeShort(regSz); + out.writeShort(insSz); + out.writeShort(outsSz); + out.writeShort(triesSz); + out.writeInt(debugOff); + out.writeInt(insnsSz); + + writeCodes(file, out); + + if (catches != null) { + if (needPadding) { + if (annotates) { + out.annotate(2, " padding: 0"); + } + out.writeShort(0); + } + + catches.writeTo(file, out); + } + + if (annotates) { + /* + * These are pointed at in the code header (above), but it's less + * distracting to expand on them at the bottom of the code. + */ + if (debugInfo != null) { + out.annotate(0, " debug info"); + debugInfo.annotateTo(file, out, " "); + } + } + } + + /** + * Helper for {@link #writeTo0} which writes out the actual bytecode. + * + * @param file {@code non-null;} file we are part of + * @param out {@code non-null;} where to write to + */ + private void writeCodes(DexFile file, AnnotatedOutput out) { + DalvInsnList insns = code.getInsns(); + + try { + insns.writeTo(out); + } catch (RuntimeException ex) { + throw ExceptionWithContext.withContext(ex, "...while writing " + + "instructions for " + ref.toHuman()); + } + } + + /** + * Get the in registers count. + * + * @return the count + */ + private int getInsSize() { + return ref.getParameterWordCount(isStatic); + } + + /** + * Get the out registers count. + * + * @return the count + */ + private int getOutsSize() { + return code.getInsns().getOutsSize(); + } + + /** + * Get the total registers count. + * + * @return the count + */ + private int getRegistersSize() { + return code.getInsns().getRegistersSize(); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/DebugInfoConstants.java b/dexgen/src/com/android/dexgen/dex/file/DebugInfoConstants.java new file mode 100644 index 0000000..780f350 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/DebugInfoConstants.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +/** + * Constants for the dex debug info state machine format. + */ +public interface DebugInfoConstants { + + /* + * normal opcodes + */ + + /** + * Terminates a debug info sequence for a method.<p> + * Args: none + * + */ + static final int DBG_END_SEQUENCE = 0x00; + + /** + * Advances the program counter/address register without emitting + * a positions entry.<p> + * + * Args: + * <ol> + * <li>Unsigned LEB128 — amount to advance pc by + * </ol> + */ + static final int DBG_ADVANCE_PC = 0x01; + + /** + * Advances the line register without emitting + * a positions entry.<p> + * + * Args: + * <ol> + * <li>Signed LEB128 — amount to change line register by. + * </ol> + */ + static final int DBG_ADVANCE_LINE = 0x02; + + /** + * Introduces a local variable at the current address.<p> + * + * Args: + * <ol> + * <li>Unsigned LEB128 — register that will contain local. + * <li>Unsigned LEB128 — string index (shifted by 1) of local name. + * <li>Unsigned LEB128 — type index (shifted by 1) of type. + * </ol> + */ + static final int DBG_START_LOCAL = 0x03; + + /** + * Introduces a local variable at the current address with a type + * signature specified.<p> + * + * Args: + * <ol> + * <li>Unsigned LEB128 — register that will contain local. + * <li>Unsigned LEB128 — string index (shifted by 1) of local name. + * <li>Unsigned LEB128 — type index (shifted by 1) of type. + * <li>Unsigned LEB128 — string index (shifted by 1) of + * type signature. + * </ol> + */ + static final int DBG_START_LOCAL_EXTENDED = 0x04; + + /** + * Marks a currently-live local variable as out of scope at the + * current address.<p> + * + * Args: + * <ol> + * <li>Unsigned LEB128 — register that contained local + * </ol> + */ + static final int DBG_END_LOCAL = 0x05; + + /** + * Re-introduces a local variable at the current address. The name + * and type are the same as the last local that was live in the specified + * register.<p> + * + * Args: + * <ol> + * <li>Unsigned LEB128 — register to re-start. + * </ol> + */ + static final int DBG_RESTART_LOCAL = 0x06; + + + /** + * Sets the "prologue_end" state machine register, indicating that the + * next position entry that is added should be considered the end of + * a method prologue (an appropriate place for a method breakpoint).<p> + * + * The prologue_end register is cleared by any special + * ({@code >= OPCODE_BASE}) opcode. + */ + static final int DBG_SET_PROLOGUE_END = 0x07; + + /** + * Sets the "epilogue_begin" state machine register, indicating that the + * next position entry that is added should be considered the beginning of + * a method epilogue (an appropriate place to suspend execution before + * method exit).<p> + * + * The epilogue_begin register is cleared by any special + * ({@code >= OPCODE_BASE}) opcode. + */ + static final int DBG_SET_EPILOGUE_BEGIN = 0x08; + + /** + * Sets the current file that that line numbers refer to. All subsequent + * line number entries make reference to this source file name, instead + * of the default name specified in code_item. + * + * Args: + * <ol> + * <li>Unsigned LEB128 — string index (shifted by 1) of source + * file name. + * </ol> + */ + static final int DBG_SET_FILE = 0x09; + + /* IF YOU ADD A NEW OPCODE, increase OPCODE_BASE */ + + /* + * "special opcode" configuration, essentially what's found in + * the line number program header in DWARFv3, Section 6.2.4 + */ + + /** the smallest value a special opcode can take */ + static final int DBG_FIRST_SPECIAL = 0x0a; + static final int DBG_LINE_BASE = -4; + static final int DBG_LINE_RANGE = 15; + // MIN_INSN_LENGTH is always 1 +} diff --git a/dexgen/src/com/android/dexgen/dex/file/DebugInfoDecoder.java b/dexgen/src/com/android/dexgen/dex/file/DebugInfoDecoder.java new file mode 100644 index 0000000..da614d2 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/DebugInfoDecoder.java @@ -0,0 +1,653 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.dex.code.DalvCode; +import com.android.dexgen.dex.code.DalvInsnList; +import com.android.dexgen.dex.code.LocalList; +import com.android.dexgen.dex.code.PositionList; +import com.android.dexgen.rop.cst.CstMethodRef; +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.rop.type.Prototype; +import com.android.dexgen.rop.type.StdTypeList; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.util.ExceptionWithContext; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import static com.android.dexgen.dex.file.DebugInfoConstants.*; + +/** + * A decoder for the dex debug info state machine format. + * This code exists mostly as a reference implementation and test for + * for the {@code DebugInfoEncoder} + */ +public class DebugInfoDecoder { + /** encoded debug info */ + private final byte[] encoded; + + /** positions decoded */ + private final ArrayList<PositionEntry> positions; + + /** locals decoded */ + private final ArrayList<LocalEntry> locals; + + /** size of code block in code units */ + private final int codesize; + + /** indexed by register, the last local variable live in a reg */ + private final LocalEntry[] lastEntryForReg; + + /** method descriptor of method this debug info is for */ + private final Prototype desc; + + /** true if method is static */ + private final boolean isStatic; + + /** dex file this debug info will be stored in */ + private final DexFile file; + + /** + * register size, in register units, of the register space + * used by this method + */ + private final int regSize; + + /** current decoding state: line number */ + private int line = 1; + + /** current decoding state: bytecode address */ + private int address = 0; + + /** string index of the string "this" */ + private final int thisStringIdx; + + /** + * Constructs an instance. + * + * @param encoded encoded debug info + * @param codesize size of code block in code units + * @param regSize register size, in register units, of the register space + * used by this method + * @param isStatic true if method is static + * @param ref method descriptor of method this debug info is for + * @param file dex file this debug info will be stored in + */ + DebugInfoDecoder(byte[] encoded, int codesize, int regSize, + boolean isStatic, CstMethodRef ref, DexFile file) { + if (encoded == null) { + throw new NullPointerException("encoded == null"); + } + + this.encoded = encoded; + this.isStatic = isStatic; + this.desc = ref.getPrototype(); + this.file = file; + this.regSize = regSize; + + positions = new ArrayList<PositionEntry>(); + locals = new ArrayList<LocalEntry>(); + this.codesize = codesize; + lastEntryForReg = new LocalEntry[regSize]; + + int idx = -1; + + try { + idx = file.getStringIds().indexOf(new CstUtf8("this")); + } catch (IllegalArgumentException ex) { + /* + * Silently tolerate not finding "this". It just means that + * no method has local variable info that looks like + * a standard instance method. + */ + } + + thisStringIdx = idx; + } + + /** + * An entry in the resulting postions table + */ + static private class PositionEntry { + /** bytecode address */ + public int address; + + /** line number */ + public int line; + + public PositionEntry(int address, int line) { + this.address = address; + this.line = line; + } + } + + /** + * An entry in the resulting locals table + */ + static private class LocalEntry { + /** address of event */ + public int address; + + /** {@code true} iff it's a local start */ + public boolean isStart; + + /** register number */ + public int reg; + + /** index of name in strings table */ + public int nameIndex; + + /** index of type in types table */ + public int typeIndex; + + /** index of type signature in strings table */ + public int signatureIndex; + + public LocalEntry(int address, boolean isStart, int reg, int nameIndex, + int typeIndex, int signatureIndex) { + this.address = address; + this.isStart = isStart; + this.reg = reg; + this.nameIndex = nameIndex; + this.typeIndex = typeIndex; + this.signatureIndex = signatureIndex; + } + + public String toString() { + return String.format("[%x %s v%d %04x %04x %04x]", + address, isStart ? "start" : "end", reg, + nameIndex, typeIndex, signatureIndex); + } + } + + /** + * Gets the decoded positions list. + * Valid after calling {@code decode}. + * + * @return positions list in ascending address order. + */ + public List<PositionEntry> getPositionList() { + return positions; + } + + /** + * Gets the decoded locals list, in ascending start-address order. + * Valid after calling {@code decode}. + * + * @return locals list in ascending address order. + */ + public List<LocalEntry> getLocals() { + return locals; + } + + /** + * Decodes the debug info sequence. + */ + public void decode() { + try { + decode0(); + } catch (Exception ex) { + throw ExceptionWithContext.withContext(ex, + "...while decoding debug info"); + } + } + + /** + * Reads a string index. String indicies are offset by 1, and a 0 value + * in the stream (-1 as returned by this method) means "null" + * + * @param bs + * @return index into file's string ids table, -1 means null + * @throws IOException + */ + private int readStringIndex(InputStream bs) throws IOException { + int offsetIndex = readUnsignedLeb128(bs); + + return offsetIndex - 1; + } + + /** + * Gets the register that begins the method's parameter range (including + * the 'this' parameter for non-static methods). The range continues until + * {@code regSize} + * + * @return register as noted above. + */ + private int getParamBase() { + return regSize + - desc.getParameterTypes().getWordCount() - (isStatic? 0 : 1); + } + + private void decode0() throws IOException { + ByteArrayInputStream bs = new ByteArrayInputStream(encoded); + + line = readUnsignedLeb128(bs); + int szParams = readUnsignedLeb128(bs); + StdTypeList params = desc.getParameterTypes(); + int curReg = getParamBase(); + + if (szParams != params.size()) { + throw new RuntimeException( + "Mismatch between parameters_size and prototype"); + } + + if (!isStatic) { + // Start off with implicit 'this' entry + LocalEntry thisEntry = + new LocalEntry(0, true, curReg, thisStringIdx, 0, 0); + locals.add(thisEntry); + lastEntryForReg[curReg] = thisEntry; + curReg++; + } + + for (int i = 0; i < szParams; i++) { + Type paramType = params.getType(i); + LocalEntry le; + + int nameIdx = readStringIndex(bs); + + if (nameIdx == -1) { + /* + * Unnamed parameter; often but not always filled in by an + * extended start op after the prologue + */ + le = new LocalEntry(0, true, curReg, -1, 0, 0); + } else { + // TODO: Final 0 should be idx of paramType.getDescriptor(). + le = new LocalEntry(0, true, curReg, nameIdx, 0, 0); + } + + locals.add(le); + lastEntryForReg[curReg] = le; + curReg += paramType.getCategory(); + } + + for (;;) { + int opcode = bs.read(); + + if (opcode < 0) { + throw new RuntimeException + ("Reached end of debug stream without " + + "encountering end marker"); + } + + switch (opcode) { + case DBG_START_LOCAL: { + int reg = readUnsignedLeb128(bs); + int nameIdx = readStringIndex(bs); + int typeIdx = readStringIndex(bs); + LocalEntry le = new LocalEntry( + address, true, reg, nameIdx, typeIdx, 0); + + locals.add(le); + lastEntryForReg[reg] = le; + } + break; + + case DBG_START_LOCAL_EXTENDED: { + int reg = readUnsignedLeb128(bs); + int nameIdx = readStringIndex(bs); + int typeIdx = readStringIndex(bs); + int sigIdx = readStringIndex(bs); + LocalEntry le = new LocalEntry( + address, true, reg, nameIdx, typeIdx, sigIdx); + + locals.add(le); + lastEntryForReg[reg] = le; + } + break; + + case DBG_RESTART_LOCAL: { + int reg = readUnsignedLeb128(bs); + LocalEntry prevle; + LocalEntry le; + + try { + prevle = lastEntryForReg[reg]; + + if (prevle.isStart) { + throw new RuntimeException("nonsensical " + + "RESTART_LOCAL on live register v" + + reg); + } + + le = new LocalEntry(address, true, reg, + prevle.nameIndex, prevle.typeIndex, 0); + } catch (NullPointerException ex) { + throw new RuntimeException( + "Encountered RESTART_LOCAL on new v" + reg); + } + + locals.add(le); + lastEntryForReg[reg] = le; + } + break; + + case DBG_END_LOCAL: { + int reg = readUnsignedLeb128(bs); + LocalEntry prevle; + LocalEntry le; + + try { + prevle = lastEntryForReg[reg]; + + if (!prevle.isStart) { + throw new RuntimeException("nonsensical " + + "END_LOCAL on dead register v" + reg); + } + + le = new LocalEntry(address, false, reg, + prevle.nameIndex, prevle.typeIndex, + prevle.signatureIndex); + } catch (NullPointerException ex) { + throw new RuntimeException( + "Encountered END_LOCAL on new v" + reg); + } + + locals.add(le); + lastEntryForReg[reg] = le; + } + break; + + case DBG_END_SEQUENCE: + // all done + return; + + case DBG_ADVANCE_PC: + address += readUnsignedLeb128(bs); + break; + + case DBG_ADVANCE_LINE: + line += readSignedLeb128(bs); + break; + + case DBG_SET_PROLOGUE_END: + //TODO do something with this. + break; + + case DBG_SET_EPILOGUE_BEGIN: + //TODO do something with this. + break; + + case DBG_SET_FILE: + //TODO do something with this. + break; + + default: + if (opcode < DBG_FIRST_SPECIAL) { + throw new RuntimeException( + "Invalid extended opcode encountered " + + opcode); + } + + int adjopcode = opcode - DBG_FIRST_SPECIAL; + + address += adjopcode / DBG_LINE_RANGE; + line += DBG_LINE_BASE + (adjopcode % DBG_LINE_RANGE); + + positions.add(new PositionEntry(address, line)); + break; + + } + } + } + + /** + * Validates an encoded debug info stream against data used to encode it, + * throwing an exception if they do not match. Used to validate the + * encoder. + * + * @param info encoded debug info + * @param file {@code non-null;} file to refer to during decoding + * @param ref {@code non-null;} method whose info is being decoded + * @param code {@code non-null;} original code object that was encoded + * @param isStatic whether the method is static + */ + public static void validateEncode(byte[] info, DexFile file, + CstMethodRef ref, DalvCode code, boolean isStatic) { + PositionList pl = code.getPositions(); + LocalList ll = code.getLocals(); + DalvInsnList insns = code.getInsns(); + int codeSize = insns.codeSize(); + int countRegisters = insns.getRegistersSize(); + + try { + validateEncode0(info, codeSize, countRegisters, + isStatic, ref, file, pl, ll); + } catch (RuntimeException ex) { + System.err.println("instructions:"); + insns.debugPrint(System.err, " ", true); + System.err.println("local list:"); + ll.debugPrint(System.err, " "); + throw ExceptionWithContext.withContext(ex, + "while processing " + ref.toHuman()); + } + } + + private static void validateEncode0(byte[] info, int codeSize, + int countRegisters, boolean isStatic, CstMethodRef ref, + DexFile file, PositionList pl, LocalList ll) { + DebugInfoDecoder decoder + = new DebugInfoDecoder(info, codeSize, countRegisters, + isStatic, ref, file); + + decoder.decode(); + + /* + * Go through the decoded position entries, matching up + * with original entries. + */ + + List<PositionEntry> decodedEntries = decoder.getPositionList(); + + if (decodedEntries.size() != pl.size()) { + throw new RuntimeException( + "Decoded positions table not same size was " + + decodedEntries.size() + " expected " + pl.size()); + } + + for (PositionEntry entry : decodedEntries) { + boolean found = false; + for (int i = pl.size() - 1; i >= 0; i--) { + PositionList.Entry ple = pl.get(i); + + if (entry.line == ple.getPosition().getLine() + && entry.address == ple.getAddress()) { + found = true; + break; + } + } + + if (!found) { + throw new RuntimeException ("Could not match position entry: " + + entry.address + ", " + entry.line); + } + } + + /* + * Go through the original local list, in order, matching up + * with decoded entries. + */ + + List<LocalEntry> decodedLocals = decoder.getLocals(); + int thisStringIdx = decoder.thisStringIdx; + int decodedSz = decodedLocals.size(); + int paramBase = decoder.getParamBase(); + + /* + * Preflight to fill in any parameters that were skipped in + * the prologue (including an implied "this") but then + * identified by full signature. + */ + for (int i = 0; i < decodedSz; i++) { + LocalEntry entry = decodedLocals.get(i); + int idx = entry.nameIndex; + + if ((idx < 0) || (idx == thisStringIdx)) { + for (int j = i + 1; j < decodedSz; j++) { + LocalEntry e2 = decodedLocals.get(j); + if (e2.address != 0) { + break; + } + if ((entry.reg == e2.reg) && e2.isStart) { + decodedLocals.set(i, e2); + decodedLocals.remove(j); + decodedSz--; + break; + } + } + } + } + + int origSz = ll.size(); + int decodeAt = 0; + boolean problem = false; + + for (int i = 0; i < origSz; i++) { + LocalList.Entry origEntry = ll.get(i); + + if (origEntry.getDisposition() + == LocalList.Disposition.END_REPLACED) { + /* + * The encoded list doesn't represent replacements, so + * ignore them for the sake of comparison. + */ + continue; + } + + LocalEntry decodedEntry; + + do { + decodedEntry = decodedLocals.get(decodeAt); + if (decodedEntry.nameIndex >= 0) { + break; + } + /* + * A negative name index means this is an anonymous + * parameter, and we shouldn't expect to see it in the + * original list. So, skip it. + */ + decodeAt++; + } while (decodeAt < decodedSz); + + int decodedAddress = decodedEntry.address; + + if (decodedEntry.reg != origEntry.getRegister()) { + System.err.println("local register mismatch at orig " + i + + " / decoded " + decodeAt); + problem = true; + break; + } + + if (decodedEntry.isStart != origEntry.isStart()) { + System.err.println("local start/end mismatch at orig " + i + + " / decoded " + decodeAt); + problem = true; + break; + } + + /* + * The secondary check here accounts for the fact that a + * parameter might not be marked as starting at 0 in the + * original list. + */ + if ((decodedAddress != origEntry.getAddress()) + && !((decodedAddress == 0) + && (decodedEntry.reg >= paramBase))) { + System.err.println("local address mismatch at orig " + i + + " / decoded " + decodeAt); + problem = true; + break; + } + + decodeAt++; + } + + if (problem) { + System.err.println("decoded locals:"); + for (LocalEntry e : decodedLocals) { + System.err.println(" " + e); + } + throw new RuntimeException("local table problem"); + } + } + + /** + * Reads a DWARFv3-style signed LEB128 integer to the specified stream. + * See DWARF v3 section 7.6. An invalid sequence produces an IOException. + * + * @param bs stream to input from + * @return read value + * @throws IOException on invalid sequence in addition to + * those caused by the InputStream + */ + public static int readSignedLeb128(InputStream bs) throws IOException { + int result = 0; + int cur; + int count = 0; + int signBits = -1; + + do { + cur = bs.read(); + result |= (cur & 0x7f) << (count * 7); + signBits <<= 7; + count++; + } while (((cur & 0x80) == 0x80) && count < 5); + + if ((cur & 0x80) == 0x80) { + throw new IOException ("invalid LEB128 sequence"); + } + + // Sign extend if appropriate + if (((signBits >> 1) & result) != 0 ) { + result |= signBits; + } + + return result; + } + + /** + * Reads a DWARFv3-style unsigned LEB128 integer to the specified stream. + * See DWARF v3 section 7.6. An invalid sequence produces an IOException. + * + * @param bs stream to input from + * @return read value, which should be treated as an unsigned value. + * @throws IOException on invalid sequence in addition to + * those caused by the InputStream + */ + public static int readUnsignedLeb128(InputStream bs) throws IOException { + int result = 0; + int cur; + int count = 0; + + do { + cur = bs.read(); + result |= (cur & 0x7f) << (count * 7); + count++; + } while (((cur & 0x80) == 0x80) && count < 5); + + if ((cur & 0x80) == 0x80) { + throw new IOException ("invalid LEB128 sequence"); + } + + return result; + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/DebugInfoEncoder.java b/dexgen/src/com/android/dexgen/dex/file/DebugInfoEncoder.java new file mode 100644 index 0000000..663de7e --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/DebugInfoEncoder.java @@ -0,0 +1,920 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.dex.code.LocalList; +import com.android.dexgen.dex.code.PositionList; +import com.android.dexgen.rop.code.RegisterSpec; +import com.android.dexgen.rop.code.SourcePosition; +import com.android.dexgen.rop.cst.CstMethodRef; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.rop.type.Prototype; +import com.android.dexgen.rop.type.StdTypeList; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.ByteArrayAnnotatedOutput; +import com.android.dexgen.util.ExceptionWithContext; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.BitSet; + +import static com.android.dexgen.dex.file.DebugInfoConstants.*; + +/** + * An encoder for the dex debug info state machine format. The format + * for each method enrty is as follows: + * <ol> + * <li> signed LEB128: initial value for line register. + * <li> n instances of signed LEB128: string indicies (offset by 1) + * for each method argument in left-to-right order + * with {@code this} excluded. A value of '0' indicates "no name" + * <li> A sequence of special or normal opcodes as defined in + * {@code DebugInfoConstants}. + * <li> A single terminating {@code OP_END_SEQUENCE} + * </ol> + */ +public final class DebugInfoEncoder { + private static final boolean DEBUG = false; + + /** {@code null-ok;} positions (line numbers) to encode */ + private final PositionList positions; + + /** {@code null-ok;} local variables to encode */ + private final LocalList locals; + + private final ByteArrayAnnotatedOutput output; + private final DexFile file; + private final int codeSize; + private final int regSize; + + private final Prototype desc; + private final boolean isStatic; + + /** current encoding state: bytecode address */ + private int address = 0; + + /** current encoding state: line number */ + private int line = 1; + + /** + * if non-null: the output to write annotations to. No normal + * output is written to this. + */ + private AnnotatedOutput annotateTo; + + /** if non-null: another possible output for annotations */ + private PrintWriter debugPrint; + + /** if non-null: the prefix for each annotation or debugPrint line */ + private String prefix; + + /** true if output should be consumed during annotation */ + private boolean shouldConsume; + + /** indexed by register; last local alive in register */ + private final LocalList.Entry[] lastEntryForReg; + + /** + * Creates an instance. + * + * @param positions {@code null-ok;} positions (line numbers) to encode + * @param locals {@code null-ok;} local variables to encode + * @param file {@code null-ok;} may only be {@code null} if simply using + * this class to do a debug print + * @param codeSize + * @param regSize + * @param isStatic + * @param ref + */ + public DebugInfoEncoder(PositionList positions, LocalList locals, + DexFile file, int codeSize, int regSize, + boolean isStatic, CstMethodRef ref) { + this.positions = positions; + this.locals = locals; + this.file = file; + this.desc = ref.getPrototype(); + this.isStatic = isStatic; + this.codeSize = codeSize; + this.regSize = regSize; + + output = new ByteArrayAnnotatedOutput(); + lastEntryForReg = new LocalList.Entry[regSize]; + } + + /** + * Annotates or writes a message to the {@code debugPrint} writer + * if applicable. + * + * @param length the number of bytes associated with this message + * @param message the message itself + */ + private void annotate(int length, String message) { + if (prefix != null) { + message = prefix + message; + } + + if (annotateTo != null) { + annotateTo.annotate(shouldConsume ? length : 0, message); + } + + if (debugPrint != null) { + debugPrint.println(message); + } + } + + /** + * Converts this (PositionList, LocalList) pair into a state machine + * sequence. + * + * @return {@code non-null;} encoded byte sequence without padding and + * terminated with a {@code 0x00} byte + */ + public byte[] convert() { + try { + byte[] ret; + ret = convert0(); + + if (DEBUG) { + for (int i = 0 ; i < ret.length; i++) { + System.err.printf("byte %02x\n", (0xff & ret[i])); + } + } + + return ret; + } catch (IOException ex) { + throw ExceptionWithContext + .withContext(ex, "...while encoding debug info"); + } + } + + /** + * Converts and produces annotations on a stream. Does not write + * actual bits to the {@code AnnotatedOutput}. + * + * @param prefix {@code null-ok;} prefix to attach to each line of output + * @param debugPrint {@code null-ok;} if specified, an alternate output for + * annotations + * @param out {@code null-ok;} if specified, where annotations should go + * @param consume whether to claim to have consumed output for + * {@code out} + * @return {@code non-null;} encoded output + */ + public byte[] convertAndAnnotate(String prefix, PrintWriter debugPrint, + AnnotatedOutput out, boolean consume) { + this.prefix = prefix; + this.debugPrint = debugPrint; + annotateTo = out; + shouldConsume = consume; + + byte[] result = convert(); + + return result; + } + + private byte[] convert0() throws IOException { + ArrayList<PositionList.Entry> sortedPositions = buildSortedPositions(); + ArrayList<LocalList.Entry> methodArgs = extractMethodArguments(); + + emitHeader(sortedPositions, methodArgs); + + // TODO: Make this mark be the actual prologue end. + output.writeByte(DBG_SET_PROLOGUE_END); + + if (annotateTo != null || debugPrint != null) { + annotate(1, String.format("%04x: prologue end",address)); + } + + int positionsSz = sortedPositions.size(); + int localsSz = locals.size(); + + // Current index in sortedPositions + int curPositionIdx = 0; + // Current index in locals + int curLocalIdx = 0; + + for (;;) { + /* + * Emit any information for the current address. + */ + + curLocalIdx = emitLocalsAtAddress(curLocalIdx); + curPositionIdx = + emitPositionsAtAddress(curPositionIdx, sortedPositions); + + /* + * Figure out what the next important address is. + */ + + int nextAddrL = Integer.MAX_VALUE; // local variable + int nextAddrP = Integer.MAX_VALUE; // position (line number) + + if (curLocalIdx < localsSz) { + nextAddrL = locals.get(curLocalIdx).getAddress(); + } + + if (curPositionIdx < positionsSz) { + nextAddrP = sortedPositions.get(curPositionIdx).getAddress(); + } + + int next = Math.min(nextAddrP, nextAddrL); + + // No next important address == done. + if (next == Integer.MAX_VALUE) { + break; + } + + /* + * If the only work remaining are local ends at the end of the + * block, stop here. Those are implied anyway. + */ + if (next == codeSize + && nextAddrL == Integer.MAX_VALUE + && nextAddrP == Integer.MAX_VALUE) { + break; + } + + if (next == nextAddrP) { + // Combined advance PC + position entry + emitPosition(sortedPositions.get(curPositionIdx++)); + } else { + emitAdvancePc(next - address); + } + } + + emitEndSequence(); + + return output.toByteArray(); + } + + /** + * Emits all local variable activity that occurs at the current + * {@link #address} starting at the given index into {@code + * locals} and including all subsequent activity at the same + * address. + * + * @param curLocalIdx Current index in locals + * @return new value for {@code curLocalIdx} + * @throws IOException + */ + private int emitLocalsAtAddress(int curLocalIdx) + throws IOException { + int sz = locals.size(); + + // TODO: Don't emit ends implied by starts. + + while ((curLocalIdx < sz) + && (locals.get(curLocalIdx).getAddress() == address)) { + LocalList.Entry entry = locals.get(curLocalIdx++); + int reg = entry.getRegister(); + LocalList.Entry prevEntry = lastEntryForReg[reg]; + + if (entry == prevEntry) { + /* + * Here we ignore locals entries for parameters, + * which have already been represented and placed in the + * lastEntryForReg array. + */ + continue; + } + + // At this point we have a new entry one way or another. + lastEntryForReg[reg] = entry; + + if (entry.isStart()) { + if ((prevEntry != null) && entry.matches(prevEntry)) { + /* + * The previous local in this register has the same + * name and type as the one being introduced now, so + * use the more efficient "restart" form. + */ + if (prevEntry.isStart()) { + /* + * We should never be handed a start when a + * a matching local is already active. + */ + throw new RuntimeException("shouldn't happen"); + } + emitLocalRestart(entry); + } else { + emitLocalStart(entry); + } + } else { + /* + * Only emit a local end if it is *not* due to a direct + * replacement. Direct replacements imply an end of the + * previous local in the same register. + * + * TODO: Make sure the runtime can deal with implied + * local ends from category-2 interactions, and when so, + * also stop emitting local ends for those cases. + */ + if (entry.getDisposition() + != LocalList.Disposition.END_REPLACED) { + emitLocalEnd(entry); + } + } + } + + return curLocalIdx; + } + + /** + * Emits all positions that occur at the current {@code address} + * + * @param curPositionIdx Current index in sortedPositions + * @param sortedPositions positions, sorted by ascending address + * @return new value for {@code curPositionIdx} + * @throws IOException + */ + private int emitPositionsAtAddress(int curPositionIdx, + ArrayList<PositionList.Entry> sortedPositions) + throws IOException { + int positionsSz = sortedPositions.size(); + while ((curPositionIdx < positionsSz) + && (sortedPositions.get(curPositionIdx).getAddress() + == address)) { + emitPosition(sortedPositions.get(curPositionIdx++)); + } + return curPositionIdx; + } + + /** + * Emits the header sequence, which consists of LEB128-encoded initial + * line number and string indicies for names of all non-"this" arguments. + * + * @param sortedPositions positions, sorted by ascending address + * @param methodArgs local list entries for method argumens arguments, + * in left-to-right order omitting "this" + * @throws IOException + */ + private void emitHeader(ArrayList<PositionList.Entry> sortedPositions, + ArrayList<LocalList.Entry> methodArgs) throws IOException { + boolean annotate = (annotateTo != null) || (debugPrint != null); + int mark = output.getCursor(); + + // Start by initializing the line number register. + if (sortedPositions.size() > 0) { + PositionList.Entry entry = sortedPositions.get(0); + line = entry.getPosition().getLine(); + } + output.writeUnsignedLeb128(line); + + if (annotate) { + annotate(output.getCursor() - mark, "line_start: " + line); + } + + int curParam = getParamBase(); + // paramTypes will not include 'this' + StdTypeList paramTypes = desc.getParameterTypes(); + int szParamTypes = paramTypes.size(); + + /* + * Initialize lastEntryForReg to have an initial + * entry for the 'this' pointer. + */ + if (!isStatic) { + for (LocalList.Entry arg : methodArgs) { + if (curParam == arg.getRegister()) { + lastEntryForReg[curParam] = arg; + break; + } + } + curParam++; + } + + // Write out the number of parameter entries that will follow. + mark = output.getCursor(); + output.writeUnsignedLeb128(szParamTypes); + + if (annotate) { + annotate(output.getCursor() - mark, + String.format("parameters_size: %04x", szParamTypes)); + } + + /* + * Then emit the string indicies of all the method parameters. + * Note that 'this', if applicable, is excluded. + */ + for (int i = 0; i < szParamTypes; i++) { + Type pt = paramTypes.get(i); + LocalList.Entry found = null; + + mark = output.getCursor(); + + for (LocalList.Entry arg : methodArgs) { + if (curParam == arg.getRegister()) { + found = arg; + + if (arg.getSignature() != null) { + /* + * Parameters with signatures will be re-emitted + * in complete as LOCAL_START_EXTENDED's below. + */ + emitStringIndex(null); + } else { + emitStringIndex(arg.getName()); + } + lastEntryForReg[curParam] = arg; + + break; + } + } + + if (found == null) { + /* + * Emit a null symbol for "unnamed." This is common + * for, e.g., synthesized methods and inner-class + * this$0 arguments. + */ + emitStringIndex(null); + } + + if (annotate) { + String parameterName + = (found == null || found.getSignature() != null) + ? "<unnamed>" : found.getName().toHuman(); + annotate(output.getCursor() - mark, + "parameter " + parameterName + " " + + RegisterSpec.PREFIX + curParam); + } + + curParam += pt.getCategory(); + } + + /* + * If anything emitted above has a type signature, emit it again as + * a LOCAL_RESTART_EXTENDED + */ + + for (LocalList.Entry arg : lastEntryForReg) { + if (arg == null) { + continue; + } + + CstUtf8 signature = arg.getSignature(); + + if (signature != null) { + emitLocalStartExtended(arg); + } + } + } + + /** + * Builds a list of position entries, sorted by ascending address. + * + * @return A sorted positions list + */ + private ArrayList<PositionList.Entry> buildSortedPositions() { + int sz = (positions == null) ? 0 : positions.size(); + ArrayList<PositionList.Entry> result = new ArrayList(sz); + + for (int i = 0; i < sz; i++) { + result.add(positions.get(i)); + } + + // Sort ascending by address. + Collections.sort (result, new Comparator<PositionList.Entry>() { + public int compare (PositionList.Entry a, PositionList.Entry b) { + return a.getAddress() - b.getAddress(); + } + + public boolean equals (Object obj) { + return obj == this; + } + }); + return result; + } + + /** + * Gets the register that begins the method's parameter range (including + * the 'this' parameter for non-static methods). The range continues until + * {@code regSize} + * + * @return register as noted above + */ + private int getParamBase() { + return regSize + - desc.getParameterTypes().getWordCount() - (isStatic? 0 : 1); + } + + /** + * Extracts method arguments from a locals list. These will be collected + * from the input list and sorted by ascending register in the + * returned list. + * + * @return list of non-{@code this} method argument locals, + * sorted by ascending register + */ + private ArrayList<LocalList.Entry> extractMethodArguments() { + ArrayList<LocalList.Entry> result + = new ArrayList(desc.getParameterTypes().size()); + int argBase = getParamBase(); + BitSet seen = new BitSet(regSize - argBase); + int sz = locals.size(); + + for (int i = 0; i < sz; i++) { + LocalList.Entry e = locals.get(i); + int reg = e.getRegister(); + + if (reg < argBase) { + continue; + } + + // only the lowest-start-address entry is included. + if (seen.get(reg - argBase)) { + continue; + } + + seen.set(reg - argBase); + result.add(e); + } + + // Sort by ascending register. + Collections.sort(result, new Comparator<LocalList.Entry>() { + public int compare(LocalList.Entry a, LocalList.Entry b) { + return a.getRegister() - b.getRegister(); + } + + public boolean equals(Object obj) { + return obj == this; + } + }); + + return result; + } + + /** + * Returns a string representation of this LocalList entry that is + * appropriate for emitting as an annotation. + * + * @param e {@code non-null;} entry + * @return {@code non-null;} annotation string + */ + private String entryAnnotationString(LocalList.Entry e) { + StringBuilder sb = new StringBuilder(); + + sb.append(RegisterSpec.PREFIX); + sb.append(e.getRegister()); + sb.append(' '); + + CstUtf8 name = e.getName(); + if (name == null) { + sb.append("null"); + } else { + sb.append(name.toHuman()); + } + sb.append(' '); + + CstType type = e.getType(); + if (type == null) { + sb.append("null"); + } else { + sb.append(type.toHuman()); + } + + CstUtf8 signature = e.getSignature(); + + if (signature != null) { + sb.append(' '); + sb.append(signature.toHuman()); + } + + return sb.toString(); + } + + /** + * Emits a {@link DebugInfoConstants#DBG_RESTART_LOCAL DBG_RESTART_LOCAL} + * sequence. + * + * @param entry entry associated with this restart + * @throws IOException + */ + private void emitLocalRestart(LocalList.Entry entry) + throws IOException { + + int mark = output.getCursor(); + + output.writeByte(DBG_RESTART_LOCAL); + emitUnsignedLeb128(entry.getRegister()); + + if (annotateTo != null || debugPrint != null) { + annotate(output.getCursor() - mark, + String.format("%04x: +local restart %s", + address, entryAnnotationString(entry))); + } + + if (DEBUG) { + System.err.println("emit local restart"); + } + } + + /** + * Emits a string index as an unsigned LEB128. The actual value written + * is shifted by 1, so that the '0' value is reserved for "null". The + * null symbol is used in some cases by the parameter name list + * at the beginning of the sequence. + * + * @param string {@code null-ok;} string to emit + * @throws IOException + */ + private void emitStringIndex(CstUtf8 string) throws IOException { + if ((string == null) || (file == null)) { + output.writeUnsignedLeb128(0); + } else { + output.writeUnsignedLeb128( + 1 + file.getStringIds().indexOf(string)); + } + + if (DEBUG) { + System.err.printf("Emit string %s\n", + string == null ? "<null>" : string.toQuoted()); + } + } + + /** + * Emits a type index as an unsigned LEB128. The actual value written + * is shifted by 1, so that the '0' value is reserved for "null". + * + * @param type {@code null-ok;} type to emit + * @throws IOException + */ + private void emitTypeIndex(CstType type) throws IOException { + if ((type == null) || (file == null)) { + output.writeUnsignedLeb128(0); + } else { + output.writeUnsignedLeb128( + 1 + file.getTypeIds().indexOf(type)); + } + + if (DEBUG) { + System.err.printf("Emit type %s\n", + type == null ? "<null>" : type.toHuman()); + } + } + + /** + * Emits a {@link DebugInfoConstants#DBG_START_LOCAL DBG_START_LOCAL} or + * {@link DebugInfoConstants#DBG_START_LOCAL_EXTENDED + * DBG_START_LOCAL_EXTENDED} sequence. + * + * @param entry entry to emit + * @throws IOException + */ + private void emitLocalStart(LocalList.Entry entry) + throws IOException { + + if (entry.getSignature() != null) { + emitLocalStartExtended(entry); + return; + } + + int mark = output.getCursor(); + + output.writeByte(DBG_START_LOCAL); + + emitUnsignedLeb128(entry.getRegister()); + emitStringIndex(entry.getName()); + emitTypeIndex(entry.getType()); + + if (annotateTo != null || debugPrint != null) { + annotate(output.getCursor() - mark, + String.format("%04x: +local %s", address, + entryAnnotationString(entry))); + } + + if (DEBUG) { + System.err.println("emit local start"); + } + } + + /** + * Emits a {@link DebugInfoConstants#DBG_START_LOCAL_EXTENDED + * DBG_START_LOCAL_EXTENDED} sequence. + * + * @param entry entry to emit + * @throws IOException + */ + private void emitLocalStartExtended(LocalList.Entry entry) + throws IOException { + + int mark = output.getCursor(); + + output.writeByte(DBG_START_LOCAL_EXTENDED); + + emitUnsignedLeb128(entry.getRegister()); + emitStringIndex(entry.getName()); + emitTypeIndex(entry.getType()); + emitStringIndex(entry.getSignature()); + + if (annotateTo != null || debugPrint != null) { + annotate(output.getCursor() - mark, + String.format("%04x: +localx %s", address, + entryAnnotationString(entry))); + } + + if (DEBUG) { + System.err.println("emit local start"); + } + } + + /** + * Emits a {@link DebugInfoConstants#DBG_END_LOCAL DBG_END_LOCAL} sequence. + * + * @param entry {@code entry non-null;} entry associated with end. + * @throws IOException + */ + private void emitLocalEnd(LocalList.Entry entry) + throws IOException { + + int mark = output.getCursor(); + + output.writeByte(DBG_END_LOCAL); + output.writeUnsignedLeb128(entry.getRegister()); + + if (annotateTo != null || debugPrint != null) { + annotate(output.getCursor() - mark, + String.format("%04x: -local %s", address, + entryAnnotationString(entry))); + } + + if (DEBUG) { + System.err.println("emit local end"); + } + } + + /** + * Emits the necessary byte sequences to emit the given position table + * entry. This will typically be a single special opcode, although + * it may also require DBG_ADVANCE_PC or DBG_ADVANCE_LINE. + * + * @param entry position entry to emit. + * @throws IOException + */ + private void emitPosition(PositionList.Entry entry) + throws IOException { + + SourcePosition pos = entry.getPosition(); + int newLine = pos.getLine(); + int newAddress = entry.getAddress(); + + int opcode; + + int deltaLines = newLine - line; + int deltaAddress = newAddress - address; + + if (deltaAddress < 0) { + throw new RuntimeException( + "Position entries must be in ascending address order"); + } + + if ((deltaLines < DBG_LINE_BASE) + || (deltaLines > (DBG_LINE_BASE + DBG_LINE_RANGE -1))) { + emitAdvanceLine(deltaLines); + deltaLines = 0; + } + + opcode = computeOpcode (deltaLines, deltaAddress); + + if ((opcode & ~0xff) > 0) { + emitAdvancePc(deltaAddress); + deltaAddress = 0; + opcode = computeOpcode (deltaLines, deltaAddress); + + if ((opcode & ~0xff) > 0) { + emitAdvanceLine(deltaLines); + deltaLines = 0; + opcode = computeOpcode (deltaLines, deltaAddress); + } + } + + output.writeByte(opcode); + + line += deltaLines; + address += deltaAddress; + + if (annotateTo != null || debugPrint != null) { + annotate(1, + String.format("%04x: line %d", address, line)); + } + } + + /** + * Computes a special opcode that will encode the given position change. + * If the return value is > 0xff, then the request cannot be fulfilled. + * Essentially the same as described in "DWARF Debugging Format Version 3" + * section 6.2.5.1. + * + * @param deltaLines {@code >= DBG_LINE_BASE, <= DBG_LINE_BASE + + * DBG_LINE_RANGE;} the line change to encode + * @param deltaAddress {@code >= 0;} the address change to encode + * @return {@code <= 0xff} if in range, otherwise parameters are out + * of range + */ + private static int computeOpcode(int deltaLines, int deltaAddress) { + if (deltaLines < DBG_LINE_BASE + || deltaLines > (DBG_LINE_BASE + DBG_LINE_RANGE -1)) { + + throw new RuntimeException("Parameter out of range"); + } + + return (deltaLines - DBG_LINE_BASE) + + (DBG_LINE_RANGE * deltaAddress) + DBG_FIRST_SPECIAL; + } + + /** + * Emits an {@link DebugInfoConstants#DBG_ADVANCE_LINE DBG_ADVANCE_LINE} + * sequence. + * + * @param deltaLines amount to change line number register by + * @throws IOException + */ + private void emitAdvanceLine(int deltaLines) throws IOException { + int mark = output.getCursor(); + + output.writeByte(DBG_ADVANCE_LINE); + output.writeSignedLeb128(deltaLines); + line += deltaLines; + + if (annotateTo != null || debugPrint != null) { + annotate(output.getCursor() - mark, + String.format("line = %d", line)); + } + + if (DEBUG) { + System.err.printf("Emitting advance_line for %d\n", deltaLines); + } + } + + /** + * Emits an {@link DebugInfoConstants#DBG_ADVANCE_PC DBG_ADVANCE_PC} + * sequence. + * + * @param deltaAddress {@code >= 0;} amount to change program counter by + * @throws IOException + */ + private void emitAdvancePc(int deltaAddress) throws IOException { + int mark = output.getCursor(); + + output.writeByte(DBG_ADVANCE_PC); + output.writeUnsignedLeb128(deltaAddress); + address += deltaAddress; + + if (annotateTo != null || debugPrint != null) { + annotate(output.getCursor() - mark, + String.format("%04x: advance pc", address)); + } + + if (DEBUG) { + System.err.printf("Emitting advance_pc for %d\n", deltaAddress); + } + } + + /** + * Emits an unsigned LEB128 value. + * + * @param n {@code >= 0;} value to emit. Note that, although this can + * represent integers larger than Integer.MAX_VALUE, we currently don't + * allow that. + * @throws IOException + */ + private void emitUnsignedLeb128(int n) throws IOException { + // We'll never need the top end of the unsigned range anyway. + if (n < 0) { + throw new RuntimeException( + "Signed value where unsigned required: " + n); + } + + output.writeUnsignedLeb128(n); + } + + /** + * Emits the {@link DebugInfoConstants#DBG_END_SEQUENCE DBG_END_SEQUENCE} + * bytecode. + */ + private void emitEndSequence() { + output.writeByte(DBG_END_SEQUENCE); + + if (annotateTo != null || debugPrint != null) { + annotate(1, "end sequence"); + } + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/DebugInfoItem.java b/dexgen/src/com/android/dexgen/dex/file/DebugInfoItem.java new file mode 100644 index 0000000..82ad444 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/DebugInfoItem.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.dex.code.DalvCode; +import com.android.dexgen.dex.code.DalvInsnList; +import com.android.dexgen.dex.code.LocalList; +import com.android.dexgen.dex.code.PositionList; +import com.android.dexgen.rop.cst.CstMethodRef; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.ExceptionWithContext; + +import java.io.PrintWriter; + +public class DebugInfoItem extends OffsettedItem { + /** the required alignment for instances of this class */ + private static final int ALIGNMENT = 1; + + private static final boolean ENABLE_ENCODER_SELF_CHECK = false; + + /** {@code non-null;} the code this item represents */ + private final DalvCode code; + + private byte[] encoded; + + private final boolean isStatic; + private final CstMethodRef ref; + + public DebugInfoItem(DalvCode code, boolean isStatic, CstMethodRef ref) { + // We don't know the write size yet. + super (ALIGNMENT, -1); + + if (code == null) { + throw new NullPointerException("code == null"); + } + + this.code = code; + this.isStatic = isStatic; + this.ref = ref; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_DEBUG_INFO_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + // No contents to add. + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + // Encode the data and note the size. + + try { + encoded = encode(addedTo.getFile(), null, null, null, false); + setWriteSize(encoded.length); + } catch (RuntimeException ex) { + throw ExceptionWithContext.withContext(ex, + "...while placing debug info for " + ref.toHuman()); + } + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + throw new RuntimeException("unsupported"); + } + + /** + * Writes annotations for the elements of this list, as + * zero-length. This is meant to be used for dumping this instance + * directly after a code dump (with the real local list actually + * existing elsewhere in the output). + * + * @param file {@code non-null;} the file to use for referencing other sections + * @param out {@code non-null;} where to annotate to + * @param prefix {@code null-ok;} prefix to attach to each line of output + */ + public void annotateTo(DexFile file, AnnotatedOutput out, String prefix) { + encode(file, prefix, null, out, false); + } + + /** + * Does a human-friendly dump of this instance. + * + * @param out {@code non-null;} where to dump + * @param prefix {@code non-null;} prefix to attach to each line of output + */ + public void debugPrint(PrintWriter out, String prefix) { + encode(null, prefix, out, null, false); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + if (out.annotates()) { + /* + * Re-run the encoder to generate the annotations, + * but write the bits from the original encode + */ + + out.annotate(offsetString() + " debug info"); + encode(file, null, null, out, true); + } + + out.write(encoded); + } + + /** + * Performs debug info encoding. + * + * @param file {@code null-ok;} file to refer to during encoding + * @param prefix {@code null-ok;} prefix to attach to each line of output + * @param debugPrint {@code null-ok;} if specified, an alternate output for + * annotations + * @param out {@code null-ok;} if specified, where annotations should go + * @param consume whether to claim to have consumed output for + * {@code out} + * @return {@code non-null;} the encoded array + */ + private byte[] encode(DexFile file, String prefix, PrintWriter debugPrint, + AnnotatedOutput out, boolean consume) { + byte[] result = encode0(file, prefix, debugPrint, out, consume); + + if (ENABLE_ENCODER_SELF_CHECK && (file != null)) { + try { + DebugInfoDecoder.validateEncode(result, file, ref, code, + isStatic); + } catch (RuntimeException ex) { + // Reconvert, annotating to System.err. + encode0(file, "", new PrintWriter(System.err, true), null, + false); + throw ex; + } + } + + return result; + } + + /** + * Helper for {@link #encode} to do most of the work. + * + * @param file {@code null-ok;} file to refer to during encoding + * @param prefix {@code null-ok;} prefix to attach to each line of output + * @param debugPrint {@code null-ok;} if specified, an alternate output for + * annotations + * @param out {@code null-ok;} if specified, where annotations should go + * @param consume whether to claim to have consumed output for + * {@code out} + * @return {@code non-null;} the encoded array + */ + private byte[] encode0(DexFile file, String prefix, PrintWriter debugPrint, + AnnotatedOutput out, boolean consume) { + PositionList positions = code.getPositions(); + LocalList locals = code.getLocals(); + DalvInsnList insns = code.getInsns(); + int codeSize = insns.codeSize(); + int regSize = insns.getRegistersSize(); + + DebugInfoEncoder encoder = + new DebugInfoEncoder(positions, locals, + file, codeSize, regSize, isStatic, ref); + + byte[] result; + + if ((debugPrint == null) && (out == null)) { + result = encoder.convert(); + } else { + result = encoder.convertAndAnnotate(prefix, debugPrint, out, + consume); + } + + return result; + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/DexFile.java b/dexgen/src/com/android/dexgen/dex/file/DexFile.java new file mode 100644 index 0000000..e92aa10 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/DexFile.java @@ -0,0 +1,646 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.dex.file.MixedItemSection.SortType; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstBaseMethodRef; +import com.android.dexgen.rop.cst.CstEnumRef; +import com.android.dexgen.rop.cst.CstFieldRef; +import com.android.dexgen.rop.cst.CstString; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.util.ByteArrayAnnotatedOutput; +import com.android.dexgen.util.ExceptionWithContext; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Writer; +import java.security.DigestException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.zip.Adler32; + +/** + * Representation of an entire {@code .dex} (Dalvik EXecutable) + * file, which itself consists of a set of Dalvik classes. + */ +public final class DexFile { + /** {@code non-null;} word data section */ + private final MixedItemSection wordData; + + /** + * {@code non-null;} type lists section. This is word data, but separating + * it from {@link #wordData} helps break what would otherwise be a + * circular dependency between the that and {@link #protoIds}. + */ + private final MixedItemSection typeLists; + + /** + * {@code non-null;} map section. The map needs to be in a section by itself + * for the self-reference mechanics to work in a reasonably + * straightforward way. See {@link MapItem#addMap} for more detail. + */ + private final MixedItemSection map; + + /** {@code non-null;} string data section */ + private final MixedItemSection stringData; + + /** {@code non-null;} string identifiers section */ + private final StringIdsSection stringIds; + + /** {@code non-null;} type identifiers section */ + private final TypeIdsSection typeIds; + + /** {@code non-null;} prototype identifiers section */ + private final ProtoIdsSection protoIds; + + /** {@code non-null;} field identifiers section */ + private final FieldIdsSection fieldIds; + + /** {@code non-null;} method identifiers section */ + private final MethodIdsSection methodIds; + + /** {@code non-null;} class definitions section */ + private final ClassDefsSection classDefs; + + /** {@code non-null;} class data section */ + private final MixedItemSection classData; + + /** {@code non-null;} byte data section */ + private final MixedItemSection byteData; + + /** {@code non-null;} file header */ + private final HeaderSection header; + + /** + * {@code non-null;} array of sections in the order they will appear in the + * final output file + */ + private final Section[] sections; + + /** {@code >= -1;} total file size or {@code -1} if unknown */ + private int fileSize; + + /** {@code >= 40;} maximum width of the file dump */ + private int dumpWidth; + + /** + * Constructs an instance. It is initially empty. + */ + public DexFile() { + header = new HeaderSection(this); + typeLists = new MixedItemSection(null, this, 4, SortType.NONE); + wordData = new MixedItemSection("word_data", this, 4, SortType.TYPE); + stringData = + new MixedItemSection("string_data", this, 1, SortType.INSTANCE); + classData = new MixedItemSection(null, this, 1, SortType.NONE); + byteData = new MixedItemSection("byte_data", this, 1, SortType.TYPE); + stringIds = new StringIdsSection(this); + typeIds = new TypeIdsSection(this); + protoIds = new ProtoIdsSection(this); + fieldIds = new FieldIdsSection(this); + methodIds = new MethodIdsSection(this); + classDefs = new ClassDefsSection(this); + map = new MixedItemSection("map", this, 4, SortType.NONE); + + /* + * This is the list of sections in the order they appear in + * the final output. + */ + sections = new Section[] { + header, stringIds, typeIds, protoIds, fieldIds, methodIds, + classDefs, wordData, typeLists, stringData, byteData, + classData, map }; + + fileSize = -1; + dumpWidth = 79; + } + + /** + * Adds a class to this instance. It is illegal to attempt to add more + * than one class with the same name. + * + * @param clazz {@code non-null;} the class to add + */ + public void add(ClassDefItem clazz) { + classDefs.add(clazz); + } + + /** + * Gets the class definition with the given name, if any. + * + * @param name {@code non-null;} the class name to look for + * @return {@code null-ok;} the class with the given name, or {@code null} + * if there is no such class + */ + public ClassDefItem getClassOrNull(String name) { + try { + Type type = Type.internClassName(name); + return (ClassDefItem) classDefs.get(new CstType(type)); + } catch (IllegalArgumentException ex) { + // Translate exception, per contract. + return null; + } + } + + /** + * Writes the contents of this instance as either a binary or a + * human-readable form, or both. + * + * @param out {@code null-ok;} where to write to + * @param humanOut {@code null-ok;} where to write human-oriented output to + * @param verbose whether to be verbose when writing human-oriented output + */ + public void writeTo(OutputStream out, Writer humanOut, boolean verbose) + throws IOException { + boolean annotate = (humanOut != null); + ByteArrayAnnotatedOutput result = toDex0(annotate, verbose); + + if (out != null) { + out.write(result.getArray()); + } + + if (annotate) { + result.writeAnnotationsTo(humanOut); + } + } + + /** + * Returns the contents of this instance as a {@code .dex} file, + * in {@code byte[]} form. + * + * @param humanOut {@code null-ok;} where to write human-oriented output to + * @param verbose whether to be verbose when writing human-oriented output + * @return {@code non-null;} a {@code .dex} file for this instance + */ + public byte[] toDex(Writer humanOut, boolean verbose) + throws IOException { + boolean annotate = (humanOut != null); + ByteArrayAnnotatedOutput result = toDex0(annotate, verbose); + + if (annotate) { + result.writeAnnotationsTo(humanOut); + } + + return result.getArray(); + } + + /** + * Sets the maximum width of the human-oriented dump of the instance. + * + * @param dumpWidth {@code >= 40;} the width + */ + public void setDumpWidth(int dumpWidth) { + if (dumpWidth < 40) { + throw new IllegalArgumentException("dumpWidth < 40"); + } + + this.dumpWidth = dumpWidth; + } + + /** + * Gets the total file size, if known. + * + * <p>This is package-scope in order to allow + * the {@link HeaderSection} to set itself up properly.</p> + * + * @return {@code >= 0;} the total file size + * @throws RuntimeException thrown if the file size is not yet known + */ + /*package*/ int getFileSize() { + if (fileSize < 0) { + throw new RuntimeException("file size not yet known"); + } + + return fileSize; + } + + /** + * Gets the string data section. + * + * <p>This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.</p> + * + * @return {@code non-null;} the string data section + */ + /*package*/ MixedItemSection getStringData() { + return stringData; + } + + /** + * Gets the word data section. + * + * <p>This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.</p> + * + * @return {@code non-null;} the word data section + */ + /*package*/ MixedItemSection getWordData() { + return wordData; + } + + /** + * Gets the type lists section. + * + * <p>This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.</p> + * + * @return {@code non-null;} the word data section + */ + /*package*/ MixedItemSection getTypeLists() { + return typeLists; + } + + /** + * Gets the map section. + * + * <p>This is package-scope in order to allow the header section + * to query it.</p> + * + * @return {@code non-null;} the map section + */ + /*package*/ MixedItemSection getMap() { + return map; + } + + /** + * Gets the string identifiers section. + * + * <p>This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.</p> + * + * @return {@code non-null;} the string identifiers section + */ + /*package*/ StringIdsSection getStringIds() { + return stringIds; + } + + /** + * Gets the class definitions section. + * + * <p>This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.</p> + * + * @return {@code non-null;} the class definitions section + */ + /*package*/ ClassDefsSection getClassDefs() { + return classDefs; + } + + /** + * Gets the class data section. + * + * <p>This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.</p> + * + * @return {@code non-null;} the class data section + */ + /*package*/ MixedItemSection getClassData() { + return classData; + } + + /** + * Gets the type identifiers section. + * + * <p>This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.</p> + * + * @return {@code non-null;} the class identifiers section + */ + /*package*/ TypeIdsSection getTypeIds() { + return typeIds; + } + + /** + * Gets the prototype identifiers section. + * + * <p>This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.</p> + * + * @return {@code non-null;} the prototype identifiers section + */ + /*package*/ ProtoIdsSection getProtoIds() { + return protoIds; + } + + /** + * Gets the field identifiers section. + * + * <p>This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.</p> + * + * @return {@code non-null;} the field identifiers section + */ + /*package*/ FieldIdsSection getFieldIds() { + return fieldIds; + } + + /** + * Gets the method identifiers section. + * + * <p>This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.</p> + * + * @return {@code non-null;} the method identifiers section + */ + /*package*/ MethodIdsSection getMethodIds() { + return methodIds; + } + + /** + * Gets the byte data section. + * + * <p>This is package-scope in order to allow + * the various {@link Item} instances to add items to the + * instance.</p> + * + * @return {@code non-null;} the byte data section + */ + /*package*/ MixedItemSection getByteData() { + return byteData; + } + + /** + * Gets the first section of the file that is to be considered + * part of the data section. + * + * <p>This is package-scope in order to allow the header section + * to query it.</p> + * + * @return {@code non-null;} the section + */ + /*package*/ Section getFirstDataSection() { + return wordData; + } + + /** + * Gets the last section of the file that is to be considered + * part of the data section. + * + * <p>This is package-scope in order to allow the header section + * to query it.</p> + * + * @return {@code non-null;} the section + */ + /*package*/ Section getLastDataSection() { + return map; + } + + /** + * Interns the given constant in the appropriate section of this + * instance, or do nothing if the given constant isn't the sort + * that should be interned. + * + * @param cst {@code non-null;} constant to possibly intern + */ + /*package*/ void internIfAppropriate(Constant cst) { + if (cst instanceof CstString) { + stringIds.intern((CstString) cst); + } else if (cst instanceof CstUtf8) { + stringIds.intern((CstUtf8) cst); + } else if (cst instanceof CstType) { + typeIds.intern((CstType) cst); + } else if (cst instanceof CstBaseMethodRef) { + methodIds.intern((CstBaseMethodRef) cst); + } else if (cst instanceof CstFieldRef) { + fieldIds.intern((CstFieldRef) cst); + } else if (cst instanceof CstEnumRef) { + fieldIds.intern(((CstEnumRef) cst).getFieldRef()); + } else if (cst == null) { + throw new NullPointerException("cst == null"); + } + } + + /** + * Gets the {@link IndexedItem} corresponding to the given constant, + * if it is a constant that has such a correspondence, or return + * {@code null} if it isn't such a constant. This will throw + * an exception if the given constant <i>should</i> have been found + * but wasn't. + * + * @param cst {@code non-null;} the constant to look up + * @return {@code null-ok;} its corresponding item, if it has a corresponding + * item, or {@code null} if it's not that sort of constant + */ + /*package*/ IndexedItem findItemOrNull(Constant cst) { + IndexedItem item; + + if (cst instanceof CstString) { + return stringIds.get(cst); + } else if (cst instanceof CstType) { + return typeIds.get(cst); + } else if (cst instanceof CstBaseMethodRef) { + return methodIds.get(cst); + } else if (cst instanceof CstFieldRef) { + return fieldIds.get(cst); + } else { + return null; + } + } + + /** + * Returns the contents of this instance as a {@code .dex} file, + * in a {@link ByteArrayAnnotatedOutput} instance. + * + * @param annotate whether or not to keep annotations + * @param verbose if annotating, whether to be verbose + * @return {@code non-null;} a {@code .dex} file for this instance + */ + private ByteArrayAnnotatedOutput toDex0(boolean annotate, + boolean verbose) { + /* + * The following is ordered so that the prepare() calls which + * add items happen before the calls to the sections that get + * added to. + */ + + classDefs.prepare(); + classData.prepare(); + wordData.prepare(); + byteData.prepare(); + methodIds.prepare(); + fieldIds.prepare(); + protoIds.prepare(); + typeLists.prepare(); + typeIds.prepare(); + stringIds.prepare(); + stringData.prepare(); + header.prepare(); + + // Place the sections within the file. + + int count = sections.length; + int offset = 0; + + for (int i = 0; i < count; i++) { + Section one = sections[i]; + int placedAt = one.setFileOffset(offset); + if (placedAt < offset) { + throw new RuntimeException("bogus placement for section " + i); + } + + try { + if (one == map) { + /* + * Inform the map of all the sections, and add it + * to the file. This can only be done after all + * the other items have been sorted and placed. + */ + MapItem.addMap(sections, map); + map.prepare(); + } + + if (one instanceof MixedItemSection) { + /* + * Place the items of a MixedItemSection that just + * got placed. + */ + ((MixedItemSection) one).placeItems(); + } + + offset = placedAt + one.writeSize(); + } catch (RuntimeException ex) { + throw ExceptionWithContext.withContext(ex, + "...while writing section " + i); + } + } + + // Write out all the sections. + + fileSize = offset; + byte[] barr = new byte[fileSize]; + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(barr); + + if (annotate) { + out.enableAnnotations(dumpWidth, verbose); + } + + for (int i = 0; i < count; i++) { + try { + Section one = sections[i]; + int zeroCount = one.getFileOffset() - out.getCursor(); + if (zeroCount < 0) { + throw new ExceptionWithContext("excess write of " + + (-zeroCount)); + } + out.writeZeroes(one.getFileOffset() - out.getCursor()); + one.writeTo(out); + } catch (RuntimeException ex) { + ExceptionWithContext ec; + if (ex instanceof ExceptionWithContext) { + ec = (ExceptionWithContext) ex; + } else { + ec = new ExceptionWithContext(ex); + } + ec.addContext("...while writing section " + i); + throw ec; + } + } + + if (out.getCursor() != fileSize) { + throw new RuntimeException("foreshortened write"); + } + + // Perform final bookkeeping. + + calcSignature(barr); + calcChecksum(barr); + + if (annotate) { + wordData.writeIndexAnnotation(out, ItemType.TYPE_CODE_ITEM, + "\nmethod code index:\n\n"); + getStatistics().writeAnnotation(out); + out.finishAnnotating(); + } + + return out; + } + + /** + * Generates and returns statistics for all the items in the file. + * + * @return {@code non-null;} the statistics + */ + public Statistics getStatistics() { + Statistics stats = new Statistics(); + + for (Section s : sections) { + stats.addAll(s); + } + + return stats; + } + + /** + * Calculates the signature for the {@code .dex} file in the + * given array, and modify the array to contain it. + * + * @param bytes {@code non-null;} the bytes of the file + */ + private static void calcSignature(byte[] bytes) { + MessageDigest md; + + try { + md = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException ex) { + throw new RuntimeException(ex); + } + + md.update(bytes, 32, bytes.length - 32); + + try { + int amt = md.digest(bytes, 12, 20); + if (amt != 20) { + throw new RuntimeException("unexpected digest write: " + amt + + " bytes"); + } + } catch (DigestException ex) { + throw new RuntimeException(ex); + } + } + + /** + * Calculates the checksum for the {@code .dex} file in the + * given array, and modify the array to contain it. + * + * @param bytes {@code non-null;} the bytes of the file + */ + private static void calcChecksum(byte[] bytes) { + Adler32 a32 = new Adler32(); + + a32.update(bytes, 12, bytes.length - 12); + + int sum = (int) a32.getValue(); + + bytes[8] = (byte) sum; + bytes[9] = (byte) (sum >> 8); + bytes[10] = (byte) (sum >> 16); + bytes[11] = (byte) (sum >> 24); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/EncodedArrayItem.java b/dexgen/src/com/android/dexgen/dex/file/EncodedArrayItem.java new file mode 100644 index 0000000..cef2375 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/EncodedArrayItem.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.annotation.Annotation; +import com.android.dexgen.rop.annotation.AnnotationVisibility; +import com.android.dexgen.rop.annotation.NameValuePair; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstAnnotation; +import com.android.dexgen.rop.cst.CstArray; +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.ByteArrayAnnotatedOutput; + +import java.util.Arrays; +import java.util.Comparator; + +/** + * Encoded array of constant values. + */ +public final class EncodedArrayItem extends OffsettedItem { + /** the required alignment for instances of this class */ + private static final int ALIGNMENT = 1; + + /** {@code non-null;} the array to represent */ + private final CstArray array; + + /** + * {@code null-ok;} encoded form, ready for writing to a file; set during + * {@link #place0} + */ + private byte[] encodedForm; + + /** + * Constructs an instance. + * + * @param array {@code non-null;} array to represent + */ + public EncodedArrayItem(CstArray array) { + /* + * The write size isn't known up-front because (the variable-lengthed) + * leb128 type is used to represent some things. + */ + super(ALIGNMENT, -1); + + if (array == null) { + throw new NullPointerException("array == null"); + } + + this.array = array; + this.encodedForm = null; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_ENCODED_ARRAY_ITEM; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return array.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(OffsettedItem other) { + EncodedArrayItem otherArray = (EncodedArrayItem) other; + + return array.compareTo(otherArray.array); + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return array.toHuman(); + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + ValueEncoder.addContents(file, array); + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + // Encode the data and note the size. + + ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput(); + ValueEncoder encoder = new ValueEncoder(addedTo.getFile(), out); + + encoder.writeArray(array, false); + encodedForm = out.toByteArray(); + setWriteSize(encodedForm.length); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + boolean annotates = out.annotates(); + + if (annotates) { + out.annotate(0, offsetString() + " encoded array"); + + /* + * The output is to be annotated, so redo the work previously + * done by place0(), except this time annotations will actually + * get emitted. + */ + ValueEncoder encoder = new ValueEncoder(file, out); + encoder.writeArray(array, true); + } else { + out.write(encodedForm); + } + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/EncodedField.java b/dexgen/src/com/android/dexgen/dex/file/EncodedField.java new file mode 100644 index 0000000..5af2b1f --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/EncodedField.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.code.AccessFlags; +import com.android.dexgen.rop.cst.CstFieldRef; +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; +import com.android.dexgen.util.Leb128Utils; + +import java.io.PrintWriter; + +/** + * Representation of a field of a class, of any sort. + */ +public final class EncodedField extends EncodedMember + implements Comparable<EncodedField> { + /** {@code non-null;} constant for the field */ + private final CstFieldRef field; + + /** + * Constructs an instance. + * + * @param field {@code non-null;} constant for the field + * @param accessFlags access flags + */ + public EncodedField(CstFieldRef field, int accessFlags) { + super(accessFlags); + + if (field == null) { + throw new NullPointerException("field == null"); + } + + /* + * TODO: Maybe check accessFlags, at least for + * easily-checked stuff? + */ + + this.field = field; + } + + /** {@inheritDoc} */ + public int hashCode() { + return field.hashCode(); + } + + /** {@inheritDoc} */ + public boolean equals(Object other) { + if (! (other instanceof EncodedField)) { + return false; + } + + return compareTo((EncodedField) other) == 0; + } + + /** + * {@inheritDoc} + * + * <p><b>Note:</b> This compares the method constants only, + * ignoring any associated code, because it should never be the + * case that two different items with the same method constant + * ever appear in the same list (or same file, even).</p> + */ + public int compareTo(EncodedField other) { + return field.compareTo(other.field); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(100); + + sb.append(getClass().getName()); + sb.append('{'); + sb.append(Hex.u2(getAccessFlags())); + sb.append(' '); + sb.append(field); + sb.append('}'); + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + FieldIdsSection fieldIds = file.getFieldIds(); + fieldIds.intern(field); + } + + /** {@inheritDoc} */ + @Override + public CstUtf8 getName() { + return field.getNat().getName(); + } + + /** {@inheritDoc} */ + public String toHuman() { + return field.toHuman(); + } + + /** {@inheritDoc} */ + @Override + public void debugPrint(PrintWriter out, boolean verbose) { + // TODO: Maybe put something better here? + out.println(toString()); + } + + /** + * Gets the constant for the field. + * + * @return {@code non-null;} the constant + */ + public CstFieldRef getRef() { + return field; + } + + /** {@inheritDoc} */ + @Override + public int encode(DexFile file, AnnotatedOutput out, + int lastIndex, int dumpSeq) { + int fieldIdx = file.getFieldIds().indexOf(field); + int diff = fieldIdx - lastIndex; + int accessFlags = getAccessFlags(); + + if (out.annotates()) { + out.annotate(0, String.format(" [%x] %s", dumpSeq, + field.toHuman())); + out.annotate(Leb128Utils.unsignedLeb128Size(diff), + " field_idx: " + Hex.u4(fieldIdx)); + out.annotate(Leb128Utils.unsignedLeb128Size(accessFlags), + " access_flags: " + + AccessFlags.fieldString(accessFlags)); + } + + out.writeUnsignedLeb128(diff); + out.writeUnsignedLeb128(accessFlags); + + return fieldIdx; + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/EncodedMember.java b/dexgen/src/com/android/dexgen/dex/file/EncodedMember.java new file mode 100644 index 0000000..6c31704 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/EncodedMember.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.ToHuman; + +import java.io.PrintWriter; + +/** + * Representation of a member (field or method) of a class, for the + * purposes of encoding it inside a {@link ClassDataItem}. + */ +public abstract class EncodedMember implements ToHuman { + /** access flags */ + private final int accessFlags; + + /** + * Constructs an instance. + * + * @param accessFlags access flags for the member + */ + public EncodedMember(int accessFlags) { + this.accessFlags = accessFlags; + } + + /** + * Gets the access flags. + * + * @return the access flags + */ + public final int getAccessFlags() { + return accessFlags; + } + + /** + * Gets the name. + * + * @return {@code non-null;} the name + */ + public abstract CstUtf8 getName(); + + /** + * Does a human-friendly dump of this instance. + * + * @param out {@code non-null;} where to dump + * @param verbose whether to be verbose with the output + */ + public abstract void debugPrint(PrintWriter out, boolean verbose); + + /** + * Populates a {@link DexFile} with items from within this instance. + * + * @param file {@code non-null;} the file to populate + */ + public abstract void addContents(DexFile file); + + /** + * Encodes this instance to the given output. + * + * @param file {@code non-null;} file this instance is part of + * @param out {@code non-null;} where to write to + * @param lastIndex {@code >= 0;} the previous member index value encoded, or + * {@code 0} if this is the first element to encode + * @param dumpSeq {@code >= 0;} sequence number of this instance for + * annotation purposes + * @return {@code >= 0;} the member index value that was encoded + */ + public abstract int encode(DexFile file, AnnotatedOutput out, + int lastIndex, int dumpSeq); +} diff --git a/dexgen/src/com/android/dexgen/dex/file/EncodedMethod.java b/dexgen/src/com/android/dexgen/dex/file/EncodedMethod.java new file mode 100644 index 0000000..a35ca2c --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/EncodedMethod.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.dex.code.DalvCode; +import com.android.dexgen.rop.code.AccessFlags; +import com.android.dexgen.rop.cst.CstMethodRef; +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.rop.type.TypeList; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; +import com.android.dexgen.util.Leb128Utils; + +import java.io.PrintWriter; + +/** + * Class that representats a method of a class. + */ +public final class EncodedMethod extends EncodedMember + implements Comparable<EncodedMethod> { + /** {@code non-null;} constant for the method */ + private final CstMethodRef method; + + /** + * {@code null-ok;} code for the method, if the method is neither + * {@code abstract} nor {@code native} + */ + private final CodeItem code; + + /** + * Constructs an instance. + * + * @param method {@code non-null;} constant for the method + * @param accessFlags access flags + * @param code {@code null-ok;} code for the method, if it is neither + * {@code abstract} nor {@code native} + * @param throwsList {@code non-null;} list of possibly-thrown exceptions, + * just used in generating debugging output (listings) + */ + public EncodedMethod(CstMethodRef method, int accessFlags, + DalvCode code, TypeList throwsList) { + super(accessFlags); + + if (method == null) { + throw new NullPointerException("method == null"); + } + + this.method = method; + + if (code == null) { + this.code = null; + } else { + boolean isStatic = (accessFlags & AccessFlags.ACC_STATIC) != 0; + this.code = new CodeItem(method, code, isStatic, throwsList); + } + } + + /** {@inheritDoc} */ + public boolean equals(Object other) { + if (! (other instanceof EncodedMethod)) { + return false; + } + + return compareTo((EncodedMethod) other) == 0; + } + + /** + * {@inheritDoc} + * + * <p><b>Note:</b> This compares the method constants only, + * ignoring any associated code, because it should never be the + * case that two different items with the same method constant + * ever appear in the same list (or same file, even).</p> + */ + public int compareTo(EncodedMethod other) { + return method.compareTo(other.method); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(100); + + sb.append(getClass().getName()); + sb.append('{'); + sb.append(Hex.u2(getAccessFlags())); + sb.append(' '); + sb.append(method); + + if (code != null) { + sb.append(' '); + sb.append(code); + } + + sb.append('}'); + + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + MethodIdsSection methodIds = file.getMethodIds(); + MixedItemSection wordData = file.getWordData(); + + methodIds.intern(method); + + if (code != null) { + wordData.add(code); + } + } + + /** {@inheritDoc} */ + public final String toHuman() { + return method.toHuman(); + } + + /** {@inheritDoc} */ + @Override + public final CstUtf8 getName() { + return method.getNat().getName(); + } + + /** {@inheritDoc} */ + @Override + public void debugPrint(PrintWriter out, boolean verbose) { + if (code == null) { + out.println(getRef().toHuman() + ": abstract or native"); + } else { + code.debugPrint(out, " ", verbose); + } + } + + /** + * Gets the constant for the method. + * + * @return {@code non-null;} the constant + */ + public final CstMethodRef getRef() { + return method; + } + + /** {@inheritDoc} */ + @Override + public int encode(DexFile file, AnnotatedOutput out, + int lastIndex, int dumpSeq) { + int methodIdx = file.getMethodIds().indexOf(method); + int diff = methodIdx - lastIndex; + int accessFlags = getAccessFlags(); + int codeOff = OffsettedItem.getAbsoluteOffsetOr0(code); + boolean hasCode = (codeOff != 0); + boolean shouldHaveCode = (accessFlags & + (AccessFlags.ACC_ABSTRACT | AccessFlags.ACC_NATIVE)) == 0; + + /* + * Verify that code appears if and only if a method is + * declared to have it. + */ + if (hasCode != shouldHaveCode) { + throw new UnsupportedOperationException( + "code vs. access_flags mismatch"); + } + + if (out.annotates()) { + out.annotate(0, String.format(" [%x] %s", dumpSeq, + method.toHuman())); + out.annotate(Leb128Utils.unsignedLeb128Size(diff), + " method_idx: " + Hex.u4(methodIdx)); + out.annotate(Leb128Utils.unsignedLeb128Size(accessFlags), + " access_flags: " + + AccessFlags.methodString(accessFlags)); + out.annotate(Leb128Utils.unsignedLeb128Size(codeOff), + " code_off: " + Hex.u4(codeOff)); + } + + out.writeUnsignedLeb128(diff); + out.writeUnsignedLeb128(accessFlags); + out.writeUnsignedLeb128(codeOff); + + return methodIdx; + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/FieldAnnotationStruct.java b/dexgen/src/com/android/dexgen/dex/file/FieldAnnotationStruct.java new file mode 100644 index 0000000..95e4dbc --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/FieldAnnotationStruct.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.annotation.Annotations; +import com.android.dexgen.rop.cst.CstFieldRef; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; +import com.android.dexgen.util.ToHuman; + +/** + * Association of a field and its annotations. + */ +public final class FieldAnnotationStruct + implements ToHuman, Comparable<FieldAnnotationStruct> { + /** {@code non-null;} the field in question */ + private final CstFieldRef field; + + /** {@code non-null;} the associated annotations */ + private AnnotationSetItem annotations; + + /** + * Constructs an instance. + * + * @param field {@code non-null;} the field in question + * @param annotations {@code non-null;} the associated annotations + */ + public FieldAnnotationStruct(CstFieldRef field, + AnnotationSetItem annotations) { + if (field == null) { + throw new NullPointerException("field == null"); + } + + if (annotations == null) { + throw new NullPointerException("annotations == null"); + } + + this.field = field; + this.annotations = annotations; + } + + /** {@inheritDoc} */ + public int hashCode() { + return field.hashCode(); + } + + /** {@inheritDoc} */ + public boolean equals(Object other) { + if (! (other instanceof FieldAnnotationStruct)) { + return false; + } + + return field.equals(((FieldAnnotationStruct) other).field); + } + + /** {@inheritDoc} */ + public int compareTo(FieldAnnotationStruct other) { + return field.compareTo(other.field); + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + FieldIdsSection fieldIds = file.getFieldIds(); + MixedItemSection wordData = file.getWordData(); + + fieldIds.intern(field); + annotations = wordData.intern(annotations); + } + + /** {@inheritDoc} */ + public void writeTo(DexFile file, AnnotatedOutput out) { + int fieldIdx = file.getFieldIds().indexOf(field); + int annotationsOff = annotations.getAbsoluteOffset(); + + if (out.annotates()) { + out.annotate(0, " " + field.toHuman()); + out.annotate(4, " field_idx: " + Hex.u4(fieldIdx)); + out.annotate(4, " annotations_off: " + + Hex.u4(annotationsOff)); + } + + out.writeInt(fieldIdx); + out.writeInt(annotationsOff); + } + + /** {@inheritDoc} */ + public String toHuman() { + return field.toHuman() + ": " + annotations; + } + + /** + * Gets the field this item is for. + * + * @return {@code non-null;} the field + */ + public CstFieldRef getField() { + return field; + } + + /** + * Gets the associated annotations. + * + * @return {@code non-null;} the annotations + */ + public Annotations getAnnotations() { + return annotations.getAnnotations(); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/FieldIdItem.java b/dexgen/src/com/android/dexgen/dex/file/FieldIdItem.java new file mode 100644 index 0000000..4d3721e --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/FieldIdItem.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.cst.CstFieldRef; + +/** + * Representation of a field reference inside a Dalvik file. + */ +public final class FieldIdItem extends MemberIdItem { + /** + * Constructs an instance. + * + * @param field {@code non-null;} the constant for the field + */ + public FieldIdItem(CstFieldRef field) { + super(field); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_FIELD_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + super.addContents(file); + + TypeIdsSection typeIds = file.getTypeIds(); + typeIds.intern(getFieldRef().getType()); + } + + /** + * Gets the field constant. + * + * @return {@code non-null;} the constant + */ + public CstFieldRef getFieldRef() { + return (CstFieldRef) getRef(); + } + + /** {@inheritDoc} */ + @Override + protected int getTypoidIdx(DexFile file) { + TypeIdsSection typeIds = file.getTypeIds(); + return typeIds.indexOf(getFieldRef().getType()); + } + + /** {@inheritDoc} */ + @Override + protected String getTypoidName() { + return "type_idx"; + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/FieldIdsSection.java b/dexgen/src/com/android/dexgen/dex/file/FieldIdsSection.java new file mode 100644 index 0000000..65177e4 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/FieldIdsSection.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstFieldRef; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; + +import java.util.Collection; +import java.util.TreeMap; + +/** + * Field refs list section of a {@code .dex} file. + */ +public final class FieldIdsSection extends MemberIdsSection { + /** + * {@code non-null;} map from field constants to {@link + * FieldIdItem} instances + */ + private final TreeMap<CstFieldRef, FieldIdItem> fieldIds; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param file {@code non-null;} file that this instance is part of + */ + public FieldIdsSection(DexFile file) { + super("field_ids", file); + + fieldIds = new TreeMap<CstFieldRef, FieldIdItem>(); + } + + /** {@inheritDoc} */ + @Override + public Collection<? extends Item> items() { + return fieldIds.values(); + } + + /** {@inheritDoc} */ + @Override + public IndexedItem get(Constant cst) { + if (cst == null) { + throw new NullPointerException("cst == null"); + } + + throwIfNotPrepared(); + + IndexedItem result = fieldIds.get((CstFieldRef) cst); + + if (result == null) { + throw new IllegalArgumentException("not found"); + } + + return result; + } + + /** + * Writes the portion of the file header that refers to this instance. + * + * @param out {@code non-null;} where to write + */ + public void writeHeaderPart(AnnotatedOutput out) { + throwIfNotPrepared(); + + int sz = fieldIds.size(); + int offset = (sz == 0) ? 0 : getFileOffset(); + + if (out.annotates()) { + out.annotate(4, "field_ids_size: " + Hex.u4(sz)); + out.annotate(4, "field_ids_off: " + Hex.u4(offset)); + } + + out.writeInt(sz); + out.writeInt(offset); + } + + /** + * Interns an element into this instance. + * + * @param field {@code non-null;} the reference to intern + * @return {@code non-null;} the interned reference + */ + public FieldIdItem intern(CstFieldRef field) { + if (field == null) { + throw new NullPointerException("field == null"); + } + + throwIfPrepared(); + + FieldIdItem result = fieldIds.get(field); + + if (result == null) { + result = new FieldIdItem(field); + fieldIds.put(field, result); + } + + return result; + } + + /** + * Gets the index of the given reference, which must have been added + * to this instance. + * + * @param ref {@code non-null;} the reference to look up + * @return {@code >= 0;} the reference's index + */ + public int indexOf(CstFieldRef ref) { + if (ref == null) { + throw new NullPointerException("ref == null"); + } + + throwIfNotPrepared(); + + FieldIdItem item = fieldIds.get(ref); + + if (item == null) { + throw new IllegalArgumentException("not found"); + } + + return item.getIndex(); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/HeaderItem.java b/dexgen/src/com/android/dexgen/dex/file/HeaderItem.java new file mode 100644 index 0000000..ed04e25 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/HeaderItem.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; + +/** + * File header section of a {@code .dex} file. + */ +public final class HeaderItem extends IndexedItem { + /** + * {@code non-null;} the file format magic number, represented as the + * low-order bytes of a string + */ + private static final String MAGIC = "dex\n035\0"; + + /** size of this section, in bytes */ + private static final int HEADER_SIZE = 0x70; + + /** the endianness tag */ + private static final int ENDIAN_TAG = 0x12345678; + + /** + * Constructs an instance. + */ + public HeaderItem() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_HEADER_ITEM; + } + + /** {@inheritDoc} */ + @Override + public int writeSize() { + return HEADER_SIZE; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + // Nothing to do here. + } + + /** {@inheritDoc} */ + @Override + public void writeTo(DexFile file, AnnotatedOutput out) { + int mapOff = file.getMap().getFileOffset(); + Section firstDataSection = file.getFirstDataSection(); + Section lastDataSection = file.getLastDataSection(); + int dataOff = firstDataSection.getFileOffset(); + int dataSize = lastDataSection.getFileOffset() + + lastDataSection.writeSize() - dataOff; + + if (out.annotates()) { + out.annotate(8, "magic: " + new CstUtf8(MAGIC).toQuoted()); + out.annotate(4, "checksum"); + out.annotate(20, "signature"); + out.annotate(4, "file_size: " + + Hex.u4(file.getFileSize())); + out.annotate(4, "header_size: " + Hex.u4(HEADER_SIZE)); + out.annotate(4, "endian_tag: " + Hex.u4(ENDIAN_TAG)); + out.annotate(4, "link_size: 0"); + out.annotate(4, "link_off: 0"); + out.annotate(4, "map_off: " + Hex.u4(mapOff)); + } + + // Write the magic number. + for (int i = 0; i < 8; i++) { + out.writeByte(MAGIC.charAt(i)); + } + + // Leave space for the checksum and signature. + out.writeZeroes(24); + + out.writeInt(file.getFileSize()); + out.writeInt(HEADER_SIZE); + out.writeInt(ENDIAN_TAG); + + /* + * Write zeroes for the link size and data, as the output + * isn't a staticly linked file. + */ + out.writeZeroes(8); + + out.writeInt(mapOff); + + // Write out each section's respective header part. + file.getStringIds().writeHeaderPart(out); + file.getTypeIds().writeHeaderPart(out); + file.getProtoIds().writeHeaderPart(out); + file.getFieldIds().writeHeaderPart(out); + file.getMethodIds().writeHeaderPart(out); + file.getClassDefs().writeHeaderPart(out); + + if (out.annotates()) { + out.annotate(4, "data_size: " + Hex.u4(dataSize)); + out.annotate(4, "data_off: " + Hex.u4(dataOff)); + } + + out.writeInt(dataSize); + out.writeInt(dataOff); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/HeaderSection.java b/dexgen/src/com/android/dexgen/dex/file/HeaderSection.java new file mode 100644 index 0000000..967a90a --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/HeaderSection.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.cst.Constant; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * File header section of a {@code .dex} file. + */ +public final class HeaderSection extends UniformItemSection { + /** {@code non-null;} the list of the one item in the section */ + private final List<HeaderItem> list; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param file {@code non-null;} file that this instance is part of + */ + public HeaderSection(DexFile file) { + super(null, file, 4); + + HeaderItem item = new HeaderItem(); + item.setIndex(0); + + this.list = Collections.singletonList(item); + } + + /** {@inheritDoc} */ + @Override + public IndexedItem get(Constant cst) { + return null; + } + + /** {@inheritDoc} */ + @Override + public Collection<? extends Item> items() { + return list; + } + + /** {@inheritDoc} */ + @Override + protected void orderItems() { + // Nothing to do here. + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/IdItem.java b/dexgen/src/com/android/dexgen/dex/file/IdItem.java new file mode 100644 index 0000000..0f8301e --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/IdItem.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.cst.CstType; + +/** + * Representation of a reference to an item inside a Dalvik file. + */ +public abstract class IdItem extends IndexedItem { + /** + * {@code non-null;} the type constant for the defining class of + * the reference + */ + private final CstType type; + + /** + * Constructs an instance. + * + * @param type {@code non-null;} the type constant for the defining + * class of the reference + */ + public IdItem(CstType type) { + if (type == null) { + throw new NullPointerException("type == null"); + } + + this.type = type; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + TypeIdsSection typeIds = file.getTypeIds(); + typeIds.intern(type); + } + + /** + * Gets the type constant for the defining class of the + * reference. + * + * @return {@code non-null;} the type constant + */ + public final CstType getDefiningClass() { + return type; + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/IndexedItem.java b/dexgen/src/com/android/dexgen/dex/file/IndexedItem.java new file mode 100644 index 0000000..cdc73cb --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/IndexedItem.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +/** + * An item in a Dalvik file which is referenced by index. + */ +public abstract class IndexedItem extends Item { + /** {@code >= -1;} assigned index of the item, or {@code -1} if not + * yet assigned */ + private int index; + + /** + * Constructs an instance. The index is initially unassigned. + */ + public IndexedItem() { + index = -1; + } + + /** + * Gets whether or not this instance has been assigned an index. + * + * @return {@code true} iff this instance has been assigned an index + */ + public final boolean hasIndex() { + return (index >= 0); + } + + /** + * Gets the item index. + * + * @return {@code >= 0;} the index + * @throws RuntimeException thrown if the item index is not yet assigned + */ + public final int getIndex() { + if (index < 0) { + throw new RuntimeException("index not yet set"); + } + + return index; + } + + /** + * Sets the item index. This method may only ever be called once + * per instance, and this will throw a {@code RuntimeException} if + * called a second (or subsequent) time. + * + * @param index {@code >= 0;} the item index + */ + public final void setIndex(int index) { + if (this.index != -1) { + throw new RuntimeException("index already set"); + } + + this.index = index; + } + + /** + * Gets the index of this item as a string, suitable for including in + * annotations. + * + * @return {@code non-null;} the index string + */ + public final String indexString() { + return '[' + Integer.toHexString(index) + ']'; + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/Item.java b/dexgen/src/com/android/dexgen/dex/file/Item.java new file mode 100644 index 0000000..45cdc94 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/Item.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.util.AnnotatedOutput; + +/** + * Base class for any structurally-significant and (potentially) + * repeated piece of a Dalvik file. + */ +public abstract class Item { + /** + * Constructs an instance. + */ + public Item() { + // This space intentionally left blank. + } + + /** + * Returns the item type for this instance. + * + * @return {@code non-null;} the item type + */ + public abstract ItemType itemType(); + + /** + * Returns the human name for the particular type of item this + * instance is. + * + * @return {@code non-null;} the name + */ + public final String typeName() { + return itemType().toHuman(); + } + + /** + * Gets the size of this instance when written, in bytes. + * + * @return {@code >= 0;} the write size + */ + public abstract int writeSize(); + + /** + * Populates a {@link DexFile} with items from within this instance. + * This will <i>not</i> add an item to the file for this instance itself + * (which should have been done by whatever refers to this instance). + * + * <p><b>Note:</b> Subclasses must override this to do something + * appropriate.</p> + * + * @param file {@code non-null;} the file to populate + */ + public abstract void addContents(DexFile file); + + /** + * Writes the representation of this instance to the given data section, + * using the given {@link DexFile} to look things up as needed. + * If this instance keeps track of its offset, then this method will + * note the written offset and will also throw an exception if this + * instance has already been written. + * + * @param file {@code non-null;} the file to use for reference + * @param out {@code non-null;} where to write to + */ + public abstract void writeTo(DexFile file, AnnotatedOutput out); +} diff --git a/dexgen/src/com/android/dexgen/dex/file/ItemType.java b/dexgen/src/com/android/dexgen/dex/file/ItemType.java new file mode 100644 index 0000000..b3e32d0 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/ItemType.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.util.ToHuman; + +/** + * Enumeration of all the top-level item types. + */ +public enum ItemType implements ToHuman { + TYPE_HEADER_ITEM( 0x0000, "header_item"), + TYPE_STRING_ID_ITEM( 0x0001, "string_id_item"), + TYPE_TYPE_ID_ITEM( 0x0002, "type_id_item"), + TYPE_PROTO_ID_ITEM( 0x0003, "proto_id_item"), + TYPE_FIELD_ID_ITEM( 0x0004, "field_id_item"), + TYPE_METHOD_ID_ITEM( 0x0005, "method_id_item"), + TYPE_CLASS_DEF_ITEM( 0x0006, "class_def_item"), + TYPE_MAP_LIST( 0x1000, "map_list"), + TYPE_TYPE_LIST( 0x1001, "type_list"), + TYPE_ANNOTATION_SET_REF_LIST( 0x1002, "annotation_set_ref_list"), + TYPE_ANNOTATION_SET_ITEM( 0x1003, "annotation_set_item"), + TYPE_CLASS_DATA_ITEM( 0x2000, "class_data_item"), + TYPE_CODE_ITEM( 0x2001, "code_item"), + TYPE_STRING_DATA_ITEM( 0x2002, "string_data_item"), + TYPE_DEBUG_INFO_ITEM( 0x2003, "debug_info_item"), + TYPE_ANNOTATION_ITEM( 0x2004, "annotation_item"), + TYPE_ENCODED_ARRAY_ITEM( 0x2005, "encoded_array_item"), + TYPE_ANNOTATIONS_DIRECTORY_ITEM(0x2006, "annotations_directory_item"), + TYPE_MAP_ITEM( -1, "map_item"), + TYPE_TYPE_ITEM( -1, "type_item"), + TYPE_EXCEPTION_HANDLER_ITEM( -1, "exception_handler_item"), + TYPE_ANNOTATION_SET_REF_ITEM( -1, "annotation_set_ref_item"); + + /** value when represented in a {@link MapItem} */ + private final int mapValue; + + /** {@code non-null;} name of the type */ + private final String typeName; + + /** {@code non-null;} the short human name */ + private final String humanName; + + /** + * Constructs an instance. + * + * @param mapValue value when represented in a {@link MapItem} + * @param typeName {@code non-null;} name of the type + */ + private ItemType(int mapValue, String typeName) { + this.mapValue = mapValue; + this.typeName = typeName; + + // Make the human name. + String human = typeName; + if (human.endsWith("_item")) { + human = human.substring(0, human.length() - 5); + } + this.humanName = human.replace('_', ' '); + } + + /** + * Gets the map value. + * + * @return the map value + */ + public int getMapValue() { + return mapValue; + } + + /** + * Gets the type name. + * + * @return {@code non-null;} the type name + */ + public String getTypeName() { + return typeName; + } + + /** {@inheritDoc} */ + public String toHuman() { + return humanName; + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/MapItem.java b/dexgen/src/com/android/dexgen/dex/file/MapItem.java new file mode 100644 index 0000000..02472d4 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/MapItem.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; + +import java.util.ArrayList; + +/** + * Class that represents a map item. + */ +public final class MapItem extends OffsettedItem { + /** file alignment of this class, in bytes */ + private static final int ALIGNMENT = 4; + + /** write size of this class, in bytes: three {@code uint}s */ + private static final int WRITE_SIZE = (4 * 3); + + /** {@code non-null;} item type this instance covers */ + private final ItemType type; + + /** {@code non-null;} section this instance covers */ + private final Section section; + + /** + * {@code null-ok;} first item covered or {@code null} if this is + * a self-reference + */ + private final Item firstItem; + + /** + * {@code null-ok;} last item covered or {@code null} if this is + * a self-reference + */ + private final Item lastItem; + + /** + * {@code > 0;} count of items covered; {@code 1} if this + * is a self-reference + */ + private final int itemCount; + + /** + * Constructs a list item with instances of this class representing + * the contents of the given array of sections, adding it to the + * given map section. + * + * @param sections {@code non-null;} the sections + * @param mapSection {@code non-null;} the section that the resulting map + * should be added to; it should be empty on entry to this method + */ + public static void addMap(Section[] sections, + MixedItemSection mapSection) { + if (sections == null) { + throw new NullPointerException("sections == null"); + } + + if (mapSection.items().size() != 0) { + throw new IllegalArgumentException( + "mapSection.items().size() != 0"); + } + + ArrayList<MapItem> items = new ArrayList<MapItem>(50); + + for (Section section : sections) { + ItemType currentType = null; + Item firstItem = null; + Item lastItem = null; + int count = 0; + + for (Item item : section.items()) { + ItemType type = item.itemType(); + if (type != currentType) { + if (count != 0) { + items.add(new MapItem(currentType, section, + firstItem, lastItem, count)); + } + currentType = type; + firstItem = item; + count = 0; + } + lastItem = item; + count++; + } + + if (count != 0) { + // Add a MapItem for the final items in the section. + items.add(new MapItem(currentType, section, + firstItem, lastItem, count)); + } else if (section == mapSection) { + // Add a MapItem for the self-referential section. + items.add(new MapItem(mapSection)); + } + } + + mapSection.add( + new UniformListItem<MapItem>(ItemType.TYPE_MAP_LIST, items)); + } + + /** + * Constructs an instance. + * + * @param type {@code non-null;} item type this instance covers + * @param section {@code non-null;} section this instance covers + * @param firstItem {@code non-null;} first item covered + * @param lastItem {@code non-null;} last item covered + * @param itemCount {@code > 0;} count of items covered + */ + private MapItem(ItemType type, Section section, Item firstItem, + Item lastItem, int itemCount) { + super(ALIGNMENT, WRITE_SIZE); + + if (type == null) { + throw new NullPointerException("type == null"); + } + + if (section == null) { + throw new NullPointerException("section == null"); + } + + if (firstItem == null) { + throw new NullPointerException("firstItem == null"); + } + + if (lastItem == null) { + throw new NullPointerException("lastItem == null"); + } + + if (itemCount <= 0) { + throw new IllegalArgumentException("itemCount <= 0"); + } + + this.type = type; + this.section = section; + this.firstItem = firstItem; + this.lastItem = lastItem; + this.itemCount = itemCount; + } + + /** + * Constructs a self-referential instance. This instance is meant to + * represent the section containing the {@code map_list}. + * + * @param section {@code non-null;} section this instance covers + */ + private MapItem(Section section) { + super(ALIGNMENT, WRITE_SIZE); + + if (section == null) { + throw new NullPointerException("section == null"); + } + + this.type = ItemType.TYPE_MAP_LIST; + this.section = section; + this.firstItem = null; + this.lastItem = null; + this.itemCount = 1; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_MAP_ITEM; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(100); + + sb.append(getClass().getName()); + sb.append('{'); + sb.append(section.toString()); + sb.append(' '); + sb.append(type.toHuman()); + sb.append('}'); + + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + // We have nothing to add. + } + + /** {@inheritDoc} */ + @Override + public final String toHuman() { + return toString(); + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + int value = type.getMapValue(); + int offset; + + if (firstItem == null) { + offset = section.getFileOffset(); + } else { + offset = section.getAbsoluteItemOffset(firstItem); + } + + if (out.annotates()) { + out.annotate(0, offsetString() + ' ' + type.getTypeName() + + " map"); + out.annotate(2, " type: " + Hex.u2(value) + " // " + + type.toString()); + out.annotate(2, " unused: 0"); + out.annotate(4, " size: " + Hex.u4(itemCount)); + out.annotate(4, " offset: " + Hex.u4(offset)); + } + + out.writeShort(value); + out.writeShort(0); // unused + out.writeInt(itemCount); + out.writeInt(offset); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/MemberIdItem.java b/dexgen/src/com/android/dexgen/dex/file/MemberIdItem.java new file mode 100644 index 0000000..d638f07 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/MemberIdItem.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.cst.CstMemberRef; +import com.android.dexgen.rop.cst.CstNat; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; + +/** + * Representation of a member (field or method) reference inside a + * Dalvik file. + */ +public abstract class MemberIdItem extends IdItem { + /** size of instances when written out to a file, in bytes */ + public static final int WRITE_SIZE = 8; + + /** {@code non-null;} the constant for the member */ + private final CstMemberRef cst; + + /** + * Constructs an instance. + * + * @param cst {@code non-null;} the constant for the member + */ + public MemberIdItem(CstMemberRef cst) { + super(cst.getDefiningClass()); + + this.cst = cst; + } + + /** {@inheritDoc} */ + @Override + public int writeSize() { + return WRITE_SIZE; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + super.addContents(file); + + StringIdsSection stringIds = file.getStringIds(); + stringIds.intern(getRef().getNat().getName()); + } + + /** {@inheritDoc} */ + @Override + public final void writeTo(DexFile file, AnnotatedOutput out) { + TypeIdsSection typeIds = file.getTypeIds(); + StringIdsSection stringIds = file.getStringIds(); + CstNat nat = cst.getNat(); + int classIdx = typeIds.indexOf(getDefiningClass()); + int nameIdx = stringIds.indexOf(nat.getName()); + int typoidIdx = getTypoidIdx(file); + + if (out.annotates()) { + out.annotate(0, indexString() + ' ' + cst.toHuman()); + out.annotate(2, " class_idx: " + Hex.u2(classIdx)); + out.annotate(2, String.format(" %-10s %s", getTypoidName() + ':', + Hex.u2(typoidIdx))); + out.annotate(4, " name_idx: " + Hex.u4(nameIdx)); + } + + out.writeShort(classIdx); + out.writeShort(typoidIdx); + out.writeInt(nameIdx); + } + + /** + * Returns the index of the type-like thing associated with + * this item, in order that it may be written out. Subclasses must + * override this to get whatever it is they need to store. + * + * @param file {@code non-null;} the file being written + * @return the index in question + */ + protected abstract int getTypoidIdx(DexFile file); + + /** + * Returns the field name of the type-like thing associated with + * this item, for listing-generating purposes. Subclasses must override + * this. + * + * @return {@code non-null;} the name in question + */ + protected abstract String getTypoidName(); + + /** + * Gets the member constant. + * + * @return {@code non-null;} the constant + */ + public final CstMemberRef getRef() { + return cst; + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/MemberIdsSection.java b/dexgen/src/com/android/dexgen/dex/file/MemberIdsSection.java new file mode 100644 index 0000000..dcfca30 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/MemberIdsSection.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +/** + * Member (field or method) refs list section of a {@code .dex} file. + */ +public abstract class MemberIdsSection extends UniformItemSection { + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param name {@code null-ok;} the name of this instance, for annotation + * purposes + * @param file {@code non-null;} file that this instance is part of + */ + public MemberIdsSection(String name, DexFile file) { + super(name, file, 4); + } + + /** {@inheritDoc} */ + @Override + protected void orderItems() { + int idx = 0; + + for (Object i : items()) { + ((MemberIdItem) i).setIndex(idx); + idx++; + } + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/MethodAnnotationStruct.java b/dexgen/src/com/android/dexgen/dex/file/MethodAnnotationStruct.java new file mode 100644 index 0000000..e511f10 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/MethodAnnotationStruct.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.annotation.Annotations; +import com.android.dexgen.rop.cst.CstMethodRef; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; +import com.android.dexgen.util.ToHuman; + +/** + * Association of a method and its annotations. + */ +public final class MethodAnnotationStruct + implements ToHuman, Comparable<MethodAnnotationStruct> { + /** {@code non-null;} the method in question */ + private final CstMethodRef method; + + /** {@code non-null;} the associated annotations */ + private AnnotationSetItem annotations; + + /** + * Constructs an instance. + * + * @param method {@code non-null;} the method in question + * @param annotations {@code non-null;} the associated annotations + */ + public MethodAnnotationStruct(CstMethodRef method, + AnnotationSetItem annotations) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + if (annotations == null) { + throw new NullPointerException("annotations == null"); + } + + this.method = method; + this.annotations = annotations; + } + + /** {@inheritDoc} */ + public int hashCode() { + return method.hashCode(); + } + + /** {@inheritDoc} */ + public boolean equals(Object other) { + if (! (other instanceof MethodAnnotationStruct)) { + return false; + } + + return method.equals(((MethodAnnotationStruct) other).method); + } + + /** {@inheritDoc} */ + public int compareTo(MethodAnnotationStruct other) { + return method.compareTo(other.method); + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + MethodIdsSection methodIds = file.getMethodIds(); + MixedItemSection wordData = file.getWordData(); + + methodIds.intern(method); + annotations = wordData.intern(annotations); + } + + /** {@inheritDoc} */ + public void writeTo(DexFile file, AnnotatedOutput out) { + int methodIdx = file.getMethodIds().indexOf(method); + int annotationsOff = annotations.getAbsoluteOffset(); + + if (out.annotates()) { + out.annotate(0, " " + method.toHuman()); + out.annotate(4, " method_idx: " + Hex.u4(methodIdx)); + out.annotate(4, " annotations_off: " + + Hex.u4(annotationsOff)); + } + + out.writeInt(methodIdx); + out.writeInt(annotationsOff); + } + + /** {@inheritDoc} */ + public String toHuman() { + return method.toHuman() + ": " + annotations; + } + + /** + * Gets the method this item is for. + * + * @return {@code non-null;} the method + */ + public CstMethodRef getMethod() { + return method; + } + + /** + * Gets the associated annotations. + * + * @return {@code non-null;} the annotations + */ + public Annotations getAnnotations() { + return annotations.getAnnotations(); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/MethodIdItem.java b/dexgen/src/com/android/dexgen/dex/file/MethodIdItem.java new file mode 100644 index 0000000..da14e19 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/MethodIdItem.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.cst.CstBaseMethodRef; + +/** + * Representation of a method reference inside a Dalvik file. + */ +public final class MethodIdItem extends MemberIdItem { + /** + * Constructs an instance. + * + * @param method {@code non-null;} the constant for the method + */ + public MethodIdItem(CstBaseMethodRef method) { + super(method); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_METHOD_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + super.addContents(file); + + ProtoIdsSection protoIds = file.getProtoIds(); + protoIds.intern(getMethodRef().getPrototype()); + } + + /** + * Gets the method constant. + * + * @return {@code non-null;} the constant + */ + public CstBaseMethodRef getMethodRef() { + return (CstBaseMethodRef) getRef(); + } + + /** {@inheritDoc} */ + @Override + protected int getTypoidIdx(DexFile file) { + ProtoIdsSection protoIds = file.getProtoIds(); + return protoIds.indexOf(getMethodRef().getPrototype()); + } + + /** {@inheritDoc} */ + @Override + protected String getTypoidName() { + return "proto_idx"; + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/MethodIdsSection.java b/dexgen/src/com/android/dexgen/dex/file/MethodIdsSection.java new file mode 100644 index 0000000..3a06af7 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/MethodIdsSection.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstBaseMethodRef; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; + +import java.util.Collection; +import java.util.TreeMap; + +/** + * Method refs list section of a {@code .dex} file. + */ +public final class MethodIdsSection extends MemberIdsSection { + /** + * {@code non-null;} map from method constants to {@link + * MethodIdItem} instances + */ + private final TreeMap<CstBaseMethodRef, MethodIdItem> methodIds; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param file {@code non-null;} file that this instance is part of + */ + public MethodIdsSection(DexFile file) { + super("method_ids", file); + + methodIds = new TreeMap<CstBaseMethodRef, MethodIdItem>(); + } + + /** {@inheritDoc} */ + @Override + public Collection<? extends Item> items() { + return methodIds.values(); + } + + /** {@inheritDoc} */ + @Override + public IndexedItem get(Constant cst) { + if (cst == null) { + throw new NullPointerException("cst == null"); + } + + throwIfNotPrepared(); + + IndexedItem result = methodIds.get((CstBaseMethodRef) cst); + + if (result == null) { + throw new IllegalArgumentException("not found"); + } + + return result; + } + + /** + * Writes the portion of the file header that refers to this instance. + * + * @param out {@code non-null;} where to write + */ + public void writeHeaderPart(AnnotatedOutput out) { + throwIfNotPrepared(); + + int sz = methodIds.size(); + int offset = (sz == 0) ? 0 : getFileOffset(); + + if (out.annotates()) { + out.annotate(4, "method_ids_size: " + Hex.u4(sz)); + out.annotate(4, "method_ids_off: " + Hex.u4(offset)); + } + + out.writeInt(sz); + out.writeInt(offset); + } + + /** + * Interns an element into this instance. + * + * @param method {@code non-null;} the reference to intern + * @return {@code non-null;} the interned reference + */ + public MethodIdItem intern(CstBaseMethodRef method) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + throwIfPrepared(); + + MethodIdItem result = methodIds.get(method); + + if (result == null) { + result = new MethodIdItem(method); + methodIds.put(method, result); + } + + return result; + } + + /** + * Gets the index of the given reference, which must have been added + * to this instance. + * + * @param ref {@code non-null;} the reference to look up + * @return {@code >= 0;} the reference's index + */ + public int indexOf(CstBaseMethodRef ref) { + if (ref == null) { + throw new NullPointerException("ref == null"); + } + + throwIfNotPrepared(); + + MethodIdItem item = methodIds.get(ref); + + if (item == null) { + throw new IllegalArgumentException("not found"); + } + + return item.getIndex(); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/MixedItemSection.java b/dexgen/src/com/android/dexgen/dex/file/MixedItemSection.java new file mode 100644 index 0000000..2fda33b --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/MixedItemSection.java @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.ExceptionWithContext; +import com.android.dexgen.util.Hex; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.TreeMap; + +/** + * A section of a {@code .dex} file which consists of a sequence of + * {@link OffsettedItem} objects, which may each be of a different concrete + * class and/or size. + * + * <b>Note:</b> It is invalid for an item in an instance of this class to + * have a larger alignment requirement than the alignment of this instance. + */ +public final class MixedItemSection extends Section { + static enum SortType { + /** no sorting */ + NONE, + + /** sort by type only */ + TYPE, + + /** sort in class-major order, with instances sorted per-class */ + INSTANCE; + }; + + /** {@code non-null;} sorter which sorts instances by type */ + private static final Comparator<OffsettedItem> TYPE_SORTER = + new Comparator<OffsettedItem>() { + public int compare(OffsettedItem item1, OffsettedItem item2) { + ItemType type1 = item1.itemType(); + ItemType type2 = item2.itemType(); + return type1.compareTo(type2); + } + }; + + /** {@code non-null;} the items in this part */ + private final ArrayList<OffsettedItem> items; + + /** {@code non-null;} items that have been explicitly interned */ + private final HashMap<OffsettedItem, OffsettedItem> interns; + + /** {@code non-null;} how to sort the items */ + private final SortType sort; + + /** + * {@code >= -1;} the current size of this part, in bytes, or {@code -1} + * if not yet calculated + */ + private int writeSize; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param name {@code null-ok;} the name of this instance, for annotation + * purposes + * @param file {@code non-null;} file that this instance is part of + * @param alignment {@code > 0;} alignment requirement for the final output; + * must be a power of 2 + * @param sort how the items should be sorted in the final output + */ + public MixedItemSection(String name, DexFile file, int alignment, + SortType sort) { + super(name, file, alignment); + + this.items = new ArrayList<OffsettedItem>(100); + this.interns = new HashMap<OffsettedItem, OffsettedItem>(100); + this.sort = sort; + this.writeSize = -1; + } + + /** {@inheritDoc} */ + @Override + public Collection<? extends Item> items() { + return items; + } + + /** {@inheritDoc} */ + @Override + public int writeSize() { + throwIfNotPrepared(); + return writeSize; + } + + /** {@inheritDoc} */ + @Override + public int getAbsoluteItemOffset(Item item) { + OffsettedItem oi = (OffsettedItem) item; + return oi.getAbsoluteOffset(); + } + + /** + * Gets the size of this instance, in items. + * + * @return {@code >= 0;} the size + */ + public int size() { + return items.size(); + } + + /** + * Writes the portion of the file header that refers to this instance. + * + * @param out {@code non-null;} where to write + */ + public void writeHeaderPart(AnnotatedOutput out) { + throwIfNotPrepared(); + + if (writeSize == -1) { + throw new RuntimeException("write size not yet set"); + } + + int sz = writeSize; + int offset = (sz == 0) ? 0 : getFileOffset(); + String name = getName(); + + if (name == null) { + name = "<unnamed>"; + } + + int spaceCount = 15 - name.length(); + char[] spaceArr = new char[spaceCount]; + Arrays.fill(spaceArr, ' '); + String spaces = new String(spaceArr); + + if (out.annotates()) { + out.annotate(4, name + "_size:" + spaces + Hex.u4(sz)); + out.annotate(4, name + "_off: " + spaces + Hex.u4(offset)); + } + + out.writeInt(sz); + out.writeInt(offset); + } + + /** + * Adds an item to this instance. This will in turn tell the given item + * that it has been added to this instance. It is invalid to add the + * same item to more than one instance, nor to add the same items + * multiple times to a single instance. + * + * @param item {@code non-null;} the item to add + */ + public void add(OffsettedItem item) { + throwIfPrepared(); + + try { + if (item.getAlignment() > getAlignment()) { + throw new IllegalArgumentException( + "incompatible item alignment"); + } + } catch (NullPointerException ex) { + // Elucidate the exception. + throw new NullPointerException("item == null"); + } + + items.add(item); + } + + /** + * Interns an item in this instance, returning the interned instance + * (which may not be the one passed in). This will add the item if no + * equal item has been added. + * + * @param item {@code non-null;} the item to intern + * @return {@code non-null;} the equivalent interned instance + */ + public <T extends OffsettedItem> T intern(T item) { + throwIfPrepared(); + + OffsettedItem result = interns.get(item); + + if (result != null) { + return (T) result; + } + + add(item); + interns.put(item, item); + return item; + } + + /** + * Gets an item which was previously interned. + * + * @param item {@code non-null;} the item to look for + * @return {@code non-null;} the equivalent already-interned instance + */ + public <T extends OffsettedItem> T get(T item) { + throwIfNotPrepared(); + + OffsettedItem result = interns.get(item); + + if (result != null) { + return (T) result; + } + + throw new NoSuchElementException(item.toString()); + } + + /** + * Writes an index of contents of the items in this instance of the + * given type. If there are none, this writes nothing. If there are any, + * then the index is preceded by the given intro string. + * + * @param out {@code non-null;} where to write to + * @param itemType {@code non-null;} the item type of interest + * @param intro {@code non-null;} the introductory string for non-empty indices + */ + public void writeIndexAnnotation(AnnotatedOutput out, ItemType itemType, + String intro) { + throwIfNotPrepared(); + + TreeMap<String, OffsettedItem> index = + new TreeMap<String, OffsettedItem>(); + + for (OffsettedItem item : items) { + if (item.itemType() == itemType) { + String label = item.toHuman(); + index.put(label, item); + } + } + + if (index.size() == 0) { + return; + } + + out.annotate(0, intro); + + for (Map.Entry<String, OffsettedItem> entry : index.entrySet()) { + String label = entry.getKey(); + OffsettedItem item = entry.getValue(); + out.annotate(0, item.offsetString() + ' ' + label + '\n'); + } + } + + /** {@inheritDoc} */ + @Override + protected void prepare0() { + DexFile file = getFile(); + + /* + * It's okay for new items to be added as a result of an + * addContents() call; we just have to deal with the possibility. + */ + + int i = 0; + for (;;) { + int sz = items.size(); + if (i >= sz) { + break; + } + + for (/*i*/; i < sz; i++) { + OffsettedItem one = items.get(i); + one.addContents(file); + } + } + } + + /** + * Places all the items in this instance at particular offsets. This + * will call {@link OffsettedItem#place} on each item. If an item + * does not know its write size before the call to {@code place}, + * it is that call which is responsible for setting the write size. + * This method may only be called once per instance; subsequent calls + * will throw an exception. + */ + public void placeItems() { + throwIfNotPrepared(); + + switch (sort) { + case INSTANCE: { + Collections.sort(items); + break; + } + case TYPE: { + Collections.sort(items, TYPE_SORTER); + break; + } + } + + int sz = items.size(); + int outAt = 0; + for (int i = 0; i < sz; i++) { + OffsettedItem one = items.get(i); + try { + int placedAt = one.place(this, outAt); + + if (placedAt < outAt) { + throw new RuntimeException("bogus place() result for " + + one); + } + + outAt = placedAt + one.writeSize(); + } catch (RuntimeException ex) { + throw ExceptionWithContext.withContext(ex, + "...while placing " + one); + } + } + + writeSize = outAt; + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(AnnotatedOutput out) { + boolean annotates = out.annotates(); + boolean first = true; + DexFile file = getFile(); + int at = 0; + + for (OffsettedItem one : items) { + if (annotates) { + if (first) { + first = false; + } else { + out.annotate(0, "\n"); + } + } + + int alignMask = one.getAlignment() - 1; + int writeAt = (at + alignMask) & ~alignMask; + + if (at != writeAt) { + out.writeZeroes(writeAt - at); + at = writeAt; + } + + one.writeTo(file, out); + at += one.writeSize(); + } + + if (at != writeSize) { + throw new RuntimeException("output size mismatch"); + } + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/OffsettedItem.java b/dexgen/src/com/android/dexgen/dex/file/OffsettedItem.java new file mode 100644 index 0000000..246f903 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/OffsettedItem.java @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.ExceptionWithContext; + +/** + * An item in a Dalvik file which is referenced by absolute offset. + */ +public abstract class OffsettedItem extends Item + implements Comparable<OffsettedItem> { + /** {@code > 0;} alignment requirement */ + private final int alignment; + + /** {@code >= -1;} the size of this instance when written, in bytes, or + * {@code -1} if not yet known */ + private int writeSize; + + /** + * {@code null-ok;} section the item was added to, or {@code null} if + * not yet added + */ + private Section addedTo; + + /** + * {@code >= -1;} assigned offset of the item from the start of its section, + * or {@code -1} if not yet assigned + */ + private int offset; + + /** + * Gets the absolute offset of the given item, returning {@code 0} + * if handed {@code null}. + * + * @param item {@code null-ok;} the item in question + * @return {@code >= 0;} the item's absolute offset, or {@code 0} + * if {@code item == null} + */ + public static int getAbsoluteOffsetOr0(OffsettedItem item) { + if (item == null) { + return 0; + } + + return item.getAbsoluteOffset(); + } + + /** + * Constructs an instance. The offset is initially unassigned. + * + * @param alignment {@code > 0;} output alignment requirement; must be a + * power of 2 + * @param writeSize {@code >= -1;} the size of this instance when written, + * in bytes, or {@code -1} if not immediately known + */ + public OffsettedItem(int alignment, int writeSize) { + Section.validateAlignment(alignment); + + if (writeSize < -1) { + throw new IllegalArgumentException("writeSize < -1"); + } + + this.alignment = alignment; + this.writeSize = writeSize; + this.addedTo = null; + this.offset = -1; + } + + /** + * {@inheritDoc} + * + * Comparisons for this class are defined to be type-major (if the + * types don't match then the objects are not equal), with + * {@link #compareTo0} deciding same-type comparisons. + */ + @Override + public final boolean equals(Object other) { + if (this == other) { + return true; + } + + OffsettedItem otherItem = (OffsettedItem) other; + ItemType thisType = itemType(); + ItemType otherType = otherItem.itemType(); + + if (thisType != otherType) { + return false; + } + + return (compareTo0(otherItem) == 0); + } + + /** + * {@inheritDoc} + * + * Comparisons for this class are defined to be class-major (if the + * classes don't match then the objects are not equal), with + * {@link #compareTo0} deciding same-class comparisons. + */ + public final int compareTo(OffsettedItem other) { + if (this == other) { + return 0; + } + + ItemType thisType = itemType(); + ItemType otherType = other.itemType(); + + if (thisType != otherType) { + return thisType.compareTo(otherType); + } + + return compareTo0(other); + } + + /** + * Sets the write size of this item. This may only be called once + * per instance, and only if the size was unknown upon instance + * creation. + * + * @param writeSize {@code > 0;} the write size, in bytes + */ + public final void setWriteSize(int writeSize) { + if (writeSize < 0) { + throw new IllegalArgumentException("writeSize < 0"); + } + + if (this.writeSize >= 0) { + throw new UnsupportedOperationException("writeSize already set"); + } + + this.writeSize = writeSize; + } + + /** {@inheritDoc} + * + * @throws UnsupportedOperationException thrown if the write size + * is not yet known + */ + @Override + public final int writeSize() { + if (writeSize < 0) { + throw new UnsupportedOperationException("writeSize is unknown"); + } + + return writeSize; + } + + /** {@inheritDoc} */ + @Override + public final void writeTo(DexFile file, AnnotatedOutput out) { + out.alignTo(alignment); + + try { + if (writeSize < 0) { + throw new UnsupportedOperationException( + "writeSize is unknown"); + } + out.assertCursor(getAbsoluteOffset()); + } catch (RuntimeException ex) { + throw ExceptionWithContext.withContext(ex, + "...while writing " + this); + } + + writeTo0(file, out); + } + + /** + * Gets the relative item offset. The offset is from the start of + * the section which the instance was written to. + * + * @return {@code >= 0;} the offset + * @throws RuntimeException thrown if the offset is not yet known + */ + public final int getRelativeOffset() { + if (offset < 0) { + throw new RuntimeException("offset not yet known"); + } + + return offset; + } + + /** + * Gets the absolute item offset. The offset is from the start of + * the file which the instance was written to. + * + * @return {@code >= 0;} the offset + * @throws RuntimeException thrown if the offset is not yet known + */ + public final int getAbsoluteOffset() { + if (offset < 0) { + throw new RuntimeException("offset not yet known"); + } + + return addedTo.getAbsoluteOffset(offset); + } + + /** + * Indicates that this item has been added to the given section at + * the given offset. It is only valid to call this method once per + * instance. + * + * @param addedTo {@code non-null;} the section this instance has + * been added to + * @param offset {@code >= 0;} the desired offset from the start of the + * section where this instance was placed + * @return {@code >= 0;} the offset that this instance should be placed at + * in order to meet its alignment constraint + */ + public final int place(Section addedTo, int offset) { + if (addedTo == null) { + throw new NullPointerException("addedTo == null"); + } + + if (offset < 0) { + throw new IllegalArgumentException("offset < 0"); + } + + if (this.addedTo != null) { + throw new RuntimeException("already written"); + } + + int mask = alignment - 1; + offset = (offset + mask) & ~mask; + + this.addedTo = addedTo; + this.offset = offset; + + place0(addedTo, offset); + + return offset; + } + + /** + * Gets the alignment requirement of this instance. An instance should + * only be written when so aligned. + * + * @return {@code > 0;} the alignment requirement; must be a power of 2 + */ + public final int getAlignment() { + return alignment; + } + + /** + * Gets the absolute offset of this item as a string, suitable for + * including in annotations. + * + * @return {@code non-null;} the offset string + */ + public final String offsetString() { + return '[' + Integer.toHexString(getAbsoluteOffset()) + ']'; + } + + /** + * Gets a short human-readable string representing this instance. + * + * @return {@code non-null;} the human form + */ + public abstract String toHuman(); + + /** + * Compares this instance to another which is guaranteed to be of + * the same class. The default implementation of this method is to + * throw an exception (unsupported operation). If a particular + * class needs to actually sort, then it should override this + * method. + * + * @param other {@code non-null;} instance to compare to + * @return {@code -1}, {@code 0}, or {@code 1}, depending + * on the sort order of this instance and the other + */ + protected int compareTo0(OffsettedItem other) { + throw new UnsupportedOperationException("unsupported"); + } + + /** + * Does additional work required when placing an instance. The + * default implementation of this method is a no-op. If a + * particular class needs to do something special, then it should + * override this method. In particular, if this instance did not + * know its write size up-front, then this method is responsible + * for setting it. + * + * @param addedTo {@code non-null;} the section this instance has been added to + * @param offset {@code >= 0;} the offset from the start of the + * section where this instance was placed + */ + protected void place0(Section addedTo, int offset) { + // This space intentionally left blank. + } + + /** + * Performs the actual write of the contents of this instance to + * the given data section. This is called by {@link #writeTo}, + * which will have taken care of ensuring alignment. + * + * @param file {@code non-null;} the file to use for reference + * @param out {@code non-null;} where to write to + */ + protected abstract void writeTo0(DexFile file, AnnotatedOutput out); +} diff --git a/dexgen/src/com/android/dexgen/dex/file/ParameterAnnotationStruct.java b/dexgen/src/com/android/dexgen/dex/file/ParameterAnnotationStruct.java new file mode 100644 index 0000000..440da1c --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/ParameterAnnotationStruct.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.annotation.Annotations; +import com.android.dexgen.rop.annotation.AnnotationsList; +import com.android.dexgen.rop.cst.CstMethodRef; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; +import com.android.dexgen.util.ToHuman; + +import java.util.ArrayList; + +/** + * Association of a method and its parameter annotations. + */ +public final class ParameterAnnotationStruct + implements ToHuman, Comparable<ParameterAnnotationStruct> { + /** {@code non-null;} the method in question */ + private final CstMethodRef method; + + /** {@code non-null;} the associated annotations list */ + private final AnnotationsList annotationsList; + + /** {@code non-null;} the associated annotations list, as an item */ + private final UniformListItem<AnnotationSetRefItem> annotationsItem; + + /** + * Constructs an instance. + * + * @param method {@code non-null;} the method in question + * @param annotationsList {@code non-null;} the associated annotations list + */ + public ParameterAnnotationStruct(CstMethodRef method, + AnnotationsList annotationsList) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + if (annotationsList == null) { + throw new NullPointerException("annotationsList == null"); + } + + this.method = method; + this.annotationsList = annotationsList; + + /* + * Construct an item for the annotations list. TODO: This + * requires way too much copying; fix it. + */ + + int size = annotationsList.size(); + ArrayList<AnnotationSetRefItem> arrayList = new + ArrayList<AnnotationSetRefItem>(size); + + for (int i = 0; i < size; i++) { + Annotations annotations = annotationsList.get(i); + AnnotationSetItem item = new AnnotationSetItem(annotations); + arrayList.add(new AnnotationSetRefItem(item)); + } + + this.annotationsItem = new UniformListItem<AnnotationSetRefItem>( + ItemType.TYPE_ANNOTATION_SET_REF_LIST, arrayList); + } + + /** {@inheritDoc} */ + public int hashCode() { + return method.hashCode(); + } + + /** {@inheritDoc} */ + public boolean equals(Object other) { + if (! (other instanceof ParameterAnnotationStruct)) { + return false; + } + + return method.equals(((ParameterAnnotationStruct) other).method); + } + + /** {@inheritDoc} */ + public int compareTo(ParameterAnnotationStruct other) { + return method.compareTo(other.method); + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + MethodIdsSection methodIds = file.getMethodIds(); + MixedItemSection wordData = file.getWordData(); + + methodIds.intern(method); + wordData.add(annotationsItem); + } + + /** {@inheritDoc} */ + public void writeTo(DexFile file, AnnotatedOutput out) { + int methodIdx = file.getMethodIds().indexOf(method); + int annotationsOff = annotationsItem.getAbsoluteOffset(); + + if (out.annotates()) { + out.annotate(0, " " + method.toHuman()); + out.annotate(4, " method_idx: " + Hex.u4(methodIdx)); + out.annotate(4, " annotations_off: " + + Hex.u4(annotationsOff)); + } + + out.writeInt(methodIdx); + out.writeInt(annotationsOff); + } + + /** {@inheritDoc} */ + public String toHuman() { + StringBuilder sb = new StringBuilder(); + + sb.append(method.toHuman()); + sb.append(": "); + + boolean first = true; + for (AnnotationSetRefItem item : annotationsItem.getItems()) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(item.toHuman()); + } + + return sb.toString(); + } + + /** + * Gets the method this item is for. + * + * @return {@code non-null;} the method + */ + public CstMethodRef getMethod() { + return method; + } + + /** + * Gets the associated annotations list. + * + * @return {@code non-null;} the annotations list + */ + public AnnotationsList getAnnotationsList() { + return annotationsList; + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/ProtoIdItem.java b/dexgen/src/com/android/dexgen/dex/file/ProtoIdItem.java new file mode 100644 index 0000000..ef48cd4 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/ProtoIdItem.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.rop.type.Prototype; +import com.android.dexgen.rop.type.StdTypeList; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; + +/** + * Representation of a method prototype reference inside a Dalvik file. + */ +public final class ProtoIdItem extends IndexedItem { + /** size of instances when written out to a file, in bytes */ + public static final int WRITE_SIZE = 12; + + /** {@code non-null;} the wrapped prototype */ + private final Prototype prototype; + + /** {@code non-null;} the short-form of the prototype */ + private final CstUtf8 shortForm; + + /** + * {@code null-ok;} the list of parameter types or {@code null} if this + * prototype has no parameters + */ + private TypeListItem parameterTypes; + + /** + * Constructs an instance. + * + * @param prototype {@code non-null;} the constant for the prototype + */ + public ProtoIdItem(Prototype prototype) { + if (prototype == null) { + throw new NullPointerException("prototype == null"); + } + + this.prototype = prototype; + this.shortForm = makeShortForm(prototype); + + StdTypeList parameters = prototype.getParameterTypes(); + this.parameterTypes = (parameters.size() == 0) ? null + : new TypeListItem(parameters); + } + + /** + * Creates the short-form of the given prototype. + * + * @param prototype {@code non-null;} the prototype + * @return {@code non-null;} the short form + */ + private static CstUtf8 makeShortForm(Prototype prototype) { + StdTypeList parameters = prototype.getParameterTypes(); + int size = parameters.size(); + StringBuilder sb = new StringBuilder(size + 1); + + sb.append(shortFormCharFor(prototype.getReturnType())); + + for (int i = 0; i < size; i++) { + sb.append(shortFormCharFor(parameters.getType(i))); + } + + return new CstUtf8(sb.toString()); + } + + /** + * Gets the short-form character for the given type. + * + * @param type {@code non-null;} the type + * @return the corresponding short-form character + */ + private static char shortFormCharFor(Type type) { + char descriptorChar = type.getDescriptor().charAt(0); + + if (descriptorChar == '[') { + return 'L'; + } + + return descriptorChar; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_PROTO_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public int writeSize() { + return WRITE_SIZE; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + StringIdsSection stringIds = file.getStringIds(); + TypeIdsSection typeIds = file.getTypeIds(); + MixedItemSection typeLists = file.getTypeLists(); + + typeIds.intern(prototype.getReturnType()); + stringIds.intern(shortForm); + + if (parameterTypes != null) { + parameterTypes = typeLists.intern(parameterTypes); + } + } + + /** {@inheritDoc} */ + @Override + public void writeTo(DexFile file, AnnotatedOutput out) { + int shortyIdx = file.getStringIds().indexOf(shortForm); + int returnIdx = file.getTypeIds().indexOf(prototype.getReturnType()); + int paramsOff = OffsettedItem.getAbsoluteOffsetOr0(parameterTypes); + + if (out.annotates()) { + StringBuilder sb = new StringBuilder(); + sb.append(prototype.getReturnType().toHuman()); + sb.append(" proto("); + + StdTypeList params = prototype.getParameterTypes(); + int size = params.size(); + + for (int i = 0; i < size; i++) { + if (i != 0) { + sb.append(", "); + } + sb.append(params.getType(i).toHuman()); + } + + sb.append(")"); + out.annotate(0, indexString() + ' ' + sb.toString()); + out.annotate(4, " shorty_idx: " + Hex.u4(shortyIdx) + + " // " + shortForm.toQuoted()); + out.annotate(4, " return_type_idx: " + Hex.u4(returnIdx) + + " // " + prototype.getReturnType().toHuman()); + out.annotate(4, " parameters_off: " + Hex.u4(paramsOff)); + } + + out.writeInt(shortyIdx); + out.writeInt(returnIdx); + out.writeInt(paramsOff); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/ProtoIdsSection.java b/dexgen/src/com/android/dexgen/dex/file/ProtoIdsSection.java new file mode 100644 index 0000000..b2af84e --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/ProtoIdsSection.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.type.Prototype; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; + +import java.util.Collection; +import java.util.TreeMap; + +/** + * Proto (method prototype) identifiers list section of a + * {@code .dex} file. + */ +public final class ProtoIdsSection extends UniformItemSection { + /** + * {@code non-null;} map from method prototypes to {@link ProtoIdItem} instances + */ + private final TreeMap<Prototype, ProtoIdItem> protoIds; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param file {@code non-null;} file that this instance is part of + */ + public ProtoIdsSection(DexFile file) { + super("proto_ids", file, 4); + + protoIds = new TreeMap<Prototype, ProtoIdItem>(); + } + + /** {@inheritDoc} */ + @Override + public Collection<? extends Item> items() { + return protoIds.values(); + } + + /** {@inheritDoc} */ + @Override + public IndexedItem get(Constant cst) { + throw new UnsupportedOperationException("unsupported"); + } + + /** + * Writes the portion of the file header that refers to this instance. + * + * @param out {@code non-null;} where to write + */ + public void writeHeaderPart(AnnotatedOutput out) { + throwIfNotPrepared(); + + int sz = protoIds.size(); + int offset = (sz == 0) ? 0 : getFileOffset(); + + if (sz > 65536) { + throw new UnsupportedOperationException("too many proto ids"); + } + + if (out.annotates()) { + out.annotate(4, "proto_ids_size: " + Hex.u4(sz)); + out.annotate(4, "proto_ids_off: " + Hex.u4(offset)); + } + + out.writeInt(sz); + out.writeInt(offset); + } + + /** + * Interns an element into this instance. + * + * @param prototype {@code non-null;} the prototype to intern + * @return {@code non-null;} the interned reference + */ + public ProtoIdItem intern(Prototype prototype) { + if (prototype == null) { + throw new NullPointerException("prototype == null"); + } + + throwIfPrepared(); + + ProtoIdItem result = protoIds.get(prototype); + + if (result == null) { + result = new ProtoIdItem(prototype); + protoIds.put(prototype, result); + } + + return result; + } + + /** + * Gets the index of the given prototype, which must have + * been added to this instance. + * + * @param prototype {@code non-null;} the prototype to look up + * @return {@code >= 0;} the reference's index + */ + public int indexOf(Prototype prototype) { + if (prototype == null) { + throw new NullPointerException("prototype == null"); + } + + throwIfNotPrepared(); + + ProtoIdItem item = protoIds.get(prototype); + + if (item == null) { + throw new IllegalArgumentException("not found"); + } + + return item.getIndex(); + } + + /** {@inheritDoc} */ + @Override + protected void orderItems() { + int idx = 0; + + for (Object i : items()) { + ((ProtoIdItem) i).setIndex(idx); + idx++; + } + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/Section.java b/dexgen/src/com/android/dexgen/dex/file/Section.java new file mode 100644 index 0000000..7efaf6b --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/Section.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.util.AnnotatedOutput; + +import java.util.Collection; + +/** + * A section of a {@code .dex} file. Each section consists of a list + * of items of some sort or other. + */ +public abstract class Section { + /** {@code null-ok;} name of this part, for annotation purposes */ + private final String name; + + /** {@code non-null;} file that this instance is part of */ + private final DexFile file; + + /** {@code > 0;} alignment requirement for the final output; + * must be a power of 2 */ + private final int alignment; + + /** {@code >= -1;} offset from the start of the file to this part, or + * {@code -1} if not yet known */ + private int fileOffset; + + /** whether {@link #prepare} has been called successfully on this + * instance */ + private boolean prepared; + + /** + * Validates an alignment. + * + * @param alignment the alignment + * @throws IllegalArgumentException thrown if {@code alignment} + * isn't a positive power of 2 + */ + public static void validateAlignment(int alignment) { + if ((alignment <= 0) || + (alignment & (alignment - 1)) != 0) { + throw new IllegalArgumentException("invalid alignment"); + } + } + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param name {@code null-ok;} the name of this instance, for annotation + * purposes + * @param file {@code non-null;} file that this instance is part of + * @param alignment {@code > 0;} alignment requirement for the final output; + * must be a power of 2 + */ + public Section(String name, DexFile file, int alignment) { + if (file == null) { + throw new NullPointerException("file == null"); + } + + validateAlignment(alignment); + + this.name = name; + this.file = file; + this.alignment = alignment; + this.fileOffset = -1; + this.prepared = false; + } + + /** + * Gets the file that this instance is part of. + * + * @return {@code non-null;} the file + */ + public final DexFile getFile() { + return file; + } + + /** + * Gets the alignment for this instance's final output. + * + * @return {@code > 0;} the alignment + */ + public final int getAlignment() { + return alignment; + } + + /** + * Gets the offset from the start of the file to this part. This + * throws an exception if the offset has not yet been set. + * + * @return {@code >= 0;} the file offset + */ + public final int getFileOffset() { + if (fileOffset < 0) { + throw new RuntimeException("fileOffset not set"); + } + + return fileOffset; + } + + /** + * Sets the file offset. It is only valid to call this method once + * once per instance. + * + * @param fileOffset {@code >= 0;} the desired offset from the start of the + * file where this for this instance + * @return {@code >= 0;} the offset that this instance should be placed at + * in order to meet its alignment constraint + */ + public final int setFileOffset(int fileOffset) { + if (fileOffset < 0) { + throw new IllegalArgumentException("fileOffset < 0"); + } + + if (this.fileOffset >= 0) { + throw new RuntimeException("fileOffset already set"); + } + + int mask = alignment - 1; + fileOffset = (fileOffset + mask) & ~mask; + + this.fileOffset = fileOffset; + + return fileOffset; + } + + /** + * Writes this instance to the given raw data object. + * + * @param out {@code non-null;} where to write to + */ + public final void writeTo(AnnotatedOutput out) { + throwIfNotPrepared(); + align(out); + + int cursor = out.getCursor(); + + if (fileOffset < 0) { + fileOffset = cursor; + } else if (fileOffset != cursor) { + throw new RuntimeException("alignment mismatch: for " + this + + ", at " + cursor + + ", but expected " + fileOffset); + } + + if (out.annotates()) { + if (name != null) { + out.annotate(0, "\n" + name + ":"); + } else if (cursor != 0) { + out.annotate(0, "\n"); + } + } + + writeTo0(out); + } + + /** + * Returns the absolute file offset, given an offset from the + * start of this instance's output. This is only valid to call + * once this instance has been assigned a file offset (via {@link + * #setFileOffset}). + * + * @param relative {@code >= 0;} the relative offset + * @return {@code >= 0;} the corresponding absolute file offset + */ + public final int getAbsoluteOffset(int relative) { + if (relative < 0) { + throw new IllegalArgumentException("relative < 0"); + } + + if (fileOffset < 0) { + throw new RuntimeException("fileOffset not yet set"); + } + + return fileOffset + relative; + } + + /** + * Returns the absolute file offset of the given item which must + * be contained in this section. This is only valid to call + * once this instance has been assigned a file offset (via {@link + * #setFileOffset}). + * + * <p><b>Note:</b> Subclasses must implement this as appropriate for + * their contents.</p> + * + * @param item {@code non-null;} the item in question + * @return {@code >= 0;} the item's absolute file offset + */ + public abstract int getAbsoluteItemOffset(Item item); + + /** + * Prepares this instance for writing. This performs any necessary + * prerequisites, including particularly adding stuff to other + * sections. This method may only be called once per instance; + * subsequent calls will throw an exception. + */ + public final void prepare() { + throwIfPrepared(); + prepare0(); + prepared = true; + } + + /** + * Gets the collection of all the items in this section. + * It is not valid to attempt to change the returned list. + * + * @return {@code non-null;} the items + */ + public abstract Collection<? extends Item> items(); + + /** + * Does the main work of {@link #prepare}. + */ + protected abstract void prepare0(); + + /** + * Gets the size of this instance when output, in bytes. + * + * @return {@code >= 0;} the size of this instance, in bytes + */ + public abstract int writeSize(); + + /** + * Throws an exception if {@link #prepare} has not been + * called on this instance. + */ + protected final void throwIfNotPrepared() { + if (!prepared) { + throw new RuntimeException("not prepared"); + } + } + + /** + * Throws an exception if {@link #prepare} has already been called + * on this instance. + */ + protected final void throwIfPrepared() { + if (prepared) { + throw new RuntimeException("already prepared"); + } + } + + /** + * Aligns the output of the given data to the alignment of this instance. + * + * @param out {@code non-null;} the output to align + */ + protected final void align(AnnotatedOutput out) { + out.alignTo(alignment); + } + + /** + * Writes this instance to the given raw data object. This gets + * called by {@link #writeTo} after aligning the cursor of + * {@code out} and verifying that either the assigned file + * offset matches the actual cursor {@code out} or that the + * file offset was not previously assigned, in which case it gets + * assigned to {@code out}'s cursor. + * + * @param out {@code non-null;} where to write to + */ + protected abstract void writeTo0(AnnotatedOutput out); + + /** + * Returns the name of this section, for annotation purposes. + * + * @return {@code null-ok;} name of this part, for annotation purposes + */ + protected final String getName() { + return name; + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/Statistics.java b/dexgen/src/com/android/dexgen/dex/file/Statistics.java new file mode 100644 index 0000000..1ec2f93 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/Statistics.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.util.AnnotatedOutput; + +import java.util.Collection; +import java.util.HashMap; +import java.util.TreeMap; + +/** + * Statistics about the contents of a file. + */ +public final class Statistics { + /** {@code non-null;} data about each type of item */ + private final HashMap<String, Data> dataMap; + + /** + * Constructs an instance. + */ + public Statistics() { + dataMap = new HashMap<String, Data>(50); + } + + /** + * Adds the given item to the statistics. + * + * @param item {@code non-null;} the item to add + */ + public void add(Item item) { + String typeName = item.typeName(); + Data data = dataMap.get(typeName); + + if (data == null) { + dataMap.put(typeName, new Data(item, typeName)); + } else { + data.add(item); + } + } + + /** + * Adds the given list of items to the statistics. + * + * @param list {@code non-null;} the list of items to add + */ + public void addAll(Section list) { + Collection<? extends Item> items = list.items(); + for (Item item : items) { + add(item); + } + } + + /** + * Writes the statistics as an annotation. + * + * @param out {@code non-null;} where to write to + */ + public final void writeAnnotation(AnnotatedOutput out) { + if (dataMap.size() == 0) { + return; + } + + out.annotate(0, "\nstatistics:\n"); + + TreeMap<String, Data> sortedData = new TreeMap<String, Data>(); + + for (Data data : dataMap.values()) { + sortedData.put(data.name, data); + } + + for (Data data : sortedData.values()) { + data.writeAnnotation(out); + } + } + + public String toHuman() { + StringBuilder sb = new StringBuilder(); + + sb.append("Statistics:\n"); + + TreeMap<String, Data> sortedData = new TreeMap<String, Data>(); + + for (Data data : dataMap.values()) { + sortedData.put(data.name, data); + } + + for (Data data : sortedData.values()) { + sb.append(data.toHuman()); + } + + return sb.toString(); + } + + /** + * Statistical data about a particular class. + */ + private static class Data { + /** {@code non-null;} name to use as a label */ + private final String name; + + /** {@code >= 0;} number of instances */ + private int count; + + /** {@code >= 0;} total size of instances in bytes */ + private int totalSize; + + /** {@code >= 0;} largest size of any individual item */ + private int largestSize; + + /** {@code >= 0;} smallest size of any individual item */ + private int smallestSize; + + /** + * Constructs an instance for the given item. + * + * @param item {@code non-null;} item in question + * @param name {@code non-null;} type name to use + */ + public Data(Item item, String name) { + int size = item.writeSize(); + + this.name = name; + this.count = 1; + this.totalSize = size; + this.largestSize = size; + this.smallestSize = size; + } + + /** + * Incorporates a new item. This assumes the type name matches. + * + * @param item {@code non-null;} item to incorporate + */ + public void add(Item item) { + int size = item.writeSize(); + + count++; + totalSize += size; + + if (size > largestSize) { + largestSize = size; + } + + if (size < smallestSize) { + smallestSize = size; + } + } + + /** + * Writes this instance as an annotation. + * + * @param out {@code non-null;} where to write to + */ + public void writeAnnotation(AnnotatedOutput out) { + out.annotate(toHuman()); + } + + /** + * Generates a human-readable string for this data item. + * + * @return string for human consumption. + */ + public String toHuman() { + StringBuilder sb = new StringBuilder(); + + sb.append(" " + name + ": " + + count + " item" + (count == 1 ? "" : "s") + "; " + + totalSize + " bytes total\n"); + + if (smallestSize == largestSize) { + sb.append(" " + smallestSize + " bytes/item\n"); + } else { + int average = totalSize / count; + sb.append(" " + smallestSize + ".." + largestSize + + " bytes/item; average " + average + "\n"); + } + + return sb.toString(); + } + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/StringDataItem.java b/dexgen/src/com/android/dexgen/dex/file/StringDataItem.java new file mode 100644 index 0000000..7e28323 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/StringDataItem.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.ByteArray; +import com.android.dexgen.util.Hex; +import com.android.dexgen.util.Leb128Utils; + +/** + * Representation of string data for a particular string, in a Dalvik file. + */ +public final class StringDataItem extends OffsettedItem { + /** {@code non-null;} the string value */ + private final CstUtf8 value; + + /** + * Constructs an instance. + * + * @param value {@code non-null;} the string value + */ + public StringDataItem(CstUtf8 value) { + super(1, writeSize(value)); + + this.value = value; + } + + /** + * Gets the write size for a given value. + * + * @param value {@code non-null;} the string value + * @return {@code >= 2}; the write size, in bytes + */ + private static int writeSize(CstUtf8 value) { + int utf16Size = value.getUtf16Size(); + + // The +1 is for the '\0' termination byte. + return Leb128Utils.unsignedLeb128Size(utf16Size) + + value.getUtf8Size() + 1; + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_STRING_DATA_ITEM; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + // Nothing to do here. + } + + /** {@inheritDoc} */ + @Override + public void writeTo0(DexFile file, AnnotatedOutput out) { + ByteArray bytes = value.getBytes(); + int utf16Size = value.getUtf16Size(); + + if (out.annotates()) { + out.annotate(Leb128Utils.unsignedLeb128Size(utf16Size), + "utf16_size: " + Hex.u4(utf16Size)); + out.annotate(bytes.size() + 1, value.toQuoted()); + } + + out.writeUnsignedLeb128(utf16Size); + out.write(bytes); + out.writeByte(0); + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + return value.toQuoted(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(OffsettedItem other) { + StringDataItem otherData = (StringDataItem) other; + + return value.compareTo(otherData.value); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/StringIdItem.java b/dexgen/src/com/android/dexgen/dex/file/StringIdItem.java new file mode 100644 index 0000000..30f31d4 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/StringIdItem.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; + +/** + * Representation of a string inside a Dalvik file. + */ +public final class StringIdItem + extends IndexedItem implements Comparable { + /** size of instances when written out to a file, in bytes */ + public static final int WRITE_SIZE = 4; + + /** {@code non-null;} the string value */ + private final CstUtf8 value; + + /** {@code null-ok;} associated string data object, if known */ + private StringDataItem data; + + /** + * Constructs an instance. + * + * @param value {@code non-null;} the string value + */ + public StringIdItem(CstUtf8 value) { + if (value == null) { + throw new NullPointerException("value == null"); + } + + this.value = value; + this.data = null; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof StringIdItem)) { + return false; + } + + StringIdItem otherString = (StringIdItem) other; + return value.equals(otherString.value); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return value.hashCode(); + } + + /** {@inheritDoc} */ + public int compareTo(Object other) { + StringIdItem otherString = (StringIdItem) other; + return value.compareTo(otherString.value); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_STRING_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public int writeSize() { + return WRITE_SIZE; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + if (data == null) { + // The string data hasn't yet been added, so add it. + MixedItemSection stringData = file.getStringData(); + data = new StringDataItem(value); + stringData.add(data); + } + } + + /** {@inheritDoc} */ + @Override + public void writeTo(DexFile file, AnnotatedOutput out) { + int dataOff = data.getAbsoluteOffset(); + + if (out.annotates()) { + out.annotate(0, indexString() + ' ' + value.toQuoted(100)); + out.annotate(4, " string_data_off: " + Hex.u4(dataOff)); + } + + out.writeInt(dataOff); + } + + /** + * Gets the string value. + * + * @return {@code non-null;} the value + */ + public CstUtf8 getValue() { + return value; + } + + /** + * Gets the associated data object for this instance, if known. + * + * @return {@code null-ok;} the associated data object or {@code null} + * if not yet known + */ + public StringDataItem getData() { + return data; + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/StringIdsSection.java b/dexgen/src/com/android/dexgen/dex/file/StringIdsSection.java new file mode 100644 index 0000000..9047fb9 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/StringIdsSection.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstNat; +import com.android.dexgen.rop.cst.CstString; +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; + +import java.util.Collection; +import java.util.TreeMap; + +/** + * Strings list section of a {@code .dex} file. + */ +public final class StringIdsSection + extends UniformItemSection { + /** + * {@code non-null;} map from string constants to {@link + * StringIdItem} instances + */ + private final TreeMap<CstUtf8, StringIdItem> strings; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param file {@code non-null;} file that this instance is part of + */ + public StringIdsSection(DexFile file) { + super("string_ids", file, 4); + + strings = new TreeMap<CstUtf8, StringIdItem>(); + } + + /** {@inheritDoc} */ + @Override + public Collection<? extends Item> items() { + return strings.values(); + } + + /** {@inheritDoc} */ + @Override + public IndexedItem get(Constant cst) { + if (cst == null) { + throw new NullPointerException("cst == null"); + } + + throwIfNotPrepared(); + + if (cst instanceof CstString) { + cst = ((CstString) cst).getString(); + } + + IndexedItem result = strings.get((CstUtf8) cst); + + if (result == null) { + throw new IllegalArgumentException("not found"); + } + + return result; + } + + /** + * Writes the portion of the file header that refers to this instance. + * + * @param out {@code non-null;} where to write + */ + public void writeHeaderPart(AnnotatedOutput out) { + throwIfNotPrepared(); + + int sz = strings.size(); + int offset = (sz == 0) ? 0 : getFileOffset(); + + if (out.annotates()) { + out.annotate(4, "string_ids_size: " + Hex.u4(sz)); + out.annotate(4, "string_ids_off: " + Hex.u4(offset)); + } + + out.writeInt(sz); + out.writeInt(offset); + } + + /** + * Interns an element into this instance. + * + * @param string {@code non-null;} the string to intern, as a regular Java + * {@code String} + * @return {@code non-null;} the interned string + */ + public StringIdItem intern(String string) { + CstUtf8 utf8 = new CstUtf8(string); + return intern(new StringIdItem(utf8)); + } + + /** + * Interns an element into this instance. + * + * @param string {@code non-null;} the string to intern, as a {@link CstString} + * @return {@code non-null;} the interned string + */ + public StringIdItem intern(CstString string) { + CstUtf8 utf8 = string.getString(); + return intern(new StringIdItem(utf8)); + } + + /** + * Interns an element into this instance. + * + * @param string {@code non-null;} the string to intern, as a constant + * @return {@code non-null;} the interned string + */ + public StringIdItem intern(CstUtf8 string) { + return intern(new StringIdItem(string)); + } + + /** + * Interns an element into this instance. + * + * @param string {@code non-null;} the string to intern + * @return {@code non-null;} the interned string + */ + public StringIdItem intern(StringIdItem string) { + if (string == null) { + throw new NullPointerException("string == null"); + } + + throwIfPrepared(); + + CstUtf8 value = string.getValue(); + StringIdItem already = strings.get(value); + + if (already != null) { + return already; + } + + strings.put(value, string); + return string; + } + + /** + * Interns the components of a name-and-type into this instance. + * + * @param nat {@code non-null;} the name-and-type + */ + public void intern(CstNat nat) { + intern(nat.getName()); + intern(nat.getDescriptor()); + } + + /** + * Gets the index of the given string, which must have been added + * to this instance. + * + * @param string {@code non-null;} the string to look up + * @return {@code >= 0;} the string's index + */ + public int indexOf(CstUtf8 string) { + if (string == null) { + throw new NullPointerException("string == null"); + } + + throwIfNotPrepared(); + + StringIdItem s = strings.get(string); + + if (s == null) { + throw new IllegalArgumentException("not found"); + } + + return s.getIndex(); + } + + /** + * Gets the index of the given string, which must have been added + * to this instance. + * + * @param string {@code non-null;} the string to look up + * @return {@code >= 0;} the string's index + */ + public int indexOf(CstString string) { + return indexOf(string.getString()); + } + + /** {@inheritDoc} */ + @Override + protected void orderItems() { + int idx = 0; + + for (StringIdItem s : strings.values()) { + s.setIndex(idx); + idx++; + } + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/TypeIdItem.java b/dexgen/src/com/android/dexgen/dex/file/TypeIdItem.java new file mode 100644 index 0000000..2c029b0 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/TypeIdItem.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; + +/** + * Representation of a type reference inside a Dalvik file. + */ +public final class TypeIdItem extends IdItem { + /** size of instances when written out to a file, in bytes */ + public static final int WRITE_SIZE = 4; + + /** + * Constructs an instance. + * + * @param type {@code non-null;} the constant for the type + */ + public TypeIdItem(CstType type) { + super(type); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_TYPE_ID_ITEM; + } + + /** {@inheritDoc} */ + @Override + public int writeSize() { + return WRITE_SIZE; + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + file.getStringIds().intern(getDefiningClass().getDescriptor()); + } + + /** {@inheritDoc} */ + @Override + public void writeTo(DexFile file, AnnotatedOutput out) { + CstType type = getDefiningClass(); + CstUtf8 descriptor = type.getDescriptor(); + int idx = file.getStringIds().indexOf(descriptor); + + if (out.annotates()) { + out.annotate(0, indexString() + ' ' + descriptor.toHuman()); + out.annotate(4, " descriptor_idx: " + Hex.u4(idx)); + } + + out.writeInt(idx); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/TypeIdsSection.java b/dexgen/src/com/android/dexgen/dex/file/TypeIdsSection.java new file mode 100644 index 0000000..b02b592 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/TypeIdsSection.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; + +import java.util.Collection; +import java.util.TreeMap; + +/** + * Type identifiers list section of a {@code .dex} file. + */ +public final class TypeIdsSection extends UniformItemSection { + /** + * {@code non-null;} map from types to {@link TypeIdItem} instances + */ + private final TreeMap<Type, TypeIdItem> typeIds; + + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param file {@code non-null;} file that this instance is part of + */ + public TypeIdsSection(DexFile file) { + super("type_ids", file, 4); + + typeIds = new TreeMap<Type, TypeIdItem>(); + } + + /** {@inheritDoc} */ + @Override + public Collection<? extends Item> items() { + return typeIds.values(); + } + + /** {@inheritDoc} */ + @Override + public IndexedItem get(Constant cst) { + if (cst == null) { + throw new NullPointerException("cst == null"); + } + + throwIfNotPrepared(); + + Type type = ((CstType) cst).getClassType(); + IndexedItem result = typeIds.get(type); + + if (result == null) { + throw new IllegalArgumentException("not found: " + cst); + } + + return result; + } + + /** + * Writes the portion of the file header that refers to this instance. + * + * @param out {@code non-null;} where to write + */ + public void writeHeaderPart(AnnotatedOutput out) { + throwIfNotPrepared(); + + int sz = typeIds.size(); + int offset = (sz == 0) ? 0 : getFileOffset(); + + if (sz > 65536) { + throw new UnsupportedOperationException("too many type ids"); + } + + if (out.annotates()) { + out.annotate(4, "type_ids_size: " + Hex.u4(sz)); + out.annotate(4, "type_ids_off: " + Hex.u4(offset)); + } + + out.writeInt(sz); + out.writeInt(offset); + } + + /** + * Interns an element into this instance. + * + * @param type {@code non-null;} the type to intern + * @return {@code non-null;} the interned reference + */ + public TypeIdItem intern(Type type) { + if (type == null) { + throw new NullPointerException("type == null"); + } + + throwIfPrepared(); + + TypeIdItem result = typeIds.get(type); + + if (result == null) { + result = new TypeIdItem(new CstType(type)); + typeIds.put(type, result); + } + + return result; + } + + /** + * Interns an element into this instance. + * + * @param type {@code non-null;} the type to intern + * @return {@code non-null;} the interned reference + */ + public TypeIdItem intern(CstType type) { + if (type == null) { + throw new NullPointerException("type == null"); + } + + throwIfPrepared(); + + Type typePerSe = type.getClassType(); + TypeIdItem result = typeIds.get(typePerSe); + + if (result == null) { + result = new TypeIdItem(type); + typeIds.put(typePerSe, result); + } + + return result; + } + + /** + * Gets the index of the given type, which must have + * been added to this instance. + * + * @param type {@code non-null;} the type to look up + * @return {@code >= 0;} the reference's index + */ + public int indexOf(Type type) { + if (type == null) { + throw new NullPointerException("type == null"); + } + + throwIfNotPrepared(); + + TypeIdItem item = typeIds.get(type); + + if (item == null) { + throw new IllegalArgumentException("not found: " + type); + } + + return item.getIndex(); + } + + /** + * Gets the index of the given type, which must have + * been added to this instance. + * + * @param type {@code non-null;} the type to look up + * @return {@code >= 0;} the reference's index + */ + public int indexOf(CstType type) { + if (type == null) { + throw new NullPointerException("type == null"); + } + + return indexOf(type.getClassType()); + } + + /** {@inheritDoc} */ + @Override + protected void orderItems() { + int idx = 0; + + for (Object i : items()) { + ((TypeIdItem) i).setIndex(idx); + idx++; + } + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/TypeListItem.java b/dexgen/src/com/android/dexgen/dex/file/TypeListItem.java new file mode 100644 index 0000000..a78c63d --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/TypeListItem.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.type.StdTypeList; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.rop.type.TypeList; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; + +/** + * Representation of a list of class references. + */ +public final class TypeListItem extends OffsettedItem { + /** alignment requirement */ + private static final int ALIGNMENT = 4; + + /** element size in bytes */ + private static final int ELEMENT_SIZE = 2; + + /** header size in bytes */ + private static final int HEADER_SIZE = 4; + + /** {@code non-null;} the actual list */ + private final TypeList list; + + /** + * Constructs an instance. + * + * @param list {@code non-null;} the actual list + */ + public TypeListItem(TypeList list) { + super(ALIGNMENT, (list.size() * ELEMENT_SIZE) + HEADER_SIZE); + + this.list = list; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return StdTypeList.hashContents(list); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return ItemType.TYPE_TYPE_LIST; + } + + /** {@inheritDoc} */ + public void addContents(DexFile file) { + TypeIdsSection typeIds = file.getTypeIds(); + int sz = list.size(); + + for (int i = 0; i < sz; i++) { + typeIds.intern(list.getType(i)); + } + } + + /** {@inheritDoc} */ + @Override + public String toHuman() { + throw new RuntimeException("unsupported"); + } + + /** + * Gets the underlying list. + * + * @return {@code non-null;} the list + */ + public TypeList getList() { + return list; + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + TypeIdsSection typeIds = file.getTypeIds(); + int sz = list.size(); + + if (out.annotates()) { + out.annotate(0, offsetString() + " type_list"); + out.annotate(HEADER_SIZE, " size: " + Hex.u4(sz)); + for (int i = 0; i < sz; i++) { + Type one = list.getType(i); + int idx = typeIds.indexOf(one); + out.annotate(ELEMENT_SIZE, + " " + Hex.u2(idx) + " // " + one.toHuman()); + } + } + + out.writeInt(sz); + + for (int i = 0; i < sz; i++) { + out.writeShort(typeIds.indexOf(list.getType(i))); + } + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(OffsettedItem other) { + TypeList thisList = this.list; + TypeList otherList = ((TypeListItem) other).list; + + return StdTypeList.compareContents(thisList, otherList); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/UniformItemSection.java b/dexgen/src/com/android/dexgen/dex/file/UniformItemSection.java new file mode 100644 index 0000000..63ba36b --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/UniformItemSection.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.util.AnnotatedOutput; + +import java.util.Collection; + +/** + * A section of a {@code .dex} file which consists of a sequence of + * {@link Item} objects. Each of the items must have the same size in + * the output. + */ +public abstract class UniformItemSection extends Section { + /** + * Constructs an instance. The file offset is initially unknown. + * + * @param name {@code null-ok;} the name of this instance, for annotation + * purposes + * @param file {@code non-null;} file that this instance is part of + * @param alignment {@code > 0;} alignment requirement for the final output; + * must be a power of 2 + */ + public UniformItemSection(String name, DexFile file, int alignment) { + super(name, file, alignment); + } + + /** {@inheritDoc} */ + @Override + public final int writeSize() { + Collection<? extends Item> items = items(); + int sz = items.size(); + + if (sz == 0) { + return 0; + } + + // Since each item has to be the same size, we can pick any. + return sz * items.iterator().next().writeSize(); + } + + /** + * Gets the item corresponding to the given {@link Constant}. This + * will throw an exception if the constant is not found, including + * if this instance isn't the sort that maps constants to {@link + * IndexedItem} instances. + * + * @param cst {@code non-null;} constant to look for + * @return {@code non-null;} the corresponding item found in this instance + */ + public abstract IndexedItem get(Constant cst); + + /** {@inheritDoc} */ + @Override + protected final void prepare0() { + DexFile file = getFile(); + + orderItems(); + + for (Item one : items()) { + one.addContents(file); + } + } + + /** {@inheritDoc} */ + @Override + protected final void writeTo0(AnnotatedOutput out) { + DexFile file = getFile(); + int alignment = getAlignment(); + + for (Item one : items()) { + one.writeTo(file, out); + out.alignTo(alignment); + } + } + + /** {@inheritDoc} */ + @Override + public final int getAbsoluteItemOffset(Item item) { + /* + * Since all items must be the same size, we can use the size + * of the one we're given to calculate its offset. + */ + IndexedItem ii = (IndexedItem) item; + int relativeOffset = ii.getIndex() * ii.writeSize(); + + return getAbsoluteOffset(relativeOffset); + } + + /** + * Alters or picks the order for items in this instance if desired, + * so that subsequent calls to {@link #items} will yield a + * so-ordered collection. If the items in this instance are indexed, + * then this method should also assign indices. + */ + protected abstract void orderItems(); +} diff --git a/dexgen/src/com/android/dexgen/dex/file/UniformListItem.java b/dexgen/src/com/android/dexgen/dex/file/UniformListItem.java new file mode 100644 index 0000000..88a120d --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/UniformListItem.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; + +import java.util.HashMap; +import java.util.List; + +/** + * Class that represents a contiguous list of uniform items. Each + * item in the list, in particular, must have the same write size and + * alignment. + * + * <p>This class inherits its alignment from its items, bumped up to + * {@code 4} if the items have a looser alignment requirement. If + * it is more than {@code 4}, then there will be a gap after the + * output list size (which is four bytes) and before the first item.</p> + * + * @param <T> type of element contained in an instance + */ +public final class UniformListItem<T extends OffsettedItem> + extends OffsettedItem { + /** the size of the list header */ + private static final int HEADER_SIZE = 4; + + /** {@code non-null;} the item type */ + private final ItemType itemType; + + /** {@code non-null;} the contents */ + private final List<T> items; + + /** + * Constructs an instance. It is illegal to modify the given list once + * it is used to construct an instance of this class. + * + * @param itemType {@code non-null;} the type of the item + * @param items {@code non-null and non-empty;} list of items to represent + */ + public UniformListItem(ItemType itemType, List<T> items) { + super(getAlignment(items), writeSize(items)); + + if (itemType == null) { + throw new NullPointerException("itemType == null"); + } + + this.items = items; + this.itemType = itemType; + } + + /** + * Helper for {@link #UniformListItem}, which returns the alignment + * requirement implied by the given list. See the header comment for + * more details. + * + * @param items {@code non-null;} list of items being represented + * @return {@code >= 4;} the alignment requirement + */ + private static int getAlignment(List<? extends OffsettedItem> items) { + try { + // Since they all must have the same alignment, any one will do. + return Math.max(HEADER_SIZE, items.get(0).getAlignment()); + } catch (IndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("items.size() == 0"); + } catch (NullPointerException ex) { + // Translate the exception. + throw new NullPointerException("items == null"); + } + } + + /** + * Calculates the write size for the given list. + * + * @param items {@code non-null;} the list in question + * @return {@code >= 0;} the write size + */ + private static int writeSize(List<? extends OffsettedItem> items) { + /* + * This class assumes all included items are the same size, + * an assumption which is verified in place0(). + */ + OffsettedItem first = items.get(0); + return (items.size() * first.writeSize()) + getAlignment(items); + } + + /** {@inheritDoc} */ + @Override + public ItemType itemType() { + return itemType; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(100); + + sb.append(getClass().getName()); + sb.append(items); + + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public void addContents(DexFile file) { + for (OffsettedItem i : items) { + i.addContents(file); + } + } + + /** {@inheritDoc} */ + @Override + public final String toHuman() { + StringBuffer sb = new StringBuffer(100); + boolean first = true; + + sb.append("{"); + + for (OffsettedItem i : items) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(i.toHuman()); + } + + sb.append("}"); + return sb.toString(); + } + + /** + * Gets the underlying list of items. + * + * @return {@code non-null;} the list + */ + public final List<T> getItems() { + return items; + } + + /** {@inheritDoc} */ + @Override + protected void place0(Section addedTo, int offset) { + offset += headerSize(); + + boolean first = true; + int theSize = -1; + int theAlignment = -1; + + for (OffsettedItem i : items) { + int size = i.writeSize(); + if (first) { + theSize = size; + theAlignment = i.getAlignment(); + first = false; + } else { + if (size != theSize) { + throw new UnsupportedOperationException( + "item size mismatch"); + } + if (i.getAlignment() != theAlignment) { + throw new UnsupportedOperationException( + "item alignment mismatch"); + } + } + + offset = i.place(addedTo, offset) + size; + } + } + + /** {@inheritDoc} */ + @Override + protected void writeTo0(DexFile file, AnnotatedOutput out) { + int size = items.size(); + + if (out.annotates()) { + out.annotate(0, offsetString() + " " + typeName()); + out.annotate(4, " size: " + Hex.u4(size)); + } + + out.writeInt(size); + + for (OffsettedItem i : items) { + i.writeTo(file, out); + } + } + + /** + * Get the size of the header of this list. + * + * @return {@code >= 0;} the header size + */ + private int headerSize() { + /* + * Because of how this instance was set up, this is the same + * as the alignment. + */ + return getAlignment(); + } +} diff --git a/dexgen/src/com/android/dexgen/dex/file/ValueEncoder.java b/dexgen/src/com/android/dexgen/dex/file/ValueEncoder.java new file mode 100644 index 0000000..7f30779 --- /dev/null +++ b/dexgen/src/com/android/dexgen/dex/file/ValueEncoder.java @@ -0,0 +1,529 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.dex.file; + +import com.android.dexgen.rop.annotation.Annotation; +import com.android.dexgen.rop.annotation.NameValuePair; +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstAnnotation; +import com.android.dexgen.rop.cst.CstArray; +import com.android.dexgen.rop.cst.CstBoolean; +import com.android.dexgen.rop.cst.CstByte; +import com.android.dexgen.rop.cst.CstChar; +import com.android.dexgen.rop.cst.CstDouble; +import com.android.dexgen.rop.cst.CstEnumRef; +import com.android.dexgen.rop.cst.CstFieldRef; +import com.android.dexgen.rop.cst.CstFloat; +import com.android.dexgen.rop.cst.CstInteger; +import com.android.dexgen.rop.cst.CstKnownNull; +import com.android.dexgen.rop.cst.CstLiteralBits; +import com.android.dexgen.rop.cst.CstLong; +import com.android.dexgen.rop.cst.CstMethodRef; +import com.android.dexgen.rop.cst.CstShort; +import com.android.dexgen.rop.cst.CstString; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.util.AnnotatedOutput; +import com.android.dexgen.util.Hex; + +import java.util.Collection; + +/** + * Handler for writing out {@code encoded_values} and parts + * thereof. + */ +public final class ValueEncoder { + /** annotation value type constant: {@code byte} */ + private static final int VALUE_BYTE = 0x00; + + /** annotation value type constant: {@code short} */ + private static final int VALUE_SHORT = 0x02; + + /** annotation value type constant: {@code char} */ + private static final int VALUE_CHAR = 0x03; + + /** annotation value type constant: {@code int} */ + private static final int VALUE_INT = 0x04; + + /** annotation value type constant: {@code long} */ + private static final int VALUE_LONG = 0x06; + + /** annotation value type constant: {@code float} */ + private static final int VALUE_FLOAT = 0x10; + + /** annotation value type constant: {@code double} */ + private static final int VALUE_DOUBLE = 0x11; + + /** annotation value type constant: {@code string} */ + private static final int VALUE_STRING = 0x17; + + /** annotation value type constant: {@code type} */ + private static final int VALUE_TYPE = 0x18; + + /** annotation value type constant: {@code field} */ + private static final int VALUE_FIELD = 0x19; + + /** annotation value type constant: {@code method} */ + private static final int VALUE_METHOD = 0x1a; + + /** annotation value type constant: {@code enum} */ + private static final int VALUE_ENUM = 0x1b; + + /** annotation value type constant: {@code array} */ + private static final int VALUE_ARRAY = 0x1c; + + /** annotation value type constant: {@code annotation} */ + private static final int VALUE_ANNOTATION = 0x1d; + + /** annotation value type constant: {@code null} */ + private static final int VALUE_NULL = 0x1e; + + /** annotation value type constant: {@code boolean} */ + private static final int VALUE_BOOLEAN = 0x1f; + + /** {@code non-null;} file being written */ + private final DexFile file; + + /** {@code non-null;} output stream to write to */ + private final AnnotatedOutput out; + + /** + * Construct an instance. + * + * @param file {@code non-null;} file being written + * @param out {@code non-null;} output stream to write to + */ + public ValueEncoder(DexFile file, AnnotatedOutput out) { + if (file == null) { + throw new NullPointerException("file == null"); + } + + if (out == null) { + throw new NullPointerException("out == null"); + } + + this.file = file; + this.out = out; + } + + /** + * Writes out the encoded form of the given constant. + * + * @param cst {@code non-null;} the constant to write + */ + public void writeConstant(Constant cst) { + int type = constantToValueType(cst); + int arg; + + switch (type) { + case VALUE_BYTE: + case VALUE_SHORT: + case VALUE_INT: + case VALUE_LONG: { + long value = ((CstLiteralBits) cst).getLongBits(); + writeSignedIntegralValue(type, value); + break; + } + case VALUE_CHAR: { + long value = ((CstLiteralBits) cst).getLongBits(); + writeUnsignedIntegralValue(type, value); + break; + } + case VALUE_FLOAT: { + // Shift value left 32 so that right-zero-extension works. + long value = ((CstFloat) cst).getLongBits() << 32; + writeRightZeroExtendedValue(type, value); + break; + } + case VALUE_DOUBLE: { + long value = ((CstDouble) cst).getLongBits(); + writeRightZeroExtendedValue(type, value); + break; + } + case VALUE_STRING: { + int index = file.getStringIds().indexOf((CstString) cst); + writeUnsignedIntegralValue(type, (long) index); + break; + } + case VALUE_TYPE: { + int index = file.getTypeIds().indexOf((CstType) cst); + writeUnsignedIntegralValue(type, (long) index); + break; + } + case VALUE_FIELD: { + int index = file.getFieldIds().indexOf((CstFieldRef) cst); + writeUnsignedIntegralValue(type, (long) index); + break; + } + case VALUE_METHOD: { + int index = file.getMethodIds().indexOf((CstMethodRef) cst); + writeUnsignedIntegralValue(type, (long) index); + break; + } + case VALUE_ENUM: { + CstFieldRef fieldRef = ((CstEnumRef) cst).getFieldRef(); + int index = file.getFieldIds().indexOf(fieldRef); + writeUnsignedIntegralValue(type, (long) index); + break; + } + case VALUE_ARRAY: { + out.writeByte(type); + writeArray((CstArray) cst, false); + break; + } + case VALUE_ANNOTATION: { + out.writeByte(type); + writeAnnotation(((CstAnnotation) cst).getAnnotation(), + false); + break; + } + case VALUE_NULL: { + out.writeByte(type); + break; + } + case VALUE_BOOLEAN: { + int value = ((CstBoolean) cst).getIntBits(); + out.writeByte(type | (value << 5)); + break; + } + default: { + throw new RuntimeException("Shouldn't happen"); + } + } + } + + /** + * Gets the value type for the given constant. + * + * @param cst {@code non-null;} the constant + * @return the value type; one of the {@code VALUE_*} constants + * defined by this class + */ + private static int constantToValueType(Constant cst) { + /* + * TODO: Constant should probable have an associated enum, so this + * can be a switch(). + */ + if (cst instanceof CstByte) { + return VALUE_BYTE; + } else if (cst instanceof CstShort) { + return VALUE_SHORT; + } else if (cst instanceof CstChar) { + return VALUE_CHAR; + } else if (cst instanceof CstInteger) { + return VALUE_INT; + } else if (cst instanceof CstLong) { + return VALUE_LONG; + } else if (cst instanceof CstFloat) { + return VALUE_FLOAT; + } else if (cst instanceof CstDouble) { + return VALUE_DOUBLE; + } else if (cst instanceof CstString) { + return VALUE_STRING; + } else if (cst instanceof CstType) { + return VALUE_TYPE; + } else if (cst instanceof CstFieldRef) { + return VALUE_FIELD; + } else if (cst instanceof CstMethodRef) { + return VALUE_METHOD; + } else if (cst instanceof CstEnumRef) { + return VALUE_ENUM; + } else if (cst instanceof CstArray) { + return VALUE_ARRAY; + } else if (cst instanceof CstAnnotation) { + return VALUE_ANNOTATION; + } else if (cst instanceof CstKnownNull) { + return VALUE_NULL; + } else if (cst instanceof CstBoolean) { + return VALUE_BOOLEAN; + } else { + throw new RuntimeException("Shouldn't happen"); + } + } + + /** + * Writes out the encoded form of the given array, that is, as + * an {@code encoded_array} and not including a + * {@code value_type} prefix. If the output stream keeps + * (debugging) annotations and {@code topLevel} is + * {@code true}, then this method will write (debugging) + * annotations. + * + * @param array {@code non-null;} array instance to write + * @param topLevel {@code true} iff the given annotation is the + * top-level annotation or {@code false} if it is a sub-annotation + * of some other annotation + */ + public void writeArray(CstArray array, boolean topLevel) { + boolean annotates = topLevel && out.annotates(); + CstArray.List list = ((CstArray) array).getList(); + int size = list.size(); + + if (annotates) { + out.annotate(" size: " + Hex.u4(size)); + } + + out.writeUnsignedLeb128(size); + + for (int i = 0; i < size; i++) { + Constant cst = list.get(i); + if (annotates) { + out.annotate(" [" + Integer.toHexString(i) + "] " + + constantToHuman(cst)); + } + writeConstant(cst); + } + + if (annotates) { + out.endAnnotation(); + } + } + + /** + * Writes out the encoded form of the given annotation, that is, + * as an {@code encoded_annotation} and not including a + * {@code value_type} prefix. If the output stream keeps + * (debugging) annotations and {@code topLevel} is + * {@code true}, then this method will write (debugging) + * annotations. + * + * @param annotation {@code non-null;} annotation instance to write + * @param topLevel {@code true} iff the given annotation is the + * top-level annotation or {@code false} if it is a sub-annotation + * of some other annotation + */ + public void writeAnnotation(Annotation annotation, boolean topLevel) { + boolean annotates = topLevel && out.annotates(); + StringIdsSection stringIds = file.getStringIds(); + TypeIdsSection typeIds = file.getTypeIds(); + + CstType type = annotation.getType(); + int typeIdx = typeIds.indexOf(type); + + if (annotates) { + out.annotate(" type_idx: " + Hex.u4(typeIdx) + " // " + + type.toHuman()); + } + + out.writeUnsignedLeb128(typeIds.indexOf(annotation.getType())); + + Collection<NameValuePair> pairs = annotation.getNameValuePairs(); + int size = pairs.size(); + + if (annotates) { + out.annotate(" size: " + Hex.u4(size)); + } + + out.writeUnsignedLeb128(size); + + int at = 0; + for (NameValuePair pair : pairs) { + CstUtf8 name = pair.getName(); + int nameIdx = stringIds.indexOf(name); + Constant value = pair.getValue(); + + if (annotates) { + out.annotate(0, " elements[" + at + "]:"); + at++; + out.annotate(" name_idx: " + Hex.u4(nameIdx) + " // " + + name.toHuman()); + } + + out.writeUnsignedLeb128(nameIdx); + + if (annotates) { + out.annotate(" value: " + constantToHuman(value)); + } + + writeConstant(value); + } + + if (annotates) { + out.endAnnotation(); + } + } + + /** + * Gets the colloquial type name and human form of the type of the + * given constant, when used as an encoded value. + * + * @param cst {@code non-null;} the constant + * @return {@code non-null;} its type name and human form + */ + public static String constantToHuman(Constant cst) { + int type = constantToValueType(cst); + + if (type == VALUE_NULL) { + return "null"; + } + + StringBuilder sb = new StringBuilder(); + + sb.append(cst.typeName()); + sb.append(' '); + sb.append(cst.toHuman()); + + return sb.toString(); + } + + /** + * Helper for {@link #writeConstant}, which writes out the value + * for any signed integral type. + * + * @param type the type constant + * @param value {@code long} bits of the value + */ + private void writeSignedIntegralValue(int type, long value) { + /* + * Figure out how many bits are needed to represent the value, + * including a sign bit: The bit count is subtracted from 65 + * and not 64 to account for the sign bit. The xor operation + * has the effect of leaving non-negative values alone and + * unary complementing negative values (so that a leading zero + * count always returns a useful number for our present + * purpose). + */ + int requiredBits = + 65 - Long.numberOfLeadingZeros(value ^ (value >> 63)); + + // Round up the requiredBits to a number of bytes. + int requiredBytes = (requiredBits + 0x07) >> 3; + + /* + * Write the header byte, which includes the type and + * requiredBytes - 1. + */ + out.writeByte(type | ((requiredBytes - 1) << 5)); + + // Write the value, per se. + while (requiredBytes > 0) { + out.writeByte((byte) value); + value >>= 8; + requiredBytes--; + } + } + + /** + * Helper for {@link #writeConstant}, which writes out the value + * for any unsigned integral type. + * + * @param type the type constant + * @param value {@code long} bits of the value + */ + private void writeUnsignedIntegralValue(int type, long value) { + // Figure out how many bits are needed to represent the value. + int requiredBits = 64 - Long.numberOfLeadingZeros(value); + if (requiredBits == 0) { + requiredBits = 1; + } + + // Round up the requiredBits to a number of bytes. + int requiredBytes = (requiredBits + 0x07) >> 3; + + /* + * Write the header byte, which includes the type and + * requiredBytes - 1. + */ + out.writeByte(type | ((requiredBytes - 1) << 5)); + + // Write the value, per se. + while (requiredBytes > 0) { + out.writeByte((byte) value); + value >>= 8; + requiredBytes--; + } + } + + /** + * Helper for {@link #writeConstant}, which writes out a + * right-zero-extended value. + * + * @param type the type constant + * @param value {@code long} bits of the value + */ + private void writeRightZeroExtendedValue(int type, long value) { + // Figure out how many bits are needed to represent the value. + int requiredBits = 64 - Long.numberOfTrailingZeros(value); + if (requiredBits == 0) { + requiredBits = 1; + } + + // Round up the requiredBits to a number of bytes. + int requiredBytes = (requiredBits + 0x07) >> 3; + + // Scootch the first bits to be written down to the low-order bits. + value >>= 64 - (requiredBytes * 8); + + /* + * Write the header byte, which includes the type and + * requiredBytes - 1. + */ + out.writeByte(type | ((requiredBytes - 1) << 5)); + + // Write the value, per se. + while (requiredBytes > 0) { + out.writeByte((byte) value); + value >>= 8; + requiredBytes--; + } + } + + + /** + * Helper for {@code addContents()} methods, which adds + * contents for a particular {@link Annotation}, calling itself + * recursively should it encounter a nested annotation. + * + * @param file {@code non-null;} the file to add to + * @param annotation {@code non-null;} the annotation to add contents for + */ + public static void addContents(DexFile file, Annotation annotation) { + TypeIdsSection typeIds = file.getTypeIds(); + StringIdsSection stringIds = file.getStringIds(); + + typeIds.intern(annotation.getType()); + + for (NameValuePair pair : annotation.getNameValuePairs()) { + stringIds.intern(pair.getName()); + addContents(file, pair.getValue()); + } + } + + /** + * Helper for {@code addContents()} methods, which adds + * contents for a particular constant, calling itself recursively + * should it encounter a {@link CstArray} and calling {@link + * #addContents(DexFile,Annotation)} recursively should it + * encounter a {@link CstAnnotation}. + * + * @param file {@code non-null;} the file to add to + * @param cst {@code non-null;} the constant to add contents for + */ + public static void addContents(DexFile file, Constant cst) { + if (cst instanceof CstAnnotation) { + addContents(file, ((CstAnnotation) cst).getAnnotation()); + } else if (cst instanceof CstArray) { + CstArray.List list = ((CstArray) cst).getList(); + int size = list.size(); + for (int i = 0; i < size; i++) { + addContents(file, list.get(i)); + } + } else { + file.internIfAppropriate(cst); + } + } +} diff --git a/dexgen/src/com/android/dexgen/rop/AttConstantValue.java b/dexgen/src/com/android/dexgen/rop/AttConstantValue.java new file mode 100644 index 0000000..189dd75 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/AttConstantValue.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop; + +import com.android.dexgen.rop.cst.CstDouble; +import com.android.dexgen.rop.cst.CstFloat; +import com.android.dexgen.rop.cst.CstInteger; +import com.android.dexgen.rop.cst.CstLong; +import com.android.dexgen.rop.cst.CstString; +import com.android.dexgen.rop.cst.TypedConstant; + +/** + * Attribute class for standard {@code ConstantValue} attributes. + */ +public final class AttConstantValue extends BaseAttribute { + /** {@code non-null;} attribute name for attributes of this type */ + public static final String ATTRIBUTE_NAME = "ConstantValue"; + + /** {@code non-null;} the constant value */ + private final TypedConstant constantValue; + + /** + * Constructs an instance. + * + * @param constantValue {@code non-null;} the constant value, which must + * be an instance of one of: {@code CstString}, + * {@code CstInteger}, {@code CstLong}, + * {@code CstFloat}, or {@code CstDouble} + */ + public AttConstantValue(TypedConstant constantValue) { + super(ATTRIBUTE_NAME); + + if (!((constantValue instanceof CstString) || + (constantValue instanceof CstInteger) || + (constantValue instanceof CstLong) || + (constantValue instanceof CstFloat) || + (constantValue instanceof CstDouble))) { + if (constantValue == null) { + throw new NullPointerException("constantValue == null"); + } + throw new IllegalArgumentException("bad type for constantValue"); + } + + this.constantValue = constantValue; + } + + /** {@inheritDoc} */ + public int byteLength() { + return 8; + } + + /** + * Gets the constant value of this instance. The returned value + * is an instance of one of: {@code CstString}, + * {@code CstInteger}, {@code CstLong}, + * {@code CstFloat}, or {@code CstDouble}. + * + * @return {@code non-null;} the constant value + */ + public TypedConstant getConstantValue() { + return constantValue; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/Attribute.java b/dexgen/src/com/android/dexgen/rop/Attribute.java new file mode 100644 index 0000000..02f1e14 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/Attribute.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop; + +/** + * Interface representing attributes of class files (directly or indirectly). + */ +public interface Attribute { + /** + * Get the name of the attribute. + * + * @return {@code non-null;} the name + */ + public String getName(); + + /** + * Get the total length of the attribute in bytes, including the + * header. Since the header is always six bytes, the result of + * this method is always at least {@code 6}. + * + * @return {@code >= 6;} the total length, in bytes + */ + public int byteLength(); +} diff --git a/dexgen/src/com/android/dexgen/rop/AttributeList.java b/dexgen/src/com/android/dexgen/rop/AttributeList.java new file mode 100644 index 0000000..205b9b7 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/AttributeList.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop; + +/** + * Interface for lists of attributes. + */ +public interface AttributeList { + /** + * Get whether this instance is mutable. Note that the + * {@code AttributeList} interface itself doesn't provide any means + * of mutation, but that doesn't mean that there isn't a non-interface + * way of mutating an instance. + * + * @return {@code true} iff this instance is somehow mutable + */ + public boolean isMutable(); + + /** + * Get the number of attributes in the list. + * + * @return the size + */ + public int size(); + + /** + * Get the {@code n}th attribute. + * + * @param n {@code n >= 0, n < size();} which attribute + * @return {@code non-null;} the attribute in question + */ + public Attribute get(int n); + + /** + * Get the total length of this list in bytes, when part of a + * class file. The returned value includes the two bytes for the + * {@code attributes_count} length indicator. + * + * @return {@code >= 2;} the total length, in bytes + */ + public int byteLength(); + + /** + * Get the first attribute in the list with the given name, if any. + * + * @param name {@code non-null;} attribute name + * @return {@code null-ok;} first attribute in the list with the given name, + * or {@code null} if there is none + */ + public Attribute findFirst(String name); + + /** + * Get the next attribute in the list after the given one, with the same + * name, if any. + * + * @param attrib {@code non-null;} attribute to start looking after + * @return {@code null-ok;} next attribute after {@code attrib} with the + * same name as {@code attrib} + */ + public Attribute findNext(Attribute attrib); +} diff --git a/dexgen/src/com/android/dexgen/rop/BaseAttribute.java b/dexgen/src/com/android/dexgen/rop/BaseAttribute.java new file mode 100644 index 0000000..7ce88c0 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/BaseAttribute.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop; + + +/** + * Base implementation of {@link Attribute}, which directly stores + * the attribute name but leaves the rest up to subclasses. + */ +public abstract class BaseAttribute implements Attribute { + /** {@code non-null;} attribute name */ + private final String name; + + /** + * Constructs an instance. + * + * @param name {@code non-null;} attribute name + */ + public BaseAttribute(String name) { + if (name == null) { + throw new NullPointerException("name == null"); + } + + this.name = name; + } + + /** {@inheritDoc} */ + public String getName() { + return name; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/ByteBlock.java b/dexgen/src/com/android/dexgen/rop/ByteBlock.java new file mode 100644 index 0000000..bdeeb0b --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/ByteBlock.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop; + +import com.android.dexgen.util.Hex; +import com.android.dexgen.util.IntList; +import com.android.dexgen.util.LabeledItem; + +/** + * Representation of a basic block in a bytecode array. + */ +public final class ByteBlock implements LabeledItem { + /** {@code >= 0;} label for this block */ + private final int label; + + /** {@code >= 0;} bytecode offset (inclusive) of the start of the block */ + private final int start; + + /** {@code > start;} bytecode offset (exclusive) of the end of the block */ + private final int end; + + /** {@code non-null;} list of successors that this block may branch to */ + private final IntList successors; + + /** {@code non-null;} list of exceptions caught and their handler targets */ + private final ByteCatchList catches; + + /** + * Constructs an instance. + * + * @param label {@code >= 0;} target label for this block + * @param start {@code >= 0;} bytecode offset (inclusive) of the start + * of the block + * @param end {@code > start;} bytecode offset (exclusive) of the end + * of the block + * @param successors {@code non-null;} list of successors that this block may + * branch to + * @param catches {@code non-null;} list of exceptions caught and their + * handler targets + */ + public ByteBlock(int label, int start, int end, IntList successors, + ByteCatchList catches) { + if (label < 0) { + throw new IllegalArgumentException("label < 0"); + } + + if (start < 0) { + throw new IllegalArgumentException("start < 0"); + } + + if (end <= start) { + throw new IllegalArgumentException("end <= start"); + } + + if (successors == null) { + throw new NullPointerException("targets == null"); + } + + int sz = successors.size(); + for (int i = 0; i < sz; i++) { + if (successors.get(i) < 0) { + throw new IllegalArgumentException("successors[" + i + + "] == " + + successors.get(i)); + } + } + + if (catches == null) { + throw new NullPointerException("catches == null"); + } + + this.label = label; + this.start = start; + this.end = end; + this.successors = successors; + this.catches = catches; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return '{' + Hex.u2(label) + ": " + Hex.u2(start) + ".." + + Hex.u2(end) + '}'; + } + + /** + * Gets the label of this block. + * + * @return {@code >= 0;} the label + */ + public int getLabel() { + return label; + } + + /** + * Gets the bytecode offset (inclusive) of the start of this block. + * + * @return {@code >= 0;} the start offset + */ + public int getStart() { + return start; + } + + /** + * Gets the bytecode offset (exclusive) of the end of this block. + * + * @return {@code > getStart();} the end offset + */ + public int getEnd() { + return end; + } + + /** + * Gets the list of successors that this block may branch to + * non-exceptionally. + * + * @return {@code non-null;} the successor list + */ + public IntList getSuccessors() { + return successors; + } + + /** + * Gets the list of exceptions caught and their handler targets. + * + * @return {@code non-null;} the catch list + */ + public ByteCatchList getCatches() { + return catches; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/ByteCatchList.java b/dexgen/src/com/android/dexgen/rop/ByteCatchList.java new file mode 100644 index 0000000..d2a4857 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/ByteCatchList.java @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop; + +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.type.StdTypeList; +import com.android.dexgen.rop.type.TypeList; +import com.android.dexgen.util.FixedSizeList; +import com.android.dexgen.util.IntList; + +/** + * List of catch entries, that is, the elements of an "exception table," + * which is part of a standard {@code Code} attribute. + */ +public final class ByteCatchList extends FixedSizeList { + /** {@code non-null;} convenient zero-entry instance */ + public static final ByteCatchList EMPTY = new ByteCatchList(0); + + /** + * Constructs an instance. + * + * @param count the number of elements to be in the table + */ + public ByteCatchList(int count) { + super(count); + } + + /** + * Gets the total length of this structure in bytes, when included in + * a {@code Code} attribute. The returned value includes the + * two bytes for {@code exception_table_length}. + * + * @return {@code >= 2;} the total length, in bytes + */ + public int byteLength() { + return 2 + size() * 8; + } + + /** + * Gets the indicated item. + * + * @param n {@code >= 0;} which item + * @return {@code null-ok;} the indicated item + */ + public Item get(int n) { + return (Item) get0(n); + } + + /** + * Sets the item at the given index. + * + * @param n {@code >= 0, < size();} which entry to set + * @param item {@code non-null;} the item + */ + public void set(int n, Item item) { + if (item == null) { + throw new NullPointerException("item == null"); + } + + set0(n, item); + } + + /** + * Sets the item at the given index. + * + * @param n {@code >= 0, < size();} which entry to set + * @param startPc {@code >= 0;} the start pc (inclusive) of the handler's range + * @param endPc {@code >= startPc;} the end pc (exclusive) of the + * handler's range + * @param handlerPc {@code >= 0;} the pc of the exception handler + * @param exceptionClass {@code null-ok;} the exception class or + * {@code null} to catch all exceptions with this handler + */ + public void set(int n, int startPc, int endPc, int handlerPc, + CstType exceptionClass) { + set0(n, new Item(startPc, endPc, handlerPc, exceptionClass)); + } + + /** + * Gets the list of items active at the given address. The result is + * automatically made immutable. + * + * @param pc which address + * @return {@code non-null;} list of exception handlers active at + * {@code pc} + */ + public ByteCatchList listFor(int pc) { + int sz = size(); + Item[] resultArr = new Item[sz]; + int resultSz = 0; + + for (int i = 0; i < sz; i++) { + Item one = get(i); + if (one.covers(pc) && typeNotFound(one, resultArr, resultSz)) { + resultArr[resultSz] = one; + resultSz++; + } + } + + if (resultSz == 0) { + return EMPTY; + } + + ByteCatchList result = new ByteCatchList(resultSz); + for (int i = 0; i < resultSz; i++) { + result.set(i, resultArr[i]); + } + + result.setImmutable(); + return result; + } + + /** + * Helper method for {@link #listFor}, which tells whether a match + * is <i>not</i> found for the exception type of the given item in + * the given array. A match is considered to be either an exact type + * match or the class {@code Object} which represents a catch-all. + * + * @param item {@code non-null;} item with the exception type to look for + * @param arr {@code non-null;} array to search in + * @param count {@code non-null;} maximum number of elements in the array to check + * @return {@code true} iff the exception type is <i>not</i> found + */ + private static boolean typeNotFound(Item item, Item[] arr, int count) { + CstType type = item.getExceptionClass(); + + for (int i = 0; i < count; i++) { + CstType one = arr[i].getExceptionClass(); + if ((one == type) || (one == CstType.OBJECT)) { + return false; + } + } + + return true; + } + + /** + * Returns a target list corresponding to this instance. The result + * is a list of all the exception handler addresses, with the given + * {@code noException} address appended if appropriate. The + * result is automatically made immutable. + * + * @param noException {@code >= -1;} the no-exception address to append, or + * {@code -1} not to append anything + * @return {@code non-null;} list of exception targets, with + * {@code noException} appended if necessary + */ + public IntList toTargetList(int noException) { + if (noException < -1) { + throw new IllegalArgumentException("noException < -1"); + } + + boolean hasDefault = (noException >= 0); + int sz = size(); + + if (sz == 0) { + if (hasDefault) { + /* + * The list is empty, but there is a no-exception + * address; so, the result is just that address. + */ + return IntList.makeImmutable(noException); + } + /* + * The list is empty and there isn't even a no-exception + * address. + */ + return IntList.EMPTY; + } + + IntList result = new IntList(sz + (hasDefault ? 1 : 0)); + + for (int i = 0; i < sz; i++) { + result.add(get(i).getHandlerPc()); + } + + if (hasDefault) { + result.add(noException); + } + + result.setImmutable(); + return result; + } + + /** + * Returns a rop-style catches list equivalent to this one. + * + * @return {@code non-null;} the converted instance + */ + public TypeList toRopCatchList() { + int sz = size(); + if (sz == 0) { + return StdTypeList.EMPTY; + } + + StdTypeList result = new StdTypeList(sz); + + for (int i = 0; i < sz; i++) { + result.set(i, get(i).getExceptionClass().getClassType()); + } + + result.setImmutable(); + return result; + } + + /** + * Item in an exception handler list. + */ + public static class Item { + /** {@code >= 0;} the start pc (inclusive) of the handler's range */ + private final int startPc; + + /** {@code >= startPc;} the end pc (exclusive) of the handler's range */ + private final int endPc; + + /** {@code >= 0;} the pc of the exception handler */ + private final int handlerPc; + + /** {@code null-ok;} the exception class or {@code null} to catch all + * exceptions with this handler */ + private final CstType exceptionClass; + + /** + * Constructs an instance. + * + * @param startPc {@code >= 0;} the start pc (inclusive) of the + * handler's range + * @param endPc {@code >= startPc;} the end pc (exclusive) of the + * handler's range + * @param handlerPc {@code >= 0;} the pc of the exception handler + * @param exceptionClass {@code null-ok;} the exception class or + * {@code null} to catch all exceptions with this handler + */ + public Item(int startPc, int endPc, int handlerPc, + CstType exceptionClass) { + if (startPc < 0) { + throw new IllegalArgumentException("startPc < 0"); + } + + if (endPc < startPc) { + throw new IllegalArgumentException("endPc < startPc"); + } + + if (handlerPc < 0) { + throw new IllegalArgumentException("handlerPc < 0"); + } + + this.startPc = startPc; + this.endPc = endPc; + this.handlerPc = handlerPc; + this.exceptionClass = exceptionClass; + } + + /** + * Gets the start pc (inclusive) of the handler's range. + * + * @return {@code >= 0;} the start pc (inclusive) of the handler's range. + */ + public int getStartPc() { + return startPc; + } + + /** + * Gets the end pc (exclusive) of the handler's range. + * + * @return {@code >= startPc;} the end pc (exclusive) of the + * handler's range. + */ + public int getEndPc() { + return endPc; + } + + /** + * Gets the pc of the exception handler. + * + * @return {@code >= 0;} the pc of the exception handler + */ + public int getHandlerPc() { + return handlerPc; + } + + /** + * Gets the class of exception handled. + * + * @return {@code non-null;} the exception class; {@link CstType#OBJECT} + * if this entry handles all possible exceptions + */ + public CstType getExceptionClass() { + return (exceptionClass != null) ? + exceptionClass : CstType.OBJECT; + } + + /** + * Returns whether the given address is in the range of this item. + * + * @param pc the address + * @return {@code true} iff this item covers {@code pc} + */ + public boolean covers(int pc) { + return (pc >= startPc) && (pc < endPc); + } + } +} diff --git a/dexgen/src/com/android/dexgen/rop/Field.java b/dexgen/src/com/android/dexgen/rop/Field.java new file mode 100644 index 0000000..3e0364b --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/Field.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop; + +import com.android.dexgen.rop.cst.TypedConstant; + +/** + * Interface representing fields of class files. + */ +public interface Field + extends Member { + /** + * Get the constant value for this field, if any. This only returns + * non-{@code null} for a {@code static final} field which + * includes a {@code ConstantValue} attribute. + * + * @return {@code null-ok;} the constant value, or {@code null} if this + * field isn't a constant + */ + public TypedConstant getConstantValue(); +} diff --git a/dexgen/src/com/android/dexgen/rop/FieldList.java b/dexgen/src/com/android/dexgen/rop/FieldList.java new file mode 100644 index 0000000..ab4f28f --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/FieldList.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop; + +/** + * Interface for lists of fields. + */ +public interface FieldList +{ + /** + * Get whether this instance is mutable. Note that the + * {@code FieldList} interface itself doesn't provide any means + * of mutation, but that doesn't mean that there isn't a non-interface + * way of mutating an instance. + * + * @return {@code true} iff this instance is somehow mutable + */ + public boolean isMutable(); + + /** + * Get the number of fields in the list. + * + * @return the size + */ + public int size(); + + /** + * Get the {@code n}th field. + * + * @param n {@code n >= 0, n < size();} which field + * @return {@code non-null;} the field in question + */ + public Field get(int n); +} diff --git a/dexgen/src/com/android/dexgen/rop/LineNumberList.java b/dexgen/src/com/android/dexgen/rop/LineNumberList.java new file mode 100644 index 0000000..f780066 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/LineNumberList.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop; + +import com.android.dexgen.util.FixedSizeList; + +/** + * List of "line number" entries, which are the contents of + * {@code LineNumberTable} attributes. + */ +public final class LineNumberList extends FixedSizeList { + /** {@code non-null;} zero-size instance */ + public static final LineNumberList EMPTY = new LineNumberList(0); + + /** + * Returns an instance which is the concatenation of the two given + * instances. + * + * @param list1 {@code non-null;} first instance + * @param list2 {@code non-null;} second instance + * @return {@code non-null;} combined instance + */ + public static LineNumberList concat(LineNumberList list1, + LineNumberList list2) { + if (list1 == EMPTY) { + // easy case + return list2; + } + + int sz1 = list1.size(); + int sz2 = list2.size(); + LineNumberList result = new LineNumberList(sz1 + sz2); + + for (int i = 0; i < sz1; i++) { + result.set(i, list1.get(i)); + } + + for (int i = 0; i < sz2; i++) { + result.set(sz1 + i, list2.get(i)); + } + + return result; + } + + /** + * Constructs an instance. + * + * @param count the number of elements to be in the list + */ + public LineNumberList(int count) { + super(count); + } + + /** + * Gets the indicated item. + * + * @param n {@code >= 0;} which item + * @return {@code null-ok;} the indicated item + */ + public Item get(int n) { + return (Item) get0(n); + } + + /** + * Sets the item at the given index. + * + * @param n {@code >= 0, < size();} which element + * @param item {@code non-null;} the item + */ + public void set(int n, Item item) { + if (item == null) { + throw new NullPointerException("item == null"); + } + + set0(n, item); + } + + /** + * Sets the item at the given index. + * + * @param n {@code >= 0, < size();} which element + * @param startPc {@code >= 0;} start pc of this item + * @param lineNumber {@code >= 0;} corresponding line number + */ + public void set(int n, int startPc, int lineNumber) { + set0(n, new Item(startPc, lineNumber)); + } + + /** + * Gets the line number associated with the given address. + * + * @param pc {@code >= 0;} the address to look up + * @return {@code >= -1;} the associated line number, or {@code -1} if + * none is known + */ + public int pcToLine(int pc) { + /* + * Line number entries don't have to appear in any particular + * order, so we have to do a linear search. TODO: If + * this turns out to be a bottleneck, consider sorting the + * list prior to use. + */ + int sz = size(); + int bestPc = -1; + int bestLine = -1; + + for (int i = 0; i < sz; i++) { + Item one = get(i); + int onePc = one.getStartPc(); + if ((onePc <= pc) && (onePc > bestPc)) { + bestPc = onePc; + bestLine = one.getLineNumber(); + if (bestPc == pc) { + // We can't do better than this + break; + } + } + } + + return bestLine; + } + + /** + * Item in a line number table. + */ + public static class Item { + /** {@code >= 0;} start pc of this item */ + private final int startPc; + + /** {@code >= 0;} corresponding line number */ + private final int lineNumber; + + /** + * Constructs an instance. + * + * @param startPc {@code >= 0;} start pc of this item + * @param lineNumber {@code >= 0;} corresponding line number + */ + public Item(int startPc, int lineNumber) { + if (startPc < 0) { + throw new IllegalArgumentException("startPc < 0"); + } + + if (lineNumber < 0) { + throw new IllegalArgumentException("lineNumber < 0"); + } + + this.startPc = startPc; + this.lineNumber = lineNumber; + } + + /** + * Gets the start pc of this item. + * + * @return the start pc + */ + public int getStartPc() { + return startPc; + } + + /** + * Gets the line number of this item. + * + * @return the line number + */ + public int getLineNumber() { + return lineNumber; + } + } +} diff --git a/dexgen/src/com/android/dexgen/rop/Member.java b/dexgen/src/com/android/dexgen/rop/Member.java new file mode 100644 index 0000000..dfc17be --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/Member.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop; + +import com.android.dexgen.rop.cst.CstNat; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.cst.CstUtf8; + +/** + * Interface representing members of class files (that is, fields and methods). + */ +public interface Member { + /** + * Get the defining class. + * + * @return {@code non-null;} the defining class + */ + public CstType getDefiningClass(); + + /** + * Get the field {@code access_flags}. + * + * @return the access flags + */ + public int getAccessFlags(); + + /** + * Get the field {@code name_index} of the member. This is + * just a convenient shorthand for {@code getNat().getName()}. + * + * @return {@code non-null;} the name + */ + public CstUtf8 getName(); + + /** + * Get the field {@code descriptor_index} of the member. This is + * just a convenient shorthand for {@code getNat().getDescriptor()}. + * + * @return {@code non-null;} the descriptor + */ + public CstUtf8 getDescriptor(); + + /** + * Get the name and type associated with this member. This is a + * combination of the fields {@code name_index} and + * {@code descriptor_index} in the original classfile, interpreted + * via the constant pool. + * + * @return {@code non-null;} the name and type + */ + public CstNat getNat(); + + /** + * Get the field {@code attributes} (along with + * {@code attributes_count}). + * + * @return {@code non-null;} the constant pool + */ + public AttributeList getAttributes(); +} diff --git a/dexgen/src/com/android/dexgen/rop/StdAttributeList.java b/dexgen/src/com/android/dexgen/rop/StdAttributeList.java new file mode 100644 index 0000000..bebee21 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/StdAttributeList.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop; + +import com.android.dexgen.util.FixedSizeList; + +/** + * Standard implementation of {@link AttributeList}, which directly stores + * an array of {@link Attribute} objects and can be made immutable. + */ +public final class StdAttributeList extends FixedSizeList + implements AttributeList { + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + */ + public StdAttributeList(int size) { + super(size); + } + + /** {@inheritDoc} */ + public Attribute get(int n) { + return (Attribute) get0(n); + } + + /** {@inheritDoc} */ + public int byteLength() { + int sz = size(); + int result = 2; // u2 attributes_count + + for (int i = 0; i < sz; i++) { + result += get(i).byteLength(); + } + + return result; + } + + /** {@inheritDoc} */ + public Attribute findFirst(String name) { + int sz = size(); + + for (int i = 0; i < sz; i++) { + Attribute att = get(i); + if (att.getName().equals(name)) { + return att; + } + } + + return null; + } + + /** {@inheritDoc} */ + public Attribute findNext(Attribute attrib) { + int sz = size(); + int at; + + outer: { + for (at = 0; at < sz; at++) { + Attribute att = get(at); + if (att == attrib) { + break outer; + } + } + + return null; + } + + String name = attrib.getName(); + + for (at++; at < sz; at++) { + Attribute att = get(at); + if (att.getName().equals(name)) { + return att; + } + } + + return null; + } + + /** + * Sets the attribute at the given index. + * + * @param n {@code >= 0, < size();} which attribute + * @param attribute {@code null-ok;} the attribute object + */ + public void set(int n, Attribute attribute) { + set0(n, attribute); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/StdField.java b/dexgen/src/com/android/dexgen/rop/StdField.java new file mode 100644 index 0000000..9adcc30 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/StdField.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop; + +import com.android.dexgen.rop.cst.CstNat; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.rop.cst.TypedConstant; + +/** + * Standard implementation of {@link Field}, which directly stores + * all the associated data. + */ +public final class StdField extends StdMember implements Field { + /** + * Constructs an instance. + * + * @param definingClass {@code non-null;} the defining class + * @param accessFlags access flags + * @param nat {@code non-null;} member name and type (descriptor) + * @param attributes {@code non-null;} list of associated attributes + */ + public StdField(CstType definingClass, int accessFlags, CstNat nat, + AttributeList attributes) { + super(definingClass, accessFlags, nat, attributes); + } + + /** + * Constructs an instance having Java field as its pattern. + * + * @param field {@code non-null;} pattern for dex field + */ + public StdField(java.lang.reflect.Field field) { + this(CstType.intern(field.getDeclaringClass()), + field.getModifiers(), + new CstNat(new CstUtf8(field.getName()), + CstType.intern(field.getType()).getDescriptor()), + new StdAttributeList(0)); + } + + /** + * Constructs an instance taking field description as user-friendly arguments. + * + * @param declaringClass {@code non-null;} the class field belongs to + * @param type {@code non-null;} type of the field + * @param name {@code non-null;} name of the field + * @param modifiers access flags of the field + */ + public StdField(Class definingClass, Class type, String name, int modifiers) { + this(CstType.intern(definingClass), + modifiers, + new CstNat(new CstUtf8(name), CstType.intern(type).getDescriptor()), + new StdAttributeList(0)); + } + + /** {@inheritDoc} */ + public TypedConstant getConstantValue() { + AttributeList attribs = getAttributes(); + AttConstantValue cval = (AttConstantValue) + attribs.findFirst(AttConstantValue.ATTRIBUTE_NAME); + + if (cval == null) { + return null; + } + + return cval.getConstantValue(); + } +}
\ No newline at end of file diff --git a/dexgen/src/com/android/dexgen/rop/StdFieldList.java b/dexgen/src/com/android/dexgen/rop/StdFieldList.java new file mode 100644 index 0000000..ccb7465 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/StdFieldList.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop; + +import com.android.dexgen.util.FixedSizeList; + +/** + * Standard implementation of {@link FieldList}, which directly stores + * an array of {@link Field} objects and can be made immutable. + */ +public final class StdFieldList extends FixedSizeList implements FieldList { + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + */ + public StdFieldList(int size) { + super(size); + } + + /** {@inheritDoc} */ + public Field get(int n) { + return (Field) get0(n); + } + + /** + * Sets the field at the given index. + * + * @param n {@code >= 0, < size();} which field + * @param field {@code null-ok;} the field object + */ + public void set(int n, Field field) { + set0(n, field); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/StdMember.java b/dexgen/src/com/android/dexgen/rop/StdMember.java new file mode 100644 index 0000000..6c46051 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/StdMember.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop; + +import com.android.dexgen.rop.cst.CstNat; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.cst.CstUtf8; + +/** + * Standard implementation of {@link Member}, which directly stores + * all the associated data. + */ +public abstract class StdMember implements Member { + /** {@code non-null;} the defining class */ + private final CstType definingClass; + + /** access flags */ + private final int accessFlags; + + /** {@code non-null;} member name and type */ + private final CstNat nat; + + /** {@code non-null;} list of associated attributes */ + private final AttributeList attributes; + + /** + * Constructs an instance. + * + * @param definingClass {@code non-null;} the defining class + * @param accessFlags access flags + * @param nat {@code non-null;} member name and type (descriptor) + * @param attributes {@code non-null;} list of associated attributes + */ + public StdMember(CstType definingClass, int accessFlags, CstNat nat, + AttributeList attributes) { + if (definingClass == null) { + throw new NullPointerException("definingClass == null"); + } + + if (nat == null) { + throw new NullPointerException("nat == null"); + } + + if (attributes == null) { + throw new NullPointerException("attributes == null"); + } + + this.definingClass = definingClass; + this.accessFlags = accessFlags; + this.nat = nat; + this.attributes = attributes; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(100); + + sb.append(getClass().getName()); + sb.append('{'); + sb.append(nat.toHuman()); + sb.append('}'); + + return sb.toString(); + } + + /** {@inheritDoc} */ + public final CstType getDefiningClass() { + return definingClass; + } + + /** {@inheritDoc} */ + public final int getAccessFlags() { + return accessFlags; + } + + /** {@inheritDoc} */ + public final CstNat getNat() { + return nat; + } + + /** {@inheritDoc} */ + public final CstUtf8 getName() { + return nat.getName(); + } + + /** {@inheritDoc} */ + public final CstUtf8 getDescriptor() { + return nat.getDescriptor(); + } + + /** {@inheritDoc} */ + public final AttributeList getAttributes() { + return attributes; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/annotation/Annotation.java b/dexgen/src/com/android/dexgen/rop/annotation/Annotation.java new file mode 100644 index 0000000..918d2bc --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/annotation/Annotation.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.annotation; + +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstAnnotation; +import com.android.dexgen.rop.cst.CstFieldRef; +import com.android.dexgen.rop.cst.CstLiteralBits; +import com.android.dexgen.rop.cst.CstMethodRef; +import com.android.dexgen.rop.cst.CstNat; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.rop.cst.TypedConstant; +import com.android.dexgen.util.Hex; +import com.android.dexgen.util.MutabilityControl; +import com.android.dexgen.util.ToHuman; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.TreeMap; + +/** + * An annotation on an element of a class. Annotations have an + * associated type and additionally consist of a set of (name, value) + * pairs, where the names are unique. + */ +public final class Annotation extends MutabilityControl + implements Comparable<Annotation>, ToHuman { + /** {@code non-null;} type of the annotation */ + private final CstType type; + + /** {@code non-null;} the visibility of the annotation */ + private final AnnotationVisibility visibility; + + /** {@code non-null;} map from names to {@link NameValuePair} instances */ + private final TreeMap<CstUtf8, NameValuePair> elements; + + /** + * Construct an instance. It initially contains no elements. + * + * @param type {@code non-null;} type of the annotation + * @param visibility {@code non-null;} the visibility of the annotation + */ + public Annotation(CstType type, AnnotationVisibility visibility) { + if (type == null) { + throw new NullPointerException("type == null"); + } + + if (visibility == null) { + throw new NullPointerException("visibility == null"); + } + + this.type = type; + this.visibility = visibility; + this.elements = new TreeMap<CstUtf8, NameValuePair>(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (! (other instanceof Annotation)) { + return false; + } + + Annotation otherAnnotation = (Annotation) other; + + if (! (type.equals(otherAnnotation.type) + && (visibility == otherAnnotation.visibility))) { + return false; + } + + return elements.equals(otherAnnotation.elements); + } + + /** {@inheritDoc} */ + public int hashCode() { + int hash = type.hashCode(); + hash = (hash * 31) + elements.hashCode(); + hash = (hash * 31) + visibility.hashCode(); + return hash; + } + + /** {@inheritDoc} */ + public int compareTo(Annotation other) { + int result = type.compareTo(other.type); + + if (result != 0) { + return result; + } + + result = visibility.compareTo(other.visibility); + + if (result != 0) { + return result; + } + + Iterator<NameValuePair> thisIter = elements.values().iterator(); + Iterator<NameValuePair> otherIter = other.elements.values().iterator(); + + while (thisIter.hasNext() && otherIter.hasNext()) { + NameValuePair thisOne = thisIter.next(); + NameValuePair otherOne = otherIter.next(); + + result = thisOne.compareTo(otherOne); + if (result != 0) { + return result; + } + } + + if (thisIter.hasNext()) { + return 1; + } else if (otherIter.hasNext()) { + return -1; + } + + return 0; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return toHuman(); + } + + /** {@inheritDoc} */ + public String toHuman() { + StringBuilder sb = new StringBuilder(); + + sb.append(visibility.toHuman()); + sb.append("-annotation "); + sb.append(type.toHuman()); + sb.append(" {"); + + boolean first = true; + for (NameValuePair pair : elements.values()) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(pair.getName().toHuman()); + sb.append(": "); + sb.append(pair.getValue().toHuman()); + } + + sb.append("}"); + return sb.toString(); + } + + /** + * Gets the type of this instance. + * + * @return {@code non-null;} the type + */ + public CstType getType() { + return type; + } + + /** + * Gets the visibility of this instance. + * + * @return {@code non-null;} the visibility + */ + public AnnotationVisibility getVisibility() { + return visibility; + } + + /** + * Put an element into the set of (name, value) pairs for this instance. + * If there is a preexisting element with the same name, it will be + * replaced by this method. + * + * @param pair {@code non-null;} the (name, value) pair to place into this instance + */ + public void put(NameValuePair pair) { + throwIfImmutable(); + + if (pair == null) { + throw new NullPointerException("pair == null"); + } + + elements.put(pair.getName(), pair); + } + + /** + * Add an element to the set of (name, value) pairs for this instance. + * It is an error to call this method if there is a preexisting element + * with the same name. + * + * @param pair {@code non-null;} the (name, value) pair to add to this instance + */ + public void add(NameValuePair pair) { + throwIfImmutable(); + + if (pair == null) { + throw new NullPointerException("pair == null"); + } + + CstUtf8 name = pair.getName(); + + if (elements.get(name) != null) { + throw new IllegalArgumentException("name already added: " + name); + } + + elements.put(name, pair); + } + + /** + * Gets the set of name-value pairs contained in this instance. The + * result is always unmodifiable. + * + * @return {@code non-null;} the set of name-value pairs + */ + public Collection<NameValuePair> getNameValuePairs() { + return Collections.unmodifiableCollection(elements.values()); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/annotation/AnnotationVisibility.java b/dexgen/src/com/android/dexgen/rop/annotation/AnnotationVisibility.java new file mode 100644 index 0000000..5239164 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/annotation/AnnotationVisibility.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.annotation; + +import com.android.dexgen.util.ToHuman; + +/** + * Visibility scope of an annotation. + */ +public enum AnnotationVisibility implements ToHuman { + RUNTIME("runtime"), + BUILD("build"), + SYSTEM("system"), + EMBEDDED("embedded"); + + /** {@code non-null;} the human-oriented string representation */ + private final String human; + + /** + * Constructs an instance. + * + * @param human {@code non-null;} the human-oriented string representation + */ + private AnnotationVisibility(String human) { + this.human = human; + } + + /** {@inheritDoc} */ + public String toHuman() { + return human; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/annotation/Annotations.java b/dexgen/src/com/android/dexgen/rop/annotation/Annotations.java new file mode 100644 index 0000000..a7eca04 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/annotation/Annotations.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.annotation; + +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.util.MutabilityControl; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.TreeMap; + +/** + * List of {@link Annotation} instances. + */ +public final class Annotations extends MutabilityControl + implements Comparable<Annotations> { + /** {@code non-null;} immutable empty instance */ + public static final Annotations EMPTY = new Annotations(); + + static { + EMPTY.setImmutable(); + } + + /** {@code non-null;} map from types to annotations */ + private final TreeMap<CstType, Annotation> annotations; + + /** + * Constructs an immutable instance which is the combination of the + * two given instances. The two instances must contain disjoint sets + * of types. + * + * @param a1 {@code non-null;} an instance + * @param a2 {@code non-null;} the other instance + * @return {@code non-null;} the combination + * @throws IllegalArgumentException thrown if there is a duplicate type + */ + public static Annotations combine(Annotations a1, Annotations a2) { + Annotations result = new Annotations(); + + result.addAll(a1); + result.addAll(a2); + result.setImmutable(); + + return result; + } + + /** + * Constructs an immutable instance which is the combination of the + * given instance with the given additional annotation. The latter's + * type must not already appear in the former. + * + * @param annotations {@code non-null;} the instance to augment + * @param annotation {@code non-null;} the additional annotation + * @return {@code non-null;} the combination + * @throws IllegalArgumentException thrown if there is a duplicate type + */ + public static Annotations combine(Annotations annotations, + Annotation annotation) { + Annotations result = new Annotations(); + + result.addAll(annotations); + result.add(annotation); + result.setImmutable(); + + return result; + } + + /** + * Constructs an empty instance. + */ + public Annotations() { + annotations = new TreeMap<CstType, Annotation>(); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return annotations.hashCode(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (! (other instanceof Annotations)) { + return false; + } + + Annotations otherAnnotations = (Annotations) other; + + return annotations.equals(otherAnnotations.annotations); + } + + /** {@inheritDoc} */ + public int compareTo(Annotations other) { + Iterator<Annotation> thisIter = annotations.values().iterator(); + Iterator<Annotation> otherIter = other.annotations.values().iterator(); + + while (thisIter.hasNext() && otherIter.hasNext()) { + Annotation thisOne = thisIter.next(); + Annotation otherOne = otherIter.next(); + + int result = thisOne.compareTo(otherOne); + if (result != 0) { + return result; + } + } + + if (thisIter.hasNext()) { + return 1; + } else if (otherIter.hasNext()) { + return -1; + } + + return 0; + } + + /** {@inheritDoc} */ + public String toString() { + StringBuilder sb = new StringBuilder(); + boolean first = true; + + sb.append("annotations{"); + + for (Annotation a : annotations.values()) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(a.toHuman()); + } + + sb.append("}"); + return sb.toString(); + } + + /** + * Gets the number of elements in this instance. + * + * @return {@code >= 0;} the size + */ + public int size() { + return annotations.size(); + } + + /** + * Adds an element to this instance. There must not already be an + * element of the same type. + * + * @param annotation {@code non-null;} the element to add + * @throws IllegalArgumentException thrown if there is a duplicate type + */ + public void add(Annotation annotation) { + throwIfImmutable(); + + if (annotation == null) { + throw new NullPointerException("annotation == null"); + } + + CstType type = annotation.getType(); + + if (annotations.containsKey(type)) { + throw new IllegalArgumentException("duplicate type: " + + type.toHuman()); + } + + annotations.put(type, annotation); + } + + /** + * Adds all of the elements of the given instance to this one. The + * instances must not have any duplicate types. + * + * @param toAdd {@code non-null;} the annotations to add + * @throws IllegalArgumentException thrown if there is a duplicate type + */ + public void addAll(Annotations toAdd) { + throwIfImmutable(); + + if (toAdd == null) { + throw new NullPointerException("toAdd == null"); + } + + for (Annotation a : toAdd.annotations.values()) { + add(a); + } + } + + /** + * Gets the set of annotations contained in this instance. The + * result is always unmodifiable. + * + * @return {@code non-null;} the set of annotations + */ + public Collection<Annotation> getAnnotations() { + return Collections.unmodifiableCollection(annotations.values()); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/annotation/AnnotationsList.java b/dexgen/src/com/android/dexgen/rop/annotation/AnnotationsList.java new file mode 100644 index 0000000..1159932 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/annotation/AnnotationsList.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.annotation; + +import com.android.dexgen.util.FixedSizeList; + +/** + * List of {@link Annotations} instances. + */ +public final class AnnotationsList + extends FixedSizeList { + /** {@code non-null;} immutable empty instance */ + public static final AnnotationsList EMPTY = new AnnotationsList(0); + + /** + * Constructs an immutable instance which is the combination of + * the two given instances. The two instances must each have the + * same number of elements, and each pair of elements must contain + * disjoint sets of types. + * + * @param list1 {@code non-null;} an instance + * @param list2 {@code non-null;} the other instance + * @return {@code non-null;} the combination + */ + public static AnnotationsList combine(AnnotationsList list1, + AnnotationsList list2) { + int size = list1.size(); + + if (size != list2.size()) { + throw new IllegalArgumentException("list1.size() != list2.size()"); + } + + AnnotationsList result = new AnnotationsList(size); + + for (int i = 0; i < size; i++) { + Annotations a1 = list1.get(i); + Annotations a2 = list2.get(i); + result.set(i, Annotations.combine(a1, a2)); + } + + result.setImmutable(); + return result; + } + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + */ + public AnnotationsList(int size) { + super(size); + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public Annotations get(int n) { + return (Annotations) get0(n); + } + + /** + * Sets the element at the given index. The given element must be + * immutable. + * + * @param n {@code >= 0, < size();} which index + * @param a {@code null-ok;} the element to set at {@code n} + */ + public void set(int n, Annotations a) { + a.throwIfMutable(); + set0(n, a); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/annotation/NameValuePair.java b/dexgen/src/com/android/dexgen/rop/annotation/NameValuePair.java new file mode 100644 index 0000000..9f96f14 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/annotation/NameValuePair.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.annotation; + +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstString; +import com.android.dexgen.rop.cst.CstUtf8; + +/** + * A (name, value) pair. These are used as the contents of an annotation. + */ +public final class NameValuePair implements Comparable<NameValuePair> { + /** {@code non-null;} the name */ + private final CstUtf8 name; + + /** {@code non-null;} the value */ + private final Constant value; + + /** + * Construct an instance. + * + * @param name {@code non-null;} the name + * @param value {@code non-null;} the value + */ + public NameValuePair(CstUtf8 name, Constant value) { + if (name == null) { + throw new NullPointerException("name == null"); + } + + if (value == null) { + throw new NullPointerException("value == null"); + } + + // Reject CstUtf8 values. (They should be CstStrings.) + if (value instanceof CstUtf8) { + throw new IllegalArgumentException("bad value: " + value); + } + + this.name = name; + this.value = value; + } + + /** {@inheritDoc} */ + public String toString() { + return name.toHuman() + ":" + value; + } + + /** {@inheritDoc} */ + public int hashCode() { + return name.hashCode() * 31 + value.hashCode(); + } + + /** {@inheritDoc} */ + public boolean equals(Object other) { + if (! (other instanceof NameValuePair)) { + return false; + } + + NameValuePair otherPair = (NameValuePair) other; + + return name.equals(otherPair.name) + && value.equals(otherPair.value); + } + + /** + * {@inheritDoc} + * + * <p>Instances of this class compare in name-major and value-minor + * order.</p> + */ + public int compareTo(NameValuePair other) { + int result = name.compareTo(other.name); + + if (result != 0) { + return result; + } + + return value.compareTo(other.value); + } + + /** + * Gets the name. + * + * @return {@code non-null;} the name + */ + public CstUtf8 getName() { + return name; + } + + /** + * Gets the value. + * + * @return {@code non-null;} the value + */ + public Constant getValue() { + return value; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/AccessFlags.java b/dexgen/src/com/android/dexgen/rop/code/AccessFlags.java new file mode 100644 index 0000000..4a8b435 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/AccessFlags.java @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.util.Hex; + +/** + * Constants used as "access flags" in various places in classes, and + * related utilities. Although, at the rop layer, flags are generally + * ignored, this is the layer of communication, and as such, this + * package is where these definitions belong. The flag definitions are + * identical to Java access flags, but {@code ACC_SUPER} isn't + * used at all in translated code, and {@code ACC_SYNCHRONIZED} + * is only used in a very limited way. + */ +public final class AccessFlags { + /** public member / class */ + public static final int ACC_PUBLIC = 0x0001; + + /** private member */ + public static final int ACC_PRIVATE = 0x0002; + + /** protected member */ + public static final int ACC_PROTECTED = 0x0004; + + /** static member */ + public static final int ACC_STATIC = 0x0008; + + /** final member / class */ + public static final int ACC_FINAL = 0x0010; + + /** + * synchronized method; only valid in dex files for {@code native} + * methods + */ + public static final int ACC_SYNCHRONIZED = 0x0020; + + /** + * class with new-style {@code invokespecial} for superclass + * method access + */ + public static final int ACC_SUPER = 0x0020; + + /** volatile field */ + public static final int ACC_VOLATILE = 0x0040; + + /** bridge method (generated) */ + public static final int ACC_BRIDGE = 0x0040; + + /** transient field */ + public static final int ACC_TRANSIENT = 0x0080; + + /** varargs method */ + public static final int ACC_VARARGS = 0x0080; + + /** native method */ + public static final int ACC_NATIVE = 0x0100; + + /** "class" is in fact an public static final interface */ + public static final int ACC_INTERFACE = 0x0200; + + /** abstract method / class */ + public static final int ACC_ABSTRACT = 0x0400; + + /** + * method with strict floating point ({@code strictfp}) + * behavior + */ + public static final int ACC_STRICT = 0x0800; + + /** synthetic member */ + public static final int ACC_SYNTHETIC = 0x1000; + + /** class is an annotation type */ + public static final int ACC_ANNOTATION = 0x2000; + + /** + * class is an enumerated type; field is an element of an enumerated + * type + */ + public static final int ACC_ENUM = 0x4000; + + /** method is a constructor */ + public static final int ACC_CONSTRUCTOR = 0x10000; + + /** + * method was declared {@code synchronized}; has no effect on + * execution (other than inspecting this flag, per se) + */ + public static final int ACC_DECLARED_SYNCHRONIZED = 0x20000; + + /** flags defined on classes */ + public static final int CLASS_FLAGS = + ACC_PUBLIC | ACC_FINAL | ACC_SUPER | ACC_INTERFACE | ACC_ABSTRACT | + ACC_SYNTHETIC | ACC_ANNOTATION | ACC_ENUM; + + /** flags defined on inner classes */ + public static final int INNER_CLASS_FLAGS = + ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL | + ACC_INTERFACE | ACC_ABSTRACT | ACC_SYNTHETIC | ACC_ANNOTATION | + ACC_ENUM; + + /** flags defined on fields */ + public static final int FIELD_FLAGS = + ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL | + ACC_VOLATILE | ACC_TRANSIENT | ACC_SYNTHETIC | ACC_ENUM; + + /** flags defined on methods */ + public static final int METHOD_FLAGS = + ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL | + ACC_SYNCHRONIZED | ACC_BRIDGE | ACC_VARARGS | ACC_NATIVE | + ACC_ABSTRACT | ACC_STRICT | ACC_SYNTHETIC | ACC_CONSTRUCTOR | + ACC_DECLARED_SYNCHRONIZED; + + /** indicates conversion of class flags */ + private static final int CONV_CLASS = 1; + + /** indicates conversion of field flags */ + private static final int CONV_FIELD = 2; + + /** indicates conversion of method flags */ + private static final int CONV_METHOD = 3; + + /** + * This class is uninstantiable. + */ + private AccessFlags() { + // This space intentionally left blank. + } + + /** + * Returns a human-oriented string representing the given access flags, + * as defined on classes (not fields or methods). + * + * @param flags the flags + * @return {@code non-null;} human-oriented string + */ + public static String classString(int flags) { + return humanHelper(flags, CLASS_FLAGS, CONV_CLASS); + } + + /** + * Returns a human-oriented string representing the given access flags, + * as defined on inner classes. + * + * @param flags the flags + * @return {@code non-null;} human-oriented string + */ + public static String innerClassString(int flags) { + return humanHelper(flags, INNER_CLASS_FLAGS, CONV_CLASS); + } + + /** + * Returns a human-oriented string representing the given access flags, + * as defined on fields (not classes or methods). + * + * @param flags the flags + * @return {@code non-null;} human-oriented string + */ + public static String fieldString(int flags) { + return humanHelper(flags, FIELD_FLAGS, CONV_FIELD); + } + + /** + * Returns a human-oriented string representing the given access flags, + * as defined on methods (not classes or fields). + * + * @param flags the flags + * @return {@code non-null;} human-oriented string + */ + public static String methodString(int flags) { + return humanHelper(flags, METHOD_FLAGS, CONV_METHOD); + } + + /** + * Returns whether the flag {@code ACC_PUBLIC} is on in the given + * flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_PUBLIC} flag + */ + public static boolean isPublic(int flags) { + return (flags & ACC_PUBLIC) != 0; + } + + /** + * Returns whether the flag {@code ACC_PROTECTED} is on in the given + * flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_PROTECTED} flag + */ + public static boolean isProtected(int flags) { + return (flags & ACC_PROTECTED) != 0; + } + + /** + * Returns whether the flag {@code ACC_PRIVATE} is on in the given + * flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_PRIVATE} flag + */ + public static boolean isPrivate(int flags) { + return (flags & ACC_PRIVATE) != 0; + } + + /** + * Returns whether the flag {@code ACC_STATIC} is on in the given + * flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_STATIC} flag + */ + public static boolean isStatic(int flags) { + return (flags & ACC_STATIC) != 0; + } + + /** + * Returns whether the flag {@code ACC_SYNCHRONIZED} is on in + * the given flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_SYNCHRONIZED} flag + */ + public static boolean isSynchronized(int flags) { + return (flags & ACC_SYNCHRONIZED) != 0; + } + + /** + * Returns whether the flag {@code ACC_ABSTRACT} is on in the given + * flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_ABSTRACT} flag + */ + public static boolean isAbstract(int flags) { + return (flags & ACC_ABSTRACT) != 0; + } + + /** + * Returns whether the flag {@code ACC_NATIVE} is on in the given + * flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_NATIVE} flag + */ + public static boolean isNative(int flags) { + return (flags & ACC_NATIVE) != 0; + } + + /** + * Returns whether the flag {@code ACC_ANNOTATION} is on in the given + * flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_ANNOTATION} flag + */ + public static boolean isAnnotation(int flags) { + return (flags & ACC_ANNOTATION) != 0; + } + + /** + * Returns whether the flag {@code ACC_DECLARED_SYNCHRONIZED} is + * on in the given flags. + * + * @param flags the flags to check + * @return the value of the {@code ACC_DECLARED_SYNCHRONIZED} flag + */ + public static boolean isDeclaredSynchronized(int flags) { + return (flags & ACC_DECLARED_SYNCHRONIZED) != 0; + } + + /** + * Helper to return a human-oriented string representing the given + * access flags. + * + * @param flags the defined flags + * @param mask mask for the "defined" bits + * @param what what the flags represent (one of {@code CONV_*}) + * @return {@code non-null;} human-oriented string + */ + private static String humanHelper(int flags, int mask, int what) { + StringBuffer sb = new StringBuffer(80); + int extra = flags & ~mask; + + flags &= mask; + + if ((flags & ACC_PUBLIC) != 0) { + sb.append("|public"); + } + if ((flags & ACC_PRIVATE) != 0) { + sb.append("|private"); + } + if ((flags & ACC_PROTECTED) != 0) { + sb.append("|protected"); + } + if ((flags & ACC_STATIC) != 0) { + sb.append("|static"); + } + if ((flags & ACC_FINAL) != 0) { + sb.append("|final"); + } + if ((flags & ACC_SYNCHRONIZED) != 0) { + if (what == CONV_CLASS) { + sb.append("|super"); + } else { + sb.append("|synchronized"); + } + } + if ((flags & ACC_VOLATILE) != 0) { + if (what == CONV_METHOD) { + sb.append("|bridge"); + } else { + sb.append("|volatile"); + } + } + if ((flags & ACC_TRANSIENT) != 0) { + if (what == CONV_METHOD) { + sb.append("|varargs"); + } else { + sb.append("|transient"); + } + } + if ((flags & ACC_NATIVE) != 0) { + sb.append("|native"); + } + if ((flags & ACC_INTERFACE) != 0) { + sb.append("|interface"); + } + if ((flags & ACC_ABSTRACT) != 0) { + sb.append("|abstract"); + } + if ((flags & ACC_STRICT) != 0) { + sb.append("|strictfp"); + } + if ((flags & ACC_SYNTHETIC) != 0) { + sb.append("|synthetic"); + } + if ((flags & ACC_ANNOTATION) != 0) { + sb.append("|annotation"); + } + if ((flags & ACC_ENUM) != 0) { + sb.append("|enum"); + } + if ((flags & ACC_CONSTRUCTOR) != 0) { + sb.append("|constructor"); + } + if ((flags & ACC_DECLARED_SYNCHRONIZED) != 0) { + sb.append("|declared_synchronized"); + } + + if ((extra != 0) || (sb.length() == 0)) { + sb.append('|'); + sb.append(Hex.u2(extra)); + } + + return sb.substring(1); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/BasicBlock.java b/dexgen/src/com/android/dexgen/rop/code/BasicBlock.java new file mode 100644 index 0000000..0f7a59e --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/BasicBlock.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.rop.type.TypeList; +import com.android.dexgen.util.Hex; +import com.android.dexgen.util.IntList; +import com.android.dexgen.util.LabeledItem; + +/** + * Basic block of register-based instructions. + */ +public final class BasicBlock implements LabeledItem { + /** {@code >= 0;} target label for this block */ + private final int label; + + /** {@code non-null;} list of instructions in this block */ + private final InsnList insns; + + /** + * {@code non-null;} full list of successors that this block may + * branch to + */ + private final IntList successors; + + /** + * {@code >= -1;} the primary / standard-flow / "default" successor, or + * {@code -1} if this block has no successors (that is, it + * exits the function/method) + */ + private final int primarySuccessor; + + /** + * Constructs an instance. The predecessor set is set to {@code null}. + * + * @param label {@code >= 0;} target label for this block + * @param insns {@code non-null;} list of instructions in this block + * @param successors {@code non-null;} full list of successors that this + * block may branch to + * @param primarySuccessor {@code >= -1;} the primary / standard-flow / + * "default" successor, or {@code -1} if this block has no + * successors (that is, it exits the function/method or is an + * unconditional throw) + */ + public BasicBlock(int label, InsnList insns, IntList successors, + int primarySuccessor) { + if (label < 0) { + throw new IllegalArgumentException("label < 0"); + } + + try { + insns.throwIfMutable(); + } catch (NullPointerException ex) { + // Elucidate exception. + throw new NullPointerException("insns == null"); + } + + int sz = insns.size(); + + if (sz == 0) { + throw new IllegalArgumentException("insns.size() == 0"); + } + + for (int i = sz - 2; i >= 0; i--) { + Rop one = insns.get(i).getOpcode(); + if (one.getBranchingness() != Rop.BRANCH_NONE) { + throw new IllegalArgumentException("insns[" + i + "] is a " + + "branch or can throw"); + } + } + + Insn lastInsn = insns.get(sz - 1); + if (lastInsn.getOpcode().getBranchingness() == Rop.BRANCH_NONE) { + throw new IllegalArgumentException("insns does not end with " + + "a branch or throwing " + + "instruction"); + } + + try { + successors.throwIfMutable(); + } catch (NullPointerException ex) { + // Elucidate exception. + throw new NullPointerException("successors == null"); + } + + if (primarySuccessor < -1) { + throw new IllegalArgumentException("primarySuccessor < -1"); + } + + if (primarySuccessor >= 0 && !successors.contains(primarySuccessor)) { + throw new IllegalArgumentException( + "primarySuccessor not in successors"); + } + + this.label = label; + this.insns = insns; + this.successors = successors; + this.primarySuccessor = primarySuccessor; + } + + /** + * {@inheritDoc} + * + * Instances of this class compare by identity. That is, + * {@code x.equals(y)} is only true if {@code x == y}. + */ + @Override + public boolean equals(Object other) { + return (this == other); + } + + /** + * {@inheritDoc} + * + * Return the identity hashcode of this instance. This is proper, + * since instances of this class compare by identity (see {@link #equals}). + */ + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + /** + * Gets the target label of this block. + * + * @return {@code >= 0;} the label + */ + public int getLabel() { + return label; + } + + /** + * Gets the list of instructions inside this block. + * + * @return {@code non-null;} the instruction list + */ + public InsnList getInsns() { + return insns; + } + + /** + * Gets the list of successors that this block may branch to. + * + * @return {@code non-null;} the successors list + */ + public IntList getSuccessors() { + return successors; + } + + /** + * Gets the primary successor of this block. + * + * @return {@code >= -1;} the primary successor, or {@code -1} if this + * block has no successors at all + */ + public int getPrimarySuccessor() { + return primarySuccessor; + } + + /** + * Gets the secondary successor of this block. It is only valid to call + * this method on blocks that have exactly two successors. + * + * @return {@code >= 0;} the secondary successor + */ + public int getSecondarySuccessor() { + if (successors.size() != 2) { + throw new UnsupportedOperationException( + "block doesn't have exactly two successors"); + } + + int succ = successors.get(0); + if (succ == primarySuccessor) { + succ = successors.get(1); + } + + return succ; + } + + /** + * Gets the first instruction of this block. This is just a + * convenient shorthand for {@code getInsns().get(0)}. + * + * @return {@code non-null;} the first instruction + */ + public Insn getFirstInsn() { + return insns.get(0); + } + + /** + * Gets the last instruction of this block. This is just a + * convenient shorthand for {@code getInsns().getLast()}. + * + * @return {@code non-null;} the last instruction + */ + public Insn getLastInsn() { + return insns.getLast(); + } + + /** + * Returns whether this block might throw an exception. This is + * just a convenient shorthand for {@code getLastInsn().canThrow()}. + * + * @return {@code true} iff this block might throw an + * exception + */ + public boolean canThrow() { + return insns.getLast().canThrow(); + } + + /** + * Returns whether this block has any associated exception handlers. + * This is just a shorthand for inspecting the last instruction in + * the block to see if it could throw, and if so, whether it in fact + * has any associated handlers. + * + * @return {@code true} iff this block has any associated + * exception handlers + */ + public boolean hasExceptionHandlers() { + Insn lastInsn = insns.getLast(); + return lastInsn.getCatches().size() != 0; + } + + /** + * Returns the exception handler types associated with this block, + * if any. This is just a shorthand for inspecting the last + * instruction in the block to see if it could throw, and if so, + * grabbing the catch list out of it. If not, this returns an + * empty list (not {@code null}). + * + * @return {@code non-null;} the exception handler types associated with + * this block + */ + public TypeList getExceptionHandlerTypes() { + Insn lastInsn = insns.getLast(); + return lastInsn.getCatches(); + } + + /** + * Returns an instance that is identical to this one, except that + * the registers in each instruction are offset by the given + * amount. + * + * @param delta the amount to offset register numbers by + * @return {@code non-null;} an appropriately-constructed instance + */ + public BasicBlock withRegisterOffset(int delta) { + return new BasicBlock(label, insns.withRegisterOffset(delta), + successors, primarySuccessor); + } + + public String toString() { + return '{' + Hex.u2(label) + '}'; + } + + /** + * BasicBlock visitor interface + */ + public interface Visitor { + /** + * Visits a basic block + * @param b block visited + */ + public void visitBlock (BasicBlock b); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/BasicBlockList.java b/dexgen/src/com/android/dexgen/rop/code/BasicBlockList.java new file mode 100644 index 0000000..f01c588 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/BasicBlockList.java @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.rop.type.StdTypeList; +import com.android.dexgen.rop.type.TypeList; +import com.android.dexgen.util.Hex; +import com.android.dexgen.util.IntList; +import com.android.dexgen.util.LabeledList; + +/** + * List of {@link BasicBlock} instances. + */ +public final class BasicBlockList extends LabeledList { + /** + * {@code >= -1;} the count of registers required by this method or + * {@code -1} if not yet calculated + */ + private int regCount; + + /** + * Constructs an instance. All indices initially contain {@code null}, + * and the first-block label is initially {@code -1}. + * + * @param size the size of the list + */ + public BasicBlockList(int size) { + super(size); + + regCount = -1; + } + + /** + * Constructs a mutable copy for {@code getMutableCopy()}. + * + * @param old block to copy + */ + private BasicBlockList (BasicBlockList old) { + super(old); + regCount = old.regCount; + } + + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public BasicBlock get(int n) { + return (BasicBlock) get0(n); + } + + /** + * Sets the basic block at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param bb {@code null-ok;} the element to set at {@code n} + */ + public void set(int n, BasicBlock bb) { + super.set(n, bb); + + // Reset regCount, since it will need to be recalculated. + regCount = -1; + } + + /** + * Returns how many registers this method requires. This is simply + * the maximum of register-number-plus-category referred to by this + * instance's instructions (indirectly through {@link BasicBlock} + * instances). + * + * @return {@code >= 0;} the register count + */ + public int getRegCount() { + if (regCount == -1) { + RegCountVisitor visitor = new RegCountVisitor(); + forEachInsn(visitor); + regCount = visitor.getRegCount(); + } + + return regCount; + } + + /** + * Gets the total instruction count for this instance. This is the + * sum of the instruction counts of each block. + * + * @return {@code >= 0;} the total instruction count + */ + public int getInstructionCount() { + int sz = size(); + int result = 0; + + for (int i = 0; i < sz; i++) { + BasicBlock one = (BasicBlock) getOrNull0(i); + if (one != null) { + result += one.getInsns().size(); + } + } + + return result; + } + + /** + * Gets the total instruction count for this instance, ignoring + * mark-local instructions which are not actually emitted. + * + * @return {@code >= 0;} the total instruction count + */ + public int getEffectiveInstructionCount() { + int sz = size(); + int result = 0; + + for (int i = 0; i < sz; i++) { + BasicBlock one = (BasicBlock) getOrNull0(i); + if (one != null) { + InsnList insns = one.getInsns(); + int insnsSz = insns.size(); + + for (int j = 0; j < insnsSz; j++) { + Insn insn = insns.get(j); + + if (insn.getOpcode().getOpcode() != RegOps.MARK_LOCAL) { + result++; + } + } + } + } + + return result; + } + + + /** + * Gets the first block in the list with the given label, if any. + * + * @param label {@code >= 0;} the label to look for + * @return {@code non-null;} the so-labelled block + * @throws IllegalArgumentException thrown if the label isn't found + */ + public BasicBlock labelToBlock(int label) { + int idx = indexOfLabel(label); + + if (idx < 0) { + throw new IllegalArgumentException("no such label: " + + Hex.u2(label)); + } + + return get(idx); + } + + /** + * Visits each instruction of each block in the list, in order. + * + * @param visitor {@code non-null;} visitor to use + */ + public void forEachInsn(Insn.Visitor visitor) { + int sz = size(); + + for (int i = 0; i < sz; i++) { + BasicBlock one = get(i); + InsnList insns = one.getInsns(); + insns.forEach(visitor); + } + } + + /** + * Returns an instance that is identical to this one, except that + * the registers in each instruction are offset by the given + * amount. Mutability of the result is inherited from the + * original. + * + * @param delta the amount to offset register numbers by + * @return {@code non-null;} an appropriately-constructed instance + */ + public BasicBlockList withRegisterOffset(int delta) { + int sz = size(); + BasicBlockList result = new BasicBlockList(sz); + + for (int i = 0; i < sz; i++) { + BasicBlock one = (BasicBlock) get0(i); + if (one != null) { + result.set(i, one.withRegisterOffset(delta)); + } + } + + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } + + /** + * Returns a mutable copy of this list. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public BasicBlockList getMutableCopy() { + return new BasicBlockList(this); + } + + /** + * Gets the preferred successor for the given block. If the block + * only has one successor, then that is the preferred successor. + * Otherwise, if the block has a primay successor, then that is + * the preferred successor. If the block has no successors, then + * this returns {@code null}. + * + * @param block {@code non-null;} the block in question + * @return {@code null-ok;} the preferred successor, if any + */ + public BasicBlock preferredSuccessorOf(BasicBlock block) { + int primarySuccessor = block.getPrimarySuccessor(); + IntList successors = block.getSuccessors(); + int succSize = successors.size(); + + switch (succSize) { + case 0: { + return null; + } + case 1: { + return labelToBlock(successors.get(0)); + } + } + + if (primarySuccessor != -1) { + return labelToBlock(primarySuccessor); + } else { + return labelToBlock(successors.get(0)); + } + } + + /** + * Compares the catches of two blocks for equality. This includes + * both the catch types and target labels. + * + * @param block1 {@code non-null;} one block to compare + * @param block2 {@code non-null;} the other block to compare + * @return {@code true} if the two blocks' non-primary successors + * are identical + */ + public boolean catchesEqual(BasicBlock block1, + BasicBlock block2) { + TypeList catches1 = block1.getExceptionHandlerTypes(); + TypeList catches2 = block2.getExceptionHandlerTypes(); + + if (!StdTypeList.equalContents(catches1, catches2)) { + return false; + } + + IntList succ1 = block1.getSuccessors(); + IntList succ2 = block2.getSuccessors(); + int size = succ1.size(); // Both are guaranteed to be the same size. + + int primary1 = block1.getPrimarySuccessor(); + int primary2 = block2.getPrimarySuccessor(); + + if (((primary1 == -1) || (primary2 == -1)) + && (primary1 != primary2)) { + /* + * For the current purpose, both blocks in question must + * either both have a primary or both not have a primary to + * be considered equal, and it turns out here that that's not + * the case. + */ + return false; + } + + for (int i = 0; i < size; i++) { + int label1 = succ1.get(i); + int label2 = succ2.get(i); + + if (label1 == primary1) { + /* + * It should be the case that block2's primary is at the + * same index. If not, we consider the blocks unequal for + * the current purpose. + */ + if (label2 != primary2) { + return false; + } + continue; + } + + if (label1 != label2) { + return false; + } + } + + return true; + } + + /** + * Instruction visitor class for counting registers used. + */ + private static class RegCountVisitor + implements Insn.Visitor { + /** {@code >= 0;} register count in-progress */ + private int regCount; + + /** + * Constructs an instance. + */ + public RegCountVisitor() { + regCount = 0; + } + + /** + * Gets the register count. + * + * @return {@code >= 0;} the count + */ + public int getRegCount() { + return regCount; + } + + /** {@inheritDoc} */ + public void visitPlainInsn(PlainInsn insn) { + visit(insn); + } + + /** {@inheritDoc} */ + public void visitPlainCstInsn(PlainCstInsn insn) { + visit(insn); + } + + /** {@inheritDoc} */ + public void visitSwitchInsn(SwitchInsn insn) { + visit(insn); + } + + /** {@inheritDoc} */ + public void visitThrowingCstInsn(ThrowingCstInsn insn) { + visit(insn); + } + + /** {@inheritDoc} */ + public void visitThrowingInsn(ThrowingInsn insn) { + visit(insn); + } + + /** {@inheritDoc} */ + public void visitFillArrayDataInsn(FillArrayDataInsn insn) { + visit(insn); + } + + /** + * Helper for all the {@code visit*} methods. + * + * @param insn {@code non-null;} instruction being visited + */ + private void visit(Insn insn) { + RegisterSpec result = insn.getResult(); + + if (result != null) { + processReg(result); + } + + RegisterSpecList sources = insn.getSources(); + int sz = sources.size(); + + for (int i = 0; i < sz; i++) { + processReg(sources.get(i)); + } + } + + /** + * Processes the given register spec. + * + * @param spec {@code non-null;} the register spec + */ + private void processReg(RegisterSpec spec) { + int reg = spec.getNextReg(); + + if (reg > regCount) { + regCount = reg; + } + } + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/ConservativeTranslationAdvice.java b/dexgen/src/com/android/dexgen/rop/code/ConservativeTranslationAdvice.java new file mode 100644 index 0000000..080432b --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/ConservativeTranslationAdvice.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +/** + * Implementation of {@link TranslationAdvice} which conservatively answers + * {@code false} to all methods. + */ +public final class ConservativeTranslationAdvice + implements TranslationAdvice { + /** {@code non-null;} standard instance of this class */ + public static final ConservativeTranslationAdvice THE_ONE = + new ConservativeTranslationAdvice(); + + /** + * This class is not publicly instantiable. Use {@link #THE_ONE}. + */ + private ConservativeTranslationAdvice() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + public boolean hasConstantOperation(Rop opcode, + RegisterSpec sourceA, RegisterSpec sourceB) { + return false; + } + + /** {@inheritDoc} */ + public boolean requiresSourcesInOrder(Rop opcode, + RegisterSpecList sources) { + return false; + } + + /** {@inheritDoc} */ + public int getMaxOptimalRegisterCount() { + return Integer.MAX_VALUE; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/CstInsn.java b/dexgen/src/com/android/dexgen/rop/code/CstInsn.java new file mode 100644 index 0000000..dc5ed39 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/CstInsn.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.rop.cst.Constant; + +/** + * Instruction which contains an explicit reference to a constant. + */ +public abstract class CstInsn + extends Insn { + /** {@code non-null;} the constant */ + private final Constant cst; + + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param result {@code null-ok;} spec for the result, if any + * @param sources {@code non-null;} specs for all the sources + * @param cst {@code non-null;} constant + */ + public CstInsn(Rop opcode, SourcePosition position, RegisterSpec result, + RegisterSpecList sources, Constant cst) { + super(opcode, position, result, sources); + + if (cst == null) { + throw new NullPointerException("cst == null"); + } + + this.cst = cst; + } + + /** {@inheritDoc} */ + @Override + public String getInlineString() { + return cst.toHuman(); + } + + /** + * Gets the constant. + * + * @return {@code non-null;} the constant + */ + public Constant getConstant() { + return cst; + } + + /** {@inheritDoc} */ + @Override + public boolean contentEquals(Insn b) { + /* + * The cast (CstInsn)b below should always succeed since + * Insn.contentEquals compares classes of this and b. + */ + return super.contentEquals(b) + && cst.equals(((CstInsn)b).getConstant()); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/DexTranslationAdvice.java b/dexgen/src/com/android/dexgen/rop/code/DexTranslationAdvice.java new file mode 100644 index 0000000..b46182d --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/DexTranslationAdvice.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.rop.cst.CstInteger; +import com.android.dexgen.rop.type.Type; + +/** + * Implementation of {@link TranslationAdvice} which represents what + * the dex format will be able to represent. + */ +public final class DexTranslationAdvice + implements TranslationAdvice { + /** {@code non-null;} standard instance of this class */ + public static final DexTranslationAdvice THE_ONE = + new DexTranslationAdvice(); + + /** debug advice for disabling invoke-range optimization */ + public static final DexTranslationAdvice NO_SOURCES_IN_ORDER = + new DexTranslationAdvice(true); + + /** + * The minimum source width, in register units, for an invoke + * instruction that requires its sources to be in order and contiguous. + */ + private static final int MIN_INVOKE_IN_ORDER = 6; + + /** when true: always returns false for requiresSourcesInOrder */ + private final boolean disableSourcesInOrder; + + /** + * This class is not publicly instantiable. Use {@link #THE_ONE}. + */ + private DexTranslationAdvice() { + disableSourcesInOrder = false; + } + + private DexTranslationAdvice(boolean disableInvokeRange) { + this.disableSourcesInOrder = disableInvokeRange; + } + + /** {@inheritDoc} */ + public boolean hasConstantOperation(Rop opcode, + RegisterSpec sourceA, RegisterSpec sourceB) { + if (sourceA.getType() != Type.INT) { + return false; + } + + if (! (sourceB.getTypeBearer() instanceof CstInteger)) { + return false; + } + + CstInteger cst = (CstInteger) sourceB.getTypeBearer(); + + // TODO handle rsub + switch (opcode.getOpcode()) { + // These have 8 and 16 bit cst representations + case RegOps.REM: + case RegOps.ADD: + case RegOps.MUL: + case RegOps.DIV: + case RegOps.AND: + case RegOps.OR: + case RegOps.XOR: + return cst.fitsIn16Bits(); + // These only have 8 bit cst reps + case RegOps.SHL: + case RegOps.SHR: + case RegOps.USHR: + return cst.fitsIn8Bits(); + default: + return false; + } + } + + /** {@inheritDoc} */ + public boolean requiresSourcesInOrder(Rop opcode, + RegisterSpecList sources) { + + return !disableSourcesInOrder && opcode.isCallLike() + && totalRopWidth(sources) >= MIN_INVOKE_IN_ORDER; + } + + /** + * Calculates the total rop width of the list of SSA registers + * + * @param sources {@code non-null;} list of SSA registers + * @return {@code >= 0;} rop-form width in register units + */ + private int totalRopWidth(RegisterSpecList sources) { + int sz = sources.size(); + int total = 0; + + for (int i = 0; i < sz; i++) { + total += sources.get(i).getCategory(); + } + + return total; + } + + /** {@inheritDoc} */ + public int getMaxOptimalRegisterCount() { + return 16; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/Exceptions.java b/dexgen/src/com/android/dexgen/rop/code/Exceptions.java new file mode 100644 index 0000000..d6584d0 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/Exceptions.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.rop.type.StdTypeList; +import com.android.dexgen.rop.type.Type; + +/** + * Common exception types. + */ +public final class Exceptions { + /** {@code non-null;} the type {@code java.lang.ArithmeticException} */ + public static final Type TYPE_ArithmeticException = + Type.intern("Ljava/lang/ArithmeticException;"); + + /** + * {@code non-null;} the type + * {@code java.lang.ArrayIndexOutOfBoundsException} + */ + public static final Type TYPE_ArrayIndexOutOfBoundsException = + Type.intern("Ljava/lang/ArrayIndexOutOfBoundsException;"); + + /** {@code non-null;} the type {@code java.lang.ArrayStoreException} */ + public static final Type TYPE_ArrayStoreException = + Type.intern("Ljava/lang/ArrayStoreException;"); + + /** {@code non-null;} the type {@code java.lang.ClassCastException} */ + public static final Type TYPE_ClassCastException = + Type.intern("Ljava/lang/ClassCastException;"); + + /** {@code non-null;} the type {@code java.lang.Error} */ + public static final Type TYPE_Error = Type.intern("Ljava/lang/Error;"); + + /** + * {@code non-null;} the type + * {@code java.lang.IllegalMonitorStateException} + */ + public static final Type TYPE_IllegalMonitorStateException = + Type.intern("Ljava/lang/IllegalMonitorStateException;"); + + /** {@code non-null;} the type {@code java.lang.NegativeArraySizeException} */ + public static final Type TYPE_NegativeArraySizeException = + Type.intern("Ljava/lang/NegativeArraySizeException;"); + + /** {@code non-null;} the type {@code java.lang.NullPointerException} */ + public static final Type TYPE_NullPointerException = + Type.intern("Ljava/lang/NullPointerException;"); + + /** {@code non-null;} the list {@code [java.lang.Error]} */ + public static final StdTypeList LIST_Error = StdTypeList.make(TYPE_Error); + + /** + * {@code non-null;} the list {@code[java.lang.Error, + * java.lang.ArithmeticException]} + */ + public static final StdTypeList LIST_Error_ArithmeticException = + StdTypeList.make(TYPE_Error, TYPE_ArithmeticException); + + /** + * {@code non-null;} the list {@code[java.lang.Error, + * java.lang.ClassCastException]} + */ + public static final StdTypeList LIST_Error_ClassCastException = + StdTypeList.make(TYPE_Error, TYPE_ClassCastException); + + /** + * {@code non-null;} the list {@code [java.lang.Error, + * java.lang.NegativeArraySizeException]} + */ + public static final StdTypeList LIST_Error_NegativeArraySizeException = + StdTypeList.make(TYPE_Error, TYPE_NegativeArraySizeException); + + /** + * {@code non-null;} the list {@code [java.lang.Error, + * java.lang.NullPointerException]} + */ + public static final StdTypeList LIST_Error_NullPointerException = + StdTypeList.make(TYPE_Error, TYPE_NullPointerException); + + /** + * {@code non-null;} the list {@code [java.lang.Error, + * java.lang.NullPointerException, + * java.lang.ArrayIndexOutOfBoundsException]} + */ + public static final StdTypeList LIST_Error_Null_ArrayIndexOutOfBounds = + StdTypeList.make(TYPE_Error, + TYPE_NullPointerException, + TYPE_ArrayIndexOutOfBoundsException); + + /** + * {@code non-null;} the list {@code [java.lang.Error, + * java.lang.NullPointerException, + * java.lang.ArrayIndexOutOfBoundsException, + * java.lang.ArrayStoreException]} + */ + public static final StdTypeList LIST_Error_Null_ArrayIndex_ArrayStore = + StdTypeList.make(TYPE_Error, + TYPE_NullPointerException, + TYPE_ArrayIndexOutOfBoundsException, + TYPE_ArrayStoreException); + + /** + * {@code non-null;} the list {@code [java.lang.Error, + * java.lang.NullPointerException, + * java.lang.IllegalMonitorStateException]} + */ + public static final StdTypeList + LIST_Error_Null_IllegalMonitorStateException = + StdTypeList.make(TYPE_Error, + TYPE_NullPointerException, + TYPE_IllegalMonitorStateException); + + /** + * This class is uninstantiable. + */ + private Exceptions() { + // This space intentionally left blank. + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/FillArrayDataInsn.java b/dexgen/src/com/android/dexgen/rop/code/FillArrayDataInsn.java new file mode 100644 index 0000000..3289b58 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/FillArrayDataInsn.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.type.StdTypeList; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.rop.type.TypeList; + +import java.util.ArrayList; + +/** + * Instruction which fills a newly created array with a predefined list of + * constant values. + */ +public final class FillArrayDataInsn + extends Insn { + + /** non-null: initial values to fill the newly created array */ + private final ArrayList<Constant> initValues; + + /** + * non-null: type of the array. Will be used to determine the width of + * elements in the array-data table. + */ + private final Constant arrayType; + + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param sources {@code non-null;} specs for all the sources + * @param initValues {@code non-null;} list of initial values to fill the array + * @param cst {@code non-null;} type of the new array + */ + public FillArrayDataInsn(Rop opcode, SourcePosition position, + RegisterSpecList sources, + ArrayList<Constant> initValues, + Constant cst) { + super(opcode, position, null, sources); + + if (opcode.getBranchingness() != Rop.BRANCH_NONE) { + throw new IllegalArgumentException("bogus branchingness"); + } + + this.initValues = initValues; + this.arrayType = cst; + } + + + /** {@inheritDoc} */ + @Override + public TypeList getCatches() { + return StdTypeList.EMPTY; + } + + /** + * Return the list of init values + * @return {@code non-null;} list of init values + */ + public ArrayList<Constant> getInitValues() { + return initValues; + } + + /** + * Return the type of the newly created array + * @return {@code non-null;} array type + */ + public Constant getConstant() { + return arrayType; + } + + /** {@inheritDoc} */ + @Override + public void accept(Visitor visitor) { + visitor.visitFillArrayDataInsn(this); + } + + /** {@inheritDoc} */ + @Override + public Insn withAddedCatch(Type type) { + throw new UnsupportedOperationException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public Insn withRegisterOffset(int delta) { + return new FillArrayDataInsn(getOpcode(), getPosition(), + getSources().withOffset(delta), + initValues, arrayType); + } + + /** {@inheritDoc} */ + @Override + public Insn withNewRegisters(RegisterSpec result, + RegisterSpecList sources) { + + return new FillArrayDataInsn(getOpcode(), getPosition(), + sources, initValues, arrayType); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/Insn.java b/dexgen/src/com/android/dexgen/rop/code/Insn.java new file mode 100644 index 0000000..4bb10d2 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/Insn.java @@ -0,0 +1,458 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.rop.type.StdTypeList; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.rop.type.TypeList; +import com.android.dexgen.util.ToHuman; + +/** + * A register-based instruction. An instruction is the combination of + * an opcode (which specifies operation and source/result types), a + * list of actual sources and result registers/values, and additional + * information. + */ +public abstract class Insn implements ToHuman { + /** {@code non-null;} opcode */ + private final Rop opcode; + + /** {@code non-null;} source position */ + private final SourcePosition position; + + /** {@code null-ok;} spec for the result of this instruction, if any */ + private final RegisterSpec result; + + /** {@code non-null;} specs for all the sources of this instruction */ + private final RegisterSpecList sources; + + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param result {@code null-ok;} spec for the result, if any + * @param sources {@code non-null;} specs for all the sources + */ + public Insn(Rop opcode, SourcePosition position, RegisterSpec result, + RegisterSpecList sources) { + if (opcode == null) { + throw new NullPointerException("opcode == null"); + } + + if (position == null) { + throw new NullPointerException("position == null"); + } + + if (sources == null) { + throw new NullPointerException("sources == null"); + } + + this.opcode = opcode; + this.position = position; + this.result = result; + this.sources = sources; + } + + /** + * {@inheritDoc} + * + * Instances of this class compare by identity. That is, + * {@code x.equals(y)} is only true if {@code x == y}. + */ + @Override + public final boolean equals(Object other) { + return (this == other); + } + + /** + * {@inheritDoc} + * + * This implementation returns the identity hashcode of this + * instance. This is proper, since instances of this class compare + * by identity (see {@link #equals}). + */ + @Override + public final int hashCode() { + return System.identityHashCode(this); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return toStringWithInline(getInlineString()); + } + + /** + * Gets a human-oriented (and slightly lossy) string for this instance. + * + * @return {@code non-null;} the human string form + */ + public String toHuman() { + return toHumanWithInline(getInlineString()); + } + + /** + * Gets an "inline" string portion for toHuman(), if available. This + * is the portion that appears after the Rop opcode + * + * @return {@code null-ok;} if non-null, the inline text for toHuman() + */ + public String getInlineString() { + return null; + } + + /** + * Gets the opcode. + * + * @return {@code non-null;} the opcode + */ + public final Rop getOpcode() { + return opcode; + } + + /** + * Gets the source position. + * + * @return {@code non-null;} the source position + */ + public final SourcePosition getPosition() { + return position; + } + + /** + * Gets the result spec, if any. A return value of {@code null} + * means this instruction returns nothing. + * + * @return {@code null-ok;} the result spec, if any + */ + public final RegisterSpec getResult() { + return result; + } + + /** + * Gets the spec of a local variable assignment that occurs at this + * instruction, or null if no local variable assignment occurs. This + * may be the result register, or for {@code mark-local} insns + * it may be the source. + * + * @return {@code null-ok;} a named register spec or null + */ + public final RegisterSpec getLocalAssignment() { + RegisterSpec assignment; + if (opcode.getOpcode() == RegOps.MARK_LOCAL) { + assignment = sources.get(0); + } else { + assignment = result; + } + + if (assignment == null) { + return null; + } + + LocalItem localItem = assignment.getLocalItem(); + + if (localItem == null) { + return null; + } + + return assignment; + } + + /** + * Gets the source specs. + * + * @return {@code non-null;} the source specs + */ + public final RegisterSpecList getSources() { + return sources; + } + + /** + * Gets whether this instruction can possibly throw an exception. This + * is just a convenient wrapper for {@code getOpcode().canThrow()}. + * + * @return {@code true} iff this instruction can possibly throw + */ + public final boolean canThrow() { + return opcode.canThrow(); + } + + /** + * Gets the list of possibly-caught exceptions. This returns {@link + * StdTypeList#EMPTY} if this instruction has no handlers, + * which can be <i>either</i> if this instruction can't possibly + * throw or if it merely doesn't handle any of its possible + * exceptions. To determine whether this instruction can throw, + * use {@link #canThrow}. + * + * @return {@code non-null;} the catches list + */ + public abstract TypeList getCatches(); + + /** + * Calls the appropriate method on the given visitor, depending on the + * class of this instance. Subclasses must override this. + * + * @param visitor {@code non-null;} the visitor to call on + */ + public abstract void accept(Visitor visitor); + + /** + * Returns an instance that is just like this one, except that it + * has a catch list with the given item appended to the end. This + * method throws an exception if this instance can't possibly + * throw. To determine whether this instruction can throw, use + * {@link #canThrow}. + * + * @param type {@code non-null;} type to append to the catch list + * @return {@code non-null;} an appropriately-constructed instance + */ + public abstract Insn withAddedCatch(Type type); + + /** + * Returns an instance that is just like this one, except that all + * register references have been offset by the given delta. + * + * @param delta the amount to offset register references by + * @return {@code non-null;} an appropriately-constructed instance + */ + public abstract Insn withRegisterOffset(int delta); + + /** + * Returns an instance that is just like this one, except that, if + * possible, the insn is converted into a version in which the last + * source (if it is a constant) is represented directly rather than + * as a register reference. {@code this} is returned in cases where + * the translation is not possible. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public Insn withLastSourceLiteral() { + return this; + } + + /** + * Returns an exact copy of this Insn + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public Insn copy() { + return withRegisterOffset(0); + } + + + /** + * Compares, handling nulls safely + * + * @param a first object + * @param b second object + * @return true if they're equal or both null. + */ + private static boolean equalsHandleNulls (Object a, Object b) { + return (a == b) || ((a != null) && a.equals(b)); + } + + /** + * Compares Insn contents, since {@code Insn.equals()} is defined + * to be an identity compare. Insn's are {@code contentEquals()} + * if they have the same opcode, registers, source position, and other + * metadata. + * + * @return true in the case described above + */ + public boolean contentEquals(Insn b) { + return opcode == b.getOpcode() + && position.equals(b.getPosition()) + && (getClass() == b.getClass()) + && equalsHandleNulls(result, b.getResult()) + && equalsHandleNulls(sources, b.getSources()) + && StdTypeList.equalContents(getCatches(), b.getCatches()); + } + + /** + * Returns an instance that is just like this one, except + * with new result and source registers. + * + * @param result {@code null-ok;} new result register + * @param sources {@code non-null;} new sources registers + * @return {@code non-null;} an appropriately-constructed instance + */ + public abstract Insn withNewRegisters(RegisterSpec result, + RegisterSpecList sources); + + /** + * Returns the string form of this instance, with the given bit added in + * the standard location for an inline argument. + * + * @param extra {@code null-ok;} the inline argument string + * @return {@code non-null;} the string form + */ + protected final String toStringWithInline(String extra) { + StringBuffer sb = new StringBuffer(80); + + sb.append("Insn{"); + sb.append(position); + sb.append(' '); + sb.append(opcode); + + if (extra != null) { + sb.append(' '); + sb.append(extra); + } + + sb.append(" :: "); + + if (result != null) { + sb.append(result); + sb.append(" <- "); + } + + sb.append(sources); + sb.append('}'); + + return sb.toString(); + } + + /** + * Returns the human string form of this instance, with the given + * bit added in the standard location for an inline argument. + * + * @param extra {@code null-ok;} the inline argument string + * @return {@code non-null;} the human string form + */ + protected final String toHumanWithInline(String extra) { + StringBuffer sb = new StringBuffer(80); + + sb.append(position); + sb.append(": "); + sb.append(opcode.getNickname()); + + if (extra != null) { + sb.append("("); + sb.append(extra); + sb.append(")"); + } + + if (result == null) { + sb.append(" ."); + } else { + sb.append(" "); + sb.append(result.toHuman()); + } + + sb.append(" <-"); + + int sz = sources.size(); + if (sz == 0) { + sb.append(" ."); + } else { + for (int i = 0; i < sz; i++) { + sb.append(" "); + sb.append(sources.get(i).toHuman()); + } + } + + return sb.toString(); + } + + + /** + * Visitor interface for this (outer) class. + */ + public static interface Visitor { + /** + * Visits a {@link PlainInsn}. + * + * @param insn {@code non-null;} the instruction to visit + */ + public void visitPlainInsn(PlainInsn insn); + + /** + * Visits a {@link PlainCstInsn}. + * + * @param insn {@code non-null;} the instruction to visit + */ + public void visitPlainCstInsn(PlainCstInsn insn); + + /** + * Visits a {@link SwitchInsn}. + * + * @param insn {@code non-null;} the instruction to visit + */ + public void visitSwitchInsn(SwitchInsn insn); + + /** + * Visits a {@link ThrowingCstInsn}. + * + * @param insn {@code non-null;} the instruction to visit + */ + public void visitThrowingCstInsn(ThrowingCstInsn insn); + + /** + * Visits a {@link ThrowingInsn}. + * + * @param insn {@code non-null;} the instruction to visit + */ + public void visitThrowingInsn(ThrowingInsn insn); + + /** + * Visits a {@link FillArrayDataInsn}. + * + * @param insn {@code non-null;} the instruction to visit + */ + public void visitFillArrayDataInsn(FillArrayDataInsn insn); + } + + /** + * Base implementation of {@link Visitor}, which has empty method + * bodies for all methods. + */ + public static class BaseVisitor implements Visitor { + /** {@inheritDoc} */ + public void visitPlainInsn(PlainInsn insn) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + public void visitPlainCstInsn(PlainCstInsn insn) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + public void visitSwitchInsn(SwitchInsn insn) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + public void visitThrowingCstInsn(ThrowingCstInsn insn) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + public void visitThrowingInsn(ThrowingInsn insn) { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + public void visitFillArrayDataInsn(FillArrayDataInsn insn) { + // This space intentionally left blank. + } + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/InsnList.java b/dexgen/src/com/android/dexgen/rop/code/InsnList.java new file mode 100644 index 0000000..0046972 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/InsnList.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.util.FixedSizeList; + +/** + * List of {@link Insn} instances. + */ +public final class InsnList + extends FixedSizeList { + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + */ + public InsnList(int size) { + super(size); + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public Insn get(int n) { + return (Insn) get0(n); + } + + /** + * Sets the instruction at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param insn {@code non-null;} the instruction to set at {@code n} + */ + public void set(int n, Insn insn) { + set0(n, insn); + } + + /** + * Gets the last instruction. This is just a convenient shorthand for + * {@code get(size() - 1)}. + * + * @return {@code non-null;} the last instruction + */ + public Insn getLast() { + return get(size() - 1); + } + + /** + * Visits each instruction in the list, in order. + * + * @param visitor {@code non-null;} visitor to use + */ + public void forEach(Insn.Visitor visitor) { + int sz = size(); + + for (int i = 0; i < sz; i++) { + get(i).accept(visitor); + } + } + + /** + * Compares the contents of this {@code InsnList} with another. + * The blocks must have the same number of insns, and each Insn must + * also return true to {@code Insn.contentEquals()}. + * + * @param b to compare + * @return true in the case described above. + */ + public boolean contentEquals(InsnList b) { + if (b == null) return false; + + int sz = size(); + + if (sz != b.size()) return false; + + for (int i = 0; i < sz; i++) { + if (!get(i).contentEquals(b.get(i))) { + return false; + } + } + + return true; + } + + /** + * Returns an instance that is identical to this one, except that + * the registers in each instruction are offset by the given + * amount. Mutability of the result is inherited from the + * original. + * + * @param delta the amount to offset register numbers by + * @return {@code non-null;} an appropriately-constructed instance + */ + public InsnList withRegisterOffset(int delta) { + int sz = size(); + InsnList result = new InsnList(sz); + + for (int i = 0; i < sz; i++) { + Insn one = (Insn) get0(i); + if (one != null) { + result.set0(i, one.withRegisterOffset(delta)); + } + } + + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/LocalItem.java b/dexgen/src/com/android/dexgen/rop/code/LocalItem.java new file mode 100644 index 0000000..78386f1 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/LocalItem.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.rop.cst.CstUtf8; + +/** + * A local variable item: either a name or a signature or both. + */ +public class LocalItem implements Comparable<LocalItem> { + /** {@code null-ok;} local variable name */ + private final CstUtf8 name; + + /** {@code null-ok;} local variable signature */ + private final CstUtf8 signature; + + /** + * Make a new item. If both name and signature are null, null is returned. + * + * TODO: intern these + * + * @param name {@code null-ok;} local variable name + * @param signature {@code null-ok;} local variable signature + * @return {@code non-null;} appropriate instance. + */ + public static LocalItem make(CstUtf8 name, CstUtf8 signature) { + if (name == null && signature == null) { + return null; + } + + return new LocalItem (name, signature); + } + + /** + * Constructs instance. + * + * @param name {@code null-ok;} local variable name + * @param signature {@code null-ok;} local variable signature + */ + private LocalItem(CstUtf8 name, CstUtf8 signature) { + this.name = name; + this.signature = signature; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof LocalItem)) { + return false; + } + + LocalItem local = (LocalItem) other; + + return 0 == compareTo(local); + } + + /** + * Compares two strings like String.compareTo(), excepts treats a null + * as the least-possible string value. + * + * @return negative integer, zero, or positive integer in accordance + * with Comparable.compareTo() + */ + private static int compareHandlesNulls(CstUtf8 a, CstUtf8 b) { + if (a == b) { + return 0; + } else if (a == null) { + return -1; + } else if (b == null) { + return 1; + } else { + return a.compareTo(b); + } + } + + /** {@inheritDoc} */ + public int compareTo(LocalItem local) { + int ret; + + ret = compareHandlesNulls(name, local.name); + + if (ret != 0) { + return ret; + } + + ret = compareHandlesNulls(signature, local.signature); + + return ret; + } + + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return (name == null ? 0 : name.hashCode()) * 31 + + (signature == null ? 0 : signature.hashCode()); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + if (name != null && signature == null) { + return name.toQuoted(); + } else if (name == null && signature == null) { + return ""; + } + + return "[" + (name == null ? "" : name.toQuoted()) + + "|" + (signature == null ? "" : signature.toQuoted()); + } + + /** + * Gets name. + * + * @return {@code null-ok;} name + */ + public CstUtf8 getName() { + return name; + } + + /** + * Gets signature. + * + * @return {@code null-ok;} signature + */ + public CstUtf8 getSignature() { + return signature; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/LocalVariableExtractor.java b/dexgen/src/com/android/dexgen/rop/code/LocalVariableExtractor.java new file mode 100644 index 0000000..14f5f15 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/LocalVariableExtractor.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.util.Bits; +import com.android.dexgen.util.IntList; + +/** + * Code to figure out which local variables are active at which points in + * a method. + */ +public final class LocalVariableExtractor { + /** {@code non-null;} method being extracted from */ + private final RopMethod method; + + /** {@code non-null;} block list for the method */ + private final BasicBlockList blocks; + + /** {@code non-null;} result in-progress */ + private final LocalVariableInfo resultInfo; + + /** {@code non-null;} work set indicating blocks needing to be processed */ + private final int[] workSet; + + /** + * Extracts out all the local variable information from the given method. + * + * @param method {@code non-null;} the method to extract from + * @return {@code non-null;} the extracted information + */ + public static LocalVariableInfo extract(RopMethod method) { + LocalVariableExtractor lve = new LocalVariableExtractor(method); + return lve.doit(); + } + + /** + * Constructs an instance. This method is private. Use {@link #extract}. + * + * @param method {@code non-null;} the method to extract from + */ + private LocalVariableExtractor(RopMethod method) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + BasicBlockList blocks = method.getBlocks(); + int maxLabel = blocks.getMaxLabel(); + + this.method = method; + this.blocks = blocks; + this.resultInfo = new LocalVariableInfo(method); + this.workSet = Bits.makeBitSet(maxLabel); + } + + /** + * Does the extraction. + * + * @return {@code non-null;} the extracted information + */ + private LocalVariableInfo doit() { + for (int label = method.getFirstLabel(); + label >= 0; + label = Bits.findFirst(workSet, 0)) { + Bits.clear(workSet, label); + processBlock(label); + } + + resultInfo.setImmutable(); + return resultInfo; + } + + /** + * Processes a single block. + * + * @param label {@code >= 0;} label of the block to process + */ + private void processBlock(int label) { + RegisterSpecSet primaryState = resultInfo.mutableCopyOfStarts(label); + BasicBlock block = blocks.labelToBlock(label); + InsnList insns = block.getInsns(); + int insnSz = insns.size(); + + /* + * We may have to treat the last instruction specially: If it + * can (but doesn't always) throw, and the exception can be + * caught within the same method, then we need to use the + * state *before* executing it to be what is merged into + * exception targets. + */ + boolean canThrowDuringLastInsn = block.hasExceptionHandlers() && + (insns.getLast().getResult() != null); + int freezeSecondaryStateAt = insnSz - 1; + RegisterSpecSet secondaryState = primaryState; + + /* + * Iterate over the instructions, adding information for each place + * that the active variable set changes. + */ + + for (int i = 0; i < insnSz; i++) { + if (canThrowDuringLastInsn && (i == freezeSecondaryStateAt)) { + // Until this point, primaryState == secondaryState. + primaryState.setImmutable(); + primaryState = primaryState.mutableCopy(); + } + + Insn insn = insns.get(i); + RegisterSpec result; + + result = insn.getLocalAssignment(); + + if (result == null) { + /* + * If an assignment assigns over an existing local, make + * sure to mark the local as going out of scope. + */ + + result = insn.getResult(); + + if (result != null + && primaryState.get(result.getReg()) != null) { + primaryState.remove(primaryState.get(result.getReg())); + } + continue; + } + + result = result.withSimpleType(); + + RegisterSpec already = primaryState.get(result); + /* + * The equals() check ensures we only add new info if + * the instruction causes a change to the set of + * active variables. + */ + if (!result.equals(already)) { + /* + * If this insn represents a local moving from one register + * to another, remove the association between the old register + * and the local. + */ + RegisterSpec previous + = primaryState.localItemToSpec(result.getLocalItem()); + + if (previous != null + && (previous.getReg() != result.getReg())) { + + primaryState.remove(previous); + } + + resultInfo.addAssignment(insn, result); + primaryState.put(result); + } + } + + primaryState.setImmutable(); + + /* + * Merge this state into the start state for each successor, + * and update the work set where required (that is, in cases + * where the start state for a block changes). + */ + + IntList successors = block.getSuccessors(); + int succSz = successors.size(); + int primarySuccessor = block.getPrimarySuccessor(); + + for (int i = 0; i < succSz; i++) { + int succ = successors.get(i); + RegisterSpecSet state = (succ == primarySuccessor) ? + primaryState : secondaryState; + + if (resultInfo.mergeStarts(succ, state)) { + Bits.set(workSet, succ); + } + } + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/LocalVariableInfo.java b/dexgen/src/com/android/dexgen/rop/code/LocalVariableInfo.java new file mode 100644 index 0000000..b126a4c --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/LocalVariableInfo.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.rop.type.TypeBearer; +import com.android.dexgen.util.MutabilityControl; + +import java.util.HashMap; + +/** + * Container for local variable information for a particular {@link + * RopMethod}. + */ +public final class LocalVariableInfo + extends MutabilityControl { + /** {@code >= 0;} the register count for the method */ + private final int regCount; + + /** + * {@code non-null;} {@link RegisterSpecSet} to use when indicating a block + * that has no locals; it is empty and immutable but has an appropriate + * max size for the method + */ + private final RegisterSpecSet emptySet; + + /** + * {@code non-null;} array consisting of register sets representing the + * sets of variables already assigned upon entry to each block, + * where array indices correspond to block labels + */ + private final RegisterSpecSet[] blockStarts; + + /** {@code non-null;} map from instructions to the variable each assigns */ + private final HashMap<Insn, RegisterSpec> insnAssignments; + + /** + * Constructs an instance. + * + * @param method {@code non-null;} the method being represented by this instance + */ + public LocalVariableInfo(RopMethod method) { + if (method == null) { + throw new NullPointerException("method == null"); + } + + BasicBlockList blocks = method.getBlocks(); + int maxLabel = blocks.getMaxLabel(); + + this.regCount = blocks.getRegCount(); + this.emptySet = new RegisterSpecSet(regCount); + this.blockStarts = new RegisterSpecSet[maxLabel]; + this.insnAssignments = + new HashMap<Insn, RegisterSpec>(blocks.getInstructionCount()); + + emptySet.setImmutable(); + } + + /** + * Sets the register set associated with the start of the block with + * the given label. + * + * @param label {@code >= 0;} the block label + * @param specs {@code non-null;} the register set to associate with the block + */ + public void setStarts(int label, RegisterSpecSet specs) { + throwIfImmutable(); + + if (specs == null) { + throw new NullPointerException("specs == null"); + } + + try { + blockStarts[label] = specs; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("bogus label"); + } + } + + /** + * Merges the given register set into the set for the block with the + * given label. If there was not already an associated set, then this + * is the same as calling {@link #setStarts}. Otherwise, this will + * merge the two sets and call {@link #setStarts} on the result of the + * merge. + * + * @param label {@code >= 0;} the block label + * @param specs {@code non-null;} the register set to merge into the start set + * for the block + * @return {@code true} if the merge resulted in an actual change + * to the associated set (including storing one for the first time) or + * {@code false} if there was no change + */ + public boolean mergeStarts(int label, RegisterSpecSet specs) { + RegisterSpecSet start = getStarts0(label); + boolean changed = false; + + if (start == null) { + setStarts(label, specs); + return true; + } + + RegisterSpecSet newStart = start.mutableCopy(); + newStart.intersect(specs, true); + + if (start.equals(newStart)) { + return false; + } + + newStart.setImmutable(); + setStarts(label, newStart); + + return true; + } + + /** + * Gets the register set associated with the start of the block + * with the given label. This returns an empty set with the appropriate + * max size if no set was associated with the block in question. + * + * @param label {@code >= 0;} the block label + * @return {@code non-null;} the associated register set + */ + public RegisterSpecSet getStarts(int label) { + RegisterSpecSet result = getStarts0(label); + + return (result != null) ? result : emptySet; + } + + /** + * Gets the register set associated with the start of the given + * block. This is just convenient shorthand for + * {@code getStarts(block.getLabel())}. + * + * @param block {@code non-null;} the block in question + * @return {@code non-null;} the associated register set + */ + public RegisterSpecSet getStarts(BasicBlock block) { + return getStarts(block.getLabel()); + } + + /** + * Gets a mutable copy of the register set associated with the + * start of the block with the given label. This returns a + * newly-allocated empty {@link RegisterSpecSet} of appropriate + * max size if there is not yet any set associated with the block. + * + * @param label {@code >= 0;} the block label + * @return {@code non-null;} the associated register set + */ + public RegisterSpecSet mutableCopyOfStarts(int label) { + RegisterSpecSet result = getStarts0(label); + + return (result != null) ? + result.mutableCopy() : new RegisterSpecSet(regCount); + } + + /** + * Adds an assignment association for the given instruction and + * register spec. This throws an exception if the instruction + * doesn't actually perform a named variable assignment. + * + * <b>Note:</b> Although the instruction contains its own spec for + * the result, it still needs to be passed in explicitly to this + * method, since the spec that is stored here should always have a + * simple type and the one in the instruction can be an arbitrary + * {@link TypeBearer} (such as a constant value). + * + * @param insn {@code non-null;} the instruction in question + * @param spec {@code non-null;} the associated register spec + */ + public void addAssignment(Insn insn, RegisterSpec spec) { + throwIfImmutable(); + + if (insn == null) { + throw new NullPointerException("insn == null"); + } + + if (spec == null) { + throw new NullPointerException("spec == null"); + } + + insnAssignments.put(insn, spec); + } + + /** + * Gets the named register being assigned by the given instruction, if + * previously stored in this instance. + * + * @param insn {@code non-null;} instruction in question + * @return {@code null-ok;} the named register being assigned, if any + */ + public RegisterSpec getAssignment(Insn insn) { + return insnAssignments.get(insn); + } + + /** + * Gets the number of assignments recorded by this instance. + * + * @return {@code >= 0;} the number of assignments + */ + public int getAssignmentCount() { + return insnAssignments.size(); + } + + public void debugDump() { + for (int label = 0 ; label < blockStarts.length; label++) { + if (blockStarts[label] == null) { + continue; + } + + if (blockStarts[label] == emptySet) { + System.out.printf("%04x: empty set\n", label); + } else { + System.out.printf("%04x: %s\n", label, blockStarts[label]); + } + } + } + + /** + * Helper method, to get the starts for a label, throwing the + * right exception for range problems. + * + * @param label {@code >= 0;} the block label + * @return {@code null-ok;} associated register set or {@code null} if there + * is none + */ + private RegisterSpecSet getStarts0(int label) { + try { + return blockStarts[label]; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("bogus label"); + } + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/PlainCstInsn.java b/dexgen/src/com/android/dexgen/rop/code/PlainCstInsn.java new file mode 100644 index 0000000..5f8f753 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/PlainCstInsn.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.type.StdTypeList; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.rop.type.TypeList; + +/** + * Instruction which contains an explicit reference to a constant + * but which cannot throw an exception. + */ +public final class PlainCstInsn + extends CstInsn { + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param result {@code null-ok;} spec for the result, if any + * @param sources {@code non-null;} specs for all the sources + * @param cst {@code non-null;} the constant + */ + public PlainCstInsn(Rop opcode, SourcePosition position, + RegisterSpec result, RegisterSpecList sources, + Constant cst) { + super(opcode, position, result, sources, cst); + + if (opcode.getBranchingness() != Rop.BRANCH_NONE) { + throw new IllegalArgumentException("bogus branchingness"); + } + } + + /** {@inheritDoc} */ + @Override + public TypeList getCatches() { + return StdTypeList.EMPTY; + } + + /** {@inheritDoc} */ + @Override + public void accept(Visitor visitor) { + visitor.visitPlainCstInsn(this); + } + + /** {@inheritDoc} */ + @Override + public Insn withAddedCatch(Type type) { + throw new UnsupportedOperationException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public Insn withRegisterOffset(int delta) { + return new PlainCstInsn(getOpcode(), getPosition(), + getResult().withOffset(delta), + getSources().withOffset(delta), + getConstant()); + } + + /** {@inheritDoc} */ + @Override + public Insn withNewRegisters(RegisterSpec result, + RegisterSpecList sources) { + + return new PlainCstInsn(getOpcode(), getPosition(), + result, + sources, + getConstant()); + + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/PlainInsn.java b/dexgen/src/com/android/dexgen/rop/code/PlainInsn.java new file mode 100644 index 0000000..c79e7c1 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/PlainInsn.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.type.StdTypeList; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.rop.type.TypeBearer; +import com.android.dexgen.rop.type.TypeList; + +/** + * Plain instruction, which has no embedded data and which cannot possibly + * throw an exception. + */ +public final class PlainInsn + extends Insn { + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param result {@code null-ok;} spec for the result, if any + * @param sources {@code non-null;} specs for all the sources + */ + public PlainInsn(Rop opcode, SourcePosition position, + RegisterSpec result, RegisterSpecList sources) { + super(opcode, position, result, sources); + + switch (opcode.getBranchingness()) { + case Rop.BRANCH_SWITCH: + case Rop.BRANCH_THROW: { + throw new IllegalArgumentException("bogus branchingness"); + } + } + + if (result != null && opcode.getBranchingness() != Rop.BRANCH_NONE) { + // move-result-pseudo is required here + throw new IllegalArgumentException + ("can't mix branchingness with result"); + } + } + + /** + * Constructs a single-source instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param result {@code null-ok;} spec for the result, if any + * @param source {@code non-null;} spec for the source + */ + public PlainInsn(Rop opcode, SourcePosition position, RegisterSpec result, + RegisterSpec source) { + this(opcode, position, result, RegisterSpecList.make(source)); + } + + /** {@inheritDoc} */ + @Override + public TypeList getCatches() { + return StdTypeList.EMPTY; + } + + /** {@inheritDoc} */ + @Override + public void accept(Visitor visitor) { + visitor.visitPlainInsn(this); + } + + /** {@inheritDoc} */ + @Override + public Insn withAddedCatch(Type type) { + throw new UnsupportedOperationException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public Insn withRegisterOffset(int delta) { + return new PlainInsn(getOpcode(), getPosition(), + getResult().withOffset(delta), + getSources().withOffset(delta)); + } + + /** {@inheritDoc} */ + @Override + public Insn withLastSourceLiteral() { + RegisterSpecList sources = getSources(); + int szSources = sources.size(); + + if (szSources == 0) { + return this; + } + + TypeBearer lastType = sources.get(szSources - 1).getTypeBearer(); + + if (!lastType.isConstant()) { + return this; + } + + Constant cst = (Constant) lastType; + + RegisterSpecList newSources = sources.withoutLast(); + + Rop newRop; + try { + newRop = Rops.ropFor(getOpcode().getOpcode(), + getResult(), newSources, (Constant)lastType); + } catch (IllegalArgumentException ex) { + // There's no rop for this case + return this; + } + + return new PlainCstInsn(newRop, getPosition(), + getResult(), newSources, cst); + } + + + /** {@inheritDoc} */ + @Override + public Insn withNewRegisters(RegisterSpec result, + RegisterSpecList sources) { + + return new PlainInsn(getOpcode(), getPosition(), + result, + sources); + + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/RegOps.java b/dexgen/src/com/android/dexgen/rop/code/RegOps.java new file mode 100644 index 0000000..3af8b7d --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/RegOps.java @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.util.Hex; + +/** + * All the register-based opcodes, and related utilities. + * + * <p><b>Note:</b> Opcode descriptions use a rough pseudocode. {@code r} + * is the result register, {@code x} is the first argument, + * {@code y} is the second argument, and {@code z} is the + * third argument. The expression which describes + * the operation uses Java-ish syntax but is preceded by type indicators for + * each of the values. + */ +public final class RegOps { + /** {@code nop()} */ + public static final int NOP = 1; + + /** {@code T: any type; r,x: T :: r = x;} */ + public static final int MOVE = 2; + + /** {@code T: any type; r,param(x): T :: r = param(x)} */ + public static final int MOVE_PARAM = 3; + + /** + * {@code T: Throwable; r: T :: r = caught_exception}. + * <b>Note:</b> This opcode should only ever be used in the + * first instruction of a block, and such blocks must be + * the start of an exception handler. + */ + public static final int MOVE_EXCEPTION = 4; + + /** {@code T: any type; r, literal: T :: r = literal;} */ + public static final int CONST = 5; + + /** {@code goto label} */ + public static final int GOTO = 6; + + /** + * {@code T: int or Object; x,y: T :: if (x == y) goto + * label} + */ + public static final int IF_EQ = 7; + + /** + * {@code T: int or Object; x,y: T :: if (x != y) goto + * label} + */ + public static final int IF_NE = 8; + + /** {@code x,y: int :: if (x < y) goto label} */ + public static final int IF_LT = 9; + + /** {@code x,y: int :: if (x >= y) goto label} */ + public static final int IF_GE = 10; + + /** {@code x,y: int :: if (x <= y) goto label} */ + public static final int IF_LE = 11; + + /** {@code x,y: int :: if (x > y) goto label} */ + public static final int IF_GT = 12; + + /** {@code x: int :: goto table[x]} */ + public static final int SWITCH = 13; + + /** {@code T: any numeric type; r,x,y: T :: r = x + y} */ + public static final int ADD = 14; + + /** {@code T: any numeric type; r,x,y: T :: r = x - y} */ + public static final int SUB = 15; + + /** {@code T: any numeric type; r,x,y: T :: r = x * y} */ + public static final int MUL = 16; + + /** {@code T: any numeric type; r,x,y: T :: r = x / y} */ + public static final int DIV = 17; + + /** + * {@code T: any numeric type; r,x,y: T :: r = x % y} + * (Java-style remainder) + */ + public static final int REM = 18; + + /** {@code T: any numeric type; r,x: T :: r = -x} */ + public static final int NEG = 19; + + /** {@code T: any integral type; r,x,y: T :: r = x & y} */ + public static final int AND = 20; + + /** {@code T: any integral type; r,x,y: T :: r = x | y} */ + public static final int OR = 21; + + /** {@code T: any integral type; r,x,y: T :: r = x ^ y} */ + public static final int XOR = 22; + + /** + * {@code T: any integral type; r,x: T; y: int :: r = x << y} + */ + public static final int SHL = 23; + + /** + * {@code T: any integral type; r,x: T; y: int :: r = x >> y} + * (signed right-shift) + */ + public static final int SHR = 24; + + /** + * {@code T: any integral type; r,x: T; y: int :: r = x >>> y} + * (unsigned right-shift) + */ + public static final int USHR = 25; + + /** {@code T: any integral type; r,x: T :: r = ~x} */ + public static final int NOT = 26; + + /** + * {@code T: any numeric type; r: int; x,y: T :: r = (x == y) ? 0 + * : (x > y) ? 1 : -1} (Java-style "cmpl" where a NaN is + * considered "less than" all other values; also used for integral + * comparisons) + */ + public static final int CMPL = 27; + + /** + * {@code T: any floating point type; r: int; x,y: T :: r = (x == y) ? 0 + * : (x < y) ? -1 : 1} (Java-style "cmpg" where a NaN is + * considered "greater than" all other values) + */ + public static final int CMPG = 28; + + /** + * {@code T: any numeric type; U: any numeric type; r: T; x: U :: + * r = (T) x} (numeric type conversion between the four + * "real" numeric types) + */ + public static final int CONV = 29; + + /** + * {@code r,x: int :: r = (x << 24) >> 24} (Java-style + * convert int to byte) + */ + public static final int TO_BYTE = 30; + + /** + * {@code r,x: int :: r = x & 0xffff} (Java-style convert int to char) + */ + public static final int TO_CHAR = 31; + + /** + * {@code r,x: int :: r = (x << 16) >> 16} (Java-style + * convert int to short) + */ + public static final int TO_SHORT = 32; + + /** {@code T: return type for the method; x: T; return x} */ + public static final int RETURN = 33; + + /** {@code T: any type; r: int; x: T[]; :: r = x.length} */ + public static final int ARRAY_LENGTH = 34; + + /** {@code x: Throwable :: throw(x)} */ + public static final int THROW = 35; + + /** {@code x: Object :: monitorenter(x)} */ + public static final int MONITOR_ENTER = 36; + + /** {@code x: Object :: monitorexit(x)} */ + public static final int MONITOR_EXIT = 37; + + /** {@code T: any type; r: T; x: T[]; y: int :: r = x[y]} */ + public static final int AGET = 38; + + /** {@code T: any type; x: T; y: T[]; z: int :: x[y] = z} */ + public static final int APUT = 39; + + /** + * {@code T: any non-array object type :: r = + * alloc(T)} (allocate heap space for an object) + */ + public static final int NEW_INSTANCE = 40; + + /** {@code T: any array type; r: T; x: int :: r = new T[x]} */ + public static final int NEW_ARRAY = 41; + + /** + * {@code T: any array type; r: T; x: int; v0..vx: T :: r = new T[x] + * {v0, ..., vx}} + */ + public static final int FILLED_NEW_ARRAY = 42; + + /** + * {@code T: any object type; x: Object :: (T) x} (can + * throw {@code ClassCastException}) + */ + public static final int CHECK_CAST = 43; + + /** + * {@code T: any object type; x: Object :: x instanceof T} + */ + public static final int INSTANCE_OF = 44; + + /** + * {@code T: any type; r: T; x: Object; f: instance field spec of + * type T :: r = x.f} + */ + public static final int GET_FIELD = 45; + + /** + * {@code T: any type; r: T; f: static field spec of type T :: r = + * f} + */ + public static final int GET_STATIC = 46; + + /** + * {@code T: any type; x: T; y: Object; f: instance field spec of type + * T :: y.f = x} + */ + public static final int PUT_FIELD = 47; + + /** + * {@code T: any type; f: static field spec of type T; x: T :: f = x} + */ + public static final int PUT_STATIC = 48; + + /** + * {@code Tr, T0, T1...: any types; r: Tr; m: static method spec; + * y0: T0; y1: T1 ... :: r = m(y0, y1, ...)} (call static + * method) + */ + public static final int INVOKE_STATIC = 49; + + /** + * {@code Tr, T0, T1...: any types; r: Tr; x: Object; m: instance method + * spec; y0: T0; y1: T1 ... :: r = x.m(y0, y1, ...)} (call normal + * virtual method) + */ + public static final int INVOKE_VIRTUAL = 50; + + /** + * {@code Tr, T0, T1...: any types; r: Tr; x: Object; m: instance method + * spec; y0: T0; y1: T1 ... :: r = x.m(y0, y1, ...)} (call + * superclass virtual method) + */ + public static final int INVOKE_SUPER = 51; + + /** + * {@code Tr, T0, T1...: any types; r: Tr; x: Object; m: instance method + * spec; y0: T0; y1: T1 ... :: r = x.m(y0, y1, ...)} (call + * direct/special method) + */ + public static final int INVOKE_DIRECT = 52; + + /** + * {@code Tr, T0, T1...: any types; r: Tr; x: Object; m: interface + * (instance) method spec; y0: T0; y1: T1 ... :: r = x.m(y0, y1, + * ...)} (call interface method) + */ + public static final int INVOKE_INTERFACE = 53; + + /** + * {@code T0: any type; name: local variable name :: mark(name,T0)} + * (mark beginning or end of local variable name) + */ + public static final int MARK_LOCAL = 54; + + /** + * {@code T: Any type; r: T :: r = return_type}. + * <b>Note:</b> This opcode should only ever be used in the + * first instruction of a block following an invoke-*. + */ + public static final int MOVE_RESULT = 55; + + /** + * {@code T: Any type; r: T :: r = return_type}. + * <b>Note:</b> This opcode should only ever be used in the + * first instruction of a block following a non-invoke throwing insn + */ + public static final int MOVE_RESULT_PSEUDO = 56; + + /** {@code T: Any primitive type; v0..vx: T :: {v0, ..., vx}} */ + public static final int FILL_ARRAY_DATA = 57; + + /** + * This class is uninstantiable. + */ + private RegOps() { + // This space intentionally left blank. + } + + /** + * Gets the name of the given opcode. + * + * @param opcode {@code >= 0, <= 255;} the opcode + * @return {@code non-null;} its name + */ + public static String opName(int opcode) { + switch (opcode) { + case NOP: return "nop"; + case MOVE: return "move"; + case MOVE_PARAM: return "move-param"; + case MOVE_EXCEPTION: return "move-exception"; + case CONST: return "const"; + case GOTO: return "goto"; + case IF_EQ: return "if-eq"; + case IF_NE: return "if-ne"; + case IF_LT: return "if-lt"; + case IF_GE: return "if-ge"; + case IF_LE: return "if-le"; + case IF_GT: return "if-gt"; + case SWITCH: return "switch"; + case ADD: return "add"; + case SUB: return "sub"; + case MUL: return "mul"; + case DIV: return "div"; + case REM: return "rem"; + case NEG: return "neg"; + case AND: return "and"; + case OR: return "or"; + case XOR: return "xor"; + case SHL: return "shl"; + case SHR: return "shr"; + case USHR: return "ushr"; + case NOT: return "not"; + case CMPL: return "cmpl"; + case CMPG: return "cmpg"; + case CONV: return "conv"; + case TO_BYTE: return "to-byte"; + case TO_CHAR: return "to-char"; + case TO_SHORT: return "to-short"; + case RETURN: return "return"; + case ARRAY_LENGTH: return "array-length"; + case THROW: return "throw"; + case MONITOR_ENTER: return "monitor-enter"; + case MONITOR_EXIT: return "monitor-exit"; + case AGET: return "aget"; + case APUT: return "aput"; + case NEW_INSTANCE: return "new-instance"; + case NEW_ARRAY: return "new-array"; + case FILLED_NEW_ARRAY: return "filled-new-array"; + case CHECK_CAST: return "check-cast"; + case INSTANCE_OF: return "instance-of"; + case GET_FIELD: return "get-field"; + case GET_STATIC: return "get-static"; + case PUT_FIELD: return "put-field"; + case PUT_STATIC: return "put-static"; + case INVOKE_STATIC: return "invoke-static"; + case INVOKE_VIRTUAL: return "invoke-virtual"; + case INVOKE_SUPER: return "invoke-super"; + case INVOKE_DIRECT: return "invoke-direct"; + case INVOKE_INTERFACE: return "invoke-interface"; + case MOVE_RESULT: return "move-result"; + case MOVE_RESULT_PSEUDO: return "move-result-pseudo"; + case FILL_ARRAY_DATA: return "fill-array-data"; + } + + return "unknown-" + Hex.u1(opcode); + } + + /** + * Given an IF_* RegOp, returns the right-to-left flipped version. For + * example, IF_GT becomes IF_LT. + * + * @param opcode An IF_* RegOp + * @return flipped IF Regop + */ + public static int flippedIfOpcode(final int opcode) { + switch (opcode) { + case RegOps.IF_EQ: + case RegOps.IF_NE: + return opcode; + case RegOps.IF_LT: + return RegOps.IF_GT; + case RegOps.IF_GE: + return RegOps.IF_LE; + case RegOps.IF_LE: + return RegOps.IF_GE; + case RegOps.IF_GT: + return RegOps.IF_LT; + default: + throw new RuntimeException("Unrecognized IF regop: " + opcode); + } + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/RegisterSpec.java b/dexgen/src/com/android/dexgen/rop/code/RegisterSpec.java new file mode 100644 index 0000000..30deeca --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/RegisterSpec.java @@ -0,0 +1,650 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.rop.type.TypeBearer; +import com.android.dexgen.util.ToHuman; + +import java.util.HashMap; + +/** + * Combination of a register number and a type, used as the sources and + * destinations of register-based operations. + */ +public final class RegisterSpec + implements TypeBearer, ToHuman, Comparable<RegisterSpec> { + /** {@code non-null;} string to prefix register numbers with */ + public static final String PREFIX = "v"; + + /** {@code non-null;} intern table for instances */ + private static final HashMap<Object, RegisterSpec> theInterns = + new HashMap<Object, RegisterSpec>(1000); + + /** {@code non-null;} common comparison instance used while interning */ + private static final ForComparison theInterningItem = new ForComparison(); + + /** {@code >= 0;} register number */ + private final int reg; + + /** {@code non-null;} type loaded or stored */ + private final TypeBearer type; + + /** {@code null-ok;} local variable info associated with this register, if any */ + private final LocalItem local; + + /** + * Intern the given triple as an instance of this class. + * + * @param reg {@code >= 0;} the register number + * @param type {@code non-null;} the type (or possibly actual value) which + * is loaded from or stored to the indicated register + * @param local {@code null-ok;} the associated local variable, if any + * @return {@code non-null;} an appropriately-constructed instance + */ + private static RegisterSpec intern(int reg, TypeBearer type, + LocalItem local) { + theInterningItem.set(reg, type, local); + RegisterSpec found = theInterns.get(theInterningItem); + + if (found != null) { + return found; + } + + found = theInterningItem.toRegisterSpec(); + theInterns.put(found, found); + return found; + } + + /** + * Returns an instance for the given register number and type, with + * no variable info. This method is allowed to return shared + * instances (but doesn't necessarily do so). + * + * @param reg {@code >= 0;} the register number + * @param type {@code non-null;} the type (or possibly actual value) which + * is loaded from or stored to the indicated register + * @return {@code non-null;} an appropriately-constructed instance + */ + public static RegisterSpec make(int reg, TypeBearer type) { + return intern(reg, type, null); + } + + /** + * Returns an instance for the given register number, type, and + * variable info. This method is allowed to return shared + * instances (but doesn't necessarily do so). + * + * @param reg {@code >= 0;} the register number + * @param type {@code non-null;} the type (or possibly actual value) which + * is loaded from or stored to the indicated register + * @param local {@code non-null;} the associated local variable + * @return {@code non-null;} an appropriately-constructed instance + */ + public static RegisterSpec make(int reg, TypeBearer type, + LocalItem local) { + if (local == null) { + throw new NullPointerException("local == null"); + } + + return intern(reg, type, local); + } + + /** + * Returns an instance for the given register number, type, and + * variable info. This method is allowed to return shared + * instances (but doesn't necessarily do so). + * + * @param reg {@code >= 0;} the register number + * @param type {@code non-null;} the type (or possibly actual value) which + * is loaded from or stored to the indicated register + * @param local {@code null-ok;} the associated variable info or null for + * none + * @return {@code non-null;} an appropriately-constructed instance + */ + public static RegisterSpec makeLocalOptional( + int reg, TypeBearer type, LocalItem local) { + + return intern(reg, type, local); + } + + /** + * Gets the string form for the given register number. + * + * @param reg {@code >= 0;} the register number + * @return {@code non-null;} the string form + */ + public static String regString(int reg) { + return PREFIX + reg; + } + + /** + * Constructs an instance. This constructor is private. Use + * {@link #make}. + * + * @param reg {@code >= 0;} the register number + * @param type {@code non-null;} the type (or possibly actual value) which + * is loaded from or stored to the indicated register + * @param local {@code null-ok;} the associated local variable, if any + */ + private RegisterSpec(int reg, TypeBearer type, LocalItem local) { + if (reg < 0) { + throw new IllegalArgumentException("reg < 0"); + } + + if (type == null) { + throw new NullPointerException("type == null"); + } + + this.reg = reg; + this.type = type; + this.local = local; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof RegisterSpec)) { + if (other instanceof ForComparison) { + ForComparison fc = (ForComparison) other; + return equals(fc.reg, fc.type, fc.local); + } + return false; + } + + RegisterSpec spec = (RegisterSpec) other; + return equals(spec.reg, spec.type, spec.local); + } + + /** + * Like {@code equals}, but only consider the simple types of the + * registers. That is, this compares {@code getType()} on the types + * to ignore whatever arbitrary extra stuff might be carried around + * by an outer {@link TypeBearer}. + * + * @param other {@code null-ok;} spec to compare to + * @return {@code true} iff {@code this} and {@code other} are equal + * in the stated way + */ + public boolean equalsUsingSimpleType(RegisterSpec other) { + if (!matchesVariable(other)) { + return false; + } + + return (reg == other.reg); + } + + /** + * Like {@link #equalsUsingSimpleType} but ignoring the register number. + * This is useful to determine if two instances refer to the "same" + * local variable. + * + * @param other {@code null-ok;} spec to compare to + * @return {@code true} iff {@code this} and {@code other} are equal + * in the stated way + */ + public boolean matchesVariable(RegisterSpec other) { + if (other == null) { + return false; + } + + return type.getType().equals(other.type.getType()) + && ((local == other.local) + || ((local != null) && local.equals(other.local))); + } + + /** + * Helper for {@link #equals} and {@link #ForComparison.equals}, + * which actually does the test. + * + * @param reg value of the instance variable, for another instance + * @param type value of the instance variable, for another instance + * @param local value of the instance variable, for another instance + * @return whether this instance is equal to one with the given + * values + */ + private boolean equals(int reg, TypeBearer type, LocalItem local) { + return (this.reg == reg) + && this.type.equals(type) + && ((this.local == local) + || ((this.local != null) && this.local.equals(local))); + } + + /** + * Compares by (in priority order) register number, unwrapped type + * (that is types not {@link TypeBearer}s, and local info. + * + * @param other {@code non-null;} spec to compare to + * @return {@code -1..1;} standard result of comparison + */ + public int compareTo(RegisterSpec other) { + if (this.reg < other.reg) { + return -1; + } else if (this.reg > other.reg) { + return 1; + } + + int compare = type.getType().compareTo(other.type.getType()); + + if (compare != 0) { + return compare; + } + + if (this.local == null) { + return (other.local == null) ? 0 : -1; + } else if (other.local == null) { + return 1; + } + + return this.local.compareTo(other.local); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return hashCodeOf(reg, type, local); + } + + /** + * Helper for {@link #hashCode} and {@link #ForComparison.hashCode}, + * which actually does the calculation. + * + * @param reg value of the instance variable + * @param type value of the instance variable + * @param local value of the instance variable + * @return the hash code + */ + private static int hashCodeOf(int reg, TypeBearer type, LocalItem local) { + int hash = (local != null) ? local.hashCode() : 0; + + hash = (hash * 31 + type.hashCode()) * 31 + reg; + return hash; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return toString0(false); + } + + /** {@inheritDoc} */ + public String toHuman() { + return toString0(true); + } + + /** {@inheritDoc} */ + public Type getType() { + return type.getType(); + } + + /** {@inheritDoc} */ + public TypeBearer getFrameType() { + return type.getFrameType(); + } + + /** {@inheritDoc} */ + public final int getBasicType() { + return type.getBasicType(); + } + + /** {@inheritDoc} */ + public final int getBasicFrameType() { + return type.getBasicFrameType(); + } + + /** {@inheritDoc} */ + public final boolean isConstant() { + return false; + } + + /** + * Gets the register number. + * + * @return {@code >= 0;} the register number + */ + public int getReg() { + return reg; + } + + /** + * Gets the type (or actual value) which is loaded from or stored + * to the register associated with this instance. + * + * @return {@code non-null;} the type + */ + public TypeBearer getTypeBearer() { + return type; + } + + /** + * Gets the variable info associated with this instance, if any. + * + * @return {@code null-ok;} the variable info, or {@code null} if this + * instance has none + */ + public LocalItem getLocalItem() { + return local; + } + + /** + * Gets the next available register number after the one in this + * instance. This is equal to the register number plus the width + * (category) of the type used. Among other things, this may also + * be used to determine the minimum required register count + * implied by this instance. + * + * @return {@code >= 0;} the required registers size + */ + public int getNextReg() { + return reg + getCategory(); + } + + /** + * Gets the category of this instance's type. This is just a convenient + * shorthand for {@code getType().getCategory()}. + * + * @see #isCategory1 + * @see #isCategory2 + * @return {@code 1..2;} the category of this instance's type + */ + public int getCategory() { + return type.getType().getCategory(); + } + + /** + * Gets whether this instance's type is category 1. This is just a + * convenient shorthand for {@code getType().isCategory1()}. + * + * @see #getCategory + * @see #isCategory2 + * @return whether or not this instance's type is of category 1 + */ + public boolean isCategory1() { + return type.getType().isCategory1(); + } + + /** + * Gets whether this instance's type is category 2. This is just a + * convenient shorthand for {@code getType().isCategory2()}. + * + * @see #getCategory + * @see #isCategory1 + * @return whether or not this instance's type is of category 2 + */ + public boolean isCategory2() { + return type.getType().isCategory2(); + } + + /** + * Gets the string form for just the register number of this instance. + * + * @return {@code non-null;} the register string form + */ + public String regString() { + return regString(reg); + } + + /** + * Returns an instance that is the intersection between this instance + * and the given one, if any. The intersection is defined as follows: + * + * <ul> + * <li>If {@code other} is {@code null}, then the result + * is {@code null}. + * <li>If the register numbers don't match, then the intersection + * is {@code null}. Otherwise, the register number of the + * intersection is the same as the one in the two instances.</li> + * <li>If the types returned by {@code getType()} are not + * {@code equals()}, then the intersection is null.</li> + * <li>If the type bearers returned by {@code getTypeBearer()} + * are {@code equals()}, then the intersection's type bearer + * is the one from this instance. Otherwise, the intersection's + * type bearer is the {@code getType()} of this instance.</li> + * <li>If the locals are {@code equals()}, then the local info + * of the intersection is the local info of this instance. Otherwise, + * the local info of the intersection is {@code null}.</li> + * </ul> + * + * @param other {@code null-ok;} instance to intersect with (or {@code null}) + * @param localPrimary whether local variables are primary to the + * intersection; if {@code true}, then the only non-null + * results occur when registers being intersected have equal local + * infos (or both have {@code null} local infos) + * @return {@code null-ok;} the intersection + */ + public RegisterSpec intersect(RegisterSpec other, boolean localPrimary) { + if (this == other) { + // Easy out. + return this; + } + + if ((other == null) || (reg != other.getReg())) { + return null; + } + + LocalItem resultLocal = + ((local == null) || !local.equals(other.getLocalItem())) + ? null : local; + boolean sameName = (resultLocal == local); + + if (localPrimary && !sameName) { + return null; + } + + Type thisType = getType(); + Type otherType = other.getType(); + + // Note: Types are always interned. + if (thisType != otherType) { + return null; + } + + TypeBearer resultTypeBearer = + type.equals(other.getTypeBearer()) ? type : thisType; + + if ((resultTypeBearer == type) && sameName) { + // It turns out that the intersection is "this" after all. + return this; + } + + return (resultLocal == null) ? make(reg, resultTypeBearer) : + make(reg, resultTypeBearer, resultLocal); + } + + /** + * Returns an instance that is identical to this one, except that the + * register number is replaced by the given one. + * + * @param newReg {@code >= 0;} the new register number + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpec withReg(int newReg) { + if (reg == newReg) { + return this; + } + + return makeLocalOptional(newReg, type, local); + } + + /** + * Returns an instance that is identical to this one, except that + * the type is replaced by the given one. + * + * @param newType {@code non-null;} the new type + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpec withType(TypeBearer newType) { + return makeLocalOptional(reg, newType, local); + } + + /** + * Returns an instance that is identical to this one, except that the + * register number is offset by the given amount. + * + * @param delta the amount to offset the register number by + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpec withOffset(int delta) { + if (delta == 0) { + return this; + } + + return withReg(reg + delta); + } + + /** + * Returns an instance that is identical to this one, except that + * the type bearer is replaced by the actual underlying type + * (thereby stripping off non-type information) with any + * initialization information stripped away as well. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpec withSimpleType() { + TypeBearer orig = type; + Type newType; + + if (orig instanceof Type) { + newType = (Type) orig; + } else { + newType = orig.getType(); + } + + if (newType.isUninitialized()) { + newType = newType.getInitializedType(); + } + + if (newType == orig) { + return this; + } + + return makeLocalOptional(reg, newType, local); + } + + /** + * Returns an instance that is identical to this one except that the + * local variable is as specified in the parameter. + * + * @param local {@code null-ok;} the local item or null for none + * @return an appropriate instance + */ + public RegisterSpec withLocalItem(LocalItem local) { + if ((this.local== local) + || ((this.local != null) && this.local.equals(local))) { + + return this; + } + + return makeLocalOptional(reg, type, local); + } + + + /** + * Helper for {@link #toString} and {@link #toHuman}. + * + * @param human whether to be human-oriented + * @return {@code non-null;} the string form + */ + private String toString0(boolean human) { + StringBuffer sb = new StringBuffer(40); + + sb.append(regString()); + sb.append(":"); + + if (local != null) { + sb.append(local.toString()); + } + + Type justType = type.getType(); + sb.append(justType); + + if (justType != type) { + sb.append("="); + if (human && (type instanceof Constant)) { + sb.append(((Constant) type).toHuman()); + } else { + sb.append(type); + } + } + + return sb.toString(); + } + + /** + * Holder of register spec data for the purposes of comparison (so that + * {@code RegisterSpec} itself can still keep {@code final} + * instance variables. + */ + private static class ForComparison { + /** {@code >= 0;} register number */ + private int reg; + + /** {@code non-null;} type loaded or stored */ + private TypeBearer type; + + /** + * {@code null-ok;} local variable associated with this + * register, if any + */ + private LocalItem local; + + /** + * Set all the instance variables. + * + * @param reg {@code >= 0;} the register number + * @param type {@code non-null;} the type (or possibly actual + * value) which is loaded from or stored to the indicated + * register + * @param local {@code null-ok;} the associated local variable, if any + * @return {@code non-null;} an appropriately-constructed instance + */ + public void set(int reg, TypeBearer type, LocalItem local) { + this.reg = reg; + this.type = type; + this.local = local; + } + + /** + * Construct a {@code RegisterSpec} of this instance's + * contents. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpec toRegisterSpec() { + return new RegisterSpec(reg, type, local); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof RegisterSpec)) { + return false; + } + + RegisterSpec spec = (RegisterSpec) other; + return spec.equals(reg, type, local); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return hashCodeOf(reg, type, local); + } + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/RegisterSpecList.java b/dexgen/src/com/android/dexgen/rop/code/RegisterSpecList.java new file mode 100644 index 0000000..a0f7a24 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/RegisterSpecList.java @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.rop.type.TypeList; +import com.android.dexgen.util.FixedSizeList; + +/** + * List of {@link RegisterSpec} instances. + */ +public final class RegisterSpecList + extends FixedSizeList implements TypeList { + /** {@code non-null;} no-element instance */ + public static final RegisterSpecList EMPTY = new RegisterSpecList(0); + + /** + * Makes a single-element instance. + * + * @param spec {@code non-null;} the element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static RegisterSpecList make(RegisterSpec spec) { + RegisterSpecList result = new RegisterSpecList(1); + result.set(0, spec); + return result; + } + + /** + * Makes a two-element instance. + * + * @param spec0 {@code non-null;} the first element + * @param spec1 {@code non-null;} the second element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static RegisterSpecList make(RegisterSpec spec0, + RegisterSpec spec1) { + RegisterSpecList result = new RegisterSpecList(2); + result.set(0, spec0); + result.set(1, spec1); + return result; + } + + /** + * Makes a three-element instance. + * + * @param spec0 {@code non-null;} the first element + * @param spec1 {@code non-null;} the second element + * @param spec2 {@code non-null;} the third element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static RegisterSpecList make(RegisterSpec spec0, RegisterSpec spec1, + RegisterSpec spec2) { + RegisterSpecList result = new RegisterSpecList(3); + result.set(0, spec0); + result.set(1, spec1); + result.set(2, spec2); + return result; + } + + /** + * Makes a four-element instance. + * + * @param spec0 {@code non-null;} the first element + * @param spec1 {@code non-null;} the second element + * @param spec2 {@code non-null;} the third element + * @param spec3 {@code non-null;} the fourth element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static RegisterSpecList make(RegisterSpec spec0, RegisterSpec spec1, + RegisterSpec spec2, + RegisterSpec spec3) { + RegisterSpecList result = new RegisterSpecList(4); + result.set(0, spec0); + result.set(1, spec1); + result.set(2, spec2); + result.set(3, spec3); + return result; + } + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + */ + public RegisterSpecList(int size) { + super(size); + } + + /** {@inheritDoc} */ + public Type getType(int n) { + return get(n).getType().getType(); + } + + /** {@inheritDoc} */ + public int getWordCount() { + int sz = size(); + int result = 0; + + for (int i = 0; i < sz; i++) { + result += getType(i).getCategory(); + } + + return result; + } + + /** {@inheritDoc} */ + public TypeList withAddedType(Type type) { + throw new UnsupportedOperationException("unsupported"); + } + + /** + * Gets the indicated element. It is an error to call this with the + * index for an element which was never set; if you do that, this + * will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which element + * @return {@code non-null;} the indicated element + */ + public RegisterSpec get(int n) { + return (RegisterSpec) get0(n); + } + + /** + * Returns a RegisterSpec in this list that uses the specified register, + * or null if there is none in this list. + * @param reg Register to find + * @return RegisterSpec that uses argument or null. + */ + public RegisterSpec specForRegister(int reg) { + int sz = size(); + for (int i = 0; i < sz; i++) { + RegisterSpec rs; + + rs = get(i); + + if (rs.getReg() == reg) { + return rs; + } + } + + return null; + } + + /** + * Returns the index of a RegisterSpec in this list that uses the specified + * register, or -1 if none in this list uses the register. + * @param reg Register to find + * @return index of RegisterSpec or -1 + */ + public int indexOfRegister(int reg) { + int sz = size(); + for (int i = 0; i < sz; i++) { + RegisterSpec rs; + + rs = get(i); + + if (rs.getReg() == reg) { + return i; + } + } + + return -1; + } + + /** + * Sets the element at the given index. + * + * @param n {@code >= 0, < size();} which element + * @param spec {@code non-null;} the value to store + */ + public void set(int n, RegisterSpec spec) { + set0(n, spec); + } + + /** + * Gets the minimum required register count implied by this + * instance. This is equal to the highest register number referred + * to plus the widest width (largest category) of the type used in + * that register. + * + * @return {@code >= 0;} the required registers size + */ + public int getRegistersSize() { + int sz = size(); + int result = 0; + + for (int i = 0; i < sz; i++) { + RegisterSpec spec = (RegisterSpec) get0(i); + if (spec != null) { + int min = spec.getNextReg(); + if (min > result) { + result = min; + } + } + } + + return result; + } + + /** + * Returns a new instance, which is the same as this instance, + * except that it has an additional element prepended to the original. + * Mutability of the result is inherited from the original. + * + * @param spec {@code non-null;} the new first spec (to prepend) + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpecList withFirst(RegisterSpec spec) { + int sz = size(); + RegisterSpecList result = new RegisterSpecList(sz + 1); + + for (int i = 0; i < sz; i++) { + result.set0(i + 1, get0(i)); + } + + result.set0(0, spec); + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } + + /** + * Returns a new instance, which is the same as this instance, + * except that its first element is removed. Mutability of the + * result is inherited from the original. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpecList withoutFirst() { + int newSize = size() - 1; + + if (newSize == 0) { + return EMPTY; + } + + RegisterSpecList result = new RegisterSpecList(newSize); + + for (int i = 0; i < newSize; i++) { + result.set0(i, get0(i + 1)); + } + + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } + + /** + * Returns a new instance, which is the same as this instance, + * except that its last element is removed. Mutability of the + * result is inherited from the original. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpecList withoutLast() { + int newSize = size() - 1; + + if (newSize == 0) { + return EMPTY; + } + + RegisterSpecList result = new RegisterSpecList(newSize); + + for (int i = 0; i < newSize; i++) { + result.set0(i, get0(i)); + } + + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } + + /** + * Returns an instance that is identical to this one, except that + * all register numbers are offset by the given amount. Mutability + * of the result is inherited from the original. + * + * @param delta the amount to offset the register numbers by + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpecList withOffset(int delta) { + int sz = size(); + + if (sz == 0) { + // Don't bother making a new zero-element instance. + return this; + } + + RegisterSpecList result = new RegisterSpecList(sz); + + for (int i = 0; i < sz; i++) { + RegisterSpec one = (RegisterSpec) get0(i); + if (one != null) { + result.set0(i, one.withOffset(delta)); + } + } + + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } + + /** + * Returns an instance that is identical to this one, except that + * all register numbers are renumbered sequentially from the given + * base, with the first number duplicated if indicated. + * + * @param base the base register number + * @param duplicateFirst whether to duplicate the first number + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpecList withSequentialRegisters(int base, + boolean duplicateFirst) { + int sz = size(); + + if (sz == 0) { + // Don't bother making a new zero-element instance. + return this; + } + + RegisterSpecList result = new RegisterSpecList(sz); + + for (int i = 0; i < sz; i++) { + RegisterSpec one = (RegisterSpec) get0(i); + result.set0(i, one.withReg(base)); + if (duplicateFirst) { + duplicateFirst = false; + } else { + base += one.getCategory(); + } + } + + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } + +} diff --git a/dexgen/src/com/android/dexgen/rop/code/RegisterSpecSet.java b/dexgen/src/com/android/dexgen/rop/code/RegisterSpecSet.java new file mode 100644 index 0000000..69e67e9 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/RegisterSpecSet.java @@ -0,0 +1,397 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.util.MutabilityControl; + +/** + * Set of {@link RegisterSpec} instances, where a given register number + * may appear only once in the set. + */ +public final class RegisterSpecSet + extends MutabilityControl { + /** {@code non-null;} no-element instance */ + public static final RegisterSpecSet EMPTY = new RegisterSpecSet(0); + + /** + * {@code non-null;} array of register specs, where each element is + * {@code null} or is an instance whose {@code reg} + * matches the array index + */ + private final RegisterSpec[] specs; + + /** {@code >= -1;} size of the set or {@code -1} if not yet calculated */ + private int size; + + /** + * Constructs an instance. The instance is initially empty. + * + * @param maxSize {@code >= 0;} the maximum register number (exclusive) that + * may be represented in this instance + */ + public RegisterSpecSet(int maxSize) { + super(maxSize != 0); + + this.specs = new RegisterSpec[maxSize]; + this.size = 0; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof RegisterSpecSet)) { + return false; + } + + RegisterSpecSet otherSet = (RegisterSpecSet) other; + RegisterSpec[] otherSpecs = otherSet.specs; + int len = specs.length; + + if ((len != otherSpecs.length) || (size() != otherSet.size())) { + return false; + } + + for (int i = 0; i < len; i++) { + RegisterSpec s1 = specs[i]; + RegisterSpec s2 = otherSpecs[i]; + + if (s1 == s2) { + continue; + } + + if ((s1 == null) || !s1.equals(s2)) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + int len = specs.length; + int hash = 0; + + for (int i = 0; i < len; i++) { + RegisterSpec spec = specs[i]; + int oneHash = (spec == null) ? 0 : spec.hashCode(); + hash = (hash * 31) + oneHash; + } + + return hash; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + int len = specs.length; + StringBuffer sb = new StringBuffer(len * 25); + + sb.append('{'); + + boolean any = false; + for (int i = 0; i < len; i++) { + RegisterSpec spec = specs[i]; + if (spec != null) { + if (any) { + sb.append(", "); + } else { + any = true; + } + sb.append(spec); + } + } + + sb.append('}'); + return sb.toString(); + } + + /** + * Gets the maximum number of registers that may be in this instance, which + * is also the maximum-plus-one of register numbers that may be + * represented. + * + * @return {@code >= 0;} the maximum size + */ + public int getMaxSize() { + return specs.length; + } + + /** + * Gets the current size of this instance. + * + * @return {@code >= 0;} the size + */ + public int size() { + int result = size; + + if (result < 0) { + int len = specs.length; + + result = 0; + for (int i = 0; i < len; i++) { + if (specs[i] != null) { + result++; + } + } + + size = result; + } + + return result; + } + + /** + * Gets the element with the given register number, if any. + * + * @param reg {@code >= 0;} the desired register number + * @return {@code null-ok;} the element with the given register number or + * {@code null} if there is none + */ + public RegisterSpec get(int reg) { + try { + return specs[reg]; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("bogus reg"); + } + } + + /** + * Gets the element with the same register number as the given + * spec, if any. This is just a convenient shorthand for + * {@code get(spec.getReg())}. + * + * @param spec {@code non-null;} spec with the desired register number + * @return {@code null-ok;} the element with the matching register number or + * {@code null} if there is none + */ + public RegisterSpec get(RegisterSpec spec) { + return get(spec.getReg()); + } + + /** + * Returns the spec in this set that's currently associated with a + * given local (type, name, and signature), or {@code null} if there is + * none. This ignores the register number of the given spec but + * matches on everything else. + * + * @param spec {@code non-null;} local to look for + * @return {@code null-ok;} first register found that matches, if any + */ + public RegisterSpec findMatchingLocal(RegisterSpec spec) { + int length = specs.length; + + for (int reg = 0; reg < length; reg++) { + RegisterSpec s = specs[reg]; + + if (s == null) { + continue; + } + + if (spec.matchesVariable(s)) { + return s; + } + } + + return null; + } + + /** + * Returns the spec in this set that's currently associated with a given + * local (name and signature), or {@code null} if there is none. + * + * @param local {@code non-null;} local item to search for + * @return {@code null-ok;} first register found with matching name and signature + */ + public RegisterSpec localItemToSpec(LocalItem local) { + int length = specs.length; + + for (int reg = 0; reg < length; reg++) { + RegisterSpec spec = specs[reg]; + + if ((spec != null) && local.equals(spec.getLocalItem())) { + return spec; + } + } + + return null; + } + + /** + * Removes a spec from the set. Only the register number + * of the parameter is significant. + * + * @param toRemove {@code non-null;} register to remove. + */ + public void remove(RegisterSpec toRemove) { + try { + specs[toRemove.getReg()] = null; + size = -1; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("bogus reg"); + } + } + + /** + * Puts the given spec into the set. If there is already an element in + * the set with the same register number, it is replaced. Additionally, + * if the previous element is for a category-2 register, then that + * previous element is nullified. Finally, if the given spec is for + * a category-2 register, then the immediately subsequent element + * is nullified. + * + * @param spec {@code non-null;} the register spec to put in the instance + */ + public void put(RegisterSpec spec) { + throwIfImmutable(); + + if (spec == null) { + throw new NullPointerException("spec == null"); + } + + size = -1; + + try { + int reg = spec.getReg(); + specs[reg] = spec; + + if (reg > 0) { + int prevReg = reg - 1; + RegisterSpec prevSpec = specs[prevReg]; + if ((prevSpec != null) && (prevSpec.getCategory() == 2)) { + specs[prevReg] = null; + } + } + + if (spec.getCategory() == 2) { + specs[reg + 1] = null; + } + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("spec.getReg() out of range"); + } + } + + /** + * Put the entire contents of the given set into this one. + * + * @param set {@code non-null;} the set to put into this instance + */ + public void putAll(RegisterSpecSet set) { + int max = set.getMaxSize(); + + for (int i = 0; i < max; i++) { + RegisterSpec spec = set.get(i); + if (spec != null) { + put(spec); + } + } + } + + /** + * Intersects this instance with the given one, modifying this + * instance. The intersection consists of the pairwise + * {@link RegisterSpec#intersect} of corresponding elements from + * this instance and the given one where both are non-null. + * + * @param other {@code non-null;} set to intersect with + * @param localPrimary whether local variables are primary to + * the intersection; if {@code true}, then the only non-null + * result elements occur when registers being intersected have + * equal names (or both have {@code null} names) + */ + public void intersect(RegisterSpecSet other, boolean localPrimary) { + throwIfImmutable(); + + RegisterSpec[] otherSpecs = other.specs; + int thisLen = specs.length; + int len = Math.min(thisLen, otherSpecs.length); + + size = -1; + + for (int i = 0; i < len; i++) { + RegisterSpec spec = specs[i]; + + if (spec == null) { + continue; + } + + RegisterSpec intersection = + spec.intersect(otherSpecs[i], localPrimary); + if (intersection != spec) { + specs[i] = intersection; + } + } + + for (int i = len; i < thisLen; i++) { + specs[i] = null; + } + } + + /** + * Returns an instance that is identical to this one, except that + * all register numbers are offset by the given amount. Mutability + * of the result is inherited from the original. + * + * @param delta the amount to offset the register numbers by + * @return {@code non-null;} an appropriately-constructed instance + */ + public RegisterSpecSet withOffset(int delta) { + int len = specs.length; + RegisterSpecSet result = new RegisterSpecSet(len + delta); + + for (int i = 0; i < len; i++) { + RegisterSpec spec = specs[i]; + if (spec != null) { + result.put(spec.withOffset(delta)); + } + } + + result.size = size; + + if (isImmutable()) { + result.setImmutable(); + } + + return result; + } + + /** + * Makes and return a mutable copy of this instance. + * + * @return {@code non-null;} the mutable copy + */ + public RegisterSpecSet mutableCopy() { + int len = specs.length; + RegisterSpecSet copy = new RegisterSpecSet(len); + + for (int i = 0; i < len; i++) { + RegisterSpec spec = specs[i]; + if (spec != null) { + copy.put(spec); + } + } + + copy.size = size; + + return copy; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/Rop.java b/dexgen/src/com/android/dexgen/rop/code/Rop.java new file mode 100644 index 0000000..db9a6c2 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/Rop.java @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.rop.type.StdTypeList; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.rop.type.TypeList; +import com.android.dexgen.util.Hex; + +/** + * Class that describes all the immutable parts of register-based operations. + */ +public final class Rop { + /** minimum {@code BRANCH_*} value */ + public static final int BRANCH_MIN = 1; + + /** indicates a non-branching op */ + public static final int BRANCH_NONE = 1; + + /** indicates a function/method return */ + public static final int BRANCH_RETURN = 2; + + /** indicates an unconditional goto */ + public static final int BRANCH_GOTO = 3; + + /** indicates a two-way branch */ + public static final int BRANCH_IF = 4; + + /** indicates a switch-style branch */ + public static final int BRANCH_SWITCH = 5; + + /** indicates a throw-style branch (both always-throws and may-throw) */ + public static final int BRANCH_THROW = 6; + + /** maximum {@code BRANCH_*} value */ + public static final int BRANCH_MAX = 6; + + /** the opcode; one of the constants in {@link RegOps} */ + private final int opcode; + + /** + * {@code non-null;} result type of this operation; {@link Type#VOID} for + * no-result operations + */ + private final Type result; + + /** {@code non-null;} types of all the sources of this operation */ + private final TypeList sources; + + /** {@code non-null;} list of possible types thrown by this operation */ + private final TypeList exceptions; + + /** + * the branchingness of this op; one of the {@code BRANCH_*} + * constants in this class + */ + private final int branchingness; + + /** whether this is a function/method call op or similar */ + private final boolean isCallLike; + + /** {@code null-ok;} nickname, if specified (used for debugging) */ + private final String nickname; + + /** + * Constructs an instance. This method is private. Use one of the + * public constructors. + * + * @param opcode the opcode; one of the constants in {@link RegOps} + * @param result {@code non-null;} result type of this operation; {@link + * Type#VOID} for no-result operations + * @param sources {@code non-null;} types of all the sources of this operation + * @param exceptions {@code non-null;} list of possible types thrown by this + * operation + * @param branchingness the branchingness of this op; one of the + * {@code BRANCH_*} constants + * @param isCallLike whether the op is a function/method call or similar + * @param nickname {@code null-ok;} optional nickname (used for debugging) + */ + public Rop(int opcode, Type result, TypeList sources, + TypeList exceptions, int branchingness, boolean isCallLike, + String nickname) { + if (result == null) { + throw new NullPointerException("result == null"); + } + + if (sources == null) { + throw new NullPointerException("sources == null"); + } + + if (exceptions == null) { + throw new NullPointerException("exceptions == null"); + } + + if ((branchingness < BRANCH_MIN) || (branchingness > BRANCH_MAX)) { + throw new IllegalArgumentException("bogus branchingness"); + } + + if ((exceptions.size() != 0) && (branchingness != BRANCH_THROW)) { + throw new IllegalArgumentException("exceptions / branchingness " + + "mismatch"); + } + + this.opcode = opcode; + this.result = result; + this.sources = sources; + this.exceptions = exceptions; + this.branchingness = branchingness; + this.isCallLike = isCallLike; + this.nickname = nickname; + } + + /** + * Constructs an instance. The constructed instance is never a + * call-like op (see {@link #isCallLike}). + * + * @param opcode the opcode; one of the constants in {@link RegOps} + * @param result {@code non-null;} result type of this operation; {@link + * Type#VOID} for no-result operations + * @param sources {@code non-null;} types of all the sources of this operation + * @param exceptions {@code non-null;} list of possible types thrown by this + * operation + * @param branchingness the branchingness of this op; one of the + * {@code BRANCH_*} constants + * @param nickname {@code null-ok;} optional nickname (used for debugging) + */ + public Rop(int opcode, Type result, TypeList sources, + TypeList exceptions, int branchingness, String nickname) { + this(opcode, result, sources, exceptions, branchingness, false, + nickname); + } + + /** + * Constructs a no-exception instance. The constructed instance is never a + * call-like op (see {@link #isCallLike}). + * + * @param opcode the opcode; one of the constants in {@link RegOps} + * @param result {@code non-null;} result type of this operation; {@link + * Type#VOID} for no-result operations + * @param sources {@code non-null;} types of all the sources of this operation + * @param branchingness the branchingness of this op; one of the + * {@code BRANCH_*} constants + * @param nickname {@code null-ok;} optional nickname (used for debugging) + */ + public Rop(int opcode, Type result, TypeList sources, int branchingness, + String nickname) { + this(opcode, result, sources, StdTypeList.EMPTY, branchingness, false, + nickname); + } + + /** + * Constructs a non-branching no-exception instance. The + * {@code branchingness} is always {@code BRANCH_NONE}, + * and it is never a call-like op (see {@link #isCallLike}). + * + * @param opcode the opcode; one of the constants in {@link RegOps} + * @param result {@code non-null;} result type of this operation; {@link + * Type#VOID} for no-result operations + * @param sources {@code non-null;} types of all the sources of this operation + * @param nickname {@code null-ok;} optional nickname (used for debugging) + */ + public Rop(int opcode, Type result, TypeList sources, String nickname) { + this(opcode, result, sources, StdTypeList.EMPTY, Rop.BRANCH_NONE, + false, nickname); + } + + /** + * Constructs a non-empty exceptions instance. Its + * {@code branchingness} is always {@code BRANCH_THROW}, + * but it is never a call-like op (see {@link #isCallLike}). + * + * @param opcode the opcode; one of the constants in {@link RegOps} + * @param result {@code non-null;} result type of this operation; {@link + * Type#VOID} for no-result operations + * @param sources {@code non-null;} types of all the sources of this operation + * @param exceptions {@code non-null;} list of possible types thrown by this + * operation + * @param nickname {@code null-ok;} optional nickname (used for debugging) + */ + public Rop(int opcode, Type result, TypeList sources, TypeList exceptions, + String nickname) { + this(opcode, result, sources, exceptions, Rop.BRANCH_THROW, false, + nickname); + } + + /** + * Constructs a non-nicknamed instance with non-empty exceptions, which + * is always a call-like op (see {@link #isCallLike}). Its + * {@code branchingness} is always {@code BRANCH_THROW}. + * + * @param opcode the opcode; one of the constants in {@link RegOps} + * @param sources {@code non-null;} types of all the sources of this operation + * @param exceptions {@code non-null;} list of possible types thrown by this + * operation + */ + public Rop(int opcode, TypeList sources, TypeList exceptions) { + this(opcode, Type.VOID, sources, exceptions, Rop.BRANCH_THROW, true, + null); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (this == other) { + // Easy out. + return true; + } + + if (!(other instanceof Rop)) { + return false; + } + + Rop rop = (Rop) other; + + return (opcode == rop.opcode) && + (branchingness == rop.branchingness) && + (result == rop.result) && + sources.equals(rop.sources) && + exceptions.equals(rop.exceptions); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + int h = (opcode * 31) + branchingness; + h = (h * 31) + result.hashCode(); + h = (h * 31) + sources.hashCode(); + h = (h * 31) + exceptions.hashCode(); + + return h; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(40); + + sb.append("Rop{"); + + sb.append(RegOps.opName(opcode)); + + if (result != Type.VOID) { + sb.append(" "); + sb.append(result); + } else { + sb.append(" ."); + } + + sb.append(" <-"); + + int sz = sources.size(); + if (sz == 0) { + sb.append(" ."); + } else { + for (int i = 0; i < sz; i++) { + sb.append(' '); + sb.append(sources.getType(i)); + } + } + + if (isCallLike) { + sb.append(" call"); + } + + sz = exceptions.size(); + if (sz != 0) { + sb.append(" throws"); + for (int i = 0; i < sz; i++) { + sb.append(' '); + Type one = exceptions.getType(i); + if (one == Type.THROWABLE) { + sb.append("<any>"); + } else { + sb.append(exceptions.getType(i)); + } + } + } else { + switch (branchingness) { + case BRANCH_NONE: sb.append(" flows"); break; + case BRANCH_RETURN: sb.append(" returns"); break; + case BRANCH_GOTO: sb.append(" gotos"); break; + case BRANCH_IF: sb.append(" ifs"); break; + case BRANCH_SWITCH: sb.append(" switches"); break; + default: sb.append(" " + Hex.u1(branchingness)); break; + } + } + + sb.append('}'); + + return sb.toString(); + } + + /** + * Gets the opcode. + * + * @return the opcode + */ + public int getOpcode() { + return opcode; + } + + /** + * Gets the result type. A return value of {@link Type#VOID} + * means this operation returns nothing. + * + * @return {@code null-ok;} the result spec + */ + public Type getResult() { + return result; + } + + /** + * Gets the source types. + * + * @return {@code non-null;} the source types + */ + public TypeList getSources() { + return sources; + } + + /** + * Gets the list of exception types that might be thrown. + * + * @return {@code non-null;} the list of exception types + */ + public TypeList getExceptions() { + return exceptions; + } + + /** + * Gets the branchingness of this instance. + * + * @return the branchingness + */ + public int getBranchingness() { + return branchingness; + } + + /** + * Gets whether this opcode is a function/method call or similar. + * + * @return {@code true} iff this opcode is call-like + */ + public boolean isCallLike() { + return isCallLike; + } + + + /** + * Gets whether this opcode is commutative (the order of its sources are + * unimportant) or not. All commutative Rops have exactly two sources and + * have no branchiness. + * + * @return true if rop is commutative + */ + public boolean isCommutative() { + switch (opcode) { + case RegOps.AND: + case RegOps.OR: + case RegOps.XOR: + case RegOps.ADD: + case RegOps.MUL: + return true; + default: + return false; + } + } + + /** + * Gets the nickname. If this instance has no nickname, this returns + * the result of calling {@link #toString}. + * + * @return {@code non-null;} the nickname + */ + public String getNickname() { + if (nickname != null) { + return nickname; + } + + return toString(); + } + + /** + * Gets whether this operation can possibly throw an exception. This + * is just a convenient wrapper for + * {@code getExceptions().size() != 0}. + * + * @return {@code true} iff this operation can possibly throw + */ + public final boolean canThrow() { + return (exceptions.size() != 0); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/RopMethod.java b/dexgen/src/com/android/dexgen/rop/code/RopMethod.java new file mode 100644 index 0000000..ba65b3f --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/RopMethod.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.util.Bits; +import com.android.dexgen.util.Hex; +import com.android.dexgen.util.IntList; + +/** + * All of the parts that make up a method at the rop layer. + */ +public final class RopMethod { + /** {@code non-null;} basic block list of the method */ + private final BasicBlockList blocks; + + /** {@code >= 0;} label for the block which starts the method */ + private final int firstLabel; + + /** + * {@code null-ok;} array of predecessors for each block, indexed by block + * label + */ + private IntList[] predecessors; + + /** + * {@code null-ok;} the predecessors for the implicit "exit" block, that is + * the labels for the blocks that return, if calculated + */ + private IntList exitPredecessors; + + /** + * Constructs an instance. + * + * @param blocks {@code non-null;} basic block list of the method + * @param firstLabel {@code >= 0;} the label of the first block to execute + */ + public RopMethod(BasicBlockList blocks, int firstLabel) { + if (blocks == null) { + throw new NullPointerException("blocks == null"); + } + + if (firstLabel < 0) { + throw new IllegalArgumentException("firstLabel < 0"); + } + + this.blocks = blocks; + this.firstLabel = firstLabel; + + this.predecessors = null; + this.exitPredecessors = null; + } + + /** + * Gets the basic block list for this method. + * + * @return {@code non-null;} the list + */ + public BasicBlockList getBlocks() { + return blocks; + } + + /** + * Gets the label for the first block in the method that this list + * represents. + * + * @return {@code >= 0;} the first-block label + */ + public int getFirstLabel() { + return firstLabel; + } + + /** + * Gets the predecessors associated with the given block. This throws + * an exception if there is no block with the given label. + * + * @param label {@code >= 0;} the label of the block in question + * @return {@code non-null;} the predecessors of that block + */ + public IntList labelToPredecessors(int label) { + if (exitPredecessors == null) { + calcPredecessors(); + } + + IntList result = predecessors[label]; + + if (result == null) { + throw new RuntimeException("no such block: " + Hex.u2(label)); + } + + return result; + } + + /** + * Gets the exit predecessors for this instance. + * + * @return {@code non-null;} the exit predecessors + */ + public IntList getExitPredecessors() { + if (exitPredecessors == null) { + calcPredecessors(); + } + + return exitPredecessors; + } + + + /** + * Returns an instance that is identical to this one, except that + * the registers in each instruction are offset by the given + * amount. + * + * @param delta the amount to offset register numbers by + * @return {@code non-null;} an appropriately-constructed instance + */ + public RopMethod withRegisterOffset(int delta) { + RopMethod result = new RopMethod(blocks.withRegisterOffset(delta), + firstLabel); + + if (exitPredecessors != null) { + /* + * The predecessors have been calculated. It's safe to + * inject these into the new instance, since the + * transformation being applied doesn't affect the + * predecessors. + */ + result.exitPredecessors = exitPredecessors; + result.predecessors = predecessors; + } + + return result; + } + + /** + * Calculates the predecessor sets for each block as well as for the + * exit. + */ + private void calcPredecessors() { + int maxLabel = blocks.getMaxLabel(); + IntList[] predecessors = new IntList[maxLabel]; + IntList exitPredecessors = new IntList(10); + int sz = blocks.size(); + + /* + * For each block, find its successors, and add the block's label to + * the successor's predecessors. + */ + for (int i = 0; i < sz; i++) { + BasicBlock one = blocks.get(i); + int label = one.getLabel(); + IntList successors = one.getSuccessors(); + int ssz = successors.size(); + if (ssz == 0) { + // This block exits. + exitPredecessors.add(label); + } else { + for (int j = 0; j < ssz; j++) { + int succLabel = successors.get(j); + IntList succPreds = predecessors[succLabel]; + if (succPreds == null) { + succPreds = new IntList(10); + predecessors[succLabel] = succPreds; + } + succPreds.add(label); + } + } + } + + // Sort and immutablize all the predecessor lists. + for (int i = 0; i < maxLabel; i++) { + IntList preds = predecessors[i]; + if (preds != null) { + preds.sort(); + preds.setImmutable(); + } + } + + exitPredecessors.sort(); + exitPredecessors.setImmutable(); + + /* + * The start label might not ever have had any predecessors + * added to it (probably doesn't, because of how Java gets + * translated into rop form). So, check for this and rectify + * the situation if required. + */ + if (predecessors[firstLabel] == null) { + predecessors[firstLabel] = IntList.EMPTY; + } + + this.predecessors = predecessors; + this.exitPredecessors = exitPredecessors; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/Rops.java b/dexgen/src/com/android/dexgen/rop/code/Rops.java new file mode 100644 index 0000000..ad9327e --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/Rops.java @@ -0,0 +1,2086 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.cst.CstBaseMethodRef; +import com.android.dexgen.rop.cst.CstMethodRef; +import com.android.dexgen.rop.cst.CstType; +import com.android.dexgen.rop.type.Prototype; +import com.android.dexgen.rop.type.StdTypeList; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.rop.type.TypeBearer; +import com.android.dexgen.rop.type.TypeList; + +/** + * Standard instances of {@link Rop}. + */ +public final class Rops { + /** {@code nop()} */ + public static final Rop NOP = + new Rop(RegOps.NOP, Type.VOID, StdTypeList.EMPTY, "nop"); + + /** {@code r,x: int :: r = x;} */ + public static final Rop MOVE_INT = + new Rop(RegOps.MOVE, Type.INT, StdTypeList.INT, "move-int"); + + /** {@code r,x: long :: r = x;} */ + public static final Rop MOVE_LONG = + new Rop(RegOps.MOVE, Type.LONG, StdTypeList.LONG, "move-long"); + + /** {@code r,x: float :: r = x;} */ + public static final Rop MOVE_FLOAT = + new Rop(RegOps.MOVE, Type.FLOAT, StdTypeList.FLOAT, "move-float"); + + /** {@code r,x: double :: r = x;} */ + public static final Rop MOVE_DOUBLE = + new Rop(RegOps.MOVE, Type.DOUBLE, StdTypeList.DOUBLE, "move-double"); + + /** {@code r,x: Object :: r = x;} */ + public static final Rop MOVE_OBJECT = + new Rop(RegOps.MOVE, Type.OBJECT, StdTypeList.OBJECT, "move-object"); + + /** + * {@code r,x: ReturnAddress :: r = x;} + * + * Note that this rop-form instruction has no dex-form equivilent and + * must be removed before the dex conversion. + */ + public static final Rop MOVE_RETURN_ADDRESS = + new Rop(RegOps.MOVE, Type.RETURN_ADDRESS, + StdTypeList.RETURN_ADDRESS, "move-return-address"); + + /** {@code r,param(x): int :: r = param(x);} */ + public static final Rop MOVE_PARAM_INT = + new Rop(RegOps.MOVE_PARAM, Type.INT, StdTypeList.EMPTY, + "move-param-int"); + + /** {@code r,param(x): long :: r = param(x);} */ + public static final Rop MOVE_PARAM_LONG = + new Rop(RegOps.MOVE_PARAM, Type.LONG, StdTypeList.EMPTY, + "move-param-long"); + + /** {@code r,param(x): float :: r = param(x);} */ + public static final Rop MOVE_PARAM_FLOAT = + new Rop(RegOps.MOVE_PARAM, Type.FLOAT, StdTypeList.EMPTY, + "move-param-float"); + + /** {@code r,param(x): double :: r = param(x);} */ + public static final Rop MOVE_PARAM_DOUBLE = + new Rop(RegOps.MOVE_PARAM, Type.DOUBLE, StdTypeList.EMPTY, + "move-param-double"); + + /** {@code r,param(x): Object :: r = param(x);} */ + public static final Rop MOVE_PARAM_OBJECT = + new Rop(RegOps.MOVE_PARAM, Type.OBJECT, StdTypeList.EMPTY, + "move-param-object"); + + /** {@code r, literal: int :: r = literal;} */ + public static final Rop CONST_INT = + new Rop(RegOps.CONST, Type.INT, StdTypeList.EMPTY, "const-int"); + + /** {@code r, literal: long :: r = literal;} */ + public static final Rop CONST_LONG = + new Rop(RegOps.CONST, Type.LONG, StdTypeList.EMPTY, "const-long"); + + /** {@code r, literal: float :: r = literal;} */ + public static final Rop CONST_FLOAT = + new Rop(RegOps.CONST, Type.FLOAT, StdTypeList.EMPTY, "const-float"); + + /** {@code r, literal: double :: r = literal;} */ + public static final Rop CONST_DOUBLE = + new Rop(RegOps.CONST, Type.DOUBLE, StdTypeList.EMPTY, "const-double"); + + /** {@code r, literal: Object :: r = literal;} */ + public static final Rop CONST_OBJECT = + new Rop(RegOps.CONST, Type.OBJECT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "const-object"); + + /** {@code r, literal: Object :: r = literal;} */ + public static final Rop CONST_OBJECT_NOTHROW = + new Rop(RegOps.CONST, Type.OBJECT, StdTypeList.EMPTY, + "const-object-nothrow"); + + /** {@code goto label} */ + public static final Rop GOTO = + new Rop(RegOps.GOTO, Type.VOID, StdTypeList.EMPTY, Rop.BRANCH_GOTO, + "goto"); + + /** {@code x: int :: if (x == 0) goto label} */ + public static final Rop IF_EQZ_INT = + new Rop(RegOps.IF_EQ, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF, + "if-eqz-int"); + + /** {@code x: int :: if (x != 0) goto label} */ + public static final Rop IF_NEZ_INT = + new Rop(RegOps.IF_NE, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF, + "if-nez-int"); + + /** {@code x: int :: if (x < 0) goto label} */ + public static final Rop IF_LTZ_INT = + new Rop(RegOps.IF_LT, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF, + "if-ltz-int"); + + /** {@code x: int :: if (x >= 0) goto label} */ + public static final Rop IF_GEZ_INT = + new Rop(RegOps.IF_GE, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF, + "if-gez-int"); + + /** {@code x: int :: if (x <= 0) goto label} */ + public static final Rop IF_LEZ_INT = + new Rop(RegOps.IF_LE, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF, + "if-lez-int"); + + /** {@code x: int :: if (x > 0) goto label} */ + public static final Rop IF_GTZ_INT = + new Rop(RegOps.IF_GT, Type.VOID, StdTypeList.INT, Rop.BRANCH_IF, + "if-gtz-int"); + + /** {@code x: Object :: if (x == null) goto label} */ + public static final Rop IF_EQZ_OBJECT = + new Rop(RegOps.IF_EQ, Type.VOID, StdTypeList.OBJECT, Rop.BRANCH_IF, + "if-eqz-object"); + + /** {@code x: Object :: if (x != null) goto label} */ + public static final Rop IF_NEZ_OBJECT = + new Rop(RegOps.IF_NE, Type.VOID, StdTypeList.OBJECT, Rop.BRANCH_IF, + "if-nez-object"); + + /** {@code x,y: int :: if (x == y) goto label} */ + public static final Rop IF_EQ_INT = + new Rop(RegOps.IF_EQ, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF, + "if-eq-int"); + + /** {@code x,y: int :: if (x != y) goto label} */ + public static final Rop IF_NE_INT = + new Rop(RegOps.IF_NE, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF, + "if-ne-int"); + + /** {@code x,y: int :: if (x < y) goto label} */ + public static final Rop IF_LT_INT = + new Rop(RegOps.IF_LT, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF, + "if-lt-int"); + + /** {@code x,y: int :: if (x >= y) goto label} */ + public static final Rop IF_GE_INT = + new Rop(RegOps.IF_GE, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF, + "if-ge-int"); + + /** {@code x,y: int :: if (x <= y) goto label} */ + public static final Rop IF_LE_INT = + new Rop(RegOps.IF_LE, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF, + "if-le-int"); + + /** {@code x,y: int :: if (x > y) goto label} */ + public static final Rop IF_GT_INT = + new Rop(RegOps.IF_GT, Type.VOID, StdTypeList.INT_INT, Rop.BRANCH_IF, + "if-gt-int"); + + /** {@code x,y: Object :: if (x == y) goto label} */ + public static final Rop IF_EQ_OBJECT = + new Rop(RegOps.IF_EQ, Type.VOID, StdTypeList.OBJECT_OBJECT, + Rop.BRANCH_IF, "if-eq-object"); + + /** {@code x,y: Object :: if (x != y) goto label} */ + public static final Rop IF_NE_OBJECT = + new Rop(RegOps.IF_NE, Type.VOID, StdTypeList.OBJECT_OBJECT, + Rop.BRANCH_IF, "if-ne-object"); + + /** {@code x: int :: goto switchtable[x]} */ + public static final Rop SWITCH = + new Rop(RegOps.SWITCH, Type.VOID, StdTypeList.INT, Rop.BRANCH_SWITCH, + "switch"); + + /** {@code r,x,y: int :: r = x + y;} */ + public static final Rop ADD_INT = + new Rop(RegOps.ADD, Type.INT, StdTypeList.INT_INT, "add-int"); + + /** {@code r,x,y: long :: r = x + y;} */ + public static final Rop ADD_LONG = + new Rop(RegOps.ADD, Type.LONG, StdTypeList.LONG_LONG, "add-long"); + + /** {@code r,x,y: float :: r = x + y;} */ + public static final Rop ADD_FLOAT = + new Rop(RegOps.ADD, Type.FLOAT, StdTypeList.FLOAT_FLOAT, "add-float"); + + /** {@code r,x,y: double :: r = x + y;} */ + public static final Rop ADD_DOUBLE = + new Rop(RegOps.ADD, Type.DOUBLE, StdTypeList.DOUBLE_DOUBLE, + Rop.BRANCH_NONE, "add-double"); + + /** {@code r,x,y: int :: r = x - y;} */ + public static final Rop SUB_INT = + new Rop(RegOps.SUB, Type.INT, StdTypeList.INT_INT, "sub-int"); + + /** {@code r,x,y: long :: r = x - y;} */ + public static final Rop SUB_LONG = + new Rop(RegOps.SUB, Type.LONG, StdTypeList.LONG_LONG, "sub-long"); + + /** {@code r,x,y: float :: r = x - y;} */ + public static final Rop SUB_FLOAT = + new Rop(RegOps.SUB, Type.FLOAT, StdTypeList.FLOAT_FLOAT, "sub-float"); + + /** {@code r,x,y: double :: r = x - y;} */ + public static final Rop SUB_DOUBLE = + new Rop(RegOps.SUB, Type.DOUBLE, StdTypeList.DOUBLE_DOUBLE, + Rop.BRANCH_NONE, "sub-double"); + + /** {@code r,x,y: int :: r = x * y;} */ + public static final Rop MUL_INT = + new Rop(RegOps.MUL, Type.INT, StdTypeList.INT_INT, "mul-int"); + + /** {@code r,x,y: long :: r = x * y;} */ + public static final Rop MUL_LONG = + new Rop(RegOps.MUL, Type.LONG, StdTypeList.LONG_LONG, "mul-long"); + + /** {@code r,x,y: float :: r = x * y;} */ + public static final Rop MUL_FLOAT = + new Rop(RegOps.MUL, Type.FLOAT, StdTypeList.FLOAT_FLOAT, "mul-float"); + + /** {@code r,x,y: double :: r = x * y;} */ + public static final Rop MUL_DOUBLE = + new Rop(RegOps.MUL, Type.DOUBLE, StdTypeList.DOUBLE_DOUBLE, + Rop.BRANCH_NONE, "mul-double"); + + /** {@code r,x,y: int :: r = x / y;} */ + public static final Rop DIV_INT = + new Rop(RegOps.DIV, Type.INT, StdTypeList.INT_INT, + Exceptions.LIST_Error_ArithmeticException, "div-int"); + + /** {@code r,x,y: long :: r = x / y;} */ + public static final Rop DIV_LONG = + new Rop(RegOps.DIV, Type.LONG, StdTypeList.LONG_LONG, + Exceptions.LIST_Error_ArithmeticException, "div-long"); + + /** {@code r,x,y: float :: r = x / y;} */ + public static final Rop DIV_FLOAT = + new Rop(RegOps.DIV, Type.FLOAT, StdTypeList.FLOAT_FLOAT, "div-float"); + + /** {@code r,x,y: double :: r = x / y;} */ + public static final Rop DIV_DOUBLE = + new Rop(RegOps.DIV, Type.DOUBLE, StdTypeList.DOUBLE_DOUBLE, + "div-double"); + + /** {@code r,x,y: int :: r = x % y;} */ + public static final Rop REM_INT = + new Rop(RegOps.REM, Type.INT, StdTypeList.INT_INT, + Exceptions.LIST_Error_ArithmeticException, "rem-int"); + + /** {@code r,x,y: long :: r = x % y;} */ + public static final Rop REM_LONG = + new Rop(RegOps.REM, Type.LONG, StdTypeList.LONG_LONG, + Exceptions.LIST_Error_ArithmeticException, "rem-long"); + + /** {@code r,x,y: float :: r = x % y;} */ + public static final Rop REM_FLOAT = + new Rop(RegOps.REM, Type.FLOAT, StdTypeList.FLOAT_FLOAT, "rem-float"); + + /** {@code r,x,y: double :: r = x % y;} */ + public static final Rop REM_DOUBLE = + new Rop(RegOps.REM, Type.DOUBLE, StdTypeList.DOUBLE_DOUBLE, + "rem-double"); + + /** {@code r,x: int :: r = -x;} */ + public static final Rop NEG_INT = + new Rop(RegOps.NEG, Type.INT, StdTypeList.INT, "neg-int"); + + /** {@code r,x: long :: r = -x;} */ + public static final Rop NEG_LONG = + new Rop(RegOps.NEG, Type.LONG, StdTypeList.LONG, "neg-long"); + + /** {@code r,x: float :: r = -x;} */ + public static final Rop NEG_FLOAT = + new Rop(RegOps.NEG, Type.FLOAT, StdTypeList.FLOAT, "neg-float"); + + /** {@code r,x: double :: r = -x;} */ + public static final Rop NEG_DOUBLE = + new Rop(RegOps.NEG, Type.DOUBLE, StdTypeList.DOUBLE, "neg-double"); + + /** {@code r,x,y: int :: r = x & y;} */ + public static final Rop AND_INT = + new Rop(RegOps.AND, Type.INT, StdTypeList.INT_INT, "and-int"); + + /** {@code r,x,y: long :: r = x & y;} */ + public static final Rop AND_LONG = + new Rop(RegOps.AND, Type.LONG, StdTypeList.LONG_LONG, "and-long"); + + /** {@code r,x,y: int :: r = x | y;} */ + public static final Rop OR_INT = + new Rop(RegOps.OR, Type.INT, StdTypeList.INT_INT, "or-int"); + + /** {@code r,x,y: long :: r = x | y;} */ + public static final Rop OR_LONG = + new Rop(RegOps.OR, Type.LONG, StdTypeList.LONG_LONG, "or-long"); + + /** {@code r,x,y: int :: r = x ^ y;} */ + public static final Rop XOR_INT = + new Rop(RegOps.XOR, Type.INT, StdTypeList.INT_INT, "xor-int"); + + /** {@code r,x,y: long :: r = x ^ y;} */ + public static final Rop XOR_LONG = + new Rop(RegOps.XOR, Type.LONG, StdTypeList.LONG_LONG, "xor-long"); + + /** {@code r,x,y: int :: r = x << y;} */ + public static final Rop SHL_INT = + new Rop(RegOps.SHL, Type.INT, StdTypeList.INT_INT, "shl-int"); + + /** {@code r,x: long; y: int :: r = x << y;} */ + public static final Rop SHL_LONG = + new Rop(RegOps.SHL, Type.LONG, StdTypeList.LONG_INT, "shl-long"); + + /** {@code r,x,y: int :: r = x >> y;} */ + public static final Rop SHR_INT = + new Rop(RegOps.SHR, Type.INT, StdTypeList.INT_INT, "shr-int"); + + /** {@code r,x: long; y: int :: r = x >> y;} */ + public static final Rop SHR_LONG = + new Rop(RegOps.SHR, Type.LONG, StdTypeList.LONG_INT, "shr-long"); + + /** {@code r,x,y: int :: r = x >>> y;} */ + public static final Rop USHR_INT = + new Rop(RegOps.USHR, Type.INT, StdTypeList.INT_INT, "ushr-int"); + + /** {@code r,x: long; y: int :: r = x >>> y;} */ + public static final Rop USHR_LONG = + new Rop(RegOps.USHR, Type.LONG, StdTypeList.LONG_INT, "ushr-long"); + + /** {@code r,x: int :: r = ~x;} */ + public static final Rop NOT_INT = + new Rop(RegOps.NOT, Type.INT, StdTypeList.INT, "not-int"); + + /** {@code r,x: long :: r = ~x;} */ + public static final Rop NOT_LONG = + new Rop(RegOps.NOT, Type.LONG, StdTypeList.LONG, "not-long"); + + /** {@code r,x,c: int :: r = x + c;} */ + public static final Rop ADD_CONST_INT = + new Rop(RegOps.ADD, Type.INT, StdTypeList.INT, "add-const-int"); + + /** {@code r,x,c: long :: r = x + c;} */ + public static final Rop ADD_CONST_LONG = + new Rop(RegOps.ADD, Type.LONG, StdTypeList.LONG, "add-const-long"); + + /** {@code r,x,c: float :: r = x + c;} */ + public static final Rop ADD_CONST_FLOAT = + new Rop(RegOps.ADD, Type.FLOAT, StdTypeList.FLOAT, "add-const-float"); + + /** {@code r,x,c: double :: r = x + c;} */ + public static final Rop ADD_CONST_DOUBLE = + new Rop(RegOps.ADD, Type.DOUBLE, StdTypeList.DOUBLE, + "add-const-double"); + + /** {@code r,x,c: int :: r = x - c;} */ + public static final Rop SUB_CONST_INT = + new Rop(RegOps.SUB, Type.INT, StdTypeList.INT, "sub-const-int"); + + /** {@code r,x,c: long :: r = x - c;} */ + public static final Rop SUB_CONST_LONG = + new Rop(RegOps.SUB, Type.LONG, StdTypeList.LONG, "sub-const-long"); + + /** {@code r,x,c: float :: r = x - c;} */ + public static final Rop SUB_CONST_FLOAT = + new Rop(RegOps.SUB, Type.FLOAT, StdTypeList.FLOAT, "sub-const-float"); + + /** {@code r,x,c: double :: r = x - c;} */ + public static final Rop SUB_CONST_DOUBLE = + new Rop(RegOps.SUB, Type.DOUBLE, StdTypeList.DOUBLE, + "sub-const-double"); + + /** {@code r,x,c: int :: r = x * c;} */ + public static final Rop MUL_CONST_INT = + new Rop(RegOps.MUL, Type.INT, StdTypeList.INT, "mul-const-int"); + + /** {@code r,x,c: long :: r = x * c;} */ + public static final Rop MUL_CONST_LONG = + new Rop(RegOps.MUL, Type.LONG, StdTypeList.LONG, "mul-const-long"); + + /** {@code r,x,c: float :: r = x * c;} */ + public static final Rop MUL_CONST_FLOAT = + new Rop(RegOps.MUL, Type.FLOAT, StdTypeList.FLOAT, "mul-const-float"); + + /** {@code r,x,c: double :: r = x * c;} */ + public static final Rop MUL_CONST_DOUBLE = + new Rop(RegOps.MUL, Type.DOUBLE, StdTypeList.DOUBLE, + "mul-const-double"); + + /** {@code r,x,c: int :: r = x / c;} */ + public static final Rop DIV_CONST_INT = + new Rop(RegOps.DIV, Type.INT, StdTypeList.INT, + Exceptions.LIST_Error_ArithmeticException, "div-const-int"); + + /** {@code r,x,c: long :: r = x / c;} */ + public static final Rop DIV_CONST_LONG = + new Rop(RegOps.DIV, Type.LONG, StdTypeList.LONG, + Exceptions.LIST_Error_ArithmeticException, "div-const-long"); + + /** {@code r,x,c: float :: r = x / c;} */ + public static final Rop DIV_CONST_FLOAT = + new Rop(RegOps.DIV, Type.FLOAT, StdTypeList.FLOAT, "div-const-float"); + + /** {@code r,x,c: double :: r = x / c;} */ + public static final Rop DIV_CONST_DOUBLE = + new Rop(RegOps.DIV, Type.DOUBLE, StdTypeList.DOUBLE, + "div-const-double"); + + /** {@code r,x,c: int :: r = x % c;} */ + public static final Rop REM_CONST_INT = + new Rop(RegOps.REM, Type.INT, StdTypeList.INT, + Exceptions.LIST_Error_ArithmeticException, "rem-const-int"); + + /** {@code r,x,c: long :: r = x % c;} */ + public static final Rop REM_CONST_LONG = + new Rop(RegOps.REM, Type.LONG, StdTypeList.LONG, + Exceptions.LIST_Error_ArithmeticException, "rem-const-long"); + + /** {@code r,x,c: float :: r = x % c;} */ + public static final Rop REM_CONST_FLOAT = + new Rop(RegOps.REM, Type.FLOAT, StdTypeList.FLOAT, "rem-const-float"); + + /** {@code r,x,c: double :: r = x % c;} */ + public static final Rop REM_CONST_DOUBLE = + new Rop(RegOps.REM, Type.DOUBLE, StdTypeList.DOUBLE, + "rem-const-double"); + + /** {@code r,x,c: int :: r = x & c;} */ + public static final Rop AND_CONST_INT = + new Rop(RegOps.AND, Type.INT, StdTypeList.INT, "and-const-int"); + + /** {@code r,x,c: long :: r = x & c;} */ + public static final Rop AND_CONST_LONG = + new Rop(RegOps.AND, Type.LONG, StdTypeList.LONG, "and-const-long"); + + /** {@code r,x,c: int :: r = x | c;} */ + public static final Rop OR_CONST_INT = + new Rop(RegOps.OR, Type.INT, StdTypeList.INT, "or-const-int"); + + /** {@code r,x,c: long :: r = x | c;} */ + public static final Rop OR_CONST_LONG = + new Rop(RegOps.OR, Type.LONG, StdTypeList.LONG, "or-const-long"); + + /** {@code r,x,c: int :: r = x ^ c;} */ + public static final Rop XOR_CONST_INT = + new Rop(RegOps.XOR, Type.INT, StdTypeList.INT, "xor-const-int"); + + /** {@code r,x,c: long :: r = x ^ c;} */ + public static final Rop XOR_CONST_LONG = + new Rop(RegOps.XOR, Type.LONG, StdTypeList.LONG, "xor-const-long"); + + /** {@code r,x,c: int :: r = x << c;} */ + public static final Rop SHL_CONST_INT = + new Rop(RegOps.SHL, Type.INT, StdTypeList.INT, "shl-const-int"); + + /** {@code r,x: long; c: int :: r = x << c;} */ + public static final Rop SHL_CONST_LONG = + new Rop(RegOps.SHL, Type.LONG, StdTypeList.INT, "shl-const-long"); + + /** {@code r,x,c: int :: r = x >> c;} */ + public static final Rop SHR_CONST_INT = + new Rop(RegOps.SHR, Type.INT, StdTypeList.INT, "shr-const-int"); + + /** {@code r,x: long; c: int :: r = x >> c;} */ + public static final Rop SHR_CONST_LONG = + new Rop(RegOps.SHR, Type.LONG, StdTypeList.INT, "shr-const-long"); + + /** {@code r,x,c: int :: r = x >>> c;} */ + public static final Rop USHR_CONST_INT = + new Rop(RegOps.USHR, Type.INT, StdTypeList.INT, "ushr-const-int"); + + /** {@code r,x: long; c: int :: r = x >>> c;} */ + public static final Rop USHR_CONST_LONG = + new Rop(RegOps.USHR, Type.LONG, StdTypeList.INT, "ushr-const-long"); + + /** {@code r: int; x,y: long :: r = cmp(x, y);} */ + public static final Rop CMPL_LONG = + new Rop(RegOps.CMPL, Type.INT, StdTypeList.LONG_LONG, "cmpl-long"); + + /** {@code r: int; x,y: float :: r = cmpl(x, y);} */ + public static final Rop CMPL_FLOAT = + new Rop(RegOps.CMPL, Type.INT, StdTypeList.FLOAT_FLOAT, "cmpl-float"); + + /** {@code r: int; x,y: double :: r = cmpl(x, y);} */ + public static final Rop CMPL_DOUBLE = + new Rop(RegOps.CMPL, Type.INT, StdTypeList.DOUBLE_DOUBLE, + "cmpl-double"); + + /** {@code r: int; x,y: float :: r = cmpg(x, y);} */ + public static final Rop CMPG_FLOAT = + new Rop(RegOps.CMPG, Type.INT, StdTypeList.FLOAT_FLOAT, "cmpg-float"); + + /** {@code r: int; x,y: double :: r = cmpg(x, y);} */ + public static final Rop CMPG_DOUBLE = + new Rop(RegOps.CMPG, Type.INT, StdTypeList.DOUBLE_DOUBLE, + "cmpg-double"); + + /** {@code r: int; x: long :: r = (int) x} */ + public static final Rop CONV_L2I = + new Rop(RegOps.CONV, Type.INT, StdTypeList.LONG, "conv-l2i"); + + /** {@code r: int; x: float :: r = (int) x} */ + public static final Rop CONV_F2I = + new Rop(RegOps.CONV, Type.INT, StdTypeList.FLOAT, "conv-f2i"); + + /** {@code r: int; x: double :: r = (int) x} */ + public static final Rop CONV_D2I = + new Rop(RegOps.CONV, Type.INT, StdTypeList.DOUBLE, "conv-d2i"); + + /** {@code r: long; x: int :: r = (long) x} */ + public static final Rop CONV_I2L = + new Rop(RegOps.CONV, Type.LONG, StdTypeList.INT, "conv-i2l"); + + /** {@code r: long; x: float :: r = (long) x} */ + public static final Rop CONV_F2L = + new Rop(RegOps.CONV, Type.LONG, StdTypeList.FLOAT, "conv-f2l"); + + /** {@code r: long; x: double :: r = (long) x} */ + public static final Rop CONV_D2L = + new Rop(RegOps.CONV, Type.LONG, StdTypeList.DOUBLE, "conv-d2l"); + + /** {@code r: float; x: int :: r = (float) x} */ + public static final Rop CONV_I2F = + new Rop(RegOps.CONV, Type.FLOAT, StdTypeList.INT, "conv-i2f"); + + /** {@code r: float; x: long :: r = (float) x} */ + public static final Rop CONV_L2F = + new Rop(RegOps.CONV, Type.FLOAT, StdTypeList.LONG, "conv-l2f"); + + /** {@code r: float; x: double :: r = (float) x} */ + public static final Rop CONV_D2F = + new Rop(RegOps.CONV, Type.FLOAT, StdTypeList.DOUBLE, "conv-d2f"); + + /** {@code r: double; x: int :: r = (double) x} */ + public static final Rop CONV_I2D = + new Rop(RegOps.CONV, Type.DOUBLE, StdTypeList.INT, "conv-i2d"); + + /** {@code r: double; x: long :: r = (double) x} */ + public static final Rop CONV_L2D = + new Rop(RegOps.CONV, Type.DOUBLE, StdTypeList.LONG, "conv-l2d"); + + /** {@code r: double; x: float :: r = (double) x} */ + public static final Rop CONV_F2D = + new Rop(RegOps.CONV, Type.DOUBLE, StdTypeList.FLOAT, "conv-f2d"); + + /** + * {@code r,x: int :: r = (x << 24) >> 24} (Java-style + * convert int to byte) + */ + public static final Rop TO_BYTE = + new Rop(RegOps.TO_BYTE, Type.INT, StdTypeList.INT, "to-byte"); + + /** + * {@code r,x: int :: r = x & 0xffff} (Java-style + * convert int to char) + */ + public static final Rop TO_CHAR = + new Rop(RegOps.TO_CHAR, Type.INT, StdTypeList.INT, "to-char"); + + /** + * {@code r,x: int :: r = (x << 16) >> 16} (Java-style + * convert int to short) + */ + public static final Rop TO_SHORT = + new Rop(RegOps.TO_SHORT, Type.INT, StdTypeList.INT, "to-short"); + + /** {@code return void} */ + public static final Rop RETURN_VOID = + new Rop(RegOps.RETURN, Type.VOID, StdTypeList.EMPTY, Rop.BRANCH_RETURN, + "return-void"); + + /** {@code x: int; return x} */ + public static final Rop RETURN_INT = + new Rop(RegOps.RETURN, Type.VOID, StdTypeList.INT, Rop.BRANCH_RETURN, + "return-int"); + + /** {@code x: long; return x} */ + public static final Rop RETURN_LONG = + new Rop(RegOps.RETURN, Type.VOID, StdTypeList.LONG, Rop.BRANCH_RETURN, + "return-long"); + + /** {@code x: float; return x} */ + public static final Rop RETURN_FLOAT = + new Rop(RegOps.RETURN, Type.VOID, StdTypeList.FLOAT, Rop.BRANCH_RETURN, + "return-float"); + + /** {@code x: double; return x} */ + public static final Rop RETURN_DOUBLE = + new Rop(RegOps.RETURN, Type.VOID, StdTypeList.DOUBLE, + Rop.BRANCH_RETURN, "return-double"); + + /** {@code x: Object; return x} */ + public static final Rop RETURN_OBJECT = + new Rop(RegOps.RETURN, Type.VOID, StdTypeList.OBJECT, + Rop.BRANCH_RETURN, "return-object"); + + /** {@code T: any type; r: int; x: T[]; :: r = x.length} */ + public static final Rop ARRAY_LENGTH = + new Rop(RegOps.ARRAY_LENGTH, Type.INT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, "array-length"); + + /** {@code x: Throwable :: throw(x)} */ + public static final Rop THROW = + new Rop(RegOps.THROW, Type.VOID, StdTypeList.THROWABLE, + StdTypeList.THROWABLE, "throw"); + + /** {@code x: Object :: monitorenter(x)} */ + public static final Rop MONITOR_ENTER = + new Rop(RegOps.MONITOR_ENTER, Type.VOID, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, "monitor-enter"); + + /** {@code x: Object :: monitorexit(x)} */ + public static final Rop MONITOR_EXIT = + new Rop(RegOps.MONITOR_EXIT, Type.VOID, StdTypeList.OBJECT, + Exceptions.LIST_Error_Null_IllegalMonitorStateException, + "monitor-exit"); + + /** {@code r,y: int; x: int[] :: r = x[y]} */ + public static final Rop AGET_INT = + new Rop(RegOps.AGET, Type.INT, StdTypeList.INTARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aget-int"); + + /** {@code r: long; x: long[]; y: int :: r = x[y]} */ + public static final Rop AGET_LONG = + new Rop(RegOps.AGET, Type.LONG, StdTypeList.LONGARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aget-long"); + + /** {@code r: float; x: float[]; y: int :: r = x[y]} */ + public static final Rop AGET_FLOAT = + new Rop(RegOps.AGET, Type.FLOAT, StdTypeList.FLOATARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aget-float"); + + /** {@code r: double; x: double[]; y: int :: r = x[y]} */ + public static final Rop AGET_DOUBLE = + new Rop(RegOps.AGET, Type.DOUBLE, StdTypeList.DOUBLEARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aget-double"); + + /** {@code r: Object; x: Object[]; y: int :: r = x[y]} */ + public static final Rop AGET_OBJECT = + new Rop(RegOps.AGET, Type.OBJECT, StdTypeList.OBJECTARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aget-object"); + + /** {@code r: boolean; x: boolean[]; y: int :: r = x[y]} */ + public static final Rop AGET_BOOLEAN = + new Rop(RegOps.AGET, Type.INT, StdTypeList.BOOLEANARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aget-boolean"); + + /** {@code r: byte; x: byte[]; y: int :: r = x[y]} */ + public static final Rop AGET_BYTE = + new Rop(RegOps.AGET, Type.INT, StdTypeList.BYTEARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, "aget-byte"); + + /** {@code r: char; x: char[]; y: int :: r = x[y]} */ + public static final Rop AGET_CHAR = + new Rop(RegOps.AGET, Type.INT, StdTypeList.CHARARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, "aget-char"); + + /** {@code r: short; x: short[]; y: int :: r = x[y]} */ + public static final Rop AGET_SHORT = + new Rop(RegOps.AGET, Type.INT, StdTypeList.SHORTARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aget-short"); + + /** {@code x,z: int; y: int[] :: y[z] = x} */ + public static final Rop APUT_INT = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.INT_INTARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, "aput-int"); + + /** {@code x: long; y: long[]; z: int :: y[z] = x} */ + public static final Rop APUT_LONG = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.LONG_LONGARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, "aput-long"); + + /** {@code x: float; y: float[]; z: int :: y[z] = x} */ + public static final Rop APUT_FLOAT = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.FLOAT_FLOATARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aput-float"); + + /** {@code x: double; y: double[]; z: int :: y[z] = x} */ + public static final Rop APUT_DOUBLE = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.DOUBLE_DOUBLEARR_INT, + Exceptions.LIST_Error_Null_ArrayIndexOutOfBounds, + "aput-double"); + + /** {@code x: Object; y: Object[]; z: int :: y[z] = x} */ + public static final Rop APUT_OBJECT = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.OBJECT_OBJECTARR_INT, + Exceptions.LIST_Error_Null_ArrayIndex_ArrayStore, + "aput-object"); + + /** {@code x: boolean; y: boolean[]; z: int :: y[z] = x} */ + public static final Rop APUT_BOOLEAN = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.INT_BOOLEANARR_INT, + Exceptions.LIST_Error_Null_ArrayIndex_ArrayStore, + "aput-boolean"); + + /** {@code x: byte; y: byte[]; z: int :: y[z] = x} */ + public static final Rop APUT_BYTE = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.INT_BYTEARR_INT, + Exceptions.LIST_Error_Null_ArrayIndex_ArrayStore, "aput-byte"); + + /** {@code x: char; y: char[]; z: int :: y[z] = x} */ + public static final Rop APUT_CHAR = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.INT_CHARARR_INT, + Exceptions.LIST_Error_Null_ArrayIndex_ArrayStore, "aput-char"); + + /** {@code x: short; y: short[]; z: int :: y[z] = x} */ + public static final Rop APUT_SHORT = + new Rop(RegOps.APUT, Type.VOID, StdTypeList.INT_SHORTARR_INT, + Exceptions.LIST_Error_Null_ArrayIndex_ArrayStore, + "aput-short"); + + /** + * {@code T: any non-array object type :: r = + * alloc(T)} (allocate heap space for an object) + */ + public static final Rop NEW_INSTANCE = + new Rop(RegOps.NEW_INSTANCE, Type.OBJECT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "new-instance"); + + /** {@code r: int[]; x: int :: r = new int[x]} */ + public static final Rop NEW_ARRAY_INT = + new Rop(RegOps.NEW_ARRAY, Type.INT_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-int"); + + /** {@code r: long[]; x: int :: r = new long[x]} */ + public static final Rop NEW_ARRAY_LONG = + new Rop(RegOps.NEW_ARRAY, Type.LONG_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-long"); + + /** {@code r: float[]; x: int :: r = new float[x]} */ + public static final Rop NEW_ARRAY_FLOAT = + new Rop(RegOps.NEW_ARRAY, Type.FLOAT_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-float"); + + /** {@code r: double[]; x: int :: r = new double[x]} */ + public static final Rop NEW_ARRAY_DOUBLE = + new Rop(RegOps.NEW_ARRAY, Type.DOUBLE_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-double"); + + /** {@code r: boolean[]; x: int :: r = new boolean[x]} */ + public static final Rop NEW_ARRAY_BOOLEAN = + new Rop(RegOps.NEW_ARRAY, Type.BOOLEAN_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-boolean"); + + /** {@code r: byte[]; x: int :: r = new byte[x]} */ + public static final Rop NEW_ARRAY_BYTE = + new Rop(RegOps.NEW_ARRAY, Type.BYTE_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-byte"); + + /** {@code r: char[]; x: int :: r = new char[x]} */ + public static final Rop NEW_ARRAY_CHAR = + new Rop(RegOps.NEW_ARRAY, Type.CHAR_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-char"); + + /** {@code r: short[]; x: int :: r = new short[x]} */ + public static final Rop NEW_ARRAY_SHORT = + new Rop(RegOps.NEW_ARRAY, Type.SHORT_ARRAY, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-short"); + + /** + * {@code T: any non-array object type; x: Object :: (T) x} (can + * throw {@code ClassCastException}) + */ + public static final Rop CHECK_CAST = + new Rop(RegOps.CHECK_CAST, Type.VOID, StdTypeList.OBJECT, + Exceptions.LIST_Error_ClassCastException, "check-cast"); + + /** + * {@code T: any non-array object type; x: Object :: x instanceof + * T}. Note: This is listed as throwing {@code Error} + * explicitly because the op <i>can</i> throw, but there are no + * other predefined exceptions for it. + */ + public static final Rop INSTANCE_OF = + new Rop(RegOps.INSTANCE_OF, Type.INT, StdTypeList.OBJECT, + Exceptions.LIST_Error, "instance-of"); + + /** + * {@code r: int; x: Object; f: instance field spec of + * type int :: r = x.f} + */ + public static final Rop GET_FIELD_INT = + new Rop(RegOps.GET_FIELD, Type.INT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, "get-field-int"); + + /** + * {@code r: long; x: Object; f: instance field spec of + * type long :: r = x.f} + */ + public static final Rop GET_FIELD_LONG = + new Rop(RegOps.GET_FIELD, Type.LONG, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, "get-field-long"); + + /** + * {@code r: float; x: Object; f: instance field spec of + * type float :: r = x.f} + */ + public static final Rop GET_FIELD_FLOAT = + new Rop(RegOps.GET_FIELD, Type.FLOAT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, + "get-field-float"); + + /** + * {@code r: double; x: Object; f: instance field spec of + * type double :: r = x.f} + */ + public static final Rop GET_FIELD_DOUBLE = + new Rop(RegOps.GET_FIELD, Type.DOUBLE, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, + "get-field-double"); + + /** + * {@code r: Object; x: Object; f: instance field spec of + * type Object :: r = x.f} + */ + public static final Rop GET_FIELD_OBJECT = + new Rop(RegOps.GET_FIELD, Type.OBJECT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, + "get-field-object"); + + /** + * {@code r: boolean; x: Object; f: instance field spec of + * type boolean :: r = x.f} + */ + public static final Rop GET_FIELD_BOOLEAN = + new Rop(RegOps.GET_FIELD, Type.INT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, + "get-field-boolean"); + + /** + * {@code r: byte; x: Object; f: instance field spec of + * type byte :: r = x.f} + */ + public static final Rop GET_FIELD_BYTE = + new Rop(RegOps.GET_FIELD, Type.INT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, + "get-field-byte"); + + /** + * {@code r: char; x: Object; f: instance field spec of + * type char :: r = x.f} + */ + public static final Rop GET_FIELD_CHAR = + new Rop(RegOps.GET_FIELD, Type.INT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, + "get-field-char"); + + /** + * {@code r: short; x: Object; f: instance field spec of + * type short :: r = x.f} + */ + public static final Rop GET_FIELD_SHORT = + new Rop(RegOps.GET_FIELD, Type.INT, StdTypeList.OBJECT, + Exceptions.LIST_Error_NullPointerException, + "get-field-short"); + + /** {@code r: int; f: static field spec of type int :: r = f} */ + public static final Rop GET_STATIC_INT = + new Rop(RegOps.GET_STATIC, Type.INT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-static-int"); + + /** {@code r: long; f: static field spec of type long :: r = f} */ + public static final Rop GET_STATIC_LONG = + new Rop(RegOps.GET_STATIC, Type.LONG, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-static-long"); + + /** {@code r: float; f: static field spec of type float :: r = f} */ + public static final Rop GET_STATIC_FLOAT = + new Rop(RegOps.GET_STATIC, Type.FLOAT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-static-float"); + + /** {@code r: double; f: static field spec of type double :: r = f} */ + public static final Rop GET_STATIC_DOUBLE = + new Rop(RegOps.GET_STATIC, Type.DOUBLE, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-static-double"); + + /** {@code r: Object; f: static field spec of type Object :: r = f} */ + public static final Rop GET_STATIC_OBJECT = + new Rop(RegOps.GET_STATIC, Type.OBJECT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-static-object"); + + /** {@code r: boolean; f: static field spec of type boolean :: r = f} */ + public static final Rop GET_STATIC_BOOLEAN = + new Rop(RegOps.GET_STATIC, Type.INT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-field-boolean"); + + /** {@code r: byte; f: static field spec of type byte :: r = f} */ + public static final Rop GET_STATIC_BYTE = + new Rop(RegOps.GET_STATIC, Type.INT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-field-byte"); + + /** {@code r: char; f: static field spec of type char :: r = f} */ + public static final Rop GET_STATIC_CHAR = + new Rop(RegOps.GET_STATIC, Type.INT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-field-char"); + + /** {@code r: short; f: static field spec of type short :: r = f} */ + public static final Rop GET_STATIC_SHORT = + new Rop(RegOps.GET_STATIC, Type.INT, StdTypeList.EMPTY, + Exceptions.LIST_Error, "get-field-short"); + + /** + * {@code x: int; y: Object; f: instance field spec of type + * int :: y.f = x} + */ + public static final Rop PUT_FIELD_INT = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.INT_OBJECT, + Exceptions.LIST_Error_NullPointerException, "put-field-int"); + + /** + * {@code x: long; y: Object; f: instance field spec of type + * long :: y.f = x} + */ + public static final Rop PUT_FIELD_LONG = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.LONG_OBJECT, + Exceptions.LIST_Error_NullPointerException, "put-field-long"); + + /** + * {@code x: float; y: Object; f: instance field spec of type + * float :: y.f = x} + */ + public static final Rop PUT_FIELD_FLOAT = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.FLOAT_OBJECT, + Exceptions.LIST_Error_NullPointerException, + "put-field-float"); + + /** + * {@code x: double; y: Object; f: instance field spec of type + * double :: y.f = x} + */ + public static final Rop PUT_FIELD_DOUBLE = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.DOUBLE_OBJECT, + Exceptions.LIST_Error_NullPointerException, + "put-field-double"); + + /** + * {@code x: Object; y: Object; f: instance field spec of type + * Object :: y.f = x} + */ + public static final Rop PUT_FIELD_OBJECT = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.OBJECT_OBJECT, + Exceptions.LIST_Error_NullPointerException, + "put-field-object"); + + /** + * {@code x: int; y: Object; f: instance field spec of type + * boolean :: y.f = x} + */ + public static final Rop PUT_FIELD_BOOLEAN = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.INT_OBJECT, + Exceptions.LIST_Error_NullPointerException, + "put-field-boolean"); + + /** + * {@code x: int; y: Object; f: instance field spec of type + * byte :: y.f = x} + */ + public static final Rop PUT_FIELD_BYTE = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.INT_OBJECT, + Exceptions.LIST_Error_NullPointerException, + "put-field-byte"); + + /** + * {@code x: int; y: Object; f: instance field spec of type + * char :: y.f = x} + */ + public static final Rop PUT_FIELD_CHAR = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.INT_OBJECT, + Exceptions.LIST_Error_NullPointerException, + "put-field-char"); + + /** + * {@code x: int; y: Object; f: instance field spec of type + * short :: y.f = x} + */ + public static final Rop PUT_FIELD_SHORT = + new Rop(RegOps.PUT_FIELD, Type.VOID, StdTypeList.INT_OBJECT, + Exceptions.LIST_Error_NullPointerException, + "put-field-short"); + + /** {@code f: static field spec of type int; x: int :: f = x} */ + public static final Rop PUT_STATIC_INT = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.INT, + Exceptions.LIST_Error, "put-static-int"); + + /** {@code f: static field spec of type long; x: long :: f = x} */ + public static final Rop PUT_STATIC_LONG = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.LONG, + Exceptions.LIST_Error, "put-static-long"); + + /** {@code f: static field spec of type float; x: float :: f = x} */ + public static final Rop PUT_STATIC_FLOAT = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.FLOAT, + Exceptions.LIST_Error, "put-static-float"); + + /** {@code f: static field spec of type double; x: double :: f = x} */ + public static final Rop PUT_STATIC_DOUBLE = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.DOUBLE, + Exceptions.LIST_Error, "put-static-double"); + + /** {@code f: static field spec of type Object; x: Object :: f = x} */ + public static final Rop PUT_STATIC_OBJECT = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.OBJECT, + Exceptions.LIST_Error, "put-static-object"); + + /** + * {@code f: static field spec of type boolean; x: boolean :: f = + * x} + */ + public static final Rop PUT_STATIC_BOOLEAN = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.INT, + Exceptions.LIST_Error, "put-static-boolean"); + + /** {@code f: static field spec of type byte; x: byte :: f = x} */ + public static final Rop PUT_STATIC_BYTE = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.INT, + Exceptions.LIST_Error, "put-static-byte"); + + /** {@code f: static field spec of type char; x: char :: f = x} */ + public static final Rop PUT_STATIC_CHAR = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.INT, + Exceptions.LIST_Error, "put-static-char"); + + /** {@code f: static field spec of type short; x: short :: f = x} */ + public static final Rop PUT_STATIC_SHORT = + new Rop(RegOps.PUT_STATIC, Type.VOID, StdTypeList.INT, + Exceptions.LIST_Error, "put-static-short"); + + /** {@code x: Int :: local variable begins in x} */ + public static final Rop MARK_LOCAL_INT = + new Rop (RegOps.MARK_LOCAL, Type.VOID, + StdTypeList.INT, "mark-local-int"); + + /** {@code x: Long :: local variable begins in x} */ + public static final Rop MARK_LOCAL_LONG = + new Rop (RegOps.MARK_LOCAL, Type.VOID, + StdTypeList.LONG, "mark-local-long"); + + /** {@code x: Float :: local variable begins in x} */ + public static final Rop MARK_LOCAL_FLOAT = + new Rop (RegOps.MARK_LOCAL, Type.VOID, + StdTypeList.FLOAT, "mark-local-float"); + + /** {@code x: Double :: local variable begins in x} */ + public static final Rop MARK_LOCAL_DOUBLE = + new Rop (RegOps.MARK_LOCAL, Type.VOID, + StdTypeList.DOUBLE, "mark-local-double"); + + /** {@code x: Object :: local variable begins in x} */ + public static final Rop MARK_LOCAL_OBJECT = + new Rop (RegOps.MARK_LOCAL, Type.VOID, + StdTypeList.OBJECT, "mark-local-object"); + + /** {@code T: Any primitive type; v0..vx: T :: {v0, ..., vx}} */ + public static final Rop FILL_ARRAY_DATA = + new Rop(RegOps.FILL_ARRAY_DATA, Type.VOID, StdTypeList.EMPTY, + "fill-array-data"); + + /** + * Returns the appropriate rop for the given opcode, destination, + * and sources. The result is typically, but not necessarily, a + * shared instance. + * + * <p><b>Note:</b> This method does not do complete error checking on + * its arguments, and so it may return an instance which seemed "right + * enough" even though in actuality the passed arguments don't quite + * match what is returned. TODO: Revisit this issue.</p> + * + * @param opcode the opcode + * @param dest {@code non-null;} destination (result) type, or + * {@link Type#VOID} if none + * @param sources {@code non-null;} list of source types + * @param cst {@code null-ok;} associated constant, if any + * @return {@code non-null;} an appropriate instance + */ + public static Rop ropFor(int opcode, TypeBearer dest, TypeList sources, + Constant cst) { + switch (opcode) { + case RegOps.NOP: return NOP; + case RegOps.MOVE: return opMove(dest); + case RegOps.MOVE_PARAM: return opMoveParam(dest); + case RegOps.MOVE_EXCEPTION: return opMoveException(dest); + case RegOps.CONST: return opConst(dest); + case RegOps.GOTO: return GOTO; + case RegOps.IF_EQ: return opIfEq(sources); + case RegOps.IF_NE: return opIfNe(sources); + case RegOps.IF_LT: return opIfLt(sources); + case RegOps.IF_GE: return opIfGe(sources); + case RegOps.IF_LE: return opIfLe(sources); + case RegOps.IF_GT: return opIfGt(sources); + case RegOps.SWITCH: return SWITCH; + case RegOps.ADD: return opAdd(sources); + case RegOps.SUB: return opSub(sources); + case RegOps.MUL: return opMul(sources); + case RegOps.DIV: return opDiv(sources); + case RegOps.REM: return opRem(sources); + case RegOps.NEG: return opNeg(dest); + case RegOps.AND: return opAnd(sources); + case RegOps.OR: return opOr(sources); + case RegOps.XOR: return opXor(sources); + case RegOps.SHL: return opShl(sources); + case RegOps.SHR: return opShr(sources); + case RegOps.USHR: return opUshr(sources); + case RegOps.NOT: return opNot(dest); + case RegOps.CMPL: return opCmpl(sources.getType(0)); + case RegOps.CMPG: return opCmpg(sources.getType(0)); + case RegOps.CONV: return opConv(dest, sources.getType(0)); + case RegOps.TO_BYTE: return TO_BYTE; + case RegOps.TO_CHAR: return TO_CHAR; + case RegOps.TO_SHORT: return TO_SHORT; + case RegOps.RETURN: { + if (sources.size() == 0) { + return RETURN_VOID; + } + return opReturn(sources.getType(0)); + } + case RegOps.ARRAY_LENGTH: return ARRAY_LENGTH; + case RegOps.THROW: return THROW; + case RegOps.MONITOR_ENTER: return MONITOR_ENTER; + case RegOps.MONITOR_EXIT: return MONITOR_EXIT; + case RegOps.AGET: { + Type source = sources.getType(0); + Type componentType; + if (source == Type.KNOWN_NULL) { + /* + * Treat a known-null as an array of the expected + * result type. + */ + componentType = dest.getType(); + } else { + componentType = source.getComponentType(); + } + return opAget(componentType); + } + case RegOps.APUT: { + Type source = sources.getType(1); + Type componentType; + if (source == Type.KNOWN_NULL) { + /* + * Treat a known-null as an array of the type being + * stored. + */ + componentType = sources.getType(0); + } else { + componentType = source.getComponentType(); + } + return opAput(componentType); + } + case RegOps.NEW_INSTANCE: return NEW_INSTANCE; + case RegOps.NEW_ARRAY: return opNewArray(dest.getType()); + case RegOps.CHECK_CAST: return CHECK_CAST; + case RegOps.INSTANCE_OF: return INSTANCE_OF; + case RegOps.GET_FIELD: return opGetField(dest); + case RegOps.GET_STATIC: return opGetStatic(dest); + case RegOps.PUT_FIELD: return opPutField(sources.getType(0)); + case RegOps.PUT_STATIC: return opPutStatic(sources.getType(0)); + case RegOps.INVOKE_STATIC: { + return opInvokeStatic(((CstMethodRef) cst).getPrototype()); + } + case RegOps.INVOKE_VIRTUAL: { + CstBaseMethodRef cstMeth = (CstMethodRef) cst; + Prototype meth = cstMeth.getPrototype(); + CstType definer = cstMeth.getDefiningClass(); + meth = meth.withFirstParameter(definer.getClassType()); + return opInvokeVirtual(meth); + } + case RegOps.INVOKE_SUPER: { + CstBaseMethodRef cstMeth = (CstMethodRef) cst; + Prototype meth = cstMeth.getPrototype(); + CstType definer = cstMeth.getDefiningClass(); + meth = meth.withFirstParameter(definer.getClassType()); + return opInvokeSuper(meth); + } + case RegOps.INVOKE_DIRECT: { + CstBaseMethodRef cstMeth = (CstMethodRef) cst; + Prototype meth = cstMeth.getPrototype(); + CstType definer = cstMeth.getDefiningClass(); + meth = meth.withFirstParameter(definer.getClassType()); + return opInvokeDirect(meth); + } + case RegOps.INVOKE_INTERFACE: { + CstBaseMethodRef cstMeth = (CstMethodRef) cst; + Prototype meth = cstMeth.getPrototype(); + CstType definer = cstMeth.getDefiningClass(); + meth = meth.withFirstParameter(definer.getClassType()); + return opInvokeInterface(meth); + } + } + + throw new RuntimeException("unknown opcode " + RegOps.opName(opcode)); + } + + /** + * Returns the appropriate {@code move} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} type of value being moved + * @return {@code non-null;} an appropriate instance + */ + public static Rop opMove(TypeBearer type) { + switch (type.getBasicFrameType()) { + case Type.BT_INT: return MOVE_INT; + case Type.BT_LONG: return MOVE_LONG; + case Type.BT_FLOAT: return MOVE_FLOAT; + case Type.BT_DOUBLE: return MOVE_DOUBLE; + case Type.BT_OBJECT: return MOVE_OBJECT; + case Type.BT_ADDR: return MOVE_RETURN_ADDRESS; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code move-param} rop for the + * given type. The result is a shared instance. + * + * @param type {@code non-null;} type of value being moved + * @return {@code non-null;} an appropriate instance + */ + public static Rop opMoveParam(TypeBearer type) { + switch (type.getBasicFrameType()) { + case Type.BT_INT: return MOVE_PARAM_INT; + case Type.BT_LONG: return MOVE_PARAM_LONG; + case Type.BT_FLOAT: return MOVE_PARAM_FLOAT; + case Type.BT_DOUBLE: return MOVE_PARAM_DOUBLE; + case Type.BT_OBJECT: return MOVE_PARAM_OBJECT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code move-exception} rop for the + * given type. The result may be a shared instance. + * + * @param type {@code non-null;} type of the exception + * @return {@code non-null;} an appropriate instance + */ + public static Rop opMoveException(TypeBearer type) { + return new Rop(RegOps.MOVE_EXCEPTION, type.getType(), + StdTypeList.EMPTY, (String) null); + } + + /** + * Returns the appropriate {@code move-result} rop for the + * given type. The result may be a shared instance. + * + * @param type {@code non-null;} type of the parameter + * @return {@code non-null;} an appropriate instance + */ + public static Rop opMoveResult(TypeBearer type) { + return new Rop(RegOps.MOVE_RESULT, type.getType(), + StdTypeList.EMPTY, (String) null); + } + + /** + * Returns the appropriate {@code move-result-pseudo} rop for the + * given type. The result may be a shared instance. + * + * @param type {@code non-null;} type of the parameter + * @return {@code non-null;} an appropriate instance + */ + public static Rop opMoveResultPseudo(TypeBearer type) { + return new Rop(RegOps.MOVE_RESULT_PSEUDO, type.getType(), + StdTypeList.EMPTY, (String) null); + } + + /** + * Returns the appropriate {@code const} rop for the given + * type. The result is a shared instance. + * + * @param type {@code non-null;} type of the constant + * @return {@code non-null;} an appropriate instance + */ + public static Rop opConst(TypeBearer type) { + if (type.getType() == Type.KNOWN_NULL) { + return CONST_OBJECT_NOTHROW; + } + + switch (type.getBasicFrameType()) { + case Type.BT_INT: return CONST_INT; + case Type.BT_LONG: return CONST_LONG; + case Type.BT_FLOAT: return CONST_FLOAT; + case Type.BT_DOUBLE: return CONST_DOUBLE; + case Type.BT_OBJECT: return CONST_OBJECT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code if-eq} rop for the given + * sources. The result is a shared instance. + * + * @param types {@code non-null;} source types + * @return {@code non-null;} an appropriate instance + */ + public static Rop opIfEq(TypeList types) { + return pickIf(types, IF_EQZ_INT, IF_EQZ_OBJECT, + IF_EQ_INT, IF_EQ_OBJECT); + } + + /** + * Returns the appropriate {@code if-ne} rop for the given + * sources. The result is a shared instance. + * + * @param types {@code non-null;} source types + * @return {@code non-null;} an appropriate instance + */ + public static Rop opIfNe(TypeList types) { + return pickIf(types, IF_NEZ_INT, IF_NEZ_OBJECT, + IF_NE_INT, IF_NE_OBJECT); + } + + /** + * Returns the appropriate {@code if-lt} rop for the given + * sources. The result is a shared instance. + * + * @param types {@code non-null;} source types + * @return {@code non-null;} an appropriate instance + */ + public static Rop opIfLt(TypeList types) { + return pickIf(types, IF_LTZ_INT, null, IF_LT_INT, null); + } + + /** + * Returns the appropriate {@code if-ge} rop for the given + * sources. The result is a shared instance. + * + * @param types {@code non-null;} source types + * @return {@code non-null;} an appropriate instance + */ + public static Rop opIfGe(TypeList types) { + return pickIf(types, IF_GEZ_INT, null, IF_GE_INT, null); + } + + /** + * Returns the appropriate {@code if-gt} rop for the given + * sources. The result is a shared instance. + * + * @param types {@code non-null;} source types + * @return {@code non-null;} an appropriate instance + */ + public static Rop opIfGt(TypeList types) { + return pickIf(types, IF_GTZ_INT, null, IF_GT_INT, null); + } + + /** + * Returns the appropriate {@code if-le} rop for the given + * sources. The result is a shared instance. + * + * @param types {@code non-null;} source types + * @return {@code non-null;} an appropriate instance + */ + public static Rop opIfLe(TypeList types) { + return pickIf(types, IF_LEZ_INT, null, IF_LE_INT, null); + } + + /** + * Helper for all the {@code if*}-related methods, which + * checks types and picks one of the four variants, throwing if + * there's a problem. + * + * @param types {@code non-null;} the types + * @param intZ {@code non-null;} the int-to-0 comparison + * @param objZ {@code null-ok;} the object-to-null comparison + * @param intInt {@code non-null;} the int-to-int comparison + * @param objObj {@code non-null;} the object-to-object comparison + * @return {@code non-null;} the appropriate instance + */ + private static Rop pickIf(TypeList types, Rop intZ, Rop objZ, Rop intInt, + Rop objObj) { + switch(types.size()) { + case 1: { + switch (types.getType(0).getBasicFrameType()) { + case Type.BT_INT: { + return intZ; + } + case Type.BT_OBJECT: { + if (objZ != null) { + return objZ; + } + } + } + break; + } + case 2: { + int bt = types.getType(0).getBasicFrameType(); + if (bt == types.getType(1).getBasicFrameType()) { + switch (bt) { + case Type.BT_INT: { + return intInt; + } + case Type.BT_OBJECT: { + if (objObj != null) { + return objObj; + } + } + } + } + break; + } + } + + return throwBadTypes(types); + } + + /** + * Returns the appropriate {@code add} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opAdd(TypeList types) { + return pickBinaryOp(types, ADD_CONST_INT, ADD_CONST_LONG, + ADD_CONST_FLOAT, ADD_CONST_DOUBLE, ADD_INT, + ADD_LONG, ADD_FLOAT, ADD_DOUBLE); + } + + /** + * Returns the appropriate {@code sub} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opSub(TypeList types) { + return pickBinaryOp(types, SUB_CONST_INT, SUB_CONST_LONG, + SUB_CONST_FLOAT, SUB_CONST_DOUBLE, SUB_INT, + SUB_LONG, SUB_FLOAT, SUB_DOUBLE); + } + + /** + * Returns the appropriate {@code mul} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opMul(TypeList types) { + return pickBinaryOp(types, MUL_CONST_INT, MUL_CONST_LONG, + MUL_CONST_FLOAT, MUL_CONST_DOUBLE, MUL_INT, + MUL_LONG, MUL_FLOAT, MUL_DOUBLE); + } + + /** + * Returns the appropriate {@code div} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opDiv(TypeList types) { + return pickBinaryOp(types, DIV_CONST_INT, DIV_CONST_LONG, + DIV_CONST_FLOAT, DIV_CONST_DOUBLE, DIV_INT, + DIV_LONG, DIV_FLOAT, DIV_DOUBLE); + } + + /** + * Returns the appropriate {@code rem} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opRem(TypeList types) { + return pickBinaryOp(types, REM_CONST_INT, REM_CONST_LONG, + REM_CONST_FLOAT, REM_CONST_DOUBLE, REM_INT, + REM_LONG, REM_FLOAT, REM_DOUBLE); + } + + /** + * Returns the appropriate {@code and} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opAnd(TypeList types) { + return pickBinaryOp(types, AND_CONST_INT, AND_CONST_LONG, null, null, + AND_INT, AND_LONG, null, null); + } + + /** + * Returns the appropriate {@code or} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opOr(TypeList types) { + return pickBinaryOp(types, OR_CONST_INT, OR_CONST_LONG, null, null, + OR_INT, OR_LONG, null, null); + } + + /** + * Returns the appropriate {@code xor} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opXor(TypeList types) { + return pickBinaryOp(types, XOR_CONST_INT, XOR_CONST_LONG, null, null, + XOR_INT, XOR_LONG, null, null); + } + + /** + * Returns the appropriate {@code shl} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opShl(TypeList types) { + return pickBinaryOp(types, SHL_CONST_INT, SHL_CONST_LONG, null, null, + SHL_INT, SHL_LONG, null, null); + } + + /** + * Returns the appropriate {@code shr} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opShr(TypeList types) { + return pickBinaryOp(types, SHR_CONST_INT, SHR_CONST_LONG, null, null, + SHR_INT, SHR_LONG, null, null); + } + + /** + * Returns the appropriate {@code ushr} rop for the given + * types. The result is a shared instance. + * + * @param types {@code non-null;} types of the sources + * @return {@code non-null;} an appropriate instance + */ + public static Rop opUshr(TypeList types) { + return pickBinaryOp(types, USHR_CONST_INT, USHR_CONST_LONG, null, null, + USHR_INT, USHR_LONG, null, null); + } + + /** + * Returns the appropriate binary arithmetic rop for the given type + * and arguments. The result is a shared instance. + * + * @param types {@code non-null;} sources of the operation + * @param int1 {@code non-null;} the int-to-constant rop + * @param long1 {@code non-null;} the long-to-constant rop + * @param float1 {@code null-ok;} the float-to-constant rop, if any + * @param double1 {@code null-ok;} the double-to-constant rop, if any + * @param int2 {@code non-null;} the int-to-int rop + * @param long2 {@code non-null;} the long-to-long or long-to-int rop + * @param float2 {@code null-ok;} the float-to-float rop, if any + * @param double2 {@code null-ok;} the double-to-double rop, if any + * @return {@code non-null;} an appropriate instance + */ + private static Rop pickBinaryOp(TypeList types, Rop int1, Rop long1, + Rop float1, Rop double1, Rop int2, + Rop long2, Rop float2, Rop double2) { + int bt1 = types.getType(0).getBasicFrameType(); + Rop result = null; + + switch (types.size()) { + case 1: { + switch(bt1) { + case Type.BT_INT: return int1; + case Type.BT_LONG: return long1; + case Type.BT_FLOAT: result = float1; break; + case Type.BT_DOUBLE: result = double1; break; + } + break; + } + case 2: { + switch(bt1) { + case Type.BT_INT: return int2; + case Type.BT_LONG: return long2; + case Type.BT_FLOAT: result = float2; break; + case Type.BT_DOUBLE: result = double2; break; + } + break; + } + } + + if (result == null) { + return throwBadTypes(types); + } + + return result; + } + + /** + * Returns the appropriate {@code neg} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} type of value being operated on + * @return {@code non-null;} an appropriate instance + */ + public static Rop opNeg(TypeBearer type) { + switch (type.getBasicFrameType()) { + case Type.BT_INT: return NEG_INT; + case Type.BT_LONG: return NEG_LONG; + case Type.BT_FLOAT: return NEG_FLOAT; + case Type.BT_DOUBLE: return NEG_DOUBLE; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code not} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} type of value being operated on + * @return {@code non-null;} an appropriate instance + */ + public static Rop opNot(TypeBearer type) { + switch (type.getBasicFrameType()) { + case Type.BT_INT: return NOT_INT; + case Type.BT_LONG: return NOT_LONG; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code cmpl} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} type of value being compared + * @return {@code non-null;} an appropriate instance + */ + public static Rop opCmpl(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_LONG: return CMPL_LONG; + case Type.BT_FLOAT: return CMPL_FLOAT; + case Type.BT_DOUBLE: return CMPL_DOUBLE; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code cmpg} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} type of value being compared + * @return {@code non-null;} an appropriate instance + */ + public static Rop opCmpg(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_FLOAT: return CMPG_FLOAT; + case Type.BT_DOUBLE: return CMPG_DOUBLE; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code conv} rop for the given types. The + * result is a shared instance. + * + * @param dest {@code non-null;} target value type + * @param source {@code non-null;} source value type + * @return {@code non-null;} an appropriate instance + */ + public static Rop opConv(TypeBearer dest, TypeBearer source) { + int dbt = dest.getBasicFrameType(); + switch (source.getBasicFrameType()) { + case Type.BT_INT: { + switch (dbt) { + case Type.BT_LONG: return CONV_I2L; + case Type.BT_FLOAT: return CONV_I2F; + case Type.BT_DOUBLE: return CONV_I2D; + } + } + case Type.BT_LONG: { + switch (dbt) { + case Type.BT_INT: return CONV_L2I; + case Type.BT_FLOAT: return CONV_L2F; + case Type.BT_DOUBLE: return CONV_L2D; + } + } + case Type.BT_FLOAT: { + switch (dbt) { + case Type.BT_INT: return CONV_F2I; + case Type.BT_LONG: return CONV_F2L; + case Type.BT_DOUBLE: return CONV_F2D; + } + } + case Type.BT_DOUBLE: { + switch (dbt) { + case Type.BT_INT: return CONV_D2I; + case Type.BT_LONG: return CONV_D2L; + case Type.BT_FLOAT: return CONV_D2F; + } + } + } + + return throwBadTypes(StdTypeList.make(dest.getType(), + source.getType())); + } + + /** + * Returns the appropriate {@code return} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} type of value being returned + * @return {@code non-null;} an appropriate instance + */ + public static Rop opReturn(TypeBearer type) { + switch (type.getBasicFrameType()) { + case Type.BT_INT: return RETURN_INT; + case Type.BT_LONG: return RETURN_LONG; + case Type.BT_FLOAT: return RETURN_FLOAT; + case Type.BT_DOUBLE: return RETURN_DOUBLE; + case Type.BT_OBJECT: return RETURN_OBJECT; + case Type.BT_VOID: return RETURN_VOID; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code aget} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} element type of array being accessed + * @return {@code non-null;} an appropriate instance + */ + public static Rop opAget(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_INT: return AGET_INT; + case Type.BT_LONG: return AGET_LONG; + case Type.BT_FLOAT: return AGET_FLOAT; + case Type.BT_DOUBLE: return AGET_DOUBLE; + case Type.BT_OBJECT: return AGET_OBJECT; + case Type.BT_BOOLEAN: return AGET_BOOLEAN; + case Type.BT_BYTE: return AGET_BYTE; + case Type.BT_CHAR: return AGET_CHAR; + case Type.BT_SHORT: return AGET_SHORT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code aput} rop for the given type. The + * result is a shared instance. + * + * @param type {@code non-null;} element type of array being accessed + * @return {@code non-null;} an appropriate instance + */ + public static Rop opAput(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_INT: return APUT_INT; + case Type.BT_LONG: return APUT_LONG; + case Type.BT_FLOAT: return APUT_FLOAT; + case Type.BT_DOUBLE: return APUT_DOUBLE; + case Type.BT_OBJECT: return APUT_OBJECT; + case Type.BT_BOOLEAN: return APUT_BOOLEAN; + case Type.BT_BYTE: return APUT_BYTE; + case Type.BT_CHAR: return APUT_CHAR; + case Type.BT_SHORT: return APUT_SHORT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code new-array} rop for the given + * type. The result is a shared instance. + * + * @param arrayType {@code non-null;} array type of array being created + * @return {@code non-null;} an appropriate instance + */ + public static Rop opNewArray(TypeBearer arrayType) { + Type type = arrayType.getType(); + Type elementType = type.getComponentType(); + + switch (elementType.getBasicType()) { + case Type.BT_INT: return NEW_ARRAY_INT; + case Type.BT_LONG: return NEW_ARRAY_LONG; + case Type.BT_FLOAT: return NEW_ARRAY_FLOAT; + case Type.BT_DOUBLE: return NEW_ARRAY_DOUBLE; + case Type.BT_BOOLEAN: return NEW_ARRAY_BOOLEAN; + case Type.BT_BYTE: return NEW_ARRAY_BYTE; + case Type.BT_CHAR: return NEW_ARRAY_CHAR; + case Type.BT_SHORT: return NEW_ARRAY_SHORT; + case Type.BT_OBJECT: { + return new Rop(RegOps.NEW_ARRAY, type, StdTypeList.INT, + Exceptions.LIST_Error_NegativeArraySizeException, + "new-array-object"); + } + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code filled-new-array} rop for the given + * type. The result may be a shared instance. + * + * @param arrayType {@code non-null;} type of array being created + * @param count {@code >= 0;} number of elements that the array should have + * @return {@code non-null;} an appropriate instance + */ + public static Rop opFilledNewArray(TypeBearer arrayType, int count) { + Type type = arrayType.getType(); + Type elementType = type.getComponentType(); + + if (elementType.isCategory2()) { + return throwBadType(arrayType); + } + + if (count < 0) { + throw new IllegalArgumentException("count < 0"); + } + + StdTypeList sourceTypes = new StdTypeList(count); + + for (int i = 0; i < count; i++) { + sourceTypes.set(i, elementType); + } + + // Note: The resulting rop is considered call-like. + return new Rop(RegOps.FILLED_NEW_ARRAY, + sourceTypes, + Exceptions.LIST_Error); + } + + /** + * Returns the appropriate {@code get-field} rop for the given + * type. The result is a shared instance. + * + * @param type {@code non-null;} type of the field in question + * @return {@code non-null;} an appropriate instance + */ + public static Rop opGetField(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_INT: return GET_FIELD_INT; + case Type.BT_LONG: return GET_FIELD_LONG; + case Type.BT_FLOAT: return GET_FIELD_FLOAT; + case Type.BT_DOUBLE: return GET_FIELD_DOUBLE; + case Type.BT_OBJECT: return GET_FIELD_OBJECT; + case Type.BT_BOOLEAN: return GET_FIELD_BOOLEAN; + case Type.BT_BYTE: return GET_FIELD_BYTE; + case Type.BT_CHAR: return GET_FIELD_CHAR; + case Type.BT_SHORT: return GET_FIELD_SHORT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code put-field} rop for the given + * type. The result is a shared instance. + * + * @param type {@code non-null;} type of the field in question + * @return {@code non-null;} an appropriate instance + */ + public static Rop opPutField(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_INT: return PUT_FIELD_INT; + case Type.BT_LONG: return PUT_FIELD_LONG; + case Type.BT_FLOAT: return PUT_FIELD_FLOAT; + case Type.BT_DOUBLE: return PUT_FIELD_DOUBLE; + case Type.BT_OBJECT: return PUT_FIELD_OBJECT; + case Type.BT_BOOLEAN: return PUT_FIELD_BOOLEAN; + case Type.BT_BYTE: return PUT_FIELD_BYTE; + case Type.BT_CHAR: return PUT_FIELD_CHAR; + case Type.BT_SHORT: return PUT_FIELD_SHORT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code get-static} rop for the given + * type. The result is a shared instance. + * + * @param type {@code non-null;} type of the field in question + * @return {@code non-null;} an appropriate instance + */ + public static Rop opGetStatic(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_INT: return GET_STATIC_INT; + case Type.BT_LONG: return GET_STATIC_LONG; + case Type.BT_FLOAT: return GET_STATIC_FLOAT; + case Type.BT_DOUBLE: return GET_STATIC_DOUBLE; + case Type.BT_OBJECT: return GET_STATIC_OBJECT; + case Type.BT_BOOLEAN: return GET_STATIC_BOOLEAN; + case Type.BT_BYTE: return GET_STATIC_BYTE; + case Type.BT_CHAR: return GET_STATIC_CHAR; + case Type.BT_SHORT: return GET_STATIC_SHORT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code put-static} rop for the given + * type. The result is a shared instance. + * + * @param type {@code non-null;} type of the field in question + * @return {@code non-null;} an appropriate instance + */ + public static Rop opPutStatic(TypeBearer type) { + switch (type.getBasicType()) { + case Type.BT_INT: return PUT_STATIC_INT; + case Type.BT_LONG: return PUT_STATIC_LONG; + case Type.BT_FLOAT: return PUT_STATIC_FLOAT; + case Type.BT_DOUBLE: return PUT_STATIC_DOUBLE; + case Type.BT_OBJECT: return PUT_STATIC_OBJECT; + case Type.BT_BOOLEAN: return PUT_STATIC_BOOLEAN; + case Type.BT_BYTE: return PUT_STATIC_BYTE; + case Type.BT_CHAR: return PUT_STATIC_CHAR; + case Type.BT_SHORT: return PUT_STATIC_SHORT; + } + + return throwBadType(type); + } + + /** + * Returns the appropriate {@code invoke-static} rop for the + * given type. The result is typically a newly-allocated instance. + * + * @param meth {@code non-null;} descriptor of the method + * @return {@code non-null;} an appropriate instance + */ + public static Rop opInvokeStatic(Prototype meth) { + return new Rop(RegOps.INVOKE_STATIC, + meth.getParameterFrameTypes(), + StdTypeList.THROWABLE); + } + + /** + * Returns the appropriate {@code invoke-virtual} rop for the + * given type. The result is typically a newly-allocated instance. + * + * @param meth {@code non-null;} descriptor of the method, including the + * {@code this} parameter + * @return {@code non-null;} an appropriate instance + */ + public static Rop opInvokeVirtual(Prototype meth) { + return new Rop(RegOps.INVOKE_VIRTUAL, + meth.getParameterFrameTypes(), + StdTypeList.THROWABLE); + } + + /** + * Returns the appropriate {@code invoke-super} rop for the + * given type. The result is typically a newly-allocated instance. + * + * @param meth {@code non-null;} descriptor of the method, including the + * {@code this} parameter + * @return {@code non-null;} an appropriate instance + */ + public static Rop opInvokeSuper(Prototype meth) { + return new Rop(RegOps.INVOKE_SUPER, + meth.getParameterFrameTypes(), + StdTypeList.THROWABLE); + } + + /** + * Returns the appropriate {@code invoke-direct} rop for the + * given type. The result is typically a newly-allocated instance. + * + * @param meth {@code non-null;} descriptor of the method, including the + * {@code this} parameter + * @return {@code non-null;} an appropriate instance + */ + public static Rop opInvokeDirect(Prototype meth) { + return new Rop(RegOps.INVOKE_DIRECT, + meth.getParameterFrameTypes(), + StdTypeList.THROWABLE); + } + + /** + * Returns the appropriate {@code invoke-interface} rop for the + * given type. The result is typically a newly-allocated instance. + * + * @param meth {@code non-null;} descriptor of the method, including the + * {@code this} parameter + * @return {@code non-null;} an appropriate instance + */ + public static Rop opInvokeInterface(Prototype meth) { + return new Rop(RegOps.INVOKE_INTERFACE, + meth.getParameterFrameTypes(), + StdTypeList.THROWABLE); + } + + /** + * Returns the appropriate {@code mark-local} rop for the given type. + * The result is a shared instance. + * + * @param type {@code non-null;} type of value being marked + * @return {@code non-null;} an appropriate instance + */ + public static Rop opMarkLocal(TypeBearer type) { + switch (type.getBasicFrameType()) { + case Type.BT_INT: return MARK_LOCAL_INT; + case Type.BT_LONG: return MARK_LOCAL_LONG; + case Type.BT_FLOAT: return MARK_LOCAL_FLOAT; + case Type.BT_DOUBLE: return MARK_LOCAL_DOUBLE; + case Type.BT_OBJECT: return MARK_LOCAL_OBJECT; + } + + return throwBadType(type); + } + + /** + * This class is uninstantiable. + */ + private Rops() { + // This space intentionally left blank. + } + + /** + * Throws the right exception to complain about a bogus type. + * + * @param type {@code non-null;} the bad type + * @return never + */ + private static Rop throwBadType(TypeBearer type) { + throw new IllegalArgumentException("bad type: " + type); + } + + /** + * Throws the right exception to complain about a bogus list of types. + * + * @param types {@code non-null;} the bad types + * @return never + */ + private static Rop throwBadTypes(TypeList types) { + throw new IllegalArgumentException("bad types: " + types); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/SourcePosition.java b/dexgen/src/com/android/dexgen/rop/code/SourcePosition.java new file mode 100644 index 0000000..cd0ea25 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/SourcePosition.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.rop.cst.CstUtf8; +import com.android.dexgen.util.Hex; + +/** + * Information about a source position for code, which includes both a + * line number and original bytecode address. + */ +public final class SourcePosition { + /** {@code non-null;} convenient "no information known" instance */ + public static final SourcePosition NO_INFO = + new SourcePosition(null, -1, -1); + + /** {@code null-ok;} name of the file of origin or {@code null} if unknown */ + private final CstUtf8 sourceFile; + + /** + * {@code >= -1;} the bytecode address, or {@code -1} if that + * information is unknown + */ + private final int address; + + /** + * {@code >= -1;} the line number, or {@code -1} if that + * information is unknown + */ + private final int line; + + /** + * Constructs an instance. + * + * @param sourceFile {@code null-ok;} name of the file of origin or + * {@code null} if unknown + * @param address {@code >= -1;} original bytecode address or {@code -1} + * if unknown + * @param line {@code >= -1;} original line number or {@code -1} if + * unknown + */ + public SourcePosition(CstUtf8 sourceFile, int address, int line) { + if (address < -1) { + throw new IllegalArgumentException("address < -1"); + } + + if (line < -1) { + throw new IllegalArgumentException("line < -1"); + } + + this.sourceFile = sourceFile; + this.address = address; + this.line = line; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(50); + + if (sourceFile != null) { + sb.append(sourceFile.toHuman()); + sb.append(":"); + } + + if (line >= 0) { + sb.append(line); + } + + sb.append('@'); + + if (address < 0) { + sb.append("????"); + } else { + sb.append(Hex.u2(address)); + } + + return sb.toString(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof SourcePosition)) { + return false; + } + + if (this == other) { + return true; + } + + SourcePosition pos = (SourcePosition) other; + + return (address == pos.address) && sameLineAndFile(pos); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return sourceFile.hashCode() + address + line; + } + + /** + * Returns whether the lines match between this instance and + * the one given. + * + * @param other {@code non-null;} the instance to compare to + * @return {@code true} iff the lines match + */ + public boolean sameLine(SourcePosition other) { + return (line == other.line); + } + + /** + * Returns whether the lines and files match between this instance and + * the one given. + * + * @param other {@code non-null;} the instance to compare to + * @return {@code true} iff the lines and files match + */ + public boolean sameLineAndFile(SourcePosition other) { + return (line == other.line) && + ((sourceFile == other.sourceFile) || + ((sourceFile != null) && sourceFile.equals(other.sourceFile))); + } + + /** + * Gets the source file, if known. + * + * @return {@code null-ok;} the source file or {@code null} if unknown + */ + public CstUtf8 getSourceFile() { + return sourceFile; + } + + /** + * Gets the original bytecode address. + * + * @return {@code >= -1;} the address or {@code -1} if unknown + */ + public int getAddress() { + return address; + } + + /** + * Gets the original line number. + * + * @return {@code >= -1;} the original line number or {@code -1} if + * unknown + */ + public int getLine() { + return line; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/SwitchInsn.java b/dexgen/src/com/android/dexgen/rop/code/SwitchInsn.java new file mode 100644 index 0000000..ee4f4b6 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/SwitchInsn.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.rop.type.StdTypeList; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.rop.type.TypeList; +import com.android.dexgen.util.IntList; + +/** + * Instruction which contains switch cases. + */ +public final class SwitchInsn + extends Insn { + /** {@code non-null;} list of switch cases */ + private final IntList cases; + + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param result {@code null-ok;} spec for the result, if any + * @param sources {@code non-null;} specs for all the sources + * @param cases {@code non-null;} list of switch cases + */ + public SwitchInsn(Rop opcode, SourcePosition position, RegisterSpec result, + RegisterSpecList sources, IntList cases) { + super(opcode, position, result, sources); + + if (opcode.getBranchingness() != Rop.BRANCH_SWITCH) { + throw new IllegalArgumentException("bogus branchingness"); + } + + if (cases == null) { + throw new NullPointerException("cases == null"); + } + + this.cases = cases; + } + + /** {@inheritDoc} */ + @Override + public String getInlineString() { + return cases.toString(); + } + + /** {@inheritDoc} */ + @Override + public TypeList getCatches() { + return StdTypeList.EMPTY; + } + + /** {@inheritDoc} */ + @Override + public void accept(Visitor visitor) { + visitor.visitSwitchInsn(this); + } + + /** {@inheritDoc} */ + @Override + public Insn withAddedCatch(Type type) { + throw new UnsupportedOperationException("unsupported"); + } + + /** {@inheritDoc} */ + @Override + public Insn withRegisterOffset(int delta) { + return new SwitchInsn(getOpcode(), getPosition(), + getResult().withOffset(delta), + getSources().withOffset(delta), + cases); + } + + /** + * {@inheritDoc} + * + * <p> SwitchInsn always compares false. The current use for this method + * never encounters {@code SwitchInsn}s + */ + @Override + public boolean contentEquals(Insn b) { + return false; + } + + /** {@inheritDoc} */ + @Override + public Insn withNewRegisters(RegisterSpec result, + RegisterSpecList sources) { + + return new SwitchInsn(getOpcode(), getPosition(), + result, + sources, + cases); + } + + /** + * Gets the list of switch cases. + * + * @return {@code non-null;} the case list + */ + public IntList getCases() { + return cases; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/ThrowingCstInsn.java b/dexgen/src/com/android/dexgen/rop/code/ThrowingCstInsn.java new file mode 100644 index 0000000..7262254 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/ThrowingCstInsn.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.rop.cst.Constant; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.rop.type.TypeList; + +/** + * Instruction which contains an explicit reference to a constant + * and which might throw an exception. + */ +public final class ThrowingCstInsn + extends CstInsn { + /** {@code non-null;} list of exceptions caught */ + private final TypeList catches; + + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param sources {@code non-null;} specs for all the sources + * @param catches {@code non-null;} list of exceptions caught + * @param cst {@code non-null;} the constant + */ + public ThrowingCstInsn(Rop opcode, SourcePosition position, + RegisterSpecList sources, + TypeList catches, Constant cst) { + super(opcode, position, null, sources, cst); + + if (opcode.getBranchingness() != Rop.BRANCH_THROW) { + throw new IllegalArgumentException("bogus branchingness"); + } + + if (catches == null) { + throw new NullPointerException("catches == null"); + } + + this.catches = catches; + } + + /** {@inheritDoc} */ + @Override + public String getInlineString() { + return getConstant().toHuman() + " " + + ThrowingInsn.toCatchString(catches); + } + + /** {@inheritDoc} */ + @Override + public TypeList getCatches() { + return catches; + } + + /** {@inheritDoc} */ + @Override + public void accept(Visitor visitor) { + visitor.visitThrowingCstInsn(this); + } + + /** {@inheritDoc} */ + @Override + public Insn withAddedCatch(Type type) { + return new ThrowingCstInsn(getOpcode(), getPosition(), + getSources(), catches.withAddedType(type), + getConstant()); + } + + /** {@inheritDoc} */ + @Override + public Insn withRegisterOffset(int delta) { + return new ThrowingCstInsn(getOpcode(), getPosition(), + getSources().withOffset(delta), + catches, + getConstant()); + } + + /** {@inheritDoc} */ + @Override + public Insn withNewRegisters(RegisterSpec result, + RegisterSpecList sources) { + + return new ThrowingCstInsn(getOpcode(), getPosition(), + sources, + catches, + getConstant()); + } + + +} diff --git a/dexgen/src/com/android/dexgen/rop/code/ThrowingInsn.java b/dexgen/src/com/android/dexgen/rop/code/ThrowingInsn.java new file mode 100644 index 0000000..24611ad --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/ThrowingInsn.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.rop.type.TypeList; + +/** + * Instruction which possibly throws. The {@code successors} list in the + * basic block an instance of this class is inside corresponds in-order to + * the list of exceptions handled by this instruction, with the + * no-exception case appended as the final target. + */ +public final class ThrowingInsn + extends Insn { + /** {@code non-null;} list of exceptions caught */ + private final TypeList catches; + + /** + * Gets the string form of a register spec list to be used as a catches + * list. + * + * @param catches {@code non-null;} the catches list + * @return {@code non-null;} the string form + */ + public static String toCatchString(TypeList catches) { + StringBuffer sb = new StringBuffer(100); + + sb.append("catch"); + + int sz = catches.size(); + for (int i = 0; i < sz; i++) { + sb.append(" "); + sb.append(catches.getType(i).toHuman()); + } + + return sb.toString(); + } + + /** + * Constructs an instance. + * + * @param opcode {@code non-null;} the opcode + * @param position {@code non-null;} source position + * @param sources {@code non-null;} specs for all the sources + * @param catches {@code non-null;} list of exceptions caught + */ + public ThrowingInsn(Rop opcode, SourcePosition position, + RegisterSpecList sources, + TypeList catches) { + super(opcode, position, null, sources); + + if (opcode.getBranchingness() != Rop.BRANCH_THROW) { + throw new IllegalArgumentException("bogus branchingness"); + } + + if (catches == null) { + throw new NullPointerException("catches == null"); + } + + this.catches = catches; + } + + /** {@inheritDoc} */ + @Override + public String getInlineString() { + return toCatchString(catches); + } + + /** {@inheritDoc} */ + @Override + public TypeList getCatches() { + return catches; + } + + /** {@inheritDoc} */ + @Override + public void accept(Visitor visitor) { + visitor.visitThrowingInsn(this); + } + + /** {@inheritDoc} */ + @Override + public Insn withAddedCatch(Type type) { + return new ThrowingInsn(getOpcode(), getPosition(), + getSources(), catches.withAddedType(type)); + } + + /** {@inheritDoc} */ + @Override + public Insn withRegisterOffset(int delta) { + return new ThrowingInsn(getOpcode(), getPosition(), + getSources().withOffset(delta), + catches); + } + + /** {@inheritDoc} */ + @Override + public Insn withNewRegisters(RegisterSpec result, + RegisterSpecList sources) { + + return new ThrowingInsn(getOpcode(), getPosition(), + sources, + catches); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/code/TranslationAdvice.java b/dexgen/src/com/android/dexgen/rop/code/TranslationAdvice.java new file mode 100644 index 0000000..9edd248 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/code/TranslationAdvice.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.code; + +/** + * Interface for "advice" passed from the late stage of translation back + * to the early stage. This allows for the final target architecture to + * exert its influence early in the translation process without having + * the early stage code be explicitly tied to the target. + */ +public interface TranslationAdvice { + /** + * Returns an indication of whether the target can directly represent an + * instruction with the given opcode operating on the given arguments, + * where the last source argument is used as a constant. (That is, the + * last argument must have a type which indicates it is a known constant.) + * The instruction associated must have exactly two sources. + * + * @param opcode {@code non-null;} the opcode + * @param sourceA {@code non-null;} the first source + * @param sourceB {@code non-null;} the second source + * @return {@code true} iff the target can represent the operation + * using a constant for the last argument + */ + public boolean hasConstantOperation(Rop opcode, + RegisterSpec sourceA, RegisterSpec sourceB); + + /** + * Returns true if the translation target requires the sources of the + * specified opcode to be in order and contiguous (eg, for an invoke-range) + * + * @param opcode {@code non-null;} opcode + * @param sources {@code non-null;} source list + * @return {@code true} iff the target requires the sources to be + * in order and contiguous. + */ + public boolean requiresSourcesInOrder(Rop opcode, RegisterSpecList sources); + + /** + * Gets the maximum register width that can be represented optimally. + * For example, Dex bytecode does not have instruction forms that take + * register numbers larger than 15 for all instructions so + * DexTranslationAdvice returns 15 here. + * + * @return register count noted above + */ + public int getMaxOptimalRegisterCount(); +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/Constant.java b/dexgen/src/com/android/dexgen/rop/cst/Constant.java new file mode 100644 index 0000000..deaa5f4 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/Constant.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +import com.android.dexgen.util.ToHuman; + +/** + * Base class for constants of all sorts. + */ +public abstract class Constant + implements ToHuman, Comparable<Constant> { + /** + * Returns {@code true} if this instance is a category-2 constant, + * meaning it takes up two slots in the constant pool, or + * {@code false} if this instance is category-1. + * + * @return {@code true} iff this instance is category-2 + */ + public abstract boolean isCategory2(); + + /** + * Returns the human name for the particular type of constant + * this instance is. + * + * @return {@code non-null;} the name + */ + public abstract String typeName(); + + /** + * {@inheritDoc} + * + * This compares in class-major and value-minor order. + */ + public final int compareTo(Constant other) { + Class clazz = getClass(); + Class otherClazz = other.getClass(); + + if (clazz != otherClazz) { + return clazz.getName().compareTo(otherClazz.getName()); + } + + return compareTo0(other); + } + + /** + * Compare the values of this and another instance, which are guaranteed + * to be of the same class. Subclasses must implement this. + * + * @param other {@code non-null;} the instance to compare to + * @return {@code -1}, {@code 0}, or {@code 1}, as usual + * for a comparison + */ + protected abstract int compareTo0(Constant other); +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/ConstantPool.java b/dexgen/src/com/android/dexgen/rop/cst/ConstantPool.java new file mode 100644 index 0000000..1ea188a --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/ConstantPool.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +/** + * Interface for constant pools, which are, more or less, just lists of + * {@link Constant} objects. + */ +public interface ConstantPool { + /** + * Get the "size" of the constant pool. This corresponds to the + * class file field {@code constant_pool_count}, and is in fact + * always at least one more than the actual size of the constant pool, + * as element {@code 0} is always invalid. + * + * @return {@code >= 1;} the size + */ + public int size(); + + /** + * Get the {@code n}th entry in the constant pool, which must + * be valid. + * + * @param n {@code n >= 0, n < size();} the constant pool index + * @return {@code non-null;} the corresponding entry + * @throws IllegalArgumentException thrown if {@code n} is + * in-range but invalid + */ + public Constant get(int n); + + /** + * Get the {@code n}th entry in the constant pool, which must + * be valid unless {@code n == 0}, in which case {@code null} + * is returned. + * + * @param n {@code n >= 0, n < size();} the constant pool index + * @return {@code null-ok;} the corresponding entry, if {@code n != 0} + * @throws IllegalArgumentException thrown if {@code n} is + * in-range and non-zero but invalid + */ + public Constant get0Ok(int n); + + /** + * Get the {@code n}th entry in the constant pool, or + * {@code null} if the index is in-range but invalid. In + * particular, {@code null} is returned for index {@code 0} + * as well as the index after any entry which is defined to take up + * two slots (that is, {@code Long} and {@code Double} + * entries). + * + * @param n {@code n >= 0, n < size();} the constant pool index + * @return {@code null-ok;} the corresponding entry, or {@code null} if + * the index is in-range but invalid + */ + public Constant getOrNull(int n); +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstAnnotation.java b/dexgen/src/com/android/dexgen/rop/cst/CstAnnotation.java new file mode 100644 index 0000000..89b4fd8 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstAnnotation.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +import com.android.dexgen.rop.annotation.Annotation; + +/** + * Constant type that represents an annotation. + */ +public final class CstAnnotation extends Constant { + /** {@code non-null;} the actual annotation */ + private final Annotation annotation; + + /** + * Constructs an instance. + * + * @param annotation {@code non-null;} the annotation to hold + */ + public CstAnnotation(Annotation annotation) { + if (annotation == null) { + throw new NullPointerException("annotation == null"); + } + + annotation.throwIfMutable(); + + this.annotation = annotation; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (! (other instanceof CstAnnotation)) { + return false; + } + + return annotation.equals(((CstAnnotation) other).annotation); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return annotation.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + return annotation.compareTo(((CstAnnotation) other).annotation); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return annotation.toString(); + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "annotation"; + } + + /** {@inheritDoc} */ + @Override + public boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + public String toHuman() { + return annotation.toString(); + } + + /** + * Get the underlying annotation. + * + * @return {@code non-null;} the annotation + */ + public Annotation getAnnotation() { + return annotation; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstArray.java b/dexgen/src/com/android/dexgen/rop/cst/CstArray.java new file mode 100644 index 0000000..2fad35e --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstArray.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.util.FixedSizeList; + +/** + * Constant type to represent a fixed array of other constants. The contents + * may be of any type <i>other</i> than {@link CstUtf8}. + */ +public final class CstArray extends Constant { + /** {@code non-null;} the actual list of contents */ + private final List list; + + /** + * Constructs an instance. + * + * @param list {@code non-null;} the actual list of contents + */ + public CstArray(List list) { + if (list == null) { + throw new NullPointerException("list == null"); + } + + list.throwIfMutable(); + + this.list = list; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (! (other instanceof CstArray)) { + return false; + } + + return list.equals(((CstArray) other).list); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return list.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + return list.compareTo(((CstArray) other).list); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return list.toString("array{", ", ", "}"); + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "array"; + } + + /** {@inheritDoc} */ + @Override + public boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + public String toHuman() { + return list.toHuman("{", ", ", "}"); + } + + /** + * Get the underlying list. + * + * @return {@code non-null;} the list + */ + public List getList() { + return list; + } + + /** + * List of {@link Constant} instances. + */ + public static final class List + extends FixedSizeList implements Comparable<List> { + /** + * Constructs an instance. All indices initially contain + * {@code null}. + * + * @param size the size of the list + */ + public List(int size) { + super(size); + } + + /** {@inheritDoc} */ + public int compareTo(List other) { + int thisSize = size(); + int otherSize = other.size(); + int compareSize = (thisSize < otherSize) ? thisSize : otherSize; + + for (int i = 0; i < compareSize; i++) { + Constant thisItem = (Constant) get0(i); + Constant otherItem = (Constant) other.get0(i); + int compare = thisItem.compareTo(otherItem); + if (compare != 0) { + return compare; + } + } + + if (thisSize < otherSize) { + return -1; + } else if (thisSize > otherSize) { + return 1; + } + + return 0; + } + + /** + * Gets the element at the given index. It is an error to call + * this with the index for an element which was never set; if you + * do that, this will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which index + * @return {@code non-null;} element at that index + */ + public Constant get(int n) { + return (Constant) get0(n); + } + + /** + * Sets the element at the given index. + * + * @param n {@code >= 0, < size();} which index + * @param a {@code null-ok;} the element to set at {@code n} + */ + public void set(int n, Constant a) { + if (a instanceof CstUtf8) { + throw new IllegalArgumentException("bad value: " + a); + } + + set0(n, a); + } + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstBaseMethodRef.java b/dexgen/src/com/android/dexgen/rop/cst/CstBaseMethodRef.java new file mode 100644 index 0000000..3914272 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstBaseMethodRef.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +import com.android.dexgen.rop.type.Prototype; +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.rop.type.TypeBearer; + +/** + * Base class for constants of "methodish" type. + * + * <p><b>Note:</b> As a {@link TypeBearer}, this class bears the return type + * of the method.</p> + */ +public abstract class CstBaseMethodRef + extends CstMemberRef { + /** {@code non-null;} the raw prototype for this method */ + private final Prototype prototype; + + /** + * {@code null-ok;} the prototype for this method taken to be an instance + * method, or {@code null} if not yet calculated + */ + private Prototype instancePrototype; + + /** + * Constructs an instance. + * + * @param definingClass {@code non-null;} the type of the defining class + * @param nat {@code non-null;} the name-and-type + */ + /*package*/ CstBaseMethodRef(CstType definingClass, CstNat nat) { + super(definingClass, nat); + + String descriptor = getNat().getDescriptor().getString(); + this.prototype = Prototype.intern(descriptor); + this.instancePrototype = null; + } + + /** + * Gets the raw prototype of this method. This doesn't include a + * {@code this} argument. + * + * @return {@code non-null;} the method prototype + */ + public final Prototype getPrototype() { + return prototype; + } + + /** + * Gets the prototype of this method as either a + * {@code static} or instance method. In the case of a + * {@code static} method, this is the same as the raw + * prototype. In the case of an instance method, this has an + * appropriately-typed {@code this} argument as the first + * one. + * + * @param isStatic whether the method should be considered static + * @return {@code non-null;} the method prototype + */ + public final Prototype getPrototype(boolean isStatic) { + if (isStatic) { + return prototype; + } else { + if (instancePrototype == null) { + Type thisType = getDefiningClass().getClassType(); + instancePrototype = prototype.withFirstParameter(thisType); + } + return instancePrototype; + } + } + + /** {@inheritDoc} */ + @Override + protected final int compareTo0(Constant other) { + int cmp = super.compareTo0(other); + + if (cmp != 0) { + return cmp; + } + + CstBaseMethodRef otherMethod = (CstBaseMethodRef) other; + return prototype.compareTo(otherMethod.prototype); + } + + /** + * {@inheritDoc} + * + * In this case, this method returns the <i>return type</i> of this method. + * + * @return {@code non-null;} the method's return type + */ + public final Type getType() { + return prototype.getReturnType(); + } + + /** + * Gets the number of words of parameters required by this + * method's descriptor. Since instances of this class have no way + * to know if they will be used in a {@code static} or + * instance context, one has to indicate this explicitly as an + * argument. This method is just a convenient shorthand for + * {@code getPrototype().getParameterTypes().getWordCount()}, + * plus {@code 1} if the method is to be treated as an + * instance method. + * + * @param isStatic whether the method should be considered static + * @return {@code >= 0;} the argument word count + */ + public final int getParameterWordCount(boolean isStatic) { + return getPrototype(isStatic).getParameterTypes().getWordCount(); + } + + /** + * Gets whether this is a reference to an instance initialization + * method. This is just a convenient shorthand for + * {@code getNat().isInstanceInit()}. + * + * @return {@code true} iff this is a reference to an + * instance initialization method + */ + public final boolean isInstanceInit() { + return getNat().isInstanceInit(); + } + + /** + * Gets whether this is a reference to a class initialization + * method. This is just a convenient shorthand for + * {@code getNat().isClassInit()}. + * + * @return {@code true} iff this is a reference to an + * instance initialization method + */ + public final boolean isClassInit() { + return getNat().isClassInit(); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstBoolean.java b/dexgen/src/com/android/dexgen/rop/cst/CstBoolean.java new file mode 100644 index 0000000..a7501e3 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstBoolean.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +import com.android.dexgen.rop.type.Type; + +/** + * Constants of type {@code boolean}. + */ +public final class CstBoolean + extends CstLiteral32 { + /** {@code non-null;} instance representing {@code false} */ + public static final CstBoolean VALUE_FALSE = new CstBoolean(false); + + /** {@code non-null;} instance representing {@code true} */ + public static final CstBoolean VALUE_TRUE = new CstBoolean(true); + + /** + * Makes an instance for the given value. This will return an + * already-allocated instance. + * + * @param value the {@code boolean} value + * @return {@code non-null;} the appropriate instance + */ + public static CstBoolean make(boolean value) { + return value ? VALUE_TRUE : VALUE_FALSE; + } + + /** + * Makes an instance for the given {@code int} value. This + * will return an already-allocated instance. + * + * @param value must be either {@code 0} or {@code 1} + * @return {@code non-null;} the appropriate instance + */ + public static CstBoolean make(int value) { + if (value == 0) { + return VALUE_FALSE; + } else if (value == 1) { + return VALUE_TRUE; + } else { + throw new IllegalArgumentException("bogus value: " + value); + } + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param value the {@code boolean} value + */ + private CstBoolean(boolean value) { + super(value ? 1 : 0); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return getValue() ? "boolean{true}" : "boolean{false}"; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.BOOLEAN; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "boolean"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return getValue() ? "true" : "false"; + } + + /** + * Gets the {@code boolean} value. + * + * @return the value + */ + public boolean getValue() { + return (getIntBits() == 0) ? false : true; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstByte.java b/dexgen/src/com/android/dexgen/rop/cst/CstByte.java new file mode 100644 index 0000000..f9b97cb --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstByte.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.util.Hex; + +/** + * Constants of type {@code byte}. + */ +public final class CstByte + extends CstLiteral32 { + /** {@code non-null;} the value {@code 0} as an instance of this class */ + public static final CstByte VALUE_0 = make((byte) 0); + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param value the {@code byte} value + */ + public static CstByte make(byte value) { + return new CstByte(value); + } + + /** + * Makes an instance for the given {@code int} value. This + * may (but does not necessarily) return an already-allocated + * instance. + * + * @param value the value, which must be in range for a {@code byte} + * @return {@code non-null;} the appropriate instance + */ + public static CstByte make(int value) { + byte cast = (byte) value; + + if (cast != value) { + throw new IllegalArgumentException("bogus byte value: " + + value); + } + + return make(cast); + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param value the {@code byte} value + */ + private CstByte(byte value) { + super(value); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + int value = getIntBits(); + return "byte{0x" + Hex.u1(value) + " / " + value + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.BYTE; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "byte"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return Integer.toString(getIntBits()); + } + + /** + * Gets the {@code byte} value. + * + * @return the value + */ + public byte getValue() { + return (byte) getIntBits(); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstChar.java b/dexgen/src/com/android/dexgen/rop/cst/CstChar.java new file mode 100644 index 0000000..d006525 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstChar.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.util.Hex; + +/** + * Constants of type {@code char}. + */ +public final class CstChar + extends CstLiteral32 { + /** {@code non-null;} the value {@code 0} as an instance of this class */ + public static final CstChar VALUE_0 = make((char) 0); + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param value the {@code char} value + */ + public static CstChar make(char value) { + return new CstChar(value); + } + + /** + * Makes an instance for the given {@code int} value. This + * may (but does not necessarily) return an already-allocated + * instance. + * + * @param value the value, which must be in range for a {@code char} + * @return {@code non-null;} the appropriate instance + */ + public static CstChar make(int value) { + char cast = (char) value; + + if (cast != value) { + throw new IllegalArgumentException("bogus char value: " + + value); + } + + return make(cast); + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param value the {@code char} value + */ + private CstChar(char value) { + super(value); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + int value = getIntBits(); + return "char{0x" + Hex.u2(value) + " / " + value + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.CHAR; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "char"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return Integer.toString(getIntBits()); + } + + /** + * Gets the {@code char} value. + * + * @return the value + */ + public char getValue() { + return (char) getIntBits(); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstDouble.java b/dexgen/src/com/android/dexgen/rop/cst/CstDouble.java new file mode 100644 index 0000000..84a53e6 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstDouble.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.util.Hex; + +/** + * Constants of type {@code CONSTANT_Double_info}. + */ +public final class CstDouble + extends CstLiteral64 { + /** {@code non-null;} instance representing {@code 0} */ + public static final CstDouble VALUE_0 = + new CstDouble(Double.doubleToLongBits(0.0)); + + /** {@code non-null;} instance representing {@code 1} */ + public static final CstDouble VALUE_1 = + new CstDouble(Double.doubleToLongBits(1.0)); + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param bits the {@code double} value as {@code long} bits + */ + public static CstDouble make(long bits) { + /* + * Note: Javadoc notwithstanding, this implementation always + * allocates. + */ + return new CstDouble(bits); + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param bits the {@code double} value as {@code long} bits + */ + private CstDouble(long bits) { + super(bits); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + long bits = getLongBits(); + return "double{0x" + Hex.u8(bits) + " / " + + Double.longBitsToDouble(bits) + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.DOUBLE; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "double"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return Double.toString(Double.longBitsToDouble(getLongBits())); + } + + /** + * Gets the {@code double} value. + * + * @return the value + */ + public double getValue() { + return Double.longBitsToDouble(getLongBits()); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstEnumRef.java b/dexgen/src/com/android/dexgen/rop/cst/CstEnumRef.java new file mode 100644 index 0000000..d566946 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstEnumRef.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +import com.android.dexgen.rop.type.Type; + +/** + * Constant type to represent a reference to a particular constant + * value of an enumerated type. + */ +public final class CstEnumRef extends CstMemberRef { + /** {@code null-ok;} the corresponding field ref, lazily initialized */ + private CstFieldRef fieldRef; + + /** + * Constructs an instance. + * + * @param nat {@code non-null;} the name-and-type; the defining class is derived + * from this + */ + public CstEnumRef(CstNat nat) { + super(new CstType(nat.getFieldType()), nat); + + fieldRef = null; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "enum"; + } + + /** + * {@inheritDoc} + * + * <b>Note:</b> This returns the enumerated type. + */ + public Type getType() { + return getDefiningClass().getClassType(); + } + + /** + * Get a {@link CstFieldRef} that corresponds with this instance. + * + * @return {@code non-null;} the corresponding field reference + */ + public CstFieldRef getFieldRef() { + if (fieldRef == null) { + fieldRef = new CstFieldRef(getDefiningClass(), getNat()); + } + + return fieldRef; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstFieldRef.java b/dexgen/src/com/android/dexgen/rop/cst/CstFieldRef.java new file mode 100644 index 0000000..6a6218c --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstFieldRef.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +import com.android.dexgen.rop.type.Type; + +/** + * Constants of type {@code CONSTANT_Fieldref_info}. + */ +public final class CstFieldRef extends CstMemberRef { + /** + * Returns an instance of this class that represents the static + * field which should hold the class corresponding to a given + * primitive type. For example, if given {@link Type#INT}, this + * method returns an instance corresponding to the field + * {@code java.lang.Integer.TYPE}. + * + * @param primitiveType {@code non-null;} the primitive type + * @return {@code non-null;} the corresponding static field + */ + public static CstFieldRef forPrimitiveType(Type primitiveType) { + return new CstFieldRef(CstType.forBoxedPrimitiveType(primitiveType), + CstNat.PRIMITIVE_TYPE_NAT); + } + + /** + * Constructs an instance. + * + * @param definingClass {@code non-null;} the type of the defining class + * @param nat {@code non-null;} the name-and-type + */ + public CstFieldRef(CstType definingClass, CstNat nat) { + super(definingClass, nat); + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "field"; + } + + /** + * Returns the type of this field. + * + * @return {@code non-null;} the field's type + */ + public Type getType() { + return getNat().getFieldType(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + int cmp = super.compareTo0(other); + + if (cmp != 0) { + return cmp; + } + + CstFieldRef otherField = (CstFieldRef) other; + CstUtf8 thisDescriptor = getNat().getDescriptor(); + CstUtf8 otherDescriptor = otherField.getNat().getDescriptor(); + return thisDescriptor.compareTo(otherDescriptor); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstFloat.java b/dexgen/src/com/android/dexgen/rop/cst/CstFloat.java new file mode 100644 index 0000000..6490f8f --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstFloat.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.util.Hex; + +/** + * Constants of type {@code CONSTANT_Float_info}. + */ +public final class CstFloat + extends CstLiteral32 { + /** {@code non-null;} instance representing {@code 0} */ + public static final CstFloat VALUE_0 = make(Float.floatToIntBits(0.0f)); + + /** {@code non-null;} instance representing {@code 1} */ + public static final CstFloat VALUE_1 = make(Float.floatToIntBits(1.0f)); + + /** {@code non-null;} instance representing {@code 2} */ + public static final CstFloat VALUE_2 = make(Float.floatToIntBits(2.0f)); + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param bits the {@code float} value as {@code int} bits + */ + public static CstFloat make(int bits) { + /* + * Note: Javadoc notwithstanding, this implementation always + * allocates. + */ + return new CstFloat(bits); + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param bits the {@code float} value as {@code int} bits + */ + private CstFloat(int bits) { + super(bits); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + int bits = getIntBits(); + return "float{0x" + Hex.u4(bits) + " / " + + Float.intBitsToFloat(bits) + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.FLOAT; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "float"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return Float.toString(Float.intBitsToFloat(getIntBits())); + } + + /** + * Gets the {@code float} value. + * + * @return the value + */ + public float getValue() { + return Float.intBitsToFloat(getIntBits()); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstInteger.java b/dexgen/src/com/android/dexgen/rop/cst/CstInteger.java new file mode 100644 index 0000000..41ef0a6 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstInteger.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.util.Hex; + +/** + * Constants of type {@code CONSTANT_Integer_info}. + */ +public final class CstInteger + extends CstLiteral32 { + /** {@code non-null;} array of cached instances */ + private static final CstInteger[] cache = new CstInteger[511]; + + /** {@code non-null;} instance representing {@code -1} */ + public static final CstInteger VALUE_M1 = make(-1); + + /** {@code non-null;} instance representing {@code 0} */ + public static final CstInteger VALUE_0 = make(0); + + /** {@code non-null;} instance representing {@code 1} */ + public static final CstInteger VALUE_1 = make(1); + + /** {@code non-null;} instance representing {@code 2} */ + public static final CstInteger VALUE_2 = make(2); + + /** {@code non-null;} instance representing {@code 3} */ + public static final CstInteger VALUE_3 = make(3); + + /** {@code non-null;} instance representing {@code 4} */ + public static final CstInteger VALUE_4 = make(4); + + /** {@code non-null;} instance representing {@code 5} */ + public static final CstInteger VALUE_5 = make(5); + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param value the {@code int} value + * @return {@code non-null;} the appropriate instance + */ + public static CstInteger make(int value) { + /* + * Note: No need to synchronize, since we don't make any sort + * of guarantee about ==, and it's okay to overwrite existing + * entries too. + */ + int idx = (value & 0x7fffffff) % cache.length; + CstInteger obj = cache[idx]; + + if ((obj != null) && (obj.getValue() == value)) { + return obj; + } + + obj = new CstInteger(value); + cache[idx] = obj; + return obj; + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param value the {@code int} value + */ + private CstInteger(int value) { + super(value); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + int value = getIntBits(); + return "int{0x" + Hex.u4(value) + " / " + value + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.INT; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "int"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return Integer.toString(getIntBits()); + } + + /** + * Gets the {@code int} value. + * + * @return the value + */ + public int getValue() { + return getIntBits(); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstInterfaceMethodRef.java b/dexgen/src/com/android/dexgen/rop/cst/CstInterfaceMethodRef.java new file mode 100644 index 0000000..c514b84 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstInterfaceMethodRef.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +/** + * Constants of type {@code CONSTANT_InterfaceMethodref_info}. + */ +public final class CstInterfaceMethodRef + extends CstBaseMethodRef { + /** + * {@code null-ok;} normal {@link CstMethodRef} that corresponds to this + * instance, if calculated + */ + private CstMethodRef methodRef; + + /** + * Constructs an instance. + * + * @param definingClass {@code non-null;} the type of the defining class + * @param nat {@code non-null;} the name-and-type + */ + public CstInterfaceMethodRef(CstType definingClass, CstNat nat) { + super(definingClass, nat); + methodRef = null; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "ifaceMethod"; + } + + /** + * Gets a normal (non-interface) {@link CstMethodRef} that corresponds to + * this instance. + * + * @return {@code non-null;} an appropriate instance + */ + public CstMethodRef toMethodRef() { + if (methodRef == null) { + methodRef = new CstMethodRef(getDefiningClass(), getNat()); + } + + return methodRef; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstKnownNull.java b/dexgen/src/com/android/dexgen/rop/cst/CstKnownNull.java new file mode 100644 index 0000000..58d6933 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstKnownNull.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +import com.android.dexgen.rop.type.Type; + +/** + * Constant type to represent a known-{@code null} value. + */ +public final class CstKnownNull extends CstLiteralBits { + /** {@code non-null;} unique instance of this class */ + public static final CstKnownNull THE_ONE = new CstKnownNull(); + + /** + * Constructs an instance. This class is not publicly instantiable. Use + * {@link #THE_ONE}. + */ + private CstKnownNull() { + // This space intentionally left blank. + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + return (other instanceof CstKnownNull); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return 0x4466757a; + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + return 0; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "known-null"; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.KNOWN_NULL; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "known-null"; + } + + /** {@inheritDoc} */ + @Override + public boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + public String toHuman() { + return "null"; + } + + /** {@inheritDoc} */ + @Override + public boolean fitsInInt() { + // See comment in getIntBits(). + return true; + } + + /** + * {@inheritDoc} + * + * As "literal bits," a known-null is always represented as the + * number zero. + */ + @Override + public int getIntBits() { + return 0; + } + + /** + * {@inheritDoc} + * + * As "literal bits," a known-null is always represented as the + * number zero. + */ + @Override + public long getLongBits() { + return 0; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstLiteral32.java b/dexgen/src/com/android/dexgen/rop/cst/CstLiteral32.java new file mode 100644 index 0000000..f7f9199 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstLiteral32.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +/** + * Constants which are literal 32-bit values of some sort. + */ +public abstract class CstLiteral32 + extends CstLiteralBits { + /** the value as {@code int} bits */ + private final int bits; + + /** + * Constructs an instance. + * + * @param bits the value as {@code int} bits + */ + /*package*/ CstLiteral32(int bits) { + this.bits = bits; + } + + /** {@inheritDoc} */ + @Override + public final boolean equals(Object other) { + return (other != null) && + (getClass() == other.getClass()) && + bits == ((CstLiteral32) other).bits; + } + + /** {@inheritDoc} */ + @Override + public final int hashCode() { + return bits; + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + int otherBits = ((CstLiteral32) other).bits; + + if (bits < otherBits) { + return -1; + } else if (bits > otherBits) { + return 1; + } else { + return 0; + } + } + + /** {@inheritDoc} */ + @Override + public final boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + @Override + public final boolean fitsInInt() { + return true; + } + + /** {@inheritDoc} */ + @Override + public final int getIntBits() { + return bits; + } + + /** {@inheritDoc} */ + @Override + public final long getLongBits() { + return (long) bits; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstLiteral64.java b/dexgen/src/com/android/dexgen/rop/cst/CstLiteral64.java new file mode 100644 index 0000000..0bf3152 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstLiteral64.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +/** + * Constants which are literal 64-bit values of some sort. + */ +public abstract class CstLiteral64 + extends CstLiteralBits { + /** the value as {@code long} bits */ + private final long bits; + + /** + * Constructs an instance. + * + * @param bits the value as {@code long} bits + */ + /*package*/ CstLiteral64(long bits) { + this.bits = bits; + } + + /** {@inheritDoc} */ + @Override + public final boolean equals(Object other) { + return (other != null) && + (getClass() == other.getClass()) && + bits == ((CstLiteral64) other).bits; + } + + /** {@inheritDoc} */ + @Override + public final int hashCode() { + return (int) bits ^ (int) (bits >> 32); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + long otherBits = ((CstLiteral64) other).bits; + + if (bits < otherBits) { + return -1; + } else if (bits > otherBits) { + return 1; + } else { + return 0; + } + } + + /** {@inheritDoc} */ + @Override + public final boolean isCategory2() { + return true; + } + + /** {@inheritDoc} */ + @Override + public final boolean fitsInInt() { + return (int) bits == bits; + } + + /** {@inheritDoc} */ + @Override + public final int getIntBits() { + return (int) bits; + } + + /** {@inheritDoc} */ + @Override + public final long getLongBits() { + return bits; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstLiteralBits.java b/dexgen/src/com/android/dexgen/rop/cst/CstLiteralBits.java new file mode 100644 index 0000000..97e8bd1 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstLiteralBits.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +/** + * Constants which are literal bitwise values of some sort. + */ +public abstract class CstLiteralBits + extends TypedConstant { + /** + * Returns whether or not this instance's value may be accurately + * represented as an {@code int}. The rule is that if there + * is an {@code int} which may be sign-extended to yield this + * instance's value, then this method returns {@code true}. + * Otherwise, it returns {@code false}. + * + * @return {@code true} iff this instance fits in an {@code int} + */ + public abstract boolean fitsInInt(); + + /** + * Gets the value as {@code int} bits. If this instance contains + * more bits than fit in an {@code int}, then this returns only + * the low-order bits. + * + * @return the bits + */ + public abstract int getIntBits(); + + /** + * Gets the value as {@code long} bits. If this instance contains + * fewer bits than fit in a {@code long}, then the result of this + * method is the sign extension of the value. + * + * @return the bits + */ + public abstract long getLongBits(); + + /** + * Returns true if this value can fit in 16 bits with sign-extension. + * + * @return true if the sign-extended lower 16 bits are the same as + * the value. + */ + public boolean fitsIn16Bits() { + if (! fitsInInt()) { + return false; + } + + int bits = getIntBits(); + return (short) bits == bits; + } + + /** + * Returns true if this value can fit in 8 bits with sign-extension. + * + * @return true if the sign-extended lower 8 bits are the same as + * the value. + */ + public boolean fitsIn8Bits() { + if (! fitsInInt()) { + return false; + } + + int bits = getIntBits(); + return (byte) bits == bits; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstLong.java b/dexgen/src/com/android/dexgen/rop/cst/CstLong.java new file mode 100644 index 0000000..f737094 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstLong.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.util.Hex; + +/** + * Constants of type {@code CONSTANT_Long_info}. + */ +public final class CstLong + extends CstLiteral64 { + /** {@code non-null;} instance representing {@code 0} */ + public static final CstLong VALUE_0 = make(0); + + /** {@code non-null;} instance representing {@code 1} */ + public static final CstLong VALUE_1 = make(1); + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param value the {@code long} value + */ + public static CstLong make(long value) { + /* + * Note: Javadoc notwithstanding, this implementation always + * allocates. + */ + return new CstLong(value); + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param value the {@code long} value + */ + private CstLong(long value) { + super(value); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + long value = getLongBits(); + return "long{0x" + Hex.u8(value) + " / " + value + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.LONG; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "long"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return Long.toString(getLongBits()); + } + + /** + * Gets the {@code long} value. + * + * @return the value + */ + public long getValue() { + return getLongBits(); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstMemberRef.java b/dexgen/src/com/android/dexgen/rop/cst/CstMemberRef.java new file mode 100644 index 0000000..5abca4b --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstMemberRef.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +/** + * Constants of type {@code CONSTANT_*ref_info}. + */ +public abstract class CstMemberRef extends TypedConstant { + /** {@code non-null;} the type of the defining class */ + private final CstType definingClass; + + /** {@code non-null;} the name-and-type */ + private final CstNat nat; + + /** + * Constructs an instance. + * + * @param definingClass {@code non-null;} the type of the defining class + * @param nat {@code non-null;} the name-and-type + */ + /*package*/ CstMemberRef(CstType definingClass, CstNat nat) { + if (definingClass == null) { + throw new NullPointerException("definingClass == null"); + } + + if (nat == null) { + throw new NullPointerException("nat == null"); + } + + this.definingClass = definingClass; + this.nat = nat; + } + + /** {@inheritDoc} */ + @Override + public final boolean equals(Object other) { + if ((other == null) || (getClass() != other.getClass())) { + return false; + } + + CstMemberRef otherRef = (CstMemberRef) other; + return definingClass.equals(otherRef.definingClass) && + nat.equals(otherRef.nat); + } + + /** {@inheritDoc} */ + @Override + public final int hashCode() { + return (definingClass.hashCode() * 31) ^ nat.hashCode(); + } + + /** + * {@inheritDoc} + * + * <p><b>Note:</b> This implementation just compares the defining + * class and name, and it is up to subclasses to compare the rest + * after calling {@code super.compareTo0()}.</p> + */ + @Override + protected int compareTo0(Constant other) { + CstMemberRef otherMember = (CstMemberRef) other; + int cmp = definingClass.compareTo(otherMember.definingClass); + + if (cmp != 0) { + return cmp; + } + + CstUtf8 thisName = nat.getName(); + CstUtf8 otherName = otherMember.nat.getName(); + + return thisName.compareTo(otherName); + } + + /** {@inheritDoc} */ + @Override + public final String toString() { + return typeName() + '{' + toHuman() + '}'; + } + + /** {@inheritDoc} */ + @Override + public final boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + public final String toHuman() { + return definingClass.toHuman() + '.' + nat.toHuman(); + } + + /** + * Gets the type of the defining class. + * + * @return {@code non-null;} the type of defining class + */ + public final CstType getDefiningClass() { + return definingClass; + } + + /** + * Gets the defining name-and-type. + * + * @return {@code non-null;} the name-and-type + */ + public final CstNat getNat() { + return nat; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstMethodRef.java b/dexgen/src/com/android/dexgen/rop/cst/CstMethodRef.java new file mode 100644 index 0000000..0bf3851 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstMethodRef.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +/** + * Constants of type {@code CONSTANT_Methodref_info}. + */ +public final class CstMethodRef + extends CstBaseMethodRef { + /** + * Constructs an instance. + * + * @param definingClass {@code non-null;} the type of the defining class + * @param nat {@code non-null;} the name-and-type + */ + public CstMethodRef(CstType definingClass, CstNat nat) { + super(definingClass, nat); + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "method"; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstNat.java b/dexgen/src/com/android/dexgen/rop/cst/CstNat.java new file mode 100644 index 0000000..34d2bfc --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstNat.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +import com.android.dexgen.rop.type.Type; + +/** + * Constants of type {@code CONSTANT_NameAndType_info}. + */ +public final class CstNat extends Constant { + /** + * {@code non-null;} the instance for name {@code TYPE} and descriptor + * {@code java.lang.Class}, which is useful when dealing with + * wrapped primitives + */ + public static final CstNat PRIMITIVE_TYPE_NAT = + new CstNat(new CstUtf8("TYPE"), + new CstUtf8("Ljava/lang/Class;")); + + /** {@code non-null;} the name */ + private final CstUtf8 name; + + /** {@code non-null;} the descriptor (type) */ + private final CstUtf8 descriptor; + + /** + * Constructs an instance. + * + * @param name {@code non-null;} the name + * @param descriptor {@code non-null;} the descriptor + */ + public CstNat(CstUtf8 name, CstUtf8 descriptor) { + if (name == null) { + throw new NullPointerException("name == null"); + } + + if (descriptor == null) { + throw new NullPointerException("descriptor == null"); + } + + this.name = name; + this.descriptor = descriptor; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof CstNat)) { + return false; + } + + CstNat otherNat = (CstNat) other; + return name.equals(otherNat.name) && + descriptor.equals(otherNat.descriptor); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return (name.hashCode() * 31) ^ descriptor.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + CstNat otherNat = (CstNat) other; + int cmp = name.compareTo(otherNat.name); + + if (cmp != 0) { + return cmp; + } + + return descriptor.compareTo(otherNat.descriptor); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "nat{" + toHuman() + '}'; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "nat"; + } + + /** {@inheritDoc} */ + @Override + public boolean isCategory2() { + return false; + } + + /** + * Gets the name. + * + * @return {@code non-null;} the name + */ + public CstUtf8 getName() { + return name; + } + + /** + * Gets the descriptor. + * + * @return {@code non-null;} the descriptor + */ + public CstUtf8 getDescriptor() { + return descriptor; + } + + /** + * Returns an unadorned but human-readable version of the name-and-type + * value. + * + * @return {@code non-null;} the human form + */ + public String toHuman() { + return name.toHuman() + ':' + descriptor.toHuman(); + } + + /** + * Gets the field type corresponding to this instance's descriptor. + * This method is only valid to call if the descriptor in fact describes + * a field (and not a method). + * + * @return {@code non-null;} the field type + */ + public Type getFieldType() { + return Type.intern(descriptor.getString()); + } + + /** + * Gets whether this instance has the name of a standard instance + * initialization method. This is just a convenient shorthand for + * {@code getName().getString().equals("<init>")}. + * + * @return {@code true} iff this is a reference to an + * instance initialization method + */ + public final boolean isInstanceInit() { + return name.getString().equals("<init>"); + } + + /** + * Gets whether this instance has the name of a standard class + * initialization method. This is just a convenient shorthand for + * {@code getName().getString().equals("<clinit>")}. + * + * @return {@code true} iff this is a reference to an + * instance initialization method + */ + public final boolean isClassInit() { + return name.getString().equals("<clinit>"); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstShort.java b/dexgen/src/com/android/dexgen/rop/cst/CstShort.java new file mode 100644 index 0000000..c81a589 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstShort.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +import com.android.dexgen.rop.type.Type; +import com.android.dexgen.util.Hex; + +/** + * Constants of type {@code short}. + */ +public final class CstShort + extends CstLiteral32 { + /** {@code non-null;} the value {@code 0} as an instance of this class */ + public static final CstShort VALUE_0 = make((short) 0); + + /** + * Makes an instance for the given value. This may (but does not + * necessarily) return an already-allocated instance. + * + * @param value the {@code short} value + * @return {@code non-null;} the appropriate instance + */ + public static CstShort make(short value) { + return new CstShort(value); + } + + /** + * Makes an instance for the given {@code int} value. This + * may (but does not necessarily) return an already-allocated + * instance. + * + * @param value the value, which must be in range for a {@code short} + * @return {@code non-null;} the appropriate instance + */ + public static CstShort make(int value) { + short cast = (short) value; + + if (cast != value) { + throw new IllegalArgumentException("bogus short value: " + + value); + } + + return make(cast); + } + + /** + * Constructs an instance. This constructor is private; use {@link #make}. + * + * @param value the {@code short} value + */ + private CstShort(short value) { + super(value); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + int value = getIntBits(); + return "short{0x" + Hex.u2(value) + " / " + value + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.SHORT; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "short"; + } + + /** {@inheritDoc} */ + public String toHuman() { + return Integer.toString(getIntBits()); + } + + /** + * Gets the {@code short} value. + * + * @return the value + */ + public short getValue() { + return (short) getIntBits(); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstString.java b/dexgen/src/com/android/dexgen/rop/cst/CstString.java new file mode 100644 index 0000000..a2babf4 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstString.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +import com.android.dexgen.rop.type.Type; + +/** + * Constants of type {@code CONSTANT_String_info}. + */ +public final class CstString + extends TypedConstant { + /** {@code non-null;} the string value */ + private final CstUtf8 string; + + /** + * Constructs an instance. + * + * @param string {@code non-null;} the string value + */ + public CstString(CstUtf8 string) { + if (string == null) { + throw new NullPointerException("string == null"); + } + + this.string = string; + } + + /** + * Constructs an instance. + * + * @param string {@code non-null;} the string value + */ + public CstString(String string) { + this(new CstUtf8(string)); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof CstString)) { + return false; + } + + return string.equals(((CstString) other).string); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return string.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + return string.compareTo(((CstString) other).string); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "string{" + toHuman() + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.STRING; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "string"; + } + + /** {@inheritDoc} */ + @Override + public boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + public String toHuman() { + return string.toQuoted(); + } + + /** + * Gets the string value. + * + * @return {@code non-null;} the string value + */ + public CstUtf8 getString() { + return string; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstType.java b/dexgen/src/com/android/dexgen/rop/cst/CstType.java new file mode 100644 index 0000000..8ad418b --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstType.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +import com.android.dexgen.rop.type.Type; + +import java.util.HashMap; + +/** + * Constants that represent an arbitrary type (reference or primitive). + */ +public final class CstType extends TypedConstant { + /** {@code non-null;} map of interned types */ + private static final HashMap<Type, CstType> interns = + new HashMap<Type, CstType>(100); + + /** {@code non-null;} instance corresponding to the class {@code Object} */ + public static final CstType OBJECT = intern(Type.OBJECT); + + /** {@code non-null;} instance corresponding to the class {@code Boolean} */ + public static final CstType BOOLEAN = intern(Type.BOOLEAN_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Byte} */ + public static final CstType BYTE = intern(Type.BYTE_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Character} */ + public static final CstType CHARACTER = intern(Type.CHARACTER_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Double} */ + public static final CstType DOUBLE = intern(Type.DOUBLE_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Float} */ + public static final CstType FLOAT = intern(Type.FLOAT_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Long} */ + public static final CstType LONG = intern(Type.LONG_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Integer} */ + public static final CstType INTEGER = intern(Type.INTEGER_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Short} */ + public static final CstType SHORT = intern(Type.SHORT_CLASS); + + /** {@code non-null;} instance corresponding to the class {@code Void} */ + public static final CstType VOID = intern(Type.VOID_CLASS); + + /** {@code non-null;} instance corresponding to the type {@code boolean[]} */ + public static final CstType BOOLEAN_ARRAY = intern(Type.BOOLEAN_ARRAY); + + /** {@code non-null;} instance corresponding to the type {@code byte[]} */ + public static final CstType BYTE_ARRAY = intern(Type.BYTE_ARRAY); + + /** {@code non-null;} instance corresponding to the type {@code char[]} */ + public static final CstType CHAR_ARRAY = intern(Type.CHAR_ARRAY); + + /** {@code non-null;} instance corresponding to the type {@code double[]} */ + public static final CstType DOUBLE_ARRAY = intern(Type.DOUBLE_ARRAY); + + /** {@code non-null;} instance corresponding to the type {@code float[]} */ + public static final CstType FLOAT_ARRAY = intern(Type.FLOAT_ARRAY); + + /** {@code non-null;} instance corresponding to the type {@code long[]} */ + public static final CstType LONG_ARRAY = intern(Type.LONG_ARRAY); + + /** {@code non-null;} instance corresponding to the type {@code int[]} */ + public static final CstType INT_ARRAY = intern(Type.INT_ARRAY); + + /** {@code non-null;} instance corresponding to the type {@code short[]} */ + public static final CstType SHORT_ARRAY = intern(Type.SHORT_ARRAY); + + /** {@code non-null;} the underlying type */ + private final Type type; + + /** + * {@code null-ok;} the type descriptor corresponding to this instance, if + * calculated + */ + private CstUtf8 descriptor; + + /** + * Returns an instance of this class that represents the wrapper + * class corresponding to a given primitive type. For example, if + * given {@link Type#INT}, this method returns the class reference + * {@code java.lang.Integer}. + * + * @param primitiveType {@code non-null;} the primitive type + * @return {@code non-null;} the corresponding wrapper class + */ + public static CstType forBoxedPrimitiveType(Type primitiveType) { + switch (primitiveType.getBasicType()) { + case Type.BT_BOOLEAN: return BOOLEAN; + case Type.BT_BYTE: return BYTE; + case Type.BT_CHAR: return CHARACTER; + case Type.BT_DOUBLE: return DOUBLE; + case Type.BT_FLOAT: return FLOAT; + case Type.BT_INT: return INTEGER; + case Type.BT_LONG: return LONG; + case Type.BT_SHORT: return SHORT; + case Type.BT_VOID: return VOID; + } + + throw new IllegalArgumentException("not primitive: " + primitiveType); + } + + /** + * Returns an interned instance of this class for the given type. + * + * @param type {@code non-null;} the underlying type + * @return {@code non-null;} an appropriately-constructed instance + */ + public static CstType intern(Type type) { + CstType cst = interns.get(type); + + if (cst == null) { + cst = new CstType(type); + interns.put(type, cst); + } + + return cst; + } + + /** + * Returns an interned instance of this class for the given + * {@code Class} instance. + * + * @param clazz {@code non-null;} the underlying {@code Class} object + * @return {@code non-null;} an appropriately-constructed instance + */ + public static CstType intern(Class clazz) { + return intern(Type.intern(clazz)); + } + + /** + * Constructs an instance. + * + * @param type {@code non-null;} the underlying type + */ + public CstType(Type type) { + if (type == null) { + throw new NullPointerException("type == null"); + } + + if (type == type.KNOWN_NULL) { + throw new UnsupportedOperationException( + "KNOWN_NULL is not representable"); + } + + this.type = type; + this.descriptor = null; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof CstType)) { + return false; + } + + return type == ((CstType) other).type; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return type.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + String thisDescriptor = type.getDescriptor(); + String otherDescriptor = ((CstType) other).type.getDescriptor(); + return thisDescriptor.compareTo(otherDescriptor); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "type{" + toHuman() + '}'; + } + + /** {@inheritDoc} */ + public Type getType() { + return Type.CLASS; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "type"; + } + + /** {@inheritDoc} */ + @Override + public boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + public String toHuman() { + return type.toHuman(); + } + + /** + * Gets the underlying type (as opposed to the type corresponding + * to this instance as a constant, which is always + * {@code Class}). + * + * @return {@code non-null;} the type corresponding to the name + */ + public Type getClassType() { + return type; + } + + /** + * Gets the type descriptor for this instance. + * + * @return {@code non-null;} the descriptor + */ + public CstUtf8 getDescriptor() { + if (descriptor == null) { + descriptor = new CstUtf8(type.getDescriptor()); + } + + return descriptor; + } +}
\ No newline at end of file diff --git a/dexgen/src/com/android/dexgen/rop/cst/CstUtf8.java b/dexgen/src/com/android/dexgen/rop/cst/CstUtf8.java new file mode 100644 index 0000000..161a57b --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/CstUtf8.java @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +import com.android.dexgen.util.ByteArray; +import com.android.dexgen.util.Hex; + +/** + * Constants of type {@code CONSTANT_Utf8_info}. + */ +public final class CstUtf8 extends Constant { + /** + * {@code non-null;} instance representing {@code ""}, that is, the + * empty string + */ + public static final CstUtf8 EMPTY_STRING = new CstUtf8(""); + + /** {@code non-null;} the UTF-8 value as a string */ + private final String string; + + /** {@code non-null;} the UTF-8 value as bytes */ + private final ByteArray bytes; + + /** + * Converts a string into its Java-style UTF-8 form. Java-style UTF-8 + * differs from normal UTF-8 in the handling of character '\0' and + * surrogate pairs. + * + * @param string {@code non-null;} the string to convert + * @return {@code non-null;} the UTF-8 bytes for it + */ + public static byte[] stringToUtf8Bytes(String string) { + int len = string.length(); + byte[] bytes = new byte[len * 3]; // Avoid having to reallocate. + int outAt = 0; + + for (int i = 0; i < len; i++) { + char c = string.charAt(i); + if ((c != 0) && (c < 0x80)) { + bytes[outAt] = (byte) c; + outAt++; + } else if (c < 0x800) { + bytes[outAt] = (byte) (((c >> 6) & 0x1f) | 0xc0); + bytes[outAt + 1] = (byte) ((c & 0x3f) | 0x80); + outAt += 2; + } else { + bytes[outAt] = (byte) (((c >> 12) & 0x0f) | 0xe0); + bytes[outAt + 1] = (byte) (((c >> 6) & 0x3f) | 0x80); + bytes[outAt + 2] = (byte) ((c & 0x3f) | 0x80); + outAt += 3; + } + } + + byte[] result = new byte[outAt]; + System.arraycopy(bytes, 0, result, 0, outAt); + return result; + } + + /** + * Converts an array of UTF-8 bytes into a string. + * + * @param bytes {@code non-null;} the bytes to convert + * @return {@code non-null;} the converted string + */ + public static String utf8BytesToString(ByteArray bytes) { + int length = bytes.size(); + char[] chars = new char[length]; // This is sized to avoid a realloc. + int outAt = 0; + + for (int at = 0; length > 0; /*at*/) { + int v0 = bytes.getUnsignedByte(at); + char out; + switch (v0 >> 4) { + case 0x00: case 0x01: case 0x02: case 0x03: + case 0x04: case 0x05: case 0x06: case 0x07: { + // 0XXXXXXX -- single-byte encoding + length--; + if (v0 == 0) { + // A single zero byte is illegal. + return throwBadUtf8(v0, at); + } + out = (char) v0; + at++; + break; + } + case 0x0c: case 0x0d: { + // 110XXXXX -- two-byte encoding + length -= 2; + if (length < 0) { + return throwBadUtf8(v0, at); + } + int v1 = bytes.getUnsignedByte(at + 1); + if ((v1 & 0xc0) != 0x80) { + return throwBadUtf8(v1, at + 1); + } + int value = ((v0 & 0x1f) << 6) | (v1 & 0x3f); + if ((value != 0) && (value < 0x80)) { + /* + * This should have been represented with + * one-byte encoding. + */ + return throwBadUtf8(v1, at + 1); + } + out = (char) value; + at += 2; + break; + } + case 0x0e: { + // 1110XXXX -- three-byte encoding + length -= 3; + if (length < 0) { + return throwBadUtf8(v0, at); + } + int v1 = bytes.getUnsignedByte(at + 1); + if ((v1 & 0xc0) != 0x80) { + return throwBadUtf8(v1, at + 1); + } + int v2 = bytes.getUnsignedByte(at + 2); + if ((v1 & 0xc0) != 0x80) { + return throwBadUtf8(v2, at + 2); + } + int value = ((v0 & 0x0f) << 12) | ((v1 & 0x3f) << 6) | + (v2 & 0x3f); + if (value < 0x800) { + /* + * This should have been represented with one- or + * two-byte encoding. + */ + return throwBadUtf8(v2, at + 2); + } + out = (char) value; + at += 3; + break; + } + default: { + // 10XXXXXX, 1111XXXX -- illegal + return throwBadUtf8(v0, at); + } + } + chars[outAt] = out; + outAt++; + } + + return new String(chars, 0, outAt); + } + + /** + * Helper for {@link #utf8BytesToString}, which throws the right + * exception for a bogus utf-8 byte. + * + * @param value the byte value + * @param offset the file offset + * @return never + * @throws IllegalArgumentException always thrown + */ + private static String throwBadUtf8(int value, int offset) { + throw new IllegalArgumentException("bad utf-8 byte " + Hex.u1(value) + + " at offset " + Hex.u4(offset)); + } + + /** + * Constructs an instance from a {@code String}. + * + * @param string {@code non-null;} the UTF-8 value as a string + */ + public CstUtf8(String string) { + if (string == null) { + throw new NullPointerException("string == null"); + } + + this.string = string.intern(); + this.bytes = new ByteArray(stringToUtf8Bytes(string)); + } + + /** + * Constructs an instance from some UTF-8 bytes. + * + * @param bytes {@code non-null;} array of the UTF-8 bytes + */ + public CstUtf8(ByteArray bytes) { + if (bytes == null) { + throw new NullPointerException("bytes == null"); + } + + this.bytes = bytes; + this.string = utf8BytesToString(bytes).intern(); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (!(other instanceof CstUtf8)) { + return false; + } + + return string.equals(((CstUtf8) other).string); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return string.hashCode(); + } + + /** {@inheritDoc} */ + @Override + protected int compareTo0(Constant other) { + return string.compareTo(((CstUtf8) other).string); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return "utf8{\"" + toHuman() + "\"}"; + } + + /** {@inheritDoc} */ + @Override + public String typeName() { + return "utf8"; + } + + /** {@inheritDoc} */ + @Override + public boolean isCategory2() { + return false; + } + + /** {@inheritDoc} */ + public String toHuman() { + int len = string.length(); + StringBuilder sb = new StringBuilder(len * 3 / 2); + + for (int i = 0; i < len; i++) { + char c = string.charAt(i); + if ((c >= ' ') && (c < 0x7f)) { + if ((c == '\'') || (c == '\"') || (c == '\\')) { + sb.append('\\'); + } + sb.append(c); + } else if (c <= 0x7f) { + switch (c) { + case '\n': sb.append("\\n"); break; + case '\r': sb.append("\\r"); break; + case '\t': sb.append("\\t"); break; + default: { + /* + * Represent the character as an octal escape. + * If the next character is a valid octal + * digit, disambiguate by using the + * three-digit form. + */ + char nextChar = + (i < (len - 1)) ? string.charAt(i + 1) : 0; + boolean displayZero = + (nextChar >= '0') && (nextChar <= '7'); + sb.append('\\'); + for (int shift = 6; shift >= 0; shift -= 3) { + char outChar = (char) (((c >> shift) & 7) + '0'); + if ((outChar != '0') || displayZero) { + sb.append(outChar); + displayZero = true; + } + } + if (! displayZero) { + // Ironic edge case: The original value was 0. + sb.append('0'); + } + break; + } + } + } else { + sb.append("\\u"); + sb.append(Character.forDigit(c >> 12, 16)); + sb.append(Character.forDigit((c >> 8) & 0x0f, 16)); + sb.append(Character.forDigit((c >> 4) & 0x0f, 16)); + sb.append(Character.forDigit(c & 0x0f, 16)); + } + } + + return sb.toString(); + } + + /** + * Gets the value as a human-oriented string, surrounded by double + * quotes. + * + * @return {@code non-null;} the quoted string + */ + public String toQuoted() { + return '\"' + toHuman() + '\"'; + } + + /** + * Gets the value as a human-oriented string, surrounded by double + * quotes, but ellipsizes the result if it is longer than the given + * maximum length + * + * @param maxLength {@code >= 5;} the maximum length of the string to return + * @return {@code non-null;} the quoted string + */ + public String toQuoted(int maxLength) { + String string = toHuman(); + int length = string.length(); + String ellipses; + + if (length <= (maxLength - 2)) { + ellipses = ""; + } else { + string = string.substring(0, maxLength - 5); + ellipses = "..."; + } + + return '\"' + string + ellipses + '\"'; + } + + /** + * Gets the UTF-8 value as a string. + * The returned string is always already interned. + * + * @return {@code non-null;} the UTF-8 value as a string + */ + public String getString() { + return string; + } + + /** + * Gets the UTF-8 value as UTF-8 encoded bytes. + * + * @return {@code non-null;} an array of the UTF-8 bytes + */ + public ByteArray getBytes() { + return bytes; + } + + /** + * Gets the size of this instance as UTF-8 code points. That is, + * get the number of bytes in the UTF-8 encoding of this instance. + * + * @return {@code >= 0;} the UTF-8 size + */ + public int getUtf8Size() { + return bytes.size(); + } + + /** + * Gets the size of this instance as UTF-16 code points. That is, + * get the number of 16-bit chars in the UTF-16 encoding of this + * instance. This is the same as the {@code length} of the + * Java {@code String} representation of this instance. + * + * @return {@code >= 0;} the UTF-16 size + */ + public int getUtf16Size() { + return string.length(); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/StdConstantPool.java b/dexgen/src/com/android/dexgen/rop/cst/StdConstantPool.java new file mode 100644 index 0000000..5f1728a --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/StdConstantPool.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +import com.android.dexgen.util.ExceptionWithContext; +import com.android.dexgen.util.Hex; +import com.android.dexgen.util.MutabilityControl; + +/** + * Standard implementation of {@link ConstantPool}, which directly stores + * an array of {@link Constant} objects and can be made immutable. + */ +public final class StdConstantPool + extends MutabilityControl implements ConstantPool { + /** {@code non-null;} array of entries */ + private final Constant[] entries; + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the pool; this corresponds to the + * class file field {@code constant_pool_count}, and is in fact + * always at least one more than the actual size of the constant pool, + * as element {@code 0} is always invalid. + */ + public StdConstantPool(int size) { + super(size > 1); + + if (size < 1) { + throw new IllegalArgumentException("size < 1"); + } + + entries = new Constant[size]; + } + + /** {@inheritDoc} */ + public int size() { + return entries.length; + } + + /** {@inheritDoc} */ + public Constant getOrNull(int n) { + try { + return entries[n]; + } catch (IndexOutOfBoundsException ex) { + // Translate the exception. + return throwInvalid(n); + } + } + + /** {@inheritDoc} */ + public Constant get0Ok(int n) { + if (n == 0) { + return null; + } + + return get(n); + } + + /** {@inheritDoc} */ + public Constant get(int n) { + try { + Constant result = entries[n]; + + if (result == null) { + throwInvalid(n); + } + + return result; + } catch (IndexOutOfBoundsException ex) { + // Translate the exception. + return throwInvalid(n); + } + } + + /** + * Sets the entry at the given index. + * + * @param n {@code >= 1, < size();} which entry + * @param cst {@code null-ok;} the constant to store + */ + public void set(int n, Constant cst) { + throwIfImmutable(); + + boolean cat2 = (cst != null) && cst.isCategory2(); + + if (n < 1) { + throw new IllegalArgumentException("n < 1"); + } + + if (cat2) { + // Storing a category-2 entry nulls out the next index. + if (n == (entries.length - 1)) { + throw new IllegalArgumentException("(n == size - 1) && " + + "cst.isCategory2()"); + } + entries[n + 1] = null; + } + + if ((cst != null) && (entries[n] == null)) { + /* + * Overwriting the second half of a category-2 entry nulls out + * the first half. + */ + Constant prev = entries[n - 1]; + if ((prev != null) && prev.isCategory2()) { + entries[n - 1] = null; + } + } + + entries[n] = cst; + } + + /** + * Throws the right exception for an invalid cpi. + * + * @param idx the bad cpi + * @return never + * @throws ExceptionWithContext always thrown + */ + private static Constant throwInvalid(int idx) { + throw new ExceptionWithContext("invalid constant pool index " + + Hex.u2(idx)); + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/TypedConstant.java b/dexgen/src/com/android/dexgen/rop/cst/TypedConstant.java new file mode 100644 index 0000000..251f057 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/TypedConstant.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +import com.android.dexgen.rop.type.TypeBearer; + +/** + * Base class for constants which implement {@link TypeBearer}. + */ +public abstract class TypedConstant + extends Constant implements TypeBearer { + /** + * {@inheritDoc} + * + * This implentation always returns {@code this}. + */ + public final TypeBearer getFrameType() { + return this; + } + + /** {@inheritDoc} */ + public final int getBasicType() { + return getType().getBasicType(); + } + + /** {@inheritDoc} */ + public final int getBasicFrameType() { + return getType().getBasicFrameType(); + } + + /** {@inheritDoc} */ + public final boolean isConstant() { + return true; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/cst/Zeroes.java b/dexgen/src/com/android/dexgen/rop/cst/Zeroes.java new file mode 100644 index 0000000..28da7db --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/cst/Zeroes.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.cst; + +import com.android.dexgen.rop.type.Type; + +/** + * Utility for turning types into zeroes. + */ +public final class Zeroes { + /** + * This class is uninstantiable. + */ + private Zeroes() { + // This space intentionally left blank. + } + + /** + * Gets the "zero" (or {@code null}) value for the given type. + * + * @param type {@code non-null;} the type in question + * @return {@code non-null;} its "zero" value + */ + public static Constant zeroFor(Type type) { + switch (type.getBasicType()) { + case Type.BT_BOOLEAN: return CstBoolean.VALUE_FALSE; + case Type.BT_BYTE: return CstByte.VALUE_0; + case Type.BT_CHAR: return CstChar.VALUE_0; + case Type.BT_DOUBLE: return CstDouble.VALUE_0; + case Type.BT_FLOAT: return CstFloat.VALUE_0; + case Type.BT_INT: return CstInteger.VALUE_0; + case Type.BT_LONG: return CstLong.VALUE_0; + case Type.BT_SHORT: return CstShort.VALUE_0; + case Type.BT_OBJECT: return CstKnownNull.THE_ONE; + default: { + throw new UnsupportedOperationException("no zero for type: " + + type.toHuman()); + } + } + } +} diff --git a/dexgen/src/com/android/dexgen/rop/type/Prototype.java b/dexgen/src/com/android/dexgen/rop/type/Prototype.java new file mode 100644 index 0000000..33fa918 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/type/Prototype.java @@ -0,0 +1,396 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.type; + +import java.util.HashMap; + +/** + * Representation of a method decriptor. Instances of this class are + * generally interned and may be usefully compared with each other + * using {@code ==}. + */ +public final class Prototype implements Comparable<Prototype> { + /** {@code non-null;} intern table mapping string descriptors to instances */ + private static final HashMap<String, Prototype> internTable = + new HashMap<String, Prototype>(500); + + /** {@code non-null;} method descriptor */ + private final String descriptor; + + /** {@code non-null;} return type */ + private final Type returnType; + + /** {@code non-null;} list of parameter types */ + private final StdTypeList parameterTypes; + + /** {@code null-ok;} list of parameter frame types, if calculated */ + private StdTypeList parameterFrameTypes; + + /** + * Returns the unique instance corresponding to the + * given method descriptor. See vmspec-2 sec4.3.3 for details on the + * field descriptor syntax. + * + * @param descriptor {@code non-null;} the descriptor + * @return {@code non-null;} the corresponding instance + * @throws IllegalArgumentException thrown if the descriptor has + * invalid syntax + */ + public static Prototype intern(String descriptor) { + if (descriptor == null) { + throw new NullPointerException("descriptor == null"); + } + Prototype result = internTable.get(descriptor); + if (result != null) { + return result; + } + + Type[] params = makeParameterArray(descriptor); + int paramCount = 0; + int at = 1; + + for (;;) { + int startAt = at; + char c = descriptor.charAt(at); + if (c == ')') { + at++; + break; + } + + // Skip array markers. + while (c == '[') { + at++; + c = descriptor.charAt(at); + } + + if (c == 'L') { + // It looks like the start of a class name; find the end. + int endAt = descriptor.indexOf(';', at); + if (endAt == -1) { + throw new IllegalArgumentException("bad descriptor"); + } + at = endAt + 1; + } else { + at++; + } + + params[paramCount] = + Type.intern(descriptor.substring(startAt, at)); + paramCount++; + } + + Type returnType = Type.internReturnType(descriptor.substring(at)); + StdTypeList parameterTypes = new StdTypeList(paramCount); + + for (int i = 0; i < paramCount; i++) { + parameterTypes.set(i, params[i]); + } + + result = new Prototype(descriptor, returnType, parameterTypes); + return putIntern(result); + } + + /** + * Helper for {@link #intern} which returns an empty array to + * populate with parsed parameter types, and which also ensures + * that there is a '(' at the start of the descriptor and a + * single ')' somewhere before the end. + * + * @param descriptor {@code non-null;} the descriptor string + * @return {@code non-null;} array large enough to hold all parsed parameter + * types, but which is likely actually larger than needed + */ + private static Type[] makeParameterArray(String descriptor) { + int length = descriptor.length(); + + if (descriptor.charAt(0) != '(') { + throw new IllegalArgumentException("bad descriptor"); + } + + /* + * This is a cheesy way to establish an upper bound on the + * number of parameters: Just count capital letters. + */ + int closeAt = 0; + int maxParams = 0; + for (int i = 1; i < length; i++) { + char c = descriptor.charAt(i); + if (c == ')') { + closeAt = i; + break; + } + if ((c >= 'A') && (c <= 'Z')) { + maxParams++; + } + } + + if ((closeAt == 0) || (closeAt == (length - 1))) { + throw new IllegalArgumentException("bad descriptor"); + } + + if (descriptor.indexOf(')', closeAt + 1) != -1) { + throw new IllegalArgumentException("bad descriptor"); + } + + return new Type[maxParams]; + } + + /** + * Interns an instance, adding to the descriptor as necessary based + * on the given definer, name, and flags. For example, an init + * method has an uninitialized object of type {@code definer} + * as its first argument. + * + * @param descriptor {@code non-null;} the descriptor string + * @param definer {@code non-null;} class the method is defined on + * @param isStatic whether this is a static method + * @param isInit whether this is an init method + * @return {@code non-null;} the interned instance + */ + public static Prototype intern(String descriptor, Type definer, + boolean isStatic, boolean isInit) { + Prototype base = intern(descriptor); + + if (isStatic) { + return base; + } + + if (isInit) { + definer = definer.asUninitialized(Integer.MAX_VALUE); + } + + return base.withFirstParameter(definer); + } + + /** + * Interns an instance which consists of the given number of + * {@code int}s along with the given return type + * + * @param returnType {@code non-null;} the return type + * @param count {@code > 0;} the number of elements in the prototype + * @return {@code non-null;} the interned instance + */ + public static Prototype internInts(Type returnType, int count) { + // Make the descriptor... + + StringBuffer sb = new StringBuffer(100); + + sb.append('('); + + for (int i = 0; i < count; i++) { + sb.append('I'); + } + + sb.append(')'); + sb.append(returnType.getDescriptor()); + + // ...and intern it. + return intern(sb.toString()); + } + + /** + * Constructs an instance. This is a private constructor; use one + * of the public static methods to get instances. + * + * @param descriptor {@code non-null;} the descriptor string + */ + private Prototype(String descriptor, Type returnType, + StdTypeList parameterTypes) { + if (descriptor == null) { + throw new NullPointerException("descriptor == null"); + } + + if (returnType == null) { + throw new NullPointerException("returnType == null"); + } + + if (parameterTypes == null) { + throw new NullPointerException("parameterTypes == null"); + } + + this.descriptor = descriptor; + this.returnType = returnType; + this.parameterTypes = parameterTypes; + this.parameterFrameTypes = null; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (this == other) { + /* + * Since externally-visible instances are interned, this + * check helps weed out some easy cases. + */ + return true; + } + + if (!(other instanceof Prototype)) { + return false; + } + + return descriptor.equals(((Prototype) other).descriptor); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return descriptor.hashCode(); + } + + /** {@inheritDoc} */ + public int compareTo(Prototype other) { + if (this == other) { + return 0; + } + + /* + * The return type is the major order, and then args in order, + * and then the shorter list comes first (similar to string + * sorting). + */ + + int result = returnType.compareTo(other.returnType); + + if (result != 0) { + return result; + } + + int thisSize = parameterTypes.size(); + int otherSize = other.parameterTypes.size(); + int size = Math.min(thisSize, otherSize); + + for (int i = 0; i < size; i++) { + Type thisType = parameterTypes.get(i); + Type otherType = other.parameterTypes.get(i); + + result = thisType.compareTo(otherType); + + if (result != 0) { + return result; + } + } + + if (thisSize < otherSize) { + return -1; + } else if (thisSize > otherSize) { + return 1; + } else { + return 0; + } + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return descriptor; + } + + /** + * Gets the descriptor string. + * + * @return {@code non-null;} the descriptor + */ + public String getDescriptor() { + return descriptor; + } + + /** + * Gets the return type. + * + * @return {@code non-null;} the return type + */ + public Type getReturnType() { + return returnType; + } + + /** + * Gets the list of parameter types. + * + * @return {@code non-null;} the list of parameter types + */ + public StdTypeList getParameterTypes() { + return parameterTypes; + } + + /** + * Gets the list of frame types corresponding to the list of parameter + * types. The difference between the two lists (if any) is that all + * "intlike" types (see {@link Type#isIntlike}) are replaced by + * {@link Type#INT}. + * + * @return {@code non-null;} the list of parameter frame types + */ + public StdTypeList getParameterFrameTypes() { + if (parameterFrameTypes == null) { + int sz = parameterTypes.size(); + StdTypeList list = new StdTypeList(sz); + boolean any = false; + for (int i = 0; i < sz; i++) { + Type one = parameterTypes.get(i); + if (one.isIntlike()) { + any = true; + one = Type.INT; + } + list.set(i, one); + } + parameterFrameTypes = any ? list : parameterTypes; + } + + return parameterFrameTypes; + } + + /** + * Returns a new interned instance, which is the same as this instance, + * except that it has an additional parameter prepended to the original's + * argument list. + * + * @param param {@code non-null;} the new first parameter + * @return {@code non-null;} an appropriately-constructed instance + */ + public Prototype withFirstParameter(Type param) { + String newDesc = "(" + param.getDescriptor() + descriptor.substring(1); + StdTypeList newParams = parameterTypes.withFirst(param); + + newParams.setImmutable(); + + Prototype result = + new Prototype(newDesc, returnType, newParams); + + return putIntern(result); + } + + /** + * Puts the given instance in the intern table if it's not already + * there. If a conflicting value is already in the table, then leave it. + * Return the interned value. + * + * @param desc {@code non-null;} instance to make interned + * @return {@code non-null;} the actual interned object + */ + private static Prototype putIntern(Prototype desc) { + synchronized (internTable) { + String descriptor = desc.getDescriptor(); + Prototype already = internTable.get(descriptor); + if (already != null) { + return already; + } + internTable.put(descriptor, desc); + return desc; + } + } +} diff --git a/dexgen/src/com/android/dexgen/rop/type/StdTypeList.java b/dexgen/src/com/android/dexgen/rop/type/StdTypeList.java new file mode 100644 index 0000000..a3e81ff --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/type/StdTypeList.java @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.type; + +import com.android.dexgen.util.FixedSizeList; + +/** + * Standard implementation of {@link TypeList}. + */ +public final class StdTypeList + extends FixedSizeList implements TypeList { + /** {@code non-null;} no-element instance */ + public static final StdTypeList EMPTY = new StdTypeList(0); + + /** {@code non-null;} the list {@code [int]} */ + public static final StdTypeList INT = StdTypeList.make(Type.INT); + + /** {@code non-null;} the list {@code [long]} */ + public static final StdTypeList LONG = StdTypeList.make(Type.LONG); + + /** {@code non-null;} the list {@code [float]} */ + public static final StdTypeList FLOAT = StdTypeList.make(Type.FLOAT); + + /** {@code non-null;} the list {@code [double]} */ + public static final StdTypeList DOUBLE = StdTypeList.make(Type.DOUBLE); + + /** {@code non-null;} the list {@code [Object]} */ + public static final StdTypeList OBJECT = StdTypeList.make(Type.OBJECT); + + /** {@code non-null;} the list {@code [ReturnAddress]} */ + public static final StdTypeList RETURN_ADDRESS + = StdTypeList.make(Type.RETURN_ADDRESS); + + /** {@code non-null;} the list {@code [Throwable]} */ + public static final StdTypeList THROWABLE = + StdTypeList.make(Type.THROWABLE); + + /** {@code non-null;} the list {@code [int, int]} */ + public static final StdTypeList INT_INT = + StdTypeList.make(Type.INT, Type.INT); + + /** {@code non-null;} the list {@code [long, long]} */ + public static final StdTypeList LONG_LONG = + StdTypeList.make(Type.LONG, Type.LONG); + + /** {@code non-null;} the list {@code [float, float]} */ + public static final StdTypeList FLOAT_FLOAT = + StdTypeList.make(Type.FLOAT, Type.FLOAT); + + /** {@code non-null;} the list {@code [double, double]} */ + public static final StdTypeList DOUBLE_DOUBLE = + StdTypeList.make(Type.DOUBLE, Type.DOUBLE); + + /** {@code non-null;} the list {@code [Object, Object]} */ + public static final StdTypeList OBJECT_OBJECT = + StdTypeList.make(Type.OBJECT, Type.OBJECT); + + /** {@code non-null;} the list {@code [int, Object]} */ + public static final StdTypeList INT_OBJECT = + StdTypeList.make(Type.INT, Type.OBJECT); + + /** {@code non-null;} the list {@code [long, Object]} */ + public static final StdTypeList LONG_OBJECT = + StdTypeList.make(Type.LONG, Type.OBJECT); + + /** {@code non-null;} the list {@code [float, Object]} */ + public static final StdTypeList FLOAT_OBJECT = + StdTypeList.make(Type.FLOAT, Type.OBJECT); + + /** {@code non-null;} the list {@code [double, Object]} */ + public static final StdTypeList DOUBLE_OBJECT = + StdTypeList.make(Type.DOUBLE, Type.OBJECT); + + /** {@code non-null;} the list {@code [long, int]} */ + public static final StdTypeList LONG_INT = + StdTypeList.make(Type.LONG, Type.INT); + + /** {@code non-null;} the list {@code [int[], int]} */ + public static final StdTypeList INTARR_INT = + StdTypeList.make(Type.INT_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [long[], int]} */ + public static final StdTypeList LONGARR_INT = + StdTypeList.make(Type.LONG_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [float[], int]} */ + public static final StdTypeList FLOATARR_INT = + StdTypeList.make(Type.FLOAT_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [double[], int]} */ + public static final StdTypeList DOUBLEARR_INT = + StdTypeList.make(Type.DOUBLE_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [Object[], int]} */ + public static final StdTypeList OBJECTARR_INT = + StdTypeList.make(Type.OBJECT_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [boolean[], int]} */ + public static final StdTypeList BOOLEANARR_INT = + StdTypeList.make(Type.BOOLEAN_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [byte[], int]} */ + public static final StdTypeList BYTEARR_INT = + StdTypeList.make(Type.BYTE_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [char[], int]} */ + public static final StdTypeList CHARARR_INT = + StdTypeList.make(Type.CHAR_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [short[], int]} */ + public static final StdTypeList SHORTARR_INT = + StdTypeList.make(Type.SHORT_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [int, int[], int]} */ + public static final StdTypeList INT_INTARR_INT = + StdTypeList.make(Type.INT, Type.INT_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [long, long[], int]} */ + public static final StdTypeList LONG_LONGARR_INT = + StdTypeList.make(Type.LONG, Type.LONG_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [float, float[], int]} */ + public static final StdTypeList FLOAT_FLOATARR_INT = + StdTypeList.make(Type.FLOAT, Type.FLOAT_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [double, double[], int]} */ + public static final StdTypeList DOUBLE_DOUBLEARR_INT = + StdTypeList.make(Type.DOUBLE, Type.DOUBLE_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [Object, Object[], int]} */ + public static final StdTypeList OBJECT_OBJECTARR_INT = + StdTypeList.make(Type.OBJECT, Type.OBJECT_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [int, boolean[], int]} */ + public static final StdTypeList INT_BOOLEANARR_INT = + StdTypeList.make(Type.INT, Type.BOOLEAN_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [int, byte[], int]} */ + public static final StdTypeList INT_BYTEARR_INT = + StdTypeList.make(Type.INT, Type.BYTE_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [int, char[], int]} */ + public static final StdTypeList INT_CHARARR_INT = + StdTypeList.make(Type.INT, Type.CHAR_ARRAY, Type.INT); + + /** {@code non-null;} the list {@code [int, short[], int]} */ + public static final StdTypeList INT_SHORTARR_INT = + StdTypeList.make(Type.INT, Type.SHORT_ARRAY, Type.INT); + + /** + * Makes a single-element instance. + * + * @param type {@code non-null;} the element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static StdTypeList make(Type type) { + StdTypeList result = new StdTypeList(1); + result.set(0, type); + return result; + } + + /** + * Makes a two-element instance. + * + * @param type0 {@code non-null;} the first element + * @param type1 {@code non-null;} the second element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static StdTypeList make(Type type0, Type type1) { + StdTypeList result = new StdTypeList(2); + result.set(0, type0); + result.set(1, type1); + return result; + } + + /** + * Makes a three-element instance. + * + * @param type0 {@code non-null;} the first element + * @param type1 {@code non-null;} the second element + * @param type2 {@code non-null;} the third element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static StdTypeList make(Type type0, Type type1, Type type2) { + StdTypeList result = new StdTypeList(3); + result.set(0, type0); + result.set(1, type1); + result.set(2, type2); + return result; + } + + /** + * Makes a four-element instance. + * + * @param type0 {@code non-null;} the first element + * @param type1 {@code non-null;} the second element + * @param type2 {@code non-null;} the third element + * @param type3 {@code non-null;} the fourth element + * @return {@code non-null;} an appropriately-constructed instance + */ + public static StdTypeList make(Type type0, Type type1, Type type2, + Type type3) { + StdTypeList result = new StdTypeList(4); + result.set(0, type0); + result.set(1, type1); + result.set(2, type2); + result.set(3, type3); + return result; + } + + /** + * Returns the given list as a comma-separated list of human forms. This + * is a static method so as to work on arbitrary {@link TypeList} + * instances. + * + * @param list {@code non-null;} the list to convert + * @return {@code non-null;} the human form + */ + public static String toHuman(TypeList list) { + int size = list.size(); + + if (size == 0) { + return "<empty>"; + } + + StringBuffer sb = new StringBuffer(100); + + for (int i = 0; i < size; i++) { + if (i != 0) { + sb.append(", "); + } + sb.append(list.getType(i).toHuman()); + } + + return sb.toString(); + } + + /** + * Returns a hashcode of the contents of the given list. This + * is a static method so as to work on arbitrary {@link TypeList} + * instances. + * + * @param list {@code non-null;} the list to inspect + * @return {@code non-null;} the hash code + */ + public static int hashContents(TypeList list) { + int size = list.size(); + int hash = 0; + + for (int i = 0; i < size; i++) { + hash = (hash * 31) + list.getType(i).hashCode(); + } + + return hash; + } + + /** + * Compares the contents of the given two instances for equality. This + * is a static method so as to work on arbitrary {@link TypeList} + * instances. + * + * @param list1 {@code non-null;} one list to compare + * @param list2 {@code non-null;} another list to compare + * @return whether the two lists contain corresponding equal elements + */ + public static boolean equalContents(TypeList list1, TypeList list2) { + int size = list1.size(); + + if (list2.size() != size) { + return false; + } + + for (int i = 0; i < size; i++) { + if (! list1.getType(i).equals(list2.getType(i))) { + return false; + } + } + + return true; + } + + /** + * Compares the contents of the given two instances for ordering. This + * is a static method so as to work on arbitrary {@link TypeList} + * instances. + * + * @param list1 {@code non-null;} one list to compare + * @param list2 {@code non-null;} another list to compare + * @return the order of the two lists + */ + public static int compareContents(TypeList list1, TypeList list2) { + int size1 = list1.size(); + int size2 = list2.size(); + int size = Math.min(size1, size2); + + for (int i = 0; i < size; i++) { + int comparison = list1.getType(i).compareTo(list2.getType(i)); + if (comparison != 0) { + return comparison; + } + } + + if (size1 == size2) { + return 0; + } else if (size1 < size2) { + return -1; + } else { + return 1; + } + } + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + */ + public StdTypeList(int size) { + super(size); + } + + /** {@inheritDoc} */ + public Type getType(int n) { + return get(n); + } + + /** {@inheritDoc} */ + public int getWordCount() { + int sz = size(); + int result = 0; + + for (int i = 0; i < sz; i++) { + result += get(i).getCategory(); + } + + return result; + } + + /** {@inheritDoc} */ + public TypeList withAddedType(Type type) { + int sz = size(); + StdTypeList result = new StdTypeList(sz + 1); + + for (int i = 0; i < sz; i++) { + result.set0(i, get0(i)); + } + + result.set(sz, type); + result.setImmutable(); + return result; + } + + /** + * Gets the indicated element. It is an error to call this with the + * index for an element which was never set; if you do that, this + * will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which element + * @return {@code non-null;} the indicated element + */ + public Type get(int n) { + return (Type) get0(n); + } + + /** + * Sets the type at the given index. + * + * @param n {@code >= 0, < size();} which element + * @param type {@code non-null;} the type to store + */ + public void set(int n, Type type) { + set0(n, type); + } + + /** + * Returns a new instance, which is the same as this instance, + * except that it has an additional type prepended to the + * original. + * + * @param type {@code non-null;} the new first element + * @return {@code non-null;} an appropriately-constructed instance + */ + public StdTypeList withFirst(Type type) { + int sz = size(); + StdTypeList result = new StdTypeList(sz + 1); + + result.set0(0, type); + for (int i = 0; i < sz; i++) { + result.set0(i + 1, getOrNull0(i)); + } + + return result; + } +} diff --git a/dexgen/src/com/android/dexgen/rop/type/Type.java b/dexgen/src/com/android/dexgen/rop/type/Type.java new file mode 100644 index 0000000..365bd78 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/type/Type.java @@ -0,0 +1,928 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.type; + +import com.android.dexgen.util.Hex; + +import java.util.HashMap; + +/** + * Representation of a value type, such as may appear in a field, in a + * local, on a stack, or in a method descriptor. Instances of this + * class are generally interned and may be usefully compared with each + * other using {@code ==}. + */ +public final class Type implements TypeBearer, Comparable<Type> { + /** {@code non-null;} intern table mapping string descriptors to instances */ + private static final HashMap<String, Type> internTable = + new HashMap<String, Type>(500); + + /** {@code non-null;} table mapping types as {@code Class} objects to internal form */ + private static final HashMap<Class, Type> CLASS_TYPE_MAP = + new HashMap<Class, Type>(); + + /** basic type constant for {@code void} */ + public static final int BT_VOID = 0; + + /** basic type constant for {@code boolean} */ + public static final int BT_BOOLEAN = 1; + + /** basic type constant for {@code byte} */ + public static final int BT_BYTE = 2; + + /** basic type constant for {@code char} */ + public static final int BT_CHAR = 3; + + /** basic type constant for {@code double} */ + public static final int BT_DOUBLE = 4; + + /** basic type constant for {@code float} */ + public static final int BT_FLOAT = 5; + + /** basic type constant for {@code int} */ + public static final int BT_INT = 6; + + /** basic type constant for {@code long} */ + public static final int BT_LONG = 7; + + /** basic type constant for {@code short} */ + public static final int BT_SHORT = 8; + + /** basic type constant for {@code Object} */ + public static final int BT_OBJECT = 9; + + /** basic type constant for a return address */ + public static final int BT_ADDR = 10; + + /** count of basic type constants */ + public static final int BT_COUNT = 11; + + /** {@code non-null;} instance representing {@code boolean} */ + public static final Type BOOLEAN = new Type("Z", BT_BOOLEAN); + + /** {@code non-null;} instance representing {@code byte} */ + public static final Type BYTE = new Type("B", BT_BYTE); + + /** {@code non-null;} instance representing {@code char} */ + public static final Type CHAR = new Type("C", BT_CHAR); + + /** {@code non-null;} instance representing {@code double} */ + public static final Type DOUBLE = new Type("D", BT_DOUBLE); + + /** {@code non-null;} instance representing {@code float} */ + public static final Type FLOAT = new Type("F", BT_FLOAT); + + /** {@code non-null;} instance representing {@code int} */ + public static final Type INT = new Type("I", BT_INT); + + /** {@code non-null;} instance representing {@code long} */ + public static final Type LONG = new Type("J", BT_LONG); + + /** {@code non-null;} instance representing {@code short} */ + public static final Type SHORT = new Type("S", BT_SHORT); + + /** {@code non-null;} instance representing {@code void} */ + public static final Type VOID = new Type("V", BT_VOID); + + /** {@code non-null;} instance representing a known-{@code null} */ + public static final Type KNOWN_NULL = new Type("<null>", BT_OBJECT); + + /** {@code non-null;} instance representing a subroutine return address */ + public static final Type RETURN_ADDRESS = new Type("<addr>", BT_ADDR); + + static { + /* + * Put all the primitive types into the intern table. This needs + * to happen before the array types below get interned. + */ + putIntern(BOOLEAN); + putIntern(BYTE); + putIntern(CHAR); + putIntern(DOUBLE); + putIntern(FLOAT); + putIntern(INT); + putIntern(LONG); + putIntern(SHORT); + /* + * Note: VOID isn't put in the intern table, since it's special and + * shouldn't be found by a normal call to intern(). + */ + + /* + * Create a mapping between types as Java Class objects + * and types in dx internal format. + */ + CLASS_TYPE_MAP.put(boolean.class, BOOLEAN); + CLASS_TYPE_MAP.put(short.class, SHORT); + CLASS_TYPE_MAP.put(int.class, INT); + CLASS_TYPE_MAP.put(long.class, LONG); + CLASS_TYPE_MAP.put(char.class, CHAR); + CLASS_TYPE_MAP.put(byte.class, BYTE); + CLASS_TYPE_MAP.put(float.class, FLOAT); + CLASS_TYPE_MAP.put(double.class, DOUBLE); + CLASS_TYPE_MAP.put(void.class, VOID); + } + + /** + * {@code non-null;} instance representing + * {@code java.lang.annotation.Annotation} + */ + public static final Type ANNOTATION = + intern("Ljava/lang/annotation/Annotation;"); + + /** {@code non-null;} instance representing {@code java.lang.Class} */ + public static final Type CLASS = intern("Ljava/lang/Class;"); + + /** {@code non-null;} instance representing {@code java.lang.Cloneable} */ + public static final Type CLONEABLE = intern("Ljava/lang/Cloneable;"); + + /** {@code non-null;} instance representing {@code java.lang.Object} */ + public static final Type OBJECT = intern("Ljava/lang/Object;"); + + /** {@code non-null;} instance representing {@code java.io.Serializable} */ + public static final Type SERIALIZABLE = intern("Ljava/io/Serializable;"); + + /** {@code non-null;} instance representing {@code java.lang.String} */ + public static final Type STRING = intern("Ljava/lang/String;"); + + /** {@code non-null;} instance representing {@code java.lang.Throwable} */ + public static final Type THROWABLE = intern("Ljava/lang/Throwable;"); + + /** + * {@code non-null;} instance representing {@code java.lang.Boolean}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type BOOLEAN_CLASS = intern("Ljava/lang/Boolean;"); + + /** + * {@code non-null;} instance representing {@code java.lang.Byte}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type BYTE_CLASS = intern("Ljava/lang/Byte;"); + + /** + * {@code non-null;} instance representing {@code java.lang.Character}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type CHARACTER_CLASS = intern("Ljava/lang/Character;"); + + /** + * {@code non-null;} instance representing {@code java.lang.Double}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type DOUBLE_CLASS = intern("Ljava/lang/Double;"); + + /** + * {@code non-null;} instance representing {@code java.lang.Float}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type FLOAT_CLASS = intern("Ljava/lang/Float;"); + + /** + * {@code non-null;} instance representing {@code java.lang.Integer}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type INTEGER_CLASS = intern("Ljava/lang/Integer;"); + + /** + * {@code non-null;} instance representing {@code java.lang.Long}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type LONG_CLASS = intern("Ljava/lang/Long;"); + + /** + * {@code non-null;} instance representing {@code java.lang.Short}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type SHORT_CLASS = intern("Ljava/lang/Short;"); + + /** + * {@code non-null;} instance representing {@code java.lang.Void}; the + * suffix on the name helps disambiguate this from the instance + * representing a primitive type + */ + public static final Type VOID_CLASS = intern("Ljava/lang/Void;"); + + /** {@code non-null;} instance representing {@code boolean[]} */ + public static final Type BOOLEAN_ARRAY = BOOLEAN.getArrayType(); + + /** {@code non-null;} instance representing {@code byte[]} */ + public static final Type BYTE_ARRAY = BYTE.getArrayType(); + + /** {@code non-null;} instance representing {@code char[]} */ + public static final Type CHAR_ARRAY = CHAR.getArrayType(); + + /** {@code non-null;} instance representing {@code double[]} */ + public static final Type DOUBLE_ARRAY = DOUBLE.getArrayType(); + + /** {@code non-null;} instance representing {@code float[]} */ + public static final Type FLOAT_ARRAY = FLOAT.getArrayType(); + + /** {@code non-null;} instance representing {@code int[]} */ + public static final Type INT_ARRAY = INT.getArrayType(); + + /** {@code non-null;} instance representing {@code long[]} */ + public static final Type LONG_ARRAY = LONG.getArrayType(); + + /** {@code non-null;} instance representing {@code Object[]} */ + public static final Type OBJECT_ARRAY = OBJECT.getArrayType(); + + /** {@code non-null;} instance representing {@code short[]} */ + public static final Type SHORT_ARRAY = SHORT.getArrayType(); + + /** {@code non-null;} field descriptor for the type */ + private final String descriptor; + + /** + * basic type corresponding to this type; one of the + * {@code BT_*} constants + */ + private final int basicType; + + /** + * {@code >= -1;} for an uninitialized type, bytecode index that this + * instance was allocated at; {@code Integer.MAX_VALUE} if it + * was an incoming uninitialized instance; {@code -1} if this + * is an <i>inititialized</i> instance + */ + private final int newAt; + + /** + * {@code null-ok;} the internal-form class name corresponding to this type, if + * calculated; only valid if {@code this} is a reference type and + * additionally not a return address + */ + private String className; + + /** + * {@code null-ok;} the type corresponding to an array of this type, if + * calculated + */ + private Type arrayType; + + /** + * {@code null-ok;} the type corresponding to elements of this type, if + * calculated; only valid if {@code this} is an array type + */ + private Type componentType; + + /** + * {@code null-ok;} the type corresponding to the initialized version of + * this type, if this instance is in fact an uninitialized type + */ + private Type initializedType; + + /** + * Returns the unique instance corresponding to the type represented by + * given {@code Class} object. See vmspec-2 sec4.3.2 for details on the + * field descriptor syntax. This method does <i>not</i> allow + * {@code "V"} (that is, type {@code void}) as a valid + * descriptor. + * + * @param clazz {@code non-null;} class whose descriptor + * will be internalized + * @return {@code non-null;} the corresponding instance + */ + public static Type intern(Class clazz) { + return intern(getInternalTypeName(clazz)); + } + + /** + * Returns the unique instance corresponding to the type with the + * given descriptor. See vmspec-2 sec4.3.2 for details on the + * field descriptor syntax. This method does <i>not</i> allow + * {@code "V"} (that is, type {@code void}) as a valid + * descriptor. + * + * @param descriptor {@code non-null;} the descriptor + * @return {@code non-null;} the corresponding instance + * @throws IllegalArgumentException thrown if the descriptor has + * invalid syntax + */ + public static Type intern(String descriptor) { + + Type result = internTable.get(descriptor); + if (result != null) { + return result; + } + + char firstChar; + try { + firstChar = descriptor.charAt(0); + } catch (IndexOutOfBoundsException ex) { + // Translate the exception. + throw new IllegalArgumentException("descriptor is empty"); + } catch (NullPointerException ex) { + // Elucidate the exception. + throw new NullPointerException("descriptor == null"); + } + + if (firstChar == '[') { + /* + * Recursively strip away array markers to get at the underlying + * type, and build back on to form the result. + */ + result = intern(descriptor.substring(1)); + return result.getArrayType(); + } + + /* + * If the first character isn't '[' and it wasn't found in the + * intern cache, then it had better be the descriptor for a class. + */ + + int length = descriptor.length(); + if ((firstChar != 'L') || + (descriptor.charAt(length - 1) != ';')) { + throw new IllegalArgumentException("bad descriptor" + descriptor); + } + + /* + * Validate the characters of the class name itself. Note that + * vmspec-2 does not have a coherent definition for valid + * internal-form class names, and the definition here is fairly + * liberal: A name is considered valid as long as it doesn't + * contain any of '[' ';' '.' '(' ')', and it has no more than one + * '/' in a row, and no '/' at either end. + */ + + int limit = (length - 1); // Skip the final ';'. + for (int i = 1; i < limit; i++) { + char c = descriptor.charAt(i); + switch (c) { + case '[': + case ';': + case '.': + case '(': + case ')': { + throw new IllegalArgumentException("bad descriptor" + descriptor); + } + case '/': { + if ((i == 1) || + (i == (length - 1)) || + (descriptor.charAt(i - 1) == '/')) { + throw new IllegalArgumentException("bad descriptor"); + } + break; + } + } + } + + result = new Type(descriptor, BT_OBJECT); + return putIntern(result); + } + + /** + * Returns the unique instance corresponding to the type represented by + * given {@code Class} object, allowing {@code "V"} to return the type + * for {@code void}. Other than that one caveat, this method + * is identical to {@link #intern}. + * + * @param clazz {@code non-null;} class which descriptor + * will be internalized + * @return {@code non-null;} the corresponding instance + */ + public static Type internReturnType(Class clazz) { + return internReturnType(getInternalTypeName(clazz)); + } + + /** + * Returns the unique instance corresponding to the type with the + * given descriptor, allowing {@code "V"} to return the type + * for {@code void}. Other than that one caveat, this method + * is identical to {@link #intern}. + * + * @param descriptor {@code non-null;} the descriptor + * @return {@code non-null;} the corresponding instance + * @throws IllegalArgumentException thrown if the descriptor has + * invalid syntax + */ + public static Type internReturnType(String descriptor) { + try { + if (descriptor.equals("V")) { + // This is the one special case where void may be returned. + return VOID; + } + } catch (NullPointerException ex) { + // Elucidate the exception. + throw new NullPointerException("descriptor == null"); + } + + return intern(descriptor); + } + + /** + * Returns the unique instance corresponding to the type of the + * class with the given name. Calling this method is equivalent to + * calling {@code intern(name)} if {@code name} begins + * with {@code "["} and calling {@code intern("L" + name + ";")} + * in all other cases. + * + * @param name {@code non-null;} the name of the class whose type is desired + * @return {@code non-null;} the corresponding type + * @throws IllegalArgumentException thrown if the name has + * invalid syntax + */ + public static Type internClassName(String name) { + if (name == null) { + throw new NullPointerException("name == null"); + } + + if (name.startsWith("[")) { + return intern(name); + } + + return intern('L' + name + ';'); + } + + /** + * Converts type name in the format as returned by reflection + * into dex internal form. + * + * @param clazz {@code non-null;} class whose name will be internalized + * @return string with the type name in dex internal format + */ + public static String getInternalTypeName(Class clazz) { + if (clazz == null) { + throw new NullPointerException("clazz == null"); + } + + if (clazz.isPrimitive()) { + return CLASS_TYPE_MAP.get(clazz).getDescriptor(); + } + + String slashed = clazz.getName().replace('.', '/'); + + if (clazz.isArray()) { + return slashed; + } + + return 'L' + slashed + ';'; + } + + /** + * Constructs an instance corresponding to an "uninitialized type." + * This is a private constructor; use one of the public static + * methods to get instances. + * + * @param descriptor {@code non-null;} the field descriptor for the type + * @param basicType basic type corresponding to this type; one of the + * {@code BT_*} constants + * @param newAt {@code >= -1;} allocation bytecode index + */ + private Type(String descriptor, int basicType, int newAt) { + if (descriptor == null) { + throw new NullPointerException("descriptor == null"); + } + + if ((basicType < 0) || (basicType >= BT_COUNT)) { + throw new IllegalArgumentException("bad basicType"); + } + + if (newAt < -1) { + throw new IllegalArgumentException("newAt < -1"); + } + + this.descriptor = descriptor; + this.basicType = basicType; + this.newAt = newAt; + this.arrayType = null; + this.componentType = null; + this.initializedType = null; + } + + /** + * Constructs an instance corresponding to an "initialized type." + * This is a private constructor; use one of the public static + * methods to get instances. + * + * @param descriptor {@code non-null;} the field descriptor for the type + * @param basicType basic type corresponding to this type; one of the + * {@code BT_*} constants + */ + private Type(String descriptor, int basicType) { + this(descriptor, basicType, -1); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (this == other) { + /* + * Since externally-visible types are interned, this check + * helps weed out some easy cases. + */ + return true; + } + + if (!(other instanceof Type)) { + return false; + } + + return descriptor.equals(((Type) other).descriptor); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return descriptor.hashCode(); + } + + /** {@inheritDoc} */ + public int compareTo(Type other) { + return descriptor.compareTo(other.descriptor); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + return descriptor; + } + + /** {@inheritDoc} */ + public String toHuman() { + switch (basicType) { + case BT_VOID: return "void"; + case BT_BOOLEAN: return "boolean"; + case BT_BYTE: return "byte"; + case BT_CHAR: return "char"; + case BT_DOUBLE: return "double"; + case BT_FLOAT: return "float"; + case BT_INT: return "int"; + case BT_LONG: return "long"; + case BT_SHORT: return "short"; + case BT_OBJECT: break; + default: return descriptor; + } + + if (isArray()) { + return getComponentType().toHuman() + "[]"; + } + + // Remove the "L...;" around the type and convert "/" to ".". + return getClassName().replace("/", "."); + } + + /** {@inheritDoc} */ + public Type getType() { + return this; + } + + /** {@inheritDoc} */ + public Type getFrameType() { + switch (basicType) { + case BT_BOOLEAN: + case BT_BYTE: + case BT_CHAR: + case BT_INT: + case BT_SHORT: { + return INT; + } + } + + return this; + } + + /** {@inheritDoc} */ + public int getBasicType() { + return basicType; + } + + /** {@inheritDoc} */ + public int getBasicFrameType() { + switch (basicType) { + case BT_BOOLEAN: + case BT_BYTE: + case BT_CHAR: + case BT_INT: + case BT_SHORT: { + return BT_INT; + } + } + + return basicType; + } + + /** {@inheritDoc} */ + public boolean isConstant() { + return false; + } + + /** + * Gets the descriptor. + * + * @return {@code non-null;} the descriptor + */ + public String getDescriptor() { + return descriptor; + } + + /** + * Gets the name of the class this type corresponds to, in internal + * form. This method is only valid if this instance is for a + * normal reference type (that is, a reference type and + * additionally not a return address). + * + * @return {@code non-null;} the internal-form class name + */ + public String getClassName() { + if (className == null) { + if (!isReference()) { + throw new IllegalArgumentException("not an object type: " + + descriptor); + } + + if (descriptor.charAt(0) == '[') { + className = descriptor; + } else { + className = descriptor.substring(1, descriptor.length() - 1); + } + } + + return className; + } + + /** + * Gets the category. Most instances are category 1. {@code long} + * and {@code double} are the only category 2 types. + * + * @see #isCategory1 + * @see #isCategory2 + * @return the category + */ + public int getCategory() { + switch (basicType) { + case BT_LONG: + case BT_DOUBLE: { + return 2; + } + } + + return 1; + } + + /** + * Returns whether or not this is a category 1 type. + * + * @see #getCategory + * @see #isCategory2 + * @return whether or not this is a category 1 type + */ + public boolean isCategory1() { + switch (basicType) { + case BT_LONG: + case BT_DOUBLE: { + return false; + } + } + + return true; + } + + /** + * Returns whether or not this is a category 2 type. + * + * @see #getCategory + * @see #isCategory1 + * @return whether or not this is a category 2 type + */ + public boolean isCategory2() { + switch (basicType) { + case BT_LONG: + case BT_DOUBLE: { + return true; + } + } + + return false; + } + + /** + * Gets whether this type is "intlike." An intlike type is one which, when + * placed on a stack or in a local, is automatically converted to an + * {@code int}. + * + * @return whether this type is "intlike" + */ + public boolean isIntlike() { + switch (basicType) { + case BT_BOOLEAN: + case BT_BYTE: + case BT_CHAR: + case BT_INT: + case BT_SHORT: { + return true; + } + } + + return false; + } + + /** + * Gets whether this type is a primitive type. All types are either + * primitive or reference types. + * + * @return whether this type is primitive + */ + public boolean isPrimitive() { + switch (basicType) { + case BT_BOOLEAN: + case BT_BYTE: + case BT_CHAR: + case BT_DOUBLE: + case BT_FLOAT: + case BT_INT: + case BT_LONG: + case BT_SHORT: + case BT_VOID: { + return true; + } + } + + return false; + } + + /** + * Gets whether this type is a normal reference type. A normal + * reference type is a reference type that is not a return + * address. This method is just convenient shorthand for + * {@code getBasicType() == Type.BT_OBJECT}. + * + * @return whether this type is a normal reference type + */ + public boolean isReference() { + return (basicType == BT_OBJECT); + } + + /** + * Gets whether this type is an array type. If this method returns + * {@code true}, then it is safe to use {@link #getComponentType} + * to determine the component type. + * + * @return whether this type is an array type + */ + public boolean isArray() { + return (descriptor.charAt(0) == '['); + } + + /** + * Gets whether this type is an array type or is a known-null, and + * hence is compatible with array types. + * + * @return whether this type is an array type + */ + public boolean isArrayOrKnownNull() { + return isArray() || equals(KNOWN_NULL); + } + + /** + * Gets whether this type represents an uninitialized instance. An + * uninitialized instance is what one gets back from the {@code new} + * opcode, and remains uninitialized until a valid constructor is + * invoked on it. + * + * @return whether this type is "uninitialized" + */ + public boolean isUninitialized() { + return (newAt >= 0); + } + + /** + * Gets the bytecode index at which this uninitialized type was + * allocated. This returns {@code Integer.MAX_VALUE} if this + * type is an uninitialized incoming parameter (i.e., the + * {@code this} of an {@code <init>} method) or + * {@code -1} if this type is in fact <i>initialized</i>. + * + * @return {@code >= -1;} the allocation bytecode index + */ + public int getNewAt() { + return newAt; + } + + /** + * Gets the initialized type corresponding to this instance, but only + * if this instance is in fact an uninitialized object type. + * + * @return {@code non-null;} the initialized type + */ + public Type getInitializedType() { + if (initializedType == null) { + throw new IllegalArgumentException("initialized type: " + + descriptor); + } + + return initializedType; + } + + /** + * Gets the type corresponding to an array of this type. + * + * @return {@code non-null;} the array type + */ + public Type getArrayType() { + if (arrayType == null) { + arrayType = putIntern(new Type('[' + descriptor, BT_OBJECT)); + } + + return arrayType; + } + + /** + * Gets the component type of this type. This method is only valid on + * array types. + * + * @return {@code non-null;} the component type + */ + public Type getComponentType() { + if (componentType == null) { + if (descriptor.charAt(0) != '[') { + throw new IllegalArgumentException("not an array type: " + + descriptor); + } + componentType = intern(descriptor.substring(1)); + } + + return componentType; + } + + /** + * Returns a new interned instance which is identical to this one, except + * it is indicated as uninitialized and allocated at the given bytecode + * index. This instance must be an initialized object type. + * + * @param newAt {@code >= 0;} the allocation bytecode index + * @return {@code non-null;} an appropriately-constructed instance + */ + public Type asUninitialized(int newAt) { + if (newAt < 0) { + throw new IllegalArgumentException("newAt < 0"); + } + + if (!isReference()) { + throw new IllegalArgumentException("not a reference type: " + + descriptor); + } + + if (isUninitialized()) { + /* + * Dealing with uninitialized types as a starting point is + * a pain, and it's not clear that it'd ever be used, so + * just disallow it. + */ + throw new IllegalArgumentException("already uninitialized: " + + descriptor); + } + + /* + * Create a new descriptor that is unique and shouldn't conflict + * with "normal" type descriptors + */ + String newDesc = 'N' + Hex.u2(newAt) + descriptor; + Type result = new Type(newDesc, BT_OBJECT, newAt); + result.initializedType = this; + return putIntern(result); + } + + /** + * Puts the given instance in the intern table if it's not already + * there. If a conflicting value is already in the table, then leave it. + * Return the interned value. + * + * @param type {@code non-null;} instance to make interned + * @return {@code non-null;} the actual interned object + */ + private static Type putIntern(Type type) { + synchronized (internTable) { + String descriptor = type.getDescriptor(); + Type already = internTable.get(descriptor); + if (already != null) { + return already; + } + internTable.put(descriptor, type); + return type; + } + } +}
\ No newline at end of file diff --git a/dexgen/src/com/android/dexgen/rop/type/TypeBearer.java b/dexgen/src/com/android/dexgen/rop/type/TypeBearer.java new file mode 100644 index 0000000..da7a7ef --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/type/TypeBearer.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.type; + +import com.android.dexgen.util.ToHuman; + +/** + * Object which has an associated type, possibly itself. + */ +public interface TypeBearer + extends ToHuman { + /** + * Gets the type associated with this instance. + * + * @return {@code non-null;} the type + */ + public Type getType(); + + /** + * Gets the frame type corresponding to this type. This method returns + * {@code this}, except if {@link Type#isIntlike} on the underlying + * type returns {@code true} but the underlying type is not in + * fact {@link Type#INT}, in which case this method returns an instance + * whose underlying type <i>is</i> {@code INT}. + * + * @return {@code non-null;} the frame type for this instance + */ + public TypeBearer getFrameType(); + + /** + * Gets the basic type corresponding to this instance. + * + * @return the basic type; one of the {@code BT_*} constants + * defined by {@link Type} + */ + public int getBasicType(); + + /** + * Gets the basic type corresponding to this instance's frame type. This + * is equivalent to {@code getFrameType().getBasicType()}, and + * is the same as calling {@code getFrameType()} unless this + * instance is an int-like type, in which case this method returns + * {@code BT_INT}. + * + * @see #getBasicType + * @see #getFrameType + * + * @return the basic frame type; one of the {@code BT_*} constants + * defined by {@link Type} + */ + public int getBasicFrameType(); + + /** + * Returns whether this instance represents a constant value. + * + * @return {@code true} if this instance represents a constant value + * and {@code false} if not + */ + public boolean isConstant(); +} diff --git a/dexgen/src/com/android/dexgen/rop/type/TypeList.java b/dexgen/src/com/android/dexgen/rop/type/TypeList.java new file mode 100644 index 0000000..dedcbc9 --- /dev/null +++ b/dexgen/src/com/android/dexgen/rop/type/TypeList.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.rop.type; + +/** + * List of {@link Type} instances (or of things that contain types). + */ +public interface TypeList { + /** + * Returns whether this instance is mutable. Note that the + * {@code TypeList} interface itself doesn't provide any + * means of mutation, but that doesn't mean that there isn't an + * extra-interface way of mutating an instance. + * + * @return {@code true} if this instance is mutable or + * {@code false} if it is immutable + */ + public boolean isMutable(); + + /** + * Gets the size of this list. + * + * @return {@code >= 0;} the size + */ + public int size(); + + /** + * Gets the indicated element. It is an error to call this with the + * index for an element which was never set; if you do that, this + * will throw {@code NullPointerException}. + * + * @param n {@code >= 0, < size();} which element + * @return {@code non-null;} the indicated element + */ + public Type getType(int n); + + /** + * Gets the number of 32-bit words required to hold instances of + * all the elements of this list. This is a sum of the widths (categories) + * of all the elements. + * + * @return {@code >= 0;} the required number of words + */ + public int getWordCount(); + + /** + * Returns a new instance which is identical to this one, except that + * the given item is appended to the end and it is guaranteed to be + * immutable. + * + * @param type {@code non-null;} item to append + * @return {@code non-null;} an appropriately-constructed instance + */ + public TypeList withAddedType(Type type); +} diff --git a/dexgen/src/com/android/dexgen/util/AnnotatedOutput.java b/dexgen/src/com/android/dexgen/util/AnnotatedOutput.java new file mode 100644 index 0000000..3ff4cf5 --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/AnnotatedOutput.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +/** + * Interface for a binary output destination that may be augmented + * with textual annotations. + */ +public interface AnnotatedOutput + extends Output { + /** + * Get whether this instance will actually keep annotations. + * + * @return {@code true} iff annotations are being kept + */ + public boolean annotates(); + + /** + * Get whether this instance is intended to keep verbose annotations. + * Annotators may use the result of calling this method to inform their + * annotation activity. + * + * @return {@code true} iff annotations are to be verbose + */ + public boolean isVerbose(); + + /** + * Add an annotation for the subsequent output. Any previously + * open annotation will be closed by this call, and the new + * annotation marks all subsequent output until another annotation + * call. + * + * @param msg {@code non-null;} the annotation message + */ + public void annotate(String msg); + + /** + * Add an annotation for a specified amount of subsequent + * output. Any previously open annotation will be closed by this + * call. If there is already pending annotation from one or more + * previous calls to this method, the new call "consumes" output + * after all the output covered by the previous calls. + * + * @param amt {@code >= 0;} the amount of output for this annotation to + * cover + * @param msg {@code non-null;} the annotation message + */ + public void annotate(int amt, String msg); + + /** + * End the most recent annotation. Subsequent output will be unannotated, + * until the next call to {@link #annotate}. + */ + public void endAnnotation(); + + /** + * Get the maximum width of the annotated output. This is advisory: + * Implementations of this interface are encouraged to deal with too-wide + * output, but annotaters are encouraged to attempt to avoid exceeding + * the indicated width. + * + * @return {@code >= 1;} the maximum width + */ + public int getAnnotationWidth(); +} diff --git a/dexgen/src/com/android/dexgen/util/BitIntSet.java b/dexgen/src/com/android/dexgen/util/BitIntSet.java new file mode 100644 index 0000000..0cf9fc6 --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/BitIntSet.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +import java.util.NoSuchElementException; + +/** + * A set of integers, represented by a bit set + */ +public class BitIntSet implements IntSet { + + /** also accessed in ListIntSet */ + int[] bits; + + /** + * Constructs an instance. + * + * @param max the maximum value of ints in this set. + */ + public BitIntSet(int max) { + bits = Bits.makeBitSet(max); + } + + /** @inheritDoc */ + public void add(int value) { + ensureCapacity(value); + Bits.set(bits, value, true); + } + + /** + * Ensures that the bit set has the capacity to represent the given value. + * + * @param value {@code >= 0;} value to represent + */ + private void ensureCapacity(int value) { + if (value >= Bits.getMax(bits)) { + int[] newBits = Bits.makeBitSet( + Math.max(value + 1, 2 * Bits.getMax(bits))); + System.arraycopy(bits, 0, newBits, 0, bits.length); + bits = newBits; + } + } + + /** @inheritDoc */ + public void remove(int value) { + if (value < Bits.getMax(bits)) { + Bits.set(bits, value, false); + } + } + + /** @inheritDoc */ + public boolean has(int value) { + return (value < Bits.getMax(bits)) && Bits.get(bits, value); + } + + /** @inheritDoc */ + public void merge(IntSet other) { + if (other instanceof BitIntSet) { + BitIntSet o = (BitIntSet) other; + ensureCapacity(Bits.getMax(o.bits) + 1); + Bits.or(bits, o.bits); + } else if (other instanceof ListIntSet) { + ListIntSet o = (ListIntSet) other; + int sz = o.ints.size(); + + if (sz > 0) { + ensureCapacity(o.ints.get(sz - 1)); + } + for (int i = 0; i < o.ints.size(); i++) { + Bits.set(bits, o.ints.get(i), true); + } + } else { + IntIterator iter = other.iterator(); + while (iter.hasNext()) { + add(iter.next()); + } + } + } + + /** @inheritDoc */ + public int elements() { + return Bits.bitCount(bits); + } + + /** @inheritDoc */ + public IntIterator iterator() { + return new IntIterator() { + private int idx = Bits.findFirst(bits, 0); + + /** @inheritDoc */ + public boolean hasNext() { + return idx >= 0; + } + + /** @inheritDoc */ + public int next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + int ret = idx; + + idx = Bits.findFirst(bits, idx+1); + + return ret; + } + }; + } + + /** @inheritDoc */ + public String toString() { + StringBuilder sb = new StringBuilder(); + + sb.append('{'); + + boolean first = true; + for (int i = Bits.findFirst(bits, 0) + ; i >= 0 + ; i = Bits.findFirst(bits, i + 1)) { + if (!first) { + sb.append(", "); + } + first = false; + sb.append(i); + } + + sb.append('}'); + + return sb.toString(); + } +} diff --git a/dexgen/src/com/android/dexgen/util/Bits.java b/dexgen/src/com/android/dexgen/util/Bits.java new file mode 100644 index 0000000..5c97cc9 --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/Bits.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +/** + * Utilities for treating {@code int[]}s as bit sets. + */ +public final class Bits { + /** + * This class is uninstantiable. + */ + private Bits() { + // This space intentionally left blank. + } + + /** + * Constructs a bit set to contain bits up to the given index (exclusive). + * + * @param max {@code >= 0;} the maximum bit index (exclusive) + * @return {@code non-null;} an appropriately-constructed instance + */ + public static int[] makeBitSet(int max) { + int size = (max + 0x1f) >> 5; + return new int[size]; + } + + /** + * Gets the maximum index (exclusive) for the given bit set. + * + * @param bits {@code non-null;} bit set in question + * @return {@code >= 0;} the maximum index (exclusive) that may be set + */ + public static int getMax(int[] bits) { + return bits.length * 0x20; + } + + /** + * Gets the value of the bit at the given index. + * + * @param bits {@code non-null;} bit set to operate on + * @param idx {@code >= 0, < getMax(set);} which bit + * @return the value of the indicated bit + */ + public static boolean get(int[] bits, int idx) { + int arrayIdx = idx >> 5; + int bit = 1 << (idx & 0x1f); + return (bits[arrayIdx] & bit) != 0; + } + + /** + * Sets the given bit to the given value. + * + * @param bits {@code non-null;} bit set to operate on + * @param idx {@code >= 0, < getMax(set);} which bit + * @param value the new value for the bit + */ + public static void set(int[] bits, int idx, boolean value) { + int arrayIdx = idx >> 5; + int bit = 1 << (idx & 0x1f); + + if (value) { + bits[arrayIdx] |= bit; + } else { + bits[arrayIdx] &= ~bit; + } + } + + /** + * Sets the given bit to {@code true}. + * + * @param bits {@code non-null;} bit set to operate on + * @param idx {@code >= 0, < getMax(set);} which bit + */ + public static void set(int[] bits, int idx) { + int arrayIdx = idx >> 5; + int bit = 1 << (idx & 0x1f); + bits[arrayIdx] |= bit; + } + + /** + * Sets the given bit to {@code false}. + * + * @param bits {@code non-null;} bit set to operate on + * @param idx {@code >= 0, < getMax(set);} which bit + */ + public static void clear(int[] bits, int idx) { + int arrayIdx = idx >> 5; + int bit = 1 << (idx & 0x1f); + bits[arrayIdx] &= ~bit; + } + + /** + * Returns whether or not the given bit set is empty, that is, whether + * no bit is set to {@code true}. + * + * @param bits {@code non-null;} bit set to operate on + * @return {@code true} iff all bits are {@code false} + */ + public static boolean isEmpty(int[] bits) { + int len = bits.length; + + for (int i = 0; i < len; i++) { + if (bits[i] != 0) { + return false; + } + } + + return true; + } + + /** + * Gets the number of bits set to {@code true} in the given bit set. + * + * @param bits {@code non-null;} bit set to operate on + * @return {@code >= 0;} the bit count (aka population count) of the set + */ + public static int bitCount(int[] bits) { + int len = bits.length; + int count = 0; + + for (int i = 0; i < len; i++) { + count += Integer.bitCount(bits[i]); + } + + return count; + } + + /** + * Returns whether any bits are set to {@code true} in the + * specified range. + * + * @param bits {@code non-null;} bit set to operate on + * @param start {@code >= 0;} index of the first bit in the range (inclusive) + * @param end {@code >= 0;} index of the last bit in the range (exclusive) + * @return {@code true} if any bit is set to {@code true} in + * the indicated range + */ + public static boolean anyInRange(int[] bits, int start, int end) { + int idx = findFirst(bits, start); + return (idx >= 0) && (idx < end); + } + + /** + * Finds the lowest-order bit set at or after the given index in the + * given bit set. + * + * @param bits {@code non-null;} bit set to operate on + * @param idx {@code >= 0;} minimum index to return + * @return {@code >= -1;} lowest-order bit set at or after {@code idx}, + * or {@code -1} if there is no appropriate bit index to return + */ + public static int findFirst(int[] bits, int idx) { + int len = bits.length; + int minBit = idx & 0x1f; + + for (int arrayIdx = idx >> 5; arrayIdx < len; arrayIdx++) { + int word = bits[arrayIdx]; + if (word != 0) { + int bitIdx = findFirst(word, minBit); + if (bitIdx >= 0) { + return (arrayIdx << 5) + bitIdx; + } + } + minBit = 0; + } + + return -1; + } + + /** + * Finds the lowest-order bit set at or after the given index in the + * given {@code int}. + * + * @param value the value in question + * @param idx 0..31 the minimum bit index to return + * @return {@code >= -1;} lowest-order bit set at or after {@code idx}, + * or {@code -1} if there is no appropriate bit index to return + */ + public static int findFirst(int value, int idx) { + value &= ~((1 << idx) - 1); // Mask off too-low bits. + int result = Integer.numberOfTrailingZeros(value); + return (result == 32) ? -1 : result; + } + + /** + * Ors bit array {@code b} into bit array {@code a}. + * {@code a.length} must be greater than or equal to + * {@code b.length}. + * + * @param a {@code non-null;} int array to be ored with other argument. This + * argument is modified. + * @param b {@code non-null;} int array to be ored into {@code a}. This + * argument is not modified. + */ + public static void or(int[] a, int[] b) { + for (int i = 0; i < b.length; i++) { + a[i] |= b[i]; + } + } + + public static String toHuman(int[] bits) { + StringBuilder sb = new StringBuilder(); + + boolean needsComma = false; + + sb.append('{'); + + int bitsLength = 32 * bits.length; + for (int i = 0; i < bitsLength; i++) { + if (Bits.get(bits, i)) { + if (needsComma) { + sb.append(','); + } + needsComma = true; + sb.append(i); + } + } + sb.append('}'); + + return sb.toString(); + } +} diff --git a/dexgen/src/com/android/dexgen/util/ByteArray.java b/dexgen/src/com/android/dexgen/util/ByteArray.java new file mode 100644 index 0000000..93144b3 --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/ByteArray.java @@ -0,0 +1,361 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Wrapper for a {@code byte[]}, which provides read-only access and + * can "reveal" a partial slice of the underlying array. + * + * <b>Note:</b> Multibyte accessors all use big-endian order. + */ +public final class ByteArray { + /** {@code non-null;} underlying array */ + private final byte[] bytes; + + /** {@code >= 0}; start index of the slice (inclusive) */ + private final int start; + + /** {@code >= 0, <= bytes.length}; size computed as + * {@code end - start} (in the constructor) */ + private final int size; + + /** + * Constructs an instance. + * + * @param bytes {@code non-null;} the underlying array + * @param start {@code >= 0;} start index of the slice (inclusive) + * @param end {@code >= start, <= bytes.length;} end index of + * the slice (exclusive) + */ + public ByteArray(byte[] bytes, int start, int end) { + if (bytes == null) { + throw new NullPointerException("bytes == null"); + } + + if (start < 0) { + throw new IllegalArgumentException("start < 0"); + } + + if (end < start) { + throw new IllegalArgumentException("end < start"); + } + + if (end > bytes.length) { + throw new IllegalArgumentException("end > bytes.length"); + } + + this.bytes = bytes; + this.start = start; + this.size = end - start; + } + + /** + * Constructs an instance from an entire {@code byte[]}. + * + * @param bytes {@code non-null;} the underlying array + */ + public ByteArray(byte[] bytes) { + this(bytes, 0, bytes.length); + } + + /** + * Gets the size of the array, in bytes. + * + * @return {@code >= 0;} the size + */ + public int size() { + return size; + } + + /** + * Returns a slice (that is, a sub-array) of this instance. + * + * @param start {@code >= 0;} start index of the slice (inclusive) + * @param end {@code >= start, <= size();} end index of + * the slice (exclusive) + * @return {@code non-null;} the slice + */ + public ByteArray slice(int start, int end) { + checkOffsets(start, end); + return new ByteArray(bytes, start + this.start, end + this.start); + } + + /** + * Returns the offset into the given array represented by the given + * offset into this instance. + * + * @param offset offset into this instance + * @param bytes {@code non-null;} (alleged) underlying array + * @return corresponding offset into {@code bytes} + * @throws IllegalArgumentException thrown if {@code bytes} is + * not the underlying array of this instance + */ + public int underlyingOffset(int offset, byte[] bytes) { + if (bytes != this.bytes) { + throw new IllegalArgumentException("wrong bytes"); + } + + return start + offset; + } + + /** + * Gets the {@code signed byte} value at a particular offset. + * + * @param off {@code >= 0, < size();} offset to fetch + * @return {@code signed byte} at that offset + */ + public int getByte(int off) { + checkOffsets(off, off + 1); + return getByte0(off); + } + + /** + * Gets the {@code signed short} value at a particular offset. + * + * @param off {@code >= 0, < (size() - 1);} offset to fetch + * @return {@code signed short} at that offset + */ + public int getShort(int off) { + checkOffsets(off, off + 2); + return (getByte0(off) << 8) | getUnsignedByte0(off + 1); + } + + /** + * Gets the {@code signed int} value at a particular offset. + * + * @param off {@code >= 0, < (size() - 3);} offset to fetch + * @return {@code signed int} at that offset + */ + public int getInt(int off) { + checkOffsets(off, off + 4); + return (getByte0(off) << 24) | + (getUnsignedByte0(off + 1) << 16) | + (getUnsignedByte0(off + 2) << 8) | + getUnsignedByte0(off + 3); + } + + /** + * Gets the {@code signed long} value at a particular offset. + * + * @param off {@code >= 0, < (size() - 7);} offset to fetch + * @return {@code signed int} at that offset + */ + public long getLong(int off) { + checkOffsets(off, off + 8); + int part1 = (getByte0(off) << 24) | + (getUnsignedByte0(off + 1) << 16) | + (getUnsignedByte0(off + 2) << 8) | + getUnsignedByte0(off + 3); + int part2 = (getByte0(off + 4) << 24) | + (getUnsignedByte0(off + 5) << 16) | + (getUnsignedByte0(off + 6) << 8) | + getUnsignedByte0(off + 7); + + return (part2 & 0xffffffffL) | ((long) part1) << 32; + } + + /** + * Gets the {@code unsigned byte} value at a particular offset. + * + * @param off {@code >= 0, < size();} offset to fetch + * @return {@code unsigned byte} at that offset + */ + public int getUnsignedByte(int off) { + checkOffsets(off, off + 1); + return getUnsignedByte0(off); + } + + /** + * Gets the {@code unsigned short} value at a particular offset. + * + * @param off {@code >= 0, < (size() - 1);} offset to fetch + * @return {@code unsigned short} at that offset + */ + public int getUnsignedShort(int off) { + checkOffsets(off, off + 2); + return (getUnsignedByte0(off) << 8) | getUnsignedByte0(off + 1); + } + + /** + * Copies the contents of this instance into the given raw + * {@code byte[]} at the given offset. The given array must be + * large enough. + * + * @param out {@code non-null;} array to hold the output + * @param offset {@code non-null;} index into {@code out} for the first + * byte of output + */ + public void getBytes(byte[] out, int offset) { + if ((out.length - offset) < size) { + throw new IndexOutOfBoundsException("(out.length - offset) < " + + "size()"); + } + + System.arraycopy(bytes, start, out, offset, size); + } + + /** + * Checks a range of offsets for validity, throwing if invalid. + * + * @param s start offset (inclusive) + * @param e end offset (exclusive) + */ + private void checkOffsets(int s, int e) { + if ((s < 0) || (e < s) || (e > size)) { + throw new IllegalArgumentException("bad range: " + s + ".." + e + + "; actual size " + size); + } + } + + /** + * Gets the {@code signed byte} value at the given offset, + * without doing any argument checking. + * + * @param off offset to fetch + * @return byte at that offset + */ + private int getByte0(int off) { + return bytes[start + off]; + } + + /** + * Gets the {@code unsigned byte} value at the given offset, + * without doing any argument checking. + * + * @param off offset to fetch + * @return byte at that offset + */ + private int getUnsignedByte0(int off) { + return bytes[start + off] & 0xff; + } + + /** + * Gets a {@code DataInputStream} that reads from this instance, + * with the cursor starting at the beginning of this instance's data. + * <b>Note:</b> The returned instance may be cast to {@link #GetCursor} + * if needed. + * + * @return {@code non-null;} an appropriately-constructed + * {@code DataInputStream} instance + */ + public MyDataInputStream makeDataInputStream() { + return new MyDataInputStream(makeInputStream()); + } + + /** + * Gets a {@code InputStream} that reads from this instance, + * with the cursor starting at the beginning of this instance's data. + * <b>Note:</b> The returned instance may be cast to {@link #GetCursor} + * if needed. + * + * @return {@code non-null;} an appropriately-constructed + * {@code InputStream} instancex + */ + public MyInputStream makeInputStream() { + return new MyInputStream(); + } + + /** + * Helper interface that allows one to get the cursor (of a stream). + */ + public interface GetCursor { + /** + * Gets the current cursor. + * + * @return {@code 0..size();} the cursor + */ + public int getCursor(); + } + + /** + * Helper class for {@link #makeInputStream}, which implements the + * stream functionality. + */ + public class MyInputStream extends InputStream { + /** 0..size; the cursor */ + private int cursor; + + /** 0..size; the mark */ + private int mark; + + public MyInputStream() { + cursor = 0; + mark = 0; + } + + public int read() throws IOException { + if (cursor >= size) { + return -1; + } + + int result = getUnsignedByte0(cursor); + cursor++; + return result; + } + + public int read(byte[] arr, int offset, int length) { + if ((offset + length) > arr.length) { + length = arr.length - offset; + } + + int maxLength = size - cursor; + if (length > maxLength) { + length = maxLength; + } + + System.arraycopy(bytes, cursor + start, arr, offset, length); + cursor += length; + return length; + } + + public int available() { + return size - cursor; + } + + public void mark(int reserve) { + mark = cursor; + } + + public void reset() { + cursor = mark; + } + + public boolean markSupported() { + return true; + } + } + + /** + * Helper class for {@link #makeDataInputStream}. This is used + * simply so that the cursor of a wrapped {@link #MyInputStream} + * instance may be easily determined. + */ + public static class MyDataInputStream extends DataInputStream { + /** {@code non-null;} the underlying {@link #MyInputStream} */ + private final MyInputStream wrapped; + + public MyDataInputStream(MyInputStream wrapped) { + super(wrapped); + + this.wrapped = wrapped; + } + } +} diff --git a/dexgen/src/com/android/dexgen/util/ByteArrayAnnotatedOutput.java b/dexgen/src/com/android/dexgen/util/ByteArrayAnnotatedOutput.java new file mode 100644 index 0000000..5fad9a9 --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/ByteArrayAnnotatedOutput.java @@ -0,0 +1,639 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; + +/** + * Implementation of {@link AnnotatedOutput} which stores the written data + * into a {@code byte[]}. + * + * <p><b>Note:</b> As per the {@link Output} interface, multi-byte + * writes all use little-endian order.</p> + */ +public final class ByteArrayAnnotatedOutput + implements AnnotatedOutput { + /** default size for stretchy instances */ + private static final int DEFAULT_SIZE = 1000; + + /** + * whether the instance is stretchy, that is, whether its array + * may be resized to increase capacity + */ + private final boolean stretchy; + + /** {@code non-null;} the data itself */ + private byte[] data; + + /** {@code >= 0;} current output cursor */ + private int cursor; + + /** whether annotations are to be verbose */ + private boolean verbose; + + /** + * {@code null-ok;} list of annotations, or {@code null} if this instance + * isn't keeping them + */ + private ArrayList<Annotation> annotations; + + /** {@code >= 40 (if used);} the desired maximum annotation width */ + private int annotationWidth; + + /** + * {@code >= 8 (if used);} the number of bytes of hex output to use + * in annotations + */ + private int hexCols; + + /** + * Constructs an instance with a fixed maximum size. Note that the + * given array is the only one that will be used to store data. In + * particular, no reallocation will occur in order to expand the + * capacity of the resulting instance. Also, the constructed + * instance does not keep annotations by default. + * + * @param data {@code non-null;} data array to use for output + */ + public ByteArrayAnnotatedOutput(byte[] data) { + this(data, false); + } + + /** + * Constructs a "stretchy" instance. The underlying array may be + * reallocated. The constructed instance does not keep annotations + * by default. + */ + public ByteArrayAnnotatedOutput() { + this(new byte[DEFAULT_SIZE], true); + } + + /** + * Internal constructor. + * + * @param data {@code non-null;} data array to use for output + * @param stretchy whether the instance is to be stretchy + */ + private ByteArrayAnnotatedOutput(byte[] data, boolean stretchy) { + if (data == null) { + throw new NullPointerException("data == null"); + } + + this.stretchy = stretchy; + this.data = data; + this.cursor = 0; + this.verbose = false; + this.annotations = null; + this.annotationWidth = 0; + this.hexCols = 0; + } + + /** + * Gets the underlying {@code byte[]} of this instance, which + * may be larger than the number of bytes written + * + * @see #toByteArray + * + * @return {@code non-null;} the {@code byte[]} + */ + public byte[] getArray() { + return data; + } + + /** + * Constructs and returns a new {@code byte[]} that contains + * the written contents exactly (that is, with no extra unwritten + * bytes at the end). + * + * @see #getArray + * + * @return {@code non-null;} an appropriately-constructed array + */ + public byte[] toByteArray() { + byte[] result = new byte[cursor]; + System.arraycopy(data, 0, result, 0, cursor); + return result; + } + + /** {@inheritDoc} */ + public int getCursor() { + return cursor; + } + + /** {@inheritDoc} */ + public void assertCursor(int expectedCursor) { + if (cursor != expectedCursor) { + throw new ExceptionWithContext("expected cursor " + + expectedCursor + "; actual value: " + cursor); + } + } + + /** {@inheritDoc} */ + public void writeByte(int value) { + int writeAt = cursor; + int end = writeAt + 1; + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + data[writeAt] = (byte) value; + cursor = end; + } + + /** {@inheritDoc} */ + public void writeShort(int value) { + int writeAt = cursor; + int end = writeAt + 2; + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + data[writeAt] = (byte) value; + data[writeAt + 1] = (byte) (value >> 8); + cursor = end; + } + + /** {@inheritDoc} */ + public void writeInt(int value) { + int writeAt = cursor; + int end = writeAt + 4; + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + data[writeAt] = (byte) value; + data[writeAt + 1] = (byte) (value >> 8); + data[writeAt + 2] = (byte) (value >> 16); + data[writeAt + 3] = (byte) (value >> 24); + cursor = end; + } + + /** {@inheritDoc} */ + public void writeLong(long value) { + int writeAt = cursor; + int end = writeAt + 8; + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + int half = (int) value; + data[writeAt] = (byte) half; + data[writeAt + 1] = (byte) (half >> 8); + data[writeAt + 2] = (byte) (half >> 16); + data[writeAt + 3] = (byte) (half >> 24); + + half = (int) (value >> 32); + data[writeAt + 4] = (byte) half; + data[writeAt + 5] = (byte) (half >> 8); + data[writeAt + 6] = (byte) (half >> 16); + data[writeAt + 7] = (byte) (half >> 24); + + cursor = end; + } + + /** {@inheritDoc} */ + public int writeUnsignedLeb128(int value) { + int remaining = value >> 7; + int count = 0; + + while (remaining != 0) { + writeByte((value & 0x7f) | 0x80); + value = remaining; + remaining >>= 7; + count++; + } + + writeByte(value & 0x7f); + return count + 1; + } + + /** {@inheritDoc} */ + public int writeSignedLeb128(int value) { + int remaining = value >> 7; + int count = 0; + boolean hasMore = true; + int end = ((value & Integer.MIN_VALUE) == 0) ? 0 : -1; + + while (hasMore) { + hasMore = (remaining != end) + || ((remaining & 1) != ((value >> 6) & 1)); + + writeByte((value & 0x7f) | (hasMore ? 0x80 : 0)); + value = remaining; + remaining >>= 7; + count++; + } + + return count; + } + + /** {@inheritDoc} */ + public void write(ByteArray bytes) { + int blen = bytes.size(); + int writeAt = cursor; + int end = writeAt + blen; + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + bytes.getBytes(data, writeAt); + cursor = end; + } + + /** {@inheritDoc} */ + public void write(byte[] bytes, int offset, int length) { + int writeAt = cursor; + int end = writeAt + length; + int bytesEnd = offset + length; + + // twos-complement math trick: ((x < 0) || (y < 0)) <=> ((x|y) < 0) + if (((offset | length | end) < 0) || (bytesEnd > bytes.length)) { + throw new IndexOutOfBoundsException("bytes.length " + + bytes.length + "; " + + offset + "..!" + end); + } + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + System.arraycopy(bytes, offset, data, writeAt, length); + cursor = end; + } + + /** {@inheritDoc} */ + public void write(byte[] bytes) { + write(bytes, 0, bytes.length); + } + + /** {@inheritDoc} */ + public void writeZeroes(int count) { + if (count < 0) { + throw new IllegalArgumentException("count < 0"); + } + + int end = cursor + count; + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + /* + * There is no need to actually write zeroes, since the array is + * already preinitialized with zeroes. + */ + + cursor = end; + } + + /** {@inheritDoc} */ + public void alignTo(int alignment) { + int mask = alignment - 1; + + if ((alignment < 0) || ((mask & alignment) != 0)) { + throw new IllegalArgumentException("bogus alignment"); + } + + int end = (cursor + mask) & ~mask; + + if (stretchy) { + ensureCapacity(end); + } else if (end > data.length) { + throwBounds(); + return; + } + + /* + * There is no need to actually write zeroes, since the array is + * already preinitialized with zeroes. + */ + + cursor = end; + } + + /** {@inheritDoc} */ + public boolean annotates() { + return (annotations != null); + } + + /** {@inheritDoc} */ + public boolean isVerbose() { + return verbose; + } + + /** {@inheritDoc} */ + public void annotate(String msg) { + if (annotations == null) { + return; + } + + endAnnotation(); + annotations.add(new Annotation(cursor, msg)); + } + + /** {@inheritDoc} */ + public void annotate(int amt, String msg) { + if (annotations == null) { + return; + } + + endAnnotation(); + + int asz = annotations.size(); + int lastEnd = (asz == 0) ? 0 : annotations.get(asz - 1).getEnd(); + int startAt; + + if (lastEnd <= cursor) { + startAt = cursor; + } else { + startAt = lastEnd; + } + + annotations.add(new Annotation(startAt, startAt + amt, msg)); + } + + /** {@inheritDoc} */ + public void endAnnotation() { + if (annotations == null) { + return; + } + + int sz = annotations.size(); + + if (sz != 0) { + annotations.get(sz - 1).setEndIfUnset(cursor); + } + } + + /** {@inheritDoc} */ + public int getAnnotationWidth() { + int leftWidth = 8 + (hexCols * 2) + (hexCols / 2); + + return annotationWidth - leftWidth; + } + + /** + * Indicates that this instance should keep annotations. This method may + * be called only once per instance, and only before any data has been + * written to the it. + * + * @param annotationWidth {@code >= 40;} the desired maximum annotation width + * @param verbose whether or not to indicate verbose annotations + */ + public void enableAnnotations(int annotationWidth, boolean verbose) { + if ((annotations != null) || (cursor != 0)) { + throw new RuntimeException("cannot enable annotations"); + } + + if (annotationWidth < 40) { + throw new IllegalArgumentException("annotationWidth < 40"); + } + + int hexCols = (((annotationWidth - 7) / 15) + 1) & ~1; + if (hexCols < 6) { + hexCols = 6; + } else if (hexCols > 10) { + hexCols = 10; + } + + this.annotations = new ArrayList<Annotation>(1000); + this.annotationWidth = annotationWidth; + this.hexCols = hexCols; + this.verbose = verbose; + } + + /** + * Finishes up annotation processing. This closes off any open + * annotations and removes annotations that don't refer to written + * data. + */ + public void finishAnnotating() { + // Close off the final annotation, if any. + endAnnotation(); + + // Remove annotations that refer to unwritten data. + if (annotations != null) { + int asz = annotations.size(); + while (asz > 0) { + Annotation last = annotations.get(asz - 1); + if (last.getStart() > cursor) { + annotations.remove(asz - 1); + asz--; + } else if (last.getEnd() > cursor) { + last.setEnd(cursor); + break; + } else { + break; + } + } + } + } + + /** + * Writes the annotated content of this instance to the given writer. + * + * @param out {@code non-null;} where to write to + */ + public void writeAnnotationsTo(Writer out) throws IOException { + int width2 = getAnnotationWidth(); + int width1 = annotationWidth - width2 - 1; + + TwoColumnOutput twoc = new TwoColumnOutput(out, width1, width2, "|"); + Writer left = twoc.getLeft(); + Writer right = twoc.getRight(); + int leftAt = 0; // left-hand byte output cursor + int rightAt = 0; // right-hand annotation index + int rightSz = annotations.size(); + + while ((leftAt < cursor) && (rightAt < rightSz)) { + Annotation a = annotations.get(rightAt); + int start = a.getStart(); + int end; + String text; + + if (leftAt < start) { + // This is an area with no annotation. + end = start; + start = leftAt; + text = ""; + } else { + // This is an area with an annotation. + end = a.getEnd(); + text = a.getText(); + rightAt++; + } + + left.write(Hex.dump(data, start, end - start, start, hexCols, 6)); + right.write(text); + twoc.flush(); + leftAt = end; + } + + if (leftAt < cursor) { + // There is unannotated output at the end. + left.write(Hex.dump(data, leftAt, cursor - leftAt, leftAt, + hexCols, 6)); + } + + while (rightAt < rightSz) { + // There are zero-byte annotations at the end. + right.write(annotations.get(rightAt).getText()); + rightAt++; + } + + twoc.flush(); + } + + /** + * Throws the excpetion for when an attempt is made to write past the + * end of the instance. + */ + private static void throwBounds() { + throw new IndexOutOfBoundsException("attempt to write past the end"); + } + + /** + * Reallocates the underlying array if necessary. Calls to this method + * should be guarded by a test of {@link #stretchy}. + * + * @param desiredSize {@code >= 0;} the desired minimum total size of the array + */ + private void ensureCapacity(int desiredSize) { + if (data.length < desiredSize) { + byte[] newData = new byte[desiredSize * 2 + 1000]; + System.arraycopy(data, 0, newData, 0, cursor); + data = newData; + } + } + + /** + * Annotation on output. + */ + private static class Annotation { + /** {@code >= 0;} start of annotated range (inclusive) */ + private final int start; + + /** + * {@code >= 0;} end of annotated range (exclusive); + * {@code Integer.MAX_VALUE} if unclosed + */ + private int end; + + /** {@code non-null;} annotation text */ + private final String text; + + /** + * Constructs an instance. + * + * @param start {@code >= 0;} start of annotated range + * @param end {@code >= start;} end of annotated range (exclusive) or + * {@code Integer.MAX_VALUE} if unclosed + * @param text {@code non-null;} annotation text + */ + public Annotation(int start, int end, String text) { + this.start = start; + this.end = end; + this.text = text; + } + + /** + * Constructs an instance. It is initally unclosed. + * + * @param start {@code >= 0;} start of annotated range + * @param text {@code non-null;} annotation text + */ + public Annotation(int start, String text) { + this(start, Integer.MAX_VALUE, text); + } + + /** + * Sets the end as given, but only if the instance is unclosed; + * otherwise, do nothing. + * + * @param end {@code >= start;} the end + */ + public void setEndIfUnset(int end) { + if (this.end == Integer.MAX_VALUE) { + this.end = end; + } + } + + /** + * Sets the end as given. + * + * @param end {@code >= start;} the end + */ + public void setEnd(int end) { + this.end = end; + } + + /** + * Gets the start. + * + * @return the start + */ + public int getStart() { + return start; + } + + /** + * Gets the end. + * + * @return the end + */ + public int getEnd() { + return end; + } + + /** + * Gets the text. + * + * @return {@code non-null;} the text + */ + public String getText() { + return text; + } + } +} diff --git a/dexgen/src/com/android/dexgen/util/DexClassLoaderHelper.java b/dexgen/src/com/android/dexgen/util/DexClassLoaderHelper.java new file mode 100644 index 0000000..97118ea --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/DexClassLoaderHelper.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +import dalvik.system.DexClassLoader; + +/** + * Class used indirectly for loading generated dex classes. It allows the caller + * to obtain appropriate {@code DexClassLoader} instance, which can be then used for + * loading classes. + */ +public class DexClassLoaderHelper { + + private static class DexClassLoaderHelperHolder { + private static final DexClassLoaderHelper INSTANCE = new DexClassLoaderHelper(); + } + + private DexClassLoaderHelper() { + // intentionally empty to disable direct instantiation + } + + /** + * Returns the sole instance of {@code DexClassLoaderHelper}. + * + * @return dex {@code DexClassLoaderHelper} sole instance + */ + public static DexClassLoaderHelper getInstance() { + return DexClassLoaderHelperHolder.INSTANCE; + } + + /** + * Creates and returns DexClassLoader instance with its classpath + * set to {@code pathHolder}. + * + * @param pathHolder {@code non-null;} location of jar archive containing dex + * classes canned into a working PathHolder instance. + * @return dex class loader instance with its classpath set to location + * indicated by {@code pathHolder} + */ + public ClassLoader getDexClassLoader(PathHolder pathHolder) { + ClassLoader myLoader = DexClassLoaderHelper.class.getClassLoader(); + return new DexClassLoader(pathHolder.getJarFilePath(), pathHolder.getDirLocation(), + null, myLoader); + } +}
\ No newline at end of file diff --git a/dexgen/src/com/android/dexgen/util/DexClassLoadingException.java b/dexgen/src/com/android/dexgen/util/DexClassLoadingException.java new file mode 100644 index 0000000..ba4d350 --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/DexClassLoadingException.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +/** + * An exception type used to aggregate all the unexpected situations while + * trying to save dex file with generated class and load the class afterwards. + */ +public class DexClassLoadingException extends Exception { + + /** + * Encapsulates any checked exception being thrown in time between saving + * generated dex class to a file and loading it via DexClassLoader with + * an user-friendly message and passing the original exception as well. + * + * @param thr {@code non-null;} lower level exception with more detailed + * error message + */ + public DexClassLoadingException(Throwable thr) { + super("Loading generated dex class has failed", thr); + } +}
\ No newline at end of file diff --git a/dexgen/src/com/android/dexgen/util/DexJarMaker.java b/dexgen/src/com/android/dexgen/util/DexJarMaker.java new file mode 100644 index 0000000..4fe5a56 --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/DexJarMaker.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; + +/** + * Helper class used to encapsulate generated .dex file into .jar + * so that it fits {@code DexClassLoader} constructor. + */ +public class DexJarMaker { + + /** indicates name of the dex file added to jar */ + public static final String DEX_FILE_NAME_IN_JAR = "classes" + PathHolder.DEX_FILE_EXTENSION; + + /** {@code non-null;} storage for all the paths related to current dex file */ + private final PathHolder pathHolder; + + public DexJarMaker(PathHolder pathHolder) { + this.pathHolder = pathHolder; + } + + /** Packs previously added files into a single jar archive. */ + public void create() throws DexClassLoadingException { + Manifest manifest = new Manifest(); + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); + JarOutputStream target = null; + try { + target = new JarOutputStream( + new BufferedOutputStream(new FileOutputStream(pathHolder.getJarFilePath())), + manifest); + add(new File(pathHolder.getDexFilePath()), target); + + } catch (IOException e) { + throw new DexClassLoadingException(e); + } + finally { + try { + if (target != null) { + target.close(); + } + } catch(IOException e) { + // Ignoring deliberately in order to keep the original exception clear. + } + } + } + + /** + * Adds indicated file to the requested archive. + * + * @param source {@code non-null;} dex file to add + * @param target {@code non-null;} target jar archive + * @throws IOException + */ + private void add(File source, JarOutputStream target) throws IOException { + + if (!source.isFile()) { + throw new IllegalArgumentException("Wrong source dex file provided"); + } + + BufferedInputStream in = new BufferedInputStream(new FileInputStream(source)); + JarEntry entry = new JarEntry(DEX_FILE_NAME_IN_JAR); + entry.setTime(source.lastModified()); + target.putNextEntry(entry); + + int curr = -1; + while ((curr = in.read()) != -1) { + target.write(curr); + } + target.closeEntry(); + } +}
\ No newline at end of file diff --git a/dexgen/src/com/android/dexgen/util/ExceptionWithContext.java b/dexgen/src/com/android/dexgen/util/ExceptionWithContext.java new file mode 100644 index 0000000..67e7f72 --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/ExceptionWithContext.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +import java.io.PrintStream; +import java.io.PrintWriter; + +/** + * Exception which carries around structured context. + */ +public class ExceptionWithContext + extends RuntimeException { + /** {@code non-null;} human-oriented context of the exception */ + private StringBuffer context; + + /** + * Augments the given exception with the given context, and return the + * result. The result is either the given exception if it was an + * {@link ExceptionWithContext}, or a newly-constructed exception if it + * was not. + * + * @param ex {@code non-null;} the exception to augment + * @param str {@code non-null;} context to add + * @return {@code non-null;} an appropriate instance + */ + public static ExceptionWithContext withContext(Throwable ex, String str) { + ExceptionWithContext ewc; + + if (ex instanceof ExceptionWithContext) { + ewc = (ExceptionWithContext) ex; + } else { + ewc = new ExceptionWithContext(ex); + } + + ewc.addContext(str); + return ewc; + } + + /** + * Constructs an instance. + * + * @param message human-oriented message + */ + public ExceptionWithContext(String message) { + this(message, null); + } + + /** + * Constructs an instance. + * + * @param cause {@code null-ok;} exception that caused this one + */ + public ExceptionWithContext(Throwable cause) { + this(null, cause); + } + + /** + * Constructs an instance. + * + * @param message human-oriented message + * @param cause {@code null-ok;} exception that caused this one + */ + public ExceptionWithContext(String message, Throwable cause) { + super((message != null) ? message : + (cause != null) ? cause.getMessage() : null, + cause); + + if (cause instanceof ExceptionWithContext) { + String ctx = ((ExceptionWithContext) cause).context.toString(); + context = new StringBuffer(ctx.length() + 200); + context.append(ctx); + } else { + context = new StringBuffer(200); + } + } + + /** {@inheritDoc} */ + @Override + public void printStackTrace(PrintStream out) { + super.printStackTrace(out); + out.println(context); + } + + /** {@inheritDoc} */ + @Override + public void printStackTrace(PrintWriter out) { + super.printStackTrace(out); + out.println(context); + } + + /** + * Adds a line of context to this instance. + * + * @param str {@code non-null;} new context + */ + public void addContext(String str) { + if (str == null) { + throw new NullPointerException("str == null"); + } + + context.append(str); + if (!str.endsWith("\n")) { + context.append('\n'); + } + } + + /** + * Gets the context. + * + * @return {@code non-null;} the context + */ + public String getContext() { + return context.toString(); + } + + /** + * Prints the message and context. + * + * @param out {@code non-null;} where to print to + */ + public void printContext(PrintStream out) { + out.println(getMessage()); + out.print(context); + } + + /** + * Prints the message and context. + * + * @param out {@code non-null;} where to print to + */ + public void printContext(PrintWriter out) { + out.println(getMessage()); + out.print(context); + } +} diff --git a/dexgen/src/com/android/dexgen/util/FileUtils.java b/dexgen/src/com/android/dexgen/util/FileUtils.java new file mode 100644 index 0000000..a5bbff4 --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/FileUtils.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +/** + * File I/O utilities. + */ +public final class FileUtils { + /** + * This class is uninstantiable. + */ + private FileUtils() { + // This space intentionally left blank. + } + + /** + * Reads the named file, translating {@link IOException} to a + * {@link RuntimeException} of some sort. + * + * @param fileName {@code non-null;} name of the file to read + * @return {@code non-null;} contents of the file + */ + public static byte[] readFile(String fileName) { + File file = new File(fileName); + return readFile(file); + } + + /** + * Reads the given file, translating {@link IOException} to a + * {@link RuntimeException} of some sort. + * + * @param file {@code non-null;} the file to read + * @return {@code non-null;} contents of the file + */ + public static byte[] readFile(File file) { + if (!file.exists()) { + throw new RuntimeException(file + ": file not found"); + } + + if (!file.isFile()) { + throw new RuntimeException(file + ": not a file"); + } + + if (!file.canRead()) { + throw new RuntimeException(file + ": file not readable"); + } + + long longLength = file.length(); + int length = (int) longLength; + if (length != longLength) { + throw new RuntimeException(file + ": file too long"); + } + + byte[] result = new byte[length]; + + try { + FileInputStream in = new FileInputStream(file); + int at = 0; + while (length > 0) { + int amt = in.read(result, at, length); + if (amt == -1) { + throw new RuntimeException(file + ": unexpected EOF"); + } + at += amt; + length -= amt; + } + in.close(); + } catch (IOException ex) { + throw new RuntimeException(file + ": trouble reading", ex); + } + + return result; + } +} diff --git a/dexgen/src/com/android/dexgen/util/FixedSizeList.java b/dexgen/src/com/android/dexgen/util/FixedSizeList.java new file mode 100644 index 0000000..039b5b0 --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/FixedSizeList.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +import java.util.Arrays; + +/** + * Simple (mostly) fixed-size list of objects, which may be made immutable. + */ +public class FixedSizeList + extends MutabilityControl implements ToHuman { + /** {@code non-null;} array of elements */ + private Object[] arr; + + /** + * Constructs an instance. All indices initially contain {@code null}. + * + * @param size the size of the list + */ + public FixedSizeList(int size) { + super(size != 0); + + try { + arr = new Object[size]; + } catch (NegativeArraySizeException ex) { + // Translate the exception. + throw new IllegalArgumentException("size < 0"); + } + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (this == other) { + // Easy out. + return true; + } + + if ((other == null) || (getClass() != other.getClass())) { + // Another easy out. + return false; + } + + FixedSizeList list = (FixedSizeList) other; + return Arrays.equals(arr, list.arr); + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return Arrays.hashCode(arr); + } + + /** {@inheritDoc} */ + @Override + public String toString() { + String name = getClass().getName(); + + return toString0(name.substring(name.lastIndexOf('.') + 1) + '{', + ", ", + "}", + false); + } + + /** + * {@inheritDoc} + * + * This method will only work if every element of the list + * implements {@link ToHuman}. + */ + public String toHuman() { + String name = getClass().getName(); + + return toString0(name.substring(name.lastIndexOf('.') + 1) + '{', + ", ", + "}", + true); + } + + /** + * Gets a customized string form for this instance. + * + * @param prefix {@code null-ok;} prefix for the start of the result + * @param separator {@code null-ok;} separator to insert between each item + * @param suffix {@code null-ok;} suffix for the end of the result + * @return {@code non-null;} the custom string + */ + public String toString(String prefix, String separator, String suffix) { + return toString0(prefix, separator, suffix, false); + } + + /** + * Gets a customized human string for this instance. This method will + * only work if every element of the list implements {@link + * ToHuman}. + * + * @param prefix {@code null-ok;} prefix for the start of the result + * @param separator {@code null-ok;} separator to insert between each item + * @param suffix {@code null-ok;} suffix for the end of the result + * @return {@code non-null;} the custom string + */ + public String toHuman(String prefix, String separator, String suffix) { + return toString0(prefix, separator, suffix, true); + } + + /** + * Gets the number of elements in this list. + */ + public final int size() { + return arr.length; + } + + /** + * Shrinks this instance to fit, by removing any unset + * ({@code null}) elements, leaving the remaining elements in + * their original order. + */ + public void shrinkToFit() { + int sz = arr.length; + int newSz = 0; + + for (int i = 0; i < sz; i++) { + if (arr[i] != null) { + newSz++; + } + } + + if (sz == newSz) { + return; + } + + throwIfImmutable(); + + Object[] newa = new Object[newSz]; + int at = 0; + + for (int i = 0; i < sz; i++) { + Object one = arr[i]; + if (one != null) { + newa[at] = one; + at++; + } + } + + arr = newa; + if (newSz == 0) { + setImmutable(); + } + } + + /** + * Gets the indicated element. It is an error to call this with the + * index for an element which was never set; if you do that, this + * will throw {@code NullPointerException}. This method is + * protected so that subclasses may offer a safe type-checked + * public interface to their clients. + * + * @param n {@code >= 0, < size();} which element + * @return {@code non-null;} the indicated element + */ + protected final Object get0(int n) { + try { + Object result = arr[n]; + + if (result == null) { + throw new NullPointerException("unset: " + n); + } + + return result; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + return throwIndex(n); + } + } + + /** + * Gets the indicated element, allowing {@code null}s to be + * returned. This method is protected so that subclasses may + * (optionally) offer a safe type-checked public interface to + * their clients. + * + * @param n {@code >= 0, < size();} which element + * @return {@code null-ok;} the indicated element + */ + protected final Object getOrNull0(int n) { + return arr[n]; + } + + /** + * Sets the element at the given index, but without doing any type + * checks on the element. This method is protected so that + * subclasses may offer a safe type-checked public interface to + * their clients. + * + * @param n {@code >= 0, < size();} which element + * @param obj {@code null-ok;} the value to store + */ + protected final void set0(int n, Object obj) { + throwIfImmutable(); + + try { + arr[n] = obj; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + throwIndex(n); + } + } + + /** + * Throws the appropriate exception for the given index value. + * + * @param n the index value + * @return never + * @throws IndexOutOfBoundsException always thrown + */ + private Object throwIndex(int n) { + if (n < 0) { + throw new IndexOutOfBoundsException("n < 0"); + } + + throw new IndexOutOfBoundsException("n >= size()"); + } + + /** + * Helper for {@link #toString} and {@link #toHuman}, which both of + * those call to pretty much do everything. + * + * @param prefix {@code null-ok;} prefix for the start of the result + * @param separator {@code null-ok;} separator to insert between each item + * @param suffix {@code null-ok;} suffix for the end of the result + * @param human whether the output is to be human + * @return {@code non-null;} the custom string + */ + private String toString0(String prefix, String separator, String suffix, + boolean human) { + int len = arr.length; + StringBuffer sb = new StringBuffer(len * 10 + 10); + + if (prefix != null) { + sb.append(prefix); + } + + for (int i = 0; i < len; i++) { + if ((i != 0) && (separator != null)) { + sb.append(separator); + } + + if (human) { + sb.append(((ToHuman) arr[i]).toHuman()); + } else { + sb.append(arr[i]); + } + } + + if (suffix != null) { + sb.append(suffix); + } + + return sb.toString(); + } + +} diff --git a/dexgen/src/com/android/dexgen/util/Hex.java b/dexgen/src/com/android/dexgen/util/Hex.java new file mode 100644 index 0000000..4dafb77 --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/Hex.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +/** + * Utilities for formatting numbers as hexadecimal. + */ +public final class Hex { + /** + * This class is uninstantiable. + */ + private Hex() { + // This space intentionally left blank. + } + + /** + * Formats a {@code long} as an 8-byte unsigned hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u8(long v) { + char[] result = new char[16]; + for (int i = 0; i < 16; i++) { + result[15 - i] = Character.forDigit((int) v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 4-byte unsigned hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u4(int v) { + char[] result = new char[8]; + for (int i = 0; i < 8; i++) { + result[7 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 3-byte unsigned hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u3(int v) { + char[] result = new char[6]; + for (int i = 0; i < 6; i++) { + result[5 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 2-byte unsigned hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u2(int v) { + char[] result = new char[4]; + for (int i = 0; i < 4; i++) { + result[3 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as either a 2-byte unsigned hex value + * (if the value is small enough) or a 4-byte unsigned hex value (if + * not). + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u2or4(int v) { + if (v == (char) v) { + return u2(v); + } else { + return u4(v); + } + } + + /** + * Formats an {@code int} as a 1-byte unsigned hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String u1(int v) { + char[] result = new char[2]; + for (int i = 0; i < 2; i++) { + result[1 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 4-bit unsigned hex nibble. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String uNibble(int v) { + char[] result = new char[1]; + + result[0] = Character.forDigit(v & 0x0f, 16); + return new String(result); + } + + /** + * Formats a {@code long} as an 8-byte signed hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String s8(long v) { + char[] result = new char[17]; + + if (v < 0) { + result[0] = '-'; + v = -v; + } else { + result[0] = '+'; + } + + for (int i = 0; i < 16; i++) { + result[16 - i] = Character.forDigit((int) v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 4-byte signed hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String s4(int v) { + char[] result = new char[9]; + + if (v < 0) { + result[0] = '-'; + v = -v; + } else { + result[0] = '+'; + } + + for (int i = 0; i < 8; i++) { + result[8 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 2-byte signed hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String s2(int v) { + char[] result = new char[5]; + + if (v < 0) { + result[0] = '-'; + v = -v; + } else { + result[0] = '+'; + } + + for (int i = 0; i < 4; i++) { + result[4 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats an {@code int} as a 1-byte signed hex value. + * + * @param v value to format + * @return {@code non-null;} formatted form + */ + public static String s1(int v) { + char[] result = new char[3]; + + if (v < 0) { + result[0] = '-'; + v = -v; + } else { + result[0] = '+'; + } + + for (int i = 0; i < 2; i++) { + result[2 - i] = Character.forDigit(v & 0x0f, 16); + v >>= 4; + } + + return new String(result); + } + + /** + * Formats a hex dump of a portion of a {@code byte[]}. The result + * is always newline-terminated, unless the passed-in length was zero, + * in which case the result is always the empty string ({@code ""}). + * + * @param arr {@code non-null;} array to format + * @param offset {@code >= 0;} offset to the part to dump + * @param length {@code >= 0;} number of bytes to dump + * @param outOffset {@code >= 0;} first output offset to print + * @param bpl {@code >= 0;} number of bytes of output per line + * @param addressLength {@code {2,4,6,8};} number of characters for each address + * header + * @return {@code non-null;} a string of the dump + */ + public static String dump(byte[] arr, int offset, int length, + int outOffset, int bpl, int addressLength) { + int end = offset + length; + + // twos-complement math trick: ((x < 0) || (y < 0)) <=> ((x|y) < 0) + if (((offset | length | end) < 0) || (end > arr.length)) { + throw new IndexOutOfBoundsException("arr.length " + + arr.length + "; " + + offset + "..!" + end); + } + + if (outOffset < 0) { + throw new IllegalArgumentException("outOffset < 0"); + } + + if (length == 0) { + return ""; + } + + StringBuffer sb = new StringBuffer(length * 4 + 6); + boolean bol = true; + int col = 0; + + while (length > 0) { + if (col == 0) { + String astr; + switch (addressLength) { + case 2: astr = Hex.u1(outOffset); break; + case 4: astr = Hex.u2(outOffset); break; + case 6: astr = Hex.u3(outOffset); break; + default: astr = Hex.u4(outOffset); break; + } + sb.append(astr); + sb.append(": "); + } else if ((col & 1) == 0) { + sb.append(' '); + } + sb.append(Hex.u1(arr[offset])); + outOffset++; + offset++; + col++; + if (col == bpl) { + sb.append('\n'); + col = 0; + } + length--; + } + + if (col != 0) { + sb.append('\n'); + } + + return sb.toString(); + } +} diff --git a/dexgen/src/com/android/dexgen/util/HexParser.java b/dexgen/src/com/android/dexgen/util/HexParser.java new file mode 100644 index 0000000..cc4f909 --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/HexParser.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +/** + * Utilities for parsing hexadecimal text. + */ +public final class HexParser { + /** + * This class is uninstantiable. + */ + private HexParser() { + // This space intentionally left blank. + } + + /** + * Parses the given text as hex, returning a {@code byte[]} + * corresponding to the text. The format is simple: Each line may + * start with a hex offset followed by a colon (which is verified + * and presumably used just as a comment), and then consists of + * hex digits freely interspersed with whitespace. If a pound sign + * is encountered, it and the rest of the line are ignored as a + * comment. If a double quote is encountered, then the ASCII value + * of the subsequent characters is used, until the next double + * quote. Quoted strings may not span multiple lines. + * + * @param src {@code non-null;} the source string + * @return {@code non-null;} the parsed form + */ + public static byte[] parse(String src) { + int len = src.length(); + byte[] result = new byte[len / 2]; + int at = 0; + int outAt = 0; + + while (at < len) { + int nlAt = src.indexOf('\n', at); + if (nlAt < 0) { + nlAt = len; + } + int poundAt = src.indexOf('#', at); + + String line; + if ((poundAt >= 0) && (poundAt < nlAt)) { + line = src.substring(at, poundAt); + } else { + line = src.substring(at, nlAt); + } + at = nlAt + 1; + + int colonAt = line.indexOf(':'); + + atCheck: + if (colonAt != -1) { + int quoteAt = line.indexOf('\"'); + if ((quoteAt != -1) && (quoteAt < colonAt)) { + break atCheck; + } + + String atStr = line.substring(0, colonAt).trim(); + line = line.substring(colonAt + 1); + int alleged = Integer.parseInt(atStr, 16); + if (alleged != outAt) { + throw new RuntimeException("bogus offset marker: " + + atStr); + } + } + + int lineLen = line.length(); + int value = -1; + boolean quoteMode = false; + + for (int i = 0; i < lineLen; i++) { + char c = line.charAt(i); + + if (quoteMode) { + if (c == '\"') { + quoteMode = false; + } else { + result[outAt] = (byte) c; + outAt++; + } + continue; + } + + if (c <= ' ') { + continue; + } + if (c == '\"') { + if (value != -1) { + throw new RuntimeException("spare digit around " + + "offset " + Hex.u4(outAt)); + } + quoteMode = true; + continue; + } + + int digVal = Character.digit(c, 16); + if (digVal == -1) { + throw new RuntimeException("bogus digit character: \"" + + c + "\""); + } + if (value == -1) { + value = digVal; + } else { + result[outAt] = (byte) ((value << 4) | digVal); + outAt++; + value = -1; + } + } + + if (value != -1) { + throw new RuntimeException("spare digit around offset " + + Hex.u4(outAt)); + } + + if (quoteMode) { + throw new RuntimeException("unterminated quote around " + + "offset " + Hex.u4(outAt)); + } + } + + if (outAt < result.length) { + byte[] newr = new byte[outAt]; + System.arraycopy(result, 0, newr, 0, outAt); + result = newr; + } + + return result; + } +} diff --git a/dexgen/src/com/android/dexgen/util/IndentingWriter.java b/dexgen/src/com/android/dexgen/util/IndentingWriter.java new file mode 100644 index 0000000..05d4b0c --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/IndentingWriter.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +import java.io.FilterWriter; +import java.io.IOException; +import java.io.Writer; + +/** + * Writer that wraps another writer and passes width-limited and + * optionally-prefixed output to its subordinate. When lines are + * wrapped they are automatically indented based on the start of the + * line. + */ +public final class IndentingWriter extends FilterWriter { + /** {@code null-ok;} optional prefix for every line */ + private final String prefix; + + /** {@code > 0;} the maximum output width */ + private final int width; + + /** {@code > 0;} the maximum indent */ + private final int maxIndent; + + /** {@code >= 0;} current output column (zero-based) */ + private int column; + + /** whether indent spaces are currently being collected */ + private boolean collectingIndent; + + /** {@code >= 0;} current indent amount */ + private int indent; + + /** + * Constructs an instance. + * + * @param out {@code non-null;} writer to send final output to + * @param width {@code >= 0;} the maximum output width (not including + * {@code prefix}), or {@code 0} for no maximum + * @param prefix {@code non-null;} the prefix for each line + */ + public IndentingWriter(Writer out, int width, String prefix) { + super(out); + + if (out == null) { + throw new NullPointerException("out == null"); + } + + if (width < 0) { + throw new IllegalArgumentException("width < 0"); + } + + if (prefix == null) { + throw new NullPointerException("prefix == null"); + } + + this.width = (width != 0) ? width : Integer.MAX_VALUE; + this.maxIndent = width >> 1; + this.prefix = (prefix.length() == 0) ? null : prefix; + + bol(); + } + + /** + * Constructs a no-prefix instance. + * + * @param out {@code non-null;} writer to send final output to + * @param width {@code >= 0;} the maximum output width (not including + * {@code prefix}), or {@code 0} for no maximum + */ + public IndentingWriter(Writer out, int width) { + this(out, width, ""); + } + + /** {@inheritDoc} */ + @Override + public void write(int c) throws IOException { + synchronized (lock) { + if (collectingIndent) { + if (c == ' ') { + indent++; + if (indent >= maxIndent) { + indent = maxIndent; + collectingIndent = false; + } + } else { + collectingIndent = false; + } + } + + if ((column == width) && (c != '\n')) { + out.write('\n'); + column = 0; + /* + * Note: No else, so this should fall through to the next + * if statement. + */ + } + + if (column == 0) { + if (prefix != null) { + out.write(prefix); + } + + if (!collectingIndent) { + for (int i = 0; i < indent; i++) { + out.write(' '); + } + column = indent; + } + } + + out.write(c); + + if (c == '\n') { + bol(); + } else { + column++; + } + } + } + + /** {@inheritDoc} */ + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + synchronized (lock) { + while (len > 0) { + write(cbuf[off]); + off++; + len--; + } + } + } + + /** {@inheritDoc} */ + @Override + public void write(String str, int off, int len) throws IOException { + synchronized (lock) { + while (len > 0) { + write(str.charAt(off)); + off++; + len--; + } + } + } + + /** + * Indicates that output is at the beginning of a line. + */ + private void bol() { + column = 0; + collectingIndent = (maxIndent != 0); + indent = 0; + } +} diff --git a/dexgen/src/com/android/dexgen/util/IntIterator.java b/dexgen/src/com/android/dexgen/util/IntIterator.java new file mode 100644 index 0000000..42d92fa --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/IntIterator.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +/** + * An iterator for a list of ints. + */ +public interface IntIterator { + + /** + * Checks to see if the iterator has a next value. + * + * @return true if next() will succeed + */ + boolean hasNext(); + + /** + * Returns the next value in the iterator. + * + * @return next value + * @throws java.util.NoSuchElementException if no next element exists + */ + int next(); +} diff --git a/dexgen/src/com/android/dexgen/util/IntList.java b/dexgen/src/com/android/dexgen/util/IntList.java new file mode 100644 index 0000000..ad29c0b --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/IntList.java @@ -0,0 +1,452 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +import java.util.Arrays; + +/** + * Simple list of {@code int}s. + */ +public final class IntList extends MutabilityControl { + /** {@code non-null;} immutable, no-element instance */ + public static final IntList EMPTY = new IntList(0); + + /** {@code non-null;} array of elements */ + private int[] values; + + /** {@code >= 0;} current size of the list */ + private int size; + + /** whether the values are currently sorted */ + private boolean sorted; + + static { + EMPTY.setImmutable(); + } + + /** + * Constructs a new immutable instance with the given element. + * + * @param value the sole value in the list + */ + public static IntList makeImmutable(int value) { + IntList result = new IntList(1); + + result.add(value); + result.setImmutable(); + + return result; + } + + /** + * Constructs a new immutable instance with the given elements. + * + * @param value0 the first value in the list + * @param value1 the second value in the list + */ + public static IntList makeImmutable(int value0, int value1) { + IntList result = new IntList(2); + + result.add(value0); + result.add(value1); + result.setImmutable(); + + return result; + } + + /** + * Constructs an empty instance with a default initial capacity. + */ + public IntList() { + this(4); + } + + /** + * Constructs an empty instance. + * + * @param initialCapacity {@code >= 0;} initial capacity of the list + */ + public IntList(int initialCapacity) { + super(true); + + try { + values = new int[initialCapacity]; + } catch (NegativeArraySizeException ex) { + // Translate the exception. + throw new IllegalArgumentException("size < 0"); + } + + size = 0; + sorted = true; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + int result = 0; + + for (int i = 0; i < size; i++) { + result = (result * 31) + values[i]; + } + + return result; + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (! (other instanceof IntList)) { + return false; + } + + IntList otherList = (IntList) other; + + if (sorted != otherList.sorted) { + return false; + } + + if (size != otherList.size) { + return false; + } + + for (int i = 0; i < size; i++) { + if (values[i] != otherList.values[i]) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(size * 5 + 10); + + sb.append('{'); + + for (int i = 0; i < size; i++) { + if (i != 0) { + sb.append(", "); + } + sb.append(values[i]); + } + + sb.append('}'); + + return sb.toString(); + } + + /** + * Gets the number of elements in this list. + */ + public int size() { + return size; + } + + /** + * Gets the indicated value. + * + * @param n {@code >= 0, < size();} which element + * @return the indicated element's value + */ + public int get(int n) { + if (n >= size) { + throw new IndexOutOfBoundsException("n >= size()"); + } + + try { + return values[n]; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate exception. + throw new IndexOutOfBoundsException("n < 0"); + } + } + + /** + * Sets the value at the given index. + * + * @param n {@code >= 0, < size();} which element + * @param value value to store + */ + public void set(int n, int value) { + throwIfImmutable(); + + if (n >= size) { + throw new IndexOutOfBoundsException("n >= size()"); + } + + try { + values[n] = value; + sorted = false; + } catch (ArrayIndexOutOfBoundsException ex) { + // Translate the exception. + if (n < 0) { + throw new IllegalArgumentException("n < 0"); + } + } + } + + /** + * Adds an element to the end of the list. This will increase the + * list's capacity if necessary. + * + * @param value the value to add + */ + public void add(int value) { + throwIfImmutable(); + + growIfNeeded(); + + values[size++] = value; + + if (sorted && (size > 1)) { + sorted = (value >= values[size - 2]); + } + } + + /** + * Inserts element into specified index, moving elements at and above + * that index up one. May not be used to insert at an index beyond the + * current size (that is, insertion as a last element is legal but + * no further). + * + * @param n {@code >= 0, <=size();} index of where to insert + * @param value value to insert + */ + public void insert(int n, int value) { + if (n > size) { + throw new IndexOutOfBoundsException("n > size()"); + } + + growIfNeeded(); + + System.arraycopy (values, n, values, n+1, size - n); + values[n] = value; + size++; + + sorted = sorted + && (n == 0 || value > values[n-1]) + && (n == (size - 1) || value < values[n+1]); + } + + /** + * Removes an element at a given index, shifting elements at greater + * indicies down one. + * + * @param n {@code >=0, < size();} index of element to remove + */ + public void removeIndex(int n) { + if (n >= size) { + throw new IndexOutOfBoundsException("n >= size()"); + } + + System.arraycopy (values, n + 1, values, n, size - n - 1); + size--; + + // sort status is unchanged + } + + /** + * Increases size of array if needed + */ + private void growIfNeeded() { + if (size == values.length) { + // Resize. + int[] newv = new int[size * 3 / 2 + 10]; + System.arraycopy(values, 0, newv, 0, size); + values = newv; + } + } + + /** + * Returns the last element in the array without modifying the array + * + * @return last value in the array. + * @exception IndexOutOfBoundsException if stack is empty. + */ + public int top() { + return get(size - 1); + } + + /** + * Pops an element off the end of the list and decreasing the size by one. + * + * @return value from what was the last element. + * @exception IndexOutOfBoundsException if stack is empty. + */ + public int pop() { + throwIfImmutable(); + + int result; + + result = get(size-1); + size--; + + return result; + } + + /** + * Pops N elements off the end of the list and decreasing the size by N. + * + * @param n {@code >= 0;} number of elements to remove from end. + * @exception IndexOutOfBoundsException if stack is smaller than N + */ + public void pop(int n) { + throwIfImmutable(); + + size -= n; + } + + /** + * Shrinks the size of the list. + * + * @param newSize {@code >= 0;} the new size + */ + public void shrink(int newSize) { + if (newSize < 0) { + throw new IllegalArgumentException("newSize < 0"); + } + + if (newSize > size) { + throw new IllegalArgumentException("newSize > size"); + } + + throwIfImmutable(); + + size = newSize; + } + + /** + * Makes and returns a mutable copy of the list. + * + * @return {@code non-null;} an appropriately-constructed instance + */ + public IntList mutableCopy() { + int sz = size; + IntList result = new IntList(sz); + + for (int i = 0; i < sz; i++) { + result.add(values[i]); + } + + return result; + } + + /** + * Sorts the elements in the list in-place. + */ + public void sort() { + throwIfImmutable(); + + if (!sorted) { + Arrays.sort(values, 0, size); + sorted = true; + } + } + + /** + * Returns the index of the given value, or -1 if the value does not + * appear in the list. This will do a binary search if the list is + * sorted or a linear search if not. + * @param value value to find + * @return index of value or -1 + */ + public int indexOf(int value) { + int ret = binarysearch(value); + + return ret >= 0 ? ret : -1; + + } + + /** + * Performs a binary search on a sorted list, returning the index of + * the given value if it is present or + * {@code (-(insertion point) - 1)} if the value is not present. + * If the list is not sorted, then reverts to linear search and returns + * {@code -size()} if the element is not found. + * + * @param value value to find + * @return index of value or {@code (-(insertion point) - 1)} if the + * value is not present + */ + public int binarysearch(int value) { + int sz = size; + + if (!sorted) { + // Linear search. + for (int i = 0; i < sz; i++) { + if (values[i] == value) { + return i; + } + } + + return -sz; + } + + /* + * Binary search. This variant does only one value comparison + * per iteration but does one more iteration on average than + * the variant that includes a value equality check per + * iteration. + */ + + int min = -1; + int max = sz; + + while (max > (min + 1)) { + /* + * The guessIdx calculation is equivalent to ((min + max) + * / 2) but won't go wonky when min and max are close to + * Integer.MAX_VALUE. + */ + int guessIdx = min + ((max - min) >> 1); + int guess = values[guessIdx]; + + if (value <= guess) { + max = guessIdx; + } else { + min = guessIdx; + } + } + + if ((max != sz)) { + return (value == values[max]) ? max : (-max - 1); + } else { + return -sz - 1; + } + } + + + /** + * Returns whether or not the given value appears in the list. + * This will do a binary search if the list is sorted or a linear + * search if not. + * + * @see #sort + * + * @param value value to look for + * @return whether the list contains the given value + */ + public boolean contains(int value) { + return indexOf(value) >= 0; + } +} diff --git a/dexgen/src/com/android/dexgen/util/IntSet.java b/dexgen/src/com/android/dexgen/util/IntSet.java new file mode 100644 index 0000000..4de7525 --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/IntSet.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +/** + * A set of integers + */ +public interface IntSet { + + /** + * Adds an int to a set + * + * @param value int to add + */ + void add(int value); + + /** + * Removes an int from a set. + * + * @param value int to remove + */ + void remove(int value); + + /** + * Checks to see if a value is in the set + * + * @param value int to check + * @return true if in set + */ + boolean has(int value); + + /** + * Merges {@code other} into this set, so this set becomes the + * union of the two. + * + * @param other {@code non-null;} other set to merge with. + */ + void merge(IntSet other); + + /** + * Returns the count of unique elements in this set. + * + * @return {@code > = 0;} count of unique elements + */ + int elements(); + + /** + * Iterates the set + * + * @return {@code non-null;} a set iterator + */ + IntIterator iterator(); +} diff --git a/dexgen/src/com/android/dexgen/util/LabeledItem.java b/dexgen/src/com/android/dexgen/util/LabeledItem.java new file mode 100644 index 0000000..63cd067 --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/LabeledItem.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +/** + * An item that has an integer label. + */ +public interface LabeledItem { + + /* + * Gets the label of this block. + * + * @return {@code >= 0;} the label + */ + public int getLabel(); +} diff --git a/dexgen/src/com/android/dexgen/util/LabeledList.java b/dexgen/src/com/android/dexgen/util/LabeledList.java new file mode 100644 index 0000000..a59e87d --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/LabeledList.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +import com.android.dexgen.rop.ByteBlock; + +/** + * A list of labeled items, allowing easy lookup by label. + */ +public class LabeledList extends FixedSizeList { + + /** + * Sparse array indexed by label to FixedSizeList index. + * -1 = invalid label. + */ + private final IntList labelToIndex; + + /** @inheritDoc */ + public LabeledList(int size) { + super(size); + + labelToIndex = new IntList(size); + } + + /** + * Constructs a new instance that is a copy of the old instance. + * + * @param old instance to copy + */ + protected LabeledList(LabeledList old) { + super(old.size()); + labelToIndex = old.labelToIndex.mutableCopy(); + + int sz = old.size(); + + for (int i = 0; i < sz; i++) { + Object one = old.get0(i); + if (one != null) { + set0(i, one); + } + } + } + + /** + * Gets the maximum label (exclusive) of any block added to this instance. + * + * @return {@code >= 0;} the maximum label + */ + public int getMaxLabel() { + int sz = labelToIndex.size(); + + // Gobble any deleted labels that may be at the end... + int i; + for (i = sz - 1; (i >= 0) && (labelToIndex.get(i) < 0); i--) + ; + + int newSize = i+1; + + labelToIndex.shrink(newSize); + + return newSize; + } + + /** + * Removes a label from the label-to-index mapping + * @param oldLabel label to remove + */ + protected void removeLabel(int oldLabel) { + labelToIndex.set(oldLabel, -1); + } + + /** + * Adds a label and index to the label-to-index mapping + * @param label new label + * @param index index of block. + */ + protected void addLabelIndex(int label, int index) { + int origSz = labelToIndex.size(); + + for (int i = 0; i <= (label - origSz); i++) { + labelToIndex.add(-1); + } + + labelToIndex.set(label, index); + } + + /** + * Gets the index of the first item in the list with the given + * label, if any. + * + * @param label {@code >= 0;} the label to look for + * @return {@code >= -1;} the index of the so-labelled item, or {@code -1} + * if none is found + */ + public int indexOfLabel(int label) { + if (label >= labelToIndex.size()) { + return -1; + } else { + return labelToIndex.get(label); + } + } + + /** @inheritDoc */ + @Override + public void shrinkToFit() { + super.shrinkToFit(); + + rebuildLabelToIndex(); + } + + /** + * Rebuilds the label-to-index mapping after a shrinkToFit(). + * Note: assumes that the labels that are in the list are the same + * although the indicies may have changed. + */ + protected void rebuildLabelToIndex() { + int szItems = size(); + + for (int i = 0; i < szItems; i++) { + LabeledItem li = (LabeledItem)get0(i); + + if (li != null) { + labelToIndex.set(li.getLabel(), i); + } + } + } + + /** + * Sets the element at the given index. + * + * @param n {@code >= 0, < size();} which element + * @param item {@code null-ok;} the value to store + */ + protected void set(int n, LabeledItem item) { + LabeledItem old = (LabeledItem) getOrNull0(n); + + set0(n, item); + + if (old != null) { + removeLabel(old.getLabel()); + } + + if (item != null) { + addLabelIndex(item.getLabel(), n); + } + } +} diff --git a/dexgen/src/com/android/dexgen/util/Leb128Utils.java b/dexgen/src/com/android/dexgen/util/Leb128Utils.java new file mode 100644 index 0000000..05b38e2 --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/Leb128Utils.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +/** + * LEB128 (little-endian base 128) utilities. + */ +public final class Leb128Utils { + /** + * This class is uninstantiable. + */ + private Leb128Utils() { + // This space intentionally left blank. + } + + /** + * Gets the number of bytes in the unsigned LEB128 encoding of the + * given value. + * + * @param value the value in question + * @return its write size, in bytes + */ + public static int unsignedLeb128Size(int value) { + // TODO: This could be much cleverer. + + int remaining = value >> 7; + int count = 0; + + while (remaining != 0) { + remaining >>= 7; + count++; + } + + return count + 1; + } + + /** + * Gets the number of bytes in the signed LEB128 encoding of the + * given value. + * + * @param value the value in question + * @return its write size, in bytes + */ + public static int signedLeb128Size(int value) { + // TODO: This could be much cleverer. + + int remaining = value >> 7; + int count = 0; + boolean hasMore = true; + int end = ((value & Integer.MIN_VALUE) == 0) ? 0 : -1; + + while (hasMore) { + hasMore = (remaining != end) + || ((remaining & 1) != ((value >> 6) & 1)); + + value = remaining; + remaining >>= 7; + count++; + } + + return count; + } +} diff --git a/dexgen/src/com/android/dexgen/util/ListIntSet.java b/dexgen/src/com/android/dexgen/util/ListIntSet.java new file mode 100644 index 0000000..b262ebb --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/ListIntSet.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +import java.util.NoSuchElementException; + +/** + * A set of integers, represented by a list + */ +public class ListIntSet implements IntSet { + + /** also accessed in BitIntSet */ + final IntList ints; + + /** + * Constructs an instance + */ + public ListIntSet() { + ints = new IntList(); + ints.sort(); + } + + /** @inheritDoc */ + public void add(int value) { + int index = ints.binarysearch(value); + + if (index < 0) { + ints.insert(-(index + 1), value); + } + } + + /** @inheritDoc */ + public void remove(int value) { + int index = ints.indexOf(value); + + if (index >= 0) { + ints.removeIndex(index); + } + } + + /** @inheritDoc */ + public boolean has(int value) { + return ints.indexOf(value) >= 0; + } + + /** @inheritDoc */ + public void merge(IntSet other) { + if (other instanceof ListIntSet) { + ListIntSet o = (ListIntSet) other; + int szThis = ints.size(); + int szOther = o.ints.size(); + + int i = 0; + int j = 0; + + while (j < szOther && i < szThis) { + while (j < szOther && o.ints.get(j) < ints.get(i)) { + add(o.ints.get(j++)); + } + if (j == szOther) { + break; + } + while (i < szThis && o.ints.get(j) >= ints.get(i)) { + i++; + } + } + + while (j < szOther) { + add(o.ints.get(j++)); + } + + ints.sort(); + } else if (other instanceof BitIntSet) { + BitIntSet o = (BitIntSet) other; + + for (int i = 0; i >= 0; i = Bits.findFirst(o.bits, i + 1)) { + ints.add(i); + } + ints.sort(); + } else { + IntIterator iter = other.iterator(); + while (iter.hasNext()) { + add(iter.next()); + } + } + } + + /** @inheritDoc */ + public int elements() { + return ints.size(); + } + + /** @inheritDoc */ + public IntIterator iterator() { + return new IntIterator() { + private int idx = 0; + + /** @inheritDoc */ + public boolean hasNext() { + return idx < ints.size(); + } + + /** @inheritDoc */ + public int next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + return ints.get(idx++); + } + }; + } + + /** @inheritDoc */ + public String toString() { + return ints.toString(); + } +} diff --git a/dexgen/src/com/android/dexgen/util/MutabilityControl.java b/dexgen/src/com/android/dexgen/util/MutabilityControl.java new file mode 100644 index 0000000..b3ee691 --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/MutabilityControl.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +/** + * Very simple base class that implements a flag to control the mutability + * of instances. This class just provides the flag and a utility to check + * and throw the right exception, but it is up to subclasses to place calls + * to the checker in all the right places. + */ +public class MutabilityControl { + /** whether this instance is mutable */ + private boolean mutable; + + /** + * Constructs an instance. It is initially mutable. + */ + public MutabilityControl() { + mutable = true; + } + + /** + * Constructs an instance, explicitly indicating the mutability. + * + * @param mutable {@code true} iff this instance is mutable + */ + public MutabilityControl(boolean mutable) { + this.mutable = mutable; + } + + /** + * Makes this instance immutable. + */ + public void setImmutable() { + mutable = false; + } + + /** + * Checks to see whether or not this instance is immutable. This is the + * same as calling {@code !isMutable()}. + * + * @return {@code true} iff this instance is immutable + */ + public final boolean isImmutable() { + return !mutable; + } + + /** + * Checks to see whether or not this instance is mutable. + * + * @return {@code true} iff this instance is mutable + */ + public final boolean isMutable() { + return mutable; + } + + /** + * Throws {@link MutabilityException} if this instance is + * immutable. + */ + public final void throwIfImmutable() { + if (!mutable) { + throw new MutabilityException("immutable instance"); + } + } + + /** + * Throws {@link MutabilityException} if this instance is mutable. + */ + public final void throwIfMutable() { + if (mutable) { + throw new MutabilityException("mutable instance"); + } + } +} diff --git a/dexgen/src/com/android/dexgen/util/MutabilityException.java b/dexgen/src/com/android/dexgen/util/MutabilityException.java new file mode 100644 index 0000000..2188fe5 --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/MutabilityException.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +/** + * Exception due to a mutability problem. + */ +public class MutabilityException + extends ExceptionWithContext { + public MutabilityException(String message) { + super(message); + } + + public MutabilityException(Throwable cause) { + super(cause); + } + + public MutabilityException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/dexgen/src/com/android/dexgen/util/Output.java b/dexgen/src/com/android/dexgen/util/Output.java new file mode 100644 index 0000000..469c66a --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/Output.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +/** + * Interface for a sink for binary output. This is similar to + * {@code java.util.DataOutput}, but no {@code IOExceptions} + * are declared, and multibyte output is defined to be little-endian. + */ +public interface Output { + /** + * Gets the current cursor position. This is the same as the number of + * bytes written to this instance. + * + * @return {@code >= 0;} the cursor position + */ + public int getCursor(); + + /** + * Asserts that the cursor is the given value. + * + * @param expectedCursor the expected cursor value + * @throws RuntimeException thrown if {@code getCursor() != + * expectedCursor} + */ + public void assertCursor(int expectedCursor); + + /** + * Writes a {@code byte} to this instance. + * + * @param value the value to write; all but the low 8 bits are ignored + */ + public void writeByte(int value); + + /** + * Writes a {@code short} to this instance. + * + * @param value the value to write; all but the low 16 bits are ignored + */ + public void writeShort(int value); + + /** + * Writes an {@code int} to this instance. + * + * @param value the value to write + */ + public void writeInt(int value); + + /** + * Writes a {@code long} to this instance. + * + * @param value the value to write + */ + public void writeLong(long value); + + /** + * Writes a DWARFv3-style unsigned LEB128 integer. For details, + * see the "Dalvik Executable Format" document or DWARF v3 section + * 7.6. + * + * @param value value to write, treated as an unsigned value + * @return {@code 1..5;} the number of bytes actually written + */ + public int writeUnsignedLeb128(int value); + + /** + * Writes a DWARFv3-style unsigned LEB128 integer. For details, + * see the "Dalvik Executable Format" document or DWARF v3 section + * 7.6. + * + * @param value value to write + * @return {@code 1..5;} the number of bytes actually written + */ + public int writeSignedLeb128(int value); + + /** + * Writes a {@link ByteArray} to this instance. + * + * @param bytes {@code non-null;} the array to write + */ + public void write(ByteArray bytes); + + /** + * Writes a portion of a {@code byte[]} to this instance. + * + * @param bytes {@code non-null;} the array to write + * @param offset {@code >= 0;} offset into {@code bytes} for the first + * byte to write + * @param length {@code >= 0;} number of bytes to write + */ + public void write(byte[] bytes, int offset, int length); + + /** + * Writes a {@code byte[]} to this instance. This is just + * a convenient shorthand for {@code write(bytes, 0, bytes.length)}. + * + * @param bytes {@code non-null;} the array to write + */ + public void write(byte[] bytes); + + /** + * Writes the given number of {@code 0} bytes. + * + * @param count {@code >= 0;} the number of zeroes to write + */ + public void writeZeroes(int count); + + /** + * Adds extra bytes if necessary (with value {@code 0}) to + * force alignment of the output cursor as given. + * + * @param alignment {@code > 0;} the alignment; must be a power of two + */ + public void alignTo(int alignment); +} diff --git a/dexgen/src/com/android/dexgen/util/PathHolder.java b/dexgen/src/com/android/dexgen/util/PathHolder.java new file mode 100644 index 0000000..b23ce66 --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/PathHolder.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +import java.io.File; + +/** + * Helper class used primarily for holding path on the device of different + * files arising in the dex class generation process. + */ +public class PathHolder { + + public static final String DEX_FILE_EXTENSION = ".dex"; + + public static final String JAR_FILE_EXTENSION = ".jar"; + + /** {@code non-null;} directory location of the dex-related files */ + private final String dirLocation; + + /** {@code non-null;} common file name prefix of the created files */ + private final String fileNamePrefix; + + /** + * Creates an instance of {@code PathHolder} initialized with the directory + * location for storage of temporary files and common file name prefix for these + * files. + * + * @param dirLocation {@code non-null;} path to directory used for storage of temporary files + * @param fileNamePrefix {@code non-null;} common file name prefix across all the temporary + * files involved in the dex class generation and loading process + */ + public PathHolder(String dirLocation, String fileNamePrefix) { + if (dirLocation == null) { + throw new NullPointerException("dirLocation == null"); + } + if (fileNamePrefix == null) { + throw new NullPointerException("fileNamePrefix == null"); + } + + this.dirLocation = dirLocation; + this.fileNamePrefix = fileNamePrefix; + } + + public String getFileName() { + return fileNamePrefix; + } + + public String getDexFilePath() { + return dirLocation + File.separator + fileNamePrefix + DEX_FILE_EXTENSION; + } + + public String getDexFileName() { + return fileNamePrefix + DEX_FILE_EXTENSION; + } + + public String getJarFilePath() { + return dirLocation + File.separator + fileNamePrefix + JAR_FILE_EXTENSION; + } + + public String getJarFileName() { + return fileNamePrefix + JAR_FILE_EXTENSION; + } + + public String getDirLocation() { + return dirLocation; + } +} diff --git a/dexgen/src/com/android/dexgen/util/ToHuman.java b/dexgen/src/com/android/dexgen/util/ToHuman.java new file mode 100644 index 0000000..bbf044c --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/ToHuman.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +/** + * Simple interface for objects that can return a "human" (as opposed to + * a complete but often hard to read) string form. + */ +public interface ToHuman { + /** + * Return the "human" string form of this instance. This is + * generally less "debuggy" than {@code toString()}. + * + * @return {@code non-null;} the human string form + */ + public String toHuman(); +} diff --git a/dexgen/src/com/android/dexgen/util/TwoColumnOutput.java b/dexgen/src/com/android/dexgen/util/TwoColumnOutput.java new file mode 100644 index 0000000..17a4c42 --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/TwoColumnOutput.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.StringWriter; +import java.io.Writer; + +/** + * Class that takes a combined output destination and provides two + * output writers, one of which ends up writing to the left column and + * one which goes on the right. + */ +public final class TwoColumnOutput { + /** {@code non-null;} underlying writer for final output */ + private final Writer out; + + /** {@code > 0;} the left column width */ + private final int leftWidth; + + /** {@code non-null;} pending left column output */ + private final StringBuffer leftBuf; + + /** {@code non-null;} pending right column output */ + private final StringBuffer rightBuf; + + /** {@code non-null;} left column writer */ + private final IndentingWriter leftColumn; + + /** {@code non-null;} right column writer */ + private final IndentingWriter rightColumn; + + /** + * Turns the given two strings (with widths) and spacer into a formatted + * two-column string. + * + * @param s1 {@code non-null;} first string + * @param width1 {@code > 0;} width of the first column + * @param spacer {@code non-null;} spacer string + * @param s2 {@code non-null;} second string + * @param width2 {@code > 0;} width of the second column + * @return {@code non-null;} an appropriately-formatted string + */ + public static String toString(String s1, int width1, String spacer, + String s2, int width2) { + int len1 = s1.length(); + int len2 = s2.length(); + + StringWriter sw = new StringWriter((len1 + len2) * 3); + TwoColumnOutput twoOut = + new TwoColumnOutput(sw, width1, width2, spacer); + + try { + twoOut.getLeft().write(s1); + twoOut.getRight().write(s2); + } catch (IOException ex) { + throw new RuntimeException("shouldn't happen", ex); + } + + twoOut.flush(); + return sw.toString(); + } + + /** + * Constructs an instance. + * + * @param out {@code non-null;} writer to send final output to + * @param leftWidth {@code > 0;} width of the left column, in characters + * @param rightWidth {@code > 0;} width of the right column, in characters + * @param spacer {@code non-null;} spacer string to sit between the two columns + */ + public TwoColumnOutput(Writer out, int leftWidth, int rightWidth, + String spacer) { + if (out == null) { + throw new NullPointerException("out == null"); + } + + if (leftWidth < 1) { + throw new IllegalArgumentException("leftWidth < 1"); + } + + if (rightWidth < 1) { + throw new IllegalArgumentException("rightWidth < 1"); + } + + if (spacer == null) { + throw new NullPointerException("spacer == null"); + } + + StringWriter leftWriter = new StringWriter(1000); + StringWriter rightWriter = new StringWriter(1000); + + this.out = out; + this.leftWidth = leftWidth; + this.leftBuf = leftWriter.getBuffer(); + this.rightBuf = rightWriter.getBuffer(); + this.leftColumn = new IndentingWriter(leftWriter, leftWidth); + this.rightColumn = + new IndentingWriter(rightWriter, rightWidth, spacer); + } + + /** + * Constructs an instance. + * + * @param out {@code non-null;} stream to send final output to + * @param leftWidth {@code >= 1;} width of the left column, in characters + * @param rightWidth {@code >= 1;} width of the right column, in characters + * @param spacer {@code non-null;} spacer string to sit between the two columns + */ + public TwoColumnOutput(OutputStream out, int leftWidth, int rightWidth, + String spacer) { + this(new OutputStreamWriter(out), leftWidth, rightWidth, spacer); + } + + /** + * Gets the writer to use to write to the left column. + * + * @return {@code non-null;} the left column writer + */ + public Writer getLeft() { + return leftColumn; + } + + /** + * Gets the writer to use to write to the right column. + * + * @return {@code non-null;} the right column writer + */ + public Writer getRight() { + return rightColumn; + } + + /** + * Flushes the output. If there are more lines of pending output in one + * column, then the other column will get filled with blank lines. + */ + public void flush() { + try { + appendNewlineIfNecessary(leftBuf, leftColumn); + appendNewlineIfNecessary(rightBuf, rightColumn); + outputFullLines(); + flushLeft(); + flushRight(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + /** + * Outputs to the final destination as many full line pairs as + * there are in the pending output, removing those lines from + * their respective buffers. This method terminates when at + * least one of the two column buffers is empty. + */ + private void outputFullLines() throws IOException { + for (;;) { + int leftLen = leftBuf.indexOf("\n"); + if (leftLen < 0) { + return; + } + + int rightLen = rightBuf.indexOf("\n"); + if (rightLen < 0) { + return; + } + + if (leftLen != 0) { + out.write(leftBuf.substring(0, leftLen)); + } + + if (rightLen != 0) { + writeSpaces(out, leftWidth - leftLen); + out.write(rightBuf.substring(0, rightLen)); + } + + out.write('\n'); + + leftBuf.delete(0, leftLen + 1); + rightBuf.delete(0, rightLen + 1); + } + } + + /** + * Flushes the left column buffer, printing it and clearing the buffer. + * If the buffer is already empty, this does nothing. + */ + private void flushLeft() throws IOException { + appendNewlineIfNecessary(leftBuf, leftColumn); + + while (leftBuf.length() != 0) { + rightColumn.write('\n'); + outputFullLines(); + } + } + + /** + * Flushes the right column buffer, printing it and clearing the buffer. + * If the buffer is already empty, this does nothing. + */ + private void flushRight() throws IOException { + appendNewlineIfNecessary(rightBuf, rightColumn); + + while (rightBuf.length() != 0) { + leftColumn.write('\n'); + outputFullLines(); + } + } + + /** + * Appends a newline to the given buffer via the given writer, but + * only if it isn't empty and doesn't already end with one. + * + * @param buf {@code non-null;} the buffer in question + * @param out {@code non-null;} the writer to use + */ + private static void appendNewlineIfNecessary(StringBuffer buf, + Writer out) + throws IOException { + int len = buf.length(); + + if ((len != 0) && (buf.charAt(len - 1) != '\n')) { + out.write('\n'); + } + } + + /** + * Writes the given number of spaces to the given writer. + * + * @param out {@code non-null;} where to write + * @param amt {@code >= 0;} the number of spaces to write + */ + private static void writeSpaces(Writer out, int amt) throws IOException { + while (amt > 0) { + out.write(' '); + amt--; + } + } +} diff --git a/dexgen/src/com/android/dexgen/util/Warning.java b/dexgen/src/com/android/dexgen/util/Warning.java new file mode 100644 index 0000000..204d877 --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/Warning.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +/** + * Exception which is meant to indicate a non-fatal warning. + */ +public class Warning extends RuntimeException { + /** + * Constructs an instance. + * + * @param message human-oriented message + */ + public Warning(String message) { + super(message); + } +} diff --git a/dexgen/src/com/android/dexgen/util/Writers.java b/dexgen/src/com/android/dexgen/util/Writers.java new file mode 100644 index 0000000..046967e --- /dev/null +++ b/dexgen/src/com/android/dexgen/util/Writers.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.dexgen.util; + +import java.io.PrintWriter; +import java.io.Writer; + +/** + * Utilities for dealing with {@code Writer}s. + */ +public final class Writers { + /** + * This class is uninstantiable. + */ + private Writers() { + // This space intentionally left blank. + } + + /** + * Makes a {@code PrintWriter} for the given {@code Writer}, + * returning the given writer if it already happens to be the right + * class. + * + * @param writer {@code non-null;} writer to (possibly) wrap + * @return {@code non-null;} an appropriate instance + */ + public static PrintWriter printWriterFor(Writer writer) { + if (writer instanceof PrintWriter) { + return (PrintWriter) writer; + } + + return new PrintWriter(writer); + } +} |