Skip to content

Source Code

Mayur Pawashe edited this page Sep 17, 2018 · 24 revisions

Building from Xcode

Code signing is required to gain sufficient privileges to using task_for_pid(), which allows Bit Slicer to access any user-level process.

In the Debug build configuration, a self-signed certificate is used for signing, which only works locally. Please follow these instructions to set that up.

In the Release build configuration, Xcode's project settings is set to use my Developer ID Application certificate.

Other than the steps involved with signing the code, building the project should be simple. If it is not simple to build, please file a bug.

Building is only supported using the latest SDK.

Hacking on the Code

First, before we go further, a prerequisite to understanding the code is having some experience with using Objective-C and Cocoa.

The application's life starts out in ZGAppController.m which initializes all of the components in no particular order:

  • ZGAppUpdaterController - handles updating the app to the next available version
  • ZGDocumentController - handles creating the document based system for Search Documents
  • ZGPreferencesController - responsible for the user preferences window
  • ZGMemoryViewerController - responsible for the [Memory Viewer](Memory Viewer)
  • ZGDebuggerController - responsible for the [Debugger Window](Debugger Window)
  • ZGBreakPointController - handles registering and being notified of breakpoints, used by the debugger and documents
  • ZGScriptingInterpreter - responsible for the scripting environment, used by the debugger, break point controller, and documents
  • ZGLoggerWindowController - responsible for the Logs window for [Scripting](Introduction to Scripting)
  • ZGProcessTaskManager - handles acquiring/releasing virtual memory access to processes
  • ZGHotKeyCenter - handles registering/unregistering global user hotkeys

If we wanted to find where the scripting function vm.pause() is implemented in the code for example, we may recall that a user creates scripts inside document windows. This implies that ZGDocumentController probably contains something that uses scripting. A top-down approach can be taken for finding where this function is implemented in code:

ZGAppController.m (has a)
	ZGDocumentController.m (has many)
		ZGDocument.m (has a)
			ZGDocumentWindowController.m (has a)
				ZGScriptManager.m (has many)
					ZGPyScript.m (has a)
						ZGPyVirtualMemory.m (implements)
							VirtualMemory_pause(..)

This may have also been found if we just searched for "pause" using Xcode's search and filtered out all the irrelevant noise.

Still, this gives a good picture of how the architecture works. Identifying what component uses another can usually be traversed somewhat sensibly.

Contributing

One of the coolest aspects about open source software is that anyone can contribute code to them. My first open source contribution was for a similar kind of modding utility that had been abandoned. I started out knowing nothing about the program or its code, but spent enough time to learn how parts of it work, and eventually was able to provide patches that the developer accepted.

Knowing that I may not be able to work on this project forever, I hope that others, including those who don't have a good grasp on using the application now, can feel comfortable trying to dive in the code. As always, feel free to contact me for any sort of help.

Moving on, the categories that I think the code can improve is:

  • Documentation - Currently almost none exists
  • Refactoring/Rewriting/Improving - Always some code that needs to be designed better
  • Unit Tests - Automated testing for functionality is good
  • Bug Fixes - Developers and users like these
  • Feature Additions - Users like these a lot

The Issues page includes a list of some tasks that may be worth considering. It is also worth checking if an issue has been worked on in one of the repositories' branches.

When thinking about writing or altering something with a significant amount of changes, contacting and discussing them with me first is probably a good idea. In fact, creating an issue first stating you want to work on a task is recommended.

Patches can be created and submitted via a pull request. For those unfamiliar with how GitHub pull requests work, any kind of patch/adjustment should be done on a separate branch.

They can also be submitted by creating a patch file, which works nicely for simple patches.

Before writing anything, please check the project's code style below and also check this project's Code of Conduct.

Code Style & Practices

Intention

Before writing or altering any existing code, familiarity with how and why the code is written the way it is (or should be written in many cases) must first be understood. This is also a document to help myself write code consistently.

First, programmers should be familiar with Apple's Coding Guidelines.

If a topic about style or practices is not mentioned below, check against the code base to see how it may be considered. Although keep in mind, some of the code might not be updated yet to reflect the guidelines here.

Lastly, I realize style and practices can lead up to personal debate, but this is how they are intended to be done here. On a final note, some or large amounts of the code may not be that good yet or reflect the guidelines. This may be inevitable.

Indentation

Tab characters are used for indentation. Spaces should never be used, except when using a 3rd party source file as a dependency that uses them.

