Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in / Register
Toggle navigation
S
SandHook
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
SandHook
Commits
5c250a7a
Commit
5c250a7a
authored
Feb 16, 2019
by
swift_gan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add dexmaker cache
parent
0b884655
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
4095 additions
and
24 deletions
+4095
-24
SandHook.java
hooklib/src/main/java/com/swift/sandhook/SandHook.java
+1
-1
build.gradle
xposedcompat/build.gradle
+1
-1
AnnotationId.java
xposedcompat/src/main/java/com/android/dx/AnnotationId.java
+257
-0
AppDataDirGuesser.java
...ompat/src/main/java/com/android/dx/AppDataDirGuesser.java
+225
-0
BinaryOp.java
xposedcompat/src/main/java/com/android/dx/BinaryOp.java
+129
-0
Code.java
xposedcompat/src/main/java/com/android/dx/Code.java
+922
-0
Comparison.java
xposedcompat/src/main/java/com/android/dx/Comparison.java
+77
-0
Constants.java
xposedcompat/src/main/java/com/android/dx/Constants.java
+72
-0
DexMaker.java
xposedcompat/src/main/java/com/android/dx/DexMaker.java
+700
-0
FieldId.java
xposedcompat/src/main/java/com/android/dx/FieldId.java
+77
-0
Label.java
xposedcompat/src/main/java/com/android/dx/Label.java
+92
-0
Local.java
xposedcompat/src/main/java/com/android/dx/Local.java
+75
-0
MethodId.java
xposedcompat/src/main/java/com/android/dx/MethodId.java
+131
-0
TypeId.java
xposedcompat/src/main/java/com/android/dx/TypeId.java
+153
-0
TypeList.java
xposedcompat/src/main/java/com/android/dx/TypeList.java
+67
-0
UnaryOp.java
xposedcompat/src/main/java/com/android/dx/UnaryOp.java
+44
-0
ProxyBuilder.java
...mpat/src/main/java/com/android/dx/stock/ProxyBuilder.java
+994
-0
XposedCompat.java
...in/java/com/swift/sandhook/xposedcompat/XposedCompat.java
+15
-1
DynamicBridge.java
.../swift/sandhook/xposedcompat/methodgen/DynamicBridge.java
+17
-10
HookerDexMaker.java
...swift/sandhook/xposedcompat/methodgen/HookerDexMaker.java
+32
-11
DexMakerUtils.java
.../com/swift/sandhook/xposedcompat/utils/DexMakerUtils.java
+14
-0
No files found.
hooklib/src/main/java/com/swift/sandhook/SandHook.java
View file @
5c250a7a
...
...
@@ -74,7 +74,7 @@ public class SandHook {
public
static
synchronized
void
hook
(
HookWrapper
.
HookEntity
entity
)
throws
HookErrorException
{
if
(
entity
==
null
)
throw
new
HookErrorException
(
"null entity"
);
throw
new
HookErrorException
(
"null
hook
entity"
);
Member
target
=
entity
.
target
;
Method
hook
=
entity
.
hook
;
...
...
xposedcompat/build.gradle
View file @
5c250a7a
...
...
@@ -26,7 +26,7 @@ android {
dependencies
{
implementation
fileTree
(
include:
[
'*.jar'
],
dir:
'libs'
)
implementation
'com.
linkedin.dexmaker:dexmaker:2.21.0
'
implementation
'com.
jakewharton.android.repackaged:dalvik-dx:9.0.0_r3
'
compileOnly
project
(
':hooklib'
)
}
...
...
xposedcompat/src/main/java/com/android/dx/AnnotationId.java
0 → 100755
View file @
5c250a7a
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
android
.
dx
;
import
com.android.dx.dex.file.ClassDefItem
;
import
com.android.dx.rop.annotation.Annotation
;
import
com.android.dx.rop.annotation.AnnotationVisibility
;
import
com.android.dx.rop.annotation.Annotations
;
import
com.android.dx.rop.annotation.NameValuePair
;
import
com.android.dx.rop.cst.*
;
import
java.lang.annotation.ElementType
;
import
java.util.HashMap
;
/**
* Identifies an annotation on a program element, see {@link ElementType}.
*
* Currently it is only targeting Class, Method, Field and Parameter because those are supported by
* {@link com.android.dx.dex.file.AnnotationsDirectoryItem} so far.
*
* <p><strong>NOTE:</strong>
* So far it only supports adding method annotation. The annotation of class, field and parameter
* will be implemented later.
*
* <p><strong>WARNING:</strong>
* The declared element of an annotation type should either have a default value or be set a value via
* {@code AnnotationId.set(Element)}. Otherwise it will incur
* {@link java.lang.annotation.IncompleteAnnotationException} when accessing the annotation element
* through reflection. The example is as follows:
* <pre>
* {@code @Retention(RetentionPolicy.RUNTIME)}
* {@code @Target({ElementType.METHOD})}
* {@code @interface MethodAnnotation {
* boolean elementBoolean();
* // boolean elementBoolean() default false;
* }
*
* TypeId<?> GENERATED = TypeId.get("LGenerated;");
* MethodId<?, Void> methodId = GENERATED.getMethod(VOID, "call");
* Code code = dexMaker.declare(methodId, PUBLIC);
* code.returnVoid();
*
* TypeId<MethodAnnotation> annotationTypeId = TypeId.get(MethodAnnotation.class);
* AnnotationId<?, MethodAnnotation> annotationId = AnnotationId.get(GENERATED,
* annotationTypeId, ElementType.METHOD);
*
* AnnotationId.Element element = new AnnotationId.Element("elementBoolean", true);
* annotationId.set(element);
* annotationId.addToMethod(dexMaker, methodId);
* }
* </pre>
*
* @param <D> the type that declares the program element.
* @param <V> the annotation type. It should be a known type before compile.
*/
public
final
class
AnnotationId
<
D
,
V
>
{
private
final
TypeId
<
D
>
declaringType
;
private
final
TypeId
<
V
>
type
;
/** The type of program element to be annotated */
private
final
ElementType
annotatedElement
;
/** The elements this annotation holds */
private
final
HashMap
<
String
,
NameValuePair
>
elements
;
private
AnnotationId
(
TypeId
<
D
>
declaringType
,
TypeId
<
V
>
type
,
ElementType
annotatedElement
)
{
this
.
declaringType
=
declaringType
;
this
.
type
=
type
;
this
.
annotatedElement
=
annotatedElement
;
this
.
elements
=
new
HashMap
<>();
}
/**
* Construct an instance. It initially contains no elements.
*
* @param declaringType the type declaring the program element.
* @param type the annotation type.
* @param annotatedElement the program element type to be annotated.
* @return an annotation {@code AnnotationId<D,V>} instance.
*/
public
static
<
D
,
V
>
AnnotationId
<
D
,
V
>
get
(
TypeId
<
D
>
declaringType
,
TypeId
<
V
>
type
,
ElementType
annotatedElement
)
{
if
(
annotatedElement
!=
ElementType
.
TYPE
&&
annotatedElement
!=
ElementType
.
METHOD
&&
annotatedElement
!=
ElementType
.
FIELD
&&
annotatedElement
!=
ElementType
.
PARAMETER
)
{
throw
new
IllegalArgumentException
(
"element type is not supported to annotate yet."
);
}
return
new
AnnotationId
<>(
declaringType
,
type
,
annotatedElement
);
}
/**
* Set an annotation element of this instance.
* If there is a preexisting element with the same name, it will be
* replaced by this method.
*
* @param element {@code non-null;} the annotation element to be set.
*/
public
void
set
(
Element
element
)
{
if
(
element
==
null
)
{
throw
new
NullPointerException
(
"element == null"
);
}
CstString
pairName
=
new
CstString
(
element
.
getName
());
Constant
pairValue
=
Element
.
toConstant
(
element
.
getValue
());
NameValuePair
nameValuePair
=
new
NameValuePair
(
pairName
,
pairValue
);
elements
.
put
(
element
.
getName
(),
nameValuePair
);
}
/**
* Add this annotation to a method.
*
* @param dexMaker DexMaker instance.
* @param method Method to be added to.
*/
public
void
addToMethod
(
DexMaker
dexMaker
,
MethodId
<?,
?>
method
)
{
if
(
annotatedElement
!=
ElementType
.
METHOD
)
{
throw
new
IllegalStateException
(
"This annotation is not for method"
);
}
if
(!
method
.
declaringType
.
equals
(
declaringType
))
{
throw
new
IllegalArgumentException
(
"Method"
+
method
+
"'s declaring type is inconsistent with"
+
this
);
}
ClassDefItem
classDefItem
=
dexMaker
.
getTypeDeclaration
(
declaringType
).
toClassDefItem
();
if
(
classDefItem
==
null
)
{
throw
new
NullPointerException
(
"No class defined item is found"
);
}
else
{
CstMethodRef
cstMethodRef
=
method
.
constant
;
if
(
cstMethodRef
==
null
)
{
throw
new
NullPointerException
(
"Method reference is NULL"
);
}
else
{
// Generate CstType
CstType
cstType
=
CstType
.
intern
(
type
.
ropType
);
// Generate Annotation
Annotation
annotation
=
new
Annotation
(
cstType
,
AnnotationVisibility
.
RUNTIME
);
// Add generated annotation
Annotations
annotations
=
new
Annotations
();
for
(
NameValuePair
nvp
:
elements
.
values
())
{
annotation
.
add
(
nvp
);
}
annotations
.
add
(
annotation
);
classDefItem
.
addMethodAnnotations
(
cstMethodRef
,
annotations
,
dexMaker
.
getDexFile
());
}
}
}
/**
* A wrapper of <code>NameValuePair</code> represents a (name, value) pair used as the contents
* of an annotation.
*
* An {@code Element} instance is stored in {@code AnnotationId.elements} by calling {@code
* AnnotationId.set(Element)}.
*
* <p><strong>WARNING: </strong></p>
* the name should be exact same as the annotation element declared in the annotation type
* which is referred by field {@code AnnotationId.type},otherwise the annotation will fail
* to add and {@code java.lang.reflect.Method.getAnnotations()} will return nothing.
*
*/
public
static
final
class
Element
{
/** {@code non-null;} the name */
private
final
String
name
;
/** {@code non-null;} the value */
private
final
Object
value
;
/**
* Construct an instance.
*
* @param name {@code non-null;} the name
* @param value {@code non-null;} the value
*/
public
Element
(
String
name
,
Object
value
)
{
if
(
name
==
null
)
{
throw
new
NullPointerException
(
"name == null"
);
}
if
(
value
==
null
)
{
throw
new
NullPointerException
(
"value == null"
);
}
this
.
name
=
name
;
this
.
value
=
value
;
}
public
String
getName
()
{
return
name
;
}
public
Object
getValue
()
{
return
value
;
}
/** {@inheritDoc} */
@Override
public
String
toString
()
{
return
"["
+
name
+
", "
+
value
+
"]"
;
}
/** {@inheritDoc} */
@Override
public
int
hashCode
()
{
return
name
.
hashCode
()
*
31
+
value
.
hashCode
();
}
/** {@inheritDoc} */
@Override
public
boolean
equals
(
Object
other
)
{
if
(!
(
other
instanceof
Element
))
{
return
false
;
}
Element
otherElement
=
(
Element
)
other
;
return
name
.
equals
(
otherElement
.
name
)
&&
value
.
equals
(
otherElement
.
value
);
}
/**
* Convert a value of an element to a {@code Constant}.
* <p><strong>Warning:</strong> Array or TypeId value is not supported yet.
*
* @param value an annotation element value.
* @return a Constant
*/
static
Constant
toConstant
(
Object
value
)
{
Class
clazz
=
value
.
getClass
();
if
(
clazz
.
isEnum
())
{
CstString
descriptor
=
new
CstString
(
TypeId
.
get
(
clazz
).
getName
());
CstString
name
=
new
CstString
(((
Enum
)
value
).
name
());
CstNat
cstNat
=
new
CstNat
(
name
,
descriptor
);
return
new
CstEnumRef
(
cstNat
);
}
else
if
(
clazz
.
isArray
())
{
throw
new
UnsupportedOperationException
(
"Array is not supported yet"
);
}
else
if
(
value
instanceof
TypeId
)
{
throw
new
UnsupportedOperationException
(
"TypeId is not supported yet"
);
}
else
{
return
Constants
.
getConstant
(
value
);
}
}
}
}
xposedcompat/src/main/java/com/android/dx/AppDataDirGuesser.java
0 → 100755
View file @
5c250a7a
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
android
.
dx
;
import
java.io.File
;
import
java.lang.reflect.Field
;
import
java.lang.reflect.Method
;
import
java.util.ArrayList
;
import
java.util.List
;
/**
* Uses heuristics to guess the application's private data directory.
*/
class
AppDataDirGuesser
{
// Copied from UserHandle, indicates range of uids allocated for a user.
public
static
final
int
PER_USER_RANGE
=
100000
;
public
File
guess
()
{
try
{
ClassLoader
classLoader
=
guessSuitableClassLoader
();
// Check that we have an instance of the PathClassLoader.
Class
<?>
clazz
=
Class
.
forName
(
"dalvik.system.PathClassLoader"
);
clazz
.
cast
(
classLoader
);
// Use the toString() method to calculate the data directory.
String
pathFromThisClassLoader
=
getPathFromThisClassLoader
(
classLoader
,
clazz
);
File
[]
results
=
guessPath
(
pathFromThisClassLoader
);
if
(
results
.
length
>
0
)
{
return
results
[
0
];
}
}
catch
(
ClassCastException
ignored
)
{
}
catch
(
ClassNotFoundException
ignored
)
{
}
return
null
;
}
private
ClassLoader
guessSuitableClassLoader
()
{
return
AppDataDirGuesser
.
class
.
getClassLoader
();
}
private
String
getPathFromThisClassLoader
(
ClassLoader
classLoader
,
Class
<?>
pathClassLoaderClass
)
{
// Prior to ICS, we can simply read the "path" field of the
// PathClassLoader.
try
{
Field
pathField
=
pathClassLoaderClass
.
getDeclaredField
(
"path"
);
pathField
.
setAccessible
(
true
);
return
(
String
)
pathField
.
get
(
classLoader
);
}
catch
(
NoSuchFieldException
ignored
)
{
}
catch
(
IllegalAccessException
ignored
)
{
}
catch
(
ClassCastException
ignored
)
{
}
// Parsing toString() method: yuck. But no other way to get the path.
String
result
=
classLoader
.
toString
();
return
processClassLoaderString
(
result
);
}
/**
* Given the result of a ClassLoader.toString() call, process the result so that guessPath
* can use it. There are currently two variants. For Android 4.3 and later, the string
* "DexPathList" should be recognized and the array of dex path elements is parsed. for
* earlier versions, the last nested array ('[' ... ']') is enclosing the string we are
* interested in.
*/
static
String
processClassLoaderString
(
String
input
)
{
if
(
input
.
contains
(
"DexPathList"
))
{
return
processClassLoaderString43OrLater
(
input
);
}
else
{
return
processClassLoaderString42OrEarlier
(
input
);
}
}
private
static
String
processClassLoaderString42OrEarlier
(
String
input
)
{
/* The toString output looks like this:
* dalvik.system.PathClassLoader[dexPath=path/to/apk,libraryPath=path/to/libs]
*/
int
index
=
input
.
lastIndexOf
(
'['
);
input
=
(
index
==
-
1
)
?
input
:
input
.
substring
(
index
+
1
);
index
=
input
.
indexOf
(
']'
);
input
=
(
index
==
-
1
)
?
input
:
input
.
substring
(
0
,
index
);
return
input
;
}
private
static
String
processClassLoaderString43OrLater
(
String
input
)
{
/* The toString output looks like this:
* dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/{NAME}", ...], nativeLibraryDirectories=[...]]]
*/
int
start
=
input
.
indexOf
(
"DexPathList"
)
+
"DexPathList"
.
length
();
if
(
input
.
length
()
>
start
+
4
)
{
// [[ + ]]
String
trimmed
=
input
.
substring
(
start
);
int
end
=
trimmed
.
indexOf
(
']'
);
if
(
trimmed
.
charAt
(
0
)
==
'['
&&
trimmed
.
charAt
(
1
)
==
'['
&&
end
>=
0
)
{
trimmed
=
trimmed
.
substring
(
2
,
end
);
// Comma-separated list, Arrays.toString output.
String
split
[]
=
trimmed
.
split
(
","
);
// Clean up parts. Each path element is the type of the element plus the path in
// quotes.
for
(
int
i
=
0
;
i
<
split
.
length
;
i
++)
{
int
quoteStart
=
split
[
i
].
indexOf
(
'"'
);
int
quoteEnd
=
split
[
i
].
lastIndexOf
(
'"'
);
if
(
quoteStart
>
0
&&
quoteStart
<
quoteEnd
)
{
split
[
i
]
=
split
[
i
].
substring
(
quoteStart
+
1
,
quoteEnd
);
}
}
// Need to rejoin components.
StringBuilder
sb
=
new
StringBuilder
();
for
(
String
s
:
split
)
{
if
(
sb
.
length
()
>
0
)
{
sb
.
append
(
':'
);
}
sb
.
append
(
s
);
}
return
sb
.
toString
();
}
}
// This is technically a parsing failure. Return the original string, maybe a later
// stage can still salvage this.
return
input
;
}
File
[]
guessPath
(
String
input
)
{
List
<
File
>
results
=
new
ArrayList
<>();
for
(
String
potential
:
splitPathList
(
input
))
{
if
(!
potential
.
startsWith
(
"/data/app/"
))
{
continue
;
}
int
start
=
"/data/app/"
.
length
();
int
end
=
potential
.
lastIndexOf
(
".apk"
);
if
(
end
!=
potential
.
length
()
-
4
)
{
continue
;
}
int
dash
=
potential
.
indexOf
(
"-"
);
if
(
dash
!=
-
1
)
{
end
=
dash
;
}
String
packageName
=
potential
.
substring
(
start
,
end
);
File
dataDir
=
getWriteableDirectory
(
"/data/data/"
+
packageName
);
if
(
dataDir
==
null
)
{
// If we can't access "/data/data", try to guess user specific data directory.
dataDir
=
guessUserDataDirectory
(
packageName
);
}
if
(
dataDir
!=
null
)
{
File
cacheDir
=
new
File
(
dataDir
,
"cache"
);
// The cache directory might not exist -- create if necessary
if
(
fileOrDirExists
(
cacheDir
)
||
cacheDir
.
mkdir
())
{
if
(
isWriteableDirectory
(
cacheDir
))
{
results
.
add
(
cacheDir
);
}
}
}
}
return
results
.
toArray
(
new
File
[
results
.
size
()]);
}
static
String
[]
splitPathList
(
String
input
)
{
String
trimmed
=
input
;
if
(
input
.
startsWith
(
"dexPath="
))
{
int
start
=
"dexPath="
.
length
();
int
end
=
input
.
indexOf
(
','
);
trimmed
=
(
end
==
-
1
)
?
input
.
substring
(
start
)
:
input
.
substring
(
start
,
end
);
}
return
trimmed
.
split
(
":"
);
}
boolean
fileOrDirExists
(
File
file
)
{
return
file
.
exists
();
}
boolean
isWriteableDirectory
(
File
file
)
{
return
file
.
isDirectory
()
&&
file
.
canWrite
();
}
Integer
getProcessUid
()
{
/* Uses reflection to try to fetch process UID. It will only work when executing on
* Android device. Otherwise, returns null.
*/
try
{
Method
myUid
=
Class
.
forName
(
"android.os.Process"
).
getMethod
(
"myUid"
);
// Invoke the method on a null instance, since it's a static method.
return
(
Integer
)
myUid
.
invoke
(
/* instance= */
null
);
}
catch
(
Exception
e
)
{
// Catch any exceptions thrown and default to returning a null.
return
null
;
}
}
File
guessUserDataDirectory
(
String
packageName
)
{
Integer
uid
=
getProcessUid
();
if
(
uid
==
null
)
{
// If we couldn't retrieve process uid, return null.
return
null
;
}
// We're trying to get the ID of the Android user that's running the process. It can be
// inferred from the UID of the current process.
int
userId
=
uid
/
PER_USER_RANGE
;
return
getWriteableDirectory
(
String
.
format
(
"/data/user/%d/%s"
,
userId
,
packageName
));
}
private
File
getWriteableDirectory
(
String
pathName
)
{
File
dir
=
new
File
(
pathName
);
return
isWriteableDirectory
(
dir
)
?
dir
:
null
;
}
}
xposedcompat/src/main/java/com/android/dx/BinaryOp.java
0 → 100755
View file @
5c250a7a
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
android
.
dx
;
import
com.android.dx.rop.code.Rop
;
import
com.android.dx.rop.code.Rops
;
import
com.android.dx.rop.type.TypeList
;
/**
* An operation on two values of the same type.
*
* <p>Math operations ({@link #ADD}, {@link #SUBTRACT}, {@link #MULTIPLY},
* {@link #DIVIDE}, and {@link #REMAINDER}) support ints, longs, floats and
* doubles.
*
* <p>Bit operations ({@link #AND}, {@link #OR}, {@link #XOR}, {@link
* #SHIFT_LEFT}, {@link #SHIFT_RIGHT}, {@link #UNSIGNED_SHIFT_RIGHT}) support
* ints and longs.
*
* <p>Division by zero behaves differently depending on the operand type.
* For int and long operands, {@link #DIVIDE} and {@link #REMAINDER} throw
* {@link ArithmeticException} if {@code b == 0}. For float and double operands,
* the operations return {@code NaN}.
*/
public
enum
BinaryOp
{
/** {@code a + b} */
ADD
()
{
@Override
Rop
rop
(
TypeList
types
)
{
return
Rops
.
opAdd
(
types
);
}
},
/** {@code a - b} */
SUBTRACT
()
{
@Override
Rop
rop
(
TypeList
types
)
{
return
Rops
.
opSub
(
types
);
}
},
/** {@code a * b} */
MULTIPLY
()
{
@Override
Rop
rop
(
TypeList
types
)
{
return
Rops
.
opMul
(
types
);
}
},
/** {@code a / b} */
DIVIDE
()
{
@Override
Rop
rop
(
TypeList
types
)
{
return
Rops
.
opDiv
(
types
);
}
},
/** {@code a % b} */
REMAINDER
()
{
@Override
Rop
rop
(
TypeList
types
)
{
return
Rops
.
opRem
(
types
);
}
},
/** {@code a & b} */
AND
()
{
@Override
Rop
rop
(
TypeList
types
)
{
return
Rops
.
opAnd
(
types
);
}
},
/** {@code a | b} */
OR
()
{
@Override
Rop
rop
(
TypeList
types
)
{
return
Rops
.
opOr
(
types
);
}
},
/** {@code a ^ b} */
XOR
()
{
@Override
Rop
rop
(
TypeList
types
)
{
return
Rops
.
opXor
(
types
);
}
},
/** {@code a << b} */
SHIFT_LEFT
()
{
@Override
Rop
rop
(
TypeList
types
)
{
return
Rops
.
opShl
(
types
);
}
},
/** {@code a >> b} */
SHIFT_RIGHT
()
{
@Override
Rop
rop
(
TypeList
types
)
{
return
Rops
.
opShr
(
types
);
}
},
/** {@code a >>> b} */
UNSIGNED_SHIFT_RIGHT
()
{
@Override
Rop
rop
(
TypeList
types
)
{
return
Rops
.
opUshr
(
types
);
}
};
abstract
Rop
rop
(
TypeList
types
);
}
xposedcompat/src/main/java/com/android/dx/Code.java
0 → 100755
View file @
5c250a7a
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
android
.
dx
;
import
com.android.dx.rop.code.BasicBlockList
;
import
com.android.dx.rop.code.Insn
;
import
com.android.dx.rop.code.PlainCstInsn
;
import
com.android.dx.rop.code.PlainInsn
;
import
com.android.dx.rop.code.RegisterSpecList
;
import
com.android.dx.rop.code.Rop
;
import
com.android.dx.rop.code.Rops
;
import
com.android.dx.rop.code.SourcePosition
;
import
com.android.dx.rop.code.ThrowingCstInsn
;
import
com.android.dx.rop.code.ThrowingInsn
;
import
com.android.dx.rop.cst.CstInteger
;
import
com.android.dx.rop.type.StdTypeList
;
import
com.android.dx.rop.type.Type
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.Iterator
;
import
java.util.List
;
import
static
com
.
android
.
dx
.
rop
.
code
.
Rop
.
BRANCH_GOTO
;
import
static
com
.
android
.
dx
.
rop
.
code
.
Rop
.
BRANCH_NONE
;
import
static
com
.
android
.
dx
.
rop
.
code
.
Rop
.
BRANCH_RETURN
;
import
static
com
.
android
.
dx
.
rop
.
type
.
Type
.
BT_BYTE
;
import
static
com
.
android
.
dx
.
rop
.
type
.
Type
.
BT_CHAR
;
import
static
com
.
android
.
dx
.
rop
.
type
.
Type
.
BT_INT
;
import
static
com
.
android
.
dx
.
rop
.
type
.
Type
.
BT_SHORT
;
/**
* Builds a sequence of instructions.
*
* <h3>Locals</h3>
* All data manipulation takes place in local variables. Each parameter gets its
* own local by default; access these using {@link #getParameter
* getParameter()}. Non-static methods and constructors also have a {@code this}
* parameter; it's available as {@link #getThis getThis()}. Allocate a new local
* variable using {@link #newLocal newLocal()}, and assign a default value to it
* with {@link #loadConstant loadConstant()}. Copy a value from one local to
* another with {@link #move move()}.
*
* <p>Every local variable has a fixed type. This is either a primitive type (of
* any size) or a reference type. This class emits instructions appropriate to
* the types they operate on. Not all operations are local on all types;
* attempting to emit such an operation will fail with an unchecked exception.
*
* <h3>Math and Bit Operations</h3>
* Transform a single value into another related value using {@link
* #op(UnaryOp,Local,Local) op(UnaryOp, Local, Local)}. Transform two values
* into a third value using {@link #op(BinaryOp,Local,Local,Local) op(BinaryOp,
* Local, Local, Local)}. In either overload the first {@code Local} parameter
* is where the result will be sent; the other {@code Local} parameters are the
* inputs.
*
* <h3>Comparisons</h3>
* There are three different comparison operations each with different
* constraints:
* <ul>
* <li>{@link #compareLongs compareLongs()} compares two locals each
* containing a {@code long} primitive. This is the only operation that
* can compare longs. The result of the comparison is written to another
* {@code int} local.</li>
* <li>{@link #compareFloatingPoint compareFloatingPoint()} compares two
* locals; both {@code float} primitives or both {@code double}
* primitives. This is the only operation that can compare floating
* point values. This comparison takes an extra parameter that sets
* the desired result if either parameter is {@code NaN}. The result of
* the comparison is wrtten to another {@code int} local.
* <li>{@link #compare compare()} compares two locals. The {@link
* Comparison#EQ} and {@link Comparison#NE} options compare either
* {@code int} primitives or references. The other options compare only
* {@code int} primitives. This comparison takes a {@link Label} that
* will be jumped to if the comparison is true. If the comparison is
* false the next instruction in sequence will be executed.
* </ul>
* There's no single operation to compare longs and jump, or to compare ints and
* store the result in a local. Accomplish these goals by chaining multiple
* operations together.
*
* <h3>Branches, Labels and Returns</h3>
* Basic control flow is expressed using jumps and labels. Each label must be
* marked exactly once and may be jumped to any number of times. Create a label
* using its constructor: {@code new Label()}, and mark it using {@link #mark
* mark(Label)}. All jumps to a label will execute instructions starting from
* that label. You can jump to a label that hasn't yet been marked (jumping
* forward) or to a label that has already been marked (jumping backward). Jump
* unconditionally with {@link #jump jump(Label)} or conditionally based on a
* comparison using {@link #compare compare()}.
*
* <p>Most methods should contain a return instruction. Void methods
* should use {@link #returnVoid()}; non-void methods should use {@link
* #returnValue returnValue()} with a local whose return type matches the
* method's return type. Constructors are considered void methods and should
* call {@link #returnVoid()}. Methods may make multiple returns. Methods
* containing no return statements must either loop infinitely or throw
* unconditionally; it is not legal to end a sequence of instructions without a
* jump, return or throw.
*
* <h3>Throwing and Catching</h3>
* This API uses labels to handle thrown exceptions, errors and throwables. Call
* {@link #addCatchClause addCatchClause()} to register the target label and
* throwable class. All statements that follow will jump to that catch clause if
* they throw a {@link Throwable} assignable to that type. Use {@link
* #removeCatchClause removeCatchClause()} to unregister the throwable class.
*
* <p>Throw an throwable by first assigning it to a local and then calling
* {@link #throwValue throwValue()}. Control flow will jump to the nearest label
* assigned to a type assignable to the thrown type. In this context, "nearest"
* means the label requiring the fewest stack frames to be popped.
*
* <h3>Calling methods</h3>
* A method's caller must know its return type, name, parameters, and invoke
* kind. Lookup a method on a type using {@link TypeId#getMethod
* TypeId.getMethod()}. This is more onerous than Java language invokes, which
* can infer the target method using the target object and parameters. There are
* four invoke kinds:
* <ul>
* <li>{@link #invokeStatic invokeStatic()} is used for static methods.</li>
* <li>{@link #invokeDirect invokeDirect()} is used for private instance
* methods and for constructors to call their superclass's
* constructor.</li>
* <li>{@link #invokeInterface invokeInterface()} is used to invoke a method
* whose declaring type is an interface.</li>
* <li>{@link #invokeVirtual invokeVirtual()} is used to invoke any other
* method. The target must not be static, private, a constructor, or an
* interface method.</li>
* <li>{@link #invokeSuper invokeSuper()} is used to invoke the closest
* superclass's virtual method. The target must not be static, private,
* a constructor method, or an interface method.</li>
* <li>{@link #newInstance newInstance()} is used to invoke a
* constructor.</li>
* </ul>
* All invoke methods take a local for the return value. For void methods this
* local is unused and may be null.
*
* <h3>Field Access</h3>
* Read static fields using {@link #sget sget()}; write them using {@link
* #sput sput()}. For instance values you'll need to specify the declaring
* instance; use {@link #getThis getThis()} in an instance method to use {@code
* this}. Read instance values using {@link #iget iget()} and write them with
* {@link #iput iput()}.
*
* <h3>Array Access</h3>
* Allocate an array using {@link #newArray newArray()}. Read an array's length
* with {@link #arrayLength arrayLength()} and its elements with {@link #aget
* aget()}. Write an array's elements with {@link #aput aput()}.
*
* <h3>Types</h3>
* Use {@link #cast cast()} to perform either a <strong>numeric cast</strong> or
* a <strong>type cast</strong>. Interrogate the type of a value in a local
* using {@link #instanceOfType instanceOfType()}.
*
* <h3>Synchronization</h3>
* Acquire a monitor using {@link #monitorEnter monitorEnter()}; release it with
* {@link #monitorExit monitorExit()}. It is the caller's responsibility to
* guarantee that enter and exit calls are balanced, even in the presence of
* exceptions thrown.
*
* <strong>Warning:</strong> Even if a method has the {@code synchronized} flag,
* dex requires instructions to acquire and release monitors manually. A method
* declared with {@link java.lang.reflect.Modifier#SYNCHRONIZED SYNCHRONIZED}
* but without manual calls to {@code monitorEnter()} and {@code monitorExit()}
* will not be synchronized when executed.
*/
public
final
class
Code
{
private
final
MethodId
<?,
?>
method
;
/**
* All allocated labels. Although the order of the labels in this list
* shouldn't impact behavior, it is used to determine basic block indices.
*/
private
final
List
<
Label
>
labels
=
new
ArrayList
<
Label
>();
/**
* The label currently receiving instructions. This is null if the most
* recent instruction was a return or goto.
*/
private
Label
currentLabel
;
/** true once we've fixed the positions of the parameter registers */
private
boolean
localsInitialized
;
private
final
Local
<?>
thisLocal
;
/**
* The parameters on this method. If this is non-static, the first parameter
* is 'thisLocal' and we have to offset the user's indices by one.
*/
private
final
List
<
Local
<?>>
parameters
=
new
ArrayList
<
Local
<?>>();
private
final
List
<
Local
<?>>
locals
=
new
ArrayList
<
Local
<?>>();
private
SourcePosition
sourcePosition
=
SourcePosition
.
NO_INFO
;
private
final
List
<
TypeId
<?>>
catchTypes
=
new
ArrayList
<
TypeId
<?>>();
private
final
List
<
Label
>
catchLabels
=
new
ArrayList
<
Label
>();
private
StdTypeList
catches
=
StdTypeList
.
EMPTY
;
Code
(
DexMaker
.
MethodDeclaration
methodDeclaration
)
{
this
.
method
=
methodDeclaration
.
method
;
if
(
methodDeclaration
.
isStatic
())
{
thisLocal
=
null
;
}
else
{
thisLocal
=
Local
.
get
(
this
,
method
.
declaringType
);
parameters
.
add
(
thisLocal
);
}
for
(
TypeId
<?>
parameter
:
method
.
parameters
.
types
)
{
parameters
.
add
(
Local
.
get
(
this
,
parameter
));
}
this
.
currentLabel
=
new
Label
();
adopt
(
this
.
currentLabel
);
this
.
currentLabel
.
marked
=
true
;
}
/**
* Allocates a new local variable of type {@code type}. It is an error to
* allocate a local after instructions have been emitted.
*/
public
<
T
>
Local
<
T
>
newLocal
(
TypeId
<
T
>
type
)
{
if
(
localsInitialized
)
{
throw
new
IllegalStateException
(
"Cannot allocate locals after adding instructions"
);
}
Local
<
T
>
result
=
Local
.
get
(
this
,
type
);
locals
.
add
(
result
);
return
result
;
}
/**
* Returns the local for the parameter at index {@code index} and of type
* {@code type}.
*/
public
<
T
>
Local
<
T
>
getParameter
(
int
index
,
TypeId
<
T
>
type
)
{
if
(
thisLocal
!=
null
)
{
index
++;
// adjust for the hidden 'this' parameter
}
return
coerce
(
parameters
.
get
(
index
),
type
);
}
/**
* Returns the local for {@code this} of type {@code type}. It is an error
* to call {@code getThis()} if this is a static method.
*/
public
<
T
>
Local
<
T
>
getThis
(
TypeId
<
T
>
type
)
{
if
(
thisLocal
==
null
)
{
throw
new
IllegalStateException
(
"static methods cannot access 'this'"
);
}
return
coerce
(
thisLocal
,
type
);
}
@SuppressWarnings
(
"unchecked"
)
// guarded by an equals check
private
<
T
>
Local
<
T
>
coerce
(
Local
<?>
local
,
TypeId
<
T
>
expectedType
)
{
if
(!
local
.
type
.
equals
(
expectedType
))
{
throw
new
IllegalArgumentException
(
"requested "
+
expectedType
+
" but was "
+
local
.
type
);
}
return
(
Local
<
T
>)
local
;
}
/**
* Assigns registers to locals. From the spec:
* "the N arguments to a method land in the last N registers of the
* method's invocation frame, in order. Wide arguments consume two
* registers. Instance methods are passed a this reference as their
* first argument."
*
* In addition to assigning registers to each of the locals, this creates
* instructions to move parameters into their initial registers. These
* instructions are inserted before the code's first real instruction.
*/
void
initializeLocals
()
{
if
(
localsInitialized
)
{
throw
new
AssertionError
();
}
localsInitialized
=
true
;
int
reg
=
0
;
for
(
Local
<?>
local
:
locals
)
{
reg
+=
local
.
initialize
(
reg
);
}
int
firstParamReg
=
reg
;
List
<
Insn
>
moveParameterInstructions
=
new
ArrayList
<
Insn
>();
for
(
Local
<?>
local
:
parameters
)
{
CstInteger
paramConstant
=
CstInteger
.
make
(
reg
-
firstParamReg
);
reg
+=
local
.
initialize
(
reg
);
moveParameterInstructions
.
add
(
new
PlainCstInsn
(
Rops
.
opMoveParam
(
local
.
type
.
ropType
),
sourcePosition
,
local
.
spec
(),
RegisterSpecList
.
EMPTY
,
paramConstant
));
}
labels
.
get
(
0
).
instructions
.
addAll
(
0
,
moveParameterInstructions
);
}
/**
* Returns the number of registers to hold the parameters. This includes the
* 'this' parameter if it exists.
*/
int
paramSize
()
{
int
result
=
0
;
for
(
Local
<?>
local
:
parameters
)
{
result
+=
local
.
size
();
}
return
result
;
}
// labels
/**
* Assigns {@code target} to this code.
*/
private
void
adopt
(
Label
target
)
{
if
(
target
.
code
==
this
)
{
return
;
// already adopted
}
if
(
target
.
code
!=
null
)
{
throw
new
IllegalArgumentException
(
"Cannot adopt label; it belongs to another Code"
);
}
target
.
code
=
this
;
labels
.
add
(
target
);
}
/**
* Start defining instructions for the named label.
*/
public
void
mark
(
Label
label
)
{
adopt
(
label
);
if
(
label
.
marked
)
{
throw
new
IllegalStateException
(
"already marked"
);
}
label
.
marked
=
true
;
if
(
currentLabel
!=
null
)
{
jump
(
label
);
// blocks must end with a branch, return or throw
}
currentLabel
=
label
;
}
/**
* Transfers flow control to the instructions at {@code target}. It is an
* error to jump to a label not marked on this {@code Code}.
*/
public
void
jump
(
Label
target
)
{
adopt
(
target
);
addInstruction
(
new
PlainInsn
(
Rops
.
GOTO
,
sourcePosition
,
null
,
RegisterSpecList
.
EMPTY
),
target
);
}
/**
* Registers {@code catchClause} as a branch target for all instructions
* in this frame that throw a class assignable to {@code toCatch}. This
* includes methods invoked from this frame. Deregister the clause using
* {@link #removeCatchClause removeCatchClause()}. It is an error to
* register a catch clause without also {@link #mark marking it} in the same
* {@code Code} instance.
*/
public
void
addCatchClause
(
TypeId
<?
extends
Throwable
>
toCatch
,
Label
catchClause
)
{
if
(
catchTypes
.
contains
(
toCatch
))
{
throw
new
IllegalArgumentException
(
"Already caught: "
+
toCatch
);
}
adopt
(
catchClause
);
catchTypes
.
add
(
toCatch
);
catches
=
toTypeList
(
catchTypes
);
catchLabels
.
add
(
catchClause
);
}
/**
* Deregisters the catch clause label for {@code toCatch} and returns it.
*/
public
Label
removeCatchClause
(
TypeId
<?
extends
Throwable
>
toCatch
)
{
int
index
=
catchTypes
.
indexOf
(
toCatch
);
if
(
index
==
-
1
)
{
throw
new
IllegalArgumentException
(
"No catch clause: "
+
toCatch
);
}
catchTypes
.
remove
(
index
);
catches
=
toTypeList
(
catchTypes
);
return
catchLabels
.
remove
(
index
);
}
public
void
moveException
(
Local
<?>
result
)
{
addInstruction
(
new
PlainInsn
(
Rops
.
opMoveException
(
Type
.
THROWABLE
),
SourcePosition
.
NO_INFO
,
result
.
spec
(),
RegisterSpecList
.
EMPTY
));
}
/**
* Throws the throwable in {@code toThrow}.
*/
public
void
throwValue
(
Local
<?
extends
Throwable
>
toThrow
)
{
addInstruction
(
new
ThrowingInsn
(
Rops
.
THROW
,
sourcePosition
,
RegisterSpecList
.
make
(
toThrow
.
spec
()),
catches
));
}
private
StdTypeList
toTypeList
(
List
<
TypeId
<?>>
types
)
{
StdTypeList
result
=
new
StdTypeList
(
types
.
size
());
for
(
int
i
=
0
;
i
<
types
.
size
();
i
++)
{
result
.
set
(
i
,
types
.
get
(
i
).
ropType
);
}
return
result
;
}
private
void
addInstruction
(
Insn
insn
)
{
addInstruction
(
insn
,
null
);
}
/**
* @param branch the branches to follow; interpretation depends on the
* instruction's branchingness.
*/
private
void
addInstruction
(
Insn
insn
,
Label
branch
)
{
if
(
currentLabel
==
null
||
!
currentLabel
.
marked
)
{
throw
new
IllegalStateException
(
"no current label"
);
}
currentLabel
.
instructions
.
add
(
insn
);
switch
(
insn
.
getOpcode
().
getBranchingness
())
{
case
BRANCH_NONE:
if
(
branch
!=
null
)
{
throw
new
IllegalArgumentException
(
"unexpected branch: "
+
branch
);
}
return
;
case
BRANCH_RETURN:
if
(
branch
!=
null
)
{
throw
new
IllegalArgumentException
(
"unexpected branch: "
+
branch
);
}
currentLabel
=
null
;
break
;
case
BRANCH_GOTO:
if
(
branch
==
null
)
{
throw
new
IllegalArgumentException
(
"branch == null"
);
}
currentLabel
.
primarySuccessor
=
branch
;
currentLabel
=
null
;
break
;
case
Rop
.
BRANCH_IF
:
if
(
branch
==
null
)
{
throw
new
IllegalArgumentException
(
"branch == null"
);
}
splitCurrentLabel
(
branch
,
Collections
.<
Label
>
emptyList
());
break
;
case
Rop
.
BRANCH_THROW
:
if
(
branch
!=
null
)
{
throw
new
IllegalArgumentException
(
"unexpected branch: "
+
branch
);
}
splitCurrentLabel
(
null
,
new
ArrayList
<
Label
>(
catchLabels
));
break
;
default
:
throw
new
IllegalArgumentException
();
}
}
/**
* Closes the current label and starts a new one.
*
* @param catchLabels an immutable list of catch labels
*/
private
void
splitCurrentLabel
(
Label
alternateSuccessor
,
List
<
Label
>
catchLabels
)
{
Label
newLabel
=
new
Label
();
adopt
(
newLabel
);
currentLabel
.
primarySuccessor
=
newLabel
;
currentLabel
.
alternateSuccessor
=
alternateSuccessor
;
currentLabel
.
catchLabels
=
catchLabels
;
currentLabel
=
newLabel
;
currentLabel
.
marked
=
true
;
}
// instructions: locals
/**
* Copies the constant value {@code value} to {@code target}. The constant
* must be a primitive, String, Class, TypeId, or null.
*/
public
<
T
>
void
loadConstant
(
Local
<
T
>
target
,
T
value
)
{
Rop
rop
=
value
==
null
?
Rops
.
CONST_OBJECT_NOTHROW
:
Rops
.
opConst
(
target
.
type
.
ropType
);
if
(
rop
.
getBranchingness
()
==
BRANCH_NONE
)
{
addInstruction
(
new
PlainCstInsn
(
rop
,
sourcePosition
,
target
.
spec
(),
RegisterSpecList
.
EMPTY
,
Constants
.
getConstant
(
value
)));
}
else
{
addInstruction
(
new
ThrowingCstInsn
(
rop
,
sourcePosition
,
RegisterSpecList
.
EMPTY
,
catches
,
Constants
.
getConstant
(
value
)));
moveResult
(
target
,
true
);
}
}
/**
* Copies the value in {@code source} to {@code target}.
*/
public
<
T
>
void
move
(
Local
<
T
>
target
,
Local
<
T
>
source
)
{
addInstruction
(
new
PlainInsn
(
Rops
.
opMove
(
source
.
type
.
ropType
),
sourcePosition
,
target
.
spec
(),
source
.
spec
()));
}
// instructions: unary and binary
/**
* Executes {@code op} and sets {@code target} to the result.
*/
public
<
T
>
void
op
(
UnaryOp
op
,
Local
<
T
>
target
,
Local
<
T
>
source
)
{
addInstruction
(
new
PlainInsn
(
op
.
rop
(
source
.
type
),
sourcePosition
,
target
.
spec
(),
source
.
spec
()));
}
/**
* Executes {@code op} and sets {@code target} to the result. For most
* binary operations, the types of {@code a} and {@code b} must be the same.
* Shift operations (like {@link BinaryOp#SHIFT_LEFT}) require {@code b} to
* be an {@code int}, even when {@code a} is a {@code long}.
*/
public
<
T1
,
T2
>
void
op
(
BinaryOp
op
,
Local
<
T1
>
target
,
Local
<
T1
>
a
,
Local
<
T2
>
b
)
{
Rop
rop
=
op
.
rop
(
StdTypeList
.
make
(
a
.
type
.
ropType
,
b
.
type
.
ropType
));
RegisterSpecList
sources
=
RegisterSpecList
.
make
(
a
.
spec
(),
b
.
spec
());
if
(
rop
.
getBranchingness
()
==
BRANCH_NONE
)
{
addInstruction
(
new
PlainInsn
(
rop
,
sourcePosition
,
target
.
spec
(),
sources
));
}
else
{
addInstruction
(
new
ThrowingInsn
(
rop
,
sourcePosition
,
sources
,
catches
));
moveResult
(
target
,
true
);
}
}
// instructions: branches
/**
* Compare ints or references. If the comparison is true, execution jumps to
* {@code trueLabel}. If it is false, execution continues to the next
* instruction.
*/
public
<
T
>
void
compare
(
Comparison
comparison
,
Label
trueLabel
,
Local
<
T
>
a
,
Local
<
T
>
b
)
{
adopt
(
trueLabel
);
Rop
rop
=
comparison
.
rop
(
StdTypeList
.
make
(
a
.
type
.
ropType
,
b
.
type
.
ropType
));
addInstruction
(
new
PlainInsn
(
rop
,
sourcePosition
,
null
,
RegisterSpecList
.
make
(
a
.
spec
(),
b
.
spec
())),
trueLabel
);
}
/**
* Check if an int or reference equals to zero. If the comparison is true,
* execution jumps to {@code trueLabel}. If it is false, execution continues to
* the next instruction.
*/
public
<
T
>
void
compareZ
(
Comparison
comparison
,
Label
trueLabel
,
Local
<?>
a
)
{
adopt
(
trueLabel
);
Rop
rop
=
comparison
.
rop
(
StdTypeList
.
make
(
a
.
type
.
ropType
));
addInstruction
(
new
PlainInsn
(
rop
,
sourcePosition
,
null
,
RegisterSpecList
.
make
(
a
.
spec
())),
trueLabel
);
}
/**
* Compare floats or doubles. This stores -1 in {@code target} if {@code
* a < b}, 0 in {@code target} if {@code a == b} and 1 in target if {@code
* a > b}. This stores {@code nanValue} in {@code target} if either value
* is {@code NaN}.
*/
public
<
T
extends
Number
>
void
compareFloatingPoint
(
Local
<
Integer
>
target
,
Local
<
T
>
a
,
Local
<
T
>
b
,
int
nanValue
)
{
Rop
rop
;
if
(
nanValue
==
1
)
{
rop
=
Rops
.
opCmpg
(
a
.
type
.
ropType
);
}
else
if
(
nanValue
==
-
1
)
{
rop
=
Rops
.
opCmpl
(
a
.
type
.
ropType
);
}
else
{
throw
new
IllegalArgumentException
(
"expected 1 or -1 but was "
+
nanValue
);
}
addInstruction
(
new
PlainInsn
(
rop
,
sourcePosition
,
target
.
spec
(),
RegisterSpecList
.
make
(
a
.
spec
(),
b
.
spec
())));
}
/**
* Compare longs. This stores -1 in {@code target} if {@code
* a < b}, 0 in {@code target} if {@code a == b} and 1 in target if {@code
* a > b}.
*/
public
void
compareLongs
(
Local
<
Integer
>
target
,
Local
<
Long
>
a
,
Local
<
Long
>
b
)
{
addInstruction
(
new
PlainInsn
(
Rops
.
CMPL_LONG
,
sourcePosition
,
target
.
spec
(),
RegisterSpecList
.
make
(
a
.
spec
(),
b
.
spec
())));
}
// instructions: fields
/**
* Copies the value in instance field {@code fieldId} of {@code instance} to
* {@code target}.
*/
public
<
D
,
V
>
void
iget
(
FieldId
<
D
,
?
extends
V
>
fieldId
,
Local
<
V
>
target
,
Local
<
D
>
instance
)
{
addInstruction
(
new
ThrowingCstInsn
(
Rops
.
opGetField
(
target
.
type
.
ropType
),
sourcePosition
,
RegisterSpecList
.
make
(
instance
.
spec
()),
catches
,
fieldId
.
constant
));
moveResult
(
target
,
true
);
}
/**
* Copies the value in {@code source} to the instance field {@code fieldId}
* of {@code instance}.
*/
public
<
D
,
V
>
void
iput
(
FieldId
<
D
,
V
>
fieldId
,
Local
<?
extends
D
>
instance
,
Local
<?
extends
V
>
source
)
{
addInstruction
(
new
ThrowingCstInsn
(
Rops
.
opPutField
(
source
.
type
.
ropType
),
sourcePosition
,
RegisterSpecList
.
make
(
source
.
spec
(),
instance
.
spec
()),
catches
,
fieldId
.
constant
));
}
/**
* Copies the value in the static field {@code fieldId} to {@code target}.
*/
public
<
V
>
void
sget
(
FieldId
<?,
?
extends
V
>
fieldId
,
Local
<
V
>
target
)
{
addInstruction
(
new
ThrowingCstInsn
(
Rops
.
opGetStatic
(
target
.
type
.
ropType
),
sourcePosition
,
RegisterSpecList
.
EMPTY
,
catches
,
fieldId
.
constant
));
moveResult
(
target
,
true
);
}
/**
* Copies the value in {@code source} to the static field {@code fieldId}.
*/
public
<
V
>
void
sput
(
FieldId
<?,
V
>
fieldId
,
Local
<?
extends
V
>
source
)
{
addInstruction
(
new
ThrowingCstInsn
(
Rops
.
opPutStatic
(
source
.
type
.
ropType
),
sourcePosition
,
RegisterSpecList
.
make
(
source
.
spec
()),
catches
,
fieldId
.
constant
));
}
// instructions: invoke
/**
* Calls the constructor {@code constructor} using {@code args} and assigns
* the new instance to {@code target}.
*/
public
<
T
>
void
newInstance
(
Local
<
T
>
target
,
MethodId
<
T
,
Void
>
constructor
,
Local
<?>...
args
)
{
if
(
target
==
null
)
{
throw
new
IllegalArgumentException
();
}
addInstruction
(
new
ThrowingCstInsn
(
Rops
.
NEW_INSTANCE
,
sourcePosition
,
RegisterSpecList
.
EMPTY
,
catches
,
constructor
.
declaringType
.
constant
));
moveResult
(
target
,
true
);
invokeDirect
(
constructor
,
null
,
target
,
args
);
}
/**
* Calls the static method {@code method} using {@code args} and assigns the
* result to {@code target}.
*
* @param target the local to receive the method's return value, or {@code
* null} if the return type is {@code void} or if its value not needed.
*/
public
<
R
>
void
invokeStatic
(
MethodId
<?,
R
>
method
,
Local
<?
super
R
>
target
,
Local
<?>...
args
)
{
invoke
(
Rops
.
opInvokeStatic
(
method
.
prototype
(
true
)),
method
,
target
,
null
,
args
);
}
/**
* Calls the non-private instance method {@code method} of {@code instance}
* using {@code args} and assigns the result to {@code target}.
*
* @param method a non-private, non-static, method declared on a class. May
* not be an interface method or a constructor.
* @param target the local to receive the method's return value, or {@code
* null} if the return type is {@code void} or if its value not needed.
*/
public
<
D
,
R
>
void
invokeVirtual
(
MethodId
<
D
,
R
>
method
,
Local
<?
super
R
>
target
,
Local
<?
extends
D
>
instance
,
Local
<?>...
args
)
{
invoke
(
Rops
.
opInvokeVirtual
(
method
.
prototype
(
true
)),
method
,
target
,
instance
,
args
);
}
/**
* Calls {@code method} of {@code instance} using {@code args} and assigns
* the result to {@code target}.
*
* @param method either a private method or the superclass's constructor in
* a constructor's call to {@code super()}.
* @param target the local to receive the method's return value, or {@code
* null} if the return type is {@code void} or if its value not needed.
*/
public
<
D
,
R
>
void
invokeDirect
(
MethodId
<
D
,
R
>
method
,
Local
<?
super
R
>
target
,
Local
<?
extends
D
>
instance
,
Local
<?>...
args
)
{
invoke
(
Rops
.
opInvokeDirect
(
method
.
prototype
(
true
)),
method
,
target
,
instance
,
args
);
}
/**
* Calls the closest superclass's virtual method {@code method} of {@code
* instance} using {@code args} and assigns the result to {@code target}.
*
* @param target the local to receive the method's return value, or {@code
* null} if the return type is {@code void} or if its value not needed.
*/
public
<
D
,
R
>
void
invokeSuper
(
MethodId
<
D
,
R
>
method
,
Local
<?
super
R
>
target
,
Local
<?
extends
D
>
instance
,
Local
<?>...
args
)
{
invoke
(
Rops
.
opInvokeSuper
(
method
.
prototype
(
true
)),
method
,
target
,
instance
,
args
);
}
/**
* Calls the interface method {@code method} of {@code instance} using
* {@code args} and assigns the result to {@code target}.
*
* @param method a method declared on an interface.
* @param target the local to receive the method's return value, or {@code
* null} if the return type is {@code void} or if its value not needed.
*/
public
<
D
,
R
>
void
invokeInterface
(
MethodId
<
D
,
R
>
method
,
Local
<?
super
R
>
target
,
Local
<?
extends
D
>
instance
,
Local
<?>...
args
)
{
invoke
(
Rops
.
opInvokeInterface
(
method
.
prototype
(
true
)),
method
,
target
,
instance
,
args
);
}
private
<
D
,
R
>
void
invoke
(
Rop
rop
,
MethodId
<
D
,
R
>
method
,
Local
<?
super
R
>
target
,
Local
<?
extends
D
>
object
,
Local
<?>...
args
)
{
addInstruction
(
new
ThrowingCstInsn
(
rop
,
sourcePosition
,
concatenate
(
object
,
args
),
catches
,
method
.
constant
));
if
(
target
!=
null
)
{
moveResult
(
target
,
false
);
}
}
// instructions: types
/**
* Tests if the value in {@code source} is assignable to {@code type}. If it
* is, {@code target} is assigned to 1; otherwise {@code target} is assigned
* to 0.
*/
public
void
instanceOfType
(
Local
<?>
target
,
Local
<?>
source
,
TypeId
<?>
type
)
{
addInstruction
(
new
ThrowingCstInsn
(
Rops
.
INSTANCE_OF
,
sourcePosition
,
RegisterSpecList
.
make
(
source
.
spec
()),
catches
,
type
.
constant
));
moveResult
(
target
,
true
);
}
/**
* Performs either a numeric cast or a type cast.
*
* <h3>Numeric Casts</h3>
* Converts a primitive to a different representation. Numeric casts may
* be lossy. For example, converting the double {@code 1.8d} to an integer
* yields {@code 1}, losing the fractional part. Converting the integer
* {@code 0x12345678} to a short yields {@code 0x5678}, losing the high
* bytes. The following numeric casts are supported:
*
* <p><table border="1" summary="Supported Numeric Casts">
* <tr><th>From</th><th>To</th></tr>
* <tr><td>int</td><td>byte, char, short, long, float, double</td></tr>
* <tr><td>long</td><td>int, float, double</td></tr>
* <tr><td>float</td><td>int, long, double</td></tr>
* <tr><td>double</td><td>int, long, float</td></tr>
* </table>
*
* <p>For some primitive conversions it will be necessary to chain multiple
* cast operations. For example, to go from float to short one would first
* cast float to int and then int to short.
*
* <p>Numeric casts never throw {@link ClassCastException}.
*
* <h3>Type Casts</h3>
* Checks that a reference value is assignable to the target type. If it is
* assignable it is copied to the target local. If it is not assignable a
* {@link ClassCastException} is thrown.
*/
public
void
cast
(
Local
<?>
target
,
Local
<?>
source
)
{
if
(
source
.
getType
().
ropType
.
isReference
())
{
addInstruction
(
new
ThrowingCstInsn
(
Rops
.
CHECK_CAST
,
sourcePosition
,
RegisterSpecList
.
make
(
source
.
spec
()),
catches
,
target
.
type
.
constant
));
moveResult
(
target
,
true
);
}
else
{
addInstruction
(
new
PlainInsn
(
getCastRop
(
source
.
type
.
ropType
,
target
.
type
.
ropType
),
sourcePosition
,
target
.
spec
(),
source
.
spec
()));
}
}
private
Rop
getCastRop
(
com
.
android
.
dx
.
rop
.
type
.
Type
sourceType
,
com
.
android
.
dx
.
rop
.
type
.
Type
targetType
)
{
if
(
sourceType
.
getBasicType
()
==
BT_INT
)
{
switch
(
targetType
.
getBasicType
())
{
case
BT_SHORT:
return
Rops
.
TO_SHORT
;
case
BT_CHAR:
return
Rops
.
TO_CHAR
;
case
BT_BYTE:
return
Rops
.
TO_BYTE
;
}
}
return
Rops
.
opConv
(
targetType
,
sourceType
);
}
// instructions: arrays
/**
* Sets {@code target} to the length of the array in {@code array}.
*/
public
<
T
>
void
arrayLength
(
Local
<
Integer
>
target
,
Local
<
T
>
array
)
{
addInstruction
(
new
ThrowingInsn
(
Rops
.
ARRAY_LENGTH
,
sourcePosition
,
RegisterSpecList
.
make
(
array
.
spec
()),
catches
));
moveResult
(
target
,
true
);
}
/**
* Assigns {@code target} to a newly allocated array of length {@code
* length}. The array's type is the same as {@code target}'s type.
*/
public
<
T
>
void
newArray
(
Local
<
T
>
target
,
Local
<
Integer
>
length
)
{
addInstruction
(
new
ThrowingCstInsn
(
Rops
.
opNewArray
(
target
.
type
.
ropType
),
sourcePosition
,
RegisterSpecList
.
make
(
length
.
spec
()),
catches
,
target
.
type
.
constant
));
moveResult
(
target
,
true
);
}
/**
* Assigns the element at {@code index} in {@code array} to {@code target}.
*/
public
void
aget
(
Local
<?>
target
,
Local
<?>
array
,
Local
<
Integer
>
index
)
{
addInstruction
(
new
ThrowingInsn
(
Rops
.
opAget
(
target
.
type
.
ropType
),
sourcePosition
,
RegisterSpecList
.
make
(
array
.
spec
(),
index
.
spec
()),
catches
));
moveResult
(
target
,
true
);
}
/**
* Assigns {@code source} to the element at {@code index} in {@code array}.
*/
public
void
aput
(
Local
<?>
array
,
Local
<
Integer
>
index
,
Local
<?>
source
)
{
addInstruction
(
new
ThrowingInsn
(
Rops
.
opAput
(
source
.
type
.
ropType
),
sourcePosition
,
RegisterSpecList
.
make
(
source
.
spec
(),
array
.
spec
(),
index
.
spec
()),
catches
));
}
// instructions: return
/**
* Returns from a {@code void} method. After a return it is an error to
* define further instructions after a return without first {@link #mark
* marking} an existing unmarked label.
*/
public
void
returnVoid
()
{
if
(!
method
.
returnType
.
equals
(
TypeId
.
VOID
))
{
throw
new
IllegalArgumentException
(
"declared "
+
method
.
returnType
+
" but returned void"
);
}
addInstruction
(
new
PlainInsn
(
Rops
.
RETURN_VOID
,
sourcePosition
,
null
,
RegisterSpecList
.
EMPTY
));
}
/**
* Returns the value in {@code result} to the calling method. After a return
* it is an error to define further instructions after a return without
* first {@link #mark marking} an existing unmarked label.
*/
public
void
returnValue
(
Local
<?>
result
)
{
if
(!
result
.
type
.
equals
(
method
.
returnType
))
{
// TODO: this is probably too strict.
throw
new
IllegalArgumentException
(
"declared "
+
method
.
returnType
+
" but returned "
+
result
.
type
);
}
addInstruction
(
new
PlainInsn
(
Rops
.
opReturn
(
result
.
type
.
ropType
),
sourcePosition
,
null
,
RegisterSpecList
.
make
(
result
.
spec
())));
}
private
void
moveResult
(
Local
<?>
target
,
boolean
afterNonInvokeThrowingInsn
)
{
Rop
rop
=
afterNonInvokeThrowingInsn
?
Rops
.
opMoveResultPseudo
(
target
.
type
.
ropType
)
:
Rops
.
opMoveResult
(
target
.
type
.
ropType
);
addInstruction
(
new
PlainInsn
(
rop
,
sourcePosition
,
target
.
spec
(),
RegisterSpecList
.
EMPTY
));
}
// instructions; synchronized
/**
* Awaits the lock on {@code monitor}, and acquires it.
*/
public
void
monitorEnter
(
Local
<?>
monitor
)
{
addInstruction
(
new
ThrowingInsn
(
Rops
.
MONITOR_ENTER
,
sourcePosition
,
RegisterSpecList
.
make
(
monitor
.
spec
()),
catches
));
}
/**
* Releases the held lock on {@code monitor}.
*/
public
void
monitorExit
(
Local
<?>
monitor
)
{
addInstruction
(
new
ThrowingInsn
(
Rops
.
MONITOR_EXIT
,
sourcePosition
,
RegisterSpecList
.
make
(
monitor
.
spec
()),
catches
));
}
// produce BasicBlocks for dex
BasicBlockList
toBasicBlocks
()
{
if
(!
localsInitialized
)
{
initializeLocals
();
}
cleanUpLabels
();
BasicBlockList
result
=
new
BasicBlockList
(
labels
.
size
());
for
(
int
i
=
0
;
i
<
labels
.
size
();
i
++)
{
result
.
set
(
i
,
labels
.
get
(
i
).
toBasicBlock
());
}
return
result
;
}
/**
* Removes empty labels and assigns IDs to non-empty labels.
*/
private
void
cleanUpLabels
()
{
int
id
=
0
;
for
(
Iterator
<
Label
>
i
=
labels
.
iterator
();
i
.
hasNext
();)
{
Label
label
=
i
.
next
();
if
(
label
.
isEmpty
())
{
i
.
remove
();
}
else
{
label
.
compact
();
label
.
id
=
id
++;
}
}
}
private
static
RegisterSpecList
concatenate
(
Local
<?>
first
,
Local
<?>[]
rest
)
{
int
offset
=
(
first
!=
null
)
?
1
:
0
;
RegisterSpecList
result
=
new
RegisterSpecList
(
offset
+
rest
.
length
);
if
(
first
!=
null
)
{
result
.
set
(
0
,
first
.
spec
());
}
for
(
int
i
=
0
;
i
<
rest
.
length
;
i
++)
{
result
.
set
(
i
+
offset
,
rest
[
i
].
spec
());
}
return
result
;
}
}
xposedcompat/src/main/java/com/android/dx/Comparison.java
0 → 100755
View file @
5c250a7a
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
android
.
dx
;
import
com.android.dx.rop.code.Rop
;
import
com.android.dx.rop.code.Rops
;
import
com.android.dx.rop.type.TypeList
;
/**
* A comparison between two values of the same type.
*/
public
enum
Comparison
{
/** {@code a < b}. Supports int only. */
LT
()
{
@Override
Rop
rop
(
TypeList
types
)
{
return
Rops
.
opIfLt
(
types
);
}
},
/** {@code a <= b}. Supports int only. */
LE
()
{
@Override
Rop
rop
(
TypeList
types
)
{
return
Rops
.
opIfLe
(
types
);
}
},
/** {@code a == b}. Supports int and reference types. */
EQ
()
{
@Override
Rop
rop
(
TypeList
types
)
{
return
Rops
.
opIfEq
(
types
);
}
},
/** {@code a >= b}. Supports int only. */
GE
()
{
@Override
Rop
rop
(
TypeList
types
)
{
return
Rops
.
opIfGe
(
types
);
}
},
/** {@code a > b}. Supports int only. */
GT
()
{
@Override
Rop
rop
(
TypeList
types
)
{
return
Rops
.
opIfGt
(
types
);
}
},
/** {@code a != b}. Supports int and reference types. */
NE
()
{
@Override
Rop
rop
(
TypeList
types
)
{
return
Rops
.
opIfNe
(
types
);
}
};
abstract
Rop
rop
(
TypeList
types
);
}
xposedcompat/src/main/java/com/android/dx/Constants.java
0 → 100755
View file @
5c250a7a
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
android
.
dx
;
import
com.android.dx.rop.cst.CstBoolean
;
import
com.android.dx.rop.cst.CstByte
;
import
com.android.dx.rop.cst.CstChar
;
import
com.android.dx.rop.cst.CstDouble
;
import
com.android.dx.rop.cst.CstFloat
;
import
com.android.dx.rop.cst.CstInteger
;
import
com.android.dx.rop.cst.CstKnownNull
;
import
com.android.dx.rop.cst.CstLong
;
import
com.android.dx.rop.cst.CstShort
;
import
com.android.dx.rop.cst.CstString
;
import
com.android.dx.rop.cst.CstType
;
import
com.android.dx.rop.cst.TypedConstant
;
/**
* Factory for rop constants.
*/
final
class
Constants
{
private
Constants
()
{}
/**
* Returns a rop constant for the specified value.
*
* @param value null, a boxed primitive, String, Class, or TypeId.
*/
static
TypedConstant
getConstant
(
Object
value
)
{
if
(
value
==
null
)
{
return
CstKnownNull
.
THE_ONE
;
}
else
if
(
value
instanceof
Boolean
)
{
return
CstBoolean
.
make
((
Boolean
)
value
);
}
else
if
(
value
instanceof
Byte
)
{
return
CstByte
.
make
((
Byte
)
value
);
}
else
if
(
value
instanceof
Character
)
{
return
CstChar
.
make
((
Character
)
value
);
}
else
if
(
value
instanceof
Double
)
{
return
CstDouble
.
make
(
Double
.
doubleToLongBits
((
Double
)
value
));
}
else
if
(
value
instanceof
Float
)
{
return
CstFloat
.
make
(
Float
.
floatToIntBits
((
Float
)
value
));
}
else
if
(
value
instanceof
Integer
)
{
return
CstInteger
.
make
((
Integer
)
value
);
}
else
if
(
value
instanceof
Long
)
{
return
CstLong
.
make
((
Long
)
value
);
}
else
if
(
value
instanceof
Short
)
{
return
CstShort
.
make
((
Short
)
value
);
}
else
if
(
value
instanceof
String
)
{
return
new
CstString
((
String
)
value
);
}
else
if
(
value
instanceof
Class
)
{
return
new
CstType
(
TypeId
.
get
((
Class
<?>)
value
).
ropType
);
}
else
if
(
value
instanceof
TypeId
)
{
return
new
CstType
(((
TypeId
)
value
).
ropType
);
}
else
{
throw
new
UnsupportedOperationException
(
"Not a constant: "
+
value
);
}
}
}
xposedcompat/src/main/java/com/android/dx/DexMaker.java
0 → 100755
View file @
5c250a7a
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
android
.
dx
;
import
com.android.dex.DexFormat
;
import
com.android.dx.dex.DexOptions
;
import
com.android.dx.dex.code.DalvCode
;
import
com.android.dx.dex.code.PositionList
;
import
com.android.dx.dex.code.RopTranslator
;
import
com.android.dx.dex.file.ClassDefItem
;
import
com.android.dx.dex.file.DexFile
;
import
com.android.dx.dex.file.EncodedField
;
import
com.android.dx.dex.file.EncodedMethod
;
import
com.android.dx.rop.code.AccessFlags
;
import
com.android.dx.rop.code.LocalVariableInfo
;
import
com.android.dx.rop.code.RopMethod
;
import
com.android.dx.rop.cst.CstString
;
import
com.android.dx.rop.cst.CstType
;
import
com.android.dx.rop.type.StdTypeList
;
import
java.io.File
;
import
java.io.FileOutputStream
;
import
java.io.IOException
;
import
java.io.StringReader
;
import
java.lang.reflect.InvocationTargetException
;
import
java.lang.reflect.Modifier
;
import
java.util.Arrays
;
import
java.util.Iterator
;
import
java.util.LinkedHashMap
;
import
java.util.Map
;
import
java.util.Set
;
import
java.util.jar.JarEntry
;
import
java.util.jar.JarOutputStream
;
import
dalvik.system.PathClassLoader
;
import
static
com
.
android
.
dx
.
rop
.
code
.
AccessFlags
.
ACC_CONSTRUCTOR
;
import
static
java
.
lang
.
reflect
.
Modifier
.
PRIVATE
;
import
static
java
.
lang
.
reflect
.
Modifier
.
STATIC
;
/**
* Generates a <strong>D</strong>alvik <strong>EX</strong>ecutable (dex)
* file for execution on Android. Dex files define classes and interfaces,
* including their member methods and fields, executable code, and debugging
* information. They also define annotations, though this API currently has no
* facility to create a dex file that contains annotations.
*
* <p>This library is intended to satisfy two use cases:
* <ul>
* <li><strong>For runtime code generation.</strong> By embedding this library
* in your Android application, you can dynamically generate and load
* executable code. This approach takes advantage of the fact that the
* host environment and target environment are both Android.
* <li><strong>For compile time code generation.</strong> You may use this
* library as a part of a compiler that targets Android. In this scenario
* the generated dex file must be installed on an Android device before it
* can be executed.
* </ul>
*
* <h3>Example: Fibonacci</h3>
* To illustrate how this API is used, we'll use DexMaker to generate a class
* equivalent to the following Java source: <pre> {@code
*
* package com.publicobject.fib;
*
* public class Fibonacci {
* public static int fib(int i) {
* if (i < 2) {
* return i;
* }
* return fib(i - 1) + fib(i - 2);
* }
* }}</pre>
*
* <p>We start by creating a {@link TypeId} to identify the generated {@code
* Fibonacci} class. DexMaker identifies types by their internal names like
* {@code Ljava/lang/Object;} rather than their Java identifiers like {@code
* java.lang.Object}. <pre> {@code
*
* TypeId<?> fibonacci = TypeId.get("Lcom/google/dexmaker/examples/Fibonacci;");
* }</pre>
*
* <p>Next we declare the class. It allows us to specify the type's source file
* for stack traces, its modifiers, its superclass, and the interfaces it
* implements. In this case, {@code Fibonacci} is a public class that extends
* from {@code Object}: <pre> {@code
*
* String fileName = "Fibonacci.generated";
* DexMaker dexMaker = new DexMaker();
* dexMaker.declare(fibonacci, fileName, Modifier.PUBLIC, TypeId.OBJECT);
* }</pre>
* It is illegal to declare members of a class without also declaring the class
* itself.
*
* <p>To make it easier to go from our Java method to dex instructions, we'll
* manually translate it to pseudocode fit for an assembler. We need to replace
* control flow like {@code if()} blocks and {@code for()} loops with labels and
* branches. We'll also avoid performing multiple operations in one statement,
* using local variables to hold intermediate values as necessary:
* <pre> {@code
*
* int constant1 = 1;
* int constant2 = 2;
* if (i < constant2) goto baseCase;
* int a = i - constant1;
* int b = i - constant2;
* int c = fib(a);
* int d = fib(b);
* int result = c + d;
* return result;
* baseCase:
* return i;
* }</pre>
*
* <p>We look up the {@code MethodId} for the method on the declaring type. This
* takes the method's return type (possibly {@link TypeId#VOID}), its name and
* its parameters types. Next we declare the method, specifying its modifiers by
* bitwise ORing constants from {@link Modifier}. The declare
* call returns a {@link Code} object, which we'll use to define the method's
* instructions. <pre> {@code
*
* MethodId<?, Integer> fib = fibonacci.getMethod(TypeId.INT, "fib", TypeId.INT);
* Code code = dexMaker.declare(fib, Modifier.PUBLIC | Modifier.STATIC);
* }</pre>
*
* <p>One limitation of {@code DexMaker}'s API is that it requires all local
* variables to be created before any instructions are emitted. Use {@link
* Code#newLocal newLocal()} to create a new local variable. The method's
* parameters are exposed as locals using {@link Code#getParameter
* getParameter()}. For non-static methods the {@code this} pointer is exposed
* using {@link Code#getThis getThis()}. Here we declare all of the local
* variables that we'll need for our {@code fib()} method: <pre> {@code
*
* Local<Integer> i = code.getParameter(0, TypeId.INT);
* Local<Integer> constant1 = code.newLocal(TypeId.INT);
* Local<Integer> constant2 = code.newLocal(TypeId.INT);
* Local<Integer> a = code.newLocal(TypeId.INT);
* Local<Integer> b = code.newLocal(TypeId.INT);
* Local<Integer> c = code.newLocal(TypeId.INT);
* Local<Integer> d = code.newLocal(TypeId.INT);
* Local<Integer> result = code.newLocal(TypeId.INT);
* }</pre>
*
* <p>Notice that {@link Local} has a type parameter of {@code Integer}. This is
* useful for generating code that works with existing types like {@code String}
* and {@code Integer}, but it can be a hindrance when generating code that
* involves new types. For this reason you may prefer to use raw types only and
* add {@code @SuppressWarnings("unsafe")} on your calling code. This will yield
* the same result but you won't get IDE support if you make a type error.
*
* <p>We're ready to start defining our method's instructions. The {@link Code}
* class catalogs the available instructions and their use. <pre> {@code
*
* code.loadConstant(constant1, 1);
* code.loadConstant(constant2, 2);
* Label baseCase = new Label();
* code.compare(Comparison.LT, baseCase, i, constant2);
* code.op(BinaryOp.SUBTRACT, a, i, constant1);
* code.op(BinaryOp.SUBTRACT, b, i, constant2);
* code.invokeStatic(fib, c, a);
* code.invokeStatic(fib, d, b);
* code.op(BinaryOp.ADD, result, c, d);
* code.returnValue(result);
* code.mark(baseCase);
* code.returnValue(i);
* }</pre>
*
* <p>We're done defining the dex file. We just need to write it to the
* filesystem or load it into the current process. For this example we'll load
* the generated code into the current process. This only works when the current
* process is running on Android. We use {@link #generateAndLoad
* generateAndLoad()} which takes the class loader that will be used as our
* generated code's parent class loader. It also requires a directory where
* temporary files can be written. <pre> {@code
*
* ClassLoader loader = dexMaker.generateAndLoad(
* FibonacciMaker.class.getClassLoader(), getDataDirectory());
* }</pre>
* Finally we'll use reflection to lookup our generated class on its class
* loader and invoke its {@code fib()} method: <pre> {@code
*
* Class<?> fibonacciClass = loader.loadClass("com.google.dexmaker.examples.Fibonacci");
* Method fibMethod = fibonacciClass.getMethod("fib", int.class);
* System.out.println(fibMethod.invoke(null, 8));
* }</pre>
*/
public
final
class
DexMaker
{
private
final
Map
<
TypeId
<?>,
TypeDeclaration
>
types
=
new
LinkedHashMap
<>();
// Only warn about not being able to deal with blacklisted methods once. Often this is no
// problem and warning on every class load is too spammy.
private
static
boolean
didWarnBlacklistedMethods
;
private
static
boolean
didWarnNonBaseDexClassLoader
;
private
ClassLoader
sharedClassLoader
;
private
DexFile
outputDex
;
private
boolean
markAsTrusted
;
/**
* Creates a new {@code DexMaker} instance, which can be used to create a
* single dex file.
*/
public
DexMaker
()
{
}
TypeDeclaration
getTypeDeclaration
(
TypeId
<?>
type
)
{
TypeDeclaration
result
=
types
.
get
(
type
);
if
(
result
==
null
)
{
result
=
new
TypeDeclaration
(
type
);
types
.
put
(
type
,
result
);
}
return
result
;
}
/**
* Declares {@code type}.
*
* @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link
* Modifier#FINAL} and {@link Modifier#ABSTRACT}.
*/
public
void
declare
(
TypeId
<?>
type
,
String
sourceFile
,
int
flags
,
TypeId
<?>
supertype
,
TypeId
<?>...
interfaces
)
{
TypeDeclaration
declaration
=
getTypeDeclaration
(
type
);
int
supportedFlags
=
Modifier
.
PUBLIC
|
Modifier
.
FINAL
|
Modifier
.
ABSTRACT
|
AccessFlags
.
ACC_SYNTHETIC
;
if
((
flags
&
~
supportedFlags
)
!=
0
)
{
throw
new
IllegalArgumentException
(
"Unexpected flag: "
+
Integer
.
toHexString
(
flags
));
}
if
(
declaration
.
declared
)
{
throw
new
IllegalStateException
(
"already declared: "
+
type
);
}
declaration
.
declared
=
true
;
declaration
.
flags
=
flags
;
declaration
.
supertype
=
supertype
;
declaration
.
sourceFile
=
sourceFile
;
declaration
.
interfaces
=
new
TypeList
(
interfaces
);
}
/**
* Declares a method or constructor.
*
* @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link
* Modifier#PRIVATE}, {@link Modifier#PROTECTED}, {@link Modifier#STATIC},
* {@link Modifier#FINAL} and {@link Modifier#SYNCHRONIZED}.
* <p><strong>Warning:</strong> the {@link Modifier#SYNCHRONIZED} flag
* is insufficient to generate a synchronized method. You must also use
* {@link Code#monitorEnter} and {@link Code#monitorExit} to acquire
* a monitor.
*/
public
Code
declare
(
MethodId
<?,
?>
method
,
int
flags
)
{
TypeDeclaration
typeDeclaration
=
getTypeDeclaration
(
method
.
declaringType
);
if
(
typeDeclaration
.
methods
.
containsKey
(
method
))
{
throw
new
IllegalStateException
(
"already declared: "
+
method
);
}
int
supportedFlags
=
Modifier
.
PUBLIC
|
Modifier
.
PRIVATE
|
Modifier
.
PROTECTED
|
Modifier
.
STATIC
|
Modifier
.
FINAL
|
Modifier
.
SYNCHRONIZED
|
AccessFlags
.
ACC_SYNTHETIC
|
AccessFlags
.
ACC_BRIDGE
;
if
((
flags
&
~
supportedFlags
)
!=
0
)
{
throw
new
IllegalArgumentException
(
"Unexpected flag: "
+
Integer
.
toHexString
(
flags
));
}
// replace the SYNCHRONIZED flag with the DECLARED_SYNCHRONIZED flag
if
((
flags
&
Modifier
.
SYNCHRONIZED
)
!=
0
)
{
flags
=
(
flags
&
~
Modifier
.
SYNCHRONIZED
)
|
AccessFlags
.
ACC_DECLARED_SYNCHRONIZED
;
}
if
(
method
.
isConstructor
()
||
method
.
isStaticInitializer
())
{
flags
|=
ACC_CONSTRUCTOR
;
}
MethodDeclaration
methodDeclaration
=
new
MethodDeclaration
(
method
,
flags
);
typeDeclaration
.
methods
.
put
(
method
,
methodDeclaration
);
return
methodDeclaration
.
code
;
}
/**
* Declares a field.
*
* @param flags a bitwise combination of {@link Modifier#PUBLIC}, {@link
* Modifier#PRIVATE}, {@link Modifier#PROTECTED}, {@link Modifier#STATIC},
* {@link Modifier#FINAL}, {@link Modifier#VOLATILE}, and {@link
* Modifier#TRANSIENT}.
* @param staticValue a constant representing the initial value for the
* static field, possibly null. This must be null if this field is
* non-static.
*/
public
void
declare
(
FieldId
<?,
?>
fieldId
,
int
flags
,
Object
staticValue
)
{
TypeDeclaration
typeDeclaration
=
getTypeDeclaration
(
fieldId
.
declaringType
);
if
(
typeDeclaration
.
fields
.
containsKey
(
fieldId
))
{
throw
new
IllegalStateException
(
"already declared: "
+
fieldId
);
}
int
supportedFlags
=
Modifier
.
PUBLIC
|
Modifier
.
PRIVATE
|
Modifier
.
PROTECTED
|
Modifier
.
STATIC
|
Modifier
.
FINAL
|
Modifier
.
VOLATILE
|
Modifier
.
TRANSIENT
|
AccessFlags
.
ACC_SYNTHETIC
;
if
((
flags
&
~
supportedFlags
)
!=
0
)
{
throw
new
IllegalArgumentException
(
"Unexpected flag: "
+
Integer
.
toHexString
(
flags
));
}
if
((
flags
&
Modifier
.
STATIC
)
==
0
&&
staticValue
!=
null
)
{
throw
new
IllegalArgumentException
(
"staticValue is non-null, but field is not static"
);
}
FieldDeclaration
fieldDeclaration
=
new
FieldDeclaration
(
fieldId
,
flags
,
staticValue
);
typeDeclaration
.
fields
.
put
(
fieldId
,
fieldDeclaration
);
}
/**
* Generates a dex file and returns its bytes.
*/
public
byte
[]
generate
()
{
if
(
outputDex
==
null
)
{
DexOptions
options
=
new
DexOptions
();
options
.
minSdkVersion
=
DexFormat
.
API_NO_EXTENDED_OPCODES
;
outputDex
=
new
DexFile
(
options
);
}
for
(
TypeDeclaration
typeDeclaration
:
types
.
values
())
{
outputDex
.
add
(
typeDeclaration
.
toClassDefItem
());
}
try
{
return
outputDex
.
toDex
(
null
,
false
);
}
catch
(
IOException
e
)
{
throw
new
RuntimeException
(
e
);
}
}
// Generate a file name for the jar by taking a checksum of MethodIds and
// parent class types.
private
String
generateFileName
()
{
int
checksum
=
1
;
Set
<
TypeId
<?>>
typesKeySet
=
types
.
keySet
();
Iterator
<
TypeId
<?>>
it
=
typesKeySet
.
iterator
();
int
[]
checksums
=
new
int
[
typesKeySet
.
size
()];
int
i
=
0
;
while
(
it
.
hasNext
())
{
TypeId
<?>
typeId
=
it
.
next
();
TypeDeclaration
decl
=
getTypeDeclaration
(
typeId
);
Set
<
MethodId
>
methodSet
=
decl
.
methods
.
keySet
();
if
(
decl
.
supertype
!=
null
)
{
int
sum
=
31
*
decl
.
supertype
.
hashCode
()
+
decl
.
interfaces
.
hashCode
();
checksums
[
i
++]
=
31
*
sum
+
methodSet
.
hashCode
();
}
}
Arrays
.
sort
(
checksums
);
for
(
int
sum
:
checksums
)
{
checksum
*=
31
;
checksum
+=
sum
;
}
return
"Generated_"
+
checksum
+
".jar"
;
}
/**
* Set shared class loader to use.
*
* <p>If a class wants to call package private methods of another class they need to share a
* class loader. One common case for this requirement is a mock class wanting to mock package
* private methods of the original class.
*
* <p>If the classLoader is not a subclass of {@code dalvik.system.BaseDexClassLoader} this
* option is ignored.
*
* @param classLoader the class loader the new class should be loaded by
*/
public
void
setSharedClassLoader
(
ClassLoader
classLoader
)
{
this
.
sharedClassLoader
=
classLoader
;
}
public
void
markAsTrusted
()
{
this
.
markAsTrusted
=
true
;
}
private
ClassLoader
generateClassLoader
(
File
result
,
File
dexCache
,
ClassLoader
parent
)
{
try
{
boolean
shareClassLoader
=
sharedClassLoader
!=
null
;
ClassLoader
preferredClassLoader
=
null
;
if
(
parent
!=
null
)
{
preferredClassLoader
=
parent
;
}
else
if
(
sharedClassLoader
!=
null
)
{
preferredClassLoader
=
sharedClassLoader
;
}
Class
baseDexClassLoaderClass
=
Class
.
forName
(
"dalvik.system.BaseDexClassLoader"
);
if
(
shareClassLoader
)
{
if
(!
baseDexClassLoaderClass
.
isAssignableFrom
(
preferredClassLoader
.
getClass
()))
{
if
(!
preferredClassLoader
.
getClass
().
getName
().
equals
(
"java.lang.BootClassLoader"
))
{
if
(!
didWarnNonBaseDexClassLoader
)
{
System
.
err
.
println
(
"Cannot share classloader as shared classloader '"
+
preferredClassLoader
+
"' is not a subclass of '"
+
baseDexClassLoaderClass
+
"'"
);
didWarnNonBaseDexClassLoader
=
true
;
}
}
shareClassLoader
=
false
;
}
}
// Try to load the class so that it can call hidden APIs. This is required for spying
// on system classes as real-methods of these classes might call blacklisted APIs
if
(
markAsTrusted
)
{
try
{
if
(
shareClassLoader
)
{
preferredClassLoader
.
getClass
().
getMethod
(
"addDexPath"
,
String
.
class
,
Boolean
.
TYPE
).
invoke
(
preferredClassLoader
,
result
.
getPath
(),
true
);
return
preferredClassLoader
;
}
else
{
return
(
ClassLoader
)
baseDexClassLoaderClass
.
getConstructor
(
String
.
class
,
File
.
class
,
String
.
class
,
ClassLoader
.
class
,
Boolean
.
TYPE
)
.
newInstance
(
result
.
getPath
(),
dexCache
.
getAbsoluteFile
(),
null
,
preferredClassLoader
,
true
);
}
}
catch
(
InvocationTargetException
e
)
{
if
(
e
.
getCause
()
instanceof
SecurityException
)
{
if
(!
didWarnBlacklistedMethods
)
{
System
.
err
.
println
(
"Cannot allow to call blacklisted super methods. "
+
"This might break spying on system classes."
+
e
.
getCause
());
didWarnBlacklistedMethods
=
true
;
}
}
else
{
throw
e
;
}
}
}
if
(
shareClassLoader
)
{
preferredClassLoader
.
getClass
().
getMethod
(
"addDexPath"
,
String
.
class
).
invoke
(
preferredClassLoader
,
result
.
getPath
());
return
preferredClassLoader
;
}
else
{
return
(
ClassLoader
)
Class
.
forName
(
"dalvik.system.DexClassLoader"
)
.
getConstructor
(
String
.
class
,
String
.
class
,
String
.
class
,
ClassLoader
.
class
)
.
newInstance
(
result
.
getPath
(),
dexCache
.
getAbsolutePath
(),
null
,
preferredClassLoader
);
}
}
catch
(
ClassNotFoundException
e
)
{
throw
new
UnsupportedOperationException
(
"load() requires a Dalvik VM"
,
e
);
}
catch
(
InvocationTargetException
e
)
{
throw
new
RuntimeException
(
e
.
getCause
());
}
catch
(
InstantiationException
e
)
{
throw
new
AssertionError
();
}
catch
(
NoSuchMethodException
e
)
{
throw
new
AssertionError
();
}
catch
(
IllegalAccessException
e
)
{
throw
new
AssertionError
();
}
}
public
ClassLoader
loadClassDirect
(
ClassLoader
parent
,
File
dexCache
,
String
dexFileName
)
{
File
result
=
new
File
(
dexCache
,
dexFileName
);
// Check that the file exists. If it does, return a DexClassLoader and skip all
// the dex bytecode generation.
if
(
result
.
exists
())
{
return
generateClassLoader
(
result
,
dexCache
,
parent
);
}
else
{
return
null
;
}
}
public
ClassLoader
generateAndLoad
(
ClassLoader
parent
,
File
dexCache
)
throws
IOException
{
return
generateAndLoad
(
parent
,
dexCache
,
generateFileName
());
}
/**
* Generates a dex file and loads its types into the current process.
*
* <h3>Picking a dex cache directory</h3>
* The {@code dexCache} should be an application-private directory. If
* you pass a world-writable directory like {@code /sdcard} a malicious app
* could inject code into your process. Most applications should use this:
* <pre> {@code
*
* File dexCache = getApplicationContext().getDir("dx", Context.MODE_PRIVATE);
* }</pre>
* If the {@code dexCache} is null, this method will consult the {@code
* dexmaker.dexcache} system property. If that exists, it will be used for
* the dex cache. If it doesn't exist, this method will attempt to guess
* the application's private data directory as a last resort. If that fails,
* this method will fail with an unchecked exception. You can avoid the
* exception by either providing a non-null value or setting the system
* property.
*
* @param parent the parent ClassLoader to be used when loading our
* generated types (if set, overrides
* {@link #setSharedClassLoader(ClassLoader) shared class loader}.
* @param dexCache the destination directory where generated and optimized
* dex files will be written. If null, this class will try to guess the
* application's private data dir.
*/
public
ClassLoader
generateAndLoad
(
ClassLoader
parent
,
File
dexCache
,
String
dexFileName
)
throws
IOException
{
if
(
dexCache
==
null
)
{
String
property
=
System
.
getProperty
(
"dexmaker.dexcache"
);
if
(
property
!=
null
)
{
dexCache
=
new
File
(
property
);
}
else
{
dexCache
=
new
AppDataDirGuesser
().
guess
();
if
(
dexCache
==
null
)
{
throw
new
IllegalArgumentException
(
"dexcache == null (and no default could be"
+
" found; consider setting the 'dexmaker.dexcache' system property)"
);
}
}
}
File
result
=
new
File
(
dexCache
,
dexFileName
);
if
(
result
.
exists
())
{
try
{
deleteOldDex
(
result
);
}
catch
(
Throwable
throwable
)
{}
}
byte
[]
dex
=
generate
();
/*
* This implementation currently dumps the dex to the filesystem. It
* jars the emitted .dex for the benefit of Gingerbread and earlier
* devices, which can't load .dex files directly.
*
* TODO: load the dex from memory where supported.
*/
result
.
createNewFile
();
JarOutputStream
jarOut
=
new
JarOutputStream
(
new
FileOutputStream
(
result
));
JarEntry
entry
=
new
JarEntry
(
DexFormat
.
DEX_IN_JAR_NAME
);
entry
.
setSize
(
dex
.
length
);
jarOut
.
putNextEntry
(
entry
);
jarOut
.
write
(
dex
);
jarOut
.
closeEntry
();
jarOut
.
close
();
return
generateClassLoader
(
result
,
dexCache
,
parent
);
}
public
void
deleteOldDex
(
File
dexFile
)
{
dexFile
.
delete
();
String
dexDir
=
dexFile
.
getParent
();
File
oatDir
=
new
File
(
dexDir
,
"/oat/"
);
File
oatDirArm
=
new
File
(
oatDir
,
"/arm/"
);
File
oatDirArm64
=
new
File
(
oatDir
,
"/arm64/"
);
if
(!
oatDir
.
exists
())
return
;
String
nameStart
=
dexFile
.
getName
().
replaceAll
(
".jar"
,
""
);
doDeleteOatFiles
(
oatDir
,
nameStart
);
doDeleteOatFiles
(
oatDirArm
,
nameStart
);
doDeleteOatFiles
(
oatDirArm64
,
nameStart
);
}
private
void
doDeleteOatFiles
(
File
dir
,
String
nameStart
)
{
if
(!
dir
.
exists
())
return
;
File
[]
oats
=
dir
.
listFiles
();
if
(
oats
==
null
)
return
;
for
(
File
oatFile:
oats
)
{
if
(
oatFile
.
isFile
()
&&
oatFile
.
getName
().
startsWith
(
nameStart
))
oatFile
.
delete
();
}
}
DexFile
getDexFile
()
{
if
(
outputDex
==
null
)
{
DexOptions
options
=
new
DexOptions
();
options
.
minSdkVersion
=
DexFormat
.
API_NO_EXTENDED_OPCODES
;
outputDex
=
new
DexFile
(
options
);
}
return
outputDex
;
}
static
class
TypeDeclaration
{
private
final
TypeId
<?>
type
;
/** declared state */
private
boolean
declared
;
private
int
flags
;
private
TypeId
<?>
supertype
;
private
String
sourceFile
;
private
TypeList
interfaces
;
private
ClassDefItem
classDefItem
;
private
final
Map
<
FieldId
,
FieldDeclaration
>
fields
=
new
LinkedHashMap
<>();
private
final
Map
<
MethodId
,
MethodDeclaration
>
methods
=
new
LinkedHashMap
<>();
TypeDeclaration
(
TypeId
<?>
type
)
{
this
.
type
=
type
;
}
ClassDefItem
toClassDefItem
()
{
if
(!
declared
)
{
throw
new
IllegalStateException
(
"Undeclared type "
+
type
+
" declares members: "
+
fields
.
keySet
()
+
" "
+
methods
.
keySet
());
}
DexOptions
dexOptions
=
new
DexOptions
();
dexOptions
.
minSdkVersion
=
DexFormat
.
API_NO_EXTENDED_OPCODES
;
CstType
thisType
=
type
.
constant
;
if
(
classDefItem
==
null
)
{
classDefItem
=
new
ClassDefItem
(
thisType
,
flags
,
supertype
.
constant
,
interfaces
.
ropTypes
,
new
CstString
(
sourceFile
));
for
(
MethodDeclaration
method
:
methods
.
values
())
{
EncodedMethod
encoded
=
method
.
toEncodedMethod
(
dexOptions
);
if
(
method
.
isDirect
())
{
classDefItem
.
addDirectMethod
(
encoded
);
}
else
{
classDefItem
.
addVirtualMethod
(
encoded
);
}
}
for
(
FieldDeclaration
field
:
fields
.
values
())
{
EncodedField
encoded
=
field
.
toEncodedField
();
if
(
field
.
isStatic
())
{
classDefItem
.
addStaticField
(
encoded
,
Constants
.
getConstant
(
field
.
staticValue
));
}
else
{
classDefItem
.
addInstanceField
(
encoded
);
}
}
}
return
classDefItem
;
}
}
static
class
FieldDeclaration
{
final
FieldId
<?,
?>
fieldId
;
private
final
int
accessFlags
;
private
final
Object
staticValue
;
FieldDeclaration
(
FieldId
<?,
?>
fieldId
,
int
accessFlags
,
Object
staticValue
)
{
if
((
accessFlags
&
STATIC
)
==
0
&&
staticValue
!=
null
)
{
throw
new
IllegalArgumentException
(
"instance fields may not have a value"
);
}
this
.
fieldId
=
fieldId
;
this
.
accessFlags
=
accessFlags
;
this
.
staticValue
=
staticValue
;
}
EncodedField
toEncodedField
()
{
return
new
EncodedField
(
fieldId
.
constant
,
accessFlags
);
}
public
boolean
isStatic
()
{
return
(
accessFlags
&
STATIC
)
!=
0
;
}
}
static
class
MethodDeclaration
{
final
MethodId
<?,
?>
method
;
private
final
int
flags
;
private
final
Code
code
;
public
MethodDeclaration
(
MethodId
<?,
?>
method
,
int
flags
)
{
this
.
method
=
method
;
this
.
flags
=
flags
;
this
.
code
=
new
Code
(
this
);
}
boolean
isStatic
()
{
return
(
flags
&
STATIC
)
!=
0
;
}
boolean
isDirect
()
{
return
(
flags
&
(
STATIC
|
PRIVATE
|
ACC_CONSTRUCTOR
))
!=
0
;
}
EncodedMethod
toEncodedMethod
(
DexOptions
dexOptions
)
{
RopMethod
ropMethod
=
new
RopMethod
(
code
.
toBasicBlocks
(),
0
);
LocalVariableInfo
locals
=
null
;
DalvCode
dalvCode
=
RopTranslator
.
translate
(
ropMethod
,
PositionList
.
NONE
,
locals
,
code
.
paramSize
(),
dexOptions
);
return
new
EncodedMethod
(
method
.
constant
,
flags
,
dalvCode
,
StdTypeList
.
EMPTY
);
}
}
}
xposedcompat/src/main/java/com/android/dx/FieldId.java
0 → 100755
View file @
5c250a7a
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
android
.
dx
;
import
com.android.dx.rop.cst.CstFieldRef
;
import
com.android.dx.rop.cst.CstNat
;
import
com.android.dx.rop.cst.CstString
;
/**
* Identifies a field.
*
* @param <D> the type declaring this field
* @param <V> the type of value this field holds
*/
public
final
class
FieldId
<
D
,
V
>
{
final
TypeId
<
D
>
declaringType
;
final
TypeId
<
V
>
type
;
final
String
name
;
/** cached converted state */
final
CstNat
nat
;
final
CstFieldRef
constant
;
FieldId
(
TypeId
<
D
>
declaringType
,
TypeId
<
V
>
type
,
String
name
)
{
if
(
declaringType
==
null
||
type
==
null
||
name
==
null
)
{
throw
new
NullPointerException
();
}
this
.
declaringType
=
declaringType
;
this
.
type
=
type
;
this
.
name
=
name
;
this
.
nat
=
new
CstNat
(
new
CstString
(
name
),
new
CstString
(
type
.
name
));
this
.
constant
=
new
CstFieldRef
(
declaringType
.
constant
,
nat
);
}
public
TypeId
<
D
>
getDeclaringType
()
{
return
declaringType
;
}
public
TypeId
<
V
>
getType
()
{
return
type
;
}
public
String
getName
()
{
return
name
;
}
@Override
public
boolean
equals
(
Object
o
)
{
return
o
instanceof
FieldId
&&
((
FieldId
<?,
?>)
o
).
declaringType
.
equals
(
declaringType
)
&&
((
FieldId
<?,
?>)
o
).
name
.
equals
(
name
);
}
@Override
public
int
hashCode
()
{
return
declaringType
.
hashCode
()
+
37
*
name
.
hashCode
();
}
@Override
public
String
toString
()
{
return
declaringType
+
"."
+
name
;
}
}
xposedcompat/src/main/java/com/android/dx/Label.java
0 → 100755
View file @
5c250a7a
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
android
.
dx
;
import
com.android.dx.rop.code.BasicBlock
;
import
com.android.dx.rop.code.Insn
;
import
com.android.dx.rop.code.InsnList
;
import
com.android.dx.util.IntList
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
/**
* A branch target in a list of instructions.
*/
public
final
class
Label
{
final
List
<
Insn
>
instructions
=
new
ArrayList
<>();
Code
code
;
boolean
marked
=
false
;
/** an immutable list of labels corresponding to the types in the catch list */
List
<
Label
>
catchLabels
=
Collections
.
emptyList
();
/** contains the next instruction if no branch occurs */
Label
primarySuccessor
;
/** contains the instruction to jump to if the if is true */
Label
alternateSuccessor
;
int
id
=
-
1
;
public
Label
()
{}
boolean
isEmpty
()
{
return
instructions
.
isEmpty
();
}
void
compact
()
{
for
(
int
i
=
0
;
i
<
catchLabels
.
size
();
i
++)
{
while
(
catchLabels
.
get
(
i
).
isEmpty
())
{
catchLabels
.
set
(
i
,
catchLabels
.
get
(
i
).
primarySuccessor
);
}
}
while
(
primarySuccessor
!=
null
&&
primarySuccessor
.
isEmpty
())
{
primarySuccessor
=
primarySuccessor
.
primarySuccessor
;
}
while
(
alternateSuccessor
!=
null
&&
alternateSuccessor
.
isEmpty
())
{
alternateSuccessor
=
alternateSuccessor
.
primarySuccessor
;
}
}
BasicBlock
toBasicBlock
()
{
InsnList
result
=
new
InsnList
(
instructions
.
size
());
for
(
int
i
=
0
;
i
<
instructions
.
size
();
i
++)
{
result
.
set
(
i
,
instructions
.
get
(
i
));
}
result
.
setImmutable
();
int
primarySuccessorIndex
=
-
1
;
IntList
successors
=
new
IntList
();
for
(
Label
catchLabel
:
catchLabels
)
{
successors
.
add
(
catchLabel
.
id
);
}
if
(
primarySuccessor
!=
null
)
{
primarySuccessorIndex
=
primarySuccessor
.
id
;
successors
.
add
(
primarySuccessorIndex
);
}
if
(
alternateSuccessor
!=
null
)
{
successors
.
add
(
alternateSuccessor
.
id
);
}
successors
.
setImmutable
();
return
new
BasicBlock
(
id
,
result
,
successors
,
primarySuccessorIndex
);
}
}
xposedcompat/src/main/java/com/android/dx/Local.java
0 → 100755
View file @
5c250a7a
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
android
.
dx
;
import
com.android.dx.rop.code.RegisterSpec
;
/**
* A temporary variable that holds a single value of a known type.
*/
public
final
class
Local
<
T
>
{
private
final
Code
code
;
final
TypeId
<
T
>
type
;
private
int
reg
=
-
1
;
private
RegisterSpec
spec
;
private
Local
(
Code
code
,
TypeId
<
T
>
type
)
{
this
.
code
=
code
;
this
.
type
=
type
;
}
static
<
T
>
Local
<
T
>
get
(
Code
code
,
TypeId
<
T
>
type
)
{
return
new
Local
<
T
>(
code
,
type
);
}
/**
* Assigns registers to this local.
*
* @return the number of registers required.
*/
int
initialize
(
int
nextAvailableRegister
)
{
this
.
reg
=
nextAvailableRegister
;
this
.
spec
=
RegisterSpec
.
make
(
nextAvailableRegister
,
type
.
ropType
);
return
size
();
}
/**
* Returns the number of registered required to hold this local.
*/
int
size
()
{
return
type
.
ropType
.
getCategory
();
}
RegisterSpec
spec
()
{
if
(
spec
==
null
)
{
code
.
initializeLocals
();
if
(
spec
==
null
)
{
throw
new
AssertionError
();
}
}
return
spec
;
}
public
TypeId
getType
()
{
return
type
;
}
@Override
public
String
toString
()
{
return
"v"
+
reg
+
"("
+
type
+
")"
;
}
}
xposedcompat/src/main/java/com/android/dx/MethodId.java
0 → 100755
View file @
5c250a7a
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
android
.
dx
;
import
com.android.dx.rop.cst.CstMethodRef
;
import
com.android.dx.rop.cst.CstNat
;
import
com.android.dx.rop.cst.CstString
;
import
com.android.dx.rop.type.Prototype
;
import
java.util.List
;
/**
* Identifies a method or constructor.
*
* @param <D> the type declaring this field
* @param <R> the return type of this method
*/
public
final
class
MethodId
<
D
,
R
>
{
final
TypeId
<
D
>
declaringType
;
final
TypeId
<
R
>
returnType
;
final
String
name
;
final
TypeList
parameters
;
/** cached converted state */
final
CstNat
nat
;
final
CstMethodRef
constant
;
MethodId
(
TypeId
<
D
>
declaringType
,
TypeId
<
R
>
returnType
,
String
name
,
TypeList
parameters
)
{
if
(
declaringType
==
null
||
returnType
==
null
||
name
==
null
||
parameters
==
null
)
{
throw
new
NullPointerException
();
}
this
.
declaringType
=
declaringType
;
this
.
returnType
=
returnType
;
this
.
name
=
name
;
this
.
parameters
=
parameters
;
this
.
nat
=
new
CstNat
(
new
CstString
(
name
),
new
CstString
(
descriptor
(
false
)));
this
.
constant
=
new
CstMethodRef
(
declaringType
.
constant
,
nat
);
}
public
TypeId
<
D
>
getDeclaringType
()
{
return
declaringType
;
}
public
TypeId
<
R
>
getReturnType
()
{
return
returnType
;
}
/**
* Returns true if this method is a constructor for its declaring class.
*/
public
boolean
isConstructor
()
{
return
name
.
equals
(
"<init>"
);
}
/**
* Returns true if this method is the static initializer for its declaring class.
*/
public
boolean
isStaticInitializer
()
{
return
name
.
equals
(
"<clinit>"
);
}
/**
* Returns the method's name. This is "<init>" if this is a constructor
* or "<clinit>" if a static initializer
*/
public
String
getName
()
{
return
name
;
}
public
List
<
TypeId
<?>>
getParameters
()
{
return
parameters
.
asList
();
}
/**
* Returns a descriptor like "(Ljava/lang/Class;[I)Ljava/lang/Object;".
*/
String
descriptor
(
boolean
includeThis
)
{
StringBuilder
result
=
new
StringBuilder
();
result
.
append
(
"("
);
if
(
includeThis
)
{
result
.
append
(
declaringType
.
name
);
}
for
(
TypeId
t
:
parameters
.
types
)
{
result
.
append
(
t
.
name
);
}
result
.
append
(
")"
);
result
.
append
(
returnType
.
name
);
return
result
.
toString
();
}
Prototype
prototype
(
boolean
includeThis
)
{
return
Prototype
.
intern
(
descriptor
(
includeThis
));
}
@Override
public
boolean
equals
(
Object
o
)
{
return
o
instanceof
MethodId
&&
((
MethodId
<?,
?>)
o
).
declaringType
.
equals
(
declaringType
)
&&
((
MethodId
<?,
?>)
o
).
name
.
equals
(
name
)
&&
((
MethodId
<?,
?>)
o
).
parameters
.
equals
(
parameters
)
&&
((
MethodId
<?,
?>)
o
).
returnType
.
equals
(
returnType
);
}
@Override
public
int
hashCode
()
{
int
result
=
17
;
result
=
31
*
result
+
declaringType
.
hashCode
();
result
=
31
*
result
+
name
.
hashCode
();
result
=
31
*
result
+
parameters
.
hashCode
();
result
=
31
*
result
+
returnType
.
hashCode
();
return
result
;
}
@Override
public
String
toString
()
{
return
declaringType
+
"."
+
name
+
"("
+
parameters
+
")"
;
}
}
xposedcompat/src/main/java/com/android/dx/TypeId.java
0 → 100755
View file @
5c250a7a
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
android
.
dx
;
import
com.android.dx.rop.cst.CstType
;
import
java.util.HashMap
;
import
java.util.Map
;
/**
* A primitive type, interface or class.
*
* <p><strong>Warning:</strong> Use care when dealing with boxed primitive
* types. Java's lack of support for parameterized primitive types means that
* a primitive type like {@code int} and its boxed type {@code
* java.lang.Integer} have the same type parameter: {@code TypeId<Integer>}.
* These types are different and it will be a runtime error if the boxed type
* {@code java.lang.Integer} is used where the primitive type {@code int} is
* expected.
*/
public
final
class
TypeId
<
T
>
{
/** The {@code boolean} primitive type. */
public
static
final
TypeId
<
Boolean
>
BOOLEAN
=
new
TypeId
<>(
com
.
android
.
dx
.
rop
.
type
.
Type
.
BOOLEAN
);
/** The {@code byte} primitive type. */
public
static
final
TypeId
<
Byte
>
BYTE
=
new
TypeId
<>(
com
.
android
.
dx
.
rop
.
type
.
Type
.
BYTE
);
/** The {@code char} primitive type. */
public
static
final
TypeId
<
Character
>
CHAR
=
new
TypeId
<>(
com
.
android
.
dx
.
rop
.
type
.
Type
.
CHAR
);
/** The {@code double} primitive type. */
public
static
final
TypeId
<
Double
>
DOUBLE
=
new
TypeId
<>(
com
.
android
.
dx
.
rop
.
type
.
Type
.
DOUBLE
);
/** The {@code float} primitive type. */
public
static
final
TypeId
<
Float
>
FLOAT
=
new
TypeId
<>(
com
.
android
.
dx
.
rop
.
type
.
Type
.
FLOAT
);
/** The {@code int} primitive type. */
public
static
final
TypeId
<
Integer
>
INT
=
new
TypeId
<>(
com
.
android
.
dx
.
rop
.
type
.
Type
.
INT
);
/** The {@code long} primitive type. */
public
static
final
TypeId
<
Long
>
LONG
=
new
TypeId
<>(
com
.
android
.
dx
.
rop
.
type
.
Type
.
LONG
);
/** The {@code short} primitive type. */
public
static
final
TypeId
<
Short
>
SHORT
=
new
TypeId
<>(
com
.
android
.
dx
.
rop
.
type
.
Type
.
SHORT
);
/** The {@code void} primitive type. Only used as a return type. */
public
static
final
TypeId
<
Void
>
VOID
=
new
TypeId
<>(
com
.
android
.
dx
.
rop
.
type
.
Type
.
VOID
);
/** The {@code Object} type. */
public
static
final
TypeId
<
Object
>
OBJECT
=
new
TypeId
<>(
com
.
android
.
dx
.
rop
.
type
.
Type
.
OBJECT
);
/** The {@code String} type. */
public
static
final
TypeId
<
String
>
STRING
=
new
TypeId
<>(
com
.
android
.
dx
.
rop
.
type
.
Type
.
STRING
);
private
static
final
Map
<
Class
<?>,
TypeId
<?>>
PRIMITIVE_TO_TYPE
=
new
HashMap
<>();
static
{
PRIMITIVE_TO_TYPE
.
put
(
boolean
.
class
,
BOOLEAN
);
PRIMITIVE_TO_TYPE
.
put
(
byte
.
class
,
BYTE
);
PRIMITIVE_TO_TYPE
.
put
(
char
.
class
,
CHAR
);
PRIMITIVE_TO_TYPE
.
put
(
double
.
class
,
DOUBLE
);
PRIMITIVE_TO_TYPE
.
put
(
float
.
class
,
FLOAT
);
PRIMITIVE_TO_TYPE
.
put
(
int
.
class
,
INT
);
PRIMITIVE_TO_TYPE
.
put
(
long
.
class
,
LONG
);
PRIMITIVE_TO_TYPE
.
put
(
short
.
class
,
SHORT
);
PRIMITIVE_TO_TYPE
.
put
(
void
.
class
,
VOID
);
}
final
String
name
;
/** cached converted values */
final
com
.
android
.
dx
.
rop
.
type
.
Type
ropType
;
final
CstType
constant
;
TypeId
(
com
.
android
.
dx
.
rop
.
type
.
Type
ropType
)
{
this
(
ropType
.
getDescriptor
(),
ropType
);
}
TypeId
(
String
name
,
com
.
android
.
dx
.
rop
.
type
.
Type
ropType
)
{
if
(
name
==
null
||
ropType
==
null
)
{
throw
new
NullPointerException
();
}
this
.
name
=
name
;
this
.
ropType
=
ropType
;
this
.
constant
=
CstType
.
intern
(
ropType
);
}
/**
* @param name a descriptor like "Ljava/lang/Class;".
*/
public
static
<
T
>
TypeId
<
T
>
get
(
String
name
)
{
return
new
TypeId
<>(
name
,
com
.
android
.
dx
.
rop
.
type
.
Type
.
internReturnType
(
name
));
}
public
static
<
T
>
TypeId
<
T
>
get
(
Class
<
T
>
type
)
{
if
(
type
.
isPrimitive
())
{
// guarded by equals
@SuppressWarnings
(
"unchecked"
)
TypeId
<
T
>
result
=
(
TypeId
<
T
>)
PRIMITIVE_TO_TYPE
.
get
(
type
);
return
result
;
}
String
name
=
type
.
getName
().
replace
(
'.'
,
'/'
);
return
get
(
type
.
isArray
()
?
name
:
'L'
+
name
+
';'
);
}
public
<
V
>
FieldId
<
T
,
V
>
getField
(
TypeId
<
V
>
type
,
String
name
)
{
return
new
FieldId
<>(
this
,
type
,
name
);
}
public
MethodId
<
T
,
Void
>
getConstructor
(
TypeId
<?>...
parameters
)
{
return
new
MethodId
<>(
this
,
VOID
,
"<init>"
,
new
TypeList
(
parameters
));
}
public
MethodId
<
T
,
Void
>
getStaticInitializer
()
{
return
new
MethodId
<>(
this
,
VOID
,
"<clinit>"
,
new
TypeList
(
new
TypeId
[
0
]));
}
public
<
R
>
MethodId
<
T
,
R
>
getMethod
(
TypeId
<
R
>
returnType
,
String
name
,
TypeId
<?>...
parameters
)
{
return
new
MethodId
<>(
this
,
returnType
,
name
,
new
TypeList
(
parameters
));
}
public
String
getName
()
{
return
name
;
}
@Override
public
boolean
equals
(
Object
o
)
{
return
o
instanceof
TypeId
&&
((
TypeId
)
o
).
name
.
equals
(
name
);
}
@Override
public
int
hashCode
()
{
return
name
.
hashCode
();
}
@Override
public
String
toString
()
{
return
name
;
}
}
xposedcompat/src/main/java/com/android/dx/TypeList.java
0 → 100755
View file @
5c250a7a
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
android
.
dx
;
import
com.android.dx.rop.type.StdTypeList
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.List
;
/**
* An immutable of types.
*/
final
class
TypeList
{
final
TypeId
<?>[]
types
;
final
StdTypeList
ropTypes
;
TypeList
(
TypeId
<?>[]
types
)
{
this
.
types
=
types
.
clone
();
this
.
ropTypes
=
new
StdTypeList
(
types
.
length
);
for
(
int
i
=
0
;
i
<
types
.
length
;
i
++)
{
ropTypes
.
set
(
i
,
types
[
i
].
ropType
);
}
}
/**
* Returns an immutable list.
*/
public
List
<
TypeId
<?>>
asList
()
{
return
Collections
.
unmodifiableList
(
Arrays
.
asList
(
types
));
}
@Override
public
boolean
equals
(
Object
o
)
{
return
o
instanceof
TypeList
&&
Arrays
.
equals
(((
TypeList
)
o
).
types
,
types
);
}
@Override
public
int
hashCode
()
{
return
Arrays
.
hashCode
(
types
);
}
@Override
public
String
toString
()
{
StringBuilder
result
=
new
StringBuilder
();
for
(
int
i
=
0
;
i
<
types
.
length
;
i
++)
{
if
(
i
>
0
)
{
result
.
append
(
", "
);
}
result
.
append
(
types
[
i
]);
}
return
result
.
toString
();
}
}
xposedcompat/src/main/java/com/android/dx/UnaryOp.java
0 → 100755
View file @
5c250a7a
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
android
.
dx
;
import
com.android.dx.rop.code.Rop
;
import
com.android.dx.rop.code.Rops
;
/**
* An operation on one value.
*/
public
enum
UnaryOp
{
/** {@code ~a}. Supports int and long. */
NOT
()
{
@Override
Rop
rop
(
TypeId
<?>
type
)
{
return
Rops
.
opNot
(
type
.
ropType
);
}
},
/** {@code -a}. Supports int, long, float and double. */
NEGATE
()
{
@Override
Rop
rop
(
TypeId
<?>
type
)
{
return
Rops
.
opNeg
(
type
.
ropType
);
}
};
abstract
Rop
rop
(
TypeId
<?>
type
);
}
xposedcompat/src/main/java/com/android/dx/stock/ProxyBuilder.java
0 → 100755
View file @
5c250a7a
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
android
.
dx
.
stock
;
import
com.android.dx.Code
;
import
com.android.dx.Comparison
;
import
com.android.dx.DexMaker
;
import
com.android.dx.FieldId
;
import
com.android.dx.Label
;
import
com.android.dx.Local
;
import
com.android.dx.MethodId
;
import
com.android.dx.TypeId
;
import
java.io.File
;
import
java.io.IOException
;
import
java.lang.reflect.Constructor
;
import
java.lang.reflect.Field
;
import
java.lang.reflect.InvocationHandler
;
import
java.lang.reflect.InvocationTargetException
;
import
java.lang.reflect.Method
;
import
java.lang.reflect.Modifier
;
import
java.lang.reflect.UndeclaredThrowableException
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.Comparator
;
import
java.util.HashMap
;
import
java.util.HashSet
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Set
;
import
static
java
.
lang
.
reflect
.
Modifier
.
ABSTRACT
;
import
static
java
.
lang
.
reflect
.
Modifier
.
PRIVATE
;
import
static
java
.
lang
.
reflect
.
Modifier
.
PUBLIC
;
import
static
java
.
lang
.
reflect
.
Modifier
.
STATIC
;
/**
* Creates dynamic proxies of concrete classes.
* <p>
* This is similar to the {@code java.lang.reflect.Proxy} class, but works for classes instead of
* interfaces.
* <h3>Example</h3>
* The following example demonstrates the creation of a dynamic proxy for {@code java.util.Random}
* which will always return 4 when asked for integers, and which logs method calls to every method.
* <pre>
* InvocationHandler handler = new InvocationHandler() {
* @Override
* public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
* if (method.getName().equals("nextInt")) {
* // Chosen by fair dice roll, guaranteed to be random.
* return 4;
* }
* Object result = ProxyBuilder.callSuper(proxy, method, args);
* System.out.println("Method: " + method.getName() + " args: "
* + Arrays.toString(args) + " result: " + result);
* return result;
* }
* };
* Random debugRandom = ProxyBuilder.forClass(Random.class)
* .dexCache(getInstrumentation().getTargetContext().getDir("dx", Context.MODE_PRIVATE))
* .handler(handler)
* .build();
* assertEquals(4, debugRandom.nextInt());
* debugRandom.setSeed(0);
* assertTrue(debugRandom.nextBoolean());
* </pre>
* <h3>Usage</h3>
* Call {@link #forClass(Class)} for the Class you wish to proxy. Call
* {@link #handler(InvocationHandler)} passing in an {@link InvocationHandler}, and then call
* {@link #build()}. The returned instance will be a dynamically generated subclass where all method
* calls will be delegated to the invocation handler, except as noted below.
* <p>
* The static method {@link #callSuper(Object, Method, Object...)} allows you to access the original
* super method for a given proxy. This allows the invocation handler to selectively override some
* methods but not others.
* <p>
* By default, the {@link #build()} method will call the no-arg constructor belonging to the class
* being proxied. If you wish to call a different constructor, you must provide arguments for both
* {@link #constructorArgTypes(Class[])} and {@link #constructorArgValues(Object[])}.
* <p>
* This process works only for classes with public and protected level of visibility.
* <p>
* You may proxy abstract classes. You may not proxy final classes.
* <p>
* Only non-private, non-final, non-static methods will be dispatched to the invocation handler.
* Private, static or final methods will always call through to the superclass as normal.
* <p>
* The {@link #finalize()} method on {@code Object} will not be proxied.
* <p>
* You must provide a dex cache directory via the {@link #dexCache(File)} method. You should take
* care not to make this a world-writable directory, so that third parties cannot inject code into
* your application. A suitable parameter for these output directories would be something like
* this:
* <pre>{@code
* getApplicationContext().getDir("dx", Context.MODE_PRIVATE);
* }</pre>
* <p>
* If the base class to be proxied leaks the {@code this} pointer in the constructor (bad practice),
* that is to say calls a non-private non-final method from the constructor, the invocation handler
* will not be invoked. As a simple concrete example, when proxying Random we discover that it
* internally calls setSeed during the constructor. The proxy will not intercept this call during
* proxy construction, but will intercept as normal afterwards. This behaviour may be subject to
* change in future releases.
* <p>
* This class is <b>not thread safe</b>.
*/
public
final
class
ProxyBuilder
<
T
>
{
// Version of ProxyBuilder. It should be updated if the implementation
// of the generated proxy class changes.
public
static
final
int
VERSION
=
1
;
private
static
final
String
FIELD_NAME_HANDLER
=
"$__handler"
;
private
static
final
String
FIELD_NAME_METHODS
=
"$__methodArray"
;
/**
* A cache of all proxy classes ever generated. At the time of writing,
* Android's runtime doesn't support class unloading so there's little
* value in using weak references.
*/
private
static
final
Map
<
ProxiedClass
<?>,
Class
<?>>
generatedProxyClasses
=
Collections
.
synchronizedMap
(
new
HashMap
<
ProxiedClass
<?>,
Class
<?>>());
private
final
Class
<
T
>
baseClass
;
private
ClassLoader
parentClassLoader
=
ProxyBuilder
.
class
.
getClassLoader
();
private
InvocationHandler
handler
;
private
File
dexCache
;
private
Class
<?>[]
constructorArgTypes
=
new
Class
[
0
];
private
Object
[]
constructorArgValues
=
new
Object
[
0
];
private
List
<
Class
<?>>
interfaces
=
new
ArrayList
<>();
private
Method
[]
methods
;
private
boolean
sharedClassLoader
;
private
boolean
markTrusted
;
private
ProxyBuilder
(
Class
<
T
>
clazz
)
{
baseClass
=
clazz
;
}
public
static
<
T
>
ProxyBuilder
<
T
>
forClass
(
Class
<
T
>
clazz
)
{
return
new
ProxyBuilder
<
T
>(
clazz
);
}
/**
* Specifies the parent ClassLoader to use when creating the proxy.
*
* <p>If null, {@code ProxyBuilder.class.getClassLoader()} will be used.
*/
public
ProxyBuilder
<
T
>
parentClassLoader
(
ClassLoader
parent
)
{
parentClassLoader
=
parent
;
return
this
;
}
public
ProxyBuilder
<
T
>
handler
(
InvocationHandler
handler
)
{
this
.
handler
=
handler
;
return
this
;
}
/**
* Sets the directory where executable code is stored. See {@link
* DexMaker#generateAndLoad DexMaker.generateAndLoad()} for guidance on
* choosing a secure location for the dex cache.
*/
public
ProxyBuilder
<
T
>
dexCache
(
File
dexCacheParent
)
{
dexCache
=
new
File
(
dexCacheParent
,
"v"
+
Integer
.
toString
(
VERSION
));
dexCache
.
mkdir
();
return
this
;
}
public
ProxyBuilder
<
T
>
implementing
(
Class
<?>...
interfaces
)
{
List
<
Class
<?>>
list
=
this
.
interfaces
;
for
(
Class
<?>
i
:
interfaces
)
{
if
(!
i
.
isInterface
())
{
throw
new
IllegalArgumentException
(
"Not an interface: "
+
i
.
getName
());
}
if
(!
list
.
contains
(
i
))
{
list
.
add
(
i
);
}
}
return
this
;
}
public
ProxyBuilder
<
T
>
constructorArgValues
(
Object
...
constructorArgValues
)
{
this
.
constructorArgValues
=
constructorArgValues
;
return
this
;
}
public
ProxyBuilder
<
T
>
constructorArgTypes
(
Class
<?>...
constructorArgTypes
)
{
this
.
constructorArgTypes
=
constructorArgTypes
;
return
this
;
}
public
ProxyBuilder
<
T
>
onlyMethods
(
Method
[]
methods
)
{
this
.
methods
=
methods
;
return
this
;
}
public
ProxyBuilder
<
T
>
withSharedClassLoader
()
{
this
.
sharedClassLoader
=
true
;
return
this
;
}
public
ProxyBuilder
<
T
>
markTrusted
()
{
this
.
markTrusted
=
true
;
return
this
;
}
/**
* Create a new instance of the class to proxy.
*
* @throws UnsupportedOperationException if the class we are trying to create a proxy for is
* not accessible.
* @throws IOException if an exception occurred writing to the {@code dexCache} directory.
* @throws UndeclaredThrowableException if the constructor for the base class to proxy throws
* a declared exception during construction.
* @throws IllegalArgumentException if the handler is null, if the constructor argument types
* do not match the constructor argument values, or if no such constructor exists.
*/
public
T
build
()
throws
IOException
{
check
(
handler
!=
null
,
"handler == null"
);
check
(
constructorArgTypes
.
length
==
constructorArgValues
.
length
,
"constructorArgValues.length != constructorArgTypes.length"
);
Class
<?
extends
T
>
proxyClass
=
buildProxyClass
();
Constructor
<?
extends
T
>
constructor
;
try
{
constructor
=
proxyClass
.
getConstructor
(
constructorArgTypes
);
}
catch
(
NoSuchMethodException
e
)
{
throw
new
IllegalArgumentException
(
"No constructor for "
+
baseClass
.
getName
()
+
" with parameter types "
+
Arrays
.
toString
(
constructorArgTypes
));
}
T
result
;
try
{
result
=
constructor
.
newInstance
(
constructorArgValues
);
}
catch
(
InstantiationException
e
)
{
// Should not be thrown, generated class is not abstract.
throw
new
AssertionError
(
e
);
}
catch
(
IllegalAccessException
e
)
{
// Should not be thrown, the generated constructor is accessible.
throw
new
AssertionError
(
e
);
}
catch
(
InvocationTargetException
e
)
{
// Thrown when the base class constructor throws an exception.
throw
launderCause
(
e
);
}
setInvocationHandler
(
result
,
handler
);
return
result
;
}
// TODO: test coverage for this
/**
* Generate a proxy class. Note that new instances of this class will not automatically have an
* an invocation handler, even if {@link #handler(InvocationHandler)} was called. The handler
* must be set on each instance after it is created, using
* {@link #setInvocationHandler(Object, InvocationHandler)}.
*/
public
Class
<?
extends
T
>
buildProxyClass
()
throws
IOException
{
ClassLoader
requestedClassloader
;
if
(
sharedClassLoader
)
{
requestedClassloader
=
baseClass
.
getClassLoader
();
}
else
{
requestedClassloader
=
parentClassLoader
;
}
// try the cache to see if we've generated this one before
// we only populate the map with matching types
ProxiedClass
<
T
>
cacheKey
=
new
ProxiedClass
<>(
baseClass
,
interfaces
,
requestedClassloader
,
sharedClassLoader
);
@SuppressWarnings
(
"unchecked"
)
Class
<?
extends
T
>
proxyClass
=
(
Class
)
generatedProxyClasses
.
get
(
cacheKey
);
if
(
proxyClass
!=
null
)
{
return
proxyClass
;
// cache hit!
}
// the cache missed; generate the class
DexMaker
dexMaker
=
new
DexMaker
();
String
generatedName
=
getMethodNameForProxyOf
(
baseClass
,
interfaces
);
TypeId
<?
extends
T
>
generatedType
=
TypeId
.
get
(
"L"
+
generatedName
+
";"
);
TypeId
<
T
>
superType
=
TypeId
.
get
(
baseClass
);
generateConstructorsAndFields
(
dexMaker
,
generatedType
,
superType
,
baseClass
);
Method
[]
methodsToProxy
;
if
(
methods
==
null
)
{
methodsToProxy
=
getMethodsToProxyRecursive
();
}
else
{
methodsToProxy
=
methods
;
}
// Sort the results array so that they are in a deterministic fashion.
//
// We use the same parameters to sort as used in {@link MethodId#hashCode}. This is needed
// as e.g. making a method "public" instead of "protected" should not change the id's of the
// methods. If the id's would change the classes loaded from the cache would be incorrect.
Arrays
.
sort
(
methodsToProxy
,
new
Comparator
<
Method
>()
{
@Override
public
int
compare
(
Method
method1
,
Method
method2
)
{
String
m1Signature
=
method1
.
getDeclaringClass
()
+
method1
.
getName
()
+
Arrays
.
toString
(
method1
.
getParameterTypes
())
+
method1
.
getReturnType
();
String
m2Signature
=
method2
.
getDeclaringClass
()
+
method2
.
getName
()
+
Arrays
.
toString
(
method2
.
getParameterTypes
())
+
method2
.
getReturnType
();
return
m1Signature
.
compareTo
(
m2Signature
);
}
});
generateCodeForAllMethods
(
dexMaker
,
generatedType
,
methodsToProxy
,
superType
);
dexMaker
.
declare
(
generatedType
,
generatedName
+
".generated"
,
PUBLIC
,
superType
,
getInterfacesAsTypeIds
());
if
(
sharedClassLoader
)
{
dexMaker
.
setSharedClassLoader
(
requestedClassloader
);
}
if
(
markTrusted
)
{
// The proxied class might have blacklisted methods. Blacklisting methods (and fields)
// is a new feature of Android P:
//
// https://android-developers.googleblog.com/2018/02/
// improving-stability-by-reducing-usage.html
//
// The newly generated class might not be allowed to call methods of the proxied class
// if it is not trusted. As it is not clear which classes have blacklisted methods, mark
// all generated classes as trusted.
dexMaker
.
markAsTrusted
();
}
ClassLoader
classLoader
;
if
(
sharedClassLoader
)
{
classLoader
=
dexMaker
.
generateAndLoad
(
null
,
dexCache
);
}
else
{
classLoader
=
dexMaker
.
generateAndLoad
(
parentClassLoader
,
dexCache
);
}
try
{
proxyClass
=
loadClass
(
classLoader
,
generatedName
);
}
catch
(
IllegalAccessError
e
)
{
// Thrown when the base class is not accessible.
throw
new
UnsupportedOperationException
(
"cannot proxy inaccessible class "
+
baseClass
,
e
);
}
catch
(
ClassNotFoundException
e
)
{
// Should not be thrown, we're sure to have generated this class.
throw
new
AssertionError
(
e
);
}
setMethodsStaticField
(
proxyClass
,
methodsToProxy
);
generatedProxyClasses
.
put
(
cacheKey
,
proxyClass
);
return
proxyClass
;
}
// The type cast is safe: the generated type will extend the base class type.
@SuppressWarnings
(
"unchecked"
)
private
Class
<?
extends
T
>
loadClass
(
ClassLoader
classLoader
,
String
generatedName
)
throws
ClassNotFoundException
{
return
(
Class
<?
extends
T
>)
classLoader
.
loadClass
(
generatedName
);
}
private
static
RuntimeException
launderCause
(
InvocationTargetException
e
)
{
Throwable
cause
=
e
.
getCause
();
// Errors should be thrown as they are.
if
(
cause
instanceof
Error
)
{
throw
(
Error
)
cause
;
}
// RuntimeException can be thrown as-is.
if
(
cause
instanceof
RuntimeException
)
{
throw
(
RuntimeException
)
cause
;
}
// Declared exceptions will have to be wrapped.
throw
new
UndeclaredThrowableException
(
cause
);
}
private
static
void
setMethodsStaticField
(
Class
<?>
proxyClass
,
Method
[]
methodsToProxy
)
{
try
{
Field
methodArrayField
=
proxyClass
.
getDeclaredField
(
FIELD_NAME_METHODS
);
methodArrayField
.
setAccessible
(
true
);
methodArrayField
.
set
(
null
,
methodsToProxy
);
}
catch
(
NoSuchFieldException
e
)
{
// Should not be thrown, generated proxy class has been generated with this field.
throw
new
AssertionError
(
e
);
}
catch
(
IllegalAccessException
e
)
{
// Should not be thrown, we just set the field to accessible.
throw
new
AssertionError
(
e
);
}
}
/**
* Returns the proxy's {@link InvocationHandler}.
*
* @throws IllegalArgumentException if the object supplied is not a proxy created by this class.
*/
public
static
InvocationHandler
getInvocationHandler
(
Object
instance
)
{
try
{
Field
field
=
instance
.
getClass
().
getDeclaredField
(
FIELD_NAME_HANDLER
);
field
.
setAccessible
(
true
);
return
(
InvocationHandler
)
field
.
get
(
instance
);
}
catch
(
NoSuchFieldException
e
)
{
throw
new
IllegalArgumentException
(
"Not a valid proxy instance"
,
e
);
}
catch
(
IllegalAccessException
e
)
{
// Should not be thrown, we just set the field to accessible.
throw
new
AssertionError
(
e
);
}
}
/**
* Sets the proxy's {@link InvocationHandler}.
* <p>
* If you create a proxy with {@link #build()}, the proxy will already have a handler set,
* provided that you configured one with {@link #handler(InvocationHandler)}.
* <p>
* If you generate a proxy class with {@link #buildProxyClass()}, instances of the proxy class
* will not automatically have a handler set, and it is necessary to use this method with each
* instance.
*
* @throws IllegalArgumentException if the object supplied is not a proxy created by this class.
*/
public
static
void
setInvocationHandler
(
Object
instance
,
InvocationHandler
handler
)
{
try
{
Field
handlerField
=
instance
.
getClass
().
getDeclaredField
(
FIELD_NAME_HANDLER
);
handlerField
.
setAccessible
(
true
);
handlerField
.
set
(
instance
,
handler
);
}
catch
(
NoSuchFieldException
e
)
{
throw
new
IllegalArgumentException
(
"Not a valid proxy instance"
,
e
);
}
catch
(
IllegalAccessException
e
)
{
// Should not be thrown, we just set the field to accessible.
throw
new
AssertionError
(
e
);
}
}
// TODO: test coverage for isProxyClass
/**
* Returns true if {@code c} is a proxy class created by this builder.
*/
public
static
boolean
isProxyClass
(
Class
<?>
c
)
{
// TODO: use a marker interface instead?
try
{
c
.
getDeclaredField
(
FIELD_NAME_HANDLER
);
return
true
;
}
catch
(
NoSuchFieldException
e
)
{
return
false
;
}
}
/**
* Add
*
* <pre>
* abstractMethodErrorMessage = method + " cannot be called";
* abstractMethodError = new AbstractMethodError(abstractMethodErrorMessage);
* throw abstractMethodError;
* </pre>
*
* to the {@code code}.
*
* @param code The code to add to
* @param method The method that is abstract
* @param abstractMethodErrorMessage The {@link Local} to store the error message
* @param abstractMethodError The {@link Local} to store the error object
*/
private
static
void
throwAbstractMethodError
(
Code
code
,
Method
method
,
Local
<
String
>
abstractMethodErrorMessage
,
Local
<
AbstractMethodError
>
abstractMethodError
)
{
TypeId
<
AbstractMethodError
>
abstractMethodErrorClass
=
TypeId
.
get
(
AbstractMethodError
.
class
);
MethodId
<
AbstractMethodError
,
Void
>
abstractMethodErrorConstructor
=
abstractMethodErrorClass
.
getConstructor
(
TypeId
.
STRING
);
code
.
loadConstant
(
abstractMethodErrorMessage
,
"'"
+
method
+
"' cannot be called"
);
code
.
newInstance
(
abstractMethodError
,
abstractMethodErrorConstructor
,
abstractMethodErrorMessage
);
code
.
throwValue
(
abstractMethodError
);
}
private
static
<
T
,
G
extends
T
>
void
generateCodeForAllMethods
(
DexMaker
dexMaker
,
TypeId
<
G
>
generatedType
,
Method
[]
methodsToProxy
,
TypeId
<
T
>
superclassType
)
{
TypeId
<
InvocationHandler
>
handlerType
=
TypeId
.
get
(
InvocationHandler
.
class
);
TypeId
<
Method
[]>
methodArrayType
=
TypeId
.
get
(
Method
[].
class
);
FieldId
<
G
,
InvocationHandler
>
handlerField
=
generatedType
.
getField
(
handlerType
,
FIELD_NAME_HANDLER
);
FieldId
<
G
,
Method
[]>
allMethods
=
generatedType
.
getField
(
methodArrayType
,
FIELD_NAME_METHODS
);
TypeId
<
Method
>
methodType
=
TypeId
.
get
(
Method
.
class
);
TypeId
<
Object
[]>
objectArrayType
=
TypeId
.
get
(
Object
[].
class
);
MethodId
<
InvocationHandler
,
Object
>
methodInvoke
=
handlerType
.
getMethod
(
TypeId
.
OBJECT
,
"invoke"
,
TypeId
.
OBJECT
,
methodType
,
objectArrayType
);
for
(
int
m
=
0
;
m
<
methodsToProxy
.
length
;
++
m
)
{
/*
* If the 5th method on the superclass Example that can be overridden were to look like
* this:
*
* public int doSomething(Bar param0, int param1) {
* ...
* }
*
* Then the following dex byte code will generate a method on the proxy that looks
* something like this (in idiomatic Java):
*
* // if doSomething is not abstract
* public int doSomething(Bar param0, int param1) {
* if ($__handler == null) {
* return super.doSomething(param0, param1);
* }
* return __handler.invoke(this, __methodArray[4],
* new Object[] { param0, Integer.valueOf(param1) });
* }
*
* // if doSomething is abstract
* public int doSomething(Bar param0, int param1) {
* if ($__handler == null) {
* throw new AbstractMethodError("'doSomething' cannot be called");
* }
* return __handler.invoke(this, __methodArray[4],
* new Object[] { param0, Integer.valueOf(param1) });
* }
*/
Method
method
=
methodsToProxy
[
m
];
String
name
=
method
.
getName
();
Class
<?>[]
argClasses
=
method
.
getParameterTypes
();
TypeId
<?>[]
argTypes
=
new
TypeId
<?>[
argClasses
.
length
];
for
(
int
i
=
0
;
i
<
argTypes
.
length
;
++
i
)
{
argTypes
[
i
]
=
TypeId
.
get
(
argClasses
[
i
]);
}
Class
<?>
returnType
=
method
.
getReturnType
();
TypeId
<?>
resultType
=
TypeId
.
get
(
returnType
);
MethodId
<?,
?>
methodId
=
generatedType
.
getMethod
(
resultType
,
name
,
argTypes
);
TypeId
<
AbstractMethodError
>
abstractMethodErrorClass
=
TypeId
.
get
(
AbstractMethodError
.
class
);
Code
code
=
dexMaker
.
declare
(
methodId
,
PUBLIC
);
Local
<
G
>
localThis
=
code
.
getThis
(
generatedType
);
Local
<
InvocationHandler
>
localHandler
=
code
.
newLocal
(
handlerType
);
Local
<
Object
>
invokeResult
=
code
.
newLocal
(
TypeId
.
OBJECT
);
Local
<
Integer
>
intValue
=
code
.
newLocal
(
TypeId
.
INT
);
Local
<
Object
[]>
args
=
code
.
newLocal
(
objectArrayType
);
Local
<
Integer
>
argsLength
=
code
.
newLocal
(
TypeId
.
INT
);
Local
<
Object
>
temp
=
code
.
newLocal
(
TypeId
.
OBJECT
);
Local
<?>
resultHolder
=
code
.
newLocal
(
resultType
);
Local
<
Method
[]>
methodArray
=
code
.
newLocal
(
methodArrayType
);
Local
<
Method
>
thisMethod
=
code
.
newLocal
(
methodType
);
Local
<
Integer
>
methodIndex
=
code
.
newLocal
(
TypeId
.
INT
);
Class
<?>
aBoxedClass
=
PRIMITIVE_TO_BOXED
.
get
(
returnType
);
Local
<?>
aBoxedResult
=
null
;
if
(
aBoxedClass
!=
null
)
{
aBoxedResult
=
code
.
newLocal
(
TypeId
.
get
(
aBoxedClass
));
}
Local
<
InvocationHandler
>
nullHandler
=
code
.
newLocal
(
handlerType
);
Local
<?>[]
superArgs2
=
null
;
Local
<?>
superResult2
=
null
;
MethodId
<
T
,
?>
superMethod
=
null
;
Local
<
String
>
abstractMethodErrorMessage
=
null
;
Local
<
AbstractMethodError
>
abstractMethodError
=
null
;
if
((
method
.
getModifiers
()
&
ABSTRACT
)
==
0
)
{
superArgs2
=
new
Local
<?>[
argClasses
.
length
];
superResult2
=
code
.
newLocal
(
resultType
);
superMethod
=
superclassType
.
getMethod
(
resultType
,
name
,
argTypes
);
}
else
{
abstractMethodErrorMessage
=
code
.
newLocal
(
TypeId
.
STRING
);
abstractMethodError
=
code
.
newLocal
(
abstractMethodErrorClass
);
}
code
.
loadConstant
(
methodIndex
,
m
);
code
.
sget
(
allMethods
,
methodArray
);
code
.
aget
(
thisMethod
,
methodArray
,
methodIndex
);
code
.
loadConstant
(
argsLength
,
argTypes
.
length
);
code
.
newArray
(
args
,
argsLength
);
code
.
iget
(
handlerField
,
localHandler
,
localThis
);
// if (proxy == null)
code
.
loadConstant
(
nullHandler
,
null
);
Label
handlerNullCase
=
new
Label
();
code
.
compare
(
Comparison
.
EQ
,
handlerNullCase
,
nullHandler
,
localHandler
);
// This code is what we execute when we have a valid proxy: delegate to invocation
// handler.
for
(
int
p
=
0
;
p
<
argTypes
.
length
;
++
p
)
{
code
.
loadConstant
(
intValue
,
p
);
Local
<?>
parameter
=
code
.
getParameter
(
p
,
argTypes
[
p
]);
Local
<?>
unboxedIfNecessary
=
boxIfRequired
(
code
,
parameter
,
temp
);
code
.
aput
(
args
,
intValue
,
unboxedIfNecessary
);
}
code
.
invokeInterface
(
methodInvoke
,
invokeResult
,
localHandler
,
localThis
,
thisMethod
,
args
);
generateCodeForReturnStatement
(
code
,
returnType
,
invokeResult
,
resultHolder
,
aBoxedResult
);
// This code is executed if proxy is null: call the original super method.
// This is required to handle the case of construction of an object which leaks the
// "this" pointer.
code
.
mark
(
handlerNullCase
);
if
((
method
.
getModifiers
()
&
ABSTRACT
)
==
0
)
{
for
(
int
i
=
0
;
i
<
superArgs2
.
length
;
++
i
)
{
superArgs2
[
i
]
=
code
.
getParameter
(
i
,
argTypes
[
i
]);
}
if
(
void
.
class
.
equals
(
returnType
))
{
code
.
invokeSuper
(
superMethod
,
null
,
localThis
,
superArgs2
);
code
.
returnVoid
();
}
else
{
invokeSuper
(
superMethod
,
code
,
localThis
,
superArgs2
,
superResult2
);
code
.
returnValue
(
superResult2
);
}
}
else
{
throwAbstractMethodError
(
code
,
method
,
abstractMethodErrorMessage
,
abstractMethodError
);
}
/*
* And to allow calling the original super method, the following is also generated:
*
* public String super$doSomething$java_lang_String(Bar param0, int param1) {
* int result = super.doSomething(param0, param1);
* return result;
* }
*/
MethodId
<
G
,
?>
callsSuperMethod
=
generatedType
.
getMethod
(
resultType
,
superMethodName
(
method
),
argTypes
);
Code
superCode
=
dexMaker
.
declare
(
callsSuperMethod
,
PUBLIC
);
if
((
method
.
getModifiers
()
&
ABSTRACT
)
==
0
)
{
Local
<
G
>
superThis
=
superCode
.
getThis
(
generatedType
);
Local
<?>[]
superArgs
=
new
Local
<?>[
argClasses
.
length
];
for
(
int
i
=
0
;
i
<
superArgs
.
length
;
++
i
)
{
superArgs
[
i
]
=
superCode
.
getParameter
(
i
,
argTypes
[
i
]);
}
if
(
void
.
class
.
equals
(
returnType
))
{
superCode
.
invokeSuper
(
superMethod
,
null
,
superThis
,
superArgs
);
superCode
.
returnVoid
();
}
else
{
Local
<?>
superResult
=
superCode
.
newLocal
(
resultType
);
invokeSuper
(
superMethod
,
superCode
,
superThis
,
superArgs
,
superResult
);
superCode
.
returnValue
(
superResult
);
}
}
else
{
Local
<
String
>
superAbstractMethodErrorMessage
=
superCode
.
newLocal
(
TypeId
.
STRING
);
Local
<
AbstractMethodError
>
superAbstractMethodError
=
superCode
.
newLocal
(
abstractMethodErrorClass
);
throwAbstractMethodError
(
superCode
,
method
,
superAbstractMethodErrorMessage
,
superAbstractMethodError
);
}
}
}
@SuppressWarnings
({
"unchecked"
,
"rawtypes"
})
private
static
void
invokeSuper
(
MethodId
superMethod
,
Code
superCode
,
Local
superThis
,
Local
[]
superArgs
,
Local
superResult
)
{
superCode
.
invokeSuper
(
superMethod
,
superResult
,
superThis
,
superArgs
);
}
private
static
Local
<?>
boxIfRequired
(
Code
code
,
Local
<?>
parameter
,
Local
<
Object
>
temp
)
{
MethodId
<?,
?>
unboxMethod
=
PRIMITIVE_TYPE_TO_UNBOX_METHOD
.
get
(
parameter
.
getType
());
if
(
unboxMethod
==
null
)
{
return
parameter
;
}
code
.
invokeStatic
(
unboxMethod
,
temp
,
parameter
);
return
temp
;
}
public
static
Object
callSuper
(
Object
proxy
,
Method
method
,
Object
...
args
)
throws
Throwable
{
try
{
return
proxy
.
getClass
()
.
getMethod
(
superMethodName
(
method
),
method
.
getParameterTypes
())
.
invoke
(
proxy
,
args
);
}
catch
(
InvocationTargetException
e
)
{
throw
e
.
getCause
();
}
}
/**
* The super method must include the return type, otherwise its ambiguous
* for methods with covariant return types.
*/
private
static
String
superMethodName
(
Method
method
)
{
String
returnType
=
method
.
getReturnType
().
getName
();
return
"super$"
+
method
.
getName
()
+
"$"
+
returnType
.
replace
(
'.'
,
'_'
).
replace
(
'['
,
'_'
).
replace
(
';'
,
'_'
);
}
private
static
void
check
(
boolean
condition
,
String
message
)
{
if
(!
condition
)
{
throw
new
IllegalArgumentException
(
message
);
}
}
private
static
<
T
,
G
extends
T
>
void
generateConstructorsAndFields
(
DexMaker
dexMaker
,
TypeId
<
G
>
generatedType
,
TypeId
<
T
>
superType
,
Class
<
T
>
superClass
)
{
TypeId
<
InvocationHandler
>
handlerType
=
TypeId
.
get
(
InvocationHandler
.
class
);
TypeId
<
Method
[]>
methodArrayType
=
TypeId
.
get
(
Method
[].
class
);
FieldId
<
G
,
InvocationHandler
>
handlerField
=
generatedType
.
getField
(
handlerType
,
FIELD_NAME_HANDLER
);
dexMaker
.
declare
(
handlerField
,
PRIVATE
,
null
);
FieldId
<
G
,
Method
[]>
allMethods
=
generatedType
.
getField
(
methodArrayType
,
FIELD_NAME_METHODS
);
dexMaker
.
declare
(
allMethods
,
PRIVATE
|
STATIC
,
null
);
for
(
Constructor
<
T
>
constructor
:
getConstructorsToOverwrite
(
superClass
))
{
if
(
constructor
.
getModifiers
()
==
Modifier
.
FINAL
)
{
continue
;
}
TypeId
<?>[]
types
=
classArrayToTypeArray
(
constructor
.
getParameterTypes
());
MethodId
<?,
?>
method
=
generatedType
.
getConstructor
(
types
);
Code
constructorCode
=
dexMaker
.
declare
(
method
,
PUBLIC
);
Local
<
G
>
thisRef
=
constructorCode
.
getThis
(
generatedType
);
Local
<?>[]
params
=
new
Local
[
types
.
length
];
for
(
int
i
=
0
;
i
<
params
.
length
;
++
i
)
{
params
[
i
]
=
constructorCode
.
getParameter
(
i
,
types
[
i
]);
}
MethodId
<
T
,
?>
superConstructor
=
superType
.
getConstructor
(
types
);
constructorCode
.
invokeDirect
(
superConstructor
,
null
,
thisRef
,
params
);
constructorCode
.
returnVoid
();
}
}
// The type parameter on Constructor is the class in which the constructor is declared.
// The getDeclaredConstructors() method gets constructors declared only in the given class,
// hence this cast is safe.
@SuppressWarnings
(
"unchecked"
)
private
static
<
T
>
Constructor
<
T
>[]
getConstructorsToOverwrite
(
Class
<
T
>
clazz
)
{
return
(
Constructor
<
T
>[])
clazz
.
getDeclaredConstructors
();
}
private
TypeId
<?>[]
getInterfacesAsTypeIds
()
{
TypeId
<?>[]
result
=
new
TypeId
<?>[
interfaces
.
size
()];
int
i
=
0
;
for
(
Class
<?>
implemented
:
interfaces
)
{
result
[
i
++]
=
TypeId
.
get
(
implemented
);
}
return
result
;
}
/**
* Gets all {@link Method} objects we can proxy in the hierarchy of the
* supplied class.
*/
private
Method
[]
getMethodsToProxyRecursive
()
{
Set
<
MethodSetEntry
>
methodsToProxy
=
new
HashSet
<>();
Set
<
MethodSetEntry
>
seenFinalMethods
=
new
HashSet
<>();
// Traverse the class hierarchy to ensure that all concrete methods (which could be marked
// as final) are visited before any abstract methods from interfaces.
for
(
Class
<?>
c
=
baseClass
;
c
!=
null
;
c
=
c
.
getSuperclass
())
{
getMethodsToProxy
(
methodsToProxy
,
seenFinalMethods
,
c
);
}
// Now traverse the interface hierarchy, starting with the ones implemented by the class,
// followed by any extra interfaces.
for
(
Class
<?>
c
=
baseClass
;
c
!=
null
;
c
=
c
.
getSuperclass
())
{
for
(
Class
<?>
i
:
c
.
getInterfaces
())
{
getMethodsToProxy
(
methodsToProxy
,
seenFinalMethods
,
i
);
}
}
for
(
Class
<?>
c
:
interfaces
)
{
getMethodsToProxy
(
methodsToProxy
,
seenFinalMethods
,
c
);
}
Method
[]
results
=
new
Method
[
methodsToProxy
.
size
()];
int
i
=
0
;
for
(
MethodSetEntry
entry
:
methodsToProxy
)
{
results
[
i
++]
=
entry
.
originalMethod
;
}
return
results
;
}
private
void
getMethodsToProxy
(
Set
<
MethodSetEntry
>
sink
,
Set
<
MethodSetEntry
>
seenFinalMethods
,
Class
<?>
c
)
{
for
(
Method
method
:
c
.
getDeclaredMethods
())
{
if
((
method
.
getModifiers
()
&
Modifier
.
FINAL
)
!=
0
)
{
// Skip final methods, we can't override them. We
// also need to remember them, in case the same
// method exists in a parent class.
MethodSetEntry
entry
=
new
MethodSetEntry
(
method
);
seenFinalMethods
.
add
(
entry
);
// We may have seen this method already, from an interface
// implemented by a child class. We need to remove it here.
sink
.
remove
(
entry
);
continue
;
}
if
((
method
.
getModifiers
()
&
STATIC
)
!=
0
)
{
// Skip static methods, overriding them has no effect.
continue
;
}
if
(!
Modifier
.
isPublic
(
method
.
getModifiers
())
&&
!
Modifier
.
isProtected
(
method
.
getModifiers
())
&&
(!
sharedClassLoader
||
Modifier
.
isPrivate
(
method
.
getModifiers
())))
{
// Skip private methods, since they are invoked through direct
// invocation (as opposed to virtual). Therefore, it would not
// be possible to intercept any private method defined inside
// the proxy class except through reflection.
// Skip package-private methods as well (for non-shared class
// loaders). The proxy class does
// not actually inherit package-private methods from the parent
// class because it is not a member of the parent's package.
// This is even true if the two classes have the same package
// name, as they use different class loaders.
continue
;
}
if
(
method
.
getName
().
equals
(
"finalize"
)
&&
method
.
getParameterTypes
().
length
==
0
)
{
// Skip finalize method, it's likely important that it execute as normal.
continue
;
}
MethodSetEntry
entry
=
new
MethodSetEntry
(
method
);
if
(
seenFinalMethods
.
contains
(
entry
))
{
// This method is final in a child class.
// We can't override it.
continue
;
}
sink
.
add
(
entry
);
}
// Only visit the interfaces of this class if it is itself an interface. That prevents
// visiting interfaces of a class before its super classes.
if
(
c
.
isInterface
())
{
for
(
Class
<?>
i
:
c
.
getInterfaces
())
{
getMethodsToProxy
(
sink
,
seenFinalMethods
,
i
);
}
}
}
private
static
<
T
>
String
getMethodNameForProxyOf
(
Class
<
T
>
clazz
,
List
<
Class
<?>>
interfaces
)
{
String
interfacesHash
=
Integer
.
toHexString
(
interfaces
.
hashCode
());
return
clazz
.
getName
().
replace
(
"."
,
"/"
)
+
"_"
+
interfacesHash
+
"_Proxy"
;
}
private
static
TypeId
<?>[]
classArrayToTypeArray
(
Class
<?>[]
input
)
{
TypeId
<?>[]
result
=
new
TypeId
[
input
.
length
];
for
(
int
i
=
0
;
i
<
input
.
length
;
++
i
)
{
result
[
i
]
=
TypeId
.
get
(
input
[
i
]);
}
return
result
;
}
/**
* Calculates the correct return statement code for a method.
* <p>
* A void method will not return anything. A method that returns a primitive will need to
* unbox the boxed result. Otherwise we will cast the result.
*/
// This one is tricky to fix, I gave up.
@SuppressWarnings
({
"rawtypes"
,
"unchecked"
})
private
static
void
generateCodeForReturnStatement
(
Code
code
,
Class
methodReturnType
,
Local
localForResultOfInvoke
,
Local
localOfMethodReturnType
,
Local
aBoxedResult
)
{
if
(
PRIMITIVE_TO_UNBOX_METHOD
.
containsKey
(
methodReturnType
))
{
code
.
cast
(
aBoxedResult
,
localForResultOfInvoke
);
MethodId
unboxingMethodFor
=
getUnboxMethodForPrimitive
(
methodReturnType
);
code
.
invokeVirtual
(
unboxingMethodFor
,
localOfMethodReturnType
,
aBoxedResult
);
code
.
returnValue
(
localOfMethodReturnType
);
}
else
if
(
void
.
class
.
equals
(
methodReturnType
))
{
code
.
returnVoid
();
}
else
{
code
.
cast
(
localOfMethodReturnType
,
localForResultOfInvoke
);
code
.
returnValue
(
localOfMethodReturnType
);
}
}
private
static
MethodId
<?,
?>
getUnboxMethodForPrimitive
(
Class
<?>
methodReturnType
)
{
return
PRIMITIVE_TO_UNBOX_METHOD
.
get
(
methodReturnType
);
}
private
static
final
Map
<
Class
<?>,
Class
<?>>
PRIMITIVE_TO_BOXED
;
static
{
PRIMITIVE_TO_BOXED
=
new
HashMap
<>();
PRIMITIVE_TO_BOXED
.
put
(
boolean
.
class
,
Boolean
.
class
);
PRIMITIVE_TO_BOXED
.
put
(
int
.
class
,
Integer
.
class
);
PRIMITIVE_TO_BOXED
.
put
(
byte
.
class
,
Byte
.
class
);
PRIMITIVE_TO_BOXED
.
put
(
long
.
class
,
Long
.
class
);
PRIMITIVE_TO_BOXED
.
put
(
short
.
class
,
Short
.
class
);
PRIMITIVE_TO_BOXED
.
put
(
float
.
class
,
Float
.
class
);
PRIMITIVE_TO_BOXED
.
put
(
double
.
class
,
Double
.
class
);
PRIMITIVE_TO_BOXED
.
put
(
char
.
class
,
Character
.
class
);
}
private
static
final
Map
<
TypeId
<?>,
MethodId
<?,
?>>
PRIMITIVE_TYPE_TO_UNBOX_METHOD
;
static
{
PRIMITIVE_TYPE_TO_UNBOX_METHOD
=
new
HashMap
<>();
for
(
Map
.
Entry
<
Class
<?>,
Class
<?>>
entry
:
PRIMITIVE_TO_BOXED
.
entrySet
())
{
TypeId
<?>
primitiveType
=
TypeId
.
get
(
entry
.
getKey
());
TypeId
<?>
boxedType
=
TypeId
.
get
(
entry
.
getValue
());
MethodId
<?,
?>
valueOfMethod
=
boxedType
.
getMethod
(
boxedType
,
"valueOf"
,
primitiveType
);
PRIMITIVE_TYPE_TO_UNBOX_METHOD
.
put
(
primitiveType
,
valueOfMethod
);
}
}
/**
* Map from primitive type to method used to unbox a boxed version of the primitive.
* <p>
* This is required for methods whose return type is primitive, since the
* {@link InvocationHandler} will return us a boxed result, and we'll need to convert it back to
* primitive value.
*/
private
static
final
Map
<
Class
<?>,
MethodId
<?,
?>>
PRIMITIVE_TO_UNBOX_METHOD
;
static
{
Map
<
Class
<?>,
MethodId
<?,
?>>
map
=
new
HashMap
<>();
map
.
put
(
boolean
.
class
,
TypeId
.
get
(
Boolean
.
class
).
getMethod
(
TypeId
.
BOOLEAN
,
"booleanValue"
));
map
.
put
(
int
.
class
,
TypeId
.
get
(
Integer
.
class
).
getMethod
(
TypeId
.
INT
,
"intValue"
));
map
.
put
(
byte
.
class
,
TypeId
.
get
(
Byte
.
class
).
getMethod
(
TypeId
.
BYTE
,
"byteValue"
));
map
.
put
(
long
.
class
,
TypeId
.
get
(
Long
.
class
).
getMethod
(
TypeId
.
LONG
,
"longValue"
));
map
.
put
(
short
.
class
,
TypeId
.
get
(
Short
.
class
).
getMethod
(
TypeId
.
SHORT
,
"shortValue"
));
map
.
put
(
float
.
class
,
TypeId
.
get
(
Float
.
class
).
getMethod
(
TypeId
.
FLOAT
,
"floatValue"
));
map
.
put
(
double
.
class
,
TypeId
.
get
(
Double
.
class
).
getMethod
(
TypeId
.
DOUBLE
,
"doubleValue"
));
map
.
put
(
char
.
class
,
TypeId
.
get
(
Character
.
class
).
getMethod
(
TypeId
.
CHAR
,
"charValue"
));
PRIMITIVE_TO_UNBOX_METHOD
=
map
;
}
/**
* Wrapper class to let us disambiguate {@link Method} objects.
* <p>
* The purpose of this class is to override the {@link #equals(Object)} and {@link #hashCode()}
* methods so we can use a {@link Set} to remove duplicate methods that are overrides of one
* another. For these purposes, we consider two methods to be equal if they have the same
* name, return type, and parameter types.
*/
public
static
class
MethodSetEntry
{
public
final
String
name
;
public
final
Class
<?>[]
paramTypes
;
public
final
Class
<?>
returnType
;
public
final
Method
originalMethod
;
public
MethodSetEntry
(
Method
method
)
{
originalMethod
=
method
;
name
=
method
.
getName
();
paramTypes
=
method
.
getParameterTypes
();
returnType
=
method
.
getReturnType
();
}
@Override
public
boolean
equals
(
Object
o
)
{
if
(
o
instanceof
MethodSetEntry
)
{
MethodSetEntry
other
=
(
MethodSetEntry
)
o
;
return
name
.
equals
(
other
.
name
)
&&
returnType
.
equals
(
other
.
returnType
)
&&
Arrays
.
equals
(
paramTypes
,
other
.
paramTypes
);
}
return
false
;
}
@Override
public
int
hashCode
()
{
int
result
=
17
;
result
+=
31
*
result
+
name
.
hashCode
();
result
+=
31
*
result
+
returnType
.
hashCode
();
result
+=
31
*
result
+
Arrays
.
hashCode
(
paramTypes
);
return
result
;
}
}
/**
* A class that was already proxied.
*/
private
static
class
ProxiedClass
<
U
>
{
final
Class
<
U
>
clazz
;
final
List
<
Class
<?>>
interfaces
;
/**
* Class loader requested when the proxy class was generated. This might not be the
* class loader of {@code clazz} as not all class loaders can be shared.
*
* @see DexMaker#generateClassLoader(File, File, ClassLoader)
*/
final
ClassLoader
requestedClassloader
;
final
boolean
sharedClassLoader
;
@Override
public
boolean
equals
(
Object
other
)
{
if
(
this
==
other
)
{
return
true
;
}
if
(
other
==
null
||
getClass
()
!=
other
.
getClass
())
{
return
false
;
}
ProxiedClass
<?>
that
=
(
ProxiedClass
<?>)
other
;
return
clazz
==
that
.
clazz
&&
interfaces
.
equals
(
that
.
interfaces
)
&&
requestedClassloader
==
that
.
requestedClassloader
&&
sharedClassLoader
==
that
.
sharedClassLoader
;
}
@Override
public
int
hashCode
()
{
return
clazz
.
hashCode
()
+
interfaces
.
hashCode
()
+
requestedClassloader
.
hashCode
()
+
(
sharedClassLoader
?
1
:
0
);
}
private
ProxiedClass
(
Class
<
U
>
clazz
,
List
<
Class
<?>>
interfaces
,
ClassLoader
requestedClassloader
,
boolean
sharedClassLoader
)
{
this
.
clazz
=
clazz
;
this
.
interfaces
=
new
ArrayList
<>(
interfaces
);
this
.
requestedClassloader
=
requestedClassloader
;
this
.
sharedClassLoader
=
sharedClassLoader
;
}
}
}
xposedcompat/src/main/java/com/swift/sandhook/xposedcompat/XposedCompat.java
View file @
5c250a7a
...
...
@@ -4,7 +4,7 @@ import android.app.Application;
import
android.content.Context
;
import
com.swift.sandhook.xposedcompat.classloaders.ComposeClassLoader
;
import
com.swift.sandhook.xposedcompat.
classloaders.XposedClassLoader
;
import
com.swift.sandhook.xposedcompat.
methodgen.DynamicBridge
;
import
com.swift.sandhook.xposedcompat.utils.ApplicationUtils
;
import
com.swift.sandhook.xposedcompat.utils.ProcessUtils
;
...
...
@@ -73,4 +73,18 @@ public class XposedCompat {
}
}
public
static
boolean
clearCache
()
{
try
{
cacheDir
.
delete
();
cacheDir
.
mkdirs
();
return
true
;
}
catch
(
Throwable
throwable
)
{
return
false
;
}
}
public
static
void
clearOatCache
()
{
DynamicBridge
.
clearOatFile
();
}
}
xposedcompat/src/main/java/com/swift/sandhook/xposedcompat/methodgen/DynamicBridge.java
View file @
5c250a7a
package
com
.
swift
.
sandhook
.
xposedcompat
.
methodgen
;
import
android.os.Trace
;
import
com.swift.sandhook.xposedcompat.XposedCompat
;
import
com.swift.sandhook.xposedcompat.utils.DexLog
;
import
com.swift.sandhook.xposedcompat.utils.FileUtils
;
import
java.io.File
;
import
java.lang.reflect.Constructor
;
...
...
@@ -21,7 +22,6 @@ public final class DynamicBridge {
private
static
final
HookerDexMaker
dexMaker
=
new
HookerDexMaker
();
private
static
final
AtomicBoolean
dexPathInited
=
new
AtomicBoolean
(
false
);
private
static
File
dexDir
;
private
static
File
dexOptDir
;
public
static
void
onForkPost
()
{
dexPathInited
.
set
(
false
);
...
...
@@ -45,28 +45,35 @@ public final class DynamicBridge {
// delete previous compiled dex to prevent potential crashing
// TODO find a way to reuse them in consideration of performance
try
{
// we always choose to use device encrypted storage data on android N and later
// in case some app is installing hooks before phone is unlocked
String
fixedAppDataDir
=
XposedCompat
.
cacheDir
.
getAbsolutePath
();
dexDir
=
new
File
(
fixedAppDataDir
,
"/sandxposed/"
);
dexOptDir
=
new
File
(
dexDir
,
"oat"
);
dexDir
.
mkdirs
();
try
{
FileUtils
.
delete
(
dexOptDir
);
}
catch
(
Throwable
throwable
)
{
}
}
catch
(
Throwable
throwable
)
{
DexLog
.
e
(
"error when init dex path"
,
throwable
);
}
}
Trace
.
beginSection
(
"SandHook-Xposed"
);
long
timeStart
=
System
.
currentTimeMillis
();
dexMaker
.
start
(
hookMethod
,
additionalHookInfo
,
XposedCompat
.
classLoader
,
dexDir
==
null
?
null
:
dexDir
.
getAbsolutePath
());
DexLog
.
d
(
"hook method <"
+
hookMethod
.
toString
()
+
"> use "
+
(
System
.
currentTimeMillis
()
-
timeStart
)
+
" ms."
);
Trace
.
endSection
();
hookedInfo
.
put
(
hookMethod
,
dexMaker
.
getCallBackupMethod
());
}
catch
(
Exception
e
)
{
DexLog
.
e
(
"error occur when generating dex. dexDir="
+
dexDir
,
e
);
}
}
public
static
void
clearOatFile
()
{
String
fixedAppDataDir
=
XposedCompat
.
cacheDir
.
getAbsolutePath
();
File
dexOatDir
=
new
File
(
fixedAppDataDir
,
"/sandxposed/oat/"
);
if
(!
dexOatDir
.
exists
())
return
;
try
{
dexOatDir
.
delete
();
}
catch
(
Throwable
throwable
)
{
}
}
private
static
boolean
checkMember
(
Member
member
)
{
if
(
member
instanceof
Method
)
{
...
...
xposedcompat/src/main/java/com/swift/sandhook/xposedcompat/methodgen/HookerDexMaker.java
View file @
5c250a7a
...
...
@@ -25,6 +25,7 @@ import java.util.concurrent.atomic.AtomicLong;
import
de.robv.android.xposed.XC_MethodHook
;
import
de.robv.android.xposed.XposedBridge
;
import
static
com
.
swift
.
sandhook
.
xposedcompat
.
utils
.
DexMakerUtils
.
MD5
;
import
static
com
.
swift
.
sandhook
.
xposedcompat
.
utils
.
DexMakerUtils
.
autoBoxIfNecessary
;
import
static
com
.
swift
.
sandhook
.
xposedcompat
.
utils
.
DexMakerUtils
.
autoUnboxIfNecessary
;
import
static
com
.
swift
.
sandhook
.
xposedcompat
.
utils
.
DexMakerUtils
.
createResultLocals
;
...
...
@@ -79,8 +80,6 @@ public class HookerDexMaker {
private
static
final
MethodId
<
XposedBridge
,
Void
>
logStrMethodId
=
xposedBridgeTypeId
.
getMethod
(
TypeId
.
VOID
,
"log"
,
TypeId
.
STRING
);
private
static
AtomicLong
sClassNameSuffix
=
new
AtomicLong
(
1
);
private
FieldId
<?,
XposedBridge
.
AdditionalHookInfo
>
mHookInfoFieldId
;
private
FieldId
<?,
Member
>
mMethodFieldId
;
private
MethodId
<?,
?>
mBackupMethodId
;
...
...
@@ -175,15 +174,30 @@ public class HookerDexMaker {
}
else
{
mAppClassLoader
=
appClassLoader
;
}
doMake
();
}
private
void
doMake
()
throws
Exception
{
mDexMaker
=
new
DexMaker
();
// Generate a Hooker class.
String
className
=
CLASS_NAME_PREFIX
+
sClassNameSuffix
.
getAndIncrement
();
String
classDesc
=
CLASS_DESC_PREFIX
+
className
+
";"
;
mHookerTypeId
=
TypeId
.
get
(
classDesc
);
String
className
=
getClassName
(
mMember
);
String
dexName
=
className
+
".jar"
;
HookWrapper
.
HookEntity
hookEntity
=
null
;
//try load cache first
try
{
ClassLoader
loader
=
mDexMaker
.
loadClassDirect
(
mAppClassLoader
,
new
File
(
mDexDirPath
),
dexName
);
if
(
loader
!=
null
)
{
hookEntity
=
loadHookerClass
(
loader
,
className
);
}
}
catch
(
Throwable
throwable
)
{}
//do generate
if
(
hookEntity
==
null
)
{
hookEntity
=
doMake
(
className
,
dexName
);
}
SandHook
.
hook
(
hookEntity
);
}
private
HookWrapper
.
HookEntity
doMake
(
String
className
,
String
dexName
)
throws
Exception
{
mHookerTypeId
=
TypeId
.
get
(
CLASS_DESC_PREFIX
+
className
+
";"
);
mDexMaker
.
declare
(
mHookerTypeId
,
className
+
".generated"
,
Modifier
.
PUBLIC
,
TypeId
.
OBJECT
);
generateFields
();
generateSetupMethod
();
...
...
@@ -195,9 +209,12 @@ public class HookerDexMaker {
if
(
TextUtils
.
isEmpty
(
mDexDirPath
))
{
throw
new
IllegalArgumentException
(
"dexDirPath should not be empty!!!"
);
}
// Create the dex file and load it.
loader
=
mDexMaker
.
generateAndLoad
(
mAppClassLoader
,
new
File
(
mDexDirPath
));
// Create the dex file and load it.
loader
=
mDexMaker
.
generateAndLoad
(
mAppClassLoader
,
new
File
(
mDexDirPath
),
dexName
);
return
loadHookerClass
(
loader
,
className
);
}
private
HookWrapper
.
HookEntity
loadHookerClass
(
ClassLoader
loader
,
String
className
)
throws
Exception
{
mHookClass
=
loader
.
loadClass
(
className
);
// Execute our newly-generated code in-process.
mHookClass
.
getMethod
(
METHOD_NAME_SETUP
,
Member
.
class
,
XposedBridge
.
AdditionalHookInfo
.
class
)
...
...
@@ -205,7 +222,11 @@ public class HookerDexMaker {
mHookMethod
=
mHookClass
.
getMethod
(
METHOD_NAME_HOOK
,
mActualParameterTypes
);
mBackupMethod
=
mHookClass
.
getMethod
(
METHOD_NAME_BACKUP
,
mActualParameterTypes
);
mCallBackupMethod
=
mHookClass
.
getMethod
(
METHOD_NAME_CALL_BACKUP
,
mActualParameterTypes
);
SandHook
.
hook
(
new
HookWrapper
.
HookEntity
(
mMember
,
mHookMethod
,
mBackupMethod
));
return
new
HookWrapper
.
HookEntity
(
mMember
,
mHookMethod
,
mBackupMethod
);
}
private
String
getClassName
(
Member
originMethod
)
{
return
CLASS_NAME_PREFIX
+
"_"
+
originMethod
.
getName
()
+
"_"
+
MD5
(
originMethod
.
toString
());
}
public
Method
getHookMethod
()
{
...
...
xposedcompat/src/main/java/com/swift/sandhook/xposedcompat/utils/DexMakerUtils.java
View file @
5c250a7a
...
...
@@ -12,6 +12,9 @@ import com.android.dx.rop.type.Type;
import
java.lang.reflect.InvocationTargetException
;
import
java.lang.reflect.Method
;
import
java.math.BigInteger
;
import
java.security.MessageDigest
;
import
java.security.NoSuchAlgorithmException
;
import
java.util.HashMap
;
import
java.util.Map
;
...
...
@@ -253,4 +256,15 @@ public class DexMakerUtils {
return
null
;
}
}
public
static
String
MD5
(
String
source
)
{
try
{
MessageDigest
messageDigest
=
MessageDigest
.
getInstance
(
"MD5"
);
messageDigest
.
update
(
source
.
getBytes
());
return
new
BigInteger
(
1
,
messageDigest
.
digest
()).
toString
(
32
);
}
catch
(
NoSuchAlgorithmException
e
)
{
e
.
printStackTrace
();
}
return
source
;
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment