blob: 7c1d68117d01ce559227ec2a45bddcab231ee8f8 [file] [log] [blame]
/*
* Copyright 2013, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jf.dexlib2.writer.pool;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.*;
import org.jf.dexlib2.DebugItemType;
import org.jf.dexlib2.ReferenceType;
import org.jf.dexlib2.builder.MutableMethodImplementation;
import org.jf.dexlib2.iface.*;
import org.jf.dexlib2.iface.debug.*;
import org.jf.dexlib2.iface.instruction.Instruction;
import org.jf.dexlib2.iface.instruction.ReferenceInstruction;
import org.jf.dexlib2.iface.reference.*;
import org.jf.dexlib2.iface.value.EncodedValue;
import org.jf.dexlib2.immutable.value.ImmutableEncodedValueFactory;
import org.jf.dexlib2.util.EncodedValueUtils;
import org.jf.dexlib2.util.ReferenceUtil;
import org.jf.dexlib2.writer.ClassSection;
import org.jf.dexlib2.writer.DebugWriter;
import org.jf.util.AbstractForwardSequentialList;
import org.jf.util.CollectionUtils;
import org.jf.util.ExceptionWithContext;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.*;
import java.util.Map.Entry;
public class ClassPool extends BasePool<String, PoolClassDef> implements ClassSection<CharSequence, CharSequence,
TypeListPool.Key<? extends Collection<? extends CharSequence>>, PoolClassDef, Field, PoolMethod,
Set<? extends Annotation>, EncodedValue> {
public ClassPool(@Nonnull DexPool dexPool) {
super(dexPool);
}
public void intern(@Nonnull ClassDef classDef) {
PoolClassDef poolClassDef = new PoolClassDef(classDef);
PoolClassDef prev = internedItems.put(poolClassDef.getType(), poolClassDef);
if (prev != null) {
throw new ExceptionWithContext("Class %s has already been interned", poolClassDef.getType());
}
dexPool.typeSection.intern(poolClassDef.getType());
dexPool.typeSection.internNullable(poolClassDef.getSuperclass());
dexPool.typeListSection.intern(poolClassDef.getInterfaces());
dexPool.stringSection.internNullable(poolClassDef.getSourceFile());
HashSet<String> fields = new HashSet<String>();
for (Field field: poolClassDef.getFields()) {
String fieldDescriptor = ReferenceUtil.getShortFieldDescriptor(field);
if (!fields.add(fieldDescriptor)) {
throw new ExceptionWithContext("Multiple definitions for field %s->%s",
poolClassDef.getType(), fieldDescriptor);
}
dexPool.fieldSection.intern(field);
EncodedValue initialValue = field.getInitialValue();
if (initialValue != null) {
dexPool.internEncodedValue(initialValue);
}
dexPool.annotationSetSection.intern(field.getAnnotations());
}
HashSet<String> methods = new HashSet<String>();
for (PoolMethod method: poolClassDef.getMethods()) {
String methodDescriptor = ReferenceUtil.getMethodDescriptor(method, true);
if (!methods.add(methodDescriptor)) {
throw new ExceptionWithContext("Multiple definitions for method %s->%s",
poolClassDef.getType(), methodDescriptor);
}
dexPool.methodSection.intern(method);
internCode(method);
internDebug(method);
dexPool.annotationSetSection.intern(method.getAnnotations());
for (MethodParameter parameter: method.getParameters()) {
dexPool.annotationSetSection.intern(parameter.getAnnotations());
}
}
dexPool.annotationSetSection.intern(poolClassDef.getAnnotations());
}
private void internCode(@Nonnull Method method) {
// this also handles parameter names, which aren't directly tied to the MethodImplementation, even though the debug items are
boolean hasInstruction = false;
MethodImplementation methodImpl = method.getImplementation();
if (methodImpl != null) {
for (Instruction instruction: methodImpl.getInstructions()) {
hasInstruction = true;
if (instruction instanceof ReferenceInstruction) {
Reference reference = ((ReferenceInstruction)instruction).getReference();
switch (instruction.getOpcode().referenceType) {
case ReferenceType.STRING:
dexPool.stringSection.intern((StringReference)reference);
break;
case ReferenceType.TYPE:
dexPool.typeSection.intern((TypeReference)reference);
break;
case ReferenceType.FIELD:
dexPool.fieldSection.intern((FieldReference) reference);
break;
case ReferenceType.METHOD:
dexPool.methodSection.intern((MethodReference)reference);
break;
default:
throw new ExceptionWithContext("Unrecognized reference type: %d",
instruction.getOpcode().referenceType);
}
}
}
List<? extends TryBlock> tryBlocks = methodImpl.getTryBlocks();
if (!hasInstruction && tryBlocks.size() > 0) {
throw new ExceptionWithContext("Method %s has no instructions, but has try blocks.",
ReferenceUtil.getMethodDescriptor(method));
}
for (TryBlock<? extends ExceptionHandler> tryBlock: methodImpl.getTryBlocks()) {
for (ExceptionHandler handler: tryBlock.getExceptionHandlers()) {
dexPool.typeSection.internNullable(handler.getExceptionType());
}
}
}
}
private void internDebug(@Nonnull Method method) {
for (MethodParameter param: method.getParameters()) {
String paramName = param.getName();
if (paramName != null) {
dexPool.stringSection.intern(paramName);
}
}
MethodImplementation methodImpl = method.getImplementation();
if (methodImpl != null) {
for (DebugItem debugItem: methodImpl.getDebugItems()) {
switch (debugItem.getDebugItemType()) {
case DebugItemType.START_LOCAL:
StartLocal startLocal = (StartLocal)debugItem;
dexPool.stringSection.internNullable(startLocal.getName());
dexPool.typeSection.internNullable(startLocal.getType());
dexPool.stringSection.internNullable(startLocal.getSignature());
break;
case DebugItemType.SET_SOURCE_FILE:
dexPool.stringSection.internNullable(((SetSourceFile) debugItem).getSourceFile());
break;
}
}
}
}
private ImmutableList<PoolClassDef> sortedClasses = null;
@Nonnull @Override public Collection<? extends PoolClassDef> getSortedClasses() {
if (sortedClasses == null) {
sortedClasses = Ordering.natural().immutableSortedCopy(internedItems.values());
}
return sortedClasses;
}
@Nullable @Override
public Map.Entry<? extends PoolClassDef, Integer> getClassEntryByType(@Nullable CharSequence name) {
if (name == null) {
return null;
}
final PoolClassDef classDef = internedItems.get(name.toString());
if (classDef == null) {
return null;
}
return new Map.Entry<PoolClassDef, Integer>() {
@Override public PoolClassDef getKey() {
return classDef;
}
@Override public Integer getValue() {
return classDef.classDefIndex;
}
@Override public Integer setValue(Integer value) {
return classDef.classDefIndex = value;
}
};
}
@Nonnull @Override public CharSequence getType(@Nonnull PoolClassDef classDef) {
return classDef.getType();
}
@Override public int getAccessFlags(@Nonnull PoolClassDef classDef) {
return classDef.getAccessFlags();
}
@Nullable @Override public CharSequence getSuperclass(@Nonnull PoolClassDef classDef) {
return classDef.getSuperclass();
}
@Nullable @Override public TypeListPool.Key<List<String>> getInterfaces(@Nonnull PoolClassDef classDef) {
return classDef.interfaces;
}
@Nullable @Override public CharSequence getSourceFile(@Nonnull PoolClassDef classDef) {
return classDef.getSourceFile();
}
private static final Predicate<Field> HAS_INITIALIZER = new Predicate<Field>() {
@Override
public boolean apply(Field input) {
EncodedValue encodedValue = input.getInitialValue();
return encodedValue != null && !EncodedValueUtils.isDefaultValue(encodedValue);
}
};
private static final Function<Field, EncodedValue> GET_INITIAL_VALUE = new Function<Field, EncodedValue>() {
@Override
public EncodedValue apply(Field input) {
EncodedValue initialValue = input.getInitialValue();
if (initialValue == null) {
return ImmutableEncodedValueFactory.defaultValueForType(input.getType());
}
return initialValue;
}
};
@Nullable @Override public Collection<? extends EncodedValue> getStaticInitializers(
@Nonnull PoolClassDef classDef) {
final SortedSet<Field> sortedStaticFields = classDef.getStaticFields();
final int lastIndex = CollectionUtils.lastIndexOf(sortedStaticFields, HAS_INITIALIZER);
if (lastIndex > -1) {
return new AbstractCollection<EncodedValue>() {
@Nonnull @Override public Iterator<EncodedValue> iterator() {
Iterable<Field> fields = Iterables.limit(sortedStaticFields, lastIndex + 1);
return Iterables.transform(fields, GET_INITIAL_VALUE).iterator();
}
@Override public int size() {
return lastIndex+1;
}
};
}
return null;
}
@Nonnull @Override public Collection<? extends Field> getSortedStaticFields(@Nonnull PoolClassDef classDef) {
return classDef.getStaticFields();
}
@Nonnull @Override public Collection<? extends Field> getSortedInstanceFields(@Nonnull PoolClassDef classDef) {
return classDef.getInstanceFields();
}
@Nonnull @Override public Collection<? extends Field> getSortedFields(@Nonnull PoolClassDef classDef) {
return classDef.getFields();
}
@Nonnull @Override public Collection<PoolMethod> getSortedDirectMethods(@Nonnull PoolClassDef classDef) {
return classDef.getDirectMethods();
}
@Nonnull @Override public Collection<PoolMethod> getSortedVirtualMethods(@Nonnull PoolClassDef classDef) {
return classDef.getVirtualMethods();
}
@Nonnull @Override public Collection<? extends PoolMethod> getSortedMethods(@Nonnull PoolClassDef classDef) {
return classDef.getMethods();
}
@Override public int getFieldAccessFlags(@Nonnull Field field) {
return field.getAccessFlags();
}
@Override public int getMethodAccessFlags(@Nonnull PoolMethod method) {
return method.getAccessFlags();
}
@Nullable @Override public Set<? extends Annotation> getClassAnnotations(@Nonnull PoolClassDef classDef) {
Set<? extends Annotation> annotations = classDef.getAnnotations();
if (annotations.size() == 0) {
return null;
}
return annotations;
}
@Nullable @Override public Set<? extends Annotation> getFieldAnnotations(@Nonnull Field field) {
Set<? extends Annotation> annotations = field.getAnnotations();
if (annotations.size() == 0) {
return null;
}
return annotations;
}
@Nullable @Override public Set<? extends Annotation> getMethodAnnotations(@Nonnull PoolMethod method) {
Set<? extends Annotation> annotations = method.getAnnotations();
if (annotations.size() == 0) {
return null;
}
return annotations;
}
private static final Predicate<MethodParameter> HAS_PARAMETER_ANNOTATIONS = new Predicate<MethodParameter>() {
@Override
public boolean apply(MethodParameter input) {
return input.getAnnotations().size() > 0;
}
};
private static final Function<MethodParameter, Set<? extends Annotation>> PARAMETER_ANNOTATIONS =
new Function<MethodParameter, Set<? extends Annotation>>() {
@Override
public Set<? extends Annotation> apply(MethodParameter input) {
return input.getAnnotations();
}
};
@Nullable @Override public List<? extends Set<? extends Annotation>> getParameterAnnotations(
@Nonnull final PoolMethod method) {
final List<? extends MethodParameter> parameters = method.getParameters();
boolean hasParameterAnnotations = Iterables.any(parameters, HAS_PARAMETER_ANNOTATIONS);
if (hasParameterAnnotations) {
return new AbstractForwardSequentialList<Set<? extends Annotation>>() {
@Nonnull @Override public Iterator<Set<? extends Annotation>> iterator() {
return Iterables.transform(parameters, PARAMETER_ANNOTATIONS).iterator();
}
@Override public int size() {
return parameters.size();
}
};
}
return null;
}
@Nullable @Override public Iterable<? extends DebugItem> getDebugItems(@Nonnull PoolMethod method) {
MethodImplementation impl = method.getImplementation();
if (impl != null) {
return impl.getDebugItems();
}
return null;
}
@Nullable @Override public Iterable<CharSequence> getParameterNames(@Nonnull PoolMethod method) {
return Iterables.transform(method.getParameters(), new Function<MethodParameter, CharSequence>() {
@Nullable @Override public CharSequence apply(MethodParameter input) {
return input.getName();
}
});
}
@Override public int getRegisterCount(@Nonnull PoolMethod method) {
MethodImplementation impl = method.getImplementation();
if (impl != null) {
return impl.getRegisterCount();
}
return 0;
}
@Nullable @Override public Iterable<? extends Instruction> getInstructions(@Nonnull PoolMethod method) {
MethodImplementation impl = method.getImplementation();
if (impl != null) {
return impl.getInstructions();
}
return null;
}
@Nonnull @Override public List<? extends TryBlock<? extends ExceptionHandler>> getTryBlocks(
@Nonnull PoolMethod method) {
MethodImplementation impl = method.getImplementation();
if (impl != null) {
return impl.getTryBlocks();
}
return ImmutableList.of();
}
@Nullable @Override public CharSequence getExceptionType(@Nonnull ExceptionHandler handler) {
return handler.getExceptionType();
}
@Nonnull @Override
public MutableMethodImplementation makeMutableMethodImplementation(@Nonnull PoolMethod poolMethod) {
return new MutableMethodImplementation(poolMethod.getImplementation());
}
@Override public void setEncodedArrayOffset(@Nonnull PoolClassDef classDef, int offset) {
classDef.encodedArrayOffset = offset;
}
@Override public int getEncodedArrayOffset(@Nonnull PoolClassDef classDef) {
return classDef.encodedArrayOffset;
}
@Override public void setAnnotationDirectoryOffset(@Nonnull PoolClassDef classDef, int offset) {
classDef.annotationDirectoryOffset = offset;
}
@Override public int getAnnotationDirectoryOffset(@Nonnull PoolClassDef classDef) {
return classDef.annotationDirectoryOffset;
}
@Override public void setAnnotationSetRefListOffset(@Nonnull PoolMethod method, int offset) {
method.annotationSetRefListOffset = offset;
}
@Override public int getAnnotationSetRefListOffset(@Nonnull PoolMethod method) {
return method.annotationSetRefListOffset;
}
@Override public void setCodeItemOffset(@Nonnull PoolMethod method, int offset) {
method.codeItemOffset = offset;
}
@Override public int getCodeItemOffset(@Nonnull PoolMethod method) {
return method.codeItemOffset;
}
@Override public void writeDebugItem(@Nonnull DebugWriter<CharSequence, CharSequence> writer,
DebugItem debugItem) throws IOException {
switch (debugItem.getDebugItemType()) {
case DebugItemType.START_LOCAL: {
StartLocal startLocal = (StartLocal)debugItem;
writer.writeStartLocal(startLocal.getCodeAddress(),
startLocal.getRegister(),
startLocal.getName(),
startLocal.getType(),
startLocal.getSignature());
break;
}
case DebugItemType.END_LOCAL: {
EndLocal endLocal = (EndLocal)debugItem;
writer.writeEndLocal(endLocal.getCodeAddress(), endLocal.getRegister());
break;
}
case DebugItemType.RESTART_LOCAL: {
RestartLocal restartLocal = (RestartLocal)debugItem;
writer.writeRestartLocal(restartLocal.getCodeAddress(), restartLocal.getRegister());
break;
}
case DebugItemType.PROLOGUE_END: {
writer.writePrologueEnd(debugItem.getCodeAddress());
break;
}
case DebugItemType.EPILOGUE_BEGIN: {
writer.writeEpilogueBegin(debugItem.getCodeAddress());
break;
}
case DebugItemType.LINE_NUMBER: {
LineNumber lineNumber = (LineNumber)debugItem;
writer.writeLineNumber(lineNumber.getCodeAddress(), lineNumber.getLineNumber());
break;
}
case DebugItemType.SET_SOURCE_FILE: {
SetSourceFile setSourceFile = (SetSourceFile)debugItem;
writer.writeSetSourceFile(setSourceFile.getCodeAddress(), setSourceFile.getSourceFile());
}
default:
throw new ExceptionWithContext("Unexpected debug item type: %d", debugItem.getDebugItemType());
}
}
@Override public int getItemIndex(@Nonnull PoolClassDef classDef) {
return classDef.classDefIndex;
}
@Nonnull @Override public Collection<? extends Map.Entry<PoolClassDef, Integer>> getItems() {
class MapEntry implements Map.Entry<PoolClassDef, Integer> {
@Nonnull private final PoolClassDef classDef;
public MapEntry(@Nonnull PoolClassDef classDef) {
this.classDef = classDef;
}
@Override public PoolClassDef getKey() {
return classDef;
}
@Override public Integer getValue() {
return classDef.classDefIndex;
}
@Override public Integer setValue(Integer value) {
int prev = classDef.classDefIndex;
classDef.classDefIndex = value;
return prev;
}
}
return new AbstractCollection<Entry<PoolClassDef, Integer>>() {
@Nonnull @Override public Iterator<Entry<PoolClassDef, Integer>> iterator() {
return new Iterator<Entry<PoolClassDef, Integer>>() {
Iterator<PoolClassDef> iter = internedItems.values().iterator();
@Override public boolean hasNext() {
return iter.hasNext();
}
@Override public Entry<PoolClassDef, Integer> next() {
return new MapEntry(iter.next());
}
@Override public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override public int size() {
return internedItems.size();
}
};
}
}