xasaqqq commited on
Commit
eae4dfe
1 Parent(s): ec23ae6

Upload 134 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .dockerignore +6 -0
  2. .gitattributes +1 -0
  3. .gitignore +10 -0
  4. BasePath.java +15 -0
  5. Dockerfile +29 -0
  6. LICENSE +11 -0
  7. build.gradle +42 -0
  8. gradle/wrapper/gradle-wrapper.jar +0 -0
  9. gradle/wrapper/gradle-wrapper.properties +6 -0
  10. gradlew +244 -0
  11. gradlew.bat +92 -0
  12. linuxfile/libc.so +3 -0
  13. linuxfile/ls +0 -0
  14. linuxfile/sh +0 -0
  15. packer/build.gradle +288 -0
  16. packer/src/main/java/packer/HelperTask.java +78 -0
  17. packer/src/main/java/packer/tasks/ConvertToRpc.java +42 -0
  18. packer/src/main/java/packer/tasks/PackServiceLoader.java +99 -0
  19. packer/src/main/java/packer/tasks/SimpleRemapper.java +97 -0
  20. packer/src/main/java/packer/tasks/StubTransform.java +63 -0
  21. settings.gradle +5 -0
  22. src/main/java/com/kiliokuara/kuimivm/KuimiClass.java +203 -0
  23. src/main/java/com/kiliokuara/kuimivm/KuimiField.java +81 -0
  24. src/main/java/com/kiliokuara/kuimivm/KuimiFieldTable.java +289 -0
  25. src/main/java/com/kiliokuara/kuimivm/KuimiMember.java +18 -0
  26. src/main/java/com/kiliokuara/kuimivm/KuimiMemberTable.java +40 -0
  27. src/main/java/com/kiliokuara/kuimivm/KuimiMethod.java +396 -0
  28. src/main/java/com/kiliokuara/kuimivm/KuimiMethodTable.java +479 -0
  29. src/main/java/com/kiliokuara/kuimivm/KuimiObject.java +66 -0
  30. src/main/java/com/kiliokuara/kuimivm/KuimiObjectMemory.java +28 -0
  31. src/main/java/com/kiliokuara/kuimivm/KuimiVM.java +31 -0
  32. src/main/java/com/kiliokuara/kuimivm/abstractvm/ClassPool.java +35 -0
  33. src/main/java/com/kiliokuara/kuimivm/abstractvm/KuimiAbstractVM.java +515 -0
  34. src/main/java/com/kiliokuara/kuimivm/attributes/AttributeKey.java +19 -0
  35. src/main/java/com/kiliokuara/kuimivm/attributes/AttributeMap.java +16 -0
  36. src/main/java/com/kiliokuara/kuimivm/execute/ObjectPool.java +100 -0
  37. src/main/java/com/kiliokuara/kuimivm/execute/StackTrace.java +146 -0
  38. src/main/java/com/kiliokuara/kuimivm/objects/KuimiArrays.java +100 -0
  39. src/main/java/com/kiliokuara/kuimivm/objects/KuimiString.java +31 -0
  40. src/main/java/com/kiliokuara/kuimivm/runtime/ArrayAccess.java +30 -0
  41. src/main/java/com/kiliokuara/kuimivm/runtime/ClassInitEnsure.java +22 -0
  42. src/main/java/com/kiliokuara/kuimivm/runtime/FieldAccessBridge.java +120 -0
  43. src/main/java/com/kiliokuara/kuimivm/transform/JarTransformer.java +918 -0
  44. src/main/java/com/kiliokuara/kuimivm/unidbg/ArmVarArg64Visitor.java +45 -0
  45. src/main/java/com/kiliokuara/kuimivm/unidbg/ArmVarArgVisitor.java +19 -0
  46. src/main/java/com/kiliokuara/kuimivm/unidbg/JNIMemberTable.java +62 -0
  47. src/main/java/com/kiliokuara/kuimivm/unidbg/JValueListVisitor.java +56 -0
  48. src/main/java/com/kiliokuara/kuimivm/unidbg/KuimiJniMethodHandle.java +172 -0
  49. src/main/java/com/kiliokuara/kuimivm/unidbg/KuimiUnidbgObjectMemory.java +139 -0
  50. 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
+ }