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