Add a new DebuggerConnection that separates out the socket logic from the handling...
[macgdbp.git] / Source / DebuggerProcessor.m
1 /*
2 * MacGDBp
3 * Copyright (c) 2007 - 2010, Blue Static <http://www.bluestatic.org>
4 *
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
10 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17 #import "DebuggerProcessor.h"
18
19 #import "AppDelegate.h"
20
21 // GDBpConnection (Private) ////////////////////////////////////////////////////
22
23 @interface DebuggerProcessor ()
24 @property (readwrite, copy) NSString* status;
25
26 - (void)initReceived:(NSXMLDocument*)response;
27 - (void)updateStatus:(NSXMLDocument*)response;
28 - (void)debuggerStep:(NSXMLDocument*)response;
29 - (void)rebuildStack:(NSXMLDocument*)response;
30 - (void)getStackFrame:(NSXMLDocument*)response;
31 - (void)setSource:(NSXMLDocument*)response;
32 - (void)contextsReceived:(NSXMLDocument*)response;
33 - (void)variablesReceived:(NSXMLDocument*)response;
34 - (void)propertiesReceived:(NSXMLDocument*)response;
35
36 @end
37
38 // GDBpConnection //////////////////////////////////////////////////////////////
39
40 @implementation DebuggerProcessor
41
42 @synthesize status;
43 @synthesize delegate;
44
45 /**
46 * Creates a new DebuggerConnection and initializes the socket from the given connection
47 * paramters.
48 */
49 - (id)initWithPort:(NSUInteger)aPort
50 {
51 if (self = [super init])
52 {
53 port = aPort;
54 connected = NO;
55
56 stackFrames_ = [[NSMutableDictionary alloc] init];
57 callbackContext_ = [NSMutableDictionary new];
58
59 [[BreakpointManager sharedManager] setConnection:self];
60 [self connect];
61 }
62 return self;
63 }
64
65 /**
66 * Deallocates the object
67 */
68 - (void)dealloc
69 {
70 [self close];
71 [stackFrames_ release];
72 [callbackContext_ release];
73 [super dealloc];
74 }
75
76
77 // Getters /////////////////////////////////////////////////////////////////////
78 #pragma mark Getters
79
80 /**
81 * Gets the port number
82 */
83 - (NSUInteger)port
84 {
85 return port;
86 }
87
88 /**
89 * Returns the name of the remote host
90 */
91 - (NSString*)remoteHost
92 {
93 if (!connected)
94 {
95 return @"(DISCONNECTED)";
96 }
97 // TODO: Either impl or remove.
98 return @"";
99 }
100
101 /**
102 * Returns whether or not we have an active connection
103 */
104 - (BOOL)isConnected
105 {
106 return connected;
107 }
108
109 // Commands ////////////////////////////////////////////////////////////////////
110 #pragma mark Commands
111
112 /**
113 * Reestablishes communication with the remote debugger so that a new connection doesn't have to be
114 * created every time you want to debug a page
115 */
116 - (void)reconnect
117 {
118 [self close];
119 self.status = @"Connecting";
120 [self connect];
121 }
122
123 /**
124 * Tells the debugger to continue running the script. Returns the current stack frame.
125 */
126 - (void)run
127 {
128 [self sendCommandWithCallback:@selector(debuggerStep:) format:@"run"];
129 }
130
131 /**
132 * Tells the debugger to step into the current command.
133 */
134 - (void)stepIn
135 {
136 [self sendCommandWithCallback:@selector(debuggerStep:) format:@"step_into"];
137 }
138
139 /**
140 * Tells the debugger to step out of the current context
141 */
142 - (void)stepOut
143 {
144 [self sendCommandWithCallback:@selector(debuggerStep:) format:@"step_out"];
145 }
146
147 /**
148 * Tells the debugger to step over the current function
149 */
150 - (void)stepOver
151 {
152 [self sendCommandWithCallback:@selector(debuggerStep:) format:@"step_over"];
153 }
154
155 /**
156 * Tells the debugger engine to get a specifc property. This also takes in the NSXMLElement
157 * that requested it so that the child can be attached.
158 */
159 - (NSInteger)getProperty:(NSString*)property
160 {
161 [self sendCommandWithCallback:@selector(propertiesReceived:) format:@"property_get -n \"%@\"", property];
162 }
163
164 // Breakpoint Management ///////////////////////////////////////////////////////
165 #pragma mark Breakpoints
166
167 /**
168 * Send an add breakpoint command
169 */
170 - (void)addBreakpoint:(Breakpoint*)bp
171 {
172 if (!connected)
173 return;
174
175 NSString* file = [self escapedURIPath:[bp transformedPath]];
176 NSNumber* transaction = [self sendCommandWithCallback:@selector(breakpointReceived:)
177 format:@"breakpoint_set -t line -f %@ -n %i", file, [bp line]];
178 [callbackContext_ setObject:bp forKey:transaction];
179 }
180
181 /**
182 * Removes a breakpoint
183 */
184 - (void)removeBreakpoint:(Breakpoint*)bp
185 {
186 if (!connected)
187 {
188 return;
189 }
190
191 [self sendCommandWithCallback:nil format:@"breakpoint_remove -d %i", [bp debuggerId]];
192 }
193
194 // Specific Response Handlers //////////////////////////////////////////////////
195 #pragma mark Response Handlers
196
197 /**
198 * Initial packet received. We've started a brand-new connection to the engine.
199 */
200 - (void)initReceived:(NSXMLDocument*)response
201 {
202 // Register any breakpoints that exist offline.
203 for (Breakpoint* bp in [[BreakpointManager sharedManager] breakpoints])
204 [self addBreakpoint:bp];
205
206 // Load the debugger to make it look active.
207 [delegate debuggerConnected];
208
209 // TODO: update the status.
210 }
211
212 /**
213 * Receiver for status updates. This just freshens up the UI.
214 */
215 - (void)updateStatus:(NSXMLDocument*)response
216 {
217 self.status = [[[[response rootElement] attributeForName:@"status"] stringValue] capitalizedString];
218 if (status == nil || [status isEqualToString:@"Stopped"] || [status isEqualToString:@"Stopping"])
219 {
220 connected = NO;
221 [self close];
222 [delegate debuggerDisconnected];
223
224 self.status = @"Stopped";
225 }
226 }
227
228 /**
229 * Step in/out/over and run all take this path. We first get the status of the
230 * debugger and then request fresh stack information.
231 */
232 - (void)debuggerStep:(NSXMLDocument*)response
233 {
234 [self updateStatus:response];
235 if (!connected)
236 return;
237
238 // If this is the run command, tell the delegate that a bunch of updates
239 // are coming. Also remove all existing stack routes and request a new stack.
240 // TODO: figure out if we can not clobber the stack every time.
241 NSString* command = [[[response rootElement] attributeForName:@"command"] stringValue];
242 if (YES || [command isEqualToString:@"run"])
243 {
244 if ([delegate respondsToSelector:@selector(clobberStack)])
245 [delegate clobberStack];
246 [stackFrames_ removeAllObjects];
247 stackFirstTransactionID_ = [[self sendCommandWithCallback:@selector(rebuildStack:) format:@"stack_depth"] intValue];
248 }
249 }
250
251 /**
252 * We ask for the stack_depth and now we clobber the stack and start rebuilding
253 * it.
254 */
255 - (void)rebuildStack:(NSXMLDocument*)response
256 {
257 NSInteger depth = [[[[response rootElement] attributeForName:@"depth"] stringValue] intValue];
258
259 if (stackFirstTransactionID_ == [self transactionIDFromResponse:response])
260 stackDepth_ = depth;
261
262 // We now need to alloc a bunch of stack frames and get the basic information
263 // for them.
264 for (NSInteger i = 0; i < depth; i++)
265 {
266 // Use the transaction ID to create a routing path.
267 NSNumber* routingID = [self sendCommandWithCallback:@selector(getStackFrame:) format:@"stack_get -d %d", i];
268 [stackFrames_ setObject:[StackFrame alloc] forKey:routingID];
269 }
270 }
271
272 /**
273 * The initial rebuild of the stack frame. We now have enough to initialize
274 * a StackFrame object.
275 */
276 - (void)getStackFrame:(NSXMLDocument*)response
277 {
278 // Get the routing information.
279 NSInteger routingID = [self transactionIDFromResponse:response];
280 if (routingID < stackFirstTransactionID_)
281 return;
282 NSNumber* routingNumber = [NSNumber numberWithInt:routingID];
283
284 // Make sure we initialized this frame in our last |-rebuildStack:|.
285 StackFrame* frame = [stackFrames_ objectForKey:routingNumber];
286 if (!frame)
287 return;
288
289 NSXMLElement* xmlframe = [[[response rootElement] children] objectAtIndex:0];
290
291 // Initialize the stack frame.
292 [frame initWithIndex:[[[xmlframe attributeForName:@"level"] stringValue] intValue]
293 withFilename:[[xmlframe attributeForName:@"filename"] stringValue]
294 withSource:nil
295 atLine:[[[xmlframe attributeForName:@"lineno"] stringValue] intValue]
296 inFunction:[[xmlframe attributeForName:@"where"] stringValue]
297 withVariables:nil];
298
299 // Get the source code of the file. Escape % in URL chars.
300 NSString* escapedFilename = [frame.filename stringByReplacingOccurrencesOfString:@"%" withString:@"%%"];
301 NSNumber* transaction = [self sendCommandWithCallback:@selector(setSource:) format:@"source -f %@", escapedFilename];
302 [callbackContext_ setObject:routingNumber forKey:transaction];
303
304 // Get the names of all the contexts.
305 transaction = [self sendCommandWithCallback:@selector(contextsReceived:) format:@"context_names -d %d", frame.index];
306 [callbackContext_ setObject:routingNumber forKey:transaction];
307
308 if ([delegate respondsToSelector:@selector(newStackFrame:)])
309 [delegate newStackFrame:frame];
310 }
311
312 /**
313 * Callback for setting the source of a file while rebuilding a specific stack
314 * frame.
315 */
316 - (void)setSource:(NSXMLDocument*)response
317 {
318 NSNumber* transaction = [NSNumber numberWithInt:[self transactionIDFromResponse:response]];
319 if ([transaction intValue] < stackFirstTransactionID_)
320 return;
321 NSNumber* routingNumber = [callbackContext_ objectForKey:transaction];
322 if (!routingNumber)
323 return;
324
325 [callbackContext_ removeObjectForKey:transaction];
326 StackFrame* frame = [stackFrames_ objectForKey:routingNumber];
327 if (!frame)
328 return;
329
330 frame.source = [[response rootElement] value];
331
332 if ([delegate respondsToSelector:@selector(sourceUpdated:)])
333 [delegate sourceUpdated:frame];
334 }
335
336 /**
337 * Enumerates all the contexts of a given stack frame. We then in turn get the
338 * contents of each one of these contexts.
339 */
340 - (void)contextsReceived:(NSXMLDocument*)response
341 {
342 // Get the stack frame's routing ID and use it again.
343 NSNumber* receivedTransaction = [NSNumber numberWithInt:[self transactionIDFromResponse:response]];
344 if ([receivedTransaction intValue] < stackFirstTransactionID_)
345 return;
346 NSNumber* routingID = [callbackContext_ objectForKey:receivedTransaction];
347 if (!routingID)
348 return;
349
350 // Get the stack frame by the |routingID|.
351 StackFrame* frame = [stackFrames_ objectForKey:routingID];
352
353 NSXMLElement* contextNames = [response rootElement];
354 for (NSXMLElement* context in [contextNames children])
355 {
356 NSInteger cid = [[[context attributeForName:@"id"] stringValue] intValue];
357
358 // Fetch each context's variables.
359 NSNumber* transaction = [self sendCommandWithCallback:@selector(variablesReceived:)
360 format:@"context_get -d %d -c %d", frame.index, cid];
361 [callbackContext_ setObject:routingID forKey:transaction];
362 }
363 }
364
365 /**
366 * Receives the variables from the context and attaches them to the stack frame.
367 */
368 - (void)variablesReceived:(NSXMLDocument*)response
369 {
370 // Get the stack frame's routing ID and use it again.
371 NSInteger transaction = [self transactionIDFromResponse:response];
372 if (transaction < stackFirstTransactionID_)
373 return;
374 NSNumber* receivedTransaction = [NSNumber numberWithInt:transaction];
375 NSNumber* routingID = [callbackContext_ objectForKey:receivedTransaction];
376 if (!routingID)
377 return;
378
379 // Get the stack frame by the |routingID|.
380 StackFrame* frame = [stackFrames_ objectForKey:routingID];
381
382 NSMutableArray* variables = [NSMutableArray array];
383
384 // Merge the frame's existing variables.
385 if (frame.variables)
386 [variables addObjectsFromArray:frame.variables];
387
388 // Add these new variables.
389 NSArray* addVariables = [[response rootElement] children];
390 if (addVariables)
391 [variables addObjectsFromArray:addVariables];
392
393 frame.variables = variables;
394 }
395
396 /**
397 * Callback from a |-getProperty:| request.
398 */
399 - (void)propertiesReceived:(NSXMLDocument*)response
400 {
401 NSInteger transaction = [self transactionIDFromResponse:response];
402
403 /*
404 <response>
405 <property> <!-- this is the one we requested -->
406 <property ... /> <!-- these are what we want -->
407 </property>
408 </repsonse>
409 */
410
411 // Detach all the children so we can insert them into another document.
412 NSXMLElement* parent = (NSXMLElement*)[[response rootElement] childAtIndex:0];
413 NSArray* children = [parent children];
414 [parent setChildren:nil];
415
416 [delegate receivedProperties:children forTransaction:transaction];
417 }
418
419 /**
420 * Callback for setting a breakpoint.
421 */
422 - (void)breakpointReceived:(NSXMLDocument*)response
423 {
424 NSNumber* transaction = [NSNumber numberWithInt:[self transactionIDFromResponse:response]];
425 Breakpoint* bp = [callbackContext_ objectForKey:transaction];
426 if (!bp)
427 return;
428
429 [callbackContext_ removeObjectForKey:callbackContext_];
430 [bp setDebuggerId:[[[[response rootElement] attributeForName:@"id"] stringValue] intValue]];
431 }
432
433 /**
434 * Given a file path, this returns a file:// URI and escapes any spaces for the
435 * debugger engine.
436 */
437 - (NSString*)escapedURIPath:(NSString*)path
438 {
439 // Custon GDBp paths are fine.
440 if ([[path substringToIndex:4] isEqualToString:@"gdbp"])
441 return path;
442
443 // Create a temporary URL that will escape all the nasty characters.
444 NSURL* url = [NSURL fileURLWithPath:path];
445 NSString* urlString = [url absoluteString];
446
447 // Remove the host because this is a file:// URL;
448 urlString = [urlString stringByReplacingOccurrencesOfString:[url host] withString:@""];
449
450 // Escape % for use in printf-style NSString formatters.
451 urlString = [urlString stringByReplacingOccurrencesOfString:@"%" withString:@"%%"];
452 return urlString;
453 }
454
455 @end