mirror of
https://github.com/revanced/Apktool.git
synced 2025-01-06 01:55:53 +01:00
Implement implicit method/field references in baksmali
This commit is contained in:
parent
2772be8e9d
commit
1b0a917a6a
@ -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()));
|
||||
}
|
||||
}
|
@ -40,6 +40,7 @@ dependencies {
|
||||
compile depends.guava
|
||||
|
||||
testCompile depends.junit
|
||||
testCompile project(':smali')
|
||||
|
||||
proguard depends.proguard
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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)) {
|
||||
|
@ -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);
|
||||
|
@ -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}");
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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("#");
|
||||
|
@ -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>();
|
||||
|
||||
|
@ -74,6 +74,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;
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
59
util/src/main/java/org/jf/util/TextUtils.java
Normal file
59
util/src/main/java/org/jf/util/TextUtils.java
Normal file
@ -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");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user