From 31834dceb9b476b49bbc0cb8f2c02e2319527c1f Mon Sep 17 00:00:00 2001 From: REAndroid Date: Sat, 2 Mar 2024 19:15:15 +0100 Subject: [PATCH] [DEX] Bulk type rename --- .../java/com/reandroid/dex/key/KeyPair.java | 45 +++- .../java/com/reandroid/dex/key/TypeKey.java | 13 ++ .../dex/model/DexClassRepository.java | 1 + .../com/reandroid/dex/model/DexDirectory.java | 1 + .../java/com/reandroid/dex/model/DexFile.java | 1 + .../com/reandroid/dex/refactor/Rename.java | 80 ++++--- .../reandroid/dex/refactor/RenameInfo.java | 203 ------------------ .../reandroid/dex/refactor/RenameTypes.java | 140 ++++++++++++ 8 files changed, 236 insertions(+), 248 deletions(-) delete mode 100644 src/main/java/com/reandroid/dex/refactor/RenameInfo.java create mode 100644 src/main/java/com/reandroid/dex/refactor/RenameTypes.java diff --git a/src/main/java/com/reandroid/dex/key/KeyPair.java b/src/main/java/com/reandroid/dex/key/KeyPair.java index d4df327f5..05baeb358 100644 --- a/src/main/java/com/reandroid/dex/key/KeyPair.java +++ b/src/main/java/com/reandroid/dex/key/KeyPair.java @@ -16,7 +16,12 @@ package com.reandroid.dex.key; import com.reandroid.utils.CompareUtil; +import com.reandroid.utils.collection.ArrayCollection; +import com.reandroid.utils.collection.ComputeIterator; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; import java.util.Objects; public class KeyPair implements Comparable>{ @@ -48,13 +53,42 @@ public void setSecond(T2 second) { this.second = second; } + public KeyPair flip(){ + return new KeyPair<>(getSecond(), getFirst()); + } + public boolean isValid(){ + T1 t1 = getFirst(); + if(t1 == null){ + return false; + } + T2 t2 = getSecond(); + if(t2 == null){ + return false; + } + return !t1.equals(t2); + } @Override public int compareTo(KeyPair pair) { if(pair == null){ return -1; } - return CompareUtil.compare(getFirst(), pair.getFirst()); + Key key1 = this.getFirst(); + Key key2 = pair.getFirst(); + if(key1 == null){ + if(key2 == null){ + return 0; + } + return 1; + } + if(key2 == null){ + return -1; + } + int i = key1.getDeclaring().compareInnerFirst(key2.getDeclaring()); + if(i == 0){ + i = CompareUtil.compare(key1, key2); + } + return i; } @Override public boolean equals(Object obj) { @@ -80,4 +114,13 @@ public int hashCode() { public String toString() { return getFirst() + "=" + getSecond(); } + + public static Iterator> flip(Iterator> iterator){ + return ComputeIterator.of(iterator, KeyPair::flip); + } + public static List> flip(Collection> list){ + ArrayCollection> results = new ArrayCollection<>(list.size()); + results.addAll(KeyPair.flip(list.iterator())); + return results; + } } diff --git a/src/main/java/com/reandroid/dex/key/TypeKey.java b/src/main/java/com/reandroid/dex/key/TypeKey.java index f4c970018..9a9deacd5 100644 --- a/src/main/java/com/reandroid/dex/key/TypeKey.java +++ b/src/main/java/com/reandroid/dex/key/TypeKey.java @@ -20,6 +20,7 @@ import com.reandroid.dex.smali.SmaliReader; import com.reandroid.dex.smali.SmaliWriter; import com.reandroid.utils.CompareUtil; +import com.reandroid.utils.ObjectsUtil; import com.reandroid.utils.StringsUtil; import com.reandroid.utils.collection.EmptyIterator; import com.reandroid.utils.collection.SingleIterator; @@ -230,6 +231,18 @@ public void append(SmaliWriter writer) throws IOException { writer.append(getTypeName()); } + public int compareInnerFirst(TypeKey other) { + if(this.equals(other)){ + return 0; + } + String name1 = this.getSimpleName(); + String name2 = other.getSimpleName(); + int diff = StringsUtil.diffStart(name1, name2); + if(diff > 0 && name1.charAt(diff) == '$' && diff > name1.lastIndexOf('/') + 1){ + return CompareUtil.compare(name2, name1); + } + return CompareUtil.compare(name1, name2); + } @Override public int compareTo(Object obj) { if(obj == null){ diff --git a/src/main/java/com/reandroid/dex/model/DexClassRepository.java b/src/main/java/com/reandroid/dex/model/DexClassRepository.java index 49214ce46..f37a09ded 100644 --- a/src/main/java/com/reandroid/dex/model/DexClassRepository.java +++ b/src/main/java/com/reandroid/dex/model/DexClassRepository.java @@ -32,6 +32,7 @@ public interface DexClassRepository { Iterator getDexClasses(Predicate filter); Iterator getDexClassesCloned(Predicate filter); Iterator getItems(SectionType sectionType); + Iterator getClonedItems(SectionType sectionType); Iterator getItems(SectionType sectionType, Key key); T1 getItem(SectionType sectionType, Key key); diff --git a/src/main/java/com/reandroid/dex/model/DexDirectory.java b/src/main/java/com/reandroid/dex/model/DexDirectory.java index f5f8aaf76..1caaba389 100644 --- a/src/main/java/com/reandroid/dex/model/DexDirectory.java +++ b/src/main/java/com/reandroid/dex/model/DexDirectory.java @@ -342,6 +342,7 @@ public Iterator iterator(DexFile element) { } }; } + @Override public Iterator getClonedItems(SectionType sectionType) { return new IterableIterator(clonedIterator()) { @Override diff --git a/src/main/java/com/reandroid/dex/model/DexFile.java b/src/main/java/com/reandroid/dex/model/DexFile.java index d70780a0a..c4d8cadf5 100644 --- a/src/main/java/com/reandroid/dex/model/DexFile.java +++ b/src/main/java/com/reandroid/dex/model/DexFile.java @@ -232,6 +232,7 @@ public int removeEntries(SectionType sectionType, P public Iterator removeWithKeys(SectionType sectionType, Predicate filter){ return getDexLayout().removeWithKeys(sectionType, filter); } + @Override public Iterator getClonedItems(SectionType sectionType) { return getDexLayout().getClonedItems(sectionType); } diff --git a/src/main/java/com/reandroid/dex/refactor/Rename.java b/src/main/java/com/reandroid/dex/refactor/Rename.java index 58665f200..262ea9bd9 100644 --- a/src/main/java/com/reandroid/dex/refactor/Rename.java +++ b/src/main/java/com/reandroid/dex/refactor/Rename.java @@ -15,64 +15,56 @@ */ package com.reandroid.dex.refactor; -import com.reandroid.utils.collection.*; +import com.reandroid.dex.key.Key; +import com.reandroid.dex.key.KeyPair; +import com.reandroid.dex.model.DexClassRepository; +import com.reandroid.utils.CompareUtil; +import com.reandroid.utils.StringsUtil; +import com.reandroid.utils.collection.ArrayCollection; -import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; -import java.util.Iterator; -import java.util.List; +import java.util.*; -public class Rename implements Iterable>{ - private final List> renameInfoList; +public abstract class Rename { + + private final Set> keyPairSet; public Rename(){ - this.renameInfoList = new ArrayCollection<>(); + this.keyPairSet = new HashSet<>(); } - public Iterator> getAll(){ - return new MergingIterator<>(ComputeIterator.of(iterator(), - RenameInfo::iterator)); + public void add(KeyPair keyPair){ + addToSet(keyPair); } - - public void add(RenameInfo renameInfo){ - if(renameInfo == null || contains(renameInfo)){ - return; - } - this.renameInfoList.add(renameInfo); + public void addAll(Collection> keyPairs){ + this.addAll(keyPairs.iterator()); } - public boolean contains(RenameInfo renameInfo){ - if(renameInfo == null){ - return false; + public void addAll(Iterator> iterator){ + while (iterator.hasNext()){ + addToSet(iterator.next()); } - if(this.renameInfoList.contains(renameInfo)){ - return true; - } - for(RenameInfo info : this){ - if(info.contains(renameInfo)){ - return true; - } + } + private void addToSet(KeyPair keyPair){ + if(keyPair != null && keyPair.isValid()){ + this.keyPairSet.add(keyPair); } - return false; } - @Override - public Iterator> iterator(){ - return renameInfoList.iterator(); + public int size(){ + return keyPairSet.size(); } - public void write(Writer writer) throws IOException { - for(RenameInfo info : this){ - info.write(writer, true); - } + public List> sortedList(){ + List> results = new ArrayCollection<>(keyPairSet); + results.sort(CompareUtil.getComparableComparator()); + return results; + } + + public abstract int apply(DexClassRepository classRepository); + + public Set> getKeyPairSet() { + return keyPairSet; } + @Override public String toString() { - StringWriter writer = new StringWriter(); - try { - write(writer); - writer.close(); - } catch (IOException exception) { - return exception.toString(); - } - return writer.toString(); + return StringsUtil.join(sortedList(), '\n'); } } diff --git a/src/main/java/com/reandroid/dex/refactor/RenameInfo.java b/src/main/java/com/reandroid/dex/refactor/RenameInfo.java deleted file mode 100644 index 0db79dd68..000000000 --- a/src/main/java/com/reandroid/dex/refactor/RenameInfo.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (C) 2022 github.com/REAndroid - * - * 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.reandroid.dex.refactor; - -import com.reandroid.dex.common.SectionItem; -import com.reandroid.dex.id.StringId; -import com.reandroid.dex.key.KeyItem; -import com.reandroid.dex.key.Key; -import com.reandroid.dex.model.DexDirectory; -import com.reandroid.dex.pool.DexSectionPool; -import com.reandroid.dex.sections.Section; -import com.reandroid.dex.sections.SectionList; -import com.reandroid.dex.sections.SectionType; -import com.reandroid.utils.collection.ArrayCollection; -import com.reandroid.utils.collection.CombiningIterator; -import com.reandroid.utils.collection.ComputeIterator; -import com.reandroid.utils.collection.SingleIterator; - -import java.io.IOException; -import java.io.Writer; -import java.util.*; - -public abstract class RenameInfo implements KeyItem { - private final String search; - private final String replace; - private List> childRenames; - private int renameCount; - - public RenameInfo(String search, String replace){ - this.search = search; - this.replace = replace; - } - - public void apply(DexDirectory dexDirectory){ - - } - public void apply(SectionList sectionList){ - apply(sectionList.getSection(getSectionType())); - Iterator> iterator = getChildRenames(); - while (iterator.hasNext()){ - RenameInfo renameInfo = iterator.next(); - renameInfo.apply(sectionList); - } - } - private void apply(Section section){ - if(section == null){ - return; - } - DexSectionPool pool = section.getPool(); - Key key = getKey(); - Iterable group = pool.getGroup(key); - if(group == null){ - return; - } - apply(group); - pool.update(key); - addRenameCount(); - } - - public boolean contains(RenameInfo renameInfo){ - if(renameInfo == null){ - return false; - } - List> childRenames = this.childRenames; - if(childRenames == null){ - return false; - } - if(childRenames.contains(renameInfo)){ - return true; - } - for (RenameInfo info : childRenames){ - if(info.contains(renameInfo)){ - return true; - } - } - return false; - } - public boolean lookString(StringId stringData){ - return false; - } - public boolean looksStrings(){ - return false; - } - public Iterator> iterator(){ - return CombiningIterator.of(SingleIterator.of(this), - ComputeIterator.of(getChildRenames(), RenameInfo::iterator)); - } - - public String getSearch() { - return search; - } - public String getReplace() { - return replace; - } - Iterator> getChildRenames(){ - return listChildRenames().iterator(); - } - public void add(RenameInfo renameInfo){ - if(renameInfo == null || renameInfo == this){ - return; - } - List> renameInfoList = listChildRenames(); - if(renameInfoList == null || renameInfoList.isEmpty()){ - renameInfoList = new ArrayCollection<>(); - } - renameInfoList.add(renameInfo); - } - public List> listChildRenames() { - List> childRenames = this.childRenames; - if(childRenames == null){ - childRenames = createChildRenames(); - this.childRenames = childRenames; - } - return childRenames; - } - public RenameInfo getParent(){ - return null; - } - - abstract SectionType getSectionType(); - abstract void apply(Iterable group); - abstract List> createChildRenames(); - void addRenameCount(){ - renameCount ++; - } - public int getRenameCount() { - return renameCount; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - RenameInfo renameInfo = (RenameInfo) obj; - return Objects.equals(getKey(), renameInfo.getKey()); - } - @Override - public int hashCode() { - return Objects.hash(getKey()); - } - - private int getDepth(){ - int result = 0; - RenameInfo renameInfo = getParent(); - while (renameInfo != null){ - result ++; - renameInfo = renameInfo.getParent(); - } - return result; - } - public void write(Writer writer, boolean appendCount) throws IOException { - append(writer, appendCount); - Iterator> iterator = getChildRenames(); - while (iterator.hasNext()){ - iterator.next().write(writer, appendCount); - } - } - void append(Writer writer, boolean appendCount) throws IOException { - int count = getRenameCount(); - if(appendCount && count == 0){ - return; - } - appendIndent(writer); - writer.write(getKey().toString()); - writer.write("="); - writer.write(getReplace()); - if(appendCount){ - writer.append(" // count="); - writer.write(Integer.toString(getRenameCount())); - } - writer.write("\n"); - } - void appendIndent(Writer writer) throws IOException { - int depth = getDepth() * 2; - for(int i = 0; i < depth; i++){ - writer.append(' '); - } - } - private String getLogTag(){ - return "[" + getClass().getSimpleName() + "] "; - } - @Override - public String toString() { - return getKey() + "=" + getReplace(); - } -} diff --git a/src/main/java/com/reandroid/dex/refactor/RenameTypes.java b/src/main/java/com/reandroid/dex/refactor/RenameTypes.java new file mode 100644 index 000000000..739f9b508 --- /dev/null +++ b/src/main/java/com/reandroid/dex/refactor/RenameTypes.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2022 github.com/REAndroid + * + * 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.reandroid.dex.refactor; + +import com.reandroid.dex.id.StringId; +import com.reandroid.dex.key.KeyPair; +import com.reandroid.dex.key.TypeKey; +import com.reandroid.dex.model.DexClassRepository; +import com.reandroid.dex.sections.SectionType; +import com.reandroid.utils.ObjectsUtil; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +public class RenameTypes extends Rename{ + + private int arrayDepth; + private boolean renameSignatures; + private boolean renameSource; + private boolean noRenameSourceForNoPackageClass; + + public RenameTypes(){ + super(); + this.arrayDepth = DEFAULT_ARRAY_DEPTH; + this.renameSignatures = true; + this.renameSource = true; + this.noRenameSourceForNoPackageClass = true; + } + + @Override + public int apply(DexClassRepository classRepository) { + Map map = buildMap(); + Iterator iterator = classRepository.getClonedItems(SectionType.STRING_ID); + int count = 0; + while (iterator.hasNext()){ + StringId stringId = iterator.next(); + String text = map.get(stringId.getString()); + if(text == null){ + continue; + } + stringId.setString(text); + count ++; + } + return count; + } + + public void setArrayDepth(int arrayDepth) { + if(arrayDepth < 0){ + arrayDepth = DEFAULT_ARRAY_DEPTH; + } + this.arrayDepth = arrayDepth; + } + public void setRenameSignatures(boolean renameSignatures) { + this.renameSignatures = renameSignatures; + } + public void setRenameSource(boolean renameSource) { + this.renameSource = renameSource; + } + public void setNoRenameSourceForNoPackageClass(boolean noRenameSourceForNoPackageClass) { + this.noRenameSourceForNoPackageClass = noRenameSourceForNoPackageClass; + } + + private Map buildMap() { + List> list = sortedList(); + boolean renameSignatures = this.renameSignatures; + boolean renameSource = this.renameSource; + boolean noRenameSourceForNoPackageClass = this.noRenameSourceForNoPackageClass; + + int estimatedSize = 1; + if(renameSignatures){ + estimatedSize = estimatedSize + 1; + } + if(renameSource){ + estimatedSize = estimatedSize + 1; + } + if(arrayDepth > 0){ + estimatedSize = estimatedSize + arrayDepth + 1; + } + estimatedSize = list.size() * estimatedSize; + + Map map = new HashMap<>(estimatedSize); + + int size = list.size(); + int arrayDepth = this.arrayDepth + 1; + + for(int i = 0; i < size; i++){ + + KeyPair keyPair = list.get(i); + TypeKey first = keyPair.getFirst(); + TypeKey second = keyPair.getSecond(); + + String name1 = first.getTypeName(); + String name2 = second.getTypeName(); + map.put(name1, name2); + + if(renameSignatures){ + name1 = name1.replace(';', '<'); + name2 = name2.replace(';', '<'); + map.put(name1, name2); + } + + for(int j = 1; j < arrayDepth; j++){ + name1 = first.getArrayType(j); + name2 = first.getArrayType(j); + map.put(name1, name2); + if(renameSignatures && j == 1){ + name1 = name1.replace(';', '<'); + name2 = name2.replace(';', '<'); + map.put(name1, name2); + } + } + if(renameSource){ + name1 = first.getTypeName(); + if(!noRenameSourceForNoPackageClass || name1.indexOf('/') > 0){ + name1 = first.getSourceName(); + name2 = second.getSourceName(); + map.put(name1, name2); + } + } + } + return map; + } + + public static final int DEFAULT_ARRAY_DEPTH = ObjectsUtil.of(3); +}