Clean up EvalController by using a callback in -[DebuggerBackEnd evalScript:callback:].
[macgdbp.git] / Source / DebuggerBackEnd.m
1 /*
2 * MacGDBp
3 * Copyright (c) 2007 - 2011, 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 "DebuggerBackEnd.h"
18
19 #import "AppDelegate.h"
20 #import "modp_b64.h"
21 #import "NSXMLElementAdditions.h"
22
23 @interface DebuggerBackEnd ()
24 @property(readwrite, copy, nonatomic) NSString* status;
25 @end
26
27 @implementation DebuggerBackEnd {
28 // The connection to the debugger engine.
29 NSUInteger _port;
30 ProtocolClient* _client;
31
32 // Whether or not a debugging session is currently active.
33 BOOL _active;
34
35 // The earliest transaction ID for the current build of |stackFrames_|.
36 NSInteger stackFirstTransactionID_;
37 }
38
39 @synthesize status = _status;
40 @synthesize autoAttach = _autoAttach;
41 @synthesize delegate = _delegate;
42
43 - (id)initWithPort:(NSUInteger)aPort
44 {
45 if (self = [super init]) {
46 [[BreakpointManager sharedManager] setConnection:self];
47 _port = aPort;
48 _client = [[ProtocolClient alloc] initWithDelegate:self];
49
50 _autoAttach = [[NSUserDefaults standardUserDefaults] boolForKey:@"DebuggerAttached"];
51
52 if (self.autoAttach)
53 [_client connectOnPort:_port];
54 }
55 return self;
56 }
57
58 - (void)dealloc {
59 [_client release];
60 [super dealloc];
61 }
62
63 // Getters /////////////////////////////////////////////////////////////////////
64 #pragma mark Getters
65
66 /**
67 * Gets the port number
68 */
69 - (NSUInteger)port {
70 return _port;
71 }
72
73 /**
74 * Returns whether or not we have an active connection
75 */
76 - (BOOL)isConnected {
77 return _active;
78 }
79
80 /**
81 * Sets the attached state of the debugger. This will open and close the
82 * connection as appropriate.
83 */
84 - (void)setAutoAttach:(BOOL)flag {
85 if (flag == _autoAttach)
86 return;
87
88 if (_autoAttach)
89 [_client disconnect];
90 else
91 [_client connectOnPort:_port];
92
93 _autoAttach = flag;
94 }
95
96 // Commands ////////////////////////////////////////////////////////////////////
97 #pragma mark Commands
98
99 /**
100 * Tells the debugger to continue running the script. Returns the current stack frame.
101 */
102 - (void)run {
103 [_client sendCommandWithFormat:@"run" handler:^(NSXMLDocument* message) {
104 [self debuggerStep:message];
105 }];
106 }
107
108 /**
109 * Tells the debugger to step into the current command.
110 */
111 - (void)stepIn {
112 [_client sendCommandWithFormat:@"step_into" handler:^(NSXMLDocument* message) {
113 [self debuggerStep:message];
114 }];
115 }
116
117 /**
118 * Tells the debugger to step out of the current context
119 */
120 - (void)stepOut {
121 [_client sendCommandWithFormat:@"step_out" handler:^(NSXMLDocument* message) {
122 [self debuggerStep:message];
123 }];
124 }
125
126 /**
127 * Tells the debugger to step over the current function
128 */
129 - (void)stepOver {
130 [_client sendCommandWithFormat:@"step_over" handler:^(NSXMLDocument* message) {
131 [self debuggerStep:message];
132 }];
133 }
134
135 /**
136 * Halts execution of the script.
137 */
138 - (void)stop {
139 [_client disconnect];
140 _active = NO;
141 self.status = @"Stopped";
142 }
143
144 /**
145 * Ends the current debugging session.
146 */
147 - (void)detach {
148 [_client sendCommandWithFormat:@"detach"];
149 _active = NO;
150 self.status = @"Stopped";
151 }
152
153 /**
154 * Tells the debugger engine to get a specifc property. This also takes in the NSXMLElement
155 * that requested it so that the child can be attached.
156 */
157 - (void)getChildrenOfProperty:(VariableNode*)property
158 atDepth:(NSInteger)depth
159 callback:(void (^)(NSArray*))callback {
160 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
161 /*
162 <response>
163 <property> <!-- this is the one we requested -->
164 <property ... /> <!-- these are what we want -->
165 </property>
166 </repsonse>
167 */
168
169 // Detach all the children so we can insert them into another document.
170 NSXMLElement* parent = (NSXMLElement*)[[message rootElement] childAtIndex:0];
171 NSArray* children = [parent children];
172 [parent setChildren:nil];
173
174 callback(children);
175 };
176 [_client sendCommandWithFormat:@"property_get -d %d -n %@" handler:handler, depth, [property fullName]];
177 }
178
179 - (void)loadStackFrame:(StackFrame*)frame {
180 if (frame.loaded)
181 return;
182
183 // Get the source code of the file. Escape % in URL chars.
184 if ([frame.filename length]) {
185 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
186 int receivedTransaction = [_client transactionIDFromResponse:message];
187 if (receivedTransaction < stackFirstTransactionID_)
188 return;
189
190 frame.source = [[message rootElement] base64DecodedValue];
191 if ([self.delegate respondsToSelector:@selector(sourceUpdated:)])
192 [self.delegate sourceUpdated:frame];
193 };
194 [_client sendCommandWithFormat:@"source -f %@" handler:handler, frame.filename];
195 }
196
197 // Get the names of all the contexts.
198 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
199 [self loadContexts:message forFrame:frame];
200 };
201 [_client sendCommandWithFormat:@"context_names -d %d" handler:handler, frame.index];
202
203 // This frame will be fully loaded.
204 frame.loaded = YES;
205 }
206
207 // Breakpoint Management ///////////////////////////////////////////////////////
208 #pragma mark Breakpoints
209
210 /**
211 * Send an add breakpoint command
212 */
213 - (void)addBreakpoint:(Breakpoint*)bp {
214 if (!_active)
215 return;
216
217 NSString* file = [ProtocolClient escapedFilePathURI:[bp transformedPath]];
218 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
219 [bp setDebuggerId:[[[[message rootElement] attributeForName:@"id"] stringValue] intValue]];
220 };
221 [_client sendCommandWithFormat:@"breakpoint_set -t line -f %@ -n %i" handler:handler, file, [bp line]];
222 }
223
224 /**
225 * Removes a breakpoint
226 */
227 - (void)removeBreakpoint:(Breakpoint*)bp {
228 if (!_active)
229 return;
230
231 [_client sendCommandWithFormat:@"breakpoint_remove -d %i", [bp debuggerId]];
232 }
233
234 /**
235 * Sends a string to be evaluated by the engine.
236 */
237 - (void)evalScript:(NSString*)str callback:(void (^)(NSString*))callback {
238 if (!_active)
239 return;
240
241 char* encodedString = malloc(modp_b64_encode_len([str length]));
242 modp_b64_encode(encodedString, [str UTF8String], [str length]);
243 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
244 NSXMLElement* parent = (NSXMLElement*)[[message rootElement] childAtIndex:0];
245 NSString* value = [parent base64DecodedValue];
246 callback(value);
247 };
248 [_client sendCustomCommandWithFormat:@"eval -i {txn} -- %s" handler:handler, encodedString];
249 free(encodedString);
250 }
251
252 // Protocol Client Delegate ////////////////////////////////////////////////////
253 #pragma mark Protocol Client Delegate
254
255 - (void)debuggerEngineConnected:(ProtocolClient*)client {
256 _active = YES;
257 }
258
259 /**
260 * Called when the connection is finally closed. This will reopen the listening
261 * socket if the debugger remains attached.
262 */
263 - (void)debuggerEngineDisconnected:(ProtocolClient*)client {
264 _active = NO;
265
266 if ([self.delegate respondsToSelector:@selector(debuggerDisconnected)])
267 [self.delegate debuggerDisconnected];
268
269 if (self.autoAttach)
270 [_client connectOnPort:_port];
271 }
272
273 - (void)protocolClient:(ProtocolClient*)client receivedInitialMessage:(NSXMLDocument*)message {
274 [self handleInitialResponse:message];
275 }
276
277 - (void)protocolClient:(ProtocolClient*)client receivedErrorMessage:(NSXMLDocument*)message {
278 NSArray* error = [[message rootElement] elementsForName:@"error"];
279 if ([error count] > 0) {
280 NSLog(@"Xdebug error: %@", error);
281 NSString* errorMessage = [[[[error objectAtIndex:0] children] objectAtIndex:0] stringValue];
282 [self errorEncountered:errorMessage];
283 }
284 }
285
286 // Specific Response Handlers //////////////////////////////////////////////////
287 #pragma mark Response Handlers
288
289 - (void)errorEncountered:(NSString*)error {
290 [self.delegate errorEncountered:error];
291 }
292
293 /**
294 * Initial packet received. We've started a brand-new connection to the engine.
295 */
296 - (void)handleInitialResponse:(NSXMLDocument*)response {
297 if (!self.autoAttach) {
298 [_client sendCommandWithFormat:@"detach"];
299 return;
300 }
301
302 _active = YES;
303
304 // Register any breakpoints that exist offline.
305 for (Breakpoint* bp in [[BreakpointManager sharedManager] breakpoints])
306 [self addBreakpoint:bp];
307
308 // Load the debugger to make it look active.
309 [self.delegate debuggerConnected];
310
311 // TODO: update the status.
312 }
313
314 /**
315 * Receiver for status updates. This just freshens up the UI.
316 */
317 - (void)updateStatus:(NSXMLDocument*)response {
318 self.status = [[[[response rootElement] attributeForName:@"status"] stringValue] capitalizedString];
319 _active = YES;
320 if (!_status || [_status isEqualToString:@"Stopped"]) {
321 [_delegate debuggerDisconnected];
322 _active = NO;
323 } else if ([_status isEqualToString:@"Stopping"]) {
324 [_client sendCommandWithFormat:@"stop"];
325 _active = NO;
326 }
327 }
328
329 /**
330 * Step in/out/over and run all take this path. We first get the status of the
331 * debugger and then request fresh stack information.
332 */
333 - (void)debuggerStep:(NSXMLDocument*)response {
334 [self updateStatus:response];
335 if (![self isConnected])
336 return;
337
338 // If this is the run command, tell the delegate that a bunch of updates
339 // are coming. Also remove all existing stack routes and request a new stack.
340 if ([self.delegate respondsToSelector:@selector(clobberStack)])
341 [self.delegate clobberStack];
342
343 [_client sendCommandWithFormat:@"stack_depth" handler:^(NSXMLDocument* message) {
344 stackFirstTransactionID_ = [_client transactionIDFromResponse:message];
345 [self rebuildStack:message];
346 }];
347 }
348
349 /**
350 * We ask for the stack_depth and now we clobber the stack and start rebuilding
351 * it.
352 */
353 - (void)rebuildStack:(NSXMLDocument*)response {
354 NSInteger depth = [[[[response rootElement] attributeForName:@"depth"] stringValue] intValue];
355
356 // We now need to alloc a bunch of stack frames and get the basic information
357 // for them.
358 for (NSInteger i = 0; i < depth; i++) {
359 // Use the transaction ID to create a routing path.
360 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
361 NSInteger receivedTransaction = [_client transactionIDFromResponse:message];
362 if (receivedTransaction < stackFirstTransactionID_)
363 return;
364
365 StackFrame* frame = [[[StackFrame alloc] init] autorelease];
366 NSXMLElement* xmlframe = (NSXMLElement*)[[[message rootElement] children] objectAtIndex:0];
367
368 // Initialize the stack frame.
369 frame.index = [[[xmlframe attributeForName:@"level"] stringValue] intValue];
370 frame.filename = [[xmlframe attributeForName:@"filename"] stringValue];
371 frame.lineNumber = [[[xmlframe attributeForName:@"lineno"] stringValue] intValue];
372 frame.function = [[xmlframe attributeForName:@"where"] stringValue];
373
374 // Only get the complete frame for the first level. The other frames will get
375 // information loaded lazily when the user clicks on one.
376 if (frame.index == 0) {
377 [self loadStackFrame:frame];
378 }
379
380 if ([self.delegate respondsToSelector:@selector(newStackFrame:)])
381 [self.delegate newStackFrame:frame];
382 };
383 [_client sendCommandWithFormat:@"stack_get -d %d" handler:handler, i];
384 }
385 }
386
387 /**
388 * Enumerates all the contexts of a given stack frame. We then in turn get the
389 * contents of each one of these contexts.
390 */
391 - (void)loadContexts:(NSXMLDocument*)response forFrame:(StackFrame*)frame {
392 int receivedTransaction = [_client transactionIDFromResponse:response];
393 if (receivedTransaction < stackFirstTransactionID_)
394 return;
395
396 NSXMLElement* contextNames = [response rootElement];
397 for (NSXMLElement* context in [contextNames children]) {
398 NSInteger cid = [[[context attributeForName:@"id"] stringValue] intValue];
399
400 // Fetch each context's variables.
401 ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) {
402 NSMutableArray* variables = [NSMutableArray array];
403
404 // Merge the frame's existing variables.
405 if (frame.variables)
406 [variables addObjectsFromArray:frame.variables];
407
408 // Add these new variables.
409 NSArray* addVariables = [[message rootElement] children];
410 if (addVariables) {
411 for (NSXMLElement* elm in addVariables) {
412 VariableNode* node = [[VariableNode alloc] initWithXMLNode:elm];
413 [variables addObject:[node autorelease]];
414 }
415 }
416
417 frame.variables = variables;
418 };
419 [_client sendCommandWithFormat:@"context_get -d %d -c %d" handler:handler, frame.index, cid];
420 }
421 }
422
423 @end