When creating or adding a new source file, the indentation setting should be set for it via Xcode's File Inspector. That way, Xcode will respect the indentation settings regardless of the user's preferences.

Alignment

Alignment past other text should be avoided at all cost.

This is bad:

NSString *foo; // yada yada yada
int32_t   bar; // moo moo moo

This is good:

NSString *foo; // yada yada yada
int32_t bar; // moo moo moo

Also good:

// yada yada yada
NSString *foo;

// moo moo moo
int32_t bar;

Rationale: If I wanted to change bar's type to NSNumber, the bad example would require re-adjusting the spacing, which I will call the suckers game. Readability benefits that come from this type of alignment are overrated, and can often be written in another clear way.

Objective-C message calls are encouraged to be on a single line whenever possible. Adjusting your editor so that line wrap is enabled is recommended because no maximum number of characters per line limit exists.

Separating message calls onto different lines can be done when it may increase readability. However, contrary to popular belief, message calls should not be aligned by passed-in arguments, but by its selector:

_debuggerController =
[[ZGDebuggerController alloc]
 initWithProcessTaskManager:_processTaskManager
 breakPointController:_breakPointController
 scriptingInterpreter:_scriptingInterpreter
 hotKeyCenter:_hotKeyCenter
 loggerWindowController:_loggerWindowController
 delegate:self];

Note that the message call should not be indented one level passed the variable assignment because Xcode doesn't do that automatically, and it's not needed anyway. If more levels of indentation are needed, then break up the expression instead of going down to deeper levels, or have some expressions be on the same line.

In this case, the lines after the receiver are aligned by a single space after indenting with tabs, which Xcode does automatically. This is one of the rare instances alignment is allowed because it does not play into the suckers game above.

Functions should also not be written as:

int32_t doSomething(int32_t foo,
	                int32_t bar
	                int32_t baz)

In this example, a single line would have sufficed, but if separate lines helps readability, then:

int32_t doSomething
(
	int32_t foo,
	int32_t bar,
	int32_t baz
)

But avoid this when it's not very advantageous.

Spacing

Arithmetic operations should never be written as x+y*z but as x + y * z or x + (y * z). Spacing between operands is required. Use parentheses to improve clarity.

To show how spacing is done elsewhere:

@property (nonatomic) int32_t foo;
for (uint32_t sheepIndex = 0; sheepIndex < numberOfSheeps; sheepIndex++)
doSomething(foo, bar, baz);
float foo = 32.0f;
if (foo > 0) ...
id <NSObject, OtherProtocol> someObject;
NSString *sometString = @"argh!";
return foo;
return (foo > 0 ? bar : baz) // ()'s ok for long expressions

When in doubt, look at the source code files to see how spacing may be done for a particular construct.

Braces

Open and close braces should be on separate lines. The exception to this is Objective-C Blocks, which should always have an open brace on the same line as the code before it. This is partially because Blocks are usually inlined, but also because Xcode can't deal with such code otherwise very well.

Evaluation Testing

BOOL isHungry = ...
if (isHungry) // good
if (!isHungry) // good
if (isHungry == YES) // bad
if (isHungry == NO) // bad

if (strncmp(string1, string2, length) == 0) // good
if (!strncmp(string1, string2, length)) // bad

uint32_t count = ...
if (count == 0) // good
if (count != 0) // good
if (!count) // bad
if (count) // bad

NSNumber *number = ...
if (number == nil) // good
if (number != nil) // good
if (!number) // bad
if (number) // bad

BOOL result = ...
result = (state & (1 << index)) != 0; // good
result = (state & (1 << index)); // bad

In other words, implicit testing should only be used for what are conceptually boolean results. Static typed languages like Haskell and Java get this right and enforce this in the compiler, but we are stuck with C's legacy behavior.

Practical considerations:

  • In the case of bitwise operations, using implicit testing is dangerous and wrong due to the value being cut off.

  • In the case of pointers and integers, this can also cause a real issue: say I have a NSNumber and want to change it to a uint32_t. An Objective-C compiler can raise a warning whenever the value is being explicitly tested against nil. If we used implicit testing, we would be removing some type safety checking.

  • In the case of functions like strncmp which return a result code rather than a success/failure, using implicit testing is just odd and confusing.

  • In the case of booleans, comparing explicitly against YES/NO or true/false is redundant and reads less clearly.

When comparing a variable with a constant expression, the variable should be on the left side. Eg: do if (foo == 0) but don't do if (0 == foo). The compiler can warn on an accidental if (foo = 0).

