Upload 134 files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .dockerignore +6 -0
- .gitattributes +1 -0
- .gitignore +10 -0
- BasePath.java +15 -0
- Dockerfile +29 -0
- LICENSE +11 -0
- build.gradle +42 -0
- gradle/wrapper/gradle-wrapper.jar +0 -0
- gradle/wrapper/gradle-wrapper.properties +6 -0
- gradlew +244 -0
- gradlew.bat +92 -0
- linuxfile/libc.so +3 -0
- linuxfile/ls +0 -0
- linuxfile/sh +0 -0
- packer/build.gradle +288 -0
- packer/src/main/java/packer/HelperTask.java +78 -0
- packer/src/main/java/packer/tasks/ConvertToRpc.java +42 -0
- packer/src/main/java/packer/tasks/PackServiceLoader.java +99 -0
- packer/src/main/java/packer/tasks/SimpleRemapper.java +97 -0
- packer/src/main/java/packer/tasks/StubTransform.java +63 -0
- settings.gradle +5 -0
- src/main/java/com/kiliokuara/kuimivm/KuimiClass.java +203 -0
- src/main/java/com/kiliokuara/kuimivm/KuimiField.java +81 -0
- src/main/java/com/kiliokuara/kuimivm/KuimiFieldTable.java +289 -0
- src/main/java/com/kiliokuara/kuimivm/KuimiMember.java +18 -0
- src/main/java/com/kiliokuara/kuimivm/KuimiMemberTable.java +40 -0
- src/main/java/com/kiliokuara/kuimivm/KuimiMethod.java +396 -0
- src/main/java/com/kiliokuara/kuimivm/KuimiMethodTable.java +479 -0
- src/main/java/com/kiliokuara/kuimivm/KuimiObject.java +66 -0
- src/main/java/com/kiliokuara/kuimivm/KuimiObjectMemory.java +28 -0
- src/main/java/com/kiliokuara/kuimivm/KuimiVM.java +31 -0
- src/main/java/com/kiliokuara/kuimivm/abstractvm/ClassPool.java +35 -0
- src/main/java/com/kiliokuara/kuimivm/abstractvm/KuimiAbstractVM.java +515 -0
- src/main/java/com/kiliokuara/kuimivm/attributes/AttributeKey.java +19 -0
- src/main/java/com/kiliokuara/kuimivm/attributes/AttributeMap.java +16 -0
- src/main/java/com/kiliokuara/kuimivm/execute/ObjectPool.java +100 -0
- src/main/java/com/kiliokuara/kuimivm/execute/StackTrace.java +146 -0
- src/main/java/com/kiliokuara/kuimivm/objects/KuimiArrays.java +100 -0
- src/main/java/com/kiliokuara/kuimivm/objects/KuimiString.java +31 -0
- src/main/java/com/kiliokuara/kuimivm/runtime/ArrayAccess.java +30 -0
- src/main/java/com/kiliokuara/kuimivm/runtime/ClassInitEnsure.java +22 -0
- src/main/java/com/kiliokuara/kuimivm/runtime/FieldAccessBridge.java +120 -0
- src/main/java/com/kiliokuara/kuimivm/transform/JarTransformer.java +918 -0
- src/main/java/com/kiliokuara/kuimivm/unidbg/ArmVarArg64Visitor.java +45 -0
- src/main/java/com/kiliokuara/kuimivm/unidbg/ArmVarArgVisitor.java +19 -0
- src/main/java/com/kiliokuara/kuimivm/unidbg/JNIMemberTable.java +62 -0
- src/main/java/com/kiliokuara/kuimivm/unidbg/JValueListVisitor.java +56 -0
- src/main/java/com/kiliokuara/kuimivm/unidbg/KuimiJniMethodHandle.java +172 -0
- src/main/java/com/kiliokuara/kuimivm/unidbg/KuimiUnidbgObjectMemory.java +139 -0
- src/main/java/com/kiliokuara/kuimivm/unidbg/KuimiUnidbgVM.java +27 -0
.dockerignore
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
build
|
2 |
+
.gradle
|
3 |
+
.idea
|
4 |
+
Dockerfile
|
5 |
+
README.md
|
6 |
+
.gitignore
|
.gitattributes
CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
linuxfile/libc.so filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
**/build/
|
2 |
+
.idea
|
3 |
+
.gradle
|
4 |
+
|
5 |
+
run/
|
6 |
+
**/debug-sandbox/
|
7 |
+
/testbot
|
8 |
+
/tencent/src/main/java/tencentlibfekit/BasePath.java
|
9 |
+
|
10 |
+
hs_*.log
|
BasePath.java
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package tencentlibfekit;
|
2 |
+
|
3 |
+
public class BasePath {
|
4 |
+
public static final String PROJECT_DIR = "";
|
5 |
+
static long BOT_ACCOUNT = 0L;
|
6 |
+
static String BOT_PASSWORD = "";
|
7 |
+
|
8 |
+
public static String STRUCT_DEF = "";
|
9 |
+
public static String SO = "";
|
10 |
+
public static String VMTS = "";
|
11 |
+
|
12 |
+
public static String APK_PATH = "";
|
13 |
+
|
14 |
+
public static String userDataPath = "";
|
15 |
+
}
|
Dockerfile
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM openjdk:18-bullseye AS builder
|
2 |
+
|
3 |
+
ADD ./ /build
|
4 |
+
ADD ./BasePath.java /build/tencent/src/main/java/tencentlibfekit/
|
5 |
+
WORKDIR /build
|
6 |
+
RUN chmod +x ./gradlew
|
7 |
+
RUN ./gradlew --no-daemon
|
8 |
+
RUN ./gradlew --no-daemon :packer:build
|
9 |
+
|
10 |
+
|
11 |
+
FROM openjdk:18-bullseye
|
12 |
+
RUN mkdir /app
|
13 |
+
WORKDIR /app
|
14 |
+
COPY --from=builder /build/packer/build/proguard/server-remap.jar /app/server.jar
|
15 |
+
|
16 |
+
# 允许使用 docker cp 获取 console 插件
|
17 |
+
COPY --from=builder /build/packer/build/proguard/vivo50-kfc-code45-rpc.jar /app
|
18 |
+
|
19 |
+
ENV SERVER_IDENTITY_KEY=vivo50
|
20 |
+
ENV AUTH_KEY=kfc
|
21 |
+
# 因为兼容性原因改成host和port
|
22 |
+
ENV HOST=0.0.0.0
|
23 |
+
ENV PORT=8888
|
24 |
+
|
25 |
+
EXPOSE 8888
|
26 |
+
|
27 |
+
CMD java -jar /app/server.jar
|
28 |
+
|
29 |
+
|
LICENSE
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
2 |
+
|
3 |
+
Version 2, December 2004
|
4 |
+
|
5 |
+
Copyright (C) 2004 Kilio Kuara
|
6 |
+
|
7 |
+
Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed.
|
8 |
+
|
9 |
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
10 |
+
|
11 |
+
0. You just DO WHAT THE FUCK YOU WANT TO.
|
build.gradle
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
buildscript {
|
2 |
+
repositories {
|
3 |
+
mavenCentral()
|
4 |
+
}
|
5 |
+
dependencies {
|
6 |
+
classpath 'com.guardsquare:proguard-gradle:7.3.2'
|
7 |
+
}
|
8 |
+
}
|
9 |
+
|
10 |
+
plugins {
|
11 |
+
id 'java'
|
12 |
+
id 'java-library'
|
13 |
+
}
|
14 |
+
|
15 |
+
repositories {
|
16 |
+
mavenCentral()
|
17 |
+
}
|
18 |
+
|
19 |
+
|
20 |
+
dependencies {
|
21 |
+
api 'io.github.karlatemp:unsafe-accessor:1.7.0'
|
22 |
+
api 'org.ow2.asm:asm-util:9.5'
|
23 |
+
api 'org.ow2.asm:asm-tree:9.5'
|
24 |
+
api 'org.ow2.asm:asm-commons:9.5'
|
25 |
+
|
26 |
+
api 'org.jetbrains:annotations:23.0.0'
|
27 |
+
|
28 |
+
api 'org.apache.commons:commons-jexl3:3.2.1'
|
29 |
+
|
30 |
+
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2'
|
31 |
+
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
|
32 |
+
|
33 |
+
api('com.github.zhkl0228:unidbg-android:0.9.7') {
|
34 |
+
exclude group: 'com.github.zhkl0228', module: 'unicorn'
|
35 |
+
}
|
36 |
+
api 'com.github.zhkl0228:unidbg-unicorn2:0.9.7'
|
37 |
+
|
38 |
+
}
|
39 |
+
|
40 |
+
test {
|
41 |
+
useJUnitPlatform()
|
42 |
+
}
|
gradle/wrapper/gradle-wrapper.jar
ADDED
Binary file (61.6 kB). View file
|
|
gradle/wrapper/gradle-wrapper.properties
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
distributionBase=GRADLE_USER_HOME
|
2 |
+
distributionPath=wrapper/dists
|
3 |
+
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
4 |
+
networkTimeout=10000
|
5 |
+
zipStoreBase=GRADLE_USER_HOME
|
6 |
+
zipStorePath=wrapper/dists
|
gradlew
ADDED
@@ -0,0 +1,244 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/bin/sh
|
2 |
+
|
3 |
+
#
|
4 |
+
# Copyright © 2015-2021 the original authors.
|
5 |
+
#
|
6 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7 |
+
# you may not use this file except in compliance with the License.
|
8 |
+
# You may obtain a copy of the License at
|
9 |
+
#
|
10 |
+
# https://www.apache.org/licenses/LICENSE-2.0
|
11 |
+
#
|
12 |
+
# Unless required by applicable law or agreed to in writing, software
|
13 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15 |
+
# See the License for the specific language governing permissions and
|
16 |
+
# limitations under the License.
|
17 |
+
#
|
18 |
+
|
19 |
+
##############################################################################
|
20 |
+
#
|
21 |
+
# Gradle start up script for POSIX generated by Gradle.
|
22 |
+
#
|
23 |
+
# Important for running:
|
24 |
+
#
|
25 |
+
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
26 |
+
# noncompliant, but you have some other compliant shell such as ksh or
|
27 |
+
# bash, then to run this script, type that shell name before the whole
|
28 |
+
# command line, like:
|
29 |
+
#
|
30 |
+
# ksh Gradle
|
31 |
+
#
|
32 |
+
# Busybox and similar reduced shells will NOT work, because this script
|
33 |
+
# requires all of these POSIX shell features:
|
34 |
+
# * functions;
|
35 |
+
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
36 |
+
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
37 |
+
# * compound commands having a testable exit status, especially «case»;
|
38 |
+
# * various built-in commands including «command», «set», and «ulimit».
|
39 |
+
#
|
40 |
+
# Important for patching:
|
41 |
+
#
|
42 |
+
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
43 |
+
# by Bash, Ksh, etc; in particular arrays are avoided.
|
44 |
+
#
|
45 |
+
# The "traditional" practice of packing multiple parameters into a
|
46 |
+
# space-separated string is a well documented source of bugs and security
|
47 |
+
# problems, so this is (mostly) avoided, by progressively accumulating
|
48 |
+
# options in "$@", and eventually passing that to Java.
|
49 |
+
#
|
50 |
+
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
51 |
+
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
52 |
+
# see the in-line comments for details.
|
53 |
+
#
|
54 |
+
# There are tweaks for specific operating systems such as AIX, CygWin,
|
55 |
+
# Darwin, MinGW, and NonStop.
|
56 |
+
#
|
57 |
+
# (3) This script is generated from the Groovy template
|
58 |
+
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
59 |
+
# within the Gradle project.
|
60 |
+
#
|
61 |
+
# You can find Gradle at https://github.com/gradle/gradle/.
|
62 |
+
#
|
63 |
+
##############################################################################
|
64 |
+
|
65 |
+
# Attempt to set APP_HOME
|
66 |
+
|
67 |
+
# Resolve links: $0 may be a link
|
68 |
+
app_path=$0
|
69 |
+
|
70 |
+
# Need this for daisy-chained symlinks.
|
71 |
+
while
|
72 |
+
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
73 |
+
[ -h "$app_path" ]
|
74 |
+
do
|
75 |
+
ls=$( ls -ld "$app_path" )
|
76 |
+
link=${ls#*' -> '}
|
77 |
+
case $link in #(
|
78 |
+
/*) app_path=$link ;; #(
|
79 |
+
*) app_path=$APP_HOME$link ;;
|
80 |
+
esac
|
81 |
+
done
|
82 |
+
|
83 |
+
# This is normally unused
|
84 |
+
# shellcheck disable=SC2034
|
85 |
+
APP_BASE_NAME=${0##*/}
|
86 |
+
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
87 |
+
|
88 |
+
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
89 |
+
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
90 |
+
|
91 |
+
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
92 |
+
MAX_FD=maximum
|
93 |
+
|
94 |
+
warn () {
|
95 |
+
echo "$*"
|
96 |
+
} >&2
|
97 |
+
|
98 |
+
die () {
|
99 |
+
echo
|
100 |
+
echo "$*"
|
101 |
+
echo
|
102 |
+
exit 1
|
103 |
+
} >&2
|
104 |
+
|
105 |
+
# OS specific support (must be 'true' or 'false').
|
106 |
+
cygwin=false
|
107 |
+
msys=false
|
108 |
+
darwin=false
|
109 |
+
nonstop=false
|
110 |
+
case "$( uname )" in #(
|
111 |
+
CYGWIN* ) cygwin=true ;; #(
|
112 |
+
Darwin* ) darwin=true ;; #(
|
113 |
+
MSYS* | MINGW* ) msys=true ;; #(
|
114 |
+
NONSTOP* ) nonstop=true ;;
|
115 |
+
esac
|
116 |
+
|
117 |
+
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
118 |
+
|
119 |
+
|
120 |
+
# Determine the Java command to use to start the JVM.
|
121 |
+
if [ -n "$JAVA_HOME" ] ; then
|
122 |
+
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
123 |
+
# IBM's JDK on AIX uses strange locations for the executables
|
124 |
+
JAVACMD=$JAVA_HOME/jre/sh/java
|
125 |
+
else
|
126 |
+
JAVACMD=$JAVA_HOME/bin/java
|
127 |
+
fi
|
128 |
+
if [ ! -x "$JAVACMD" ] ; then
|
129 |
+
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
130 |
+
|
131 |
+
Please set the JAVA_HOME variable in your environment to match the
|
132 |
+
location of your Java installation."
|
133 |
+
fi
|
134 |
+
else
|
135 |
+
JAVACMD=java
|
136 |
+
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
137 |
+
|
138 |
+
Please set the JAVA_HOME variable in your environment to match the
|
139 |
+
location of your Java installation."
|
140 |
+
fi
|
141 |
+
|
142 |
+
# Increase the maximum file descriptors if we can.
|
143 |
+
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
144 |
+
case $MAX_FD in #(
|
145 |
+
max*)
|
146 |
+
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
147 |
+
# shellcheck disable=SC3045
|
148 |
+
MAX_FD=$( ulimit -H -n ) ||
|
149 |
+
warn "Could not query maximum file descriptor limit"
|
150 |
+
esac
|
151 |
+
case $MAX_FD in #(
|
152 |
+
'' | soft) :;; #(
|
153 |
+
*)
|
154 |
+
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
155 |
+
# shellcheck disable=SC3045
|
156 |
+
ulimit -n "$MAX_FD" ||
|
157 |
+
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
158 |
+
esac
|
159 |
+
fi
|
160 |
+
|
161 |
+
# Collect all arguments for the java command, stacking in reverse order:
|
162 |
+
# * args from the command line
|
163 |
+
# * the main class name
|
164 |
+
# * -classpath
|
165 |
+
# * -D...appname settings
|
166 |
+
# * --module-path (only if needed)
|
167 |
+
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
168 |
+
|
169 |
+
# For Cygwin or MSYS, switch paths to Windows format before running java
|
170 |
+
if "$cygwin" || "$msys" ; then
|
171 |
+
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
172 |
+
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
173 |
+
|
174 |
+
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
175 |
+
|
176 |
+
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
177 |
+
for arg do
|
178 |
+
if
|
179 |
+
case $arg in #(
|
180 |
+
-*) false ;; # don't mess with options #(
|
181 |
+
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
182 |
+
[ -e "$t" ] ;; #(
|
183 |
+
*) false ;;
|
184 |
+
esac
|
185 |
+
then
|
186 |
+
arg=$( cygpath --path --ignore --mixed "$arg" )
|
187 |
+
fi
|
188 |
+
# Roll the args list around exactly as many times as the number of
|
189 |
+
# args, so each arg winds up back in the position where it started, but
|
190 |
+
# possibly modified.
|
191 |
+
#
|
192 |
+
# NB: a `for` loop captures its iteration list before it begins, so
|
193 |
+
# changing the positional parameters here affects neither the number of
|
194 |
+
# iterations, nor the values presented in `arg`.
|
195 |
+
shift # remove old arg
|
196 |
+
set -- "$@" "$arg" # push replacement arg
|
197 |
+
done
|
198 |
+
fi
|
199 |
+
|
200 |
+
# Collect all arguments for the java command;
|
201 |
+
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
202 |
+
# shell script including quotes and variable substitutions, so put them in
|
203 |
+
# double quotes to make sure that they get re-expanded; and
|
204 |
+
# * put everything else in single quotes, so that it's not re-expanded.
|
205 |
+
|
206 |
+
set -- \
|
207 |
+
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
208 |
+
-classpath "$CLASSPATH" \
|
209 |
+
org.gradle.wrapper.GradleWrapperMain \
|
210 |
+
"$@"
|
211 |
+
|
212 |
+
# Stop when "xargs" is not available.
|
213 |
+
if ! command -v xargs >/dev/null 2>&1
|
214 |
+
then
|
215 |
+
die "xargs is not available"
|
216 |
+
fi
|
217 |
+
|
218 |
+
# Use "xargs" to parse quoted args.
|
219 |
+
#
|
220 |
+
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
221 |
+
#
|
222 |
+
# In Bash we could simply go:
|
223 |
+
#
|
224 |
+
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
225 |
+
# set -- "${ARGS[@]}" "$@"
|
226 |
+
#
|
227 |
+
# but POSIX shell has neither arrays nor command substitution, so instead we
|
228 |
+
# post-process each arg (as a line of input to sed) to backslash-escape any
|
229 |
+
# character that might be a shell metacharacter, then use eval to reverse
|
230 |
+
# that process (while maintaining the separation between arguments), and wrap
|
231 |
+
# the whole thing up as a single "set" statement.
|
232 |
+
#
|
233 |
+
# This will of course break if any of these variables contains a newline or
|
234 |
+
# an unmatched quote.
|
235 |
+
#
|
236 |
+
|
237 |
+
eval "set -- $(
|
238 |
+
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
239 |
+
xargs -n1 |
|
240 |
+
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
241 |
+
tr '\n' ' '
|
242 |
+
)" '"$@"'
|
243 |
+
|
244 |
+
exec "$JAVACMD" "$@"
|
gradlew.bat
ADDED
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@rem
|
2 |
+
@rem Copyright 2015 the original author or authors.
|
3 |
+
@rem
|
4 |
+
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
5 |
+
@rem you may not use this file except in compliance with the License.
|
6 |
+
@rem You may obtain a copy of the License at
|
7 |
+
@rem
|
8 |
+
@rem https://www.apache.org/licenses/LICENSE-2.0
|
9 |
+
@rem
|
10 |
+
@rem Unless required by applicable law or agreed to in writing, software
|
11 |
+
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
12 |
+
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13 |
+
@rem See the License for the specific language governing permissions and
|
14 |
+
@rem limitations under the License.
|
15 |
+
@rem
|
16 |
+
|
17 |
+
@if "%DEBUG%"=="" @echo off
|
18 |
+
@rem ##########################################################################
|
19 |
+
@rem
|
20 |
+
@rem Gradle startup script for Windows
|
21 |
+
@rem
|
22 |
+
@rem ##########################################################################
|
23 |
+
|
24 |
+
@rem Set local scope for the variables with windows NT shell
|
25 |
+
if "%OS%"=="Windows_NT" setlocal
|
26 |
+
|
27 |
+
set DIRNAME=%~dp0
|
28 |
+
if "%DIRNAME%"=="" set DIRNAME=.
|
29 |
+
@rem This is normally unused
|
30 |
+
set APP_BASE_NAME=%~n0
|
31 |
+
set APP_HOME=%DIRNAME%
|
32 |
+
|
33 |
+
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
34 |
+
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
35 |
+
|
36 |
+
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
37 |
+
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
38 |
+
|
39 |
+
@rem Find java.exe
|
40 |
+
if defined JAVA_HOME goto findJavaFromJavaHome
|
41 |
+
|
42 |
+
set JAVA_EXE=java.exe
|
43 |
+
%JAVA_EXE% -version >NUL 2>&1
|
44 |
+
if %ERRORLEVEL% equ 0 goto execute
|
45 |
+
|
46 |
+
echo.
|
47 |
+
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
48 |
+
echo.
|
49 |
+
echo Please set the JAVA_HOME variable in your environment to match the
|
50 |
+
echo location of your Java installation.
|
51 |
+
|
52 |
+
goto fail
|
53 |
+
|
54 |
+
:findJavaFromJavaHome
|
55 |
+
set JAVA_HOME=%JAVA_HOME:"=%
|
56 |
+
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
57 |
+
|
58 |
+
if exist "%JAVA_EXE%" goto execute
|
59 |
+
|
60 |
+
echo.
|
61 |
+
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
62 |
+
echo.
|
63 |
+
echo Please set the JAVA_HOME variable in your environment to match the
|
64 |
+
echo location of your Java installation.
|
65 |
+
|
66 |
+
goto fail
|
67 |
+
|
68 |
+
:execute
|
69 |
+
@rem Setup the command line
|
70 |
+
|
71 |
+
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
72 |
+
|
73 |
+
|
74 |
+
@rem Execute Gradle
|
75 |
+
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
76 |
+
|
77 |
+
:end
|
78 |
+
@rem End local scope for the variables with windows NT shell
|
79 |
+
if %ERRORLEVEL% equ 0 goto mainEnd
|
80 |
+
|
81 |
+
:fail
|
82 |
+
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
83 |
+
rem the _cmd.exe /c_ return code!
|
84 |
+
set EXIT_CODE=%ERRORLEVEL%
|
85 |
+
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
86 |
+
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
87 |
+
exit /b %EXIT_CODE%
|
88 |
+
|
89 |
+
:mainEnd
|
90 |
+
if "%OS%"=="Windows_NT" endlocal
|
91 |
+
|
92 |
+
:omega
|
linuxfile/libc.so
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:e997495cab761b24e59cf7b8ca4a4f1bc580b990ec669d954239e91843aec6a8
|
3 |
+
size 1084084
|
linuxfile/ls
ADDED
Binary file (142 kB). View file
|
|
linuxfile/sh
ADDED
Binary file (130 kB). View file
|
|
packer/build.gradle
ADDED
@@ -0,0 +1,288 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import proguard.gradle.ProGuardTask
|
2 |
+
|
3 |
+
plugins {
|
4 |
+
id 'java'
|
5 |
+
id 'java-library'
|
6 |
+
}
|
7 |
+
|
8 |
+
repositories {
|
9 |
+
mavenCentral()
|
10 |
+
}
|
11 |
+
|
12 |
+
dependencies {
|
13 |
+
api rootProject
|
14 |
+
}
|
15 |
+
|
16 |
+
public abstract class OurJavaExec extends JavaExec {
|
17 |
+
@Input
|
18 |
+
abstract Property<FileCollection> getOurInput()
|
19 |
+
}
|
20 |
+
|
21 |
+
TaskProvider<OurJavaExec> createHelperTask(String taskName, String taskType, String fileExt) {
|
22 |
+
return createHelperTask(taskName, taskType, fileExt, null)
|
23 |
+
}
|
24 |
+
|
25 |
+
TaskProvider<OurJavaExec> createHelperTask(String taskName, String taskType, String fileExt, File outFile) {
|
26 |
+
def doDebug = false
|
27 |
+
|
28 |
+
def rsp = tasks.register(taskName, OurJavaExec.class)
|
29 |
+
rsp.configure { exec ->
|
30 |
+
exec.group = 'package'
|
31 |
+
File finalOutFile
|
32 |
+
if (outFile == null) {
|
33 |
+
finalOutFile = new File(exec.temporaryDir, "result." + fileExt)
|
34 |
+
} else {
|
35 |
+
finalOutFile = outFile
|
36 |
+
}
|
37 |
+
exec.outputs.file(finalOutFile)
|
38 |
+
|
39 |
+
def runtime = sourceSets.main.runtimeClasspath
|
40 |
+
exec.inputs.property('runtime', runtime)
|
41 |
+
|
42 |
+
exec.mainClass.set('packer.HelperTask')
|
43 |
+
exec.classpath = runtime
|
44 |
+
exec.dependsOn(exec.ourInput)
|
45 |
+
|
46 |
+
exec.doFirst {
|
47 |
+
def inputs = exec.ourInput
|
48 |
+
def inputFile = inputs.get().singleFile
|
49 |
+
|
50 |
+
exec.args(taskType, inputFile.absolutePath, finalOutFile.absolutePath)
|
51 |
+
exec.temporaryDir.mkdirs()
|
52 |
+
finalOutFile.parentFile?.mkdirs()
|
53 |
+
exec.workingDir(exec.temporaryDir)
|
54 |
+
exec.environment("PACKER_DEBUG", doDebug.toString())
|
55 |
+
}
|
56 |
+
|
57 |
+
}
|
58 |
+
return rsp
|
59 |
+
}
|
60 |
+
|
61 |
+
def stuDefine = project(':struct-define')
|
62 |
+
def tencent = project(':tencent')
|
63 |
+
def projThis = project
|
64 |
+
|
65 |
+
tasks.jar.enabled = false
|
66 |
+
|
67 |
+
def transformClassStub = createHelperTask("transformClassStub", "StubTransform", "jar")
|
68 |
+
|
69 |
+
def packResources = tasks.register('packResources', Zip.class) { task ->
|
70 |
+
task.group = 'package'
|
71 |
+
task.from(rootProject.file('linuxfile')) { into 'linuxfile' }
|
72 |
+
|
73 |
+
task.archiveBaseName.set('resources')
|
74 |
+
task.destinationDir(task.temporaryDir)
|
75 |
+
}
|
76 |
+
|
77 |
+
def packServiceLoader = createHelperTask("packServiceLoader", "PackServiceLoader", "jar")
|
78 |
+
packServiceLoader.configure { exec ->
|
79 |
+
exec.ourInput.set(packResources.get().outputs.files)
|
80 |
+
}
|
81 |
+
|
82 |
+
def packRpcServiceLoader = createHelperTask("packRpcServiceLoader", "ConvertToRpc", "jar")
|
83 |
+
packRpcServiceLoader.configure { exec ->
|
84 |
+
exec.ourInput.set(packServiceLoader.get().outputs.files)
|
85 |
+
}
|
86 |
+
|
87 |
+
|
88 |
+
def packConsoleJar = tasks.register('packConsoleJar', Jar.class) { task ->
|
89 |
+
task.group = 'package'
|
90 |
+
|
91 |
+
task.from(packResources) { into 'kfc/vivo50/code45'; rename { 'resources.zip' } }
|
92 |
+
|
93 |
+
|
94 |
+
task.archiveBaseName.set('vivo50-kfc-code45')
|
95 |
+
}
|
96 |
+
|
97 |
+
def packConsoleRpc = tasks.register('packConsoleRpc', Jar.class) { task ->
|
98 |
+
|
99 |
+
task.from(rootProject.file('tencent/src/main/rpcResources')) { CopySpec spec ->
|
100 |
+
spec.duplicatesStrategy(DuplicatesStrategy.INCLUDE)
|
101 |
+
}
|
102 |
+
|
103 |
+
task.inputs.files(packConsoleJar.get().outputs)
|
104 |
+
task.from(zipTree(packConsoleJar.get().outputs.files.singleFile)) {
|
105 |
+
exclude 'kfc/vivo50/code45/ServiceSelector.class'
|
106 |
+
exclude 'kfc/vivo50/code45/resources.zip'
|
107 |
+
duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
|
108 |
+
}
|
109 |
+
|
110 |
+
task.inputs.files(packRpcServiceLoader.get().outputs)
|
111 |
+
task.from(zipTree(packRpcServiceLoader.get().outputs.files.singleFile))
|
112 |
+
|
113 |
+
task.archiveBaseName.set('vivo50-kfc-code45-rpc')
|
114 |
+
}
|
115 |
+
|
116 |
+
def packServer = tasks.register('packServer', Jar.class) { task ->
|
117 |
+
task.group = 'package'
|
118 |
+
|
119 |
+
task.archiveBaseName.set('server')
|
120 |
+
|
121 |
+
task.manifest.attributes([
|
122 |
+
'Main-Class': 'tencentlibfekit.vmservice.rpc.RpcServerBootstrap',
|
123 |
+
])
|
124 |
+
}
|
125 |
+
|
126 |
+
def remapServerJar = createHelperTask("remapServerJar", "SimpleRemapper", "jar", file('build/proguard/server-remap.jar'))
|
127 |
+
|
128 |
+
|
129 |
+
projThis.tasks.named('build').configure { it.finalizedBy(':packer:packConsoleJar') }
|
130 |
+
projThis.tasks.named('build').configure { it.finalizedBy(':packer:packConsoleRpc') }
|
131 |
+
projThis.tasks.named('build').configure { it.finalizedBy(':packer:packServer') }
|
132 |
+
projThis.tasks.named('build').configure { it.finalizedBy(':packer:remapServerJar') }
|
133 |
+
|
134 |
+
|
135 |
+
stuDefine.afterEvaluate {
|
136 |
+
transformClassStub.configure { exec ->
|
137 |
+
exec.ourInput.set(stuDefine.tasks.getByName('jar').outputs.files)
|
138 |
+
}
|
139 |
+
}
|
140 |
+
|
141 |
+
tencent.afterEvaluate {
|
142 |
+
def packTencent = { Jar pack ->
|
143 |
+
pack.inputs.files(tencent.tasks.jar.outputs)
|
144 |
+
pack.from(zipTree(tencent.tasks.jar.outputs.files.singleFile)) {
|
145 |
+
exclude 'kfc/vivo50/code45/ServiceSelector.class'
|
146 |
+
exclude 'kfc/vivo50/code45/StubClasses.class'
|
147 |
+
}
|
148 |
+
|
149 |
+
pack.inputs.files(transformClassStub.get().outputs)
|
150 |
+
pack.from(zipTree(transformClassStub.get().outputs.files.singleFile))
|
151 |
+
|
152 |
+
pack.inputs.files(packServiceLoader.get().outputs)
|
153 |
+
pack.from(zipTree(packServiceLoader.get().outputs.files.singleFile))
|
154 |
+
}
|
155 |
+
|
156 |
+
packConsoleJar.configure { packTencent(it) }
|
157 |
+
|
158 |
+
packServer.configure { pack ->
|
159 |
+
pack.inputs.files(packConsoleJar.get().outputs)
|
160 |
+
pack.from(zipTree(packConsoleJar.get().outputs.files.singleFile)) {
|
161 |
+
exclude 'META-INF/mirai-console-plugin/**'
|
162 |
+
exclude 'META-INF/services/net.mamoe.mirai.console.*'
|
163 |
+
exclude 'com/kiliokuara/kuimivm/**'
|
164 |
+
}
|
165 |
+
|
166 |
+
tencent.configurations.runtimeClasspath.files.each { sfile ->
|
167 |
+
var pt = sfile.absolutePath
|
168 |
+
if (pt.contains('mirai-core')) return
|
169 |
+
|
170 |
+
if (sfile.isDirectory()) {
|
171 |
+
pack.from(sfile)
|
172 |
+
} else {
|
173 |
+
pack.from(zipTree(sfile)) {
|
174 |
+
exclude 'module-info.class'
|
175 |
+
exclude 'META-INF/versions/9/module-info.class'
|
176 |
+
exclude 'META-INF/*'
|
177 |
+
}
|
178 |
+
}
|
179 |
+
}
|
180 |
+
}
|
181 |
+
|
182 |
+
|
183 |
+
// proguard
|
184 |
+
|
185 |
+
projThis.tasks.register('buildConsoleRpc', ProGuardTask.class) { pg ->
|
186 |
+
pg.group = 'proguard'
|
187 |
+
|
188 |
+
pg.injars(packConsoleRpc.get().outputs)
|
189 |
+
pg.inputs.files(packConsoleRpc.get().outputs)
|
190 |
+
pg.inputs.files(tencent.configurations.compileClasspath)
|
191 |
+
|
192 |
+
tencent.configurations.compileClasspath.files.each { cp ->
|
193 |
+
if (cp == projThis.file('src/main/resources')) return
|
194 |
+
|
195 |
+
pg.libraryjars(cp)
|
196 |
+
}
|
197 |
+
|
198 |
+
pg.libraryjars "${System.getProperty('java.home')}/jmods/java.base.jmod"
|
199 |
+
pg.libraryjars "${System.getProperty('java.home')}/jmods/java.net.http.jmod"
|
200 |
+
|
201 |
+
pg.verbose()
|
202 |
+
pg.outjars(new File(projThis.buildDir, 'proguard/vivo50-kfc-code45-rpc.jar'))
|
203 |
+
pg.dontwarn()
|
204 |
+
pg.forceprocessing()
|
205 |
+
pg.optimizations('*')
|
206 |
+
pg.optimizationpasses(5)
|
207 |
+
|
208 |
+
pg.keep('class * extends net.mamoe.mirai.console.plugin.jvm.JavaPlugin { static ** INSTANCE; }')
|
209 |
+
|
210 |
+
pg.keepattributes('InnerClasses,Signature,RuntimeVisible*Annotations,EnclosingMethod,*Annotation*')
|
211 |
+
}
|
212 |
+
projThis.tasks.named('build').configure { it.finalizedBy(':packer:buildConsoleRpc') }
|
213 |
+
|
214 |
+
|
215 |
+
def buildServer = projThis.tasks.register('buildServer', ProGuardTask.class) { pg ->
|
216 |
+
pg.group = 'proguard'
|
217 |
+
|
218 |
+
pg.injars(packServer.get().outputs)
|
219 |
+
pg.inputs.files(packServer.get().outputs)
|
220 |
+
pg.inputs.files(tencent.configurations.compileClasspath)
|
221 |
+
|
222 |
+
// tencent.configurations.compileClasspath.files.each { cp ->
|
223 |
+
// if (cp == projThis.file('src/main/resources')) return
|
224 |
+
//
|
225 |
+
// pg.libraryjars(cp)
|
226 |
+
// }
|
227 |
+
|
228 |
+
pg.libraryjars "${System.getProperty('java.home')}/jmods/java.base.jmod"
|
229 |
+
pg.libraryjars "${System.getProperty('java.home')}/jmods/java.net.http.jmod"
|
230 |
+
pg.libraryjars "${System.getProperty('java.home')}/jmods/java.desktop.jmod"
|
231 |
+
|
232 |
+
// pg.verbose()
|
233 |
+
pg.outjars(new File(projThis.buildDir, 'proguard/server.jar'))
|
234 |
+
pg.outputs.file(new File(projThis.buildDir, 'proguard/server.jar'))
|
235 |
+
|
236 |
+
pg.dontwarn()
|
237 |
+
pg.dontnote()
|
238 |
+
|
239 |
+
pg.forceprocessing()
|
240 |
+
pg.optimizations('*')
|
241 |
+
pg.optimizationpasses(1)
|
242 |
+
|
243 |
+
pg.keepclasseswithmembers('class **.RpcServerBootstrap { public static void main(java.lang.String[]); }')
|
244 |
+
|
245 |
+
pg.keep('class io.netty.** { *; }')
|
246 |
+
pg.keep('class io.vertx.** { *; }')
|
247 |
+
|
248 |
+
pg.keep('class io.github.karlatemp.unsafeaccessor.** { *; }')
|
249 |
+
pg.keep('class com.sun.jna.** { *; }')
|
250 |
+
|
251 |
+
pg.keep('class com.google.** { *; }')
|
252 |
+
pg.keep('class com.fasterxml.jackson.** { *; }')
|
253 |
+
pg.keepclassmembers('class * { @**.SerializedName <fields>; }')
|
254 |
+
|
255 |
+
pg.keep('class org.apache.** { *; }')
|
256 |
+
pg.keep('class org.slf4j.** { *; }')
|
257 |
+
|
258 |
+
pg.keep('class com.github.unidbg.** { *; }')
|
259 |
+
pg.keep('class com.github.zhkl0228.** { *; }')
|
260 |
+
pg.keep('class de.fearlesstobi.** { *; }')
|
261 |
+
pg.keep('class unicorn.** { *; }')
|
262 |
+
pg.keep('class keystone.** { *; }')
|
263 |
+
|
264 |
+
|
265 |
+
pg.keep('class com.kiliokuara.kuimivm.** { *; }') // stupid ProGuard
|
266 |
+
pg.keepclassmembers('class * { static void initialize(**.KuimiVM,**.KuimiObject); }')
|
267 |
+
pg.keepclassmembers('class * extends com.kiliokuara.kuimivm.KuimiMethod { <methods>; }')
|
268 |
+
|
269 |
+
pg.keep allowobfuscation: true, 'class android.** { *; }'
|
270 |
+
pg.keep allowobfuscation: true, 'class com.tencent.mobileqq.** { *; }'
|
271 |
+
|
272 |
+
|
273 |
+
pg.keepattributes('Signature,RuntimeVisible*Annotations,EnclosingMethod,*Annotation*')
|
274 |
+
pg.flattenpackagehierarchy('kfc')
|
275 |
+
}
|
276 |
+
remapServerJar.configure { exec ->
|
277 |
+
exec.ourInput.set(buildServer.get().outputs.files)
|
278 |
+
}
|
279 |
+
|
280 |
+
|
281 |
+
projThis.tasks.named('build').configure { it.finalizedBy(':packer:buildServer') }
|
282 |
+
}
|
283 |
+
|
284 |
+
packConsoleJar.configure { pack ->
|
285 |
+
pack.inputs.files(rootProject.tasks.jar.outputs)
|
286 |
+
pack.from(zipTree(rootProject.tasks.jar.outputs.files.singleFile))
|
287 |
+
}
|
288 |
+
|
packer/src/main/java/packer/HelperTask.java
ADDED
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package packer;
|
2 |
+
|
3 |
+
import io.github.karlatemp.unsafeaccessor.Root;
|
4 |
+
import org.objectweb.asm.ClassWriter;
|
5 |
+
import org.objectweb.asm.Opcodes;
|
6 |
+
|
7 |
+
import java.io.*;
|
8 |
+
import java.lang.invoke.MethodType;
|
9 |
+
import java.util.HashMap;
|
10 |
+
import java.util.Map;
|
11 |
+
import java.util.zip.ZipEntry;
|
12 |
+
import java.util.zip.ZipInputStream;
|
13 |
+
import java.util.zip.ZipOutputStream;
|
14 |
+
|
15 |
+
public class HelperTask {
|
16 |
+
public static void classInit(ClassWriter cw) {
|
17 |
+
var init = cw.visitMethod(Opcodes.ACC_PRIVATE, "<init>", "()V", null, null);
|
18 |
+
init.visitVarInsn(Opcodes.ALOAD, 0);
|
19 |
+
init.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
|
20 |
+
init.visitInsn(Opcodes.RETURN);
|
21 |
+
init.visitMaxs(3, 1);
|
22 |
+
}
|
23 |
+
|
24 |
+
public static void main(String[] args) throws Throwable {
|
25 |
+
var taskName = args[0];
|
26 |
+
var input = args[1];
|
27 |
+
var output = args[2];
|
28 |
+
|
29 |
+
|
30 |
+
var taskClass = Class.forName("packer.tasks." + taskName);
|
31 |
+
|
32 |
+
Root.getTrusted(taskClass).findStatic(
|
33 |
+
taskClass, "process", MethodType.methodType(void.class, String.class, String.class)
|
34 |
+
).invoke(input, output);
|
35 |
+
}
|
36 |
+
|
37 |
+
public static Map<String, byte[]> loadZip(File file) throws IOException {
|
38 |
+
var rsp = new HashMap<String, byte[]>();
|
39 |
+
try (var zipInput = new ZipInputStream(new BufferedInputStream(new FileInputStream(file), 20480))) {
|
40 |
+
while (true) {
|
41 |
+
var entry = zipInput.getNextEntry();
|
42 |
+
if (entry == null) break;
|
43 |
+
if (entry.isDirectory()) continue;
|
44 |
+
rsp.put(entry.getName(), zipInput.readAllBytes());
|
45 |
+
}
|
46 |
+
}
|
47 |
+
return rsp;
|
48 |
+
}
|
49 |
+
|
50 |
+
public static void writeZip(OutputStream outputStream, Map<String, byte[]> zip) throws IOException {
|
51 |
+
var zos = new ZipOutputStream(new BufferedOutputStream(outputStream, 20480));
|
52 |
+
|
53 |
+
for (var entry : zip.entrySet()) {
|
54 |
+
zos.putNextEntry(new ZipEntry(entry.getKey()));
|
55 |
+
zos.write(entry.getValue());
|
56 |
+
}
|
57 |
+
|
58 |
+
zos.finish();
|
59 |
+
zos.flush();
|
60 |
+
}
|
61 |
+
|
62 |
+
public static String gitCommitHash() {
|
63 |
+
try {
|
64 |
+
var process = new ProcessBuilder("git", "rev-parse", "HEAD")
|
65 |
+
.redirectError(ProcessBuilder.Redirect.INHERIT)
|
66 |
+
.start();
|
67 |
+
process.getOutputStream().close();
|
68 |
+
try (var is = process.getInputStream()) {
|
69 |
+
return new String(is.readAllBytes()).trim();
|
70 |
+
} finally {
|
71 |
+
process.waitFor();
|
72 |
+
process.destroy();
|
73 |
+
}
|
74 |
+
} catch (Throwable throwable) {
|
75 |
+
throw new RuntimeException(throwable);
|
76 |
+
}
|
77 |
+
}
|
78 |
+
}
|
packer/src/main/java/packer/tasks/ConvertToRpc.java
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package packer.tasks;
|
2 |
+
|
3 |
+
import org.objectweb.asm.ClassReader;
|
4 |
+
import org.objectweb.asm.ClassWriter;
|
5 |
+
import org.objectweb.asm.Opcodes;
|
6 |
+
import org.objectweb.asm.tree.ClassNode;
|
7 |
+
import packer.HelperTask;
|
8 |
+
|
9 |
+
import java.io.File;
|
10 |
+
import java.io.FileOutputStream;
|
11 |
+
|
12 |
+
public class ConvertToRpc {
|
13 |
+
public static void process(String input, String output) throws Throwable {
|
14 |
+
var zip = HelperTask.loadZip(new File(input));
|
15 |
+
|
16 |
+
var cnnode = new ClassNode();
|
17 |
+
new ClassReader(zip.get("kfc/vivo50/code45/ServiceSelector.class")).accept(cnnode, 0);
|
18 |
+
|
19 |
+
{
|
20 |
+
for (var met : cnnode.methods) {
|
21 |
+
if (met.name.equals("doRpc")) {
|
22 |
+
met.instructions.clear();
|
23 |
+
met.visitInsn(Opcodes.ICONST_1);
|
24 |
+
met.visitInsn(Opcodes.IRETURN);
|
25 |
+
}
|
26 |
+
if (met.name.equals("get")) {
|
27 |
+
met.instructions.clear();
|
28 |
+
met.visitInsn(Opcodes.ACONST_NULL);
|
29 |
+
met.visitInsn(Opcodes.ARETURN);
|
30 |
+
}
|
31 |
+
}
|
32 |
+
|
33 |
+
var cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
34 |
+
cnnode.accept(cw);
|
35 |
+
zip.put("kfc/vivo50/code45/ServiceSelector.class", cw.toByteArray());
|
36 |
+
}
|
37 |
+
|
38 |
+
try (var out = new FileOutputStream(output)) {
|
39 |
+
HelperTask.writeZip(out, zip);
|
40 |
+
}
|
41 |
+
}
|
42 |
+
}
|
packer/src/main/java/packer/tasks/PackServiceLoader.java
ADDED
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package packer.tasks;
|
2 |
+
|
3 |
+
import org.objectweb.asm.ClassWriter;
|
4 |
+
import org.objectweb.asm.Opcodes;
|
5 |
+
import org.objectweb.asm.Type;
|
6 |
+
import packer.HelperTask;
|
7 |
+
|
8 |
+
import java.io.BufferedOutputStream;
|
9 |
+
import java.io.FileOutputStream;
|
10 |
+
import java.nio.file.Files;
|
11 |
+
import java.nio.file.Path;
|
12 |
+
import java.security.MessageDigest;
|
13 |
+
import java.util.HexFormat;
|
14 |
+
import java.util.zip.ZipEntry;
|
15 |
+
import java.util.zip.ZipOutputStream;
|
16 |
+
|
17 |
+
public class PackServiceLoader {
|
18 |
+
public static void process(String input, String output) throws Throwable {
|
19 |
+
System.out.println(input);
|
20 |
+
|
21 |
+
try (var zipOutput = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(output)))) {
|
22 |
+
zipOutput.putNextEntry(new ZipEntry("kfc/vivo50/code45/ServiceSelector.class"));
|
23 |
+
|
24 |
+
var cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
25 |
+
cw.visit(Opcodes.V11, Opcodes.ACC_PUBLIC, "kfc/vivo50/code45/ServiceSelector", null, "java/lang/Object", null);
|
26 |
+
|
27 |
+
HelperTask.classInit(cw);
|
28 |
+
|
29 |
+
{
|
30 |
+
var mv = cw.visitMethod(Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC, "mainClass", "()Ljava/lang/String;", null, null);
|
31 |
+
mv.visitLdcInsn(Type.getObjectType("libvtx/VGenerated"));
|
32 |
+
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;", false);
|
33 |
+
mv.visitInsn(Opcodes.ARETURN);
|
34 |
+
mv.visitMaxs(0, 0);
|
35 |
+
}
|
36 |
+
|
37 |
+
{
|
38 |
+
var mv = cw.visitMethod(Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC, "resourceSha1", "()Ljava/lang/String;", null, null);
|
39 |
+
|
40 |
+
mv.visitLdcInsn(HexFormat.of().formatHex(
|
41 |
+
MessageDigest.getInstance("sha1").digest(
|
42 |
+
Files.readAllBytes(Path.of(input))
|
43 |
+
)
|
44 |
+
));
|
45 |
+
|
46 |
+
mv.visitInsn(Opcodes.ARETURN);
|
47 |
+
mv.visitMaxs(0, 0);
|
48 |
+
}
|
49 |
+
|
50 |
+
{
|
51 |
+
var mv = cw.visitMethod(Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC, "commitHash", "()Ljava/lang/String;", null, null);
|
52 |
+
|
53 |
+
mv.visitLdcInsn(HelperTask.gitCommitHash());
|
54 |
+
|
55 |
+
mv.visitInsn(Opcodes.ARETURN);
|
56 |
+
mv.visitMaxs(0, 0);
|
57 |
+
}
|
58 |
+
|
59 |
+
{
|
60 |
+
var mv = cw.visitMethod(Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC, "buildTime", "()J", null, null);
|
61 |
+
|
62 |
+
mv.visitLdcInsn(System.currentTimeMillis());
|
63 |
+
|
64 |
+
mv.visitInsn(Opcodes.LRETURN);
|
65 |
+
mv.visitMaxs(0, 0);
|
66 |
+
}
|
67 |
+
|
68 |
+
{
|
69 |
+
var mv = cw.visitMethod(Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC, "isLocal", "()Z", null, null);
|
70 |
+
|
71 |
+
mv.visitInsn(Opcodes.ICONST_0);
|
72 |
+
|
73 |
+
mv.visitInsn(Opcodes.IRETURN);
|
74 |
+
mv.visitMaxs(0, 0);
|
75 |
+
}
|
76 |
+
{
|
77 |
+
var mv = cw.visitMethod(Opcodes.ACC_STATIC, "doRpc", "()Z", null, null);
|
78 |
+
|
79 |
+
mv.visitInsn(Opcodes.ICONST_0);
|
80 |
+
|
81 |
+
mv.visitInsn(Opcodes.IRETURN);
|
82 |
+
mv.visitMaxs(0, 0);
|
83 |
+
}
|
84 |
+
|
85 |
+
{
|
86 |
+
var mv = cw.visitMethod(Opcodes.ACC_STATIC, "get", "(Ljava/io/File;Ljava/io/File;)Lkfc/vivo50/code45/ConsoleServiceFactory;", null, null);
|
87 |
+
mv.visitTypeInsn(Opcodes.NEW, "kfc/vivo50/code45/ConsoleServiceFactory$Packed");
|
88 |
+
mv.visitInsn(Opcodes.DUP);
|
89 |
+
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
90 |
+
mv.visitVarInsn(Opcodes.ALOAD, 1);
|
91 |
+
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "kfc/vivo50/code45/ConsoleServiceFactory$Packed", "<init>", "(Ljava/io/File;Ljava/io/File;)V", false);
|
92 |
+
mv.visitInsn(Opcodes.ARETURN);
|
93 |
+
mv.visitMaxs(0, 0);
|
94 |
+
}
|
95 |
+
|
96 |
+
zipOutput.write(cw.toByteArray());
|
97 |
+
}
|
98 |
+
}
|
99 |
+
}
|
packer/src/main/java/packer/tasks/SimpleRemapper.java
ADDED
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package packer.tasks;
|
2 |
+
|
3 |
+
import org.objectweb.asm.ClassReader;
|
4 |
+
import org.objectweb.asm.ClassWriter;
|
5 |
+
import org.objectweb.asm.commons.ClassRemapper;
|
6 |
+
import org.objectweb.asm.commons.Remapper;
|
7 |
+
import org.objectweb.asm.tree.ClassNode;
|
8 |
+
import packer.HelperTask;
|
9 |
+
|
10 |
+
import java.io.File;
|
11 |
+
import java.io.FileOutputStream;
|
12 |
+
import java.util.*;
|
13 |
+
|
14 |
+
public class SimpleRemapper {
|
15 |
+
public static void process(String input, String output) throws Throwable {
|
16 |
+
var zip = HelperTask.loadZip(new File(input));
|
17 |
+
|
18 |
+
var processPackages = List.of(
|
19 |
+
"kfc/",
|
20 |
+
"com/kiliokuara/kuimivm/"
|
21 |
+
);
|
22 |
+
|
23 |
+
var classes = new HashMap<String, ClassNode>();
|
24 |
+
var remapTable = new HashMap<String, String>();
|
25 |
+
var toolkit = new Object() {
|
26 |
+
private final Set<String> usedNames = new HashSet<>();
|
27 |
+
|
28 |
+
String nextName() {
|
29 |
+
while (true) {
|
30 |
+
var newName = "n" + UUID.randomUUID().toString().replace("-", "");
|
31 |
+
if (usedNames.add(newName)) return newName;
|
32 |
+
}
|
33 |
+
}
|
34 |
+
|
35 |
+
boolean needRemap(String classname) {
|
36 |
+
for (var pkg : processPackages) {
|
37 |
+
if (classname.startsWith(pkg)) return true;
|
38 |
+
}
|
39 |
+
return false;
|
40 |
+
}
|
41 |
+
|
42 |
+
String getPackage(String cname) {
|
43 |
+
var lastIdx = cname.lastIndexOf('/');
|
44 |
+
return cname.substring(0, lastIdx);
|
45 |
+
}
|
46 |
+
};
|
47 |
+
|
48 |
+
zip.entrySet().removeIf(entry -> {
|
49 |
+
if (entry.getKey().endsWith(".class")) {
|
50 |
+
var cnode = new ClassNode();
|
51 |
+
new ClassReader(entry.getValue()).accept(cnode, 0);
|
52 |
+
classes.put(cnode.name, cnode);
|
53 |
+
return true;
|
54 |
+
}
|
55 |
+
return false;
|
56 |
+
});
|
57 |
+
|
58 |
+
var pkgRemap = new HashMap<String, String>();
|
59 |
+
|
60 |
+
for (var klass : classes.values()) {
|
61 |
+
if (!toolkit.needRemap(klass.name)) continue;
|
62 |
+
|
63 |
+
var pkg = toolkit.getPackage(klass.name);
|
64 |
+
pkgRemap.computeIfAbsent(pkg, $ -> "kfc/" + toolkit.nextName());
|
65 |
+
}
|
66 |
+
|
67 |
+
|
68 |
+
for (var klass : classes.values()) {
|
69 |
+
if (!toolkit.needRemap(klass.name)) continue;
|
70 |
+
var pkg = toolkit.getPackage(klass.name);
|
71 |
+
|
72 |
+
var rname = pkgRemap.get(pkg) + '/' + toolkit.nextName();
|
73 |
+
remapTable.put(klass.name, rname);
|
74 |
+
}
|
75 |
+
|
76 |
+
var remapper = new Remapper() {
|
77 |
+
@Override
|
78 |
+
public String map(String internalName) {
|
79 |
+
return remapTable.getOrDefault(internalName, internalName);
|
80 |
+
}
|
81 |
+
};
|
82 |
+
|
83 |
+
for (var klass : classes.values()) {
|
84 |
+
var newKlass = new ClassNode();
|
85 |
+
klass.accept(new ClassRemapper(newKlass, remapper));
|
86 |
+
|
87 |
+
var cw = new ClassWriter(0);
|
88 |
+
newKlass.accept(cw);
|
89 |
+
|
90 |
+
zip.put(newKlass.name + ".class", cw.toByteArray());
|
91 |
+
}
|
92 |
+
|
93 |
+
try (var out = new FileOutputStream(output)) {
|
94 |
+
HelperTask.writeZip(out, zip);
|
95 |
+
}
|
96 |
+
}
|
97 |
+
}
|
packer/src/main/java/packer/tasks/StubTransform.java
ADDED
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package packer.tasks;
|
2 |
+
|
3 |
+
import com.kiliokuara.kuimivm.transform.JarTransformer;
|
4 |
+
import org.objectweb.asm.ClassWriter;
|
5 |
+
import org.objectweb.asm.Opcodes;
|
6 |
+
import org.objectweb.asm.Type;
|
7 |
+
import packer.HelperTask;
|
8 |
+
|
9 |
+
import java.io.BufferedInputStream;
|
10 |
+
import java.io.BufferedOutputStream;
|
11 |
+
import java.io.FileInputStream;
|
12 |
+
import java.io.FileOutputStream;
|
13 |
+
import java.util.zip.ZipEntry;
|
14 |
+
import java.util.zip.ZipInputStream;
|
15 |
+
import java.util.zip.ZipOutputStream;
|
16 |
+
|
17 |
+
public class StubTransform {
|
18 |
+
public static void process(String input, String output) throws Throwable {
|
19 |
+
var transform = new JarTransformer();
|
20 |
+
transform.setDebug(Boolean.parseBoolean(System.getenv("PACKER_DEBUG")));
|
21 |
+
System.out.println("SRFC: " + input);
|
22 |
+
|
23 |
+
try (var zip = new ZipInputStream(new BufferedInputStream(new FileInputStream(input), 20480))) {
|
24 |
+
transform.loadFrom(zip);
|
25 |
+
}
|
26 |
+
transform.generateClassStructure(Type.getObjectType("libvtx/VGenerated"));
|
27 |
+
|
28 |
+
try (var outputStream = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(output), 20480))) {
|
29 |
+
var oot = transform.getOutput();
|
30 |
+
for (var entry : oot.entrySet()) {
|
31 |
+
// System.out.println("Writing " + entry.getKey());
|
32 |
+
outputStream.putNextEntry(new ZipEntry(entry.getKey() + ".class"));
|
33 |
+
outputStream.write(entry.getValue());
|
34 |
+
}
|
35 |
+
|
36 |
+
outputStream.putNextEntry(new ZipEntry("kfc/vivo50/code45/StubClasses.class"));
|
37 |
+
|
38 |
+
var cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
39 |
+
cw.visit(Opcodes.V11, Opcodes.ACC_PUBLIC, "kfc/vivo50/code45/StubClasses", null, "java/lang/Object", null);
|
40 |
+
|
41 |
+
HelperTask.classInit(cw);
|
42 |
+
|
43 |
+
{
|
44 |
+
var mt = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "classes", "()Ljava/util/List;", null, null);
|
45 |
+
mt.visitTypeInsn(Opcodes.NEW, "java/util/ArrayList");
|
46 |
+
mt.visitInsn(Opcodes.DUP);
|
47 |
+
mt.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/util/ArrayList", "<init>", "()V", false);
|
48 |
+
|
49 |
+
for (var name : oot.keySet()) {
|
50 |
+
mt.visitInsn(Opcodes.DUP);
|
51 |
+
mt.visitLdcInsn(Type.getObjectType(name));
|
52 |
+
mt.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/util/ArrayList", "add", "(Ljava/lang/Object;)Z", false);
|
53 |
+
mt.visitInsn(Opcodes.POP);
|
54 |
+
}
|
55 |
+
|
56 |
+
mt.visitInsn(Opcodes.ARETURN);
|
57 |
+
mt.visitMaxs(0, 0);
|
58 |
+
}
|
59 |
+
|
60 |
+
outputStream.write(cw.toByteArray());
|
61 |
+
}
|
62 |
+
}
|
63 |
+
}
|
settings.gradle
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
rootProject.name = 'kuimivm'
|
2 |
+
|
3 |
+
include ':tencent'
|
4 |
+
include ':struct-define'
|
5 |
+
include ':packer'
|
src/main/java/com/kiliokuara/kuimivm/KuimiClass.java
ADDED
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm;
|
2 |
+
|
3 |
+
import com.kiliokuara.kuimivm.objects.KuimiArrays;
|
4 |
+
import org.objectweb.asm.Opcodes;
|
5 |
+
import org.objectweb.asm.Type;
|
6 |
+
|
7 |
+
import java.lang.reflect.Modifier;
|
8 |
+
import java.util.Collections;
|
9 |
+
import java.util.List;
|
10 |
+
import java.util.Objects;
|
11 |
+
|
12 |
+
public class KuimiClass extends KuimiObject<Class<?>> {
|
13 |
+
private static final int CLASS_STATE_NOT_INITIALIZED = 0;
|
14 |
+
private static final int CLASS_STATE_CALLING_CLINIT = -1;
|
15 |
+
private static final int CLASS_STATE_INITIALIZED = 1;
|
16 |
+
private static final int CLASS_STATE_ERROR = -2;
|
17 |
+
|
18 |
+
private final KuimiVM vm;
|
19 |
+
private final Type classType;
|
20 |
+
private final KuimiObject<?> classLoader;
|
21 |
+
private final KuimiClass parent;
|
22 |
+
private final List<KuimiClass> itfs;
|
23 |
+
private final int modifiers;
|
24 |
+
|
25 |
+
|
26 |
+
private final KuimiMethodTable methodTable;
|
27 |
+
private final KuimiFieldTable fieldTable;
|
28 |
+
|
29 |
+
private volatile int classState = CLASS_STATE_NOT_INITIALIZED;
|
30 |
+
|
31 |
+
|
32 |
+
public KuimiClass(
|
33 |
+
KuimiVM vm, Type classType,
|
34 |
+
int modifiers,
|
35 |
+
KuimiObject<?> classLoader,
|
36 |
+
KuimiClass parentClass,
|
37 |
+
List<KuimiClass> interfaces
|
38 |
+
) {
|
39 |
+
Objects.requireNonNull(vm, "vm");
|
40 |
+
Objects.requireNonNull(classType, "classType");
|
41 |
+
|
42 |
+
if ((modifiers & Opcodes.ACC_INTERFACE) != 0) {
|
43 |
+
modifiers |= Opcodes.ACC_ABSTRACT;
|
44 |
+
}
|
45 |
+
|
46 |
+
this.modifiers = modifiers;
|
47 |
+
this.vm = vm;
|
48 |
+
this.classType = classType; // fixme check name
|
49 |
+
this.classLoader = classLoader;
|
50 |
+
this.parent = parentClass;
|
51 |
+
this.itfs = interfaces == null ? Collections.emptyList() : interfaces;
|
52 |
+
|
53 |
+
this.methodTable = new KuimiMethodTable(this);
|
54 |
+
this.fieldTable = new KuimiFieldTable(this);
|
55 |
+
}
|
56 |
+
|
57 |
+
@Override
|
58 |
+
public KuimiVM getVm() {
|
59 |
+
return vm;
|
60 |
+
}
|
61 |
+
|
62 |
+
@Override
|
63 |
+
public KuimiClass getObjectClass() {
|
64 |
+
return vm.getClassClass();
|
65 |
+
}
|
66 |
+
|
67 |
+
public KuimiObject<?> getClassLoader() {
|
68 |
+
return classLoader;
|
69 |
+
}
|
70 |
+
|
71 |
+
/**
|
72 |
+
* Internal name
|
73 |
+
*/
|
74 |
+
public String getTypeName() {
|
75 |
+
return classType.getClassName();
|
76 |
+
}
|
77 |
+
|
78 |
+
public Type getClassType() {
|
79 |
+
return classType;
|
80 |
+
}
|
81 |
+
|
82 |
+
public KuimiClass getSuperClass() {
|
83 |
+
return parent;
|
84 |
+
}
|
85 |
+
|
86 |
+
public List<KuimiClass> getInterfaces() {
|
87 |
+
return itfs;
|
88 |
+
}
|
89 |
+
|
90 |
+
public int getModifiers() {
|
91 |
+
return modifiers;
|
92 |
+
}
|
93 |
+
|
94 |
+
public KuimiMethodTable getMethodTable() {
|
95 |
+
return methodTable;
|
96 |
+
}
|
97 |
+
|
98 |
+
public KuimiFieldTable getFieldTable() {
|
99 |
+
return fieldTable;
|
100 |
+
}
|
101 |
+
|
102 |
+
public boolean isPrimitive() {
|
103 |
+
var sort = classType.getSort();
|
104 |
+
return sort != Type.ARRAY && sort != Type.OBJECT;
|
105 |
+
}
|
106 |
+
|
107 |
+
public boolean isArray() {
|
108 |
+
return classType.getSort() == Type.ARRAY;
|
109 |
+
}
|
110 |
+
|
111 |
+
@Override
|
112 |
+
public String toString() {
|
113 |
+
if ((modifiers & Opcodes.ACC_INTERFACE) == 0) {
|
114 |
+
return "class " + getTypeName();
|
115 |
+
}
|
116 |
+
return "interface " + getTypeName();
|
117 |
+
}
|
118 |
+
|
119 |
+
public KuimiObject<?> allocateNewObject() {
|
120 |
+
if (this == vm.getClassClass()) throw new RuntimeException("Allocating java.lang.Class is not allowed");
|
121 |
+
if (Modifier.isAbstract(modifiers)) throw new RuntimeException("Allocating a abstract class");
|
122 |
+
var tsort = classType.getSort();
|
123 |
+
if (tsort == Type.ARRAY) {
|
124 |
+
throw new RuntimeException("Allocating a array type");
|
125 |
+
}
|
126 |
+
if (tsort != Type.OBJECT) {
|
127 |
+
throw new RuntimeException("Allocating a primitive class");
|
128 |
+
}
|
129 |
+
|
130 |
+
ensureClassInitialized();
|
131 |
+
|
132 |
+
return new KuimiObject<>(this);
|
133 |
+
}
|
134 |
+
|
135 |
+
transient KuimiArrays.ArrayClass arrayType;
|
136 |
+
|
137 |
+
public KuimiArrays.ArrayClass arrayType() {
|
138 |
+
if (arrayType != null) return arrayType;
|
139 |
+
synchronized (this) {
|
140 |
+
if (arrayType != null) return arrayType;
|
141 |
+
|
142 |
+
return arrayType = new KuimiArrays.ArrayClass(this);
|
143 |
+
}
|
144 |
+
}
|
145 |
+
|
146 |
+
public boolean isInterface() {
|
147 |
+
return Modifier.isInterface(modifiers);
|
148 |
+
}
|
149 |
+
|
150 |
+
public void ensureClassInitialized() {
|
151 |
+
var cs = classState;
|
152 |
+
if (cs == CLASS_STATE_INITIALIZED) return;
|
153 |
+
if (cs == CLASS_STATE_ERROR) {
|
154 |
+
throw new IllegalStateException("Exception when initialize " + this);
|
155 |
+
}
|
156 |
+
|
157 |
+
synchronized (this) {
|
158 |
+
cs = classState;
|
159 |
+
if (cs == CLASS_STATE_INITIALIZED) return;
|
160 |
+
if (cs == CLASS_STATE_CALLING_CLINIT) return;
|
161 |
+
if (cs == CLASS_STATE_ERROR) {
|
162 |
+
throw new IllegalStateException("Exception when initialize " + this);
|
163 |
+
}
|
164 |
+
if (cs == CLASS_STATE_NOT_INITIALIZED) {
|
165 |
+
classState = CLASS_STATE_CALLING_CLINIT;
|
166 |
+
|
167 |
+
try {
|
168 |
+
if (parent != null) {
|
169 |
+
parent.ensureClassInitialized();
|
170 |
+
}
|
171 |
+
|
172 |
+
var clinit = methodTable.getDeclaredMethods().stream()
|
173 |
+
.filter(it -> Modifier.isStatic(it.getModifiers()))
|
174 |
+
.filter(it -> it.getReturnType().getClassType().getSort() == Type.VOID)
|
175 |
+
.filter(it -> it.getParameters().size() == 0)
|
176 |
+
.filter(it -> it.getMethodName().equals("<clinit>"))
|
177 |
+
.findFirst();
|
178 |
+
|
179 |
+
if (clinit.isPresent()) {
|
180 |
+
clinit.get().resolveMethodHandle().invokeExact(vm, vm.getStackTrace());
|
181 |
+
}
|
182 |
+
|
183 |
+
classState = CLASS_STATE_INITIALIZED;
|
184 |
+
} catch (Throwable throwable) {
|
185 |
+
classState = CLASS_STATE_ERROR;
|
186 |
+
|
187 |
+
if (throwable instanceof RuntimeException) throw (RuntimeException) throwable;
|
188 |
+
if (throwable instanceof Error) throw (Error) throwable;
|
189 |
+
throw new RuntimeException(throwable);
|
190 |
+
}
|
191 |
+
}
|
192 |
+
}
|
193 |
+
}
|
194 |
+
|
195 |
+
|
196 |
+
@Deprecated
|
197 |
+
public static class Internal {
|
198 |
+
public static void attachArrayType(KuimiClass thiz, KuimiArrays.ArrayClass arrType) {
|
199 |
+
thiz.arrayType = arrType;
|
200 |
+
}
|
201 |
+
}
|
202 |
+
}
|
203 |
+
|
src/main/java/com/kiliokuara/kuimivm/KuimiField.java
ADDED
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm;
|
2 |
+
|
3 |
+
import org.objectweb.asm.Type;
|
4 |
+
|
5 |
+
public class KuimiField extends KuimiMember {
|
6 |
+
private final KuimiClass declaredClass;
|
7 |
+
private final String name;
|
8 |
+
private final KuimiClass type;
|
9 |
+
private final int modifiers;
|
10 |
+
|
11 |
+
int slot = -1, objIndex = -1;
|
12 |
+
long offset = -1;
|
13 |
+
|
14 |
+
final long size;
|
15 |
+
|
16 |
+
public KuimiField(KuimiClass declaredClass, int modifiers, String name, KuimiClass type) {
|
17 |
+
this.declaredClass = declaredClass;
|
18 |
+
this.name = name;
|
19 |
+
this.type = type;
|
20 |
+
this.modifiers = modifiers;
|
21 |
+
|
22 |
+
this.size = switch (type.getClassType().getSort()) {
|
23 |
+
case Type.ARRAY, Type.OBJECT -> type.getVm().objectPointerSize();
|
24 |
+
case Type.BOOLEAN, Type.BYTE -> 1;
|
25 |
+
case Type.CHAR -> Character.BYTES;
|
26 |
+
case Type.SHORT -> Short.BYTES;
|
27 |
+
case Type.INT -> Integer.BYTES;
|
28 |
+
case Type.LONG -> Long.BYTES;
|
29 |
+
case Type.DOUBLE -> Double.BYTES;
|
30 |
+
case Type.FLOAT -> Float.BYTES;
|
31 |
+
|
32 |
+
default -> throw new RuntimeException("Assertion error: Cannot detect field type size");
|
33 |
+
};
|
34 |
+
}
|
35 |
+
|
36 |
+
|
37 |
+
public int getModifiers() {
|
38 |
+
return modifiers;
|
39 |
+
}
|
40 |
+
|
41 |
+
public KuimiClass getDeclaredClass() {
|
42 |
+
return declaredClass;
|
43 |
+
}
|
44 |
+
|
45 |
+
public KuimiClass getType() {
|
46 |
+
return type;
|
47 |
+
}
|
48 |
+
|
49 |
+
public String getName() {
|
50 |
+
return name;
|
51 |
+
}
|
52 |
+
|
53 |
+
@Override
|
54 |
+
public String toString() {
|
55 |
+
var sb = new StringBuilder();
|
56 |
+
KuimiMethod.sharedAppendModifiers(modifiers, sb);
|
57 |
+
|
58 |
+
sb.append(declaredClass.getTypeName()).append(".").append(name).append(": ").append(type.getTypeName());
|
59 |
+
return sb.toString();
|
60 |
+
}
|
61 |
+
|
62 |
+
|
63 |
+
public int getFieldSlot() {
|
64 |
+
if (slot < 0) throw new IllegalStateException("Field slot not allocated");
|
65 |
+
return slot;
|
66 |
+
}
|
67 |
+
|
68 |
+
public int getObjectIndex() {
|
69 |
+
if (objIndex < 0) {
|
70 |
+
throw new IllegalStateException("Object index not allocated or field is not a object field");
|
71 |
+
}
|
72 |
+
return objIndex;
|
73 |
+
}
|
74 |
+
|
75 |
+
public long getOffset() {
|
76 |
+
if (offset < 0) {
|
77 |
+
throw new IllegalStateException("Offset not allocated");
|
78 |
+
}
|
79 |
+
return offset;
|
80 |
+
}
|
81 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/KuimiFieldTable.java
ADDED
@@ -0,0 +1,289 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm;
|
2 |
+
|
3 |
+
import org.objectweb.asm.Opcodes;
|
4 |
+
import org.objectweb.asm.Type;
|
5 |
+
|
6 |
+
import java.lang.reflect.Modifier;
|
7 |
+
import java.util.ArrayList;
|
8 |
+
import java.util.Collections;
|
9 |
+
import java.util.List;
|
10 |
+
import java.util.Objects;
|
11 |
+
import java.util.stream.Stream;
|
12 |
+
|
13 |
+
public class KuimiFieldTable extends KuimiMemberTable {
|
14 |
+
private final KuimiClass declaredClass;
|
15 |
+
private final List<KuimiField> staticFields; // merged
|
16 |
+
private final List<KuimiField> objectFields; // merged
|
17 |
+
private final List<KuimiField> declaredFields;
|
18 |
+
private final List<KuimiField> mergedFields;
|
19 |
+
|
20 |
+
long staticFieldsSize, objectFieldsSize;
|
21 |
+
int staticObjectCount, objectCount;
|
22 |
+
|
23 |
+
public KuimiFieldTable(KuimiClass declaredClass) {
|
24 |
+
this.declaredClass = declaredClass;
|
25 |
+
|
26 |
+
this.staticFields = new ArrayList<>();
|
27 |
+
this.objectFields = new ArrayList<>();
|
28 |
+
this.declaredFields = new ArrayList<>();
|
29 |
+
this.mergedFields = new ArrayList<>();
|
30 |
+
}
|
31 |
+
|
32 |
+
|
33 |
+
public void addField(KuimiField field) {
|
34 |
+
Objects.requireNonNull(field, "field");
|
35 |
+
if (field.slot != -1) {
|
36 |
+
throw new IllegalStateException("Field is already attached.");
|
37 |
+
}
|
38 |
+
if (field.getDeclaredClass() != declaredClass) {
|
39 |
+
throw new IllegalStateException("Attaching non-class field");
|
40 |
+
}
|
41 |
+
|
42 |
+
{
|
43 |
+
var cmod = declaredClass.getModifiers();
|
44 |
+
if ((cmod & Opcodes.ACC_INTERFACE) != 0) {
|
45 |
+
var fmod = field.getModifiers();
|
46 |
+
if ((fmod & Opcodes.ACC_STATIC) == 0) {
|
47 |
+
throw new IllegalArgumentException("Attaching a object field to a interface");
|
48 |
+
}
|
49 |
+
}
|
50 |
+
}
|
51 |
+
|
52 |
+
acquireModifyLock();
|
53 |
+
try {
|
54 |
+
if (findDeclaredField(field) != null) {
|
55 |
+
throw new IllegalStateException("Field " + field + " is already attached with same signature");
|
56 |
+
}
|
57 |
+
|
58 |
+
declaredFields.add(field);
|
59 |
+
|
60 |
+
field.slot = -2; // mark already attached
|
61 |
+
} finally {
|
62 |
+
releaseModifyLock();
|
63 |
+
}
|
64 |
+
}
|
65 |
+
|
66 |
+
public void closeTable() {
|
67 |
+
{ // close parent first
|
68 |
+
var sup = declaredClass.getSuperClass();
|
69 |
+
if (sup != null) {
|
70 |
+
sup.getFieldTable().closeTable();
|
71 |
+
}
|
72 |
+
}
|
73 |
+
|
74 |
+
while (true) {
|
75 |
+
var s = status;
|
76 |
+
if (s > 0) {
|
77 |
+
throw new IllegalStateException("This field table is still under editing.");
|
78 |
+
}
|
79 |
+
if (s == 0) {
|
80 |
+
if (STATUS.compareAndSet((KuimiMemberTable) this, 0, -1)) {
|
81 |
+
break;
|
82 |
+
}
|
83 |
+
continue;
|
84 |
+
}
|
85 |
+
return;
|
86 |
+
}
|
87 |
+
|
88 |
+
{
|
89 |
+
var sup = declaredClass.getSuperClass();
|
90 |
+
if (sup != null) {
|
91 |
+
var supft = sup.getFieldTable();
|
92 |
+
staticFieldsSize = supft.staticFieldsSize;
|
93 |
+
objectFieldsSize = supft.objectFieldsSize;
|
94 |
+
|
95 |
+
staticObjectCount = supft.staticObjectCount;
|
96 |
+
objectCount = supft.objectCount;
|
97 |
+
|
98 |
+
staticFields.addAll(supft.staticFields);
|
99 |
+
objectFields.addAll(supft.objectFields);
|
100 |
+
|
101 |
+
mergedFields.addAll(supft.mergedFields);
|
102 |
+
}
|
103 |
+
}
|
104 |
+
|
105 |
+
|
106 |
+
if (declaredClass.getSuperClass() == null) { // java.lang.Object or other no parent class
|
107 |
+
var jlc = declaredClass.getVm().getClassClass();
|
108 |
+
var jlo = declaredClass.getVm().getBaseClass();
|
109 |
+
|
110 |
+
var objFields = Stream.concat(jlc.getFieldTable().declaredFields.stream(), jlo.getFieldTable().declaredFields.stream())
|
111 |
+
.filter(it -> !Modifier.isStatic(it.getModifiers()))
|
112 |
+
.toList();
|
113 |
+
|
114 |
+
long spadding = 0;
|
115 |
+
for (var f : objFields) {
|
116 |
+
spadding = alignUp(spadding, f.size);
|
117 |
+
spadding += f.size;
|
118 |
+
|
119 |
+
|
120 |
+
boolean isObject;
|
121 |
+
{
|
122 |
+
var stype = f.getType().getClassType().getSort();
|
123 |
+
isObject = stype == Type.ARRAY || stype == Type.OBJECT;
|
124 |
+
}
|
125 |
+
|
126 |
+
if (isObject) {
|
127 |
+
staticObjectCount++;
|
128 |
+
}
|
129 |
+
}
|
130 |
+
staticFieldsSize = spadding; // reserved for java.lang.Object & java.lang.Class fields
|
131 |
+
}
|
132 |
+
|
133 |
+
var sfstart = staticFieldsSize;
|
134 |
+
var ofstart = objectFieldsSize;
|
135 |
+
|
136 |
+
var oocount = objectCount;
|
137 |
+
var socount = staticObjectCount;
|
138 |
+
|
139 |
+
for (var f : declaredFields) {
|
140 |
+
var mod = f.getModifiers();
|
141 |
+
var isStatic = Modifier.isStatic(mod);
|
142 |
+
boolean isObject;
|
143 |
+
{
|
144 |
+
var stype = f.getType().getClassType().getSort();
|
145 |
+
isObject = stype == Type.ARRAY || stype == Type.OBJECT;
|
146 |
+
}
|
147 |
+
|
148 |
+
f.slot = mergedFields.size();
|
149 |
+
mergedFields.add(f);
|
150 |
+
|
151 |
+
|
152 |
+
if (isStatic) {
|
153 |
+
staticFields.add(f);
|
154 |
+
|
155 |
+
sfstart = alignUp(sfstart, f.size);
|
156 |
+
f.offset = sfstart;
|
157 |
+
sfstart += f.size;
|
158 |
+
|
159 |
+
if (isObject) {
|
160 |
+
f.objIndex = socount;
|
161 |
+
socount++;
|
162 |
+
}
|
163 |
+
} else {
|
164 |
+
objectFields.add(f);
|
165 |
+
|
166 |
+
ofstart = alignUp(ofstart, f.size);
|
167 |
+
f.offset = ofstart;
|
168 |
+
ofstart += f.size;
|
169 |
+
|
170 |
+
if (isObject) {
|
171 |
+
f.objIndex = oocount;
|
172 |
+
oocount++;
|
173 |
+
}
|
174 |
+
}
|
175 |
+
}
|
176 |
+
|
177 |
+
staticFieldsSize = sfstart;
|
178 |
+
objectFieldsSize = ofstart;
|
179 |
+
|
180 |
+
objectCount = oocount;
|
181 |
+
staticObjectCount = socount;
|
182 |
+
|
183 |
+
{ // inherit java.lang.Class
|
184 |
+
var jc = declaredClass.getVm().getClassClass();
|
185 |
+
jc.getFieldTable().closeTable();
|
186 |
+
}
|
187 |
+
}
|
188 |
+
|
189 |
+
private KuimiField findDeclaredField(KuimiField req) {
|
190 |
+
for (var f : declaredFields) {
|
191 |
+
if (!f.getType().equals(req.getType())) continue;
|
192 |
+
if (!f.getName().equals(req.getName())) continue;
|
193 |
+
|
194 |
+
return f;
|
195 |
+
}
|
196 |
+
return null;
|
197 |
+
}
|
198 |
+
|
199 |
+
|
200 |
+
private static long alignUp(long n, long alignment) {
|
201 |
+
return (n + alignment - 1) & -alignment;
|
202 |
+
}
|
203 |
+
|
204 |
+
@Override
|
205 |
+
public String toString() {
|
206 |
+
var sb = new StringBuilder();
|
207 |
+
sb.append("FieldTable[").append(declaredClass.getTypeName())
|
208 |
+
.append(", status=").append(status)
|
209 |
+
.append(", objectFieldSize=").append(objectFieldsSize)
|
210 |
+
.append(", objectObjectCount=").append(objectCount)
|
211 |
+
.append(", staticFieldSize=").append(staticFieldsSize)
|
212 |
+
.append(", staticObjectCount=").append(staticObjectCount)
|
213 |
+
.append('\n');
|
214 |
+
|
215 |
+
var vp = true;
|
216 |
+
for (var f : mergedFields) {
|
217 |
+
if (vp && f.getDeclaredClass().equals(declaredClass)) {
|
218 |
+
sb.append("|===\n");
|
219 |
+
vp = false;
|
220 |
+
}
|
221 |
+
|
222 |
+
sb.append("| S[").append(f.slot).append("]<").append(f.offset).append("\t..").append(f.offset + f.size).append("\t>{").append(f.size).append("\t}");
|
223 |
+
if (f.objIndex != -1) {
|
224 |
+
sb.append('[').append(f.objIndex).append(']');
|
225 |
+
}
|
226 |
+
sb.append(' ').append(f).append('\n');
|
227 |
+
}
|
228 |
+
return sb.append(']').toString();
|
229 |
+
}
|
230 |
+
|
231 |
+
public List<KuimiField> getDeclaredFields() {
|
232 |
+
closeTable();
|
233 |
+
return Collections.unmodifiableList(declaredFields);
|
234 |
+
}
|
235 |
+
|
236 |
+
public List<KuimiField> getMergedFields() {
|
237 |
+
closeTable();
|
238 |
+
return Collections.unmodifiableList(mergedFields);
|
239 |
+
}
|
240 |
+
|
241 |
+
public List<KuimiField> getObjectFields() {
|
242 |
+
closeTable();
|
243 |
+
return Collections.unmodifiableList(objectFields);
|
244 |
+
}
|
245 |
+
|
246 |
+
public List<KuimiField> getStaticFields() {
|
247 |
+
closeTable();
|
248 |
+
return Collections.unmodifiableList(staticFields);
|
249 |
+
}
|
250 |
+
|
251 |
+
public KuimiClass getDeclaredClass() {
|
252 |
+
return declaredClass;
|
253 |
+
}
|
254 |
+
|
255 |
+
public long getObjectFieldsSize() {
|
256 |
+
closeTable();
|
257 |
+
return objectFieldsSize;
|
258 |
+
}
|
259 |
+
|
260 |
+
public long getStaticFieldsSize() {
|
261 |
+
closeTable();
|
262 |
+
return staticFieldsSize;
|
263 |
+
}
|
264 |
+
|
265 |
+
public KuimiField findField(boolean isStatic, String name) {
|
266 |
+
closeTable();
|
267 |
+
for (var iterator = mergedFields.listIterator(mergedFields.size()); iterator.hasPrevious(); ) {
|
268 |
+
var f = iterator.previous();
|
269 |
+
|
270 |
+
if (Modifier.isStatic(f.getModifiers()) != isStatic) continue;
|
271 |
+
if (!f.getName().equals(name)) continue;
|
272 |
+
return f;
|
273 |
+
}
|
274 |
+
return null;
|
275 |
+
}
|
276 |
+
|
277 |
+
public KuimiField findField(boolean isStatic, String name, KuimiClass type) {
|
278 |
+
closeTable();
|
279 |
+
for (var iterator = mergedFields.listIterator(mergedFields.size()); iterator.hasPrevious(); ) {
|
280 |
+
var f = iterator.previous();
|
281 |
+
|
282 |
+
if (Modifier.isStatic(f.getModifiers()) != isStatic) continue;
|
283 |
+
if (!f.getType().equals(type)) continue;
|
284 |
+
if (!f.getName().equals(name)) continue;
|
285 |
+
return f;
|
286 |
+
}
|
287 |
+
return null;
|
288 |
+
}
|
289 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/KuimiMember.java
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm;
|
2 |
+
|
3 |
+
import com.kiliokuara.kuimivm.attributes.AttributeMap;
|
4 |
+
|
5 |
+
public abstract class KuimiMember {
|
6 |
+
public abstract KuimiClass getDeclaredClass();
|
7 |
+
|
8 |
+
private volatile AttributeMap<? extends KuimiMember> attributeMap;
|
9 |
+
|
10 |
+
public AttributeMap<? extends KuimiMember> getAttributeMap() {
|
11 |
+
if (attributeMap != null) return attributeMap;
|
12 |
+
|
13 |
+
synchronized (this) {
|
14 |
+
if (attributeMap != null) return attributeMap;
|
15 |
+
return attributeMap = new AttributeMap<>(this);
|
16 |
+
}
|
17 |
+
}
|
18 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/KuimiMemberTable.java
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm;
|
2 |
+
|
3 |
+
import java.lang.invoke.ConstantBootstraps;
|
4 |
+
import java.lang.invoke.MethodHandles;
|
5 |
+
import java.lang.invoke.VarHandle;
|
6 |
+
|
7 |
+
public class KuimiMemberTable {
|
8 |
+
protected volatile int status;
|
9 |
+
static final VarHandle STATUS = ConstantBootstraps.fieldVarHandle(
|
10 |
+
MethodHandles.lookup(), "status", VarHandle.class,
|
11 |
+
KuimiMemberTable.class, int.class
|
12 |
+
).withInvokeExactBehavior();
|
13 |
+
|
14 |
+
|
15 |
+
|
16 |
+
public boolean isImmutable() {
|
17 |
+
return status < 0;
|
18 |
+
}
|
19 |
+
|
20 |
+
protected final void acquireModifyLock() {
|
21 |
+
while (true) {
|
22 |
+
var s = status;
|
23 |
+
if (s < 0) {
|
24 |
+
throw new IllegalStateException("This method table is immutable.");
|
25 |
+
}
|
26 |
+
if (STATUS.compareAndSet(this, s, s + 1)) return;
|
27 |
+
}
|
28 |
+
}
|
29 |
+
|
30 |
+
protected final void releaseModifyLock() {
|
31 |
+
while (true) {
|
32 |
+
var s = status;
|
33 |
+
if (s < 1) {
|
34 |
+
throw new IllegalStateException("Internal exception: bad status, no lock can be released.");
|
35 |
+
}
|
36 |
+
if (STATUS.compareAndSet(this, s, s - 1)) return;
|
37 |
+
}
|
38 |
+
}
|
39 |
+
|
40 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/KuimiMethod.java
ADDED
@@ -0,0 +1,396 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm;
|
2 |
+
|
3 |
+
import com.kiliokuara.kuimivm.execute.StackTrace;
|
4 |
+
import com.kiliokuara.kuimivm.vaarg.KuimiMethodInvoker;
|
5 |
+
import com.kiliokuara.kuimivm.vaarg.ShortenType;
|
6 |
+
import org.objectweb.asm.Opcodes;
|
7 |
+
import org.objectweb.asm.Type;
|
8 |
+
|
9 |
+
import java.lang.invoke.MethodHandle;
|
10 |
+
import java.lang.invoke.MethodHandles;
|
11 |
+
import java.lang.invoke.MethodType;
|
12 |
+
import java.lang.reflect.Modifier;
|
13 |
+
import java.util.ArrayList;
|
14 |
+
import java.util.List;
|
15 |
+
import java.util.Objects;
|
16 |
+
import java.util.stream.Collectors;
|
17 |
+
import java.util.stream.Stream;
|
18 |
+
|
19 |
+
public class KuimiMethod extends KuimiMember {
|
20 |
+
private final KuimiClass declaredClass;
|
21 |
+
private final int modifiers;
|
22 |
+
private final String methodName;
|
23 |
+
private final KuimiClass returnType;
|
24 |
+
private final List<KuimiClass> params;
|
25 |
+
private final List<ShortenType> paramsShorten;
|
26 |
+
private final List<ShortenType> fullCallShorten;
|
27 |
+
|
28 |
+
int methodSlot = -1;
|
29 |
+
|
30 |
+
|
31 |
+
public KuimiMethod(
|
32 |
+
KuimiClass declaredClass,
|
33 |
+
int modifiers,
|
34 |
+
String methodName,
|
35 |
+
KuimiClass returnType,
|
36 |
+
List<KuimiClass> params
|
37 |
+
) {
|
38 |
+
this.declaredClass = declaredClass;
|
39 |
+
this.modifiers = modifiers;
|
40 |
+
this.methodName = methodName;
|
41 |
+
this.returnType = returnType;
|
42 |
+
this.params = params == null ? List.of() : params;
|
43 |
+
|
44 |
+
paramsShorten = this.params.stream().map(it -> ShortenType.from(it.getClassType())).toList();
|
45 |
+
if (Modifier.isStatic(modifiers)) {
|
46 |
+
fullCallShorten = paramsShorten;
|
47 |
+
} else {
|
48 |
+
fullCallShorten = Stream.concat(Stream.of(ShortenType.REF), paramsShorten.stream()).toList();
|
49 |
+
}
|
50 |
+
}
|
51 |
+
|
52 |
+
|
53 |
+
public KuimiClass getDeclaredClass() {
|
54 |
+
return declaredClass;
|
55 |
+
}
|
56 |
+
|
57 |
+
public KuimiClass getAttachedClass() {
|
58 |
+
return declaredClass;
|
59 |
+
}
|
60 |
+
|
61 |
+
public int getModifiers() {
|
62 |
+
return modifiers;
|
63 |
+
}
|
64 |
+
|
65 |
+
public KuimiClass getReturnType() {
|
66 |
+
return returnType;
|
67 |
+
}
|
68 |
+
|
69 |
+
public List<KuimiClass> getParameters() {
|
70 |
+
return params;
|
71 |
+
}
|
72 |
+
|
73 |
+
public List<ShortenType> getParamsShorten() {
|
74 |
+
return paramsShorten;
|
75 |
+
}
|
76 |
+
|
77 |
+
public String getMethodName() {
|
78 |
+
return methodName;
|
79 |
+
}
|
80 |
+
|
81 |
+
@Override
|
82 |
+
public String toString() {
|
83 |
+
var sb = new StringBuilder();
|
84 |
+
sharedAppendModifiers(modifiers, sb);
|
85 |
+
sb.append(declaredClass.getTypeName()).append('.');
|
86 |
+
sb.append(methodName);
|
87 |
+
sb.append(params.stream()
|
88 |
+
.map(KuimiClass::getTypeName)
|
89 |
+
.collect(Collectors.joining(", ", "(", ")"))
|
90 |
+
);
|
91 |
+
sb.append(": ").append(returnType.getTypeName());
|
92 |
+
|
93 |
+
return sb.toString();
|
94 |
+
}
|
95 |
+
|
96 |
+
static void sharedAppendModifiers(int modifiers, StringBuilder sb) {
|
97 |
+
if (Modifier.isPublic(modifiers)) {
|
98 |
+
sb.append("public ");
|
99 |
+
}
|
100 |
+
if (Modifier.isPrivate(modifiers)) {
|
101 |
+
sb.append("private ");
|
102 |
+
}
|
103 |
+
if (Modifier.isProtected(modifiers)) {
|
104 |
+
sb.append("protected ");
|
105 |
+
}
|
106 |
+
if (Modifier.isStatic(modifiers)) {
|
107 |
+
sb.append("static ");
|
108 |
+
}
|
109 |
+
if (Modifier.isNative(modifiers)) {
|
110 |
+
sb.append("native ");
|
111 |
+
}
|
112 |
+
if (Modifier.isSynchronized(modifiers)) {
|
113 |
+
sb.append("synchronized ");
|
114 |
+
}
|
115 |
+
if (Modifier.isAbstract(modifiers)) {
|
116 |
+
sb.append("abstract ");
|
117 |
+
}
|
118 |
+
if (Modifier.isVolatile(modifiers)) {
|
119 |
+
sb.append("volatile ");
|
120 |
+
}
|
121 |
+
if (Modifier.isFinal(modifiers)) {
|
122 |
+
sb.append("final ");
|
123 |
+
}
|
124 |
+
}
|
125 |
+
|
126 |
+
|
127 |
+
@Override
|
128 |
+
public boolean equals(Object obj) {
|
129 |
+
if (obj == null) return false;
|
130 |
+
if (obj == this) return true;
|
131 |
+
if (!(obj instanceof KuimiMethod other)) return false;
|
132 |
+
if (methodSlot != -1 && other.methodSlot != -1) {
|
133 |
+
if (methodSlot != other.methodSlot) return false;
|
134 |
+
}
|
135 |
+
if (modifiers != other.modifiers) return false;
|
136 |
+
if (!declaredClass.equals(other.declaredClass)) return false;
|
137 |
+
if (!returnType.equals(other.returnType)) return false;
|
138 |
+
if (!methodName.equals(other.methodName)) return false;
|
139 |
+
if (!params.equals(other.params)) return false;
|
140 |
+
|
141 |
+
return true;
|
142 |
+
}
|
143 |
+
|
144 |
+
@Override
|
145 |
+
public int hashCode() {
|
146 |
+
return methodName.hashCode() & (modifiers ^ returnType.hashCode()) + params.hashCode() ^ declaredClass.hashCode();
|
147 |
+
}
|
148 |
+
|
149 |
+
public int getMethodSlot() {
|
150 |
+
if (methodSlot < 0) throw new IllegalStateException("Method slot not allocated");
|
151 |
+
return methodSlot;
|
152 |
+
}
|
153 |
+
|
154 |
+
// region Execute
|
155 |
+
private transient volatile MethodHandle targetMH;
|
156 |
+
private transient volatile MethodType targetMHType;
|
157 |
+
private transient volatile KuimiMethodInvoker invoker;
|
158 |
+
|
159 |
+
public MethodHandle resolveMethodHandle() {
|
160 |
+
if (targetMH != null) return targetMH;
|
161 |
+
|
162 |
+
var mhType = flattenMethodHandleType();
|
163 |
+
if (getClass() != KuimiMethod.class) {
|
164 |
+
var lk = MethodHandles.lookup();
|
165 |
+
try {
|
166 |
+
lk = MethodHandles.privateLookupIn(getClass(), lk);
|
167 |
+
} catch (Exception ignored) {
|
168 |
+
}
|
169 |
+
|
170 |
+
try {
|
171 |
+
return targetMH = Internal.stackEnter(this, lk.findVirtual(getClass(), "execute", mhType).bindTo(this));
|
172 |
+
} catch (Exception e) {
|
173 |
+
return targetMH = Internal.notImplemented(this, mhType, e);
|
174 |
+
}
|
175 |
+
}
|
176 |
+
|
177 |
+
return targetMH = Internal.notImplemented(this, mhType, null);
|
178 |
+
}
|
179 |
+
|
180 |
+
public final KuimiMethod attachImplementation(KuimiMethod method) {
|
181 |
+
Objects.requireNonNull(method, "method");
|
182 |
+
if (getClass() != KuimiMethod.class) {
|
183 |
+
throw new IllegalStateException("Cannot attach a method handle to " + getClass());
|
184 |
+
}
|
185 |
+
if (!method.flattenMethodHandleType().equals(flattenMethodHandleType())) {
|
186 |
+
throw new IllegalArgumentException("Method type not match: " + method.flattenMethodHandleType() + " -> " + flattenMethodHandleType());
|
187 |
+
}
|
188 |
+
if (targetMH != null) {
|
189 |
+
throw new IllegalStateException("This method " + this + " is already attached.");
|
190 |
+
}
|
191 |
+
targetMH = method.resolveMethodHandle();
|
192 |
+
return this;
|
193 |
+
}
|
194 |
+
|
195 |
+
public final KuimiMethod attachImplementation(MethodHandle methodHandle) {
|
196 |
+
Objects.requireNonNull(methodHandle, "methodHandle");
|
197 |
+
if (getClass() != KuimiMethod.class) {
|
198 |
+
throw new IllegalStateException("Cannot attach a method handle to " + getClass());
|
199 |
+
}
|
200 |
+
if (!methodHandle.type().equals(flattenMethodHandleType())) {
|
201 |
+
throw new IllegalArgumentException("Method type not match: " + methodHandle.type() + " -> " + flattenMethodHandleType());
|
202 |
+
}
|
203 |
+
if (targetMH != null) {
|
204 |
+
throw new IllegalStateException("This method " + this + " is already attached.");
|
205 |
+
}
|
206 |
+
targetMH = Internal.stackEnter(this, methodHandle);
|
207 |
+
return this;
|
208 |
+
}
|
209 |
+
|
210 |
+
public final MethodType flattenMethodHandleType() {
|
211 |
+
if (targetMHType != null) {
|
212 |
+
return targetMHType;
|
213 |
+
}
|
214 |
+
|
215 |
+
var retType = switch (returnType.getClassType().getSort()) {
|
216 |
+
case Type.BYTE -> byte.class;
|
217 |
+
case Type.CHAR -> char.class;
|
218 |
+
case Type.SHORT -> short.class;
|
219 |
+
case Type.INT -> int.class;
|
220 |
+
case Type.LONG -> long.class;
|
221 |
+
case Type.FLOAT -> float.class;
|
222 |
+
case Type.DOUBLE -> double.class;
|
223 |
+
case Type.BOOLEAN -> boolean.class;
|
224 |
+
case Type.VOID -> void.class;
|
225 |
+
case Type.ARRAY, Type.OBJECT -> KuimiObject.class;
|
226 |
+
default -> throw new RuntimeException("Cannot confirm return type");
|
227 |
+
};
|
228 |
+
|
229 |
+
var args = new ArrayList<Class<?>>();
|
230 |
+
args.add(KuimiVM.class);
|
231 |
+
args.add(StackTrace.class);
|
232 |
+
|
233 |
+
if ((modifiers & Opcodes.ACC_STATIC) == 0) {
|
234 |
+
args.add(KuimiObject.class); // the this object
|
235 |
+
}
|
236 |
+
|
237 |
+
for (var param : params) {
|
238 |
+
args.add(switch (param.getClassType().getSort()) {
|
239 |
+
case Type.BYTE -> byte.class;
|
240 |
+
case Type.CHAR -> char.class;
|
241 |
+
case Type.SHORT -> short.class;
|
242 |
+
case Type.INT -> int.class;
|
243 |
+
case Type.LONG -> long.class;
|
244 |
+
case Type.FLOAT -> float.class;
|
245 |
+
case Type.DOUBLE -> double.class;
|
246 |
+
case Type.BOOLEAN -> boolean.class;
|
247 |
+
case Type.ARRAY, Type.OBJECT -> KuimiObject.class;
|
248 |
+
default -> throw new RuntimeException("Cannot confirm param type: " + param);
|
249 |
+
});
|
250 |
+
}
|
251 |
+
|
252 |
+
return targetMHType = MethodType.methodType(retType, args);
|
253 |
+
}
|
254 |
+
|
255 |
+
public KuimiMethodInvoker getInvoker() {
|
256 |
+
if (invoker != null) return invoker;
|
257 |
+
|
258 |
+
if (Modifier.isStatic(modifiers)) {
|
259 |
+
return invoker = KuimiMethodInvoker.invoker(returnType, params);
|
260 |
+
} else {
|
261 |
+
var prm = new ArrayList<KuimiClass>();
|
262 |
+
prm.add(declaredClass.getVm().getBaseClass());
|
263 |
+
prm.addAll(params);
|
264 |
+
return invoker = KuimiMethodInvoker.invoker(returnType, prm);
|
265 |
+
}
|
266 |
+
}
|
267 |
+
|
268 |
+
|
269 |
+
public boolean isInterfaceMethod() {
|
270 |
+
return declaredClass.isInterface();
|
271 |
+
}
|
272 |
+
|
273 |
+
public boolean isMergedInterface() {
|
274 |
+
return false;
|
275 |
+
}
|
276 |
+
|
277 |
+
public List<ShortenType> getFullCallShorten() {
|
278 |
+
return fullCallShorten;
|
279 |
+
}
|
280 |
+
// endregion
|
281 |
+
|
282 |
+
|
283 |
+
static class Internal {
|
284 |
+
private static final MethodHandle NEW_ERROR;
|
285 |
+
private static final MethodHandle NEW_ERROR_WITH_REASON;
|
286 |
+
|
287 |
+
|
288 |
+
private static final MethodHandle ENTER_STACK;
|
289 |
+
private static final MethodHandle LEAVE_STACK;
|
290 |
+
|
291 |
+
|
292 |
+
// out: (KuimiMethod, Throwable, <V>, KVM, StackTrace)<V>
|
293 |
+
private static final ClassValue<MethodHandle> RTYPE_ID_CLEANUP_CACHE = new ClassValue<>() {
|
294 |
+
@Override
|
295 |
+
protected MethodHandle computeValue(Class<?> rtype) {
|
296 |
+
if (rtype == void.class) {
|
297 |
+
return LEAVE_STACK;
|
298 |
+
} else {
|
299 |
+
MethodHandle cleanup = MethodHandles.identity(rtype);
|
300 |
+
|
301 |
+
cleanup = MethodHandles.collectArguments(cleanup, 1, LEAVE_STACK);
|
302 |
+
// now: (V, KuimiMethod, Throwable, KVM, StackTrace)V
|
303 |
+
|
304 |
+
// target: (KuimiMethod, Throwable, V, KVM, StackTrace)V
|
305 |
+
return MethodHandles.permuteArguments(cleanup,
|
306 |
+
MethodType.methodType(rtype, KuimiMethod.class, Throwable.class, rtype, KuimiVM.class, StackTrace.class),
|
307 |
+
2, 0, 1, 3, 4
|
308 |
+
);
|
309 |
+
}
|
310 |
+
}
|
311 |
+
};
|
312 |
+
|
313 |
+
static {
|
314 |
+
var lk = MethodHandles.lookup();
|
315 |
+
try {
|
316 |
+
NEW_ERROR = lk.findConstructor(InternalError.class, MethodType.methodType(void.class, String.class));
|
317 |
+
NEW_ERROR_WITH_REASON = lk.findConstructor(InternalError.class, MethodType.methodType(void.class, String.class, Throwable.class));
|
318 |
+
|
319 |
+
ENTER_STACK = lk.findStatic(Internal.class, "enterStack", MethodType.methodType(void.class, KuimiMethod.class, KuimiVM.class, StackTrace.class));
|
320 |
+
LEAVE_STACK = lk.findStatic(Internal.class, "leaveStack", MethodType.methodType(void.class, KuimiMethod.class, Throwable.class, KuimiVM.class, StackTrace.class));
|
321 |
+
} catch (NoSuchMethodException | IllegalAccessException e) {
|
322 |
+
throw new ExceptionInInitializerError(e);
|
323 |
+
}
|
324 |
+
}
|
325 |
+
|
326 |
+
private static void enterStack(KuimiMethod method, KuimiVM vm, StackTrace stackTrace) {
|
327 |
+
// new Throwable("enter stack: " + method + " - " + stackTrace).printStackTrace(System.out);
|
328 |
+
if (stackTrace != null) {
|
329 |
+
stackTrace.enter(method);
|
330 |
+
stackTrace.pushFrame();
|
331 |
+
}
|
332 |
+
}
|
333 |
+
|
334 |
+
private static void leaveStack(KuimiMethod method, Throwable throwable, KuimiVM vm, StackTrace stackTrace) {
|
335 |
+
// new Throwable("leave stack: " + method + " - " + stackTrace).printStackTrace(System.out);
|
336 |
+
if (stackTrace != null) {
|
337 |
+
stackTrace.leave(method);
|
338 |
+
}
|
339 |
+
}
|
340 |
+
|
341 |
+
public static MethodHandle notImplemented(KuimiMethod kuimiMethod, MethodType mhType, Throwable exception) {
|
342 |
+
var msg = "Exception when binding implementation to " + kuimiMethod;
|
343 |
+
MethodHandle emh;
|
344 |
+
if (exception == null) {
|
345 |
+
emh = NEW_ERROR.bindTo(msg);
|
346 |
+
} else {
|
347 |
+
emh = MethodHandles.insertArguments(NEW_ERROR_WITH_REASON, 0, msg, exception);
|
348 |
+
}
|
349 |
+
|
350 |
+
var terr = MethodHandles.throwException(mhType.returnType(), InternalError.class);
|
351 |
+
var empThrow = MethodHandles.collectArguments(terr, 0, emh);
|
352 |
+
|
353 |
+
|
354 |
+
return MethodHandles.dropArguments(empThrow, 0, mhType.parameterArray());
|
355 |
+
}
|
356 |
+
|
357 |
+
public static MethodHandle stackEnter(KuimiMethod kuimiMethod, MethodHandle execute) {
|
358 |
+
return MethodHandles.tryFinally(
|
359 |
+
MethodHandles.foldArguments(execute, ENTER_STACK.bindTo(kuimiMethod)),
|
360 |
+
RTYPE_ID_CLEANUP_CACHE.get(execute.type().returnType()).bindTo(kuimiMethod)
|
361 |
+
);
|
362 |
+
}
|
363 |
+
|
364 |
+
static class DelegatedMethod extends KuimiMethod {
|
365 |
+
|
366 |
+
final KuimiMethod original;
|
367 |
+
private final KuimiClass attachedClass;
|
368 |
+
|
369 |
+
public DelegatedMethod(KuimiMethod original, KuimiClass attachedClass) {
|
370 |
+
super(original.declaredClass, original.modifiers, original.methodName, original.returnType, original.params);
|
371 |
+
this.original = original;
|
372 |
+
this.attachedClass = attachedClass;
|
373 |
+
}
|
374 |
+
|
375 |
+
@Override
|
376 |
+
public MethodHandle resolveMethodHandle() {
|
377 |
+
return original.resolveMethodHandle();
|
378 |
+
}
|
379 |
+
|
380 |
+
@Override
|
381 |
+
public KuimiClass getAttachedClass() {
|
382 |
+
return attachedClass;
|
383 |
+
}
|
384 |
+
|
385 |
+
@Override
|
386 |
+
public KuimiMethodInvoker getInvoker() {
|
387 |
+
return original.getInvoker();
|
388 |
+
}
|
389 |
+
|
390 |
+
@Override
|
391 |
+
public boolean isMergedInterface() {
|
392 |
+
return true;
|
393 |
+
}
|
394 |
+
}
|
395 |
+
}
|
396 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/KuimiMethodTable.java
ADDED
@@ -0,0 +1,479 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm;
|
2 |
+
|
3 |
+
import org.objectweb.asm.Opcodes;
|
4 |
+
import org.objectweb.asm.Type;
|
5 |
+
|
6 |
+
import java.lang.reflect.Modifier;
|
7 |
+
import java.util.*;
|
8 |
+
|
9 |
+
public class KuimiMethodTable extends KuimiMemberTable {
|
10 |
+
private final KuimiClass declaredClass;
|
11 |
+
|
12 |
+
private final List<KuimiMethod> mergedMethods;
|
13 |
+
private final List<KuimiMethod> declaredMethods;
|
14 |
+
private final Map<KuimiClass, InterfaceMethodTable> interfaceMethodTableMap;
|
15 |
+
|
16 |
+
public KuimiMethodTable(KuimiClass declaredClass) {
|
17 |
+
this.declaredClass = declaredClass;
|
18 |
+
mergedMethods = new ArrayList<>();
|
19 |
+
declaredMethods = new ArrayList<>();
|
20 |
+
interfaceMethodTableMap = new IdentityHashMap<>();
|
21 |
+
}
|
22 |
+
|
23 |
+
public void closeTable() {
|
24 |
+
{ // close parent & itfs' tables first
|
25 |
+
var sup = declaredClass.getSuperClass();
|
26 |
+
if (sup != null) sup.getMethodTable().closeTable();
|
27 |
+
|
28 |
+
for (var itf : declaredClass.getInterfaces()) {
|
29 |
+
itf.getMethodTable().closeTable();
|
30 |
+
}
|
31 |
+
}
|
32 |
+
|
33 |
+
while (true) {
|
34 |
+
var s = status;
|
35 |
+
if (s > 0) {
|
36 |
+
throw new IllegalStateException("This method table is still under editing.");
|
37 |
+
}
|
38 |
+
if (s == 0) {
|
39 |
+
if (STATUS.compareAndSet((KuimiMemberTable) this, 0, -1)) break;
|
40 |
+
continue;
|
41 |
+
}
|
42 |
+
return;
|
43 |
+
}
|
44 |
+
|
45 |
+
var pendingMethods = new ArrayList<>(declaredMethods);
|
46 |
+
|
47 |
+
{ // inherit super class methods
|
48 |
+
var sup = declaredClass.getSuperClass();
|
49 |
+
if (sup != null) {
|
50 |
+
mergedMethods.addAll(sup.getMethodTable().mergedMethods);
|
51 |
+
}
|
52 |
+
}
|
53 |
+
|
54 |
+
|
55 |
+
final var Helper = new Object() {
|
56 |
+
String pkg(Type ctype) {
|
57 |
+
var cname = ctype.getClassName();
|
58 |
+
var idx = cname.lastIndexOf('.');
|
59 |
+
if (idx == -1) return "";
|
60 |
+
return cname.substring(0, idx);
|
61 |
+
}
|
62 |
+
|
63 |
+
@SuppressWarnings("StatementWithEmptyBody")
|
64 |
+
KuimiMethod tryFindOverride(KuimiMethod req) {
|
65 |
+
for (var met : mergedMethods) {
|
66 |
+
var modifiers = met.getModifiers();
|
67 |
+
|
68 |
+
if (Modifier.isPrivate(modifiers)) continue;
|
69 |
+
if (Modifier.isStatic(modifiers)) continue;
|
70 |
+
|
71 |
+
// modifier check
|
72 |
+
if (Modifier.isPublic(modifiers)) {
|
73 |
+
// noop, ok
|
74 |
+
} else if (Modifier.isProtected(modifiers)) {
|
75 |
+
// noop, ok
|
76 |
+
} else { // package private
|
77 |
+
if (!Objects.equals(
|
78 |
+
declaredClass.getClassLoader(),
|
79 |
+
met.getDeclaredClass().getClassLoader()
|
80 |
+
)) { // class loader not match, skip
|
81 |
+
continue;
|
82 |
+
}
|
83 |
+
|
84 |
+
if (!Objects.equals(
|
85 |
+
pkg(declaredClass.getClassType()),
|
86 |
+
pkg(met.getDeclaredClass().getClassType())
|
87 |
+
)) { // package not match, skip
|
88 |
+
continue;
|
89 |
+
}
|
90 |
+
}
|
91 |
+
|
92 |
+
|
93 |
+
if (!isSignMatch(req, met)) continue;
|
94 |
+
|
95 |
+
return met;
|
96 |
+
}
|
97 |
+
return null;
|
98 |
+
}
|
99 |
+
|
100 |
+
boolean isSignMatch(KuimiMethod req, KuimiMethod met) {
|
101 |
+
if (!req.getReturnType().equals(met.getReturnType())) return false;
|
102 |
+
if (!req.getParameters().equals(met.getParameters())) return false;
|
103 |
+
if (!req.getMethodName().equals(met.getMethodName())) return false;
|
104 |
+
|
105 |
+
return true;
|
106 |
+
}
|
107 |
+
|
108 |
+
KuimiMethod findPublic(KuimiMethod req) {
|
109 |
+
for (var met : mergedMethods) {
|
110 |
+
var mod = met.getModifiers();
|
111 |
+
if (Modifier.isStatic(mod)) continue;
|
112 |
+
if (!Modifier.isPublic(mod)) continue;
|
113 |
+
|
114 |
+
if (!isSignMatch(req, met)) continue;
|
115 |
+
|
116 |
+
return met;
|
117 |
+
}
|
118 |
+
return null;
|
119 |
+
}
|
120 |
+
};
|
121 |
+
|
122 |
+
{ // step 1. Direct inherit static/private/<init> methods
|
123 |
+
pendingMethods.removeIf(it -> {
|
124 |
+
|
125 |
+
check:
|
126 |
+
{
|
127 |
+
var mod = it.getModifiers();
|
128 |
+
if (Modifier.isStatic(mod)) break check;
|
129 |
+
if (Modifier.isPrivate(mod)) break check;
|
130 |
+
|
131 |
+
if ("<init>".equals(it.getMethodName())) break check;
|
132 |
+
|
133 |
+
return false;
|
134 |
+
}
|
135 |
+
|
136 |
+
it.methodSlot = mergedMethods.size();
|
137 |
+
mergedMethods.add(it);
|
138 |
+
return true;
|
139 |
+
});
|
140 |
+
}
|
141 |
+
|
142 |
+
{ // step 2. try override methods
|
143 |
+
pendingMethods.removeIf(it -> {
|
144 |
+
var theOverride = Helper.tryFindOverride(it);
|
145 |
+
if (theOverride != null) {
|
146 |
+
if (Modifier.isFinal(theOverride.getModifiers())) {
|
147 |
+
throw new IllegalStateException("Method " + it + " trying overriding a final method: " + theOverride);
|
148 |
+
}
|
149 |
+
|
150 |
+
it.methodSlot = theOverride.methodSlot;
|
151 |
+
mergedMethods.set(theOverride.methodSlot, it);
|
152 |
+
return true;
|
153 |
+
}
|
154 |
+
return false;
|
155 |
+
});
|
156 |
+
}
|
157 |
+
|
158 |
+
{ // step 3. merge pending methods
|
159 |
+
pendingMethods.removeIf(it -> {
|
160 |
+
it.methodSlot = mergedMethods.size();
|
161 |
+
mergedMethods.add(it);
|
162 |
+
|
163 |
+
return true;
|
164 |
+
});
|
165 |
+
}
|
166 |
+
|
167 |
+
// step 4. merge interfaces
|
168 |
+
if (!declaredClass.getInterfaces().isEmpty()) {
|
169 |
+
for (var itfx : declaredClass.getInterfaces()) {
|
170 |
+
pendingMethods.addAll(itfx.getMethodTable().getMergedMethods());
|
171 |
+
}
|
172 |
+
|
173 |
+
// drop static/private methods (not used in itf table)
|
174 |
+
pendingMethods.removeIf(it -> Modifier.isStatic(it.getModifiers()) || Modifier.isPrivate(it.getModifiers()));
|
175 |
+
|
176 |
+
// drop already exists methods
|
177 |
+
pendingMethods.removeIf(it -> Helper.findPublic(it) != null);
|
178 |
+
|
179 |
+
{ // drop empty method if possible
|
180 |
+
var mirror = new ArrayList<>(pendingMethods);
|
181 |
+
pendingMethods.removeIf(it -> {
|
182 |
+
if ((it.getModifiers() & Opcodes.ACC_ABSTRACT) != 0) {
|
183 |
+
|
184 |
+
for (var met : mirror) {
|
185 |
+
if ((it.getModifiers() & Opcodes.ACC_ABSTRACT) != 0) continue;
|
186 |
+
if (!Helper.isSignMatch(it, met)) continue;
|
187 |
+
|
188 |
+
return true;
|
189 |
+
}
|
190 |
+
|
191 |
+
}
|
192 |
+
return false;
|
193 |
+
});
|
194 |
+
}
|
195 |
+
|
196 |
+
{ // drop duplicated empty method
|
197 |
+
topScan:
|
198 |
+
while (true) {
|
199 |
+
//var mirror = new ArrayList<>(pendingMethods);
|
200 |
+
|
201 |
+
for (var iterator = pendingMethods.iterator(); iterator.hasNext(); ) {
|
202 |
+
var it = iterator.next();
|
203 |
+
|
204 |
+
if ((it.getModifiers() & Opcodes.ACC_ABSTRACT) == 0) continue;
|
205 |
+
|
206 |
+
|
207 |
+
for (var met : pendingMethods) {
|
208 |
+
if (met == it) continue;
|
209 |
+
|
210 |
+
if ((met.getModifiers() & Opcodes.ACC_ABSTRACT) == 0) continue;
|
211 |
+
if (!Helper.isSignMatch(it, met)) continue;
|
212 |
+
|
213 |
+
iterator.remove();
|
214 |
+
continue topScan;
|
215 |
+
}
|
216 |
+
|
217 |
+
}
|
218 |
+
|
219 |
+
break;
|
220 |
+
}
|
221 |
+
}
|
222 |
+
|
223 |
+
{ // check duplicated method not exists
|
224 |
+
for (var it : pendingMethods) {
|
225 |
+
for (var met : pendingMethods) {
|
226 |
+
if (met == it) continue;
|
227 |
+
|
228 |
+
check:
|
229 |
+
{
|
230 |
+
if (Helper.isSignMatch(met, it)) break check;
|
231 |
+
|
232 |
+
continue;
|
233 |
+
}
|
234 |
+
|
235 |
+
throw new IllegalStateException("Duplicated interface define method found: " + met + ", " + it);
|
236 |
+
}
|
237 |
+
}
|
238 |
+
}
|
239 |
+
|
240 |
+
|
241 |
+
for (var met : pendingMethods) {
|
242 |
+
var copied = new KuimiMethod.Internal.DelegatedMethod(met, declaredClass);
|
243 |
+
copied.methodSlot = mergedMethods.size();
|
244 |
+
mergedMethods.add(copied);
|
245 |
+
}
|
246 |
+
|
247 |
+
}
|
248 |
+
|
249 |
+
// step 5. recalculate interface method table
|
250 |
+
if (declaredClass.getInterfaces().isEmpty() && (declaredClass.getModifiers() & Opcodes.ACC_INTERFACE) == 0) {
|
251 |
+
|
252 |
+
var sup = declaredClass.getSuperClass();
|
253 |
+
if (sup != null) {
|
254 |
+
interfaceMethodTableMap.putAll(sup.getMethodTable().interfaceMethodTableMap);
|
255 |
+
}
|
256 |
+
} else {
|
257 |
+
var allItfs = new HashSet<KuimiClass>();
|
258 |
+
{
|
259 |
+
var sup = declaredClass.getSuperClass();
|
260 |
+
if (sup != null) {
|
261 |
+
allItfs.addAll(sup.getMethodTable().interfaceMethodTableMap.keySet());
|
262 |
+
}
|
263 |
+
}
|
264 |
+
allItfs.addAll(declaredClass.getInterfaces());
|
265 |
+
for (var itfx : declaredClass.getInterfaces()) {
|
266 |
+
allItfs.addAll(itfx.getMethodTable().interfaceMethodTableMap.keySet());
|
267 |
+
}
|
268 |
+
|
269 |
+
if ((declaredClass.getModifiers() & Opcodes.ACC_INTERFACE) != 0) {
|
270 |
+
allItfs.add(declaredClass);
|
271 |
+
}
|
272 |
+
|
273 |
+
for (var itf : allItfs) {
|
274 |
+
var methods = itf.getMethodTable().getMergedMethods();
|
275 |
+
var slots = new int[methods.size()];
|
276 |
+
for (int i = 0, methodsSize = methods.size(); i < methodsSize; i++) {
|
277 |
+
var itfMet = methods.get(i);
|
278 |
+
|
279 |
+
if (Modifier.isPrivate(itfMet.getModifiers())) continue;
|
280 |
+
if (Modifier.isStatic(itfMet.getModifiers())) continue;
|
281 |
+
|
282 |
+
var mtx = Helper.findPublic(itfMet);
|
283 |
+
if (mtx == null) {
|
284 |
+
throw new IllegalStateException("Internal exception: " + itfMet + " lost. request itf: " + itf);
|
285 |
+
}
|
286 |
+
slots[i] = mtx.methodSlot;
|
287 |
+
}
|
288 |
+
|
289 |
+
interfaceMethodTableMap.put(itf, new InterfaceMethodTable(itf, slots));
|
290 |
+
}
|
291 |
+
}
|
292 |
+
|
293 |
+
}
|
294 |
+
|
295 |
+
public KuimiMethod addMethod(KuimiMethod kuimiMethod) {
|
296 |
+
Objects.requireNonNull(kuimiMethod);
|
297 |
+
if (kuimiMethod.methodSlot != -1) {
|
298 |
+
throw new RuntimeException("Target method already attached.");
|
299 |
+
}
|
300 |
+
if (kuimiMethod.getDeclaredClass() != this.declaredClass) {
|
301 |
+
throw new IllegalArgumentException("Declared class not match, table=" + declaredClass + ", method=" + kuimiMethod.getDeclaredClass());
|
302 |
+
}
|
303 |
+
acquireModifyLock();
|
304 |
+
try {
|
305 |
+
if (findDeclaredMethod(kuimiMethod) != null) {
|
306 |
+
throw new IllegalStateException("A method with same signature was already defined.");
|
307 |
+
}
|
308 |
+
declaredMethods.add(kuimiMethod);
|
309 |
+
|
310 |
+
kuimiMethod.methodSlot = -2; // mark as attached
|
311 |
+
|
312 |
+
return kuimiMethod;
|
313 |
+
} finally {
|
314 |
+
releaseModifyLock();
|
315 |
+
}
|
316 |
+
}
|
317 |
+
|
318 |
+
|
319 |
+
private KuimiMethod findDeclaredMethod(KuimiMethod tried) {
|
320 |
+
for (var dm : declaredMethods) {
|
321 |
+
if (dm.getReturnType() != tried.getReturnType()) continue;
|
322 |
+
if (!dm.getParameters().equals(tried.getParameters())) continue;
|
323 |
+
if (!dm.getMethodName().equals(tried.getMethodName())) continue;
|
324 |
+
|
325 |
+
return dm;
|
326 |
+
}
|
327 |
+
|
328 |
+
return null;
|
329 |
+
}
|
330 |
+
|
331 |
+
|
332 |
+
public List<KuimiMethod> getDeclaredMethods() {
|
333 |
+
closeTable();
|
334 |
+
return Collections.unmodifiableList(declaredMethods);
|
335 |
+
}
|
336 |
+
|
337 |
+
public List<KuimiMethod> getDeclaredMethodNoClose() {
|
338 |
+
return Collections.unmodifiableList(declaredMethods);
|
339 |
+
}
|
340 |
+
|
341 |
+
public List<KuimiMethod> getMergedMethods() {
|
342 |
+
closeTable();
|
343 |
+
return Collections.unmodifiableList(mergedMethods);
|
344 |
+
}
|
345 |
+
|
346 |
+
private List<KuimiMethod> loopedMethods(String name) {
|
347 |
+
if (name.equals("<clinit>") || name.equals("<init>")) return getDeclaredMethods();
|
348 |
+
return getMergedMethods();
|
349 |
+
}
|
350 |
+
|
351 |
+
public KuimiMethod resolveMethod(String name, KuimiClass retType, List<KuimiClass> params, boolean isStatic) {
|
352 |
+
var list = loopedMethods(name);
|
353 |
+
for (var iterator = list.listIterator(list.size()); iterator.hasPrevious(); ) {
|
354 |
+
var met = iterator.previous();
|
355 |
+
|
356 |
+
if (Modifier.isStatic(met.getModifiers()) != isStatic) continue;
|
357 |
+
if (!retType.equals(met.getReturnType())) continue;
|
358 |
+
if (!params.equals(met.getParameters())) continue;
|
359 |
+
if (!name.equals(met.getMethodName())) continue;
|
360 |
+
return met;
|
361 |
+
}
|
362 |
+
return null;
|
363 |
+
}
|
364 |
+
|
365 |
+
public KuimiMethod resolveMethod(String name, String desc, boolean isStatic) {
|
366 |
+
var retType = declaredClass.getVm().resolveClass(Type.getReturnType(desc));
|
367 |
+
var parmas = Arrays.stream(Type.getArgumentTypes(desc)).map(it -> declaredClass.getVm().resolveClass(it)).toList();
|
368 |
+
return resolveMethod(name, retType, parmas, isStatic);
|
369 |
+
}
|
370 |
+
|
371 |
+
@Override
|
372 |
+
public String toString() {
|
373 |
+
var sb = new StringBuilder();
|
374 |
+
sb.append("MethodTable[").append(declaredClass).append(", status=").append(status).append('\n');
|
375 |
+
|
376 |
+
sb.append("|= declared:\n");
|
377 |
+
for (var dec : declaredMethods) {
|
378 |
+
sb.append("| S[").append(dec.methodSlot).append("] ").append(dec).append('\n');
|
379 |
+
}
|
380 |
+
sb.append("|= merged:\n");
|
381 |
+
for (var dec : mergedMethods) {
|
382 |
+
sb.append("| S[").append(dec.methodSlot).append("] ").append(dec).append('\n');
|
383 |
+
}
|
384 |
+
sb.append("|= itfs:\n");
|
385 |
+
for (var itfs : interfaceMethodTableMap.entrySet()) {
|
386 |
+
sb.append("| |- ").append(itfs.getKey().getTypeName()).append("\n");
|
387 |
+
|
388 |
+
var mtTable = itfs.getValue().itfType.getMethodTable().mergedMethods;
|
389 |
+
var slotMap = itfs.getValue().slotMap;
|
390 |
+
var slotMapSize = slotMap.length;
|
391 |
+
|
392 |
+
|
393 |
+
for (int i = 0, mtTableSize = mtTable.size(); i < mtTableSize; i++) {
|
394 |
+
var met = mtTable.get(i);
|
395 |
+
|
396 |
+
if (Modifier.isStatic(met.getModifiers())) continue;
|
397 |
+
if (Modifier.isPrivate(met.getModifiers())) continue;
|
398 |
+
|
399 |
+
sb.append("| | `- S[").append(i).append("->");
|
400 |
+
if (i < slotMapSize) {
|
401 |
+
sb.append(slotMap[i]).append("\t] ").append(met);
|
402 |
+
sb.append("\t\t--> ").append(mergedMethods.get(slotMap[i]));
|
403 |
+
} else {
|
404 |
+
sb.append("??\t] ").append(met);
|
405 |
+
}
|
406 |
+
sb.append('\n');
|
407 |
+
}
|
408 |
+
|
409 |
+
}
|
410 |
+
|
411 |
+
|
412 |
+
sb.append(']');
|
413 |
+
return sb.toString();
|
414 |
+
}
|
415 |
+
|
416 |
+
public Map<KuimiClass, InterfaceMethodTable> getInterfaceMethodTableMap() {
|
417 |
+
return Collections.unmodifiableMap(interfaceMethodTableMap);
|
418 |
+
}
|
419 |
+
|
420 |
+
public KuimiMethod resolveMethod(KuimiMethod method) {
|
421 |
+
var mod = method.getModifiers();
|
422 |
+
if (Modifier.isPrivate(mod)) return method;
|
423 |
+
if (Modifier.isStatic(mod)) return method;
|
424 |
+
if (method.isInterfaceMethod()) {
|
425 |
+
try {
|
426 |
+
var attached = method.getAttachedClass();
|
427 |
+
if (!attached.isInterface()) {
|
428 |
+
// attached to parent class / current class
|
429 |
+
return method;
|
430 |
+
}
|
431 |
+
var remapped = interfaceMethodTableMap.get(attached);
|
432 |
+
return getMergedMethods().get(remapped.slotMap[method.getMethodSlot()]);
|
433 |
+
} catch (Throwable throwable) {
|
434 |
+
throw new RuntimeException("Exception when resolving " + method + "<" + method.methodSlot + ">{" + method.getClass() + "} by " + this + ", \n" + method.getAttachedClass().getMethodTable());
|
435 |
+
}
|
436 |
+
}
|
437 |
+
return getMergedMethods().get(method.getMethodSlot());
|
438 |
+
}
|
439 |
+
|
440 |
+
public static class InterfaceMethodTable {
|
441 |
+
private final KuimiClass itfType;
|
442 |
+
private final int[] slotMap;
|
443 |
+
|
444 |
+
public InterfaceMethodTable(KuimiClass itfType, int[] slotMap) {
|
445 |
+
this.itfType = itfType;
|
446 |
+
this.slotMap = slotMap;
|
447 |
+
}
|
448 |
+
|
449 |
+
@Override
|
450 |
+
public String toString() {
|
451 |
+
var sb = new StringBuilder();
|
452 |
+
sb.append("InterfaceMethodTable[").append(itfType.getTypeName()).append('\n');
|
453 |
+
|
454 |
+
var mtTable = itfType.getMethodTable().getMergedMethods();
|
455 |
+
|
456 |
+
var slotMapLocal = slotMap;
|
457 |
+
var slotMapSize = slotMapLocal.length;
|
458 |
+
|
459 |
+
for (int i = 0, mtTableSize = mtTable.size(); i < mtTableSize; i++) {
|
460 |
+
var met = mtTable.get(i);
|
461 |
+
|
462 |
+
if (Modifier.isStatic(met.getModifiers())) continue;
|
463 |
+
if (Modifier.isPrivate(met.getModifiers())) continue;
|
464 |
+
|
465 |
+
sb.append("| S[").append(i).append("->");
|
466 |
+
if (i < slotMapSize) {
|
467 |
+
sb.append(slotMapLocal[i]);
|
468 |
+
} else {
|
469 |
+
sb.append("??");
|
470 |
+
}
|
471 |
+
sb.append("\t] ").append(met);
|
472 |
+
sb.append('\n');
|
473 |
+
}
|
474 |
+
|
475 |
+
sb.append(']');
|
476 |
+
return sb.toString();
|
477 |
+
}
|
478 |
+
}
|
479 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/KuimiObject.java
ADDED
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm;
|
2 |
+
|
3 |
+
import com.kiliokuara.kuimivm.attributes.AttributeMap;
|
4 |
+
|
5 |
+
import java.util.Objects;
|
6 |
+
|
7 |
+
public class KuimiObject<T> {
|
8 |
+
private final KuimiClass type;
|
9 |
+
protected volatile T delegateInstance;
|
10 |
+
private AttributeMap<KuimiObject<T>> attributeMap;
|
11 |
+
|
12 |
+
|
13 |
+
public KuimiObject(KuimiClass objectType) {
|
14 |
+
Objects.requireNonNull(objectType, "object type");
|
15 |
+
this.type = objectType;
|
16 |
+
}
|
17 |
+
|
18 |
+
KuimiObject() { // reserved for KuimiClass
|
19 |
+
this.type = null;
|
20 |
+
}
|
21 |
+
|
22 |
+
public KuimiVM getVm() {
|
23 |
+
assert type != null;
|
24 |
+
return type.getVm();
|
25 |
+
}
|
26 |
+
|
27 |
+
public T getDelegateInstance() {
|
28 |
+
return delegateInstance;
|
29 |
+
}
|
30 |
+
|
31 |
+
public void setDelegateInstance(T delegateInstance) {
|
32 |
+
this.delegateInstance = delegateInstance;
|
33 |
+
}
|
34 |
+
|
35 |
+
public KuimiClass getObjectClass() {
|
36 |
+
return type;
|
37 |
+
}
|
38 |
+
|
39 |
+
public AttributeMap<KuimiObject<T>> getAttributeMap() {
|
40 |
+
if (attributeMap != null) return attributeMap;
|
41 |
+
synchronized (this) {
|
42 |
+
if (attributeMap != null) return attributeMap;
|
43 |
+
|
44 |
+
return attributeMap = new AttributeMap<>(this);
|
45 |
+
}
|
46 |
+
}
|
47 |
+
|
48 |
+
public KuimiObjectMemory memoryView() {
|
49 |
+
return getAttributeMap().attribute(KuimiObjectMemory.ATTRIBUTE_KEY);
|
50 |
+
}
|
51 |
+
|
52 |
+
@Override
|
53 |
+
public String toString() {
|
54 |
+
var d = delegateInstance;
|
55 |
+
if (d != null) return d.toString();
|
56 |
+
|
57 |
+
return getObjectClass().getTypeName() + "@" + hashCode();
|
58 |
+
}
|
59 |
+
|
60 |
+
@Override
|
61 |
+
public int hashCode() {
|
62 |
+
var d = delegateInstance;
|
63 |
+
if (d != null) return d.hashCode();
|
64 |
+
return System.identityHashCode(this);
|
65 |
+
}
|
66 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/KuimiObjectMemory.java
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm;
|
2 |
+
|
3 |
+
import com.kiliokuara.kuimivm.attributes.AttributeKey;
|
4 |
+
|
5 |
+
import java.nio.ByteBuffer;
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Object fields' memory
|
9 |
+
*/
|
10 |
+
public class KuimiObjectMemory {
|
11 |
+
public static final AttributeKey<KuimiObject<?>, KuimiObjectMemory> ATTRIBUTE_KEY = new AttributeKey<>("field-table-memory", f -> {
|
12 |
+
if (f instanceof KuimiClass c) {
|
13 |
+
return new KuimiObjectMemory(c.getFieldTable(), true);
|
14 |
+
} else {
|
15 |
+
return new KuimiObjectMemory(f.getObjectClass().getFieldTable(), false);
|
16 |
+
}
|
17 |
+
});
|
18 |
+
|
19 |
+
public final ByteBuffer buffer;
|
20 |
+
public final KuimiObject<?>[] objects;
|
21 |
+
|
22 |
+
public KuimiObjectMemory(KuimiFieldTable table, boolean isStatic) {
|
23 |
+
buffer = ByteBuffer.allocate(
|
24 |
+
(int) (isStatic ? table.staticFieldsSize : table.objectFieldsSize)
|
25 |
+
);
|
26 |
+
objects = new KuimiObject[isStatic ? table.staticObjectCount : table.objectCount];
|
27 |
+
}
|
28 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/KuimiVM.java
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm;
|
2 |
+
|
3 |
+
import com.kiliokuara.kuimivm.execute.StackTrace;
|
4 |
+
import com.kiliokuara.kuimivm.execute.ObjectPool;
|
5 |
+
import org.objectweb.asm.Type;
|
6 |
+
|
7 |
+
public abstract class KuimiVM {
|
8 |
+
public abstract KuimiClass getBaseClass();
|
9 |
+
|
10 |
+
public abstract KuimiClass getClassClass();
|
11 |
+
public abstract KuimiClass getStringClass();
|
12 |
+
|
13 |
+
public abstract KuimiClass getPrimitiveClass(Type type);
|
14 |
+
|
15 |
+
public abstract long objectPointerSize();
|
16 |
+
|
17 |
+
public abstract KuimiClass resolveClass(Type type);
|
18 |
+
|
19 |
+
public abstract KuimiObject<?> resolveObject(int ptr);
|
20 |
+
|
21 |
+
public abstract ObjectPool getGlobalPool();
|
22 |
+
|
23 |
+
public abstract ObjectPool getWeakGlobalPool();
|
24 |
+
|
25 |
+
|
26 |
+
public abstract StackTrace getStackTrace();
|
27 |
+
|
28 |
+
public abstract void attachThread(StackTrace stackTrace);
|
29 |
+
|
30 |
+
public abstract void detatchThread();
|
31 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/abstractvm/ClassPool.java
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm.abstractvm;
|
2 |
+
|
3 |
+
import com.kiliokuara.kuimivm.KuimiClass;
|
4 |
+
|
5 |
+
import java.util.Map;
|
6 |
+
import java.util.concurrent.ConcurrentHashMap;
|
7 |
+
|
8 |
+
public class ClassPool {
|
9 |
+
private ClassPool parent;
|
10 |
+
private final Map<String, KuimiClass> classMap = new ConcurrentHashMap<>();
|
11 |
+
|
12 |
+
public ClassPool() {
|
13 |
+
}
|
14 |
+
|
15 |
+
public ClassPool(ClassPool parent) {
|
16 |
+
this.parent = parent;
|
17 |
+
}
|
18 |
+
|
19 |
+
public void put(String type, KuimiClass target) {
|
20 |
+
classMap.put(type, target);
|
21 |
+
}
|
22 |
+
|
23 |
+
public void put(KuimiClass target) {
|
24 |
+
put(target.getTypeName(), target);
|
25 |
+
}
|
26 |
+
|
27 |
+
public KuimiClass resolve(String type) {
|
28 |
+
if (parent != null) {
|
29 |
+
var pt = parent.resolve(type);
|
30 |
+
if (pt != null) return pt;
|
31 |
+
}
|
32 |
+
return classMap.get(type);
|
33 |
+
}
|
34 |
+
|
35 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/abstractvm/KuimiAbstractVM.java
ADDED
@@ -0,0 +1,515 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm.abstractvm;
|
2 |
+
|
3 |
+
import com.kiliokuara.kuimivm.*;
|
4 |
+
import com.kiliokuara.kuimivm.execute.ObjectPool;
|
5 |
+
import com.kiliokuara.kuimivm.execute.StackTrace;
|
6 |
+
import com.kiliokuara.kuimivm.objects.KuimiArrays;
|
7 |
+
import com.kiliokuara.kuimivm.objects.KuimiString;
|
8 |
+
import com.kiliokuara.kuimivm.*;
|
9 |
+
import org.objectweb.asm.Opcodes;
|
10 |
+
import org.objectweb.asm.Type;
|
11 |
+
|
12 |
+
import java.lang.invoke.MethodHandles;
|
13 |
+
import java.util.*;
|
14 |
+
import java.util.function.BiConsumer;
|
15 |
+
import java.util.function.IntFunction;
|
16 |
+
|
17 |
+
public class KuimiAbstractVM extends KuimiVM {
|
18 |
+
private final ClassPool bootstrapPool = new ClassPool();
|
19 |
+
private final ObjectPool globalPool = new ObjectPool(this, 2048);
|
20 |
+
private final ObjectPool weakGlobalPool = new ObjectPool(this, 2048);
|
21 |
+
|
22 |
+
private final KuimiClass javaLangObject, javaLangClassLoader, javaLangClass, javaLangString;
|
23 |
+
|
24 |
+
private final ThreadLocal<StackTrace> THREAD_STACK_TRACE = new ThreadLocal<>();
|
25 |
+
|
26 |
+
@SuppressWarnings({"rawtypes", "unchecked", "unused"})
|
27 |
+
public KuimiAbstractVM() {
|
28 |
+
{// base classes
|
29 |
+
// TODO: sb
|
30 |
+
javaLangObject = new KuimiClass(this, Type.getObjectType("java/lang/Object"), Opcodes.ACC_PUBLIC, null, null, null);
|
31 |
+
javaLangClass = new KuimiClass(this, Type.getObjectType("java/lang/Class"), Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, null, javaLangObject, null);
|
32 |
+
bootstrapPool.put(javaLangObject);
|
33 |
+
bootstrapPool.put(javaLangClass);
|
34 |
+
|
35 |
+
|
36 |
+
{ // primitive classes
|
37 |
+
for (var type : new Type[]{
|
38 |
+
Type.BOOLEAN_TYPE,
|
39 |
+
Type.BYTE_TYPE,
|
40 |
+
Type.CHAR_TYPE,
|
41 |
+
Type.SHORT_TYPE,
|
42 |
+
Type.INT_TYPE,
|
43 |
+
Type.LONG_TYPE,
|
44 |
+
|
45 |
+
Type.FLOAT_TYPE,
|
46 |
+
Type.DOUBLE_TYPE,
|
47 |
+
|
48 |
+
Type.VOID_TYPE,
|
49 |
+
}) {
|
50 |
+
var typeC = new KuimiClass(this, type, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, null, null, null);
|
51 |
+
bootstrapPool.put(typeC);
|
52 |
+
typeC.getMethodTable().closeTable();
|
53 |
+
}
|
54 |
+
|
55 |
+
BiConsumer<KuimiClass, IntFunction<Object>> attachPM = (elmt, allo) -> {
|
56 |
+
//noinspection deprecation
|
57 |
+
KuimiClass.Internal.attachArrayType(elmt, new KuimiArrays.PrimitiveArrayClass(elmt, allo));
|
58 |
+
};
|
59 |
+
|
60 |
+
attachPM.accept(getPrimitiveClass(Type.BOOLEAN_TYPE), boolean[]::new);
|
61 |
+
attachPM.accept(getPrimitiveClass(Type.BYTE_TYPE), byte[]::new);
|
62 |
+
attachPM.accept(getPrimitiveClass(Type.CHAR_TYPE), char[]::new);
|
63 |
+
attachPM.accept(getPrimitiveClass(Type.SHORT_TYPE), short[]::new);
|
64 |
+
attachPM.accept(getPrimitiveClass(Type.INT_TYPE), int[]::new);
|
65 |
+
attachPM.accept(getPrimitiveClass(Type.LONG_TYPE), long[]::new);
|
66 |
+
attachPM.accept(getPrimitiveClass(Type.FLOAT_TYPE), float[]::new);
|
67 |
+
attachPM.accept(getPrimitiveClass(Type.DOUBLE_TYPE), double[]::new);
|
68 |
+
}
|
69 |
+
|
70 |
+
var javaLangCharSequence = new KuimiClass(this, Type.getObjectType("java/lang/CharSequence"), Opcodes.ACC_INTERFACE | Opcodes.ACC_PUBLIC, null, javaLangObject, null);
|
71 |
+
bootstrapPool.put(javaLangCharSequence);
|
72 |
+
var javaLangString = new KuimiClass(this, Type.getObjectType("java/lang/String"), Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, null, javaLangObject, List.of(javaLangCharSequence)) {
|
73 |
+
@Override
|
74 |
+
public KuimiObject<?> allocateNewObject() {
|
75 |
+
return new KuimiString(KuimiAbstractVM.this, null);
|
76 |
+
}
|
77 |
+
};
|
78 |
+
bootstrapPool.put(javaLangString);
|
79 |
+
this.javaLangString = javaLangString;
|
80 |
+
|
81 |
+
javaLangClassLoader = new KuimiClass(this, Type.getObjectType("java/lang/ClassLoader"), Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT, null, javaLangObject, null);
|
82 |
+
javaLangClassLoader.getMethodTable().addMethod(new KuimiMethod(javaLangClassLoader, Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "getSystemClassLoader", javaLangClassLoader, List.of()) {
|
83 |
+
KuimiObject<?> execute(KuimiVM vm, StackTrace st) {
|
84 |
+
KuimiObject<ClassLoader> object = new KuimiObject<>(javaLangClassLoader);
|
85 |
+
object.setDelegateInstance(ClassLoader.getSystemClassLoader());
|
86 |
+
return object;
|
87 |
+
}
|
88 |
+
});
|
89 |
+
javaLangClassLoader.getMethodTable().addMethod(new KuimiMethod(javaLangClassLoader, Opcodes.ACC_PUBLIC, "loadClass", javaLangClass, List.of(javaLangString)) {
|
90 |
+
KuimiObject<?> execute(KuimiVM vm, StackTrace st, KuimiObject<ClassLoader> kuimiObject, KuimiObject name) throws ClassNotFoundException {
|
91 |
+
var obj = new KuimiObject<>(javaLangClass);
|
92 |
+
System.out.println("!LoadClass: " + name.getDelegateInstance());
|
93 |
+
obj.setDelegateInstance(kuimiObject.getDelegateInstance().loadClass((String) name.getDelegateInstance()));
|
94 |
+
return obj;
|
95 |
+
}
|
96 |
+
});
|
97 |
+
bootstrapPool.put(javaLangClassLoader);
|
98 |
+
|
99 |
+
var javaIoSerializable = new KuimiClass(this, Type.getObjectType("java/io/Serializable"), Opcodes.ACC_INTERFACE | Opcodes.ACC_PUBLIC, null, javaLangObject, null);
|
100 |
+
var javaLangComparable = new KuimiClass(this, Type.getObjectType("java/lang/Comparable"), Opcodes.ACC_INTERFACE | Opcodes.ACC_PUBLIC, null, javaLangObject, null);
|
101 |
+
bootstrapPool.put(javaIoSerializable);
|
102 |
+
bootstrapPool.put(javaLangComparable);
|
103 |
+
|
104 |
+
KuimiClass javaLangThrowable = new KuimiClass(this, Type.getObjectType("java/lang/Throwable"), Opcodes.ACC_PUBLIC, null, javaLangObject, List.of(javaIoSerializable));
|
105 |
+
KuimiClass javaLangException = new KuimiClass(this, Type.getObjectType("java/lang/Exception"), Opcodes.ACC_PUBLIC, null, javaLangThrowable, null);
|
106 |
+
KuimiClass javaLangError = new KuimiClass(this, Type.getObjectType("java/lang/Error"), Opcodes.ACC_PUBLIC, null, javaLangThrowable, null);
|
107 |
+
KuimiClass javaLangRuntimeException = new KuimiClass(this, Type.getObjectType("java/lang/RuntimeException"), Opcodes.ACC_PUBLIC, null, javaLangException, null);
|
108 |
+
KuimiClass javaLangNullPointerException = new KuimiClass(this, Type.getObjectType("java/lang/NullPointerException"), Opcodes.ACC_PUBLIC, null, javaLangRuntimeException, null);
|
109 |
+
KuimiClass javaLangLinkageError = new KuimiClass(this, Type.getObjectType("java/lang/LinkageError"), Opcodes.ACC_PUBLIC, null, javaLangError, null);
|
110 |
+
KuimiClass javaLangNoClassDefFoundError = new KuimiClass(this, Type.getObjectType("java/lang/NoClassDefFoundError"), Opcodes.ACC_PUBLIC, null, javaLangLinkageError, null);
|
111 |
+
KuimiClass javaLangReflectiveOperationException = new KuimiClass(this, Type.getObjectType("java/lang/ReflectiveOperationException"), Opcodes.ACC_PUBLIC, null, javaLangException, null);
|
112 |
+
KuimiClass javaLangClassNotFoundException = new KuimiClass(this, Type.getObjectType("java/lang/ClassNotFoundException"), Opcodes.ACC_PUBLIC, null, javaLangReflectiveOperationException, null);
|
113 |
+
|
114 |
+
bootstrapPool.put(javaLangThrowable);
|
115 |
+
bootstrapPool.put(javaLangException);
|
116 |
+
bootstrapPool.put(javaLangError);
|
117 |
+
bootstrapPool.put(javaLangRuntimeException);
|
118 |
+
bootstrapPool.put(javaLangNullPointerException);
|
119 |
+
bootstrapPool.put(javaLangLinkageError);
|
120 |
+
bootstrapPool.put(javaLangNoClassDefFoundError);
|
121 |
+
bootstrapPool.put(javaLangReflectiveOperationException);
|
122 |
+
bootstrapPool.put(javaLangClassNotFoundException);
|
123 |
+
|
124 |
+
|
125 |
+
javaLangCharSequence.getMethodTable().addMethod(new KuimiMethod(javaLangCharSequence, Opcodes.ACC_ABSTRACT | Opcodes.ACC_PUBLIC, "length", getPrimitiveClass(Type.INT_TYPE), List.of()));
|
126 |
+
javaLangCharSequence.getMethodTable().addMethod(new KuimiMethod(javaLangCharSequence, Opcodes.ACC_ABSTRACT | Opcodes.ACC_PUBLIC, "charAt", getPrimitiveClass(Type.CHAR_TYPE), List.of(getPrimitiveClass(Type.INT_TYPE))));
|
127 |
+
javaLangCharSequence.getMethodTable().addMethod(new KuimiMethod(javaLangCharSequence, Opcodes.ACC_ABSTRACT | Opcodes.ACC_PUBLIC, "subSequence", javaLangCharSequence, List.of(getPrimitiveClass(Type.INT_TYPE), getPrimitiveClass(Type.INT_TYPE))));
|
128 |
+
|
129 |
+
|
130 |
+
javaLangObject.getMethodTable().addMethod(new KuimiMethod(javaLangObject, Opcodes.ACC_PUBLIC, "toString", javaLangString, List.of()) {
|
131 |
+
KuimiObject<?> execute(KuimiVM vm, StackTrace stackTrace, KuimiObject<?> thiz) {
|
132 |
+
var toStrx = thiz.toString();
|
133 |
+
if (toStrx != null) {
|
134 |
+
return new KuimiString(vm, toStrx);
|
135 |
+
}
|
136 |
+
return null;
|
137 |
+
}
|
138 |
+
});
|
139 |
+
javaLangObject.getMethodTable().addMethod(new KuimiMethod(javaLangObject, Opcodes.ACC_PUBLIC, "hashCode", getPrimitiveClass(Type.INT_TYPE), List.of()) {
|
140 |
+
int execute(KuimiVM vm, StackTrace stackTrace, KuimiObject<?> thiz) {
|
141 |
+
return thiz.hashCode();
|
142 |
+
}
|
143 |
+
});
|
144 |
+
|
145 |
+
{
|
146 |
+
var objInit = javaLangObject.getMethodTable()
|
147 |
+
.addMethod(new KuimiMethod(javaLangObject, Opcodes.ACC_PUBLIC, "<init>", getPrimitiveClass(Type.VOID_TYPE), List.of()));
|
148 |
+
objInit.attachImplementation(MethodHandles.empty(objInit.flattenMethodHandleType()));
|
149 |
+
}
|
150 |
+
{
|
151 |
+
var javaLangRunnable = new KuimiClass(this, Type.getObjectType("java/lang/Runnable"), Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT | Opcodes.ACC_INTERFACE, null, javaLangObject, null);
|
152 |
+
javaLangRunnable.getMethodTable().addMethod(new KuimiMethod(javaLangRunnable, Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT, "run", getPrimitiveClass(Type.VOID_TYPE), List.of()));
|
153 |
+
}
|
154 |
+
|
155 |
+
|
156 |
+
javaLangString.getMethodTable().addMethod(new KuimiMethod(javaLangString, Opcodes.ACC_PUBLIC, "<init>", getPrimitiveClass(Type.VOID_TYPE), List.of(getPrimitiveClass(Type.BYTE_TYPE).arrayType(), javaLangString)) {
|
157 |
+
void execute(KuimiVM vm, StackTrace st, KuimiObject<?> thiz, KuimiObject<?> barr, KuimiObject<?> charset) throws Throwable {
|
158 |
+
((KuimiString) thiz).forceChangeValue(
|
159 |
+
new String((byte[]) barr.getDelegateInstance(), charset.toString())
|
160 |
+
);
|
161 |
+
}
|
162 |
+
});
|
163 |
+
javaLangString.getMethodTable().addMethod(new KuimiMethod(javaLangString, Opcodes.ACC_PUBLIC, "<init>", getPrimitiveClass(Type.VOID_TYPE), List.of(getPrimitiveClass(Type.CHAR_TYPE).arrayType())) {
|
164 |
+
void execute(KuimiVM vm, StackTrace st, KuimiObject<?> thiz, KuimiObject<?> arr) throws Throwable {
|
165 |
+
((KuimiString) thiz).forceChangeValue(
|
166 |
+
new String((char[]) arr.getDelegateInstance())
|
167 |
+
);
|
168 |
+
}
|
169 |
+
});
|
170 |
+
|
171 |
+
|
172 |
+
{ // stub field
|
173 |
+
// javaLangClass.getFieldTable().addField(new KuimiField(javaLangClass, Opcodes.ACC_PRIVATE, "stub1", getPrimitiveClass(Type.INT_TYPE)));
|
174 |
+
javaLangClass.getFieldTable().addField(new KuimiField(javaLangClass, Opcodes.ACC_PRIVATE, "classLoader", javaLangObject));
|
175 |
+
// javaLangClass.getFieldTable().addField(new KuimiField(javaLangClass, Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, "CONST_0", getPrimitiveClass(Type.INT_TYPE)));
|
176 |
+
// javaLangClass.getFieldTable().addField(new KuimiField(javaLangClass, Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, "CONST_1", javaLangObject));
|
177 |
+
}
|
178 |
+
|
179 |
+
{ // collections
|
180 |
+
var iterator = new KuimiClass(this, Type.getType(Iterator.class), Opcodes.ACC_PUBLIC | Opcodes.ACC_INTERFACE, null, javaLangObject, null);
|
181 |
+
bootstrapPool.put(iterator);
|
182 |
+
iterator.getMethodTable().addMethod(new KuimiMethod(iterator, Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT, "hasNext", getPrimitiveClass(Type.VOID_TYPE), null) {
|
183 |
+
boolean execute(KuimiVM vm, StackTrace stackTrace, KuimiObject<?> thiz) {
|
184 |
+
var d = thiz.getDelegateInstance();
|
185 |
+
if (d instanceof Iterator<?> itr) return itr.hasNext();
|
186 |
+
throw new UnsupportedOperationException();
|
187 |
+
}
|
188 |
+
});
|
189 |
+
iterator.getMethodTable().addMethod(new KuimiMethod(iterator, Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT, "next", javaLangObject, null) {
|
190 |
+
KuimiObject<?> execute(KuimiVM vm, StackTrace stackTrace, KuimiObject<?> thiz) {
|
191 |
+
var d = thiz.getDelegateInstance();
|
192 |
+
if (d instanceof Iterator<?> itr) {
|
193 |
+
var nxt = itr.next();
|
194 |
+
if (nxt == null) return null;
|
195 |
+
if (nxt instanceof KuimiObject<?>) return (KuimiObject<?>) nxt;
|
196 |
+
if (nxt instanceof String) return new KuimiString(vm, nxt.toString());
|
197 |
+
throw new UnsupportedOperationException(nxt + " <" + nxt.getClass() + ">");
|
198 |
+
}
|
199 |
+
throw new UnsupportedOperationException();
|
200 |
+
}
|
201 |
+
});
|
202 |
+
iterator.getMethodTable().addMethod(new KuimiMethod(iterator, Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT, "remove", getPrimitiveClass(Type.VOID_TYPE), null) {
|
203 |
+
void execute(KuimiVM vm, StackTrace stackTrace, KuimiObject<?> thiz) {
|
204 |
+
var d = thiz.getDelegateInstance();
|
205 |
+
if (d instanceof Iterator<?> itr) {
|
206 |
+
itr.remove();
|
207 |
+
return;
|
208 |
+
}
|
209 |
+
throw new UnsupportedOperationException();
|
210 |
+
}
|
211 |
+
});
|
212 |
+
|
213 |
+
var iterable = new KuimiClass(this, Type.getType(Iterable.class), Opcodes.ACC_PUBLIC | Opcodes.ACC_INTERFACE, null, javaLangObject, null);
|
214 |
+
bootstrapPool.put(iterable);
|
215 |
+
iterable.getMethodTable().addMethod(new KuimiMethod(iterable, Opcodes.ACC_PUBLIC, "iterator", iterator, List.of()) {
|
216 |
+
KuimiObject<?> execute(KuimiVM vm, StackTrace stackTrace, KuimiObject<?> thiz) {
|
217 |
+
var d = thiz.getDelegateInstance();
|
218 |
+
if (d instanceof Iterable<?>) {
|
219 |
+
var rsp = new KuimiObject<>(iterator);
|
220 |
+
rsp.setDelegateInstance(((Iterable<?>) d).iterator());
|
221 |
+
return rsp;
|
222 |
+
}
|
223 |
+
throw new UnsupportedOperationException();
|
224 |
+
}
|
225 |
+
});
|
226 |
+
|
227 |
+
var collection = new KuimiClass(this, Type.getType(Collection.class), Opcodes.ACC_PUBLIC | Opcodes.ACC_INTERFACE, null, javaLangObject, List.of(iterable));
|
228 |
+
bootstrapPool.put(collection);
|
229 |
+
collection.getMethodTable().addMethod(new KuimiMethod(collection, Opcodes.ACC_PUBLIC, "size", getPrimitiveClass(Type.INT_TYPE), null) {
|
230 |
+
int execute(KuimiVM vm, StackTrace stackTrace, KuimiObject<?> thiz) {
|
231 |
+
var d = thiz.getDelegateInstance();
|
232 |
+
if (d instanceof Collection<?> itr) return itr.size();
|
233 |
+
throw new UnsupportedOperationException();
|
234 |
+
}
|
235 |
+
});
|
236 |
+
collection.getMethodTable().addMethod(new KuimiMethod(collection, Opcodes.ACC_PUBLIC, "isEmpty", getPrimitiveClass(Type.BOOLEAN_TYPE), null) {
|
237 |
+
boolean execute(KuimiVM vm, StackTrace stackTrace, KuimiObject<?> thiz) {
|
238 |
+
var d = thiz.getDelegateInstance();
|
239 |
+
if (d instanceof Collection<?> itr) return itr.isEmpty();
|
240 |
+
throw new UnsupportedOperationException();
|
241 |
+
}
|
242 |
+
});
|
243 |
+
collection.getMethodTable().addMethod(new KuimiMethod(collection, Opcodes.ACC_PUBLIC, "contains", getPrimitiveClass(Type.INT_TYPE), List.of(javaLangObject)) {
|
244 |
+
boolean execute(KuimiVM vm, StackTrace stackTrace, KuimiObject<?> thiz, KuimiObject<?> other) {
|
245 |
+
var d = thiz.getDelegateInstance();
|
246 |
+
if (d instanceof Collection<?> itr) return itr.contains(other);
|
247 |
+
throw new UnsupportedOperationException();
|
248 |
+
}
|
249 |
+
});
|
250 |
+
collection.getMethodTable().addMethod(new KuimiMethod(collection, Opcodes.ACC_PUBLIC, "toArray", javaLangObject.arrayType(), null) {
|
251 |
+
KuimiObject<?> execute(KuimiVM vm, StackTrace stackTrace, KuimiObject<?> thiz) {
|
252 |
+
var d = thiz.getDelegateInstance();
|
253 |
+
|
254 |
+
if (d instanceof Collection<?> itr) //noinspection SuspiciousToArrayCall
|
255 |
+
return new KuimiArrays.ArrayObject(javaLangObject.arrayType(), itr.toArray(new KuimiObject[0]));
|
256 |
+
|
257 |
+
throw new UnsupportedOperationException();
|
258 |
+
}
|
259 |
+
});
|
260 |
+
collection.getMethodTable().addMethod(new KuimiMethod(collection, Opcodes.ACC_PUBLIC, "add", getPrimitiveClass(Type.BOOLEAN_TYPE), List.of(javaLangObject)) {
|
261 |
+
boolean execute(KuimiVM vm, StackTrace stackTrace, KuimiObject<?> thiz, KuimiObject<?> other) {
|
262 |
+
var d = thiz.getDelegateInstance();
|
263 |
+
if (d instanceof Collection itr) return itr.add(other);
|
264 |
+
throw new UnsupportedOperationException();
|
265 |
+
}
|
266 |
+
});
|
267 |
+
collection.getMethodTable().addMethod(new KuimiMethod(collection, Opcodes.ACC_PUBLIC, "remove", getPrimitiveClass(Type.BOOLEAN_TYPE), List.of(javaLangObject)) {
|
268 |
+
boolean execute(KuimiVM vm, StackTrace stackTrace, KuimiObject<?> thiz, KuimiObject<?> other) {
|
269 |
+
var d = thiz.getDelegateInstance();
|
270 |
+
if (d instanceof Collection itr) return itr.remove(other);
|
271 |
+
throw new UnsupportedOperationException();
|
272 |
+
}
|
273 |
+
});
|
274 |
+
collection.getMethodTable().addMethod(new KuimiMethod(collection, Opcodes.ACC_PUBLIC, "clear", getPrimitiveClass(Type.VOID_TYPE), List.of()) {
|
275 |
+
void execute(KuimiVM vm, StackTrace stackTrace, KuimiObject<?> thiz) {
|
276 |
+
var d = thiz.getDelegateInstance();
|
277 |
+
if (d instanceof Collection itr) {
|
278 |
+
itr.clear();
|
279 |
+
return;
|
280 |
+
}
|
281 |
+
throw new UnsupportedOperationException();
|
282 |
+
}
|
283 |
+
});
|
284 |
+
|
285 |
+
var set = new KuimiClass(this, Type.getType(Set.class), Opcodes.ACC_PUBLIC | Opcodes.ACC_INTERFACE, null, javaLangObject, List.of(collection));
|
286 |
+
bootstrapPool.put(set);
|
287 |
+
|
288 |
+
var list = new KuimiClass(this, Type.getType(List.class), Opcodes.ACC_PUBLIC | Opcodes.ACC_INTERFACE, null, javaLangObject, List.of(collection));
|
289 |
+
list.getMethodTable().addMethod(new KuimiMethod(list, Opcodes.ACC_PUBLIC, "get", javaLangObject, List.of(getPrimitiveClass(Type.INT_TYPE))) {
|
290 |
+
KuimiObject<?> execute(KuimiVM vm, StackTrace stackTrace, KuimiObject<?> thiz, int idx) {
|
291 |
+
var d = thiz.getDelegateInstance();
|
292 |
+
if (d instanceof List itr) {
|
293 |
+
return (KuimiObject<?>) itr.get(idx);
|
294 |
+
}
|
295 |
+
throw new UnsupportedOperationException();
|
296 |
+
}
|
297 |
+
});
|
298 |
+
list.getMethodTable().addMethod(new KuimiMethod(list, Opcodes.ACC_PUBLIC, "remove", javaLangObject, List.of(getPrimitiveClass(Type.INT_TYPE))) {
|
299 |
+
KuimiObject<?> execute(KuimiVM vm, StackTrace stackTrace, KuimiObject<?> thiz, int idx) {
|
300 |
+
var d = thiz.getDelegateInstance();
|
301 |
+
if (d instanceof List itr) {
|
302 |
+
return (KuimiObject<?>) itr.remove(idx);
|
303 |
+
}
|
304 |
+
throw new UnsupportedOperationException();
|
305 |
+
}
|
306 |
+
});
|
307 |
+
list.getMethodTable().addMethod(new KuimiMethod(list, Opcodes.ACC_PUBLIC, "set", javaLangObject, List.of(getPrimitiveClass(Type.INT_TYPE), javaLangObject)) {
|
308 |
+
KuimiObject<?> execute(KuimiVM vm, StackTrace stackTrace, KuimiObject<?> thiz, int idx, KuimiObject<?> val) {
|
309 |
+
var d = thiz.getDelegateInstance();
|
310 |
+
if (d instanceof List itr) {
|
311 |
+
return (KuimiObject<?>) itr.set(idx, val);
|
312 |
+
}
|
313 |
+
throw new UnsupportedOperationException();
|
314 |
+
}
|
315 |
+
});
|
316 |
+
list.getMethodTable().addMethod(new KuimiMethod(list, Opcodes.ACC_PUBLIC, "add", getPrimitiveClass(Type.VOID_TYPE), List.of(getPrimitiveClass(Type.INT_TYPE), javaLangObject)) {
|
317 |
+
void execute(KuimiVM vm, StackTrace stackTrace, KuimiObject<?> thiz, int idx, KuimiObject<?> val) {
|
318 |
+
var d = thiz.getDelegateInstance();
|
319 |
+
if (d instanceof List itr) {
|
320 |
+
itr.add(idx, val);
|
321 |
+
return;
|
322 |
+
}
|
323 |
+
throw new UnsupportedOperationException();
|
324 |
+
}
|
325 |
+
});
|
326 |
+
|
327 |
+
list.getMethodTable().addMethod(new KuimiMethod(list, Opcodes.ACC_PUBLIC, "indexOf", getPrimitiveClass(Type.INT_TYPE), List.of(javaLangObject)) {
|
328 |
+
int execute(KuimiVM vm, StackTrace stackTrace, KuimiObject<?> thiz, KuimiObject<?> val) {
|
329 |
+
var d = thiz.getDelegateInstance();
|
330 |
+
if (d instanceof List itr) {
|
331 |
+
return itr.indexOf(val);
|
332 |
+
}
|
333 |
+
throw new UnsupportedOperationException();
|
334 |
+
}
|
335 |
+
});
|
336 |
+
|
337 |
+
list.getMethodTable().addMethod(new KuimiMethod(list, Opcodes.ACC_PUBLIC, "lastIndexOf", getPrimitiveClass(Type.INT_TYPE), List.of(javaLangObject)) {
|
338 |
+
int execute(KuimiVM vm, StackTrace stackTrace, KuimiObject<?> thiz, KuimiObject<?> val) {
|
339 |
+
var d = thiz.getDelegateInstance();
|
340 |
+
if (d instanceof List itr) {
|
341 |
+
return itr.lastIndexOf(val);
|
342 |
+
}
|
343 |
+
throw new UnsupportedOperationException();
|
344 |
+
}
|
345 |
+
});
|
346 |
+
// todo: listIterator
|
347 |
+
list.getMethodTable().addMethod(new KuimiMethod(list, Opcodes.ACC_PUBLIC, "subList", list, List.of(getPrimitiveClass(Type.INT_TYPE), getPrimitiveClass(Type.INT_TYPE))) {
|
348 |
+
KuimiObject<?> execute(KuimiVM vm, StackTrace stackTrace, KuimiObject<?> thiz, int from, int end) {
|
349 |
+
var d = thiz.getDelegateInstance();
|
350 |
+
if (d instanceof List itr) {
|
351 |
+
var rsp = itr.subList(from, end);
|
352 |
+
var obj = new KuimiObject<>(list);
|
353 |
+
obj.setDelegateInstance(rsp);
|
354 |
+
return obj;
|
355 |
+
}
|
356 |
+
throw new UnsupportedOperationException();
|
357 |
+
}
|
358 |
+
});
|
359 |
+
|
360 |
+
|
361 |
+
var arrayList = new KuimiClass(this, Type.getType(ArrayList.class), Opcodes.ACC_PUBLIC, null, javaLangObject, List.of(list));
|
362 |
+
bootstrapPool.put(arrayList);
|
363 |
+
arrayList.getMethodTable().addMethod(new KuimiMethod(arrayList, Opcodes.ACC_PUBLIC, "<init>", getPrimitiveClass(Type.VOID_TYPE), List.of()) {
|
364 |
+
void execute(KuimiVM vm, StackTrace trace, KuimiObject thiz) {
|
365 |
+
thiz.setDelegateInstance(new ArrayList());
|
366 |
+
}
|
367 |
+
});
|
368 |
+
arrayList.getMethodTable().addMethod(new KuimiMethod(arrayList, Opcodes.ACC_PUBLIC, "<init>", getPrimitiveClass(Type.VOID_TYPE), List.of(getPrimitiveClass(Type.INT_TYPE))) {
|
369 |
+
void execute(KuimiVM vm, StackTrace trace, KuimiObject thiz, int size) {
|
370 |
+
thiz.setDelegateInstance(new ArrayList(size));
|
371 |
+
}
|
372 |
+
});
|
373 |
+
|
374 |
+
arrayList.getMethodTable().addMethod(new KuimiMethod(arrayList, Opcodes.ACC_PUBLIC, "<init>", getPrimitiveClass(Type.VOID_TYPE), List.of(collection)) {
|
375 |
+
void execute(KuimiVM vm, StackTrace trace, KuimiObject thiz, KuimiObject mirror) {
|
376 |
+
thiz.setDelegateInstance(new ArrayList((Collection) mirror.getDelegateInstance()));
|
377 |
+
}
|
378 |
+
});
|
379 |
+
}
|
380 |
+
}
|
381 |
+
}
|
382 |
+
|
383 |
+
|
384 |
+
@Override
|
385 |
+
public KuimiClass getBaseClass() {
|
386 |
+
return javaLangObject;
|
387 |
+
}
|
388 |
+
|
389 |
+
@Override
|
390 |
+
public KuimiClass getClassClass() {
|
391 |
+
return javaLangClass;
|
392 |
+
}
|
393 |
+
|
394 |
+
@Override
|
395 |
+
public KuimiClass getStringClass() {
|
396 |
+
return javaLangString;
|
397 |
+
}
|
398 |
+
|
399 |
+
@Override
|
400 |
+
public KuimiClass getPrimitiveClass(Type type) {
|
401 |
+
var sort = type.getSort();
|
402 |
+
if (sort == Type.ARRAY || sort == Type.OBJECT) {
|
403 |
+
throw new IllegalArgumentException(type + " not a primitive class");
|
404 |
+
}
|
405 |
+
|
406 |
+
|
407 |
+
return bootstrapPool.resolve(type.getClassName());
|
408 |
+
}
|
409 |
+
|
410 |
+
@Override
|
411 |
+
public long objectPointerSize() {
|
412 |
+
return Long.BYTES;
|
413 |
+
}
|
414 |
+
|
415 |
+
@Override
|
416 |
+
public KuimiClass resolveClass(Type type) {
|
417 |
+
if (type.getSort() == Type.ARRAY) {
|
418 |
+
var dep = type.getElementType();
|
419 |
+
var dsize = type.getDimensions();
|
420 |
+
KuimiClass dclass;
|
421 |
+
if (dep.getSort() == Type.OBJECT) {
|
422 |
+
dclass = resolveClassImpl(dep);
|
423 |
+
} else {
|
424 |
+
dclass = getPrimitiveClass(dep);
|
425 |
+
}
|
426 |
+
if (dclass == null) return null;
|
427 |
+
while (dsize-- > 0) {
|
428 |
+
dclass = dclass.arrayType();
|
429 |
+
}
|
430 |
+
return dclass;
|
431 |
+
}
|
432 |
+
if (type.getSort() == Type.OBJECT) {
|
433 |
+
return resolveClassImpl(type);
|
434 |
+
}
|
435 |
+
return getPrimitiveClass(type);
|
436 |
+
}
|
437 |
+
|
438 |
+
protected KuimiClass resolveClassImpl(Type type) {
|
439 |
+
return bootstrapPool.resolve(type.getClassName());
|
440 |
+
}
|
441 |
+
|
442 |
+
@Override
|
443 |
+
public ObjectPool getGlobalPool() {
|
444 |
+
return globalPool;
|
445 |
+
}
|
446 |
+
|
447 |
+
@Override
|
448 |
+
public ObjectPool getWeakGlobalPool() {
|
449 |
+
return weakGlobalPool;
|
450 |
+
}
|
451 |
+
|
452 |
+
@Override
|
453 |
+
public KuimiObject<?> resolveObject(int ptr) {
|
454 |
+
if (ptr == 0) return null;
|
455 |
+
|
456 |
+
var pfix = ptr & ObjectPool.OBJECT_PREFIX;
|
457 |
+
var rem = ptr & ~ObjectPool.OBJECT_PREFIX;
|
458 |
+
|
459 |
+
if (pfix == ObjectPool.GLOBAL_OBJECT_PREFIX) {
|
460 |
+
return globalPool.getObject(rem);
|
461 |
+
}
|
462 |
+
if (pfix == ObjectPool.LOCAL_OBJECT_PREFIX) {
|
463 |
+
var tstack = THREAD_STACK_TRACE.get();
|
464 |
+
if (tstack == null) return null;
|
465 |
+
|
466 |
+
return tstack.resolve(ptr);
|
467 |
+
}
|
468 |
+
if (pfix == ObjectPool.WEAK_GLOBAL_OBJECT_PREFIX) {
|
469 |
+
return weakGlobalPool.getObject(rem);
|
470 |
+
}
|
471 |
+
return null;
|
472 |
+
}
|
473 |
+
|
474 |
+
|
475 |
+
@Override
|
476 |
+
public StackTrace getStackTrace() {
|
477 |
+
return THREAD_STACK_TRACE.get();
|
478 |
+
}
|
479 |
+
|
480 |
+
@Override
|
481 |
+
public void attachThread(StackTrace stackTrace) {
|
482 |
+
Objects.requireNonNull(stackTrace, "stackTrace");
|
483 |
+
var st = THREAD_STACK_TRACE.get();
|
484 |
+
if (st == null) {
|
485 |
+
THREAD_STACK_TRACE.set(stackTrace);
|
486 |
+
return;
|
487 |
+
}
|
488 |
+
|
489 |
+
if (st.getLocalFramePoint() == -1 && st.getStackTracePoint() == -1) {
|
490 |
+
THREAD_STACK_TRACE.set(stackTrace);
|
491 |
+
return;
|
492 |
+
}
|
493 |
+
|
494 |
+
throw new IllegalStateException("Thread was already attached and cannot be re-attach now");
|
495 |
+
}
|
496 |
+
|
497 |
+
@Override
|
498 |
+
public void detatchThread() {
|
499 |
+
var st = THREAD_STACK_TRACE.get();
|
500 |
+
if (st == null) {
|
501 |
+
return;
|
502 |
+
}
|
503 |
+
|
504 |
+
if (st.getLocalFramePoint() == -1 && st.getStackTracePoint() == -1) {
|
505 |
+
THREAD_STACK_TRACE.remove();
|
506 |
+
return;
|
507 |
+
}
|
508 |
+
|
509 |
+
throw new IllegalStateException("Current thread is not detachable");
|
510 |
+
}
|
511 |
+
|
512 |
+
public ClassPool getBootstrapPool() {
|
513 |
+
return bootstrapPool;
|
514 |
+
}
|
515 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/attributes/AttributeKey.java
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm.attributes;
|
2 |
+
|
3 |
+
import java.util.function.Function;
|
4 |
+
|
5 |
+
public class AttributeKey<O, T> {
|
6 |
+
private final String name;
|
7 |
+
|
8 |
+
final Function<O, T> valueComputer;
|
9 |
+
|
10 |
+
public AttributeKey(String name, Function<O, T> valueComputer) {
|
11 |
+
this.name = name;
|
12 |
+
this.valueComputer = valueComputer;
|
13 |
+
}
|
14 |
+
|
15 |
+
@Override
|
16 |
+
public String toString() {
|
17 |
+
return "Attribute[" + name + "]";
|
18 |
+
}
|
19 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/attributes/AttributeMap.java
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm.attributes;
|
2 |
+
|
3 |
+
import java.util.concurrent.ConcurrentHashMap;
|
4 |
+
|
5 |
+
public class AttributeMap<O> extends ConcurrentHashMap<AttributeKey<? super O, ?>, Object> {
|
6 |
+
private final O owner;
|
7 |
+
|
8 |
+
public AttributeMap(O owner) {
|
9 |
+
this.owner = owner;
|
10 |
+
}
|
11 |
+
|
12 |
+
public <T> T attribute(AttributeKey<? super O, T> key) {
|
13 |
+
//noinspection unchecked
|
14 |
+
return (T) computeIfAbsent(key, k -> key.valueComputer.apply(owner));
|
15 |
+
}
|
16 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/execute/ObjectPool.java
ADDED
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm.execute;
|
2 |
+
|
3 |
+
// 0000 0000 0000 0000 0000 0000 0000 0000
|
4 |
+
// [][ ] [ ] offset
|
5 |
+
// | |
|
6 |
+
// | Page
|
7 |
+
// Type
|
8 |
+
|
9 |
+
// Type:
|
10 |
+
// 11: Global objects
|
11 |
+
// 01: Local frame objects
|
12 |
+
// 10: Weak global references
|
13 |
+
|
14 |
+
import com.kiliokuara.kuimivm.KuimiClass;
|
15 |
+
import com.kiliokuara.kuimivm.KuimiObject;
|
16 |
+
import com.kiliokuara.kuimivm.KuimiVM;
|
17 |
+
import org.objectweb.asm.Type;
|
18 |
+
|
19 |
+
import java.lang.invoke.MethodHandles;
|
20 |
+
import java.lang.invoke.VarHandle;
|
21 |
+
|
22 |
+
public class ObjectPool {
|
23 |
+
|
24 |
+
public static final int LOCAL_OBJECT_PREFIX = 0b0100_0000_0000_0000__0000_0000_0000_0000;
|
25 |
+
public static final int GLOBAL_OBJECT_PREFIX = 0b1100_0000_0000_0000__0000_0000_0000_0000;
|
26 |
+
public static final int WEAK_GLOBAL_OBJECT_PREFIX = 0b1000_0000_0000_0000__0000_0000_0000_0000;
|
27 |
+
|
28 |
+
public static final int OBJECT_PREFIX = 0b1100_0000_0000_0000__0000_0000_0000_0000;
|
29 |
+
|
30 |
+
|
31 |
+
final KuimiObject<?>[] objects;
|
32 |
+
volatile int objIndex; // first null start index
|
33 |
+
|
34 |
+
private static final VarHandle OBJ_INDEX, OBJ_MOD;
|
35 |
+
|
36 |
+
private final KuimiObject<?> OBJ_UPDATE_PLACEHOLDER;
|
37 |
+
|
38 |
+
static {
|
39 |
+
var lk = MethodHandles.lookup();
|
40 |
+
try {
|
41 |
+
OBJ_INDEX = lk.findVarHandle(ObjectPool.class, "objIndex", int.class).withInvokeExactBehavior();
|
42 |
+
OBJ_MOD = MethodHandles.arrayElementVarHandle(KuimiObject[].class).withInvokeExactBehavior();
|
43 |
+
} catch (Exception e) {
|
44 |
+
throw new ExceptionInInitializerError(e);
|
45 |
+
}
|
46 |
+
}
|
47 |
+
|
48 |
+
public ObjectPool(KuimiVM vm, int size) {
|
49 |
+
objects = new KuimiObject[size];
|
50 |
+
OBJ_UPDATE_PLACEHOLDER = new KuimiClass(vm, Type.VOID_TYPE, 0, null, null, null);
|
51 |
+
}
|
52 |
+
|
53 |
+
public int addObject(KuimiObject<?> object) {
|
54 |
+
if (object == null) return -1;
|
55 |
+
while (true) {
|
56 |
+
var sv = objIndex;
|
57 |
+
if (objects[sv] == null) {
|
58 |
+
if (OBJ_MOD.compareAndSet(objects, sv, (KuimiObject<?>) null, OBJ_UPDATE_PLACEHOLDER)) {
|
59 |
+
OBJ_MOD.setVolatile(objects, sv, object);
|
60 |
+
|
61 |
+
OBJ_INDEX.compareAndSet(this, sv, sv + 1);
|
62 |
+
|
63 |
+
return sv;
|
64 |
+
}
|
65 |
+
} else {
|
66 |
+
OBJ_INDEX.compareAndSet(this, sv, sv + 1);
|
67 |
+
}
|
68 |
+
}
|
69 |
+
}
|
70 |
+
|
71 |
+
public KuimiObject<?> removeObject(int index) {
|
72 |
+
if (index < 0) return null;
|
73 |
+
while (true) {
|
74 |
+
var obj = (KuimiObject<?>) OBJ_MOD.getVolatile(objects, index);
|
75 |
+
if (obj == null) return null;
|
76 |
+
if (obj == OBJ_UPDATE_PLACEHOLDER) continue;
|
77 |
+
|
78 |
+
if (OBJ_MOD.compareAndSet(objects, index, obj, OBJ_UPDATE_PLACEHOLDER)) {
|
79 |
+
OBJ_MOD.setVolatile(objects, index, (KuimiObject<?>) null);
|
80 |
+
|
81 |
+
while (true) {
|
82 |
+
var cidx = objIndex;
|
83 |
+
if (cidx < index) break;
|
84 |
+
if (OBJ_INDEX.compareAndSet(this, cidx, index)) break;
|
85 |
+
}
|
86 |
+
|
87 |
+
return obj;
|
88 |
+
}
|
89 |
+
}
|
90 |
+
}
|
91 |
+
|
92 |
+
public KuimiObject<?> getObject(int index) {
|
93 |
+
while (true) {
|
94 |
+
var rsp = (KuimiObject<?>) OBJ_MOD.getVolatile(objects, index);
|
95 |
+
if (rsp == OBJ_UPDATE_PLACEHOLDER) continue;
|
96 |
+
|
97 |
+
return rsp;
|
98 |
+
}
|
99 |
+
}
|
100 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/execute/StackTrace.java
ADDED
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm.execute;
|
2 |
+
|
3 |
+
import com.kiliokuara.kuimivm.KuimiMethod;
|
4 |
+
import com.kiliokuara.kuimivm.KuimiObject;
|
5 |
+
|
6 |
+
import java.util.BitSet;
|
7 |
+
|
8 |
+
public class StackTrace {
|
9 |
+
|
10 |
+
int stackPoint, localFramePoint, frameObjectPointer;
|
11 |
+
|
12 |
+
final KuimiMethod[] stackTrace$trace;
|
13 |
+
final int[] stackTrace$localFrameStart;
|
14 |
+
|
15 |
+
final KuimiObject<?>[] frameObjects;
|
16 |
+
final int[] localFrameObjectStackStart;
|
17 |
+
|
18 |
+
final BitSet localFrame$hasObjectDeletion;
|
19 |
+
|
20 |
+
public KuimiObject<?> throwable;
|
21 |
+
|
22 |
+
public StackTrace(int stackTraceSize, int maxFrames, int frameObjects) {
|
23 |
+
// 0x00_00_00_00
|
24 |
+
|
25 |
+
stackTrace$trace = new KuimiMethod[stackTraceSize];
|
26 |
+
stackTrace$localFrameStart = new int[stackTraceSize];
|
27 |
+
localFrameObjectStackStart = new int[maxFrames];
|
28 |
+
localFrame$hasObjectDeletion = new BitSet(maxFrames);
|
29 |
+
this.frameObjects = new KuimiObject[frameObjects];
|
30 |
+
|
31 |
+
stackPoint = -1;
|
32 |
+
localFramePoint = -1;
|
33 |
+
frameObjectPointer = -1;
|
34 |
+
}
|
35 |
+
|
36 |
+
public void enter(KuimiMethod method) {
|
37 |
+
stackPoint++;
|
38 |
+
stackTrace$trace[stackPoint] = method;
|
39 |
+
stackTrace$localFrameStart[stackPoint] = localFramePoint;
|
40 |
+
}
|
41 |
+
|
42 |
+
public void pushFrame() {
|
43 |
+
localFramePoint++;
|
44 |
+
localFrameObjectStackStart[localFramePoint] = frameObjectPointer;
|
45 |
+
localFrame$hasObjectDeletion.clear(localFramePoint);
|
46 |
+
}
|
47 |
+
|
48 |
+
public int pushObject(KuimiObject<?> object) {
|
49 |
+
if (object == null) return 0;
|
50 |
+
if (localFramePoint == -1) pushFrame();
|
51 |
+
|
52 |
+
if (localFrame$hasObjectDeletion.get(localFramePoint)) { // has object remove
|
53 |
+
var frameObjStart = localFrameObjectStackStart[localFramePoint];
|
54 |
+
for (int idx = frameObjStart + 1, ed = frameObjectPointer; idx <= ed; idx++) {
|
55 |
+
if (frameObjects[idx] == null) {
|
56 |
+
frameObjects[idx] = object;
|
57 |
+
return ObjectPool.LOCAL_OBJECT_PREFIX | idx;
|
58 |
+
}
|
59 |
+
}
|
60 |
+
|
61 |
+
// no empty slot found / empty slot was refilled
|
62 |
+
localFrame$hasObjectDeletion.clear(localFramePoint);
|
63 |
+
}
|
64 |
+
|
65 |
+
frameObjects[frameObjectPointer + 1] = object;
|
66 |
+
frameObjectPointer++;
|
67 |
+
|
68 |
+
return ObjectPool.LOCAL_OBJECT_PREFIX | frameObjectPointer;
|
69 |
+
}
|
70 |
+
|
71 |
+
public KuimiObject<?> deleteObject(int obj) {
|
72 |
+
{ // prefix check
|
73 |
+
var prefix = obj & ObjectPool.OBJECT_PREFIX;
|
74 |
+
if (prefix != ObjectPool.LOCAL_OBJECT_PREFIX) {
|
75 |
+
throw new IllegalArgumentException("Object " + obj + "(0x" + Integer.toHexString(obj) + ") is not a local frame object.");
|
76 |
+
}
|
77 |
+
}
|
78 |
+
|
79 |
+
var idx = obj & ~ObjectPool.OBJECT_PREFIX;
|
80 |
+
if (localFramePoint == -1) return null; // ?
|
81 |
+
|
82 |
+
var frameObjStart = localFrameObjectStackStart[localFramePoint];
|
83 |
+
if (idx > frameObjStart) {
|
84 |
+
var rsp = frameObjects[idx];
|
85 |
+
frameObjects[idx] = null;
|
86 |
+
localFrame$hasObjectDeletion.set(localFramePoint, true);
|
87 |
+
return rsp;
|
88 |
+
} else { // out of current frame.
|
89 |
+
throw new IllegalStateException("Object " + obj + "(0x" + Integer.toHexString(obj) + ")<" + idx + "> out of current frame, current frame start= " + frameObjStart);
|
90 |
+
}
|
91 |
+
}
|
92 |
+
|
93 |
+
public KuimiObject<?> resolve(int obj) {
|
94 |
+
{ // prefix check
|
95 |
+
var prefix = obj & ObjectPool.OBJECT_PREFIX;
|
96 |
+
if (prefix != ObjectPool.LOCAL_OBJECT_PREFIX) {
|
97 |
+
return null;
|
98 |
+
}
|
99 |
+
}
|
100 |
+
|
101 |
+
var idx = obj & ~ObjectPool.OBJECT_PREFIX;
|
102 |
+
if (idx <= frameObjectPointer) return frameObjects[idx];
|
103 |
+
return null;
|
104 |
+
}
|
105 |
+
|
106 |
+
public void popFrame() {
|
107 |
+
// todo: check stack trace
|
108 |
+
var fos = localFrameObjectStackStart[localFramePoint];
|
109 |
+
for (int i = fos + 1, ed = frameObjectPointer; i < ed; i++) {
|
110 |
+
frameObjects[i] = null;
|
111 |
+
}
|
112 |
+
frameObjectPointer = fos;
|
113 |
+
localFramePoint--;
|
114 |
+
}
|
115 |
+
|
116 |
+
public void leave() {
|
117 |
+
var stackTraceFrameStack = stackTrace$localFrameStart[stackPoint];
|
118 |
+
while (localFramePoint > stackTraceFrameStack) {
|
119 |
+
popFrame();
|
120 |
+
}
|
121 |
+
stackTrace$trace[stackPoint] = null;
|
122 |
+
stackPoint--;
|
123 |
+
}
|
124 |
+
|
125 |
+
public void leave(KuimiMethod method) {
|
126 |
+
if (stackTrace$trace[stackPoint] == method) {
|
127 |
+
leave();
|
128 |
+
} else {
|
129 |
+
throw new IllegalStateException();
|
130 |
+
}
|
131 |
+
}
|
132 |
+
|
133 |
+
|
134 |
+
public int getStackTracePoint() {
|
135 |
+
return stackPoint;
|
136 |
+
}
|
137 |
+
|
138 |
+
public int getLocalFramePoint() {
|
139 |
+
return localFramePoint;
|
140 |
+
}
|
141 |
+
|
142 |
+
public KuimiMethod getStackTraceMethod(int st) {
|
143 |
+
return stackTrace$trace[st];
|
144 |
+
}
|
145 |
+
}
|
146 |
+
|
src/main/java/com/kiliokuara/kuimivm/objects/KuimiArrays.java
ADDED
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm.objects;
|
2 |
+
|
3 |
+
import com.kiliokuara.kuimivm.KuimiClass;
|
4 |
+
import com.kiliokuara.kuimivm.KuimiObject;
|
5 |
+
import org.objectweb.asm.Opcodes;
|
6 |
+
import org.objectweb.asm.Type;
|
7 |
+
|
8 |
+
import java.util.HexFormat;
|
9 |
+
import java.util.function.IntFunction;
|
10 |
+
|
11 |
+
public class KuimiArrays {
|
12 |
+
public interface Array {
|
13 |
+
int length();
|
14 |
+
}
|
15 |
+
|
16 |
+
public static class ArrayObject extends KuimiObject<KuimiObject<?>[]> implements Array {
|
17 |
+
|
18 |
+
public ArrayObject(KuimiClass objectType, KuimiObject<?>[] data) {
|
19 |
+
super(objectType);
|
20 |
+
setDelegateInstance(data);
|
21 |
+
}
|
22 |
+
|
23 |
+
@Override
|
24 |
+
public int length() {
|
25 |
+
return getDelegateInstance().length;
|
26 |
+
}
|
27 |
+
}
|
28 |
+
|
29 |
+
public static class ArrayClass extends KuimiClass {
|
30 |
+
|
31 |
+
public ArrayClass(KuimiClass elmType) {
|
32 |
+
super(
|
33 |
+
elmType.getVm(), Type.getType("[" + elmType.getClassType().getDescriptor()),
|
34 |
+
Opcodes.ACC_PUBLIC,
|
35 |
+
elmType.getClassLoader(),
|
36 |
+
elmType.getVm().getBaseClass(),
|
37 |
+
null
|
38 |
+
);
|
39 |
+
}
|
40 |
+
|
41 |
+
@Override
|
42 |
+
public boolean isArray() {
|
43 |
+
return true;
|
44 |
+
}
|
45 |
+
|
46 |
+
@Override
|
47 |
+
public boolean isPrimitive() {
|
48 |
+
return false;
|
49 |
+
}
|
50 |
+
|
51 |
+
@Override
|
52 |
+
public KuimiObject<?> allocateNewObject() {
|
53 |
+
throw new RuntimeException("Allocating a array directly is not allowed");
|
54 |
+
}
|
55 |
+
|
56 |
+
public KuimiObject<?> allocateArray(int size) {
|
57 |
+
return new ArrayObject(this, new KuimiObject[size]);
|
58 |
+
}
|
59 |
+
}
|
60 |
+
|
61 |
+
public static class PrimitiveArrayClass extends ArrayClass {
|
62 |
+
|
63 |
+
private final IntFunction<Object> arrayAllocator;
|
64 |
+
|
65 |
+
public PrimitiveArrayClass(KuimiClass elmType, IntFunction<Object> arrayAllocator) {
|
66 |
+
super(elmType);
|
67 |
+
this.arrayAllocator = arrayAllocator;
|
68 |
+
}
|
69 |
+
|
70 |
+
@Override
|
71 |
+
public KuimiObject<?> allocateArray(int size) {
|
72 |
+
return new PrimitiveArray<>(this, arrayAllocator.apply(size));
|
73 |
+
}
|
74 |
+
}
|
75 |
+
|
76 |
+
public static class PrimitiveArray<T> extends KuimiObject<T> implements Array {
|
77 |
+
public PrimitiveArray(KuimiClass objectType, T array) {
|
78 |
+
super(objectType);
|
79 |
+
setDelegateInstance(array);
|
80 |
+
}
|
81 |
+
|
82 |
+
@Override
|
83 |
+
public int length() {
|
84 |
+
return java.lang.reflect.Array.getLength(getDelegateInstance());
|
85 |
+
}
|
86 |
+
|
87 |
+
@Override
|
88 |
+
public String toString() {
|
89 |
+
var dobj = getDelegateInstance();
|
90 |
+
if (dobj instanceof byte[] barr) {
|
91 |
+
var sb = new StringBuilder()
|
92 |
+
.append("byte[").append(barr.length).append("]@").append(hashCode()).append(": ");
|
93 |
+
|
94 |
+
HexFormat.ofDelimiter(" ").withUpperCase().formatHex(sb, barr);
|
95 |
+
return sb.toString();
|
96 |
+
}
|
97 |
+
return super.toString();
|
98 |
+
}
|
99 |
+
}
|
100 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/objects/KuimiString.java
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm.objects;
|
2 |
+
|
3 |
+
import com.kiliokuara.kuimivm.KuimiObject;
|
4 |
+
import com.kiliokuara.kuimivm.KuimiVM;
|
5 |
+
|
6 |
+
public class KuimiString extends KuimiObject<String> {
|
7 |
+
public KuimiString(KuimiVM kuimiVM, String data) {
|
8 |
+
super(kuimiVM.getStringClass());
|
9 |
+
super.setDelegateInstance(data);
|
10 |
+
}
|
11 |
+
|
12 |
+
@Override
|
13 |
+
public String toString() {
|
14 |
+
return getDelegateInstance();
|
15 |
+
}
|
16 |
+
|
17 |
+
@Override
|
18 |
+
public void setDelegateInstance(String delegateInstance) {
|
19 |
+
throw new UnsupportedOperationException();
|
20 |
+
}
|
21 |
+
|
22 |
+
|
23 |
+
public void forceChangeValue(String value) {
|
24 |
+
super.setDelegateInstance(value);
|
25 |
+
}
|
26 |
+
|
27 |
+
@Override
|
28 |
+
public int hashCode() {
|
29 |
+
return getDelegateInstance().hashCode();
|
30 |
+
}
|
31 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/runtime/ArrayAccess.java
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm.runtime;
|
2 |
+
|
3 |
+
import com.kiliokuara.kuimivm.KuimiClass;
|
4 |
+
import com.kiliokuara.kuimivm.KuimiObject;
|
5 |
+
|
6 |
+
public class ArrayAccess {
|
7 |
+
//@formatter:off
|
8 |
+
public static KuimiObject<?> AALOAD(KuimiObject<?> array, int index) {return ((KuimiObject<?>[]) array.getDelegateInstance())[index];}
|
9 |
+
public static byte BALOAD(KuimiObject<?> array, int index) {return ((byte[]) array.getDelegateInstance())[index];}
|
10 |
+
public static char CALOAD(KuimiObject<?> array, int index) {return ((char[]) array.getDelegateInstance())[index];}
|
11 |
+
public static short SALOAD(KuimiObject<?> array, int index) {return ((short[]) array.getDelegateInstance())[index];}
|
12 |
+
public static int IALOAD(KuimiObject<?> array, int index) {return ((int[]) array.getDelegateInstance())[index];}
|
13 |
+
public static long LALOAD(KuimiObject<?> array, int index) {return ((long[]) array.getDelegateInstance())[index];}
|
14 |
+
public static float FALOAD(KuimiObject<?> array, int index) {return ((float[]) array.getDelegateInstance())[index];}
|
15 |
+
public static double DALOAD(KuimiObject<?> array, int index) {return ((double[]) array.getDelegateInstance())[index];}
|
16 |
+
|
17 |
+
public static void AASTORE(KuimiObject<?> array, int index, KuimiObject<?> value) { ((KuimiObject<?>[]) array.getDelegateInstance())[index] = value;}
|
18 |
+
public static void BASTORE(KuimiObject<?> array, int index, byte value ) { ((byte[]) array.getDelegateInstance())[index] = value;}
|
19 |
+
public static void CASTORE(KuimiObject<?> array, int index, char value ) { ((char[]) array.getDelegateInstance())[index] = value;}
|
20 |
+
public static void SASTORE(KuimiObject<?> array, int index, short value ) { ((short[]) array.getDelegateInstance())[index] = value;}
|
21 |
+
public static void IASTORE(KuimiObject<?> array, int index, int value ) { ((int[]) array.getDelegateInstance())[index] = value;}
|
22 |
+
public static void LASTORE(KuimiObject<?> array, int index, long value ) { ((long[]) array.getDelegateInstance())[index] = value;}
|
23 |
+
public static void FASTORE(KuimiObject<?> array, int index, float value ) { ((float[]) array.getDelegateInstance())[index] = value;}
|
24 |
+
public static void DASTORE(KuimiObject<?> array, int index, double value ) { ((double[]) array.getDelegateInstance())[index] = value;}
|
25 |
+
//@formatter:on
|
26 |
+
|
27 |
+
public static KuimiObject<?> newArray(int size, KuimiClass type) {
|
28 |
+
return type.arrayType().allocateArray(size);
|
29 |
+
}
|
30 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/runtime/ClassInitEnsure.java
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm.runtime;
|
2 |
+
|
3 |
+
import com.kiliokuara.kuimivm.KuimiClass;
|
4 |
+
import com.kiliokuara.kuimivm.KuimiField;
|
5 |
+
import com.kiliokuara.kuimivm.KuimiMethod;
|
6 |
+
|
7 |
+
public class ClassInitEnsure {
|
8 |
+
public static KuimiClass ensureInit(KuimiClass kuimiClass) {
|
9 |
+
kuimiClass.ensureClassInitialized();
|
10 |
+
return kuimiClass;
|
11 |
+
}
|
12 |
+
|
13 |
+
public static KuimiField ensureInit(KuimiField field) {
|
14 |
+
field.getDeclaredClass().ensureClassInitialized();
|
15 |
+
return field;
|
16 |
+
}
|
17 |
+
|
18 |
+
public static KuimiMethod ensureInit(KuimiMethod method) {
|
19 |
+
method.getDeclaredClass().ensureClassInitialized();
|
20 |
+
return method;
|
21 |
+
}
|
22 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/runtime/FieldAccessBridge.java
ADDED
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm.runtime;
|
2 |
+
|
3 |
+
import com.kiliokuara.kuimivm.KuimiField;
|
4 |
+
import com.kiliokuara.kuimivm.KuimiObject;
|
5 |
+
|
6 |
+
public class FieldAccessBridge {
|
7 |
+
public static KuimiObject<?> getobject(KuimiObject<?> object, KuimiField field) {
|
8 |
+
return object.memoryView().objects[field.getObjectIndex()];
|
9 |
+
}
|
10 |
+
|
11 |
+
public static boolean getboolean(KuimiObject<?> object, KuimiField field) {
|
12 |
+
return object.memoryView().buffer.get((int) field.getOffset()) != 0;
|
13 |
+
}
|
14 |
+
|
15 |
+
public static byte getbyte(KuimiObject<?> object, KuimiField field) {
|
16 |
+
return object.memoryView().buffer.get((int) field.getOffset());
|
17 |
+
}
|
18 |
+
|
19 |
+
public static short getshort(KuimiObject<?> object, KuimiField field) {
|
20 |
+
return object.memoryView().buffer.getShort((int) field.getOffset());
|
21 |
+
}
|
22 |
+
|
23 |
+
public static char getchar(KuimiObject<?> object, KuimiField field) {
|
24 |
+
return object.memoryView().buffer.getChar((int) field.getOffset());
|
25 |
+
}
|
26 |
+
|
27 |
+
public static int getint(KuimiObject<?> object, KuimiField field) {
|
28 |
+
return object.memoryView().buffer.getInt((int) field.getOffset());
|
29 |
+
}
|
30 |
+
|
31 |
+
public static long getlong(KuimiObject<?> object, KuimiField field) {
|
32 |
+
return object.memoryView().buffer.getLong((int) field.getOffset());
|
33 |
+
}
|
34 |
+
|
35 |
+
public static float getfloat(KuimiObject<?> object, KuimiField field) {
|
36 |
+
return object.memoryView().buffer.getFloat((int) field.getOffset());
|
37 |
+
}
|
38 |
+
|
39 |
+
public static double getdouble(KuimiObject<?> object, KuimiField field) {
|
40 |
+
return object.memoryView().buffer.getDouble((int) field.getOffset());
|
41 |
+
}
|
42 |
+
|
43 |
+
//
|
44 |
+
|
45 |
+
public static void put(KuimiObject<?> thiz, byte val, KuimiField field) {
|
46 |
+
thiz.memoryView().buffer.put((int) field.getOffset(), val);
|
47 |
+
}
|
48 |
+
|
49 |
+
public static void put(KuimiObject<?> thiz, short val, KuimiField field) {
|
50 |
+
thiz.memoryView().buffer.putShort((int) field.getOffset(), val);
|
51 |
+
}
|
52 |
+
|
53 |
+
public static void put(KuimiObject<?> thiz, char val, KuimiField field) {
|
54 |
+
thiz.memoryView().buffer.putChar((int) field.getOffset(), val);
|
55 |
+
}
|
56 |
+
|
57 |
+
public static void put(KuimiObject<?> thiz, int val, KuimiField field) {
|
58 |
+
thiz.memoryView().buffer.putInt((int) field.getOffset(), val);
|
59 |
+
}
|
60 |
+
|
61 |
+
public static void put(KuimiObject<?> thiz, long val, KuimiField field) {
|
62 |
+
thiz.memoryView().buffer.putLong((int) field.getOffset(), val);
|
63 |
+
}
|
64 |
+
|
65 |
+
public static void put(KuimiObject<?> thiz, boolean val, KuimiField field) {
|
66 |
+
thiz.memoryView().buffer.put((int) field.getOffset(), val ? (byte) 1 : 0);
|
67 |
+
}
|
68 |
+
|
69 |
+
public static void put(KuimiObject<?> thiz, float val, KuimiField field) {
|
70 |
+
thiz.memoryView().buffer.putFloat((int) field.getOffset(), val);
|
71 |
+
}
|
72 |
+
|
73 |
+
public static void put(KuimiObject<?> thiz, double val, KuimiField field) {
|
74 |
+
thiz.memoryView().buffer.putDouble((int) field.getOffset(), val);
|
75 |
+
}
|
76 |
+
|
77 |
+
public static void put(KuimiObject<?> thiz, KuimiObject<?> val, KuimiField field) {
|
78 |
+
thiz.memoryView().objects[field.getObjectIndex()] = val;
|
79 |
+
}
|
80 |
+
|
81 |
+
|
82 |
+
//
|
83 |
+
|
84 |
+
public static void put(byte val, KuimiField field) {
|
85 |
+
put(field.getDeclaredClass(), val, field);
|
86 |
+
}
|
87 |
+
|
88 |
+
public static void put(short val, KuimiField field) {
|
89 |
+
put(field.getDeclaredClass(), val, field);
|
90 |
+
}
|
91 |
+
|
92 |
+
public static void put(char val, KuimiField field) {
|
93 |
+
put(field.getDeclaredClass(), val, field);
|
94 |
+
}
|
95 |
+
|
96 |
+
public static void put(int val, KuimiField field) {
|
97 |
+
put(field.getDeclaredClass(), val, field);
|
98 |
+
}
|
99 |
+
|
100 |
+
public static void put(long val, KuimiField field) {
|
101 |
+
put(field.getDeclaredClass(), val, field);
|
102 |
+
}
|
103 |
+
|
104 |
+
public static void put(boolean val, KuimiField field) {
|
105 |
+
put(field.getDeclaredClass(), val, field);
|
106 |
+
}
|
107 |
+
|
108 |
+
public static void put(float val, KuimiField field) {
|
109 |
+
put(field.getDeclaredClass(), val, field);
|
110 |
+
}
|
111 |
+
|
112 |
+
public static void put(double val, KuimiField field) {
|
113 |
+
put(field.getDeclaredClass(), val, field);
|
114 |
+
}
|
115 |
+
|
116 |
+
public static void put(KuimiObject<?> val, KuimiField field) {
|
117 |
+
put(field.getDeclaredClass(), val, field);
|
118 |
+
}
|
119 |
+
|
120 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/transform/JarTransformer.java
ADDED
@@ -0,0 +1,918 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm.transform;
|
2 |
+
|
3 |
+
import com.kiliokuara.kuimivm.*;
|
4 |
+
import com.kiliokuara.kuimivm.abstractvm.ClassPool;
|
5 |
+
import com.kiliokuara.kuimivm.execute.StackTrace;
|
6 |
+
import com.kiliokuara.kuimivm.abstractvm.KuimiAbstractVM;
|
7 |
+
import com.kiliokuara.kuimivm.objects.KuimiString;
|
8 |
+
import com.kiliokuara.kuimivm.runtime.ArrayAccess;
|
9 |
+
import com.kiliokuara.kuimivm.runtime.ClassInitEnsure;
|
10 |
+
import com.kiliokuara.kuimivm.runtime.FieldAccessBridge;
|
11 |
+
import com.kiliokuara.kuimivm.*;
|
12 |
+
import org.objectweb.asm.*;
|
13 |
+
import org.objectweb.asm.tree.ClassNode;
|
14 |
+
import org.objectweb.asm.tree.MethodNode;
|
15 |
+
import org.objectweb.asm.util.Textifier;
|
16 |
+
import org.objectweb.asm.util.TraceClassVisitor;
|
17 |
+
|
18 |
+
import java.io.IOException;
|
19 |
+
import java.io.PrintWriter;
|
20 |
+
import java.lang.invoke.MethodHandle;
|
21 |
+
import java.lang.reflect.Modifier;
|
22 |
+
import java.nio.file.Files;
|
23 |
+
import java.nio.file.Path;
|
24 |
+
import java.util.*;
|
25 |
+
import java.util.zip.ZipInputStream;
|
26 |
+
|
27 |
+
|
28 |
+
@SuppressWarnings("DuplicatedCode")
|
29 |
+
public class JarTransformer {
|
30 |
+
private static final Type KUIMI_VM = Type.getType(KuimiVM.class);
|
31 |
+
private static final Type STACK_TRACE = Type.getType(StackTrace.class);
|
32 |
+
private static final Type KUIMI_OBJECT = Type.getType(KuimiObject.class);
|
33 |
+
private static final Type KUIMI_CLASS = Type.getType(KuimiClass.class);
|
34 |
+
private static final Type KUIMI_FIELD = Type.getType(KuimiField.class);
|
35 |
+
private static final Type KUIMI_FIELD_TABLE = Type.getType(KuimiFieldTable.class);
|
36 |
+
private static final Type KUIMI_METHOD = Type.getType(KuimiMethod.class);
|
37 |
+
private static final Type KUIMI_METHOD_TABLE = Type.getType(KuimiMethodTable.class);
|
38 |
+
private static final Type ARRAY_ACCESS_BRIDGE = Type.getType(ArrayAccess.class);
|
39 |
+
private static final Type FIELD_ACCESS_BRIDGE = Type.getType(FieldAccessBridge.class);
|
40 |
+
private static final Type CLASS_INIT_ENSURE = Type.getType(ClassInitEnsure.class);
|
41 |
+
private static final Type TYPE = Type.getType(Type.class);
|
42 |
+
private static final Type STRING = Type.getType(String.class);
|
43 |
+
|
44 |
+
|
45 |
+
private final Map<String, ClassNode> classes;
|
46 |
+
private final Map<String, byte[]> output;
|
47 |
+
private boolean debug;
|
48 |
+
|
49 |
+
public JarTransformer(Map<String, ClassNode> classes) {
|
50 |
+
this.classes = classes;
|
51 |
+
output = new HashMap<>();
|
52 |
+
}
|
53 |
+
|
54 |
+
public JarTransformer() {
|
55 |
+
this(new HashMap<>());
|
56 |
+
}
|
57 |
+
|
58 |
+
public void merge(Map<String, ClassNode> classes) {
|
59 |
+
this.classes.putAll(classes);
|
60 |
+
}
|
61 |
+
|
62 |
+
public void loadFrom(ZipInputStream zipInputStream) throws IOException {
|
63 |
+
while (true) {
|
64 |
+
var entry = zipInputStream.getNextEntry();
|
65 |
+
if (entry == null) break;
|
66 |
+
if (!entry.getName().endsWith(".class")) continue;
|
67 |
+
|
68 |
+
var cr = new ClassNode();
|
69 |
+
new ClassReader(zipInputStream.readAllBytes()).accept(cr, 0);
|
70 |
+
|
71 |
+
classes.put(cr.name, cr);
|
72 |
+
}
|
73 |
+
}
|
74 |
+
|
75 |
+
public void setDebug(boolean debug) {
|
76 |
+
this.debug = debug;
|
77 |
+
}
|
78 |
+
|
79 |
+
protected void generateClassStructure_classRegister(MethodVisitor visitor) {
|
80 |
+
var absVM = Type.getType(KuimiAbstractVM.class).getInternalName();
|
81 |
+
visitor.visitVarInsn(Opcodes.ALOAD, 0);
|
82 |
+
visitor.visitTypeInsn(Opcodes.CHECKCAST, absVM);
|
83 |
+
|
84 |
+
var classPool = Type.getType(ClassPool.class);
|
85 |
+
visitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, absVM, "getBootstrapPool", Type.getMethodDescriptor(classPool), false);
|
86 |
+
|
87 |
+
visitor.visitVarInsn(Opcodes.ALOAD, 1);
|
88 |
+
visitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, classPool.getInternalName(), "put", Type.getMethodDescriptor(Type.VOID_TYPE, KUIMI_CLASS), false);
|
89 |
+
|
90 |
+
visitor.visitInsn(Opcodes.RETURN);
|
91 |
+
visitor.visitMaxs(0, 0);
|
92 |
+
visitor.visitEnd();
|
93 |
+
}
|
94 |
+
|
95 |
+
private static void pushInt(MethodVisitor visitor, int value) {
|
96 |
+
if (value == -1) {
|
97 |
+
visitor.visitInsn(Opcodes.ICONST_M1);
|
98 |
+
} else if (value >= 0 && value <= 5) {
|
99 |
+
visitor.visitInsn(Opcodes.ICONST_0 + value);
|
100 |
+
} else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
|
101 |
+
visitor.visitIntInsn(Opcodes.BIPUSH, value);
|
102 |
+
} else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
|
103 |
+
visitor.visitIntInsn(Opcodes.SIPUSH, value);
|
104 |
+
} else {
|
105 |
+
visitor.visitLdcInsn(value);
|
106 |
+
}
|
107 |
+
}
|
108 |
+
|
109 |
+
public void generateClassStructure(Type outputName) {
|
110 |
+
if (outputName == null) {
|
111 |
+
outputName = Type.getObjectType("com/kiliokuara/kuimivm/transform/gen/" + UUID.randomUUID());
|
112 |
+
}
|
113 |
+
var outInternalName = outputName.getInternalName();
|
114 |
+
|
115 |
+
|
116 |
+
var classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
|
117 |
+
classWriter.visit(Opcodes.V11, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, outInternalName, null, "java/lang/Object", null);
|
118 |
+
|
119 |
+
{
|
120 |
+
var classRegister = classWriter.visitMethod(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, "classRegister", Type.getMethodDescriptor(Type.VOID_TYPE, KUIMI_VM, KUIMI_CLASS), null, null);
|
121 |
+
generateClassStructure_classRegister(classRegister);
|
122 |
+
}
|
123 |
+
|
124 |
+
{
|
125 |
+
// static KuimiClass loadClass(KuimiVM vm, String name, KuimiObject<?> classLoader)
|
126 |
+
var loadClass = classWriter.visitMethod(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, "loadClass", Type.getMethodDescriptor(KUIMI_CLASS, KUIMI_VM, STRING, KUIMI_OBJECT), null, null);
|
127 |
+
|
128 |
+
{
|
129 |
+
loadClass.visitVarInsn(Opcodes.ALOAD, 0);
|
130 |
+
loadClass.visitVarInsn(Opcodes.ALOAD, 1);
|
131 |
+
loadClass.visitMethodInsn(Opcodes.INVOKESTATIC, TYPE.getInternalName(), "getObjectType", Type.getMethodDescriptor(TYPE, STRING), false);
|
132 |
+
loadClass.visitMethodInsn(Opcodes.INVOKEVIRTUAL, KUIMI_VM.getInternalName(), "resolveClass", Type.getMethodDescriptor(KUIMI_CLASS, TYPE), false);
|
133 |
+
|
134 |
+
var cont = new Label();
|
135 |
+
loadClass.visitInsn(Opcodes.DUP);
|
136 |
+
loadClass.visitJumpInsn(Opcodes.IFNULL, cont);
|
137 |
+
loadClass.visitInsn(Opcodes.ARETURN);
|
138 |
+
|
139 |
+
loadClass.visitLabel(cont);
|
140 |
+
loadClass.visitInsn(Opcodes.POP);
|
141 |
+
}
|
142 |
+
|
143 |
+
{
|
144 |
+
var hashCodeToString = new HashMap<Integer, Set<String>>();
|
145 |
+
var hashCodeToLabel = new HashMap<Integer, Label>();
|
146 |
+
var classNameToLabel = new HashMap<String, Label>();
|
147 |
+
var failLabel = new Label();
|
148 |
+
|
149 |
+
for (var klass : classes.values()) {
|
150 |
+
var hcode = klass.name.hashCode();
|
151 |
+
hashCodeToString.computeIfAbsent(hcode, $ -> new HashSet<>()).add(klass.name);
|
152 |
+
classNameToLabel.put(klass.name, new Label());
|
153 |
+
}
|
154 |
+
for (var hcode : hashCodeToString.keySet()) {
|
155 |
+
hashCodeToLabel.put(hcode, new Label());
|
156 |
+
}
|
157 |
+
|
158 |
+
loadClass.visitVarInsn(Opcodes.ALOAD, 1);
|
159 |
+
loadClass.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STRING.getInternalName(), "hashCode", "()I", false);
|
160 |
+
{
|
161 |
+
var tableLookupKeys = new int[hashCodeToLabel.size()];
|
162 |
+
var tableLookupTargets = new Label[tableLookupKeys.length];
|
163 |
+
var i = 0;
|
164 |
+
for (var entry : hashCodeToLabel.entrySet().stream().sorted(Map.Entry.comparingByKey()).toList()) {
|
165 |
+
tableLookupKeys[i] = entry.getKey();
|
166 |
+
tableLookupTargets[i] = entry.getValue();
|
167 |
+
i++;
|
168 |
+
}
|
169 |
+
loadClass.visitLookupSwitchInsn(failLabel, tableLookupKeys, tableLookupTargets);
|
170 |
+
}
|
171 |
+
|
172 |
+
for (var h2labelEntry : hashCodeToLabel.entrySet()) {
|
173 |
+
loadClass.visitLabel(h2labelEntry.getValue());
|
174 |
+
var strs = hashCodeToString.get(h2labelEntry.getKey());
|
175 |
+
|
176 |
+
for (var str : strs) {
|
177 |
+
loadClass.visitLdcInsn(str);
|
178 |
+
loadClass.visitVarInsn(Opcodes.ALOAD, 1);
|
179 |
+
loadClass.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false);
|
180 |
+
loadClass.visitJumpInsn(Opcodes.IFNE, classNameToLabel.get(str));
|
181 |
+
}
|
182 |
+
loadClass.visitJumpInsn(Opcodes.GOTO, failLabel);
|
183 |
+
}
|
184 |
+
|
185 |
+
loadClass.visitLabel(failLabel);
|
186 |
+
loadClass.visitTypeInsn(Opcodes.NEW, "java/lang/UnsupportedOperationException");
|
187 |
+
loadClass.visitInsn(Opcodes.DUP);
|
188 |
+
loadClass.visitVarInsn(Opcodes.ALOAD, 1);
|
189 |
+
loadClass.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/UnsupportedOperationException", "<init>", "(Ljava/lang/String;)V", false);
|
190 |
+
loadClass.visitInsn(Opcodes.ATHROW);
|
191 |
+
|
192 |
+
for (var klass : classes.values()) {
|
193 |
+
loadClass.visitLabel(classNameToLabel.get(klass.name));
|
194 |
+
|
195 |
+
|
196 |
+
loadClass.visitTypeInsn(Opcodes.NEW, KUIMI_CLASS.getInternalName());
|
197 |
+
loadClass.visitInsn(Opcodes.DUP);
|
198 |
+
|
199 |
+
loadClass.visitVarInsn(Opcodes.ALOAD, 0);
|
200 |
+
loadClass.visitLdcInsn(klass.name);
|
201 |
+
loadClass.visitMethodInsn(Opcodes.INVOKESTATIC, TYPE.getInternalName(), "getObjectType", Type.getMethodDescriptor(TYPE, STRING), false);
|
202 |
+
pushInt(loadClass, klass.access);
|
203 |
+
loadClass.visitVarInsn(Opcodes.ALOAD, 2); // classLoader
|
204 |
+
|
205 |
+
// parent class
|
206 |
+
loadClass.visitVarInsn(Opcodes.ALOAD, 0);
|
207 |
+
loadClass.visitLdcInsn(klass.superName);
|
208 |
+
loadClass.visitVarInsn(Opcodes.ALOAD, 2);
|
209 |
+
loadClass.visitMethodInsn(Opcodes.INVOKESTATIC, outInternalName, "loadClass", Type.getMethodDescriptor(KUIMI_CLASS, KUIMI_VM, STRING, KUIMI_OBJECT), false);
|
210 |
+
|
211 |
+
// interfaces
|
212 |
+
if (klass.interfaces == null || klass.interfaces.isEmpty()) {
|
213 |
+
loadClass.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/List", "of", "()Ljava/util/List;", true);
|
214 |
+
} else {
|
215 |
+
loadClass.visitTypeInsn(Opcodes.NEW, "java/util/ArrayList");
|
216 |
+
loadClass.visitInsn(Opcodes.DUP);
|
217 |
+
loadClass.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/util/ArrayList", "<init>", "()V", false);
|
218 |
+
|
219 |
+
for (var itf : klass.interfaces) {
|
220 |
+
loadClass.visitInsn(Opcodes.DUP);
|
221 |
+
|
222 |
+
loadClass.visitVarInsn(Opcodes.ALOAD, 0);
|
223 |
+
loadClass.visitLdcInsn(itf);
|
224 |
+
loadClass.visitVarInsn(Opcodes.ALOAD, 2);
|
225 |
+
loadClass.visitMethodInsn(Opcodes.INVOKESTATIC, outInternalName, "loadClass", Type.getMethodDescriptor(KUIMI_CLASS, KUIMI_VM, STRING, KUIMI_OBJECT), false);
|
226 |
+
|
227 |
+
loadClass.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/util/ArrayList", "add", "(Ljava/lang/Object;)Z", false);
|
228 |
+
loadClass.visitInsn(Opcodes.POP);
|
229 |
+
}
|
230 |
+
}
|
231 |
+
|
232 |
+
loadClass.visitMethodInsn(Opcodes.INVOKESPECIAL, KUIMI_CLASS.getInternalName(), "<init>", Type.getMethodDescriptor(Type.VOID_TYPE, KUIMI_VM, TYPE, Type.INT_TYPE, KUIMI_OBJECT, KUIMI_CLASS, Type.getObjectType("java/util/List")), false);
|
233 |
+
loadClass.visitInsn(Opcodes.DUP);
|
234 |
+
|
235 |
+
loadClass.visitVarInsn(Opcodes.ALOAD, 0);
|
236 |
+
loadClass.visitInsn(Opcodes.SWAP);
|
237 |
+
|
238 |
+
loadClass.visitMethodInsn(Opcodes.INVOKESTATIC, outInternalName, "classRegister", Type.getMethodDescriptor(Type.VOID_TYPE, KUIMI_VM, KUIMI_CLASS), false);
|
239 |
+
|
240 |
+
loadClass.visitInsn(Opcodes.ARETURN);
|
241 |
+
}
|
242 |
+
|
243 |
+
loadClass.visitMaxs(0, 0);
|
244 |
+
|
245 |
+
}
|
246 |
+
|
247 |
+
|
248 |
+
var tool = new Object() {
|
249 |
+
public void resolveClass(MethodVisitor init, Type type) {
|
250 |
+
init.visitVarInsn(Opcodes.ALOAD, 0);
|
251 |
+
init.visitLdcInsn(type.getDescriptor());
|
252 |
+
init.visitMethodInsn(Opcodes.INVOKESTATIC, TYPE.getInternalName(), "getType", Type.getMethodDescriptor(TYPE, STRING), false);
|
253 |
+
init.visitMethodInsn(Opcodes.INVOKEVIRTUAL, KUIMI_VM.getInternalName(), "resolveClass", Type.getMethodDescriptor(KUIMI_CLASS, TYPE), false);
|
254 |
+
}
|
255 |
+
|
256 |
+
void directLoadClass(MethodVisitor init, String name) {
|
257 |
+
|
258 |
+
init.visitVarInsn(Opcodes.ALOAD, 0);
|
259 |
+
init.visitLdcInsn(name);
|
260 |
+
init.visitVarInsn(Opcodes.ALOAD, 1);
|
261 |
+
init.visitMethodInsn(Opcodes.INVOKESTATIC, outInternalName, "loadClass", Type.getMethodDescriptor(KUIMI_CLASS, KUIMI_VM, STRING, KUIMI_OBJECT), false);
|
262 |
+
|
263 |
+
}
|
264 |
+
};
|
265 |
+
|
266 |
+
// static void init(KuimiVM vm, KuimiObject<?> classLoader)
|
267 |
+
var init = classWriter.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "initialize", Type.getMethodDescriptor(Type.VOID_TYPE, KUIMI_VM, KUIMI_OBJECT), null, null);
|
268 |
+
for (var klass : classes.values()) {
|
269 |
+
tool.directLoadClass(init, klass.name);
|
270 |
+
init.visitInsn(Opcodes.POP);
|
271 |
+
}
|
272 |
+
|
273 |
+
for (var kclass : classes.values()) {
|
274 |
+
tool.directLoadClass(init, kclass.name);
|
275 |
+
|
276 |
+
if (debug) {
|
277 |
+
init.visitInsn(Opcodes.DUP);
|
278 |
+
init.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
|
279 |
+
init.visitInsn(Opcodes.SWAP);
|
280 |
+
init.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
|
281 |
+
}
|
282 |
+
|
283 |
+
// @context: arg2 = <current-class>
|
284 |
+
init.visitInsn(Opcodes.DUP);
|
285 |
+
init.visitVarInsn(Opcodes.ASTORE, 2);
|
286 |
+
|
287 |
+
init.visitInsn(Opcodes.DUP); // for method table
|
288 |
+
|
289 |
+
// region fields
|
290 |
+
init.visitInsn(Opcodes.DUP);
|
291 |
+
init.visitMethodInsn(Opcodes.INVOKEVIRTUAL, KUIMI_CLASS.getInternalName(), "getFieldTable", Type.getMethodDescriptor(KUIMI_FIELD_TABLE), false);
|
292 |
+
|
293 |
+
for (var field : kclass.fields) {
|
294 |
+
init.visitInsn(Opcodes.DUP);
|
295 |
+
|
296 |
+
init.visitTypeInsn(Opcodes.NEW, KUIMI_FIELD.getInternalName());
|
297 |
+
init.visitInsn(Opcodes.DUP);
|
298 |
+
|
299 |
+
init.visitVarInsn(Opcodes.ALOAD, 2);// declared class
|
300 |
+
pushInt(init, field.access);
|
301 |
+
init.visitLdcInsn(field.name);
|
302 |
+
tool.resolveClass(init, Type.getType(field.desc));
|
303 |
+
init.visitMethodInsn(Opcodes.INVOKESPECIAL, KUIMI_FIELD.getInternalName(), "<init>", Type.getMethodDescriptor(Type.VOID_TYPE, KUIMI_CLASS, Type.INT_TYPE, STRING, KUIMI_CLASS), false);
|
304 |
+
|
305 |
+
if (debug) {
|
306 |
+
init.visitInsn(Opcodes.DUP);
|
307 |
+
init.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
|
308 |
+
init.visitLdcInsn(" |- Adding field ");
|
309 |
+
init.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "append", "(Ljava/lang/CharSequence;)Ljava/io/PrintStream;", false);
|
310 |
+
init.visitInsn(Opcodes.SWAP);
|
311 |
+
init.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
|
312 |
+
}
|
313 |
+
|
314 |
+
init.visitMethodInsn(Opcodes.INVOKEVIRTUAL, KUIMI_FIELD_TABLE.getInternalName(), "addField", Type.getMethodDescriptor(Type.VOID_TYPE, KUIMI_FIELD), false);
|
315 |
+
}
|
316 |
+
|
317 |
+
init.visitInsn(Opcodes.POP); // pop field table
|
318 |
+
// endregion
|
319 |
+
|
320 |
+
init.visitMethodInsn(Opcodes.INVOKEVIRTUAL, KUIMI_CLASS.getInternalName(), "getMethodTable", Type.getMethodDescriptor(KUIMI_METHOD_TABLE), false);
|
321 |
+
|
322 |
+
for (var method : kclass.methods) {
|
323 |
+
init.visitInsn(Opcodes.DUP);
|
324 |
+
|
325 |
+
init.visitTypeInsn(Opcodes.NEW, KUIMI_METHOD.getInternalName());
|
326 |
+
init.visitInsn(Opcodes.DUP);
|
327 |
+
|
328 |
+
init.visitVarInsn(Opcodes.ALOAD, 2);// declared class
|
329 |
+
pushInt(init, method.access);
|
330 |
+
init.visitLdcInsn(method.name);
|
331 |
+
tool.resolveClass(init, Type.getReturnType(method.desc));
|
332 |
+
var params = Type.getArgumentTypes(method.desc);
|
333 |
+
if (params.length == 0) {
|
334 |
+
init.visitMethodInsn(Opcodes.INVOKESTATIC, "java/util/List", "of", "()Ljava/util/List;", true);
|
335 |
+
} else {
|
336 |
+
init.visitTypeInsn(Opcodes.NEW, "java/util/ArrayList");
|
337 |
+
init.visitInsn(Opcodes.DUP);
|
338 |
+
init.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/util/ArrayList", "<init>", "()V", false);
|
339 |
+
|
340 |
+
for (var param : params) {
|
341 |
+
init.visitInsn(Opcodes.DUP);
|
342 |
+
tool.resolveClass(init, param);
|
343 |
+
init.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/util/ArrayList", "add", "(Ljava/lang/Object;)Z", false);
|
344 |
+
init.visitInsn(Opcodes.POP);
|
345 |
+
}
|
346 |
+
}
|
347 |
+
init.visitMethodInsn(Opcodes.INVOKESPECIAL, KUIMI_METHOD.getInternalName(), "<init>", Type.getMethodDescriptor(Type.VOID_TYPE, KUIMI_CLASS, Type.INT_TYPE, STRING, KUIMI_CLASS, Type.getType(List.class)), false);
|
348 |
+
|
349 |
+
|
350 |
+
init.visitMethodInsn(Opcodes.INVOKEVIRTUAL, KUIMI_METHOD_TABLE.getInternalName(), "addMethod", Type.getMethodDescriptor(KUIMI_METHOD, KUIMI_METHOD), false);
|
351 |
+
|
352 |
+
if (debug) {
|
353 |
+
init.visitInsn(Opcodes.DUP);
|
354 |
+
init.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
|
355 |
+
init.visitLdcInsn(" |- Adding method ");
|
356 |
+
init.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "append", "(Ljava/lang/CharSequence;)Ljava/io/PrintStream;", false);
|
357 |
+
init.visitInsn(Opcodes.SWAP);
|
358 |
+
init.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
|
359 |
+
}
|
360 |
+
if (method.instructions != null && method.instructions.size() > 0) {
|
361 |
+
if (debug) {
|
362 |
+
init.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
|
363 |
+
init.visitLdcInsn(" | `- Method code found");
|
364 |
+
init.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
|
365 |
+
}
|
366 |
+
|
367 |
+
var wcpName = generateMethod(method, kclass);
|
368 |
+
var paramTypes = getParamsType(method);
|
369 |
+
|
370 |
+
init.visitLdcInsn(new Handle(
|
371 |
+
Opcodes.H_INVOKESTATIC, wcpName, "execute", Type.getMethodDescriptor(
|
372 |
+
paramTypes.remove(0), paramTypes.toArray(Type[]::new)
|
373 |
+
), false));
|
374 |
+
|
375 |
+
init.visitMethodInsn(Opcodes.INVOKEVIRTUAL, KUIMI_METHOD.getInternalName(), "attachImplementation", Type.getMethodDescriptor(KUIMI_METHOD, Type.getType(MethodHandle.class)), false);
|
376 |
+
}
|
377 |
+
init.visitInsn(Opcodes.POP);
|
378 |
+
}
|
379 |
+
|
380 |
+
init.visitInsn(Opcodes.POP); // pop method table
|
381 |
+
|
382 |
+
}
|
383 |
+
|
384 |
+
init.visitInsn(Opcodes.RETURN);
|
385 |
+
init.visitMaxs(0, 0);
|
386 |
+
}
|
387 |
+
|
388 |
+
|
389 |
+
output.put(outInternalName, classWriter.toByteArray());
|
390 |
+
}
|
391 |
+
|
392 |
+
public Map<String, byte[]> getOutput() {
|
393 |
+
return output;
|
394 |
+
}
|
395 |
+
|
396 |
+
private String generateMethod(MethodNode method, ClassNode kclass) {
|
397 |
+
var wcpName = kclass.name + "$met" + method.name.replace('<', '_').replace('>', '_');
|
398 |
+
{
|
399 |
+
var counter = 0;
|
400 |
+
var baseName = wcpName + '$';
|
401 |
+
while (output.containsKey(wcpName)) {
|
402 |
+
wcpName = baseName + counter;
|
403 |
+
counter++;
|
404 |
+
}
|
405 |
+
}
|
406 |
+
|
407 |
+
try {
|
408 |
+
var cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
|
409 |
+
|
410 |
+
generateMethodImpl(method, cw, wcpName);
|
411 |
+
|
412 |
+
output.put(wcpName, cw.toByteArray());
|
413 |
+
|
414 |
+
return wcpName;
|
415 |
+
} catch (Throwable throwable) {
|
416 |
+
method.instructions.resetLabels();
|
417 |
+
var cw = new ClassWriter(0);
|
418 |
+
|
419 |
+
// TODO: output
|
420 |
+
generateMethodImpl(method, new TraceClassVisitor(cw, new Textifier(), new PrintWriter(System.out, true)), wcpName);
|
421 |
+
|
422 |
+
if (true) { // FIXME
|
423 |
+
var shorten = wcpName.substring(wcpName.lastIndexOf('/') + 1);
|
424 |
+
System.out.println(shorten);
|
425 |
+
try {
|
426 |
+
Files.write(Path.of("B:/vmts", shorten + ".class"), cw.toByteArray());
|
427 |
+
} catch (IOException e) {
|
428 |
+
throwable.addSuppressed(e);
|
429 |
+
throw throwable;
|
430 |
+
}
|
431 |
+
}
|
432 |
+
|
433 |
+
output.put(wcpName, cw.toByteArray());
|
434 |
+
|
435 |
+
throw throwable;
|
436 |
+
}
|
437 |
+
}
|
438 |
+
|
439 |
+
|
440 |
+
private ArrayList<Type> getParamsType(MethodNode method) {
|
441 |
+
var mtdesc = Type.getMethodType(method.desc);
|
442 |
+
|
443 |
+
var params = new ArrayList<Type>();
|
444 |
+
params.add(mtdesc.getReturnType());
|
445 |
+
if (!Modifier.isStatic(method.access)) {
|
446 |
+
params.add(Type.getObjectType("java/lang/Object"));
|
447 |
+
}
|
448 |
+
|
449 |
+
params.addAll(List.of(mtdesc.getArgumentTypes()));
|
450 |
+
var kuimiObject = Type.getType(KuimiObject.class);
|
451 |
+
|
452 |
+
params.replaceAll(type -> {
|
453 |
+
var sort = type.getSort();
|
454 |
+
if (sort == Type.ARRAY || sort == Type.OBJECT) return kuimiObject;
|
455 |
+
return type;
|
456 |
+
});
|
457 |
+
// var rtType = params.remove(0);
|
458 |
+
|
459 |
+
|
460 |
+
params.add(1, Type.getType(KuimiVM.class));
|
461 |
+
params.add(2, Type.getType(StackTrace.class));
|
462 |
+
|
463 |
+
return params;
|
464 |
+
}
|
465 |
+
|
466 |
+
private void generateMethodImpl(MethodNode method, ClassVisitor cw, String wcpName) {
|
467 |
+
cw.visit(
|
468 |
+
Opcodes.V11,
|
469 |
+
Opcodes.ACC_PUBLIC,
|
470 |
+
wcpName,
|
471 |
+
null, "java/lang/Object", null
|
472 |
+
);
|
473 |
+
|
474 |
+
var params = getParamsType(method);
|
475 |
+
var rtType = params.remove(0);
|
476 |
+
|
477 |
+
cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, "initialized", "Z", null, null);
|
478 |
+
|
479 |
+
var dataInit = cw.visitMethod(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, "init", Type.getMethodDescriptor(
|
480 |
+
Type.VOID_TYPE, Type.getType(KuimiVM.class)
|
481 |
+
), null, null);
|
482 |
+
|
483 |
+
var postDataInit = new MethodNode();
|
484 |
+
|
485 |
+
|
486 |
+
{
|
487 |
+
dataInit.visitFieldInsn(Opcodes.GETSTATIC, wcpName, "initialized", "Z");
|
488 |
+
var endLabel = new Label();
|
489 |
+
dataInit.visitJumpInsn(Opcodes.IFEQ, endLabel);
|
490 |
+
dataInit.visitInsn(Opcodes.RETURN);
|
491 |
+
dataInit.visitLabel(endLabel);
|
492 |
+
dataInit.visitFrame(Opcodes.F_SAME, 1, null, 0, null);
|
493 |
+
}
|
494 |
+
|
495 |
+
method.accept(new ClassVisitor(Opcodes.ASM9, cw) {
|
496 |
+
@Override
|
497 |
+
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
498 |
+
var mv = super.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "execute", Type.getMethodDescriptor(
|
499 |
+
rtType, params.toArray(Type[]::new)
|
500 |
+
), signature, exceptions);
|
501 |
+
|
502 |
+
|
503 |
+
var typeFieldMapping = new HashMap<String, String>() {
|
504 |
+
String map(String type) {
|
505 |
+
return map(Type.getObjectType(type));
|
506 |
+
}
|
507 |
+
|
508 |
+
String map(Type type) {
|
509 |
+
var rsp = get(type.getDescriptor());
|
510 |
+
if (rsp != null) return rsp;
|
511 |
+
|
512 |
+
var name = "t" + size();
|
513 |
+
cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, name, KUIMI_CLASS.getDescriptor(), null, null);
|
514 |
+
|
515 |
+
dataInit.visitVarInsn(Opcodes.ALOAD, 0);
|
516 |
+
dataInit.visitLdcInsn(type.getDescriptor());
|
517 |
+
dataInit.visitMethodInsn(Opcodes.INVOKESTATIC, TYPE.getInternalName(), "getType", Type.getMethodDescriptor(TYPE, STRING), false);
|
518 |
+
dataInit.visitMethodInsn(Opcodes.INVOKEVIRTUAL, KUIMI_VM.getInternalName(), "resolveClass", Type.getMethodDescriptor(
|
519 |
+
KUIMI_CLASS, TYPE
|
520 |
+
), false);
|
521 |
+
dataInit.visitFieldInsn(Opcodes.PUTSTATIC, wcpName, name, KUIMI_CLASS.getDescriptor());
|
522 |
+
|
523 |
+
put(type.getDescriptor(), name);
|
524 |
+
return name;
|
525 |
+
}
|
526 |
+
|
527 |
+
void loadType(MethodVisitor mv, String name) {
|
528 |
+
mv.visitFieldInsn(Opcodes.GETSTATIC, wcpName, map(name), KUIMI_CLASS.getDescriptor());
|
529 |
+
}
|
530 |
+
|
531 |
+
void loadType(MethodVisitor mv, Type type) {
|
532 |
+
mv.visitFieldInsn(Opcodes.GETSTATIC, wcpName, map(type), KUIMI_CLASS.getDescriptor());
|
533 |
+
}
|
534 |
+
};
|
535 |
+
var metMapping = new HashMap<String, String>() {
|
536 |
+
String map(boolean isStatic, String owner, String name, String descriptor) {
|
537 |
+
var key = isStatic + "-" + owner + "." + name + descriptor;
|
538 |
+
var rsp = get(key);
|
539 |
+
if (rsp != null) return rsp;
|
540 |
+
|
541 |
+
rsp = "m" + size();
|
542 |
+
|
543 |
+
|
544 |
+
cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, rsp, KUIMI_METHOD.getDescriptor(), null, null);
|
545 |
+
|
546 |
+
typeFieldMapping.loadType(postDataInit, owner);
|
547 |
+
getMethodTable(postDataInit);
|
548 |
+
|
549 |
+
postDataInit.visitLdcInsn(name);
|
550 |
+
|
551 |
+
typeFieldMapping.loadType(postDataInit, Type.getReturnType(descriptor));
|
552 |
+
|
553 |
+
postDataInit.visitTypeInsn(Opcodes.NEW, "java/util/ArrayList");
|
554 |
+
postDataInit.visitInsn(Opcodes.DUP);
|
555 |
+
postDataInit.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/util/ArrayList", "<init>", "()V", false);
|
556 |
+
|
557 |
+
for (var param : Type.getArgumentTypes(descriptor)) {
|
558 |
+
postDataInit.visitInsn(Opcodes.DUP);
|
559 |
+
typeFieldMapping.loadType(postDataInit, param);
|
560 |
+
postDataInit.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/util/ArrayList", "add", "(Ljava/lang/Object;)Z", false);
|
561 |
+
postDataInit.visitInsn(Opcodes.POP);
|
562 |
+
}
|
563 |
+
|
564 |
+
postDataInit.visitInsn(isStatic ? Opcodes.ICONST_1 : Opcodes.ICONST_0);
|
565 |
+
// public KuimiMethod resolveMethod(String name, KuimiClass retType, List<KuimiClass> params, boolean isStatic)
|
566 |
+
|
567 |
+
postDataInit.visitMethodInsn(Opcodes.INVOKEVIRTUAL, KUIMI_METHOD_TABLE.getInternalName(), "resolveMethod", Type.getMethodDescriptor(
|
568 |
+
KUIMI_METHOD, STRING, KUIMI_CLASS, Type.getType(List.class), Type.BOOLEAN_TYPE
|
569 |
+
), false);
|
570 |
+
postDataInit.visitFieldInsn(Opcodes.PUTSTATIC, wcpName, rsp, KUIMI_METHOD.getDescriptor());
|
571 |
+
|
572 |
+
put(key, rsp);
|
573 |
+
return rsp;
|
574 |
+
}
|
575 |
+
|
576 |
+
void getMethodTable(MethodVisitor mv) {
|
577 |
+
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, KUIMI_CLASS.getInternalName(), "getMethodTable", Type.getMethodDescriptor(KUIMI_METHOD_TABLE), false);
|
578 |
+
}
|
579 |
+
};
|
580 |
+
var objMap = new HashMap<String, String>() {
|
581 |
+
String ldcField(String value) {
|
582 |
+
var rsp = get(value);
|
583 |
+
if (rsp != null) return rsp;
|
584 |
+
|
585 |
+
rsp = "str" + size();
|
586 |
+
cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, rsp, KUIMI_OBJECT.getDescriptor(), null, null);
|
587 |
+
|
588 |
+
|
589 |
+
postDataInit.visitTypeInsn(Opcodes.NEW, Type.getType(KuimiString.class).getInternalName());
|
590 |
+
postDataInit.visitInsn(Opcodes.DUP);
|
591 |
+
postDataInit.visitVarInsn(Opcodes.ALOAD, 0);
|
592 |
+
postDataInit.visitLdcInsn(value);
|
593 |
+
postDataInit.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getType(KuimiString.class).getInternalName(), "<init>", Type.getMethodDescriptor(
|
594 |
+
Type.VOID_TYPE, KUIMI_VM, STRING
|
595 |
+
), false);
|
596 |
+
|
597 |
+
postDataInit.visitFieldInsn(Opcodes.PUTSTATIC, wcpName, rsp, KUIMI_OBJECT.getDescriptor());
|
598 |
+
|
599 |
+
|
600 |
+
put(value, rsp);
|
601 |
+
return rsp;
|
602 |
+
}
|
603 |
+
};
|
604 |
+
var fieldAccessMap = new HashMap<String, String>() {
|
605 |
+
String fieldField(String owner, String name, String type, boolean isStatic) {
|
606 |
+
var key = isStatic + "." + owner + "." + name + ":" + type;
|
607 |
+
var rsp = get(key);
|
608 |
+
if (rsp != null) return rsp;
|
609 |
+
|
610 |
+
rsp = "fd" + size();
|
611 |
+
|
612 |
+
cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, rsp, KUIMI_FIELD.getDescriptor(), null, null);
|
613 |
+
|
614 |
+
typeFieldMapping.loadType(postDataInit, owner);
|
615 |
+
postDataInit.visitMethodInsn(Opcodes.INVOKEVIRTUAL, KUIMI_CLASS.getInternalName(), "getFieldTable", Type.getMethodDescriptor(KUIMI_FIELD_TABLE), false);
|
616 |
+
postDataInit.visitInsn(isStatic ? Opcodes.ICONST_1 : Opcodes.ICONST_0);
|
617 |
+
postDataInit.visitLdcInsn(name);
|
618 |
+
typeFieldMapping.loadType(postDataInit, Type.getType(type));
|
619 |
+
postDataInit.visitMethodInsn(Opcodes.INVOKEVIRTUAL, KUIMI_FIELD_TABLE.getInternalName(), "findField", Type.getMethodDescriptor(
|
620 |
+
KUIMI_FIELD, Type.BOOLEAN_TYPE, Type.getType(String.class), KUIMI_CLASS
|
621 |
+
), false);
|
622 |
+
|
623 |
+
postDataInit.visitFieldInsn(Opcodes.PUTSTATIC, wcpName, rsp, KUIMI_FIELD.getDescriptor());
|
624 |
+
|
625 |
+
put(key, rsp);
|
626 |
+
return rsp;
|
627 |
+
}
|
628 |
+
};
|
629 |
+
|
630 |
+
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
631 |
+
mv.visitMethodInsn(Opcodes.INVOKESTATIC, wcpName, "init", Type.getMethodDescriptor(
|
632 |
+
Type.VOID_TYPE, Type.getType(KuimiVM.class)
|
633 |
+
), false);
|
634 |
+
|
635 |
+
|
636 |
+
return new MethodVisitor(api, mv) {
|
637 |
+
|
638 |
+
private void loadType(String name) {
|
639 |
+
typeFieldMapping.loadType(mv, name);
|
640 |
+
}
|
641 |
+
|
642 |
+
private void pushMethod(String fname) {
|
643 |
+
mv.visitFieldInsn(Opcodes.GETSTATIC, wcpName, fname, KUIMI_METHOD.getDescriptor());
|
644 |
+
}
|
645 |
+
|
646 |
+
@Override
|
647 |
+
public void visitVarInsn(int opcode, int varIndex) {
|
648 |
+
super.visitVarInsn(opcode, varIndex + 2);
|
649 |
+
}
|
650 |
+
|
651 |
+
@Override
|
652 |
+
public void visitIincInsn(int varIndex, int increment) {
|
653 |
+
super.visitIincInsn(varIndex + 2, increment);
|
654 |
+
}
|
655 |
+
|
656 |
+
@Override
|
657 |
+
public void visitLdcInsn(Object value) {
|
658 |
+
if (value instanceof String str) {
|
659 |
+
mv.visitFieldInsn(Opcodes.GETSTATIC, wcpName, objMap.ldcField(str), KUIMI_OBJECT.getDescriptor());
|
660 |
+
return;
|
661 |
+
}
|
662 |
+
super.visitLdcInsn(value);
|
663 |
+
}
|
664 |
+
|
665 |
+
@Override
|
666 |
+
public void visitInsn(int opcode) {
|
667 |
+
if (opcode == Opcodes.ARRAYLENGTH) {
|
668 |
+
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, KUIMI_OBJECT.getInternalName(), "getDelegateInstance", "()Ljava/lang/Object;", false);
|
669 |
+
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/reflect/Array", "getLength", "(Ljava/lang/Object;)I", false);
|
670 |
+
return;
|
671 |
+
}
|
672 |
+
|
673 |
+
//@formatter:off
|
674 |
+
if (opcode == Opcodes.AALOAD) {mv.visitMethodInsn(Opcodes.INVOKESTATIC, ARRAY_ACCESS_BRIDGE.getInternalName(), "AALOAD", Type.getMethodDescriptor(KUIMI_OBJECT, KUIMI_OBJECT, Type.INT_TYPE), false);return;}
|
675 |
+
if (opcode == Opcodes.BALOAD) {mv.visitMethodInsn(Opcodes.INVOKESTATIC, ARRAY_ACCESS_BRIDGE.getInternalName(), "BALOAD", Type.getMethodDescriptor(Type.BYTE_TYPE, KUIMI_OBJECT, Type.INT_TYPE), false);return;}
|
676 |
+
if (opcode == Opcodes.CALOAD) {mv.visitMethodInsn(Opcodes.INVOKESTATIC, ARRAY_ACCESS_BRIDGE.getInternalName(), "CALOAD", Type.getMethodDescriptor(Type.CHAR_TYPE, KUIMI_OBJECT, Type.INT_TYPE), false);return;}
|
677 |
+
if (opcode == Opcodes.SALOAD) {mv.visitMethodInsn(Opcodes.INVOKESTATIC, ARRAY_ACCESS_BRIDGE.getInternalName(), "SALOAD", Type.getMethodDescriptor(Type.SHORT_TYPE, KUIMI_OBJECT, Type.INT_TYPE), false);return;}
|
678 |
+
if (opcode == Opcodes.IALOAD) {mv.visitMethodInsn(Opcodes.INVOKESTATIC, ARRAY_ACCESS_BRIDGE.getInternalName(), "IALOAD", Type.getMethodDescriptor(Type.INT_TYPE, KUIMI_OBJECT, Type.INT_TYPE), false);return;}
|
679 |
+
if (opcode == Opcodes.LALOAD) {mv.visitMethodInsn(Opcodes.INVOKESTATIC, ARRAY_ACCESS_BRIDGE.getInternalName(), "LALOAD", Type.getMethodDescriptor(Type.LONG_TYPE, KUIMI_OBJECT, Type.INT_TYPE), false);return;}
|
680 |
+
if (opcode == Opcodes.FALOAD) {mv.visitMethodInsn(Opcodes.INVOKESTATIC, ARRAY_ACCESS_BRIDGE.getInternalName(), "FALOAD", Type.getMethodDescriptor(Type.FLOAT_TYPE, KUIMI_OBJECT, Type.INT_TYPE), false);return;}
|
681 |
+
if (opcode == Opcodes.DALOAD) {mv.visitMethodInsn(Opcodes.INVOKESTATIC, ARRAY_ACCESS_BRIDGE.getInternalName(), "DALOAD", Type.getMethodDescriptor(Type.DOUBLE_TYPE, KUIMI_OBJECT, Type.INT_TYPE), false);return;}
|
682 |
+
|
683 |
+
if (opcode == Opcodes.AASTORE){mv.visitMethodInsn(Opcodes.INVOKESTATIC, ARRAY_ACCESS_BRIDGE.getInternalName(), "AASTORE", Type.getMethodDescriptor(Type.VOID_TYPE, KUIMI_OBJECT, Type.INT_TYPE, KUIMI_OBJECT), false);return;}
|
684 |
+
if (opcode == Opcodes.BASTORE){mv.visitMethodInsn(Opcodes.INVOKESTATIC, ARRAY_ACCESS_BRIDGE.getInternalName(), "BASTORE", Type.getMethodDescriptor(Type.VOID_TYPE, KUIMI_OBJECT, Type.INT_TYPE, Type.BYTE_TYPE), false);return;}
|
685 |
+
if (opcode == Opcodes.CASTORE){mv.visitMethodInsn(Opcodes.INVOKESTATIC, ARRAY_ACCESS_BRIDGE.getInternalName(), "CASTORE", Type.getMethodDescriptor(Type.VOID_TYPE, KUIMI_OBJECT, Type.INT_TYPE, Type.CHAR_TYPE), false);return;}
|
686 |
+
if (opcode == Opcodes.SASTORE){mv.visitMethodInsn(Opcodes.INVOKESTATIC, ARRAY_ACCESS_BRIDGE.getInternalName(), "SASTORE", Type.getMethodDescriptor(Type.VOID_TYPE, KUIMI_OBJECT, Type.INT_TYPE, Type.SHORT_TYPE), false);return;}
|
687 |
+
if (opcode == Opcodes.IASTORE){mv.visitMethodInsn(Opcodes.INVOKESTATIC, ARRAY_ACCESS_BRIDGE.getInternalName(), "IASTORE", Type.getMethodDescriptor(Type.VOID_TYPE, KUIMI_OBJECT, Type.INT_TYPE, Type.INT_TYPE), false);return;}
|
688 |
+
if (opcode == Opcodes.LASTORE){mv.visitMethodInsn(Opcodes.INVOKESTATIC, ARRAY_ACCESS_BRIDGE.getInternalName(), "LASTORE", Type.getMethodDescriptor(Type.VOID_TYPE, KUIMI_OBJECT, Type.INT_TYPE, Type.LONG_TYPE), false);return;}
|
689 |
+
if (opcode == Opcodes.FASTORE){mv.visitMethodInsn(Opcodes.INVOKESTATIC, ARRAY_ACCESS_BRIDGE.getInternalName(), "FASTORE", Type.getMethodDescriptor(Type.VOID_TYPE, KUIMI_OBJECT, Type.INT_TYPE, Type.FLOAT_TYPE), false);return;}
|
690 |
+
if (opcode == Opcodes.DASTORE){mv.visitMethodInsn(Opcodes.INVOKESTATIC, ARRAY_ACCESS_BRIDGE.getInternalName(), "FASTORE", Type.getMethodDescriptor(Type.VOID_TYPE, KUIMI_OBJECT, Type.INT_TYPE, Type.DOUBLE_TYPE), false);return;}
|
691 |
+
//@formatter:on
|
692 |
+
|
693 |
+
super.visitInsn(opcode);
|
694 |
+
}
|
695 |
+
|
696 |
+
private void newArray(Type type) {
|
697 |
+
typeFieldMapping.loadType(mv, type);
|
698 |
+
mv.visitMethodInsn(Opcodes.INVOKESTATIC, ARRAY_ACCESS_BRIDGE.getInternalName(), "newArray", Type.getMethodDescriptor(KUIMI_OBJECT, Type.INT_TYPE, KUIMI_CLASS), false);
|
699 |
+
}
|
700 |
+
|
701 |
+
@Override
|
702 |
+
public void visitIntInsn(int opcode, int operand) {
|
703 |
+
if (opcode == Opcodes.NEWARRAY) {
|
704 |
+
switch (operand) {
|
705 |
+
case Opcodes.T_BOOLEAN -> newArray(Type.BOOLEAN_TYPE);
|
706 |
+
case Opcodes.T_BYTE -> newArray(Type.BYTE_TYPE);
|
707 |
+
case Opcodes.T_CHAR -> newArray(Type.CHAR_TYPE);
|
708 |
+
case Opcodes.T_SHORT -> newArray(Type.SHORT_TYPE);
|
709 |
+
case Opcodes.T_INT -> newArray(Type.INT_TYPE);
|
710 |
+
case Opcodes.T_LONG -> newArray(Type.LONG_TYPE);
|
711 |
+
case Opcodes.T_FLOAT -> newArray(Type.FLOAT_TYPE);
|
712 |
+
case Opcodes.T_DOUBLE -> newArray(Type.DOUBLE_TYPE);
|
713 |
+
}
|
714 |
+
return;
|
715 |
+
}
|
716 |
+
super.visitIntInsn(opcode, operand);
|
717 |
+
}
|
718 |
+
|
719 |
+
@Override
|
720 |
+
public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
|
721 |
+
var isObj = descriptor.length() != 1;
|
722 |
+
fieldAccessMap.fieldField(owner, name, descriptor, opcode == Opcodes.GETSTATIC || opcode == Opcodes.PUTSTATIC);
|
723 |
+
|
724 |
+
var dstType = Type.getType(descriptor);
|
725 |
+
var accessBridge = FIELD_ACCESS_BRIDGE;
|
726 |
+
|
727 |
+
|
728 |
+
if (opcode == Opcodes.GETSTATIC) { // TODO
|
729 |
+
typeFieldMapping.loadType(mv, owner);
|
730 |
+
}
|
731 |
+
if (opcode == Opcodes.GETSTATIC || opcode == Opcodes.GETFIELD) {
|
732 |
+
mv.visitFieldInsn(Opcodes.GETSTATIC, wcpName, fieldAccessMap.fieldField(
|
733 |
+
owner, name, descriptor, opcode == Opcodes.GETSTATIC
|
734 |
+
), KUIMI_FIELD.getDescriptor());
|
735 |
+
|
736 |
+
if (opcode == Opcodes.GETSTATIC) { // get object field don't need call init because class was initialized when object allocating
|
737 |
+
mv.visitMethodInsn(Opcodes.INVOKESTATIC, CLASS_INIT_ENSURE.getInternalName(), "ensureInit", Type.getMethodDescriptor(KUIMI_FIELD, KUIMI_FIELD), false);
|
738 |
+
}
|
739 |
+
|
740 |
+
if (isObj) {
|
741 |
+
mv.visitMethodInsn(Opcodes.INVOKESTATIC, accessBridge.getInternalName(), "getobject", Type.getMethodDescriptor(
|
742 |
+
KUIMI_OBJECT, KUIMI_OBJECT, KUIMI_FIELD
|
743 |
+
), false);
|
744 |
+
} else {
|
745 |
+
mv.visitMethodInsn(Opcodes.INVOKESTATIC, accessBridge.getInternalName(), "get" + dstType.getClassName(), Type.getMethodDescriptor(
|
746 |
+
dstType, KUIMI_OBJECT, KUIMI_FIELD
|
747 |
+
), false);
|
748 |
+
}
|
749 |
+
}
|
750 |
+
|
751 |
+
if (opcode == Opcodes.PUTSTATIC) {
|
752 |
+
mv.visitFieldInsn(Opcodes.GETSTATIC, wcpName, fieldAccessMap.fieldField(
|
753 |
+
owner, name, descriptor, true
|
754 |
+
), KUIMI_FIELD.getDescriptor());
|
755 |
+
mv.visitMethodInsn(Opcodes.INVOKESTATIC, CLASS_INIT_ENSURE.getInternalName(), "ensureInit", Type.getMethodDescriptor(KUIMI_FIELD, KUIMI_FIELD), false);
|
756 |
+
|
757 |
+
mv.visitMethodInsn(Opcodes.INVOKESTATIC, accessBridge.getInternalName(), "put", Type.getMethodDescriptor(
|
758 |
+
Type.VOID_TYPE, isObj ? KUIMI_OBJECT : dstType, KUIMI_FIELD
|
759 |
+
), false);
|
760 |
+
}
|
761 |
+
if (opcode == Opcodes.PUTFIELD) { // get object field don't need call init because class was initialized when object allocating
|
762 |
+
mv.visitFieldInsn(Opcodes.GETSTATIC, wcpName, fieldAccessMap.fieldField(
|
763 |
+
owner, name, descriptor, false
|
764 |
+
), KUIMI_FIELD.getDescriptor());
|
765 |
+
mv.visitMethodInsn(Opcodes.INVOKESTATIC, accessBridge.getInternalName(), "put", Type.getMethodDescriptor(
|
766 |
+
Type.VOID_TYPE, KUIMI_OBJECT, isObj ? KUIMI_OBJECT : dstType, KUIMI_FIELD
|
767 |
+
), false);
|
768 |
+
}
|
769 |
+
// super.visitFieldInsn(opcode, owner, name, descriptor);
|
770 |
+
}
|
771 |
+
|
772 |
+
@Override
|
773 |
+
public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {
|
774 |
+
if ("this".equals(name)) {
|
775 |
+
return;
|
776 |
+
}
|
777 |
+
super.visitLocalVariable(name, descriptor, signature, start, end, index + 2);
|
778 |
+
}
|
779 |
+
|
780 |
+
@Override
|
781 |
+
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
|
782 |
+
var fname = metMapping.map(opcode == Opcodes.INVOKESTATIC, owner, name, descriptor);
|
783 |
+
List<Type> args = new ArrayList<>(List.of(Type.getArgumentTypes(descriptor)));
|
784 |
+
args.add(0, Type.getReturnType(descriptor));
|
785 |
+
args.replaceAll(it -> {
|
786 |
+
if (it.getSort() == Type.ARRAY || it.getSort() == Type.OBJECT) {
|
787 |
+
return KUIMI_OBJECT;
|
788 |
+
}
|
789 |
+
return it;
|
790 |
+
});
|
791 |
+
|
792 |
+
|
793 |
+
if (opcode != Opcodes.INVOKESTATIC) {
|
794 |
+
// reversedInvoke
|
795 |
+
args.add(1, KUIMI_OBJECT);
|
796 |
+
}
|
797 |
+
|
798 |
+
pushMethod(fname);
|
799 |
+
mv.visitVarInsn(Opcodes.ALOAD, 0);
|
800 |
+
mv.visitVarInsn(Opcodes.ALOAD, 1);
|
801 |
+
|
802 |
+
args.add(KUIMI_METHOD);
|
803 |
+
args.add(KUIMI_VM);
|
804 |
+
args.add(STACK_TRACE);
|
805 |
+
|
806 |
+
var retType = args.remove(0);
|
807 |
+
var mDesc = Type.getMethodDescriptor(retType, args.toArray(Type[]::new));
|
808 |
+
|
809 |
+
var specialCall = opcode == Opcodes.INVOKESTATIC || opcode == Opcodes.INVOKESPECIAL;
|
810 |
+
mv.visitMethodInsn(
|
811 |
+
Opcodes.INVOKESTATIC, wcpName,
|
812 |
+
specialCall ? "specialCall" : "call",
|
813 |
+
mDesc, false
|
814 |
+
);
|
815 |
+
genWptCall(mDesc, retType, args, specialCall);
|
816 |
+
}
|
817 |
+
|
818 |
+
@Override
|
819 |
+
public void visitTypeInsn(int opcode, String type) {
|
820 |
+
if (opcode == Opcodes.NEW) {
|
821 |
+
loadType(type);
|
822 |
+
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, KUIMI_CLASS.getInternalName(), "allocateNewObject", Type.getMethodDescriptor(KUIMI_OBJECT), false);
|
823 |
+
return;
|
824 |
+
}
|
825 |
+
if (opcode == Opcodes.CHECKCAST) {
|
826 |
+
// TODO
|
827 |
+
loadType(type);
|
828 |
+
mv.visitInsn(Opcodes.POP);
|
829 |
+
return;
|
830 |
+
}
|
831 |
+
if (opcode == Opcodes.ANEWARRAY) {
|
832 |
+
newArray(Type.getObjectType(type));
|
833 |
+
return;
|
834 |
+
}
|
835 |
+
super.visitTypeInsn(opcode, type);
|
836 |
+
}
|
837 |
+
|
838 |
+
private final HashSet<String> calls = new HashSet<>();
|
839 |
+
|
840 |
+
void genWptCall(String desc, Type ret, List<Type> params, boolean specialCall) {
|
841 |
+
if (!calls.add(specialCall + "." + desc)) return;
|
842 |
+
|
843 |
+
var mtc = cw.visitMethod(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, specialCall ? "specialCall" : "call", desc, null, null);
|
844 |
+
int metSlot;
|
845 |
+
|
846 |
+
var subl = params.subList(0, params.size() - 3);
|
847 |
+
{
|
848 |
+
var ix = 0;
|
849 |
+
for (var p : subl) {
|
850 |
+
ix += p.getSize();
|
851 |
+
}
|
852 |
+
metSlot = ix;
|
853 |
+
}
|
854 |
+
|
855 |
+
if (specialCall) {
|
856 |
+
mtc.visitVarInsn(Opcodes.ALOAD, metSlot);
|
857 |
+
} else {
|
858 |
+
var mtTable = KUIMI_METHOD_TABLE;
|
859 |
+
|
860 |
+
mtc.visitVarInsn(Opcodes.ALOAD, 0); // this
|
861 |
+
mtc.visitMethodInsn(Opcodes.INVOKEVIRTUAL, KUIMI_OBJECT.getInternalName(), "getObjectClass", Type.getMethodDescriptor(KUIMI_CLASS), false);
|
862 |
+
mtc.visitMethodInsn(Opcodes.INVOKEVIRTUAL, KUIMI_CLASS.getInternalName(), "getMethodTable", Type.getMethodDescriptor(mtTable), false);
|
863 |
+
mtc.visitMethodInsn(Opcodes.INVOKEVIRTUAL, mtTable.getInternalName(), "getMergedMethods", "()Ljava/util/List;", false);
|
864 |
+
|
865 |
+
mtc.visitVarInsn(Opcodes.ALOAD, metSlot);
|
866 |
+
mtc.visitMethodInsn(Opcodes.INVOKEVIRTUAL, KUIMI_METHOD.getInternalName(), "getMethodSlot", "()I", false);
|
867 |
+
|
868 |
+
mtc.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/List", "get", "(I)Ljava/lang/Object;", true);
|
869 |
+
mtc.visitTypeInsn(Opcodes.CHECKCAST, KUIMI_METHOD.getInternalName());
|
870 |
+
}
|
871 |
+
|
872 |
+
mtc.visitMethodInsn(Opcodes.INVOKESTATIC, CLASS_INIT_ENSURE.getInternalName(), "ensureInit", Type.getMethodDescriptor(KUIMI_METHOD, KUIMI_METHOD), false);
|
873 |
+
|
874 |
+
mtc.visitMethodInsn(Opcodes.INVOKEVIRTUAL, KUIMI_METHOD.getInternalName(), "resolveMethodHandle", "()Ljava/lang/invoke/MethodHandle;", false);
|
875 |
+
|
876 |
+
|
877 |
+
mtc.visitVarInsn(Opcodes.ALOAD, metSlot + 1);
|
878 |
+
mtc.visitVarInsn(Opcodes.ALOAD, metSlot + 2);
|
879 |
+
var i = 0;
|
880 |
+
for (var p : subl) {
|
881 |
+
mtc.visitVarInsn(p.getOpcode(Opcodes.ILOAD), i);
|
882 |
+
i += p.getSize();
|
883 |
+
}
|
884 |
+
|
885 |
+
var mhCallArgs = new ArrayList<>(subl);
|
886 |
+
mhCallArgs.add(0, KUIMI_VM);
|
887 |
+
mhCallArgs.add(1, STACK_TRACE);
|
888 |
+
|
889 |
+
mtc.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact",
|
890 |
+
Type.getMethodDescriptor(ret, mhCallArgs.toArray(Type[]::new)),
|
891 |
+
false
|
892 |
+
);
|
893 |
+
mtc.visitInsn(ret.getOpcode(Opcodes.IRETURN));
|
894 |
+
|
895 |
+
mtc.visitMaxs(i + 4, i + 4);
|
896 |
+
mtc.visitEnd();
|
897 |
+
}
|
898 |
+
};
|
899 |
+
}
|
900 |
+
});
|
901 |
+
|
902 |
+
{
|
903 |
+
for (var insn : postDataInit.instructions) {
|
904 |
+
insn.accept(dataInit);
|
905 |
+
}
|
906 |
+
|
907 |
+
dataInit.visitInsn(Opcodes.ICONST_1);
|
908 |
+
dataInit.visitFieldInsn(Opcodes.PUTSTATIC, wcpName, "initialized", "Z");
|
909 |
+
dataInit.visitInsn(Opcodes.RETURN);
|
910 |
+
dataInit.visitMaxs(6, 4);
|
911 |
+
dataInit.visitEnd();
|
912 |
+
}
|
913 |
+
|
914 |
+
cw.visitEnd();
|
915 |
+
|
916 |
+
}
|
917 |
+
|
918 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/unidbg/ArmVarArg64Visitor.java
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm.unidbg;
|
2 |
+
|
3 |
+
import com.github.unidbg.Emulator;
|
4 |
+
import com.github.unidbg.pointer.UnidbgPointer;
|
5 |
+
import com.kiliokuara.kuimivm.vaarg.ShortenType;
|
6 |
+
import com.kiliokuara.kuimivm.vaarg.VaArgModelBuilder;
|
7 |
+
import unicorn.Arm64Const;
|
8 |
+
|
9 |
+
import java.nio.ByteBuffer;
|
10 |
+
import java.nio.ByteOrder;
|
11 |
+
import java.util.List;
|
12 |
+
|
13 |
+
public class ArmVarArg64Visitor extends ArmVarArgVisitor {
|
14 |
+
public static void read(Emulator<?> emulator, VaArgModelBuilder output, List<ShortenType> shorties) {
|
15 |
+
int offset = 0;
|
16 |
+
int floatOff = 0;
|
17 |
+
for (var shorty : shorties) {
|
18 |
+
switch (shorty) {
|
19 |
+
case INT, REF -> {
|
20 |
+
output.putInt(getInt(emulator, offset++));
|
21 |
+
}
|
22 |
+
case DOUBLE -> {
|
23 |
+
output.putDouble(getVectorArg(emulator, floatOff++));
|
24 |
+
}
|
25 |
+
case FLOAT -> {
|
26 |
+
output.putFloat((float) getVectorArg(emulator, floatOff++));
|
27 |
+
}
|
28 |
+
case LONG -> {
|
29 |
+
UnidbgPointer ptr = getArg(emulator, offset++);
|
30 |
+
output.putLong(ptr == null ? 0L : ptr.peer);
|
31 |
+
}
|
32 |
+
default -> throw new IllegalStateException("c=" + shorty);
|
33 |
+
}
|
34 |
+
}
|
35 |
+
}
|
36 |
+
|
37 |
+
|
38 |
+
private static double getVectorArg(Emulator<?> emulator, int index) {
|
39 |
+
ByteBuffer buffer = ByteBuffer.allocate(16);
|
40 |
+
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
41 |
+
buffer.put(emulator.getBackend().reg_read_vector(Arm64Const.UC_ARM64_REG_Q0 + index));
|
42 |
+
buffer.flip();
|
43 |
+
return buffer.getDouble();
|
44 |
+
}
|
45 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/unidbg/ArmVarArgVisitor.java
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm.unidbg;
|
2 |
+
|
3 |
+
import com.github.unidbg.Emulator;
|
4 |
+
import com.github.unidbg.pointer.UnidbgPointer;
|
5 |
+
|
6 |
+
public class ArmVarArgVisitor {
|
7 |
+
protected static final int REG_OFFSET = 3;
|
8 |
+
|
9 |
+
|
10 |
+
protected static UnidbgPointer getArg(Emulator<?> emulator, int index) {
|
11 |
+
return emulator.getContext().getPointerArg(REG_OFFSET + index);
|
12 |
+
}
|
13 |
+
|
14 |
+
protected static int getInt(Emulator<?> emulator, int index) {
|
15 |
+
UnidbgPointer ptr = getArg(emulator, index);
|
16 |
+
return ptr == null ? 0 : ptr.toIntPeer();
|
17 |
+
}
|
18 |
+
|
19 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/unidbg/JNIMemberTable.java
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm.unidbg;
|
2 |
+
|
3 |
+
import com.kiliokuara.kuimivm.KuimiField;
|
4 |
+
import com.kiliokuara.kuimivm.KuimiMember;
|
5 |
+
import com.kiliokuara.kuimivm.KuimiMethod;
|
6 |
+
import com.kiliokuara.kuimivm.attributes.AttributeKey;
|
7 |
+
|
8 |
+
import java.util.Arrays;
|
9 |
+
|
10 |
+
class JNIMemberTable {
|
11 |
+
private static final int BASE_OFFSET = 0x180;
|
12 |
+
|
13 |
+
private final AttributeKey<? super KuimiMember, SlotRecord> SLOT_RECORD = new AttributeKey<>("slot-record", it -> new SlotRecord(push(it)));
|
14 |
+
|
15 |
+
private KuimiMember[] members = new KuimiMember[256];
|
16 |
+
private volatile int pushSlotStart;
|
17 |
+
|
18 |
+
public KuimiMember resolve(int mid) {
|
19 |
+
if (mid < BASE_OFFSET) return null;
|
20 |
+
return members[mid - BASE_OFFSET];
|
21 |
+
}
|
22 |
+
|
23 |
+
public synchronized int push(KuimiMember member) {
|
24 |
+
if (member == null) return 0;
|
25 |
+
|
26 |
+
var mems = members;
|
27 |
+
for (int i = pushSlotStart, ed = mems.length; i < ed; i++) {
|
28 |
+
if (mems[i] == null) {
|
29 |
+
pushSlotStart = i + 1;
|
30 |
+
mems[i] = member;
|
31 |
+
return i + BASE_OFFSET;
|
32 |
+
}
|
33 |
+
}
|
34 |
+
|
35 |
+
members = Arrays.copyOf(mems, mems.length + 256);
|
36 |
+
return push(member);
|
37 |
+
}
|
38 |
+
|
39 |
+
public synchronized void drop(int slot) {
|
40 |
+
slot -= BASE_OFFSET;
|
41 |
+
|
42 |
+
var old = members[slot];
|
43 |
+
members[slot] = null;
|
44 |
+
if (old != null) {
|
45 |
+
old.getAttributeMap().remove(SLOT_RECORD);
|
46 |
+
}
|
47 |
+
}
|
48 |
+
|
49 |
+
public int getMemberId(KuimiMember member) {
|
50 |
+
return member.getAttributeMap().attribute(SLOT_RECORD).slot;
|
51 |
+
}
|
52 |
+
|
53 |
+
public KuimiMethod resolveMethodId(int peer) {
|
54 |
+
return (KuimiMethod) resolve(peer);
|
55 |
+
}
|
56 |
+
public KuimiField resolveFieldId(int peer){
|
57 |
+
return (KuimiField) resolve(peer);
|
58 |
+
}
|
59 |
+
|
60 |
+
record SlotRecord(int slot) {
|
61 |
+
}
|
62 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/unidbg/JValueListVisitor.java
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm.unidbg;
|
2 |
+
|
3 |
+
import com.github.unidbg.Emulator;
|
4 |
+
import com.github.unidbg.pointer.UnidbgPointer;
|
5 |
+
import com.kiliokuara.kuimivm.vaarg.VaArgModelBuilder;
|
6 |
+
import com.kiliokuara.kuimivm.KuimiClass;
|
7 |
+
import com.sun.jna.Pointer;
|
8 |
+
import org.objectweb.asm.Type;
|
9 |
+
|
10 |
+
import java.util.List;
|
11 |
+
|
12 |
+
public class JValueListVisitor {
|
13 |
+
public static void read(Emulator<?> emulator, UnidbgPointer jvalue, VaArgModelBuilder output, List<KuimiClass> params) {
|
14 |
+
|
15 |
+
Pointer pointer = jvalue;
|
16 |
+
for (var param : params) {
|
17 |
+
switch (param.getClassType().getSort()) {
|
18 |
+
case Type.OBJECT, Type.ARRAY -> {
|
19 |
+
UnidbgPointer ptr = (UnidbgPointer) pointer.getPointer(0);
|
20 |
+
output.putInt(ptr == null ? 0 : (int) ptr.toUIntPeer());
|
21 |
+
}
|
22 |
+
case Type.BYTE -> {
|
23 |
+
byte val = pointer.getByte(0);
|
24 |
+
output.putInt(val & 0xff);
|
25 |
+
}
|
26 |
+
case Type.BOOLEAN -> {
|
27 |
+
byte val = pointer.getByte(0);
|
28 |
+
output.putInt(val & 1);
|
29 |
+
}
|
30 |
+
case Type.CHAR -> {
|
31 |
+
char val = pointer.getChar(0);
|
32 |
+
output.putInt(val);
|
33 |
+
}
|
34 |
+
case Type.SHORT -> {
|
35 |
+
output.putInt(pointer.getShort(0));
|
36 |
+
}
|
37 |
+
case Type.INT -> {
|
38 |
+
output.putInt(pointer.getInt(0));
|
39 |
+
}
|
40 |
+
case Type.FLOAT -> {
|
41 |
+
output.putFloat((float) pointer.getDouble(0));
|
42 |
+
}
|
43 |
+
case Type.DOUBLE -> {
|
44 |
+
output.putDouble(pointer.getDouble(0));
|
45 |
+
}
|
46 |
+
case Type.LONG -> {
|
47 |
+
output.putLong(pointer.getLong(0));
|
48 |
+
}
|
49 |
+
default -> throw new IllegalStateException("c=" + param);
|
50 |
+
}
|
51 |
+
|
52 |
+
pointer = pointer.share(8);
|
53 |
+
}
|
54 |
+
|
55 |
+
}
|
56 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/unidbg/KuimiJniMethodHandle.java
ADDED
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm.unidbg;
|
2 |
+
|
3 |
+
import com.github.unidbg.Module;
|
4 |
+
import com.kiliokuara.kuimivm.execute.StackTrace;
|
5 |
+
import com.kiliokuara.kuimivm.KuimiMethod;
|
6 |
+
import com.kiliokuara.kuimivm.KuimiObject;
|
7 |
+
import com.kiliokuara.kuimivm.KuimiVM;
|
8 |
+
|
9 |
+
import java.lang.invoke.MethodHandle;
|
10 |
+
import java.lang.invoke.MethodHandles;
|
11 |
+
import java.lang.invoke.MethodType;
|
12 |
+
import java.lang.reflect.Modifier;
|
13 |
+
import java.util.ArrayList;
|
14 |
+
|
15 |
+
public class KuimiJniMethodHandle {
|
16 |
+
private static final MethodHandle ARRAY_LIST_NEW;
|
17 |
+
private static final MethodHandle ARRAY_LIST_ADD;
|
18 |
+
private static final MethodHandle NUMBER_LONG_VALUE;
|
19 |
+
private static final MethodHandle NUMBER_INT_VALUE;
|
20 |
+
private static final MethodHandle NUMBER_SHORT_VALUE;
|
21 |
+
private static final MethodHandle NUMBER_BYTE_VALUE;
|
22 |
+
private static final MethodHandle FLOAT_INT_BITS_TO_FLOAT;
|
23 |
+
private static final MethodHandle DOUBLE_LONG_BITS_TO_DOUBLE;
|
24 |
+
|
25 |
+
|
26 |
+
private static final MethodHandle KUIMI_VM_RESOLVE_OBJECT;
|
27 |
+
|
28 |
+
|
29 |
+
private static final MethodHandle N2C, N2B;
|
30 |
+
private static final MethodHandle RE_INVOKE;
|
31 |
+
|
32 |
+
static {
|
33 |
+
var lk = MethodHandles.lookup();
|
34 |
+
try {
|
35 |
+
ARRAY_LIST_NEW = lk.findConstructor(ArrayList.class, MethodType.methodType(void.class));
|
36 |
+
ARRAY_LIST_ADD = MethodHandles.dropReturn(lk.findVirtual(ArrayList.class, "add", MethodType.methodType(boolean.class, Object.class)));
|
37 |
+
|
38 |
+
NUMBER_LONG_VALUE = lk.findVirtual(Number.class, "longValue", MethodType.methodType(long.class));
|
39 |
+
NUMBER_INT_VALUE = lk.findVirtual(Number.class, "intValue", MethodType.methodType(int.class));
|
40 |
+
NUMBER_SHORT_VALUE = lk.findVirtual(Number.class, "shortValue", MethodType.methodType(short.class));
|
41 |
+
NUMBER_BYTE_VALUE = lk.findVirtual(Number.class, "byteValue", MethodType.methodType(byte.class));
|
42 |
+
FLOAT_INT_BITS_TO_FLOAT = lk.findStatic(Float.class, "intBitsToFloat", MethodType.methodType(float.class, int.class));
|
43 |
+
DOUBLE_LONG_BITS_TO_DOUBLE = lk.findStatic(Double.class, "longBitsToDouble", MethodType.methodType(double.class, long.class));
|
44 |
+
|
45 |
+
KUIMI_VM_RESOLVE_OBJECT = lk.findVirtual(KuimiVM.class, "resolveObject", MethodType.methodType(KuimiObject.class, int.class));
|
46 |
+
|
47 |
+
RE_INVOKE = lk.findStatic(KuimiJniMethodHandle.class, "reinvoke", MethodType.methodType(Number.class, KuimiUnidbgVM.class, KuimiMethod.class, long.class, ArrayList.class));
|
48 |
+
N2C = lk.findStatic(KuimiJniMethodHandle.class, "n2c", MethodType.methodType(char.class, Number.class));
|
49 |
+
N2B = lk.findStatic(KuimiJniMethodHandle.class, "n2b", MethodType.methodType(boolean.class, Number.class));
|
50 |
+
|
51 |
+
|
52 |
+
} catch (Throwable throwable) {
|
53 |
+
throw new ExceptionInInitializerError(throwable);
|
54 |
+
}
|
55 |
+
}
|
56 |
+
|
57 |
+
private static char n2c(Number i) {
|
58 |
+
return (char) i.intValue();
|
59 |
+
}
|
60 |
+
|
61 |
+
private static boolean n2b(Number i) {
|
62 |
+
return i.intValue() != 0;
|
63 |
+
}
|
64 |
+
|
65 |
+
private static Number reinvoke(KuimiUnidbgVM vm, KuimiMethod method, long peer, ArrayList<?> args) {
|
66 |
+
var st = (StackTrace) args.get(1);
|
67 |
+
var isStatic = Modifier.isStatic(method.getModifiers());
|
68 |
+
Object[] nativeArgs = new Object[isStatic ? args.size() : args.size() - 1];
|
69 |
+
nativeArgs[0] = vm.getJNIEnv();
|
70 |
+
int argIndex;
|
71 |
+
if (isStatic) {
|
72 |
+
nativeArgs[1] = st.pushObject(method.getDeclaredClass());
|
73 |
+
argIndex = 2;
|
74 |
+
} else {
|
75 |
+
argIndex = 1;
|
76 |
+
}
|
77 |
+
|
78 |
+
args.remove(0); // KuimiVM
|
79 |
+
args.remove(0); // StackTrace
|
80 |
+
|
81 |
+
for (var arg : args) {
|
82 |
+
if (arg instanceof Boolean bool) {
|
83 |
+
nativeArgs[argIndex++] = bool ? VMConst.JNI_TRUE : VMConst.JNI_FALSE;
|
84 |
+
} else if (arg instanceof KuimiObject<?> kobj) {
|
85 |
+
nativeArgs[argIndex++] = st.pushObject(kobj);
|
86 |
+
} else if (arg instanceof Number) {
|
87 |
+
nativeArgs[argIndex++] = arg;
|
88 |
+
} else {
|
89 |
+
nativeArgs[argIndex++] = arg;
|
90 |
+
}
|
91 |
+
}
|
92 |
+
|
93 |
+
var rsp = Module.emulateFunction(vm.emulator, peer, nativeArgs);
|
94 |
+
if (st.throwable != null) return 0;
|
95 |
+
return rsp;
|
96 |
+
}
|
97 |
+
|
98 |
+
public static MethodHandle invoker(
|
99 |
+
KuimiMethod targetMethod,
|
100 |
+
KuimiUnidbgVM vm,
|
101 |
+
long peer
|
102 |
+
) {
|
103 |
+
//(KuimiVM, StackTrace, <KuimiObject>, .....)R
|
104 |
+
var mtType = targetMethod.flattenMethodHandleType();
|
105 |
+
|
106 |
+
// (ArrayList, KuimiVM, ArrayList, StackTrace, .....)V
|
107 |
+
MethodHandle emptyModel = MethodHandles.identity(ArrayList.class);
|
108 |
+
{
|
109 |
+
var ptList = mtType.parameterList();
|
110 |
+
for (var i = ptList.size() - 1; i >= 0; i--) {
|
111 |
+
emptyModel = MethodHandles.collectArguments(emptyModel, 1, ARRAY_LIST_ADD);
|
112 |
+
}
|
113 |
+
}
|
114 |
+
|
115 |
+
{
|
116 |
+
var mpx = new ArrayList<Class<?>>();
|
117 |
+
mpx.add(ArrayList.class);
|
118 |
+
for (var ignored : mtType.parameterList()) {
|
119 |
+
mpx.add(Object.class);
|
120 |
+
}
|
121 |
+
|
122 |
+
var newType = MethodType.methodType(ArrayList.class, mpx);
|
123 |
+
int[] slots = new int[emptyModel.type().parameterCount()];
|
124 |
+
for (var i = 0; i < mtType.parameterCount(); i++) {
|
125 |
+
slots[i * 2 + 2] = i + 1;
|
126 |
+
}
|
127 |
+
|
128 |
+
emptyModel = MethodHandles.permuteArguments(emptyModel, newType, slots);
|
129 |
+
|
130 |
+
mpx.clear();
|
131 |
+
mpx.add(ArrayList.class);
|
132 |
+
mpx.addAll(mtType.parameterList());
|
133 |
+
emptyModel = emptyModel.asType(MethodType.methodType(ArrayList.class, mpx));
|
134 |
+
|
135 |
+
emptyModel = MethodHandles.collectArguments(emptyModel, 0, ARRAY_LIST_NEW);
|
136 |
+
}
|
137 |
+
// emptyModel = ArgumentCollector
|
138 |
+
var reinvoker = MethodHandles.insertArguments(RE_INVOKE, 0, vm, targetMethod, peer);
|
139 |
+
|
140 |
+
emptyModel = MethodHandles.filterReturnValue(emptyModel, reinvoker);
|
141 |
+
|
142 |
+
var rspType = mtType.returnType();
|
143 |
+
if (rspType == void.class) {
|
144 |
+
emptyModel = MethodHandles.dropReturn(emptyModel);
|
145 |
+
} else if (rspType == long.class) {
|
146 |
+
emptyModel = MethodHandles.filterReturnValue(emptyModel, NUMBER_LONG_VALUE);
|
147 |
+
} else if (rspType == int.class) {
|
148 |
+
emptyModel = MethodHandles.filterReturnValue(emptyModel, NUMBER_INT_VALUE);
|
149 |
+
} else if (rspType == short.class) {
|
150 |
+
emptyModel = MethodHandles.filterReturnValue(emptyModel, NUMBER_SHORT_VALUE);
|
151 |
+
} else if (rspType == byte.class) {
|
152 |
+
emptyModel = MethodHandles.filterReturnValue(emptyModel, NUMBER_BYTE_VALUE);
|
153 |
+
} else if (rspType == char.class) {
|
154 |
+
emptyModel = MethodHandles.filterReturnValue(emptyModel, N2C);
|
155 |
+
} else if (rspType == boolean.class) {
|
156 |
+
emptyModel = MethodHandles.filterReturnValue(emptyModel, N2B);
|
157 |
+
} else if (rspType == KuimiObject.class) {
|
158 |
+
emptyModel = MethodHandles.filterReturnValue(emptyModel, NUMBER_INT_VALUE);
|
159 |
+
emptyModel = MethodHandles.filterReturnValue(emptyModel, KUIMI_VM_RESOLVE_OBJECT.bindTo(vm.vm));
|
160 |
+
} else if (rspType == float.class) {
|
161 |
+
emptyModel = MethodHandles.filterReturnValue(emptyModel, NUMBER_INT_VALUE);
|
162 |
+
emptyModel = MethodHandles.filterReturnValue(emptyModel, FLOAT_INT_BITS_TO_FLOAT);
|
163 |
+
} else if (rspType == double.class) {
|
164 |
+
emptyModel = MethodHandles.filterReturnValue(emptyModel, NUMBER_LONG_VALUE);
|
165 |
+
emptyModel = MethodHandles.filterReturnValue(emptyModel, DOUBLE_LONG_BITS_TO_DOUBLE);
|
166 |
+
} else {
|
167 |
+
throw new IllegalArgumentException("Unknown how to convert to " + rspType);
|
168 |
+
}
|
169 |
+
|
170 |
+
return emptyModel;
|
171 |
+
}
|
172 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/unidbg/KuimiUnidbgObjectMemory.java
ADDED
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm.unidbg;
|
2 |
+
|
3 |
+
import com.github.unidbg.memory.MemoryBlock;
|
4 |
+
import com.github.unidbg.pointer.UnidbgPointer;
|
5 |
+
import com.kiliokuara.kuimivm.KuimiObject;
|
6 |
+
import com.sun.jna.Pointer;
|
7 |
+
|
8 |
+
import java.nio.ByteBuffer;
|
9 |
+
import java.nio.ByteOrder;
|
10 |
+
|
11 |
+
public class KuimiUnidbgObjectMemory {
|
12 |
+
private final KuimiUnidbgVM vm;
|
13 |
+
MemoryBlock memoryBlock;
|
14 |
+
|
15 |
+
KuimiUnidbgObjectMemory(KuimiUnidbgVM vm) {
|
16 |
+
this.vm = vm;
|
17 |
+
}
|
18 |
+
|
19 |
+
public UnidbgPointer allocateMemoryBlock(int length) {
|
20 |
+
if (memoryBlock != null) return memoryBlock.getPointer();
|
21 |
+
synchronized (this) {
|
22 |
+
if (memoryBlock != null) return memoryBlock.getPointer();
|
23 |
+
|
24 |
+
return (memoryBlock = vm.emulator.getMemory().malloc(length, true)).getPointer();
|
25 |
+
}
|
26 |
+
}
|
27 |
+
|
28 |
+
public void freeMemoryBlock(Pointer pointer) {
|
29 |
+
if (memoryBlock != null) {
|
30 |
+
synchronized (this) {
|
31 |
+
if (memoryBlock == null) return;
|
32 |
+
|
33 |
+
if (pointer != null && !memoryBlock.isSame(pointer)) return;
|
34 |
+
|
35 |
+
memoryBlock.free();
|
36 |
+
memoryBlock = null;
|
37 |
+
}
|
38 |
+
}
|
39 |
+
}
|
40 |
+
|
41 |
+
public long arrayCritical(KuimiObject<?> arrx) {
|
42 |
+
var arrObj = arrx.getDelegateInstance();
|
43 |
+
|
44 |
+
if (arrObj instanceof boolean[] d) {
|
45 |
+
var memory = allocateMemoryBlock(d.length);
|
46 |
+
for (var i = 0; i < d.length; i++) {
|
47 |
+
memory.setByte(i, d[i] ? (byte) 1 : 0);
|
48 |
+
}
|
49 |
+
return memory.peer;
|
50 |
+
}
|
51 |
+
if (arrObj instanceof byte[] d) {
|
52 |
+
var memory = allocateMemoryBlock(d.length);
|
53 |
+
memory.write(d);
|
54 |
+
return memory.peer;
|
55 |
+
}
|
56 |
+
if (arrObj instanceof char[] d) {
|
57 |
+
var memory = allocateMemoryBlock(d.length * 2);
|
58 |
+
var wp = ByteBuffer.allocate(d.length * 2);
|
59 |
+
wp.order(ByteOrder.LITTLE_ENDIAN).asCharBuffer().put(d);
|
60 |
+
memory.write(wp.array());
|
61 |
+
return memory.peer;
|
62 |
+
}
|
63 |
+
if (arrObj instanceof short[] d) {
|
64 |
+
var memory = allocateMemoryBlock(d.length * 2);
|
65 |
+
var wp = ByteBuffer.allocate(d.length * 2);
|
66 |
+
wp.order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().put(d);
|
67 |
+
memory.write(wp.array());
|
68 |
+
return memory.peer;
|
69 |
+
}
|
70 |
+
if (arrObj instanceof int[] d) {
|
71 |
+
var memory = allocateMemoryBlock(d.length * 4);
|
72 |
+
var wp = ByteBuffer.allocate(d.length * 4);
|
73 |
+
wp.order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().put(d);
|
74 |
+
memory.write(wp.array());
|
75 |
+
return memory.peer;
|
76 |
+
}
|
77 |
+
if (arrObj instanceof long[] d) {
|
78 |
+
var memory = allocateMemoryBlock(d.length * 8);
|
79 |
+
var wp = ByteBuffer.allocate(d.length * 8);
|
80 |
+
wp.order(ByteOrder.LITTLE_ENDIAN).asLongBuffer().put(d);
|
81 |
+
memory.write(wp.array());
|
82 |
+
return memory.peer;
|
83 |
+
}
|
84 |
+
if (arrObj instanceof float[] d) {
|
85 |
+
var memory = allocateMemoryBlock(d.length * Float.BYTES);
|
86 |
+
var wp = ByteBuffer.allocate(d.length * Float.BYTES);
|
87 |
+
wp.order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer().put(d);
|
88 |
+
memory.write(wp.array());
|
89 |
+
return memory.peer;
|
90 |
+
}
|
91 |
+
if (arrObj instanceof double[] d) {
|
92 |
+
var memory = allocateMemoryBlock(d.length * Double.BYTES);
|
93 |
+
var wp = ByteBuffer.allocate(d.length * Double.BYTES);
|
94 |
+
wp.order(ByteOrder.LITTLE_ENDIAN).asDoubleBuffer().put(d);
|
95 |
+
memory.write(wp.array());
|
96 |
+
return memory.peer;
|
97 |
+
}
|
98 |
+
throw new IllegalArgumentException(arrx.toString());
|
99 |
+
}
|
100 |
+
|
101 |
+
@SuppressWarnings({"rawtypes", "UnnecessaryLocalVariable", "unchecked"})
|
102 |
+
public void arrayCriticalRelease(KuimiObject<?> obj, Pointer pointer, int mode) {
|
103 |
+
if (mode == VMConst.JNI_ABORT) {
|
104 |
+
freeMemoryBlock(pointer);
|
105 |
+
return;
|
106 |
+
}
|
107 |
+
KuimiObject objRaw = obj;
|
108 |
+
if (mode == VMConst.JNI_COMMIT || mode == 0) {
|
109 |
+
var arrObj = obj.getDelegateInstance();
|
110 |
+
|
111 |
+
if (arrObj instanceof boolean[] d) {
|
112 |
+
for (var i = 0; i < d.length; i++) {
|
113 |
+
d[i] = pointer.getByte(i) != 0;
|
114 |
+
}
|
115 |
+
} else if (arrObj instanceof byte[] d) {
|
116 |
+
objRaw.setDelegateInstance(pointer.getByteArray(0, d.length));
|
117 |
+
} else if (arrObj instanceof char[] d) {
|
118 |
+
var buf = pointer.getByteBuffer(0, d.length * 2L);
|
119 |
+
buf.asCharBuffer().get(d);
|
120 |
+
} else if (arrObj instanceof short[] d) {
|
121 |
+
var buf = pointer.getByteBuffer(0, d.length * 2L);
|
122 |
+
buf.asShortBuffer().get(d);
|
123 |
+
} else if (arrObj instanceof int[] d) {
|
124 |
+
objRaw.setDelegateInstance(pointer.getIntArray(0, d.length));
|
125 |
+
} else if (arrObj instanceof long[] d) {
|
126 |
+
objRaw.setDelegateInstance(pointer.getLongArray(0, d.length));
|
127 |
+
} else if (arrObj instanceof float[] d) {
|
128 |
+
objRaw.setDelegateInstance(pointer.getFloatArray(0, d.length));
|
129 |
+
} else if (arrObj instanceof double[] d) {
|
130 |
+
objRaw.setDelegateInstance(pointer.getDoubleArray(0, d.length));
|
131 |
+
} else {
|
132 |
+
throw new IllegalArgumentException(obj.toString());
|
133 |
+
}
|
134 |
+
}
|
135 |
+
if (mode == 0) {
|
136 |
+
freeMemoryBlock(pointer);
|
137 |
+
}
|
138 |
+
}
|
139 |
+
}
|
src/main/java/com/kiliokuara/kuimivm/unidbg/KuimiUnidbgVM.java
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
package com.kiliokuara.kuimivm.unidbg;
|
2 |
+
|
3 |
+
import com.github.unidbg.AndroidEmulator;
|
4 |
+
import com.kiliokuara.kuimivm.KuimiObject;
|
5 |
+
import com.kiliokuara.kuimivm.KuimiVM;
|
6 |
+
import com.kiliokuara.kuimivm.attributes.AttributeKey;
|
7 |
+
import com.sun.jna.Pointer;
|
8 |
+
|
9 |
+
public abstract class KuimiUnidbgVM {
|
10 |
+
public final KuimiVM vm;
|
11 |
+
public final AndroidEmulator emulator;
|
12 |
+
public boolean verbose;
|
13 |
+
|
14 |
+
final AttributeKey<KuimiObject<?>, KuimiUnidbgObjectMemory> MEMORY = new AttributeKey<>("unidbg-memory", o -> new KuimiUnidbgObjectMemory(this));
|
15 |
+
final JNIMemberTable jniMemberTable = new JNIMemberTable();
|
16 |
+
|
17 |
+
public KuimiUnidbgVM(KuimiVM vm, AndroidEmulator emulator) {
|
18 |
+
this.vm = vm;
|
19 |
+
this.emulator = emulator;
|
20 |
+
}
|
21 |
+
|
22 |
+
|
23 |
+
public abstract Pointer getJavaVM();
|
24 |
+
|
25 |
+
public abstract Pointer getJNIEnv();
|
26 |
+
|
27 |
+
}
|