1
+ package com.sun.tools.jdi
2
+
3
+ import com.sun.jdi.Bootstrap
4
+ import com.sun.jdi.VirtualMachine
5
+ import com.sun.jdi.connect.Connector
6
+ import com.sun.jdi.connect.IllegalConnectorArgumentsException
7
+ import com.sun.jdi.connect.VMStartException
8
+ import com.sun.jdi.connect.spi.Connection
9
+ import com.sun.jdi.connect.spi.TransportService
10
+ import java.io.File
11
+ import java.io.IOException
12
+ import java.io.InterruptedIOException
13
+ import java.net.URLDecoder
14
+ import java.net.URLEncoder
15
+ import java.nio.charset.StandardCharsets
16
+ import java.util.*
17
+
18
+ internal const val ARG_HOME = " home"
19
+ internal const val ARG_OPTIONS = " options"
20
+ internal const val ARG_MAIN = " main"
21
+ internal const val ARG_INIT_SUSPEND = " suspend"
22
+ internal const val ARG_QUOTE = " quote"
23
+ internal const val ARG_VM_EXEC = " vmexec"
24
+ internal const val ARG_CWD = " cwd"
25
+ internal const val ARG_ENVS = " envs"
26
+
27
+
28
+ class KDACommandLineLauncher : SunCommandLineLauncher {
29
+
30
+ companion object {
31
+
32
+ fun urlEncode (arg : Collection <String >? ) = arg
33
+ ?.map { URLEncoder .encode(it, StandardCharsets .UTF_8 .name()) }
34
+ ?.fold(" " ) { a, b -> " $a \n $b " }
35
+
36
+ fun urlDecode (arg : String? ) = arg
37
+ ?.trim(' \n ' )
38
+ ?.split(" \n " )
39
+ ?.map { URLDecoder .decode(it, StandardCharsets .UTF_8 .name()) }
40
+ ?.toList()
41
+ }
42
+
43
+ constructor () : super () {
44
+ addStringArgument(
45
+ ARG_CWD ,
46
+ ARG_CWD ,
47
+ " Current working directory" ,
48
+ " " ,
49
+ false )
50
+ addStringArgument(
51
+ ARG_ENVS ,
52
+ ARG_ENVS ,
53
+ " Environment variables" ,
54
+ " " ,
55
+ false )
56
+ }
57
+
58
+ override fun name (): String? {
59
+ return this .javaClass.name
60
+ }
61
+
62
+ override fun description (): String? {
63
+ return " A custom launcher supporting cwd and env variables"
64
+ }
65
+
66
+ /* *
67
+ * Copied from SunCommandLineLauncher.java and added cwd / env processing logic
68
+ */
69
+ @Throws(IOException ::class , IllegalConnectorArgumentsException ::class , VMStartException ::class )
70
+ override fun launch (arguments : Map <String , Connector .Argument >): VirtualMachine {
71
+ val vm: VirtualMachine
72
+
73
+ val home = argument(ARG_HOME , arguments).value()
74
+ val options = argument(ARG_OPTIONS , arguments).value()
75
+ val mainClassAndArgs = argument(ARG_MAIN , arguments).value()
76
+ val wait = (argument(ARG_INIT_SUSPEND ,
77
+ arguments) as BooleanArgumentImpl ).booleanValue()
78
+ val quote = argument(ARG_QUOTE , arguments).value()
79
+ val exe = argument(ARG_VM_EXEC , arguments).value()
80
+ val cwd = argument(ARG_CWD , arguments).value()
81
+ val envs = argument(ARG_ENVS , arguments).value()?.let { urlDecode(it) }?.toTypedArray()
82
+ var exePath: String?
83
+ if (quote.length > 1 ) {
84
+ throw IllegalConnectorArgumentsException (" Invalid length" ,
85
+ ARG_QUOTE )
86
+ }
87
+ if (options.indexOf(" -Djava.compiler=" ) != - 1 &&
88
+ options.toLowerCase().indexOf(" -djava.compiler=none" ) == - 1 ) {
89
+ throw IllegalConnectorArgumentsException (" Cannot debug with a JIT compiler" ,
90
+ ARG_OPTIONS )
91
+ }
92
+
93
+ /*
94
+ * Start listening.
95
+ * If we're using the shared memory transport then we pick a
96
+ * random address rather than using the (fixed) default.
97
+ * Random() uses System.currentTimeMillis() as the seed
98
+ * which can be a problem on windows (many calls to
99
+ * currentTimeMillis can return the same value), so
100
+ * we do a few retries if we get an IOException (we
101
+ * assume the IOException is the filename is already in use.)
102
+ */
103
+ var listenKey: TransportService .ListenKey
104
+ if (usingSharedMemory) {
105
+ val rr = Random ()
106
+ var failCount = 0
107
+ while (true ) {
108
+ try {
109
+ val address = " javadebug" + rr.nextInt(100000 ).toString()
110
+ listenKey = transportService().startListening(address)
111
+ break
112
+ } catch (ioe: IOException ) {
113
+ if (++ failCount > 5 ) {
114
+ throw ioe
115
+ }
116
+ }
117
+ }
118
+ } else {
119
+ listenKey = transportService().startListening()
120
+ }
121
+ val address = listenKey.address()
122
+ try {
123
+ exePath = if (home.length > 0 ) {
124
+ home + File .separator + " bin" + File .separator + exe
125
+ } else {
126
+ exe
127
+ }
128
+ // Quote only if necessary in case the quote arg value is bogus
129
+ if (hasWhitespace(exePath)) {
130
+ exePath = quote + exePath + quote
131
+ }
132
+ var xrun = " transport=" + transport().name() +
133
+ " ,address=" + address +
134
+ " ,suspend=" + if (wait) ' y' else ' n'
135
+ // Quote only if necessary in case the quote arg value is bogus
136
+ if (hasWhitespace(xrun)) {
137
+ xrun = quote + xrun + quote
138
+ }
139
+ val command = exePath + ' ' +
140
+ options + ' ' +
141
+ " -Xdebug " +
142
+ " -Xrunjdwp:" + xrun + ' ' +
143
+ mainClassAndArgs
144
+
145
+ vm = launch(commandArray = tokenizeCommand(command, quote[0 ]), listenKey = listenKey,
146
+ ts = transportService(), cwd = cwd, envs = envs, grp = grp
147
+ )
148
+ } finally {
149
+ transportService().stopListening(listenKey)
150
+ }
151
+ return vm
152
+ }
153
+
154
+ @Throws(IOException ::class , VMStartException ::class )
155
+ fun launch (commandArray : Array <String >,
156
+ listenKey : TransportService .ListenKey ,
157
+ ts : TransportService , cwd : String? , envs : Array <String >? = null, grp : ThreadGroup ): VirtualMachine {
158
+ val helper = Helper (commandArray, listenKey, ts, cwd = cwd, envs = envs, grp = grp)
159
+ helper.launchAndAccept()
160
+ val manager = Bootstrap .virtualMachineManager()
161
+ return manager.createVirtualMachine(helper.connection(),
162
+ helper.process())
163
+ }
164
+
165
+ /* *
166
+ *
167
+ * Copied from com.sun.tools.jdi.AbstractLauncher.Helper. Add cwd support.
168
+ *
169
+ * This class simply provides a context for a single launch and
170
+ * accept. It provides instance fields that can be used by
171
+ * all threads involved. This stuff can't be in the Connector proper
172
+ * because the connector is a singleton and is not specific to any
173
+ * one launch.
174
+ */
175
+ class Helper internal constructor(private val commandArray : Array <String >, private val listenKey : TransportService .ListenKey ,
176
+ private val ts : TransportService , private val cwd : String? = null , private val envs : Array <String >? = null , private val grp : ThreadGroup ) {
177
+ private var process: Process ? = null
178
+ private var connection: Connection ? = null
179
+ private var acceptException: IOException ? = null
180
+ private var exited = false
181
+
182
+ /* *
183
+ * for wait()/notify()
184
+ */
185
+ private val lock: java.lang.Object = Object ()
186
+
187
+ fun commandString (): String {
188
+ var str = " "
189
+ for (i in commandArray.indices) {
190
+ if (i > 0 ) {
191
+ str + = " "
192
+ }
193
+ str + = commandArray[i]
194
+ }
195
+ return str
196
+ }
197
+
198
+ @Throws(IOException ::class , VMStartException ::class )
199
+ fun launchAndAccept () {
200
+ synchronized(lock) {
201
+ process = Runtime .getRuntime().exec(commandArray, envs, cwd?.let { File (it) })
202
+ val acceptingThread = acceptConnection()
203
+ val monitoringThread = monitorTarget()
204
+ try {
205
+ while (connection == null &&
206
+ acceptException == null &&
207
+ ! exited) {
208
+ lock.wait()
209
+ }
210
+ if (exited) {
211
+ throw VMStartException (
212
+ " VM initialization failed for: " + commandString(), process)
213
+ }
214
+ if (acceptException != null ) {
215
+ // Rethrow the exception in this thread
216
+ throw acceptException ? : IOException (" acceptException" )
217
+ }
218
+ } catch (e: InterruptedException ) {
219
+ throw InterruptedIOException (" Interrupted during accept" )
220
+ } finally {
221
+ acceptingThread.interrupt()
222
+ monitoringThread.interrupt()
223
+ }
224
+ }
225
+ }
226
+
227
+ fun process (): Process ? {
228
+ return process
229
+ }
230
+
231
+ fun connection (): Connection ? {
232
+ return connection
233
+ }
234
+
235
+ fun notifyOfExit () {
236
+ synchronized(lock) {
237
+ exited = true
238
+ lock.notify()
239
+ }
240
+ }
241
+
242
+ fun notifyOfConnection (connection : Connection ? ) {
243
+ synchronized(lock) {
244
+ this .connection = connection
245
+ lock.notify()
246
+ }
247
+ }
248
+
249
+ fun notifyOfAcceptException (acceptException : IOException ? ) {
250
+ synchronized(lock) {
251
+ this .acceptException = acceptException
252
+ lock.notify()
253
+ }
254
+ }
255
+
256
+ fun monitorTarget (): Thread {
257
+ val thread: Thread = object : Thread (grp,
258
+ " launched target monitor" ) {
259
+ override fun run () {
260
+ try {
261
+ process!! .waitFor()
262
+ /*
263
+ * Notify waiting thread of VM error termination
264
+ */ notifyOfExit()
265
+ } catch (e: InterruptedException ) {
266
+ // Connection has been established, stop monitoring
267
+ }
268
+ }
269
+ }
270
+ thread.isDaemon = true
271
+ thread.start()
272
+ return thread
273
+ }
274
+
275
+ fun acceptConnection (): Thread {
276
+ val thread: Thread = object : Thread (grp,
277
+ " connection acceptor" ) {
278
+ override fun run () {
279
+ try {
280
+ val connection = ts.accept(listenKey, 0 , 0 )
281
+ /*
282
+ * Notify waiting thread of connection
283
+ */ notifyOfConnection(connection)
284
+ } catch (e: InterruptedIOException ) {
285
+ // VM terminated, stop accepting
286
+ } catch (e: IOException ) {
287
+ // Report any other exception to waiting thread
288
+ notifyOfAcceptException(e)
289
+ }
290
+ }
291
+ }
292
+ thread.isDaemon = true
293
+ thread.start()
294
+ return thread
295
+ }
296
+
297
+ }
298
+ }
0 commit comments