Omitting braces around an if (...) statement is only fine when the line to execute is on the same line as the if (...). When in doubt, use braces everywhere.

Instance Variables

Instance variable names should be prefixed with _ due to modern Cocoa guidelines and automatic synthesizing. Instance variables should be avoided in the header file, and should be put in an @implementation { ... } block whenever possible. Private instance variables should also be preferred over private properties whenever possible for a few reasons:

  1. Instance variables are easier to write out under ARC, especially because we want to aim for using nonatomic when possible.
  2. They signify the intent that a custom implementation is not necessary to use, which is very important. On the other hand, private properties are rarely even necessary. "What if" it needs to be a property later is rare and really no big deal. We also don't want to encourage external cases such as method overriding and KVO anyway.
  3. They avoid some cases where a property can be declared both in the header and implementation file (i.e, readonly in header, but writable in implementation). This is messy.

Even when a public property is declared in the header file, using the backing instance variable in the implementation file is still preferable. If the only thing a property setter does is a simple copy, then instance variable access should still be used instead (and preferably it's only set once). If the property is declared in its super class, then instance variable access should not be used, however.

For outlets, we generally don't want to use __weak. The default (strong) works well despite what Apple may suggest otherwise. Weak references to outlets work very poorly on the other hand. One of several other reasons is that they crash on some AppKit classes, which may even vary based on what OS version is running. Not fun.

Instance variables are also zero-initialized in Objective-C. Initializing booleans to NO, integers to zero, or pointers to nil or NULL is redundant. When possible, variables should even be named so that they take advantage of the values being initially zero'd out.

Properties

Prefer properties to be nonatomic whenever possible. Never explicitly leave out if a property is nonatomic or atomic. Code that does this makes the intent of what the property should be very unclear. Due to an enforced compiler warning, this is fortunately difficult to leave out.

retain should never be used unless dealing with non-ARC code.

strong should be explicitly left out because it is treated as the default under ARC which is sensible.

weak should be preferred over assign whenever possible.

Dot Syntax

Dot syntax is usually encouraged where it may improve readability and save typing. Two cases exist where it should not be used, however:

  1. When calling a method on self where the method's implementation (or its property declaration) exists in the same file. It's important to clarify that such a type of method may be expensive to call and more importantly that it does have a custom implementation right here. Of course, if instance variable access can work, then that should be used instead.
  2. On AppKit's singleton classes (eg: don't do NSWorkspace.sharedWorkspace)

Variable Names

Variable names should be camelCase and descriptive. No hungarian notation is allowed. One letter variable names are almost never allowed, even for counters/indexing. Abbreviations are usually not allowed.

With Cocoa types, suffixing the variable name with the kind of object is often desirable. Eg: searchTextField.

See Cocoa's guidelines on naming variables.

Method Names

Method names should follow Cocoa's guidelines whenever possible. For categories on Cocoa's classes, method names must be prefixed.

For IBAction's, method names should start with a verb describing the action rather than describing the control invoking it:

- (IBAction)cancelButtonClicked:(id)__unused sender // bad
- (IBAction)cancelSearch:(id)__unused sender // good

Note that for methods marked with IBAction, Xcode's Interface Builder may have a difficult time parsing the method if an attribute like __unused is provided. This is Xcode's fault (rdar://21168276); just temporarily leave the attribute out when hooking the action.

Types

Types like int, unsigned int, long, short should generally not ever be used unless working with an API that uses them. Prefer int32_t, uint32_t, uint8_t, etc. NSInteger and NSUInteger are also fine to use when working with a Cocoa API and sometimes fine for a generic integer type. If the type should be unsigned, use an appropriate unsigned type.

Window/View Management

Never use awakeFromNib for anything, ever. For a window, a NSWindowController should be used which has a windowDidLoad method.

When creating a new NSWindow + NSWindowController + xib, Xcode might auto-enable the window's properties "Visible at Launch" and "Release When Closed" - these properities, however, should always be disabled.

Comments

/* */ comments should be avoided whenever possible due to being unable to nest them. They are, however, suitable for source file information at the top of the file. // is preferred elsewhere. Learn to love command / shortcut. Don't try to align comments as discussed earlier. #ifdef 0 comments most likely shouldn't be used.

Constants & Immutability

Variables should always be initialized when declaring them, or right after declaring them.

If a method or function takes a pointer to a non-Objective-C object, it should be const whenever possible to signify that the dereferenced value is not suppose to change.

const can be used for local variables as well. They are often left out though because there is no compiler enforcement for doing this.

Do not mutate a local variable's value for another purpose whenever possible. For example, this is good:

NSError *error = nil;
if (![doSomethingAndGetError:&error]) ...

NSError *error2 = nil;
if (![doSomethingElseAndGetError:&error2]) ...

We could have re-used error instead of declaring error2, but this could make our code less clear. This matters greatly as code becomes more complex, and reasoning about when a variable is re-assigned through all potential pathways becomes difficult. This not only applies to errors but any other kinds of variable as well.

Also with the error example above, don't touch the error object unless the method returned a failure result (otherwise you can crash from some of AppKit's methods).

