aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Gruver <bgruv@google.com>2014-07-19 20:23:32 -0700
committerBen Gruver <bgruv@google.com>2014-07-19 20:26:50 -0700
commitd33fd6773cc3e527b4dbb79245796d556b7facde (patch)
tree8ac6c767b6ae7d4ef94a3109e2ee6f4bb1e10032
parentce7f93cd79fc2263794f02f5fca3ff65ab3d243f (diff)
downloadsmali-d33fd6773cc3e527b4dbb79245796d556b7facde.tar.gz
Implement implicit method/field references in baksmali
-rw-r--r--baksmali/build.gradle1
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/AnnotationFormatter.java14
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java16
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/AnnotationEncodedValueAdaptor.java16
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/ArrayEncodedValueAdaptor.java10
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/EncodedValueAdaptor.java33
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/FieldDefinition.java20
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java10
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java30
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java1
-rw-r--r--baksmali/src/main/java/org/jf/baksmali/main.java8
-rw-r--r--baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java1
-rw-r--r--baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java322
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java4
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/util/ReferenceUtil.java68
-rw-r--r--dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ClassPool.java2
-rw-r--r--util/src/main/java/org/jf/util/TextUtils.java59
17 files changed, 557 insertions, 58 deletions
diff --git a/baksmali/build.gradle b/baksmali/build.gradle
index 1f2e47c8..150eb6fd 100644
--- a/baksmali/build.gradle
+++ b/baksmali/build.gradle
@@ -40,6 +40,7 @@ dependencies {
compile depends.guava
testCompile depends.junit
+ testCompile project(':smali')
proguard depends.proguard
}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/AnnotationFormatter.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/AnnotationFormatter.java
index 2b9614f1..1310f191 100644
--- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/AnnotationFormatter.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/AnnotationFormatter.java
@@ -33,13 +33,16 @@ import org.jf.dexlib2.AnnotationVisibility;
import org.jf.dexlib2.iface.Annotation;
import org.jf.util.IndentingWriter;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import java.io.IOException;
import java.util.Collection;
public class AnnotationFormatter {
- public static void writeTo(IndentingWriter writer,
- Collection<? extends Annotation> annotations) throws IOException {
+ public static void writeTo(@Nonnull IndentingWriter writer,
+ @Nonnull Collection<? extends Annotation> annotations,
+ @Nullable String containingClass) throws IOException {
boolean first = true;
for (Annotation annotation: annotations) {
if (!first) {
@@ -47,18 +50,19 @@ public class AnnotationFormatter {
}
first = false;
- writeTo(writer, annotation);
+ writeTo(writer, annotation, containingClass);
}
}
- public static void writeTo(IndentingWriter writer, Annotation annotation) throws IOException {
+ public static void writeTo(@Nonnull IndentingWriter writer, @Nonnull Annotation annotation,
+ @Nullable String containingClass) throws IOException {
writer.write(".annotation ");
writer.write(AnnotationVisibility.getVisibility(annotation.getVisibility()));
writer.write(' ');
writer.write(annotation.getType());
writer.write('\n');
- AnnotationEncodedValueAdaptor.writeElementsTo(writer, annotation.getElements());
+ AnnotationEncodedValueAdaptor.writeElementsTo(writer, annotation.getElements(), containingClass);
writer.write(".end annotation\n");
}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java
index a4eb6913..9c171f49 100644
--- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java
@@ -165,7 +165,13 @@ public class ClassDefinition {
if (classAnnotations.size() != 0) {
writer.write("\n\n");
writer.write("# annotations\n");
- AnnotationFormatter.writeTo(writer, classAnnotations);
+
+ String containingClass = null;
+ if (options.useImplicitReferences) {
+ containingClass = classDef.getType();
+ }
+
+ AnnotationFormatter.writeTo(writer, classAnnotations, containingClass);
}
}
@@ -199,7 +205,7 @@ public class ClassDefinition {
} else {
setInStaticConstructor = fieldsSetInStaticConstructor.contains(fieldString);
}
- FieldDefinition.writeTo(fieldWriter, field, setInStaticConstructor);
+ FieldDefinition.writeTo(options, fieldWriter, field, setInStaticConstructor);
}
return writtenFields;
}
@@ -237,7 +243,7 @@ public class ClassDefinition {
writer.write("# There is both a static and instance field with this signature.\n" +
"# You will need to rename one of these fields, including all references.\n");
}
- FieldDefinition.writeTo(fieldWriter, field, false);
+ FieldDefinition.writeTo(options, fieldWriter, field, false);
}
}
@@ -261,7 +267,7 @@ public class ClassDefinition {
writer.write('\n');
// TODO: check for method validation errors
- String methodString = ReferenceUtil.getShortMethodDescriptor(method);
+ String methodString = ReferenceUtil.getMethodDescriptor(method, true);
IndentingWriter methodWriter = writer;
if (!writtenMethods.add(methodString)) {
@@ -300,7 +306,7 @@ public class ClassDefinition {
writer.write('\n');
// TODO: check for method validation errors
- String methodString = ReferenceUtil.getShortMethodDescriptor(method);
+ String methodString = ReferenceUtil.getMethodDescriptor(method, true);
IndentingWriter methodWriter = writer;
if (!writtenMethods.add(methodString)) {
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/AnnotationEncodedValueAdaptor.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/AnnotationEncodedValueAdaptor.java
index 66cd506c..e8f12a29 100644
--- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/AnnotationEncodedValueAdaptor.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/AnnotationEncodedValueAdaptor.java
@@ -32,28 +32,32 @@ import org.jf.dexlib2.iface.AnnotationElement;
import org.jf.dexlib2.iface.value.AnnotationEncodedValue;
import org.jf.util.IndentingWriter;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import java.io.IOException;
import java.util.Collection;
public abstract class AnnotationEncodedValueAdaptor {
- public static void writeTo(IndentingWriter writer, AnnotationEncodedValue annotationEncodedValue)
- throws IOException {
+ public static void writeTo(@Nonnull IndentingWriter writer,
+ @Nonnull AnnotationEncodedValue annotationEncodedValue,
+ @Nullable String containingClass) throws IOException {
writer.write(".subannotation ");
writer.write(annotationEncodedValue.getType());
writer.write('\n');
- writeElementsTo(writer, annotationEncodedValue.getElements());
+ writeElementsTo(writer, annotationEncodedValue.getElements(), containingClass);
writer.write(".end subannotation");
}
- public static void writeElementsTo(IndentingWriter writer,
- Collection<? extends AnnotationElement> annotationElements) throws IOException {
+ public static void writeElementsTo(@Nonnull IndentingWriter writer,
+ @Nonnull Collection<? extends AnnotationElement> annotationElements,
+ @Nullable String containingClass) throws IOException {
writer.indent(4);
for (AnnotationElement annotationElement: annotationElements) {
writer.write(annotationElement.getName());
writer.write(" = ");
- EncodedValueAdaptor.writeTo(writer, annotationElement.getValue());
+ EncodedValueAdaptor.writeTo(writer, annotationElement.getValue(), containingClass);
writer.write('\n');
}
writer.deindent(4);
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/ArrayEncodedValueAdaptor.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/ArrayEncodedValueAdaptor.java
index 8da1b044..eb079b30 100644
--- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/ArrayEncodedValueAdaptor.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/ArrayEncodedValueAdaptor.java
@@ -28,15 +28,19 @@
package org.jf.baksmali.Adaptors.EncodedValue;
-import org.jf.util.IndentingWriter;
import org.jf.dexlib2.iface.value.ArrayEncodedValue;
import org.jf.dexlib2.iface.value.EncodedValue;
+import org.jf.util.IndentingWriter;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import java.io.IOException;
import java.util.Collection;
public class ArrayEncodedValueAdaptor {
- public static void writeTo(IndentingWriter writer, ArrayEncodedValue arrayEncodedValue) throws IOException {
+ public static void writeTo(@Nonnull IndentingWriter writer,
+ @Nonnull ArrayEncodedValue arrayEncodedValue,
+ @Nullable String containingClass) throws IOException {
writer.write('{');
Collection<? extends EncodedValue> values = arrayEncodedValue.getValue();
if (values.size() == 0) {
@@ -53,7 +57,7 @@ public class ArrayEncodedValueAdaptor {
}
first = false;
- EncodedValueAdaptor.writeTo(writer, encodedValue);
+ EncodedValueAdaptor.writeTo(writer, encodedValue, containingClass);
}
writer.deindent(4);
writer.write("\n}");
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/EncodedValueAdaptor.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/EncodedValueAdaptor.java
index f4d442e0..bce1ff7a 100644
--- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/EncodedValueAdaptor.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/EncodedValueAdaptor.java
@@ -29,22 +29,26 @@
package org.jf.baksmali.Adaptors.EncodedValue;
import org.jf.baksmali.Adaptors.ReferenceFormatter;
+import org.jf.baksmali.Renderers.*;
import org.jf.dexlib2.ValueType;
import org.jf.dexlib2.iface.value.*;
import org.jf.dexlib2.util.ReferenceUtil;
import org.jf.util.IndentingWriter;
-import org.jf.baksmali.Renderers.*;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import java.io.IOException;
public abstract class EncodedValueAdaptor {
- public static void writeTo(IndentingWriter writer, EncodedValue encodedValue) throws IOException {
+ public static void writeTo(@Nonnull IndentingWriter writer, @Nonnull EncodedValue encodedValue,
+ @Nullable String containingClass)
+ throws IOException {
switch (encodedValue.getValueType()) {
case ValueType.ANNOTATION:
- AnnotationEncodedValueAdaptor.writeTo(writer, (AnnotationEncodedValue)encodedValue);
+ AnnotationEncodedValueAdaptor.writeTo(writer, (AnnotationEncodedValue)encodedValue, containingClass);
return;
case ValueType.ARRAY:
- ArrayEncodedValueAdaptor.writeTo(writer, (ArrayEncodedValue)encodedValue);
+ ArrayEncodedValueAdaptor.writeTo(writer, (ArrayEncodedValue)encodedValue, containingClass);
return;
case ValueType.BOOLEAN:
BooleanRenderer.writeTo(writer, ((BooleanEncodedValue)encodedValue).getValue());
@@ -59,11 +63,21 @@ public abstract class EncodedValueAdaptor {
DoubleRenderer.writeTo(writer, ((DoubleEncodedValue)encodedValue).getValue());
return;
case ValueType.ENUM:
+ EnumEncodedValue enumEncodedValue = (EnumEncodedValue)encodedValue;
+ boolean useImplicitReference = false;
+ if (enumEncodedValue.getValue().getDefiningClass().equals(containingClass)) {
+ useImplicitReference = true;
+ }
writer.write(".enum ");
- ReferenceUtil.writeFieldDescriptor(writer, ((EnumEncodedValue)encodedValue).getValue());
+ ReferenceUtil.writeFieldDescriptor(writer, enumEncodedValue.getValue(), useImplicitReference);
return;
case ValueType.FIELD:
- ReferenceUtil.writeFieldDescriptor(writer, ((FieldEncodedValue)encodedValue).getValue());
+ FieldEncodedValue fieldEncodedValue = (FieldEncodedValue)encodedValue;
+ useImplicitReference = false;
+ if (fieldEncodedValue.getValue().getDefiningClass().equals(containingClass)) {
+ useImplicitReference = true;
+ }
+ ReferenceUtil.writeFieldDescriptor(writer, fieldEncodedValue.getValue(), useImplicitReference);
return;
case ValueType.FLOAT:
FloatRenderer.writeTo(writer, ((FloatEncodedValue)encodedValue).getValue());
@@ -75,7 +89,12 @@ public abstract class EncodedValueAdaptor {
LongRenderer.writeTo(writer, ((LongEncodedValue)encodedValue).getValue());
return;
case ValueType.METHOD:
- ReferenceUtil.writeMethodDescriptor(writer, ((MethodEncodedValue)encodedValue).getValue());
+ MethodEncodedValue methodEncodedValue = (MethodEncodedValue)encodedValue;
+ useImplicitReference = false;
+ if (methodEncodedValue.getValue().getDefiningClass().equals(containingClass)) {
+ useImplicitReference = true;
+ }
+ ReferenceUtil.writeMethodDescriptor(writer, methodEncodedValue.getValue(), useImplicitReference);
return;
case ValueType.NULL:
writer.write("null");
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/FieldDefinition.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/FieldDefinition.java
index a71089d8..ae017914 100644
--- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/FieldDefinition.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/FieldDefinition.java
@@ -29,6 +29,7 @@
package org.jf.baksmali.Adaptors;
import org.jf.baksmali.Adaptors.EncodedValue.EncodedValueAdaptor;
+import org.jf.baksmali.baksmaliOptions;
import org.jf.dexlib2.AccessFlags;
import org.jf.dexlib2.iface.Annotation;
import org.jf.dexlib2.iface.Field;
@@ -40,7 +41,8 @@ import java.io.IOException;
import java.util.Collection;
public class FieldDefinition {
- public static void writeTo(IndentingWriter writer, Field field, boolean setInStaticConstructor) throws IOException {
+ public static void writeTo(baksmaliOptions options, IndentingWriter writer, Field field,
+ boolean setInStaticConstructor) throws IOException {
EncodedValue initialValue = field.getInitialValue();
int accessFlags = field.getAccessFlags();
@@ -64,7 +66,13 @@ public class FieldDefinition {
writer.write(field.getType());
if (initialValue != null) {
writer.write(" = ");
- EncodedValueAdaptor.writeTo(writer, initialValue);
+
+ String containingClass = null;
+ if (options.useImplicitReferences) {
+ containingClass = field.getDefiningClass();
+ }
+
+ EncodedValueAdaptor.writeTo(writer, initialValue, containingClass);
}
writer.write('\n');
@@ -72,7 +80,13 @@ public class FieldDefinition {
Collection<? extends Annotation> annotations = field.getAnnotations();
if (annotations.size() > 0) {
writer.indent(4);
- AnnotationFormatter.writeTo(writer, annotations);
+
+ String containingClass = null;
+ if (options.useImplicitReferences) {
+ containingClass = field.getDefiningClass();
+ }
+
+ AnnotationFormatter.writeTo(writer, annotations, containingClass);
writer.deindent(4);
writer.write(".end field\n");
}
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java
index 300efd81..b0fdaf2b 100644
--- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java
@@ -41,6 +41,8 @@ import org.jf.dexlib2.iface.instruction.*;
import org.jf.dexlib2.iface.instruction.formats.Instruction20bc;
import org.jf.dexlib2.iface.instruction.formats.Instruction31t;
import org.jf.dexlib2.iface.instruction.formats.UnknownInstruction;
+import org.jf.dexlib2.iface.reference.FieldReference;
+import org.jf.dexlib2.iface.reference.MethodReference;
import org.jf.dexlib2.iface.reference.Reference;
import org.jf.dexlib2.util.ReferenceUtil;
import org.jf.util.ExceptionWithContext;
@@ -102,7 +104,13 @@ public class InstructionMethodItem<T extends Instruction> extends MethodItem {
ReferenceInstruction referenceInstruction = (ReferenceInstruction)instruction;
try {
Reference reference = referenceInstruction.getReference();
- referenceString = ReferenceUtil.getReferenceString(reference);
+
+ String classContext = null;
+ if (methodDef.classDef.options.useImplicitReferences) {
+ classContext = methodDef.method.getDefiningClass();
+ }
+
+ referenceString = ReferenceUtil.getReferenceString(reference, classContext);
assert referenceString != null;
} catch (InvalidItemIndex ex) {
writer.write("#");
diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java
index 5ce971f5..eb5caa67 100644
--- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java
+++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java
@@ -56,6 +56,7 @@ import org.jf.util.IndentingWriter;
import org.jf.util.SparseIntArray;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import java.io.IOException;
import java.util.*;
@@ -148,7 +149,13 @@ public class MethodDefinition {
writer.indent(4);
writeParameters(writer, method, methodParameters, options);
- AnnotationFormatter.writeTo(writer, method.getAnnotations());
+
+ String containingClass = null;
+ if (options.useImplicitReferences) {
+ containingClass = method.getDefiningClass();
+ }
+ AnnotationFormatter.writeTo(writer, method.getAnnotations(), containingClass);
+
writer.deindent(4);
writer.write(".end method\n");
}
@@ -191,7 +198,11 @@ public class MethodDefinition {
parameterRegisterCount);
}
- AnnotationFormatter.writeTo(writer, method.getAnnotations());
+ String containingClass = null;
+ if (classDef.options.useImplicitReferences) {
+ containingClass = method.getDefiningClass();
+ }
+ AnnotationFormatter.writeTo(writer, method.getAnnotations(), containingClass);
writer.write('\n');
@@ -264,7 +275,12 @@ public class MethodDefinition {
writer.write("\n");
if (annotations.size() > 0) {
writer.indent(4);
- AnnotationFormatter.writeTo(writer, annotations);
+
+ String containingClass = null;
+ if (options.useImplicitReferences) {
+ containingClass = method.getDefiningClass();
+ }
+ AnnotationFormatter.writeTo(writer, annotations, containingClass);
writer.deindent(4);
writer.write(".end param\n");
}
@@ -519,6 +535,14 @@ public class MethodDefinition {
}
}
+ @Nullable
+ private String getContainingClassForImplicitReference() {
+ if (classDef.options.useImplicitReferences) {
+ return classDef.classDef.getType();
+ }
+ return null;
+ }
+
public static class LabelCache {
protected HashMap<LabelMethodItem, LabelMethodItem> labels = new HashMap<LabelMethodItem, LabelMethodItem>();
diff --git a/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java b/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java
index 1e83c288..57941e1c 100644
--- a/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java
+++ b/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java
@@ -72,6 +72,7 @@ public class baksmaliOptions {
public boolean deodex = false;
public boolean ignoreErrors = false;
public boolean checkPackagePrivateAccess = false;
+ public boolean useImplicitReferences = true;
public File customInlineDefinitions = null;
public InlineMethodResolver inlineResolver = null;
public int registerInfo = 0;
diff --git a/baksmali/src/main/java/org/jf/baksmali/main.java b/baksmali/src/main/java/org/jf/baksmali/main.java
index 274f1441..e50172fe 100644
--- a/baksmali/src/main/java/org/jf/baksmali/main.java
+++ b/baksmali/src/main/java/org/jf/baksmali/main.java
@@ -205,6 +205,9 @@ public class main {
String rif = commandLine.getOptionValue("i");
options.setResourceIdFiles(rif);
break;
+ case 't':
+ options.useImplicitReferences = false;
+ break;
case 'N':
disassemble = false;
break;
@@ -420,6 +423,10 @@ public class main {
.withArgName("FILES")
.create("i");
+ Option noImplicitReferencesOption = OptionBuilder.withLongOpt("no-implicit-references")
+ .withDescription("Don't use implicit (type-less) method and field references")
+ .create("t");
+
Option dumpOption = OptionBuilder.withLongOpt("dump-to")
.withDescription("dumps the given dex file into a single annotated dump file named FILE" +
" (<dexfile>.dump by default), along with the normal disassembly")
@@ -459,6 +466,7 @@ public class main {
basicOptions.addOption(apiLevelOption);
basicOptions.addOption(jobsOption);
basicOptions.addOption(resourceIdFilesOption);
+ basicOptions.addOption(noImplicitReferencesOption);
debugOptions.addOption(dumpOption);
debugOptions.addOption(ignoreErrorsOption);
diff --git a/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java b/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java
index 38219491..1fae6ae7 100644
--- a/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java
+++ b/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java
@@ -91,6 +91,7 @@ public class AnalysisTest {
options.registerInfo = baksmaliOptions.ALL | baksmaliOptions.FULLMERGE;
options.classPath = new ClassPath();
}
+ options.useImplicitReferences = false;
for (ClassDef classDef: dexFile.getClasses()) {
StringWriter stringWriter = new StringWriter();
diff --git a/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java b/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java
new file mode 100644
index 00000000..0cda27a1
--- /dev/null
+++ b/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright 2014, 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.baksmali;
+
+import junit.framework.Assert;
+import org.antlr.runtime.RecognitionException;
+import org.jf.baksmali.Adaptors.ClassDefinition;
+import org.jf.dexlib2.iface.ClassDef;
+import org.jf.smali.SmaliTestUtils;
+import org.jf.util.IndentingWriter;
+import org.jf.util.TextUtils;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+public class ImplicitReferenceTest {
+ @Test
+ public void testImplicitMethodReferences() throws IOException, RecognitionException {
+ ClassDef classDef = SmaliTestUtils.compileSmali("" +
+ ".class public LHelloWorld;\n" +
+ ".super Ljava/lang/Object;\n" +
+ ".method public static main([Ljava/lang/String;)V\n" +
+ " .registers 1\n" +
+ " invoke-static {p0}, LHelloWorld;->toString()V\n" +
+ " invoke-static {p0}, LHelloWorld;->V()V\n" +
+ " invoke-static {p0}, LHelloWorld;->I()V\n" +
+ " return-void\n" +
+ ".end method");
+
+ String expected = "" +
+ ".class public LHelloWorld;\n" +
+ ".super Ljava/lang/Object;\n" +
+ "# direct methods\n" +
+ ".method public static main([Ljava/lang/String;)V\n" +
+ ".registers 1\n" +
+ "invoke-static {p0}, toString()V\n" +
+ "invoke-static {p0}, V()V\n" +
+ "invoke-static {p0}, I()V\n" +
+ "return-void\n" +
+ ".end method\n";
+
+ baksmaliOptions options = new baksmaliOptions();
+ options.useImplicitReferences = true;
+
+ StringWriter stringWriter = new StringWriter();
+ IndentingWriter writer = new IndentingWriter(stringWriter);
+ ClassDefinition classDefinition = new ClassDefinition(options, classDef);
+ classDefinition.writeTo(writer);
+ writer.close();
+
+ Assert.assertEquals(TextUtils.normalizeWhitespace(expected),
+ TextUtils.normalizeWhitespace(stringWriter.toString()));
+ }
+
+ @Test
+ public void testExplicitMethodReferences() throws IOException, RecognitionException {
+ ClassDef classDef = SmaliTestUtils.compileSmali("" +
+ ".class public LHelloWorld;\n" +
+ ".super Ljava/lang/Object;\n" +
+ ".method public static main([Ljava/lang/String;)V\n" +
+ " .registers 1\n" +
+ " invoke-static {p0}, LHelloWorld;->toString()V\n" +
+ " invoke-static {p0}, LHelloWorld;->V()V\n" +
+ " invoke-static {p0}, LHelloWorld;->I()V\n" +
+ " return-void\n" +
+ ".end method");
+
+ String expected = "" +
+ ".class public LHelloWorld;\n" +
+ ".super Ljava/lang/Object;\n" +
+ "# direct methods\n" +
+ ".method public static main([Ljava/lang/String;)V\n" +
+ " .registers 1\n" +
+ " invoke-static {p0}, LHelloWorld;->toString()V\n" +
+ " invoke-static {p0}, LHelloWorld;->V()V\n" +
+ " invoke-static {p0}, LHelloWorld;->I()V\n" +
+ " return-void\n" +
+ ".end method\n";
+
+ baksmaliOptions options = new baksmaliOptions();
+ options.useImplicitReferences = false;
+
+ StringWriter stringWriter = new StringWriter();
+ IndentingWriter writer = new IndentingWriter(stringWriter);
+ ClassDefinition classDefinition = new ClassDefinition(options, classDef);
+ classDefinition.writeTo(writer);
+ writer.close();
+
+ Assert.assertEquals(TextUtils.normalizeWhitespace(expected),
+ TextUtils.normalizeWhitespace(stringWriter.toString()));
+ }
+
+ @Test
+ public void testImplicitMethodLiterals() throws IOException, RecognitionException {
+ ClassDef classDef = SmaliTestUtils.compileSmali("" +
+ ".class public LHelloWorld;\n" +
+ ".super Ljava/lang/Object;\n" +
+ ".field public static field1:Ljava/lang/reflect/Method; = LHelloWorld;->toString()V\n" +
+ ".field public static field2:Ljava/lang/reflect/Method; = LHelloWorld;->V()V\n" +
+ ".field public static field3:Ljava/lang/reflect/Method; = LHelloWorld;->I()V\n" +
+ ".field public static field4:Ljava/lang/Class; = I");
+
+ String expected = "" +
+ ".class public LHelloWorld;\n" +
+ ".super Ljava/lang/Object;\n" +
+ "# static fields\n" +
+ ".field public static field1:Ljava/lang/reflect/Method; = toString()V\n" +
+ ".field public static field2:Ljava/lang/reflect/Method; = V()V\n" +
+ ".field public static field3:Ljava/lang/reflect/Method; = I()V\n" +
+ ".field public static field4:Ljava/lang/Class; = I\n";
+
+ baksmaliOptions options = new baksmaliOptions();
+ options.useImplicitReferences = true;
+
+ StringWriter stringWriter = new StringWriter();
+ IndentingWriter writer = new IndentingWriter(stringWriter);
+ ClassDefinition classDefinition = new ClassDefinition(options, classDef);
+ classDefinition.writeTo(writer);
+ writer.close();
+
+ Assert.assertEquals(TextUtils.normalizeWhitespace(expected),
+ TextUtils.normalizeWhitespace(stringWriter.toString()));
+ }
+
+ @Test
+ public void testExplicitMethodLiterals() throws IOException, RecognitionException {
+ ClassDef classDef = SmaliTestUtils.compileSmali("" +
+ ".class public LHelloWorld;\n" +
+ ".super Ljava/lang/Object;\n" +
+ ".field public static field1:Ljava/lang/reflect/Method; = LHelloWorld;->toString()V\n" +
+ ".field public static field2:Ljava/lang/reflect/Method; = LHelloWorld;->V()V\n" +
+ ".field public static field3:Ljava/lang/reflect/Method; = LHelloWorld;->I()V\n" +
+ ".field public static field4:Ljava/lang/Class; = I");
+
+ String expected = "" +
+ ".class public LHelloWorld;\n" +
+ ".super Ljava/lang/Object;\n" +
+ "# static fields\n" +
+ ".field public static field1:Ljava/lang/reflect/Method; = LHelloWorld;->toString()V\n" +
+ ".field public static field2:Ljava/lang/reflect/Method; = LHelloWorld;->V()V\n" +
+ ".field public static field3:Ljava/lang/reflect/Method; = LHelloWorld;->I()V\n" +
+ ".field public static field4:Ljava/lang/Class; = I\n";
+
+ baksmaliOptions options = new baksmaliOptions();
+ options.useImplicitReferences = false;
+
+ StringWriter stringWriter = new StringWriter();
+ IndentingWriter writer = new IndentingWriter(stringWriter);
+ ClassDefinition classDefinition = new ClassDefinition(options, classDef);
+ classDefinition.writeTo(writer);
+ writer.close();
+
+ Assert.assertEquals(TextUtils.normalizeWhitespace(expected),
+ TextUtils.normalizeWhitespace(stringWriter.toString()));
+ }
+
+ @Test
+ public void testImplicitFieldReferences() throws IOException, RecognitionException {
+ ClassDef classDef = SmaliTestUtils.compileSmali("" +
+ ".class public LHelloWorld;\n" +
+ ".super Ljava/lang/Object;\n" +
+ ".method public static main([Ljava/lang/String;)V\n" +
+ " .registers 1\n" +
+ " sget v0, LHelloWorld;->someField:I\n" +
+ " sget v0, LHelloWorld;->I:I\n" +
+ " sget v0, LHelloWorld;->V:I\n" +
+ " return-void\n" +
+ ".end method");
+
+ String expected = "" +
+ ".class public LHelloWorld;\n" +
+ ".super Ljava/lang/Object;\n" +
+ "# direct methods\n" +
+ ".method public static main([Ljava/lang/String;)V\n" +
+ " .registers 1\n" +
+ " sget p0, someField:I\n" +
+ " sget p0, I:I\n" +
+ " sget p0, V:I\n" +
+ " return-void\n" +
+ ".end method\n";
+
+ baksmaliOptions options = new baksmaliOptions();
+ options.useImplicitReferences = true;
+
+ StringWriter stringWriter = new StringWriter();
+ IndentingWriter writer = new IndentingWriter(stringWriter);
+ ClassDefinition classDefinition = new ClassDefinition(options, classDef);
+ classDefinition.writeTo(writer);
+ writer.close();
+
+ Assert.assertEquals(TextUtils.normalizeWhitespace(expected),
+ TextUtils.normalizeWhitespace(stringWriter.toString()));
+ }
+
+ @Test
+ public void testExplicitFieldReferences() throws IOException, RecognitionException {
+ ClassDef classDef = SmaliTestUtils.compileSmali("" +
+ ".class public LHelloWorld;\n" +
+ ".super Ljava/lang/Object;\n" +
+ ".method public static main([Ljava/lang/String;)V\n" +
+ " .registers 1\n" +
+ " sget v0, LHelloWorld;->someField:I\n" +
+ " sget v0, LHelloWorld;->I:I\n" +
+ " sget v0, LHelloWorld;->V:I\n" +
+ " return-void\n" +
+ ".end method");
+
+ String expected = "" +
+ ".class public LHelloWorld;\n" +
+ ".super Ljava/lang/Object;\n" +
+ "# direct methods\n" +
+ ".method public static main([Ljava/lang/String;)V\n" +
+ " .registers 1\n" +
+ " sget p0, LHelloWorld;->someField:I\n" +
+ " sget p0, LHelloWorld;->I:I\n" +
+ " sget p0, LHelloWorld;->V:I\n" +
+ " return-void\n" +
+ ".end method\n";
+
+ baksmaliOptions options = new baksmaliOptions();
+ options.useImplicitReferences = false;
+
+ StringWriter stringWriter = new StringWriter();
+ IndentingWriter writer = new IndentingWriter(stringWriter);
+ ClassDefinition classDefinition = new ClassDefinition(options, classDef);
+ classDefinition.writeTo(writer);
+ writer.close();
+
+ Assert.assertEquals(TextUtils.normalizeWhitespace(expected),
+ TextUtils.normalizeWhitespace(stringWriter.toString()));
+ }
+
+ @Test
+ public void testImplicitFieldLiterals() throws IOException, RecognitionException {
+ ClassDef classDef = SmaliTestUtils.compileSmali("" +
+ ".class public LHelloWorld;\n" +
+ ".super Ljava/lang/Object;\n" +
+ ".field public static field1:Ljava/lang/reflect/Field; = LHelloWorld;->someField:I\n" +
+ ".field public static field2:Ljava/lang/reflect/Field; = LHelloWorld;->V:I\n" +
+ ".field public static field3:Ljava/lang/reflect/Field; = LHelloWorld;->I:I");
+
+ String expected = "" +
+ ".class public LHelloWorld;\n" +
+ ".super Ljava/lang/Object;\n" +
+ "# static fields\n" +
+ ".field public static field1:Ljava/lang/reflect/Field; = someField:I\n" +
+ ".field public static field2:Ljava/lang/reflect/Field; = V:I\n" +
+ ".field public static field3:Ljava/lang/reflect/Field; = I:I\n";
+
+ baksmaliOptions options = new baksmaliOptions();
+ options.useImplicitReferences = true;
+
+ StringWriter stringWriter = new StringWriter();
+ IndentingWriter writer = new IndentingWriter(stringWriter);
+ ClassDefinition classDefinition = new ClassDefinition(options, classDef);
+ classDefinition.writeTo(writer);
+ writer.close();
+
+ Assert.assertEquals(TextUtils.normalizeWhitespace(expected),
+ TextUtils.normalizeWhitespace(stringWriter.toString()));
+ }
+
+ @Test
+ public void testExplicitFieldLiterals() throws IOException, RecognitionException {
+ ClassDef classDef = SmaliTestUtils.compileSmali("" +
+ ".class public LHelloWorld;\n" +
+ ".super Ljava/lang/Object;\n" +
+ ".field public static field1:Ljava/lang/reflect/Field; = LHelloWorld;->someField:I\n" +
+ ".field public static field2:Ljava/lang/reflect/Field; = LHelloWorld;->V:I\n" +
+ ".field public static field3:Ljava/lang/reflect/Field; = LHelloWorld;->I:I");
+
+ String expected = "" +
+ ".class public LHelloWorld;\n" +
+ ".super Ljava/lang/Object;\n" +
+ "# static fields\n" +
+ ".field public static field1:Ljava/lang/reflect/Field; = LHelloWorld;->someField:I\n" +
+ ".field public static field2:Ljava/lang/reflect/Field; = LHelloWorld;->V:I\n" +
+ ".field public static field3:Ljava/lang/reflect/Field; = LHelloWorld;->I:I\n";
+
+ baksmaliOptions options = new baksmaliOptions();
+ options.useImplicitReferences = false;
+
+ StringWriter stringWriter = new StringWriter();
+ IndentingWriter writer = new IndentingWriter(stringWriter);
+ ClassDefinition classDefinition = new ClassDefinition(options, classDef);
+ classDefinition.writeTo(writer);
+ writer.close();
+
+ Assert.assertEquals(TextUtils.normalizeWhitespace(expected),
+ TextUtils.normalizeWhitespace(stringWriter.toString()));
+ }
+}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java
index 80e9ced4..35ad0c88 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java
@@ -1637,7 +1637,7 @@ public class MethodAnalyzer {
String superclass = methodClass.getSuperclass();
if (superclass == null) {
throw new ExceptionWithContext("Couldn't find accessible class while resolving method %s",
- ReferenceUtil.getShortMethodDescriptor(resolvedMethod));
+ ReferenceUtil.getMethodDescriptor(resolvedMethod, true));
}
methodClass = classPath.getClassDef(superclass);
@@ -1648,7 +1648,7 @@ public class MethodAnalyzer {
resolvedMethod = classPath.getClass(methodClass.getType()).getMethodByVtableIndex(methodIndex);
if (resolvedMethod == null) {
throw new ExceptionWithContext("Couldn't find accessible class while resolving method %s",
- ReferenceUtil.getShortMethodDescriptor(resolvedMethod));
+ ReferenceUtil.getMethodDescriptor(resolvedMethod, true));
}
resolvedMethod = new ImmutableMethodReference(methodClass.getType(), resolvedMethod.getName(),
resolvedMethod.getParameterTypes(), resolvedMethod.getReturnType());
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/util/ReferenceUtil.java b/dexlib2/src/main/java/org/jf/dexlib2/util/ReferenceUtil.java
index a81facf3..81b042ec 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/util/ReferenceUtil.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/util/ReferenceUtil.java
@@ -34,27 +34,22 @@ package org.jf.dexlib2.util;
import org.jf.dexlib2.iface.reference.*;
import org.jf.util.StringUtils;
+import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.Writer;
public final class ReferenceUtil {
- public static String getShortMethodDescriptor(MethodReference methodReference) {
- StringBuilder sb = new StringBuilder();
- sb.append(methodReference.getName());
- sb.append('(');
- for (CharSequence paramType: methodReference.getParameterTypes()) {
- sb.append(paramType);
- }
- sb.append(')');
- sb.append(methodReference.getReturnType());
- return sb.toString();
+ public static String getMethodDescriptor(MethodReference methodReference) {
+ return getMethodDescriptor(methodReference, false);
}
- public static String getMethodDescriptor(MethodReference methodReference) {
+ public static String getMethodDescriptor(MethodReference methodReference, boolean useImplicitReference) {
StringBuilder sb = new StringBuilder();
- sb.append(methodReference.getDefiningClass());
- sb.append("->");
+ if (!useImplicitReference) {
+ sb.append(methodReference.getDefiningClass());
+ sb.append("->");
+ }
sb.append(methodReference.getName());
sb.append('(');
for (CharSequence paramType: methodReference.getParameterTypes()) {
@@ -66,8 +61,15 @@ public final class ReferenceUtil {
}
public static void writeMethodDescriptor(Writer writer, MethodReference methodReference) throws IOException {
- writer.write(methodReference.getDefiningClass());
- writer.write("->");
+ writeMethodDescriptor(writer, methodReference, false);
+ }
+
+ public static void writeMethodDescriptor(Writer writer, MethodReference methodReference,
+ boolean useImplicitReference) throws IOException {
+ if (!useImplicitReference) {
+ writer.write(methodReference.getDefiningClass());
+ writer.write("->");
+ }
writer.write(methodReference.getName());
writer.write('(');
for (CharSequence paramType: methodReference.getParameterTypes()) {
@@ -78,9 +80,15 @@ public final class ReferenceUtil {
}
public static String getFieldDescriptor(FieldReference fieldReference) {
+ return getFieldDescriptor(fieldReference, false);
+ }
+
+ public static String getFieldDescriptor(FieldReference fieldReference, boolean useImplicitReference) {
StringBuilder sb = new StringBuilder();
- sb.append(fieldReference.getDefiningClass());
- sb.append("->");
+ if (!useImplicitReference) {
+ sb.append(fieldReference.getDefiningClass());
+ sb.append("->");
+ }
sb.append(fieldReference.getName());
sb.append(':');
sb.append(fieldReference.getType());
@@ -96,15 +104,27 @@ public final class ReferenceUtil {
}
public static void writeFieldDescriptor(Writer writer, FieldReference fieldReference) throws IOException {
- writer.write(fieldReference.getDefiningClass());
- writer.write("->");
+ writeFieldDescriptor(writer, fieldReference, false);
+ }
+
+ public static void writeFieldDescriptor(Writer writer, FieldReference fieldReference,
+ boolean implicitReference) throws IOException {
+ if (!implicitReference) {
+ writer.write(fieldReference.getDefiningClass());
+ writer.write("->");
+ }
writer.write(fieldReference.getName());
writer.write(':');
writer.write(fieldReference.getType());
}
@Nullable
- public static String getReferenceString(Reference reference) {
+ public static String getReferenceString(@Nonnull Reference reference) {
+ return getReferenceString(reference, null);
+ }
+
+ @Nullable
+ public static String getReferenceString(@Nonnull Reference reference, @Nullable String containingClass) {
if (reference instanceof StringReference) {
return String.format("\"%s\"", StringUtils.escapeString(((StringReference)reference).getString()));
}
@@ -112,10 +132,14 @@ public final class ReferenceUtil {
return ((TypeReference)reference).getType();
}
if (reference instanceof FieldReference) {
- return getFieldDescriptor((FieldReference)reference);
+ FieldReference fieldReference = (FieldReference)reference;
+ boolean useImplicitReference = fieldReference.getDefiningClass().equals(containingClass);
+ return getFieldDescriptor((FieldReference)reference, useImplicitReference);
}
if (reference instanceof MethodReference) {
- return getMethodDescriptor((MethodReference)reference);
+ MethodReference methodReference = (MethodReference)reference;
+ boolean useImplicitReference = methodReference.getDefiningClass().equals(containingClass);
+ return getMethodDescriptor((MethodReference)reference, useImplicitReference);
}
return null;
}
diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ClassPool.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ClassPool.java
index 61ea02f7..f2440cdb 100644
--- a/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ClassPool.java
+++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ClassPool.java
@@ -116,7 +116,7 @@ public class ClassPool implements ClassSection<CharSequence, CharSequence,
HashSet<String> methods = new HashSet<String>();
for (PoolMethod method: poolClassDef.getMethods()) {
- String methodDescriptor = ReferenceUtil.getShortMethodDescriptor(method);
+ String methodDescriptor = ReferenceUtil.getMethodDescriptor(method, true);
if (!methods.add(methodDescriptor)) {
throw new ExceptionWithContext("Multiple definitions for method %s->%s",
poolClassDef.getType(), methodDescriptor);
diff --git a/util/src/main/java/org/jf/util/TextUtils.java b/util/src/main/java/org/jf/util/TextUtils.java
new file mode 100644
index 00000000..a01e68e7
--- /dev/null
+++ b/util/src/main/java/org/jf/util/TextUtils.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2014, 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.util;
+
+import javax.annotation.Nonnull;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class TextUtils {
+ private static String newline = System.getProperty("line.separator");
+
+ @Nonnull
+ public static String normalizeNewlines(@Nonnull String source) {
+ return normalizeNewlines(source, newline);
+ }
+
+ @Nonnull
+ public static String normalizeNewlines(@Nonnull String source, String newlineValue) {
+ return source.replace("\r", "").replace("\n", newlineValue);
+ }
+
+ @Nonnull
+ public static String normalizeWhitespace(@Nonnull String source) {
+ source = normalizeNewlines(source, "\n");
+
+ Pattern pattern = Pattern.compile("(\n[ \t]*)+");
+ Matcher matcher = pattern.matcher(source);
+ return matcher.replaceAll("\n");
+ }
+}