From 8e44ddaa4b1aaed5b5fe1e743f79e73a3e48217c Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Sat, 24 Oct 2015 01:23:38 -0400 Subject: [PATCH] Move management of the stack to DebuggerModel. This deletes the old StackController and adds a new method -[DebuggerModel updateStack:] that updates the array, attempting to preserve identical stack frames. A new target MacGDBp-Tests is also added, for testing the new DebuggerModel functionality. --- English.lproj/Debugger.xib | 2 +- MacGDBp.xcodeproj/project.pbxproj | 129 ++++++++++++++++++++++++++-- Source/DebuggerBackEnd.h | 6 -- Source/DebuggerBackEnd.m | 53 ++++++------ Source/DebuggerController.h | 6 -- Source/DebuggerController.m | 33 +------- Source/DebuggerModel.h | 10 +++ Source/DebuggerModel.m | 61 +++++++++++++- Source/StackController.h | 34 -------- Source/StackController.m | 74 ---------------- Source/StackFrame.m | 7 ++ Source/Tests/DebuggerModelTest.m | 136 ++++++++++++++++++++++++++++++ UnitTests-Info.plist | 24 ++++++ 13 files changed, 389 insertions(+), 186 deletions(-) delete mode 100644 Source/StackController.h delete mode 100644 Source/StackController.m create mode 100644 Source/Tests/DebuggerModelTest.m create mode 100644 UnitTests-Info.plist diff --git a/English.lproj/Debugger.xib b/English.lproj/Debugger.xib index 0d210fb..1d2a263 100644 --- a/English.lproj/Debugger.xib +++ b/English.lproj/Debugger.xib @@ -398,7 +398,7 @@ variables - + diff --git a/MacGDBp.xcodeproj/project.pbxproj b/MacGDBp.xcodeproj/project.pbxproj index 9902dd0..08eecc8 100644 --- a/MacGDBp.xcodeproj/project.pbxproj +++ b/MacGDBp.xcodeproj/project.pbxproj @@ -30,12 +30,14 @@ 1E6B594C11610993001189D2 /* Log.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1E6B594A11610993001189D2 /* Log.xib */; }; 1E7188690D839F6300969277 /* BSSourceView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1E7188650D839F6300969277 /* BSSourceView.mm */; }; 1E822CDD0DA28AC30027A23F /* Breakpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E822CDC0DA28AC30027A23F /* Breakpoint.m */; }; + 1E8C70A21BDB16A900D333DC /* DebuggerModelTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E8C70A11BDB16A900D333DC /* DebuggerModelTest.m */; settings = {ASSET_TAGS = (); }; }; + 1E8C70A31BDB173A00D333DC /* DebuggerModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EECC0B91BC9B1E700FB22D3 /* DebuggerModel.m */; settings = {ASSET_TAGS = (); }; }; + 1E8C70A41BDB183300D333DC /* StackFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EB7BED40ECF3CA90033283A /* StackFrame.m */; settings = {ASSET_TAGS = (); }; }; 1E9582620E252474001A3D89 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1E9582600E252474001A3D89 /* Preferences.xib */; }; 1E9582670E2524AD001A3D89 /* PreferencesController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E9582660E2524AD001A3D89 /* PreferencesController.m */; }; 1E9583200E2531BD001A3D89 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1E95831F0E2531BD001A3D89 /* Sparkle.framework */; }; 1E95834C0E2531D5001A3D89 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1E95831F0E2531BD001A3D89 /* Sparkle.framework */; }; 1EB7BED50ECF3CA90033283A /* StackFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EB7BED40ECF3CA90033283A /* StackFrame.m */; }; - 1EBF4D5D0EE35F0700B62769 /* StackController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EBF4D5C0EE35F0700B62769 /* StackController.m */; }; 1EC1337E127DBB00007946FC /* VariableNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EC1337D127DBB00007946FC /* VariableNode.m */; }; 1EC6965712BBC6A700A8D984 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 1EC6965112BBC6A700A8D984 /* LICENSE */; }; 1EC6965812BBC6A700A8D984 /* modp_b64.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1EC6965212BBC6A700A8D984 /* modp_b64.cc */; }; @@ -55,6 +57,16 @@ 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 1E8C709C1BDB167F00D333DC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8D1107260486CEB800E47090; + remoteInfo = MacGDBp; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 1E9583550E2531E7001A3D89 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; @@ -108,14 +120,15 @@ 1E7188650D839F6300969277 /* BSSourceView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = BSSourceView.mm; path = Source/BSSourceView.mm; sourceTree = ""; }; 1E822CDB0DA28AC30027A23F /* Breakpoint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Breakpoint.h; path = Source/Breakpoint.h; sourceTree = ""; }; 1E822CDC0DA28AC30027A23F /* Breakpoint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Breakpoint.m; path = Source/Breakpoint.m; sourceTree = ""; }; + 1E8C70971BDB167F00D333DC /* MacGDBp-Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "MacGDBp-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 1E8C709B1BDB167F00D333DC /* UnitTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "UnitTests-Info.plist"; sourceTree = ""; }; + 1E8C70A11BDB16A900D333DC /* DebuggerModelTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DebuggerModelTest.m; path = Source/Tests/DebuggerModelTest.m; sourceTree = ""; }; 1E9582610E252474001A3D89 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/Preferences.xib; sourceTree = ""; }; 1E9582650E2524AD001A3D89 /* PreferencesController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PreferencesController.h; path = Source/PreferencesController.h; sourceTree = ""; }; 1E9582660E2524AD001A3D89 /* PreferencesController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PreferencesController.m; path = Source/PreferencesController.m; sourceTree = ""; }; 1E95831F0E2531BD001A3D89 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Sparkle.framework; sourceTree = ""; }; 1EB7BED30ECF3CA90033283A /* StackFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StackFrame.h; path = Source/StackFrame.h; sourceTree = ""; }; 1EB7BED40ECF3CA90033283A /* StackFrame.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = StackFrame.m; path = Source/StackFrame.m; sourceTree = ""; }; - 1EBF4D5B0EE35F0700B62769 /* StackController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StackController.h; path = Source/StackController.h; sourceTree = ""; }; - 1EBF4D5C0EE35F0700B62769 /* StackController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = StackController.m; path = Source/StackController.m; sourceTree = ""; }; 1EC1337C127DBB00007946FC /* VariableNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VariableNode.h; path = Source/VariableNode.h; sourceTree = ""; }; 1EC1337D127DBB00007946FC /* VariableNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VariableNode.m; path = Source/VariableNode.m; sourceTree = ""; }; 1EC6965112BBC6A700A8D984 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; @@ -148,6 +161,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 1E8C70941BDB167F00D333DC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8D11072E0486CEB800E47090 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -198,6 +218,7 @@ isa = PBXGroup; children = ( 8D1107320486CEB800E47090 /* MacGDBp.app */, + 1E8C70971BDB167F00D333DC /* MacGDBp-Tests.xctest */, ); name = Products; sourceTree = ""; @@ -286,14 +307,13 @@ children = ( 1EECC0B81BC9B1E700FB22D3 /* DebuggerModel.h */, 1EECC0B91BC9B1E700FB22D3 /* DebuggerModel.m */, + 1E8C70A11BDB16A900D333DC /* DebuggerModelTest.m */, 1E02C5F40C610724006F1752 /* DebuggerController.h */, 1E02C5F50C610724006F1752 /* DebuggerController.m */, 1E108E3E136CC8B9002E34E0 /* EvalController.h */, 1E108E3F136CC8B9002E34E0 /* EvalController.m */, 1EB7BED30ECF3CA90033283A /* StackFrame.h */, 1EB7BED40ECF3CA90033283A /* StackFrame.m */, - 1EBF4D5B0EE35F0700B62769 /* StackController.h */, - 1EBF4D5C0EE35F0700B62769 /* StackController.m */, 1EC1337C127DBB00007946FC /* VariableNode.h */, 1EC1337D127DBB00007946FC /* VariableNode.m */, 1E109017136DD92D002E34E0 /* StripLineBreaksValueTransformer.h */, @@ -358,6 +378,7 @@ 1EEBFD110D359A9F008F835B /* dimple.png */, 8D1107310486CEB800E47090 /* Info.plist */, 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */, + 1E8C709B1BDB167F00D333DC /* UnitTests-Info.plist */, ); name = Resources; sourceTree = ""; @@ -374,6 +395,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 1E8C70961BDB167F00D333DC /* MacGDBp-Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1E8C709E1BDB167F00D333DC /* Build configuration list for PBXNativeTarget "MacGDBp-Tests" */; + buildPhases = ( + 1E8C70931BDB167F00D333DC /* Sources */, + 1E8C70941BDB167F00D333DC /* Frameworks */, + 1E8C70951BDB167F00D333DC /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 1E8C709D1BDB167F00D333DC /* PBXTargetDependency */, + ); + name = "MacGDBp-Tests"; + productName = "MacGDBp-Tests"; + productReference = 1E8C70971BDB167F00D333DC /* MacGDBp-Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 8D1107260486CEB800E47090 /* MacGDBp */ = { isa = PBXNativeTarget; buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "MacGDBp" */; @@ -400,6 +439,11 @@ 29B97313FDCFA39411CA2CEA /* Project object */ = { isa = PBXProject; attributes = { + TargetAttributes = { + 1E8C70961BDB167F00D333DC = { + CreatedOnToolsVersion = 7.0.1; + }; + }; }; buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "MacGDBp" */; compatibilityVersion = "Xcode 3.1"; @@ -416,11 +460,19 @@ projectRoot = ""; targets = ( 8D1107260486CEB800E47090 /* MacGDBp */, + 1E8C70961BDB167F00D333DC /* MacGDBp-Tests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 1E8C70951BDB167F00D333DC /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8D1107290486CEB800E47090 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -465,6 +517,16 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 1E8C70931BDB167F00D333DC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1E8C70A41BDB183300D333DC /* StackFrame.m in Sources */, + 1E8C70A21BDB16A900D333DC /* DebuggerModelTest.m in Sources */, + 1E8C70A31BDB173A00D333DC /* DebuggerModel.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8D11072C0486CEB800E47090 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -481,7 +543,6 @@ 1EFF70C30DFDC018006B9D33 /* BreakpointController.m in Sources */, 1E9582670E2524AD001A3D89 /* PreferencesController.m in Sources */, 1EB7BED50ECF3CA90033283A /* StackFrame.m in Sources */, - 1EBF4D5D0EE35F0700B62769 /* StackController.m in Sources */, 1E67E6FD0F3C052000E68F1B /* PreferencesPathsArrayController.m in Sources */, 1EECC0BA1BC9B1E800FB22D3 /* DebuggerModel.m in Sources */, 1E6B5947116106FE001189D2 /* LoggingController.m in Sources */, @@ -499,6 +560,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 1E8C709D1BDB167F00D333DC /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8D1107260486CEB800E47090 /* MacGDBp */; + targetProxy = 1E8C709C1BDB167F00D333DC /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = { isa = PBXVariantGroup; @@ -559,6 +628,46 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 1E8C709F1BDB167F00D333DC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ENABLE_MODULES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = "UnitTests-Info.plist"; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "org.bluestatic.MacGDBp-Tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 1E8C70A01BDB167F00D333DC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ENABLE_MODULES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_NO_COMMON_BLOCKS = YES; + INFOPLIST_FILE = "UnitTests-Info.plist"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = "org.bluestatic.MacGDBp-Tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; C01FCF4B08A954540054247B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -619,6 +728,14 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 1E8C709E1BDB167F00D333DC /* Build configuration list for PBXNativeTarget "MacGDBp-Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1E8C709F1BDB167F00D333DC /* Debug */, + 1E8C70A01BDB167F00D333DC /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "MacGDBp" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Source/DebuggerBackEnd.h b/Source/DebuggerBackEnd.h index b4f8eb1..c000a14 100644 --- a/Source/DebuggerBackEnd.h +++ b/Source/DebuggerBackEnd.h @@ -92,12 +92,6 @@ // Called when we disconnect. - (void)debuggerDisconnected; -// Tells the debugger to destroy the current stack display. -- (void)clobberStack; - -// Tells the debugger that a new stack frame is avaliable. -- (void)newStackFrame:(StackFrame*)frame; - // Tells the debugger that new source is available for the given frame. // TODO: rename to |-frameUpdated:|. - (void)sourceUpdated:(StackFrame*)frame; diff --git a/Source/DebuggerBackEnd.m b/Source/DebuggerBackEnd.m index 17ffb76..435d858 100644 --- a/Source/DebuggerBackEnd.m +++ b/Source/DebuggerBackEnd.m @@ -332,11 +332,6 @@ if (![self isConnected]) return; - // If this is the run command, tell the delegate that a bunch of updates - // are coming. Also remove all existing stack routes and request a new stack. - if ([self.delegate respondsToSelector:@selector(clobberStack)]) - [self.delegate clobberStack]; - [_client sendCommandWithFormat:@"stack_depth" handler:^(NSXMLDocument* message) { [self rebuildStack:message]; }]; @@ -347,35 +342,41 @@ * it. */ - (void)rebuildStack:(NSXMLDocument*)response { - NSInteger depth = [[[[response rootElement] attributeForName:@"depth"] stringValue] intValue]; + NSUInteger depth = [[[[response rootElement] attributeForName:@"depth"] stringValue] intValue]; + + // Start with frame 0. If this is a shifted frame, then only it needs to be + // re-loaded. If it is not shifted, see if another frame on the stack is equal + // to it; if so, then the frames up to that must be discarded. If not, this is + // a new stack frame that should be inserted at the top of the stack. Finally, + // the sice of the stack is trimmed to |depth| from the bottom. - // We now need to alloc a bunch of stack frames and get the basic information - // for them. - for (NSInteger i = 0; i < depth; i++) { - // Use the transaction ID to create a routing path. + __block NSMutableArray* tempStack = [[NSMutableArray alloc] init]; + + for (NSUInteger i = 0; i < depth; ++i) { ProtocolClientMessageHandler handler = ^(NSXMLDocument* message) { - StackFrame* frame = [[[StackFrame alloc] init] autorelease]; - NSXMLElement* xmlframe = (NSXMLElement*)[[[message rootElement] children] objectAtIndex:0]; - - // Initialize the stack frame. - frame.index = [[[xmlframe attributeForName:@"level"] stringValue] intValue]; - frame.filename = [[xmlframe attributeForName:@"filename"] stringValue]; - frame.lineNumber = [[[xmlframe attributeForName:@"lineno"] stringValue] intValue]; - frame.function = [[xmlframe attributeForName:@"where"] stringValue]; - - // Only get the complete frame for the first level. The other frames will get - // information loaded lazily when the user clicks on one. - if (frame.index == 0) { - [self loadStackFrame:frame]; + [tempStack addObject:[self transformXMLToStackFrame:message]]; + if (i == depth - 1) { + [self.model updateStack:[tempStack autorelease]]; } - - if ([self.delegate respondsToSelector:@selector(newStackFrame:)]) - [self.delegate newStackFrame:frame]; }; [_client sendCommandWithFormat:@"stack_get -d %d" handler:handler, i]; } } +/** + * Creates a StackFrame object from an NSXMLDocument response from the "stack_get" + * command. + */ +- (StackFrame*)transformXMLToStackFrame:(NSXMLDocument*)response { + NSXMLElement* xmlframe = (NSXMLElement*)[[[response rootElement] children] objectAtIndex:0]; + StackFrame* frame = [[[StackFrame alloc] init] autorelease]; + frame.index = [[[xmlframe attributeForName:@"level"] stringValue] intValue]; + frame.filename = [[xmlframe attributeForName:@"filename"] stringValue]; + frame.lineNumber = [[[xmlframe attributeForName:@"lineno"] stringValue] intValue]; + frame.function = [[xmlframe attributeForName:@"where"] stringValue]; + return frame; +} + /** * Enumerates all the contexts of a given stack frame. We then in turn get the * contents of each one of these contexts. diff --git a/Source/DebuggerController.h b/Source/DebuggerController.h index 418d935..cb8f036 100644 --- a/Source/DebuggerController.h +++ b/Source/DebuggerController.h @@ -16,7 +16,6 @@ #import #import "DebuggerBackEnd.h" -#import "StackController.h" #include "VariableNode.h" @class BSSourceView; @@ -27,14 +26,9 @@ DebuggerBackEnd* connection; DebuggerModel* _model; - - // This is true when the |connection| has told us to clobber. We will do - // so upon receipt of the first new stack frame. - BOOL aboutToClobber_; IBOutlet NSButton* attachedCheckbox_; - StackController* stackController; IBOutlet NSArrayController* stackArrayController; IBOutlet NSTreeController* variablesTreeController; diff --git a/Source/DebuggerController.m b/Source/DebuggerController.m index 3b3c41c..c15befd 100644 --- a/Source/DebuggerController.m +++ b/Source/DebuggerController.m @@ -25,7 +25,6 @@ @interface DebuggerController (Private) - (void)updateSourceViewer; -- (void)updateStackViewer; - (void)expandVariables; @end @@ -41,8 +40,6 @@ { if (self = [super initWithWindowNibName:@"Debugger"]) { - stackController = [[StackController alloc] init]; - NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; _model = [[DebuggerModel alloc] init]; @@ -68,7 +65,6 @@ [connection release]; [_model release]; [expandedVariables release]; - [stackController release]; [super dealloc]; } @@ -92,7 +88,7 @@ SEL action = [anItem action]; if (action == @selector(stepOut:)) { - return ([connection isConnected] && [stackController.stack count] > 1); + return ([connection isConnected] && _model.stackDepth > 1); } else if (action == @selector(stepIn:) || action == @selector(stepOver:) || action == @selector(run:) || @@ -130,7 +126,6 @@ - (void)resetDisplays { [variablesTreeController setContent:nil]; - [stackController.stack removeAllObjects]; [stackArrayController rearrangeObjects]; [[sourceViewer textView] setString:@""]; sourceViewer.file = nil; @@ -318,15 +313,6 @@ [[sourceViewer textView] display]; } -/** - * Does some house keeping to the stack viewer - */ -- (void)updateStackViewer -{ - [stackArrayController rearrangeObjects]; - [stackArrayController setSelectionIndex:0]; -} - /** * Expands the variables based on the stored set */ @@ -374,23 +360,6 @@ #pragma mark GDBpConnectionDelegate -- (void)clobberStack -{ - aboutToClobber_ = YES; -} - -- (void)newStackFrame:(StackFrame*)frame -{ - if (aboutToClobber_) - { - [stackController.stack removeAllObjects]; - aboutToClobber_ = NO; - } - [stackController push:frame]; - [self updateStackViewer]; - [self updateSourceViewer]; -} - - (void)sourceUpdated:(StackFrame*)frame { [self updateSourceViewer]; diff --git a/Source/DebuggerModel.h b/Source/DebuggerModel.h index fc01429..5b8e4a2 100644 --- a/Source/DebuggerModel.h +++ b/Source/DebuggerModel.h @@ -16,14 +16,24 @@ #import +@class StackFrame; + @interface DebuggerModel : NSObject // A human-readable representation of the debugger state. E.g., "Break" or // "Stopped". @property(copy, nonatomic) NSString* status; +@property(readonly, nonatomic) NSArray* stack; + +@property(readonly, nonatomic) NSUInteger stackDepth; + // Informs the model that a new connection was initiated. This clears any data // in the model. - (void)onNewConnection; +// Replaces the current stack with |newStack|. This will attempt to preserve +// any already loaded frames. +- (void)updateStack:(NSArray*)newStack; + @end diff --git a/Source/DebuggerModel.m b/Source/DebuggerModel.m index 676b471..b3a2c5b 100644 --- a/Source/DebuggerModel.m +++ b/Source/DebuggerModel.m @@ -16,10 +16,69 @@ #import "DebuggerModel.h" -@implementation DebuggerModel +#import "StackFrame.h" + +@implementation DebuggerModel { + NSMutableArray* _stack; +} + +- (instancetype)init { + if (self = [super init]) { + _stack = [NSMutableArray new]; + } + return self; +} + +- (void)dealloc { + [_status release]; + [_stack release]; + [super dealloc]; +} + +- (NSUInteger)stackDepth { + return self.stack.count; +} - (void)onNewConnection { self.status = nil; + [_stack removeAllObjects]; +} + +- (void)updateStack:(NSArray*)newStack { + // Iterate, in reverse order from the bottom to the top, both stacks to find + // the point of divergence. + NSEnumerator* itNewStack = [newStack reverseObjectEnumerator]; + NSEnumerator* itOldStack = [self.stack reverseObjectEnumerator]; + + StackFrame* frameNew; + StackFrame* frameOld = [itOldStack nextObject]; + NSUInteger oldStackOffset = self.stack.count; + while (frameNew = [itNewStack nextObject]) { + if ([frameNew isEqual:frameOld]) { + --oldStackOffset; + frameOld = [itOldStack nextObject]; + } else { + break; + } + } + + [self willChangeValueForKey:@"stack"]; + + // Remove any frames from the top of the stack that are not shared with the + // new stack. + [_stack removeObjectsInRange:NSMakeRange(0, oldStackOffset)]; + + // Continue inserting objects to update the stack with the new frames. + while (frameNew) { + [_stack insertObject:frameNew atIndex:0]; + frameNew = [itNewStack nextObject]; + } + + // Renumber the stack. + for (NSUInteger i = 0; i < self.stack.count; ++i) + self.stack[i].index = i; + + [self didChangeValueForKey:@"stack"]; } @end diff --git a/Source/StackController.h b/Source/StackController.h deleted file mode 100644 index 6f7edcf..0000000 --- a/Source/StackController.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * MacGDBp - * Copyright (c) 2007 - 2011, Blue Static - * - * This program is free software; you can redistribute it and/or modify it under the terms of the GNU - * General Public License as published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without - * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; if not, - * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - */ - -#import -#import "StackFrame.h" - -@interface StackController : NSObject -{ - /** - * Array of StackFrame's (LIFO stack) - */ - NSMutableArray* stack; -} - -@property(readonly) NSMutableArray* stack; - -- (StackFrame*)peek; -- (StackFrame*)pop; -- (void)push:(StackFrame*)frame; - -@end diff --git a/Source/StackController.m b/Source/StackController.m deleted file mode 100644 index 996d519..0000000 --- a/Source/StackController.m +++ /dev/null @@ -1,74 +0,0 @@ -/* - * MacGDBp - * Copyright (c) 2007 - 2011, Blue Static - * - * This program is free software; you can redistribute it and/or modify it under the terms of the GNU - * General Public License as published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without - * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; if not, - * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - */ - -#import "StackController.h" - - -@implementation StackController - -@synthesize stack; - -/** - * Constructor - */ -- (id)init -{ - if (self = [super init]) - { - stack = [[NSMutableArray alloc] init]; - } - return self; -} - -/** - * Destructor - */ -- (void)dealloc -{ - [stack release]; - [super dealloc]; -} - -/** - * Returns a reference to the top of the stack - */ -- (StackFrame*)peek -{ - return [stack lastObject]; -} - -/** - * Pops the current frame off the stack and returns the frame - */ -- (StackFrame*)pop -{ - StackFrame* frame = [stack lastObject]; - - if (frame != nil) - [stack removeLastObject]; - - return frame; -} - -/** - * Pushes a frame onto the end of the stack - */ -- (void)push:(StackFrame*)frame -{ - [stack insertObject:frame atIndex:[stack count]]; -} - -@end diff --git a/Source/StackFrame.m b/Source/StackFrame.m index e516d90..839998d 100644 --- a/Source/StackFrame.m +++ b/Source/StackFrame.m @@ -34,6 +34,13 @@ [super dealloc]; } +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[StackFrame class]]) + return NO; + StackFrame* other = (StackFrame*)object; + return [self.filename isEqualToString:other.filename] && self.lineNumber == other.lineNumber; +} + - (BOOL)isShiftedFrame:(StackFrame*)frame { return ([self.filename isEqualToString:frame.filename] && [self.function isEqualToString:frame.function]); } diff --git a/Source/Tests/DebuggerModelTest.m b/Source/Tests/DebuggerModelTest.m new file mode 100644 index 0000000..1428d47 --- /dev/null +++ b/Source/Tests/DebuggerModelTest.m @@ -0,0 +1,136 @@ +/* + * MacGDBp + * Copyright (c) 2015, Blue Static + * + * This program is free software; you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; if not, + * write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#import + +#import "DebuggerModel.h" +#import "StackFrame.h" + +@interface DebuggerModelTest : XCTestCase + +@end + +@implementation DebuggerModelTest { + DebuggerModel* _model; +} + +- (void)setUp { + [super setUp]; + _model = [[DebuggerModel alloc] init]; +} + +- (void)tearDown { + [_model release]; + [super tearDown]; +} + +- (NSMutableArray*)mutableStack { + return (NSMutableArray*)_model.stack; +} + +- (StackFrame*)makeStackFrameForFile:(NSString*)file + atLine:(NSUInteger)line + stackIndex:(NSUInteger)index { + StackFrame* frame = [[[StackFrame alloc] init] autorelease]; + frame.filename = file; + frame.lineNumber = line; + frame.index = index; + return frame; +} + +- (NSArray*)initialStack { + NSArray* initialStack = @[ + [self makeStackFrameForFile:@"/top/frame" atLine:12 stackIndex:0], + [self makeStackFrameForFile:@"/middle/frame" atLine:44 stackIndex:1], + [self makeStackFrameForFile:@"/bottom/frame" atLine:1 stackIndex:2] + ]; + initialStack[0].loaded = YES; + initialStack[1].loaded = YES; + initialStack[2].loaded = YES; + return initialStack; +} + +- (void)testEmptyStackReplace { + XCTAssertEqual(0u, _model.stackDepth); + NSArray* stack = [self initialStack]; + [_model updateStack:stack]; + XCTAssertEqual(3u, _model.stackDepth); + XCTAssertEqualObjects(stack, _model.stack); +} + +- (void)testReplaceTopFrame { + NSArray* initialStack = [self initialStack]; + [[self mutableStack] addObjectsFromArray:initialStack]; + XCTAssertEqual(3u, _model.stackDepth); + + NSArray* replacementStack = @[ + [self makeStackFrameForFile:@"/top/frame" atLine:999 stackIndex:0], + initialStack[1], + initialStack[2] + ]; + [_model updateStack:replacementStack]; + XCTAssertEqualObjects(replacementStack, _model.stack); + XCTAssertFalse(_model.stack[0].loaded); + XCTAssertEqual(0u, _model.stack[0].index); + XCTAssertTrue(_model.stack[1].loaded); + XCTAssertEqual(1u, _model.stack[1].index); + XCTAssertTrue(_model.stack[2].loaded); + XCTAssertEqual(2u, _model.stack[2].index); +} + +- (void)testAddNewTopFrame { + [_model updateStack:[self initialStack]]; + XCTAssertEqual(3u, _model.stackDepth); + + NSMutableArray* replacementStack = [NSMutableArray arrayWithArray:[self initialStack]]; + [replacementStack insertObject:[self makeStackFrameForFile:@"/top/new" atLine:44 stackIndex:0] + atIndex:0]; + replacementStack[1].index = 1; + replacementStack[2].index = 2; + replacementStack[3].index = 3; + + [_model updateStack:replacementStack]; + XCTAssertEqual(4u, _model.stackDepth); + XCTAssertEqualObjects(replacementStack, _model.stack); + XCTAssertFalse(_model.stack[0].loaded); + XCTAssertEqual(0u, _model.stack[0].index); + XCTAssertTrue(_model.stack[1].loaded); + XCTAssertEqual(1u, _model.stack[1].index); + XCTAssertTrue(_model.stack[2].loaded); + XCTAssertEqual(2u, _model.stack[2].index); +} + +- (void)testRemoveTopFrame { + NSArray* initialStack = [self initialStack]; + [_model updateStack:initialStack]; + XCTAssertEqual(3u, _model.stackDepth); + + [_model updateStack:[initialStack subarrayWithRange:NSMakeRange(1, 2)]]; + XCTAssertEqual(2u, _model.stackDepth); + XCTAssertTrue(_model.stack[0].loaded); + XCTAssertEqual(0u, _model.stack[0].index); + XCTAssertTrue(_model.stack[1].loaded); + XCTAssertEqual(1u, _model.stack[1].index); +} + +- (void)testClearStack { + [_model updateStack:[self initialStack]]; + XCTAssertEqual(3u, _model.stackDepth); + [_model updateStack:@[]]; + XCTAssertEqual(0u, _model.stackDepth); +} + +@end diff --git a/UnitTests-Info.plist b/UnitTests-Info.plist new file mode 100644 index 0000000..ba72822 --- /dev/null +++ b/UnitTests-Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + -- 2.22.5