The ternary operator is encouraged for enforcing one-time assignment as long as the expression is readable. NSArrayAdditions.m also has some methods eliminating the need to write tedious code to retrieve elements from an array, to also achieve one-time assignment.

enum's should be used whenever applicable. They should always be used with NS_ENUM or NS_OPTIONS in Objective-C. Use NSInteger if unsure about what type to assign an enum; this is often the type associated with UI tags.

Generics

Obj-C Generics should be used whenever possible for NSArray, NSDictionary, NSCache, etc.

Nullability

Obj-C headers should be wrapped around NS_ASSUME_NONNULL_BEGIN and NS_ASSUME_NONNULL_END and use the nullable and _Nullable attributes for marking what needs to be nullable.

All instance variables, except for outlets, should explicitly be marked as _Nullable or _Nonnull. Deciding which attribute to use should be based on what the value can be after the object has been initialized (via an -init type method).

The helper ZGUnwrapNullableObject(...) can be used for forcefully converting a _Nullable to a _Nonnull, raising an assertion if the value is NULL. This is similar to Swift's ! unwrapping.

Globals & Singletons

Global variables (except for constants) should almost never be used. When they are used, they hide dependencies and makes understanding how data is passed in the overall application difficult. Sometimes, static globals may be necessary though. One example is handling a C callback from an API that provides no context or user info parameter. If globals are used, they should be prefixed with a 'g' (eg: gSomeVariable).

Singletons are only allowed for classes that can't be instantiated more than once due to global state they may encapsulate such as in the example mentioned above. If a singleton class is created, its class creator method may only be called once during the application's lifetime. This enforces that we must explicitly pass the object to every component that needs it. Because singletons can only be instantiated once, they can not be deallocated either. Thus, our singleton pattern looks like:

+ (instancetype)createInterpreterOnce
{
	static ZGScriptingInterpreter *scriptingInterpreter;
	assert(scriptingInterpreter == nil);
	
	scriptingInterpreter = [[[self class] alloc] init];
	return scriptingInterpreter;
}

This differs from traditional singleton patterns because it disallows retrieving the instance multiple times. Note that static local and global variables are zero initialized.

Lastly, if an AppKit Singleton class (one that allows retrieving its instance multiple times) doesn't have to be used as a singleton (eg: NSFileManager), then it probably shouldn't be used as one.

Communications Between Objects

First, this is a good article describing the merits on the different ways of communicating between objects. The Delegation (with protocols) and Callback Pattern are the most preferable approaches; all others should generally be avoided when possible, especially KVO and NSNotificationCenter which makes some or all data-flow implicit.

All of these are last resort methods though. They are used to reduce the amount of knowledge classes need to know about each other. If two classes can avoid having both know about each other, that may be the best direction to go.

Warnings, Warnings, Errors on the Wall

Compared to other projects, the compiler warning are set pretty high, starting with -Weverything and disabling around 10 warnings. This may mean that code can be a little more difficult to write (eg: marking unused variables, using a weakly referenced variable multiple times), but preventing as many mistakes as possible and writing correct code turns out to be very worth it.

Often, when one is writing code and thinks a warning needs to be disabled, it doesn't; so don't be tempted to quickly give into ignoring the warning.

Xcode may spit out harmless DerivedData warnings due to the using Obj-C modules. This is an Xcode/compiler bug. Ignore them.

As far as safety and debugging goes:

assert() should be whenever a condition needs to be satisfied otherwise the program is broken and should crash. Assertions are intentionally left on even in Release mode. Just avoid using them in performance critical code. Don't ever use NSAssert().

ZGLog() can be used for logging that only runs under Debug mode. NSLog() should be used if the error should be reported to the user, but not in cases where their log file may be spammed with many messages.

Clone this wiki locally