Frida is composed of many sub-projects and its code spans across many different languages, such as C, C++, Vala, JavaScript, TypeScript, Python, assembly, etc., and the coding conventions may vary across any combination of project/language.
The rules presented here are not there because of @oleavr's OCD: they're about keeping the codebase readable and maintainable, and give the code a consistent structure as it grows. That helps every contributor to easily orientate in it.
Moreover, reading this doc helps save everyone's time when it comes to get a PR reviewed.
Rules in this section apply in all cases, regardless of the programming language or the project owning the code. Examples are in pseudo-javascript for brevity (with some exceptions).
Only use comments for communicating what cannot be conveyed by the code itself through meaningful variable and function names, and splitting out logic into separate functions.
NOTE: names should be long enough to be meaningful, but not ridiculously long.
// Init the dyld start address with current program counter
a = (instance->cpu_type == GUM_CPU_ARM64) ? __darwin_arm_thread_state64_get_pc (state.ts_64) : state.ts_32.__pc;
// dyld header is initially zero
b = 0;
// set the search granularity to 4k
c = 4096;
for (dyld_chunk = (a & (c - 1)) == 0 ? (a - c) : (a & ~(c - 1));
b == 0;
dyld_chunk -= c)
{
...
}
dyld_start = (instance->cpu_type == GUM_CPU_ARM64) ? __darwin_arm_thread_state64_get_pc (state.ts_64) : state.ts_32.__pc;
dyld_header = 0;
dyld_granularity = 4096;
for (dyld_chunk = (dyld_start & (dyld_granularity - 1)) == 0 ? (dyld_start - dyld_granularity) : (dyld_start & ~(dyld_granularity - 1));
dyld_header == 0;
dyld_chunk -= dyld_granularity)
{
...
}
Here's a really useful comment.
/* Compiled from helpers/upload-listener.c */
private const uint8[] UPLOAD_LISTENER_CODE = {
0xff, 0x43, 0x01, 0xd1, 0xf6, 0x57, 0x02, 0xa9, 0xf4, 0x4f, 0x03, 0xa9, 0xfd, 0x7b, 0x04, 0xa9, 0xfd, 0x03, 0x01,
0x91, 0xf3, 0x03, 0x01, 0xaa, 0xe0, 0x1f, 0x00, 0xb9, 0x28, 0x00, 0x40, 0xf9, 0xe0, 0x03, 0x1f, 0x32, 0xe1, 0x03,
0x00, 0x32, 0x02, 0x00, 0x80, 0x52, 0x00, 0x01, 0x3f, 0xd6, 0x1f, 0x04, 0x00, 0x31, 0x80, 0x05, 0x00, 0x54, 0xf4,
...
0xb2, 0x04, 0x00, 0x00, 0x14, 0xf5, 0x03, 0x46, 0xb2, 0x02, 0x00, 0x00, 0x14, 0x15, 0xa0, 0xe0, 0xd2, 0x68, 0x2a,
0x40, 0xf9, 0xe0, 0x03, 0x14, 0xaa, 0x00, 0x01, 0x3f, 0xd6, 0xe0, 0x03, 0x15, 0xaa, 0xfd, 0x7b, 0x44, 0xa9, 0xf4,
0x4f, 0x43, 0xa9, 0xf6, 0x57, 0x42, 0xa9, 0xff, 0x43, 0x01, 0x91, 0xc0, 0x03, 0x5f, 0xd6
};
The variable name shouldn't be more verbose than its type name.
private void schedule_idle (owned ScheduledFunc function) { ... }
private void schedule_idle (owned ScheduledFunc func) { ... }
Higher level functions must be placed before lower level functions, and Should be sorted chronologically. In case of C code the corresponding forward declarations must follow the same order.
function getInfoAboutA() {
/* complex code */
return allInfoAboutA;
}
function getInfoAboutB() {
/* more complex code */
return allInfoAboutB;
}
function getInfo() {
return {
infoAboutA: getInfoAboutA(),
infoAboutB: getInfoAboutB()
};
}
function getInfo() {
return {
infoAboutA: getInfoAboutA(),
infoAboutB: getInfoAboutB()
};
}
function getInfoAboutA() {
/* complex code */
return allInfoAboutA;
}
function getInfoAboutB() {
/* more complex code */
return allInfoAboutB;
}
For exported functions / public methods: functions that work in all cases come before functions which work only under certain conditions.
function getPrivilegedInfo() {
if (!amIRoot()) {
throw new Error("You must be root");
}
...
return privilegedInfo;
}
function getHarmlessInfo() {
return harmlessInfo;
}
function getHarmlessInfo() {
return harmlessInfo;
}
function getPrivilegedInfo() {
if (!amIRoot()) {
throw new Error("You must be root");
}
...
return privilegedInfo;
}
Instead of repeating chunks of code, extract it to a function and call it multiple times.
There's no fixed recipe to define what a "repeated chunk of code is", it really depends on the context. For example, even a couple of lines may be worth refactoring into a function, especially if they require some fairly complex error-handling logic.
private async LLDB.Client start_lldb_service (Fruity.LockdownClient lockdown, Cancellable? cancellable)
throws Error, LLDB.Error, IOError {
try {
var lldb_stream = yield lockdown.start_service (DEBUGSERVER_SERVICE_NAME + "?tls=handshake-only", cancellable);
return yield LLDB.Client.open (lldb_stream, cancellable);
} catch (Fruity.LockdownError e) {
if (e is Fruity.LockdownError.INVALID_SERVICE) {
throw new Error.NOT_SUPPORTED ("This feature requires an iOS Developer Disk Image to be mounted; " +
"run Xcode briefly or use ideviceimagemounter to mount one manually");
}
throw new Error.NOT_SUPPORTED ("%s", e.message);
}
}
Minimize nesting when possible.
function doSomethingMaybe() {
if (condition) {
/* do something here */
}
}
function doSomethingMaybe() {
if (!condition) {
return;
}
/* do something here */
}
Hanging indent – when breaking a long line of code – should be twice the regular indent, regardless of the indentation rules.
recursive_init_address = gum_darwin_module_resolve_symbol_address (dyld,
"__ZN11ImageLoader23recursiveInitializationERKNS_11LinkContextEjRNS_21InitializerTimingListERNS_15UninitedUpwardsE");
recursive_init_address = gum_darwin_module_resolve_symbol_address (dyld,
"__ZN11ImageLoader23recursiveInitializationERKNS_11LinkContextEjRNS_21InitializerTimingListERNS_15UninitedUpwardsE");
No trailing spaces.
Prefer strict inequality for loop conditions.
for (let i = 0; i < len; i++) {
...
}
for (let i = 0; i !== len; i++) {
...
}
Use explicit comparisons, even with 0
, NULL
, and undefined
,
instead of relying on language-specific coercion. Only use implicit
for booleans.
if (value) {
...
}
if (value !== 0) {
...
}
Internal APIs should assume the API contract is not violated – i.e. that the function is passed everything it needs – and omit any check on the arguments.
function doSomethingInternallyWithDevice(device) {
if (device === null || device === undefined) {
throw new Error('Pass a valid device');
}
/* do the thing with device */
}
function doSomethingInternallyWithDevice(device) {
/* do the thing with device */
}
This set of rules applies to C code, regardless of the project owning it.
Every C file must follow the following structure, in this order:
- Own header includes
- Internal header includes
- System/dependency includes
- typedefs
- enum and struct definitions
- Forward declarations / prototypes
- Global state / static variables
- Implementation code
Each such group should be separated by a blank line, and includes should be listed in alphabetical order.
In function calls and function definitions, put a space before the parenthesis.
ret = thread_create(task, &instance->thread);
ret = thread_create (task, &instance->thread);
When declaring pointers, put a space before and after *
.
GumModuleDetails *details;
GumModuleDetails * details;
When casting pointers, put a space before *
.
init_func = (guint32*) gum_darwin_read (task, addr, sizeof (guint32), NULL);
init_func = (guint32 *) gum_darwin_read (task, addr, sizeof (guint32), NULL);
Space before and after binary operators, no spaces around unary operators.
for (port_index=0; port_index!=previous_ports->count; port_index ++)
{
...
}
* count ++;
for (port_index = 0; port_index != previous_ports->count; port_index++)
{
...
}
*count++;
Never use more than one space (when not used for indentation).
gint i;
gint i;
It's encouraged to leave one blank line when needing to separate semantically distinct blocks of code or improve readability (some examples later). Just don't abuse that and never leave more than one blank line. (Except for Python code, which should follow the recommendations in PEP-8.)
static void
function_one (void)
{
...
}
static void
function_two (void)
{
...
}
static void
function_one (void)
{
...
}
static void
function_two (void)
{
...
}
Function names must be lowercase, e.g. find_libsystem
NOTE: this is true also for arguments, variable names, and
labels.
static gboolean
frida_find_libSystem (const GumModuleDetails * details, gpointer user_data)
{
...
}
static gboolean
frida_find_libsystem (const GumModuleDetails * details, gpointer user_data)
{
...
}
Functions which take no arguments should be declared (void)
, as ()
means that no information about the number or types of the arguments
is supplied. (Unlike C++, where ()
means "no arguments".)
static gboolean
function_without_args ()
{
...
}
static gboolean
function_without_args (void)
{
...
}
Local variables are all declared at the beginning of the block they're used in, a blank line is usually left right after.
if (is_uninitialized_clone)
{
mach_port_mod_refs (self_task, task, MACH_PORT_RIGHT_SEND, 1);
instance->task = task;
mach_vm_address_t data_address = instance->remote_agent_context;
...
}
if (is_uninitialized_clone)
{
mach_vm_address_t data_address;
mach_port_mod_refs (self_task, task, MACH_PORT_RIGHT_SEND, 1);
instance->task = task;
data_address = instance->remote_agent_context;
...
}
Local variables are listed in chronological order of usage.
Exception 1: the variable which holds the return value of a function should be the first in the list, preceded by any variable holding argument values.
Exception 2: related variables of the same type can be grouped in one line, in relative chronological order.
Variables in chronological order of usage.
case FRIDA_BREAKPOINT_CLEANUP:
{
task_t self_task;
gsize page_size;
FridaExceptionPortSet * previous_ports;
mach_msg_type_number_t port_index;
guint i;
self_task = mach_task_self ();
page_size = getpagesize ();
previous_ports = &self->previous_ports;
for (port_index = 0; port_index != previous_ports->count; port_index++)
{
...
}
...
for (i = 0; i != FRIDA_MAX_BREAKPOINTS; i++)
frida_spawn_instance_unset_nth_breakpoint (self, i);
}
The variable holding the return value comes first.
static csh
frida_create_capstone (GumCpuType cpu_type, GumAddress start)
{
csh capstone;
cs_err err;
switch (cpu_type)
{
case GUM_CPU_ARM64:
err = cs_open (CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN, &capstone);
break;
...
}
g_assert (err == CS_ERR_OK);
return capstone;
}
Argument helper variable before return value.
guint
_frida_darwin_helper_backend_demonitor_and_clone_injectee_state (FridaDarwinHelperBackend * self, void * raw_instance)
{
FridaInjectInstance * instance = raw_instance;
FridaInjectInstance * clone;
...
return clone->id;
}
Avoid redundant initialization.
cs_insn * insn = NULL;
...
insn = cs_malloc (capstone);
cs_insn * insn;
...
insn = cs_malloc (capstone);
In function definitions, the return type goes on its own line (not for the prototype).
static gboolean frida_find_libsystem (const GumModuleDetails * details, gpointer user_data)
{
...
}
static gboolean
frida_find_libsystem (const GumModuleDetails * details, gpointer user_data)
{
...
}
Curly braces go on a new line, in all cases.
if (cached_address_for_breakpoint[instance->cpu_type] == 0) {
...
}
if (cached_address_for_breakpoint[instance->cpu_type] == 0)
{
...
}
Code in a block is indented by 2 spaces.
if (magic == NULL)
goto handle_probe_dyld_error;
if (magic == NULL)
goto handle_probe_dyld_error;
Simple if
statements in which the code is only one line should not
have curly braces.
if (magic == NULL)
{
goto handle_probe_dyld_error;
}
if (magic == NULL)
goto handle_probe_dyld_error;
If the if
statement has curly braces, then also the else
should
have it (and vice-versa).
if (error == nil)
[service cleanupClientPort:client_port];
else
{
g_clear_object (&pipes);
frida_error = g_error_new (
FRIDA_ERROR,
FRIDA_ERROR_NOT_SUPPORTED,
"Unable to launch iOS app: %s",
[[error localizedDescription] UTF8String]);
}
if (error == nil)
{
[service cleanupClientPort:client_port];
}
else
{
g_clear_object (&pipes);
frida_error = g_error_new (
FRIDA_ERROR,
FRIDA_ERROR_NOT_SUPPORTED,
"Unable to launch iOS app: %s",
[[error localizedDescription] UTF8String]);
}
When an if
statement has single-statement bodies but any of them
exceed the maximum line length, curly braces must be used.
if (_frida_get_springboard_api ()->fbs != NULL)
frida_darwin_helper_backend_launch_using_fbs (identifier_value, url_value, options, aux_options, on_complete,
on_complete_target);
else
frida_darwin_helper_backend_launch_using_sbs (identifier_value, url_value, options, aux_options, on_complete,
on_complete_target);
if (_frida_get_springboard_api ()->fbs != NULL)
{
frida_darwin_helper_backend_launch_using_fbs (identifier_value, url_value, options, aux_options, on_complete,
on_complete_target);
}
else
{
frida_darwin_helper_backend_launch_using_sbs (identifier_value, url_value, options, aux_options, on_complete,
on_complete_target);
}
If the condition of an if
statement has been broken up into multiple
lines, then use braces regardless.
if ((ctx->sink_mask & GUM_BLOCK) != 0 &&
gum_x86_relocator_eob (rl) &&
insn.ci->id != X86_INS_CALL)
gum_exec_block_write_block_event_code (block, &gc, GUM_CODE_INTERRUPTIBLE);
if ((ctx->sink_mask & GUM_BLOCK) != 0 &&
gum_x86_relocator_eob (rl) &&
insn.ci->id != X86_INS_CALL)
{
gum_exec_block_write_block_event_code (block, &gc, GUM_CODE_INTERRUPTIBLE);
}
If a case needs curly braces, the break
goes inside, usually
preceded by a blank line.
switch (cpu_type)
{
case GUM_CPU_ARM:
{
/* case implementation*/
}
break;
...
}
switch (cpu_type)
{
case GUM_CPU_ARM:
{
/* case implementation*/
break;
}
...
}
This set of rules apply to JavaScript code, regardless of the project owning it.
Don't use double quotes.
throw new Error("Invalid argument");
throw new Error('Invalid argument');
Indent with 2 spaces.
Curly braces go on the same line of the statement.
if (condition)
{
...
}
else
{
...
}
if (condition) {
...
} else {
...
}
Treat semicolons as mandatory.
console.log('hello world')
console.log('hello world');
Use strict comparisons.
if (methodName == '- init') {
...
}
if (methodName === '- init') {
...
}
Put parenthesis around the ternary comparison condition unless it's simply referencing a boolean variable.
m = res[0] === '' ? '*' : res[0];
m = (res[0] === '') ? '*' : res[0];
Unless the specific project follows the semistandard conventions, put no spaces between function name and argument list.
function parseExportsFunctionPattern (pattern) {
var res = pattern.split ('!');
...
}
function parseExportsFunctionPattern(pattern) {
var res = pattern.split('!');
...
}
Reference object properties without quotes when possible.
enumerateMatches('exports:' + obj['module'] + '!' + obj['function']);
enumerateMatches('exports:' + obj.module + '!' + obj.function);
Stick to ES5
syntax, so it can be consumed by the Duktape runtime
without having to first frida-compile
the code.
In rpc.exports
, dispose()
comes first, or right after init()
.
This set of rules apply to TypeScript code, regardless of the project owning it. Rules for JavaScript also apply to TypeScript, if not explicitly overridden.
Indent with 4 spaces.
Never put spaces between function name and argument list, neither in calls nor definitions.
Don't use single quotes.
The TypeScript convention is pascal-case for enum values,
e.g. FooBarBaz
.
enum PlistType {
NONE,
BINARY,
XML
}
enum PlistType {
None,
Binary,
Xml
}
Usage of const enum
is discouraged portability-wise.
export const enum GrassColor {
Yellow = "YELLOW",
LightGreen = "LIGHTGREEN",
Green = "GREEN",
DarkGreen = "DARKGREEN"
}
export type GrassColor =
| "YELLOW"
| "LIGHTGREEN"
| "GREEN"
| "DARKGREEN"
;
Constants should be uppercase, e.g. FOO_BAR_BAZ
.
const lockdownPort = 62078;
const LOCKDOWN_PORT = 62078;
Use interface
when possible.
export type StringDict = {
[name: string]: string;
}
export interface StringDict {
[name: string]: string;
}
This set of rules apply to Vala code, regardless of the project owning it.
- Public properties should precede private ones.
- Higher level properties should precede lower level ones.
Indent only with tabs.
Rules for spaces are the same as C.
Curly braces go on the same line of the statement. Follow the same rules as C for whether to omit them or not.
if (condition)
{
...
}
else
{
...
}
if (condition) {
...
} else {
...
}
Declare variables as var
, especially when the type is obvious.
Json.Node parameters = new Json.Node (Json.NodeType.OBJECT);
var parameters = new Json.Node (Json.NodeType.OBJECT);
This set of rules apply to all python code, regardless of the project it belongs to.
Python formatting should follow the PEP-8 guidelines.
Use double quotes for regular strings, single quotes for enum-like values.
if os.environ.get("TERM", '') == "dumb":
if os.environ.get("TERM", "") == 'dumb':
Imports go in alphabetical order.
Comments should use a capital letter at the start of each sentence, and each should end in a full stop.
This set of rules apply to all code in frida-gum, regardless of the language.
Lines should not exceed 80 characters.
When defining a function implementation each argument must go on its own line, vertically aligned (not for the prototype).
static void
gum_exec_block_write_block_event_code (GumExecBlock * block, GumGeneratorContext * gc,
GumCodeContext cc)
{
...
}
static void
gum_exec_block_write_block_event_code (GumExecBlock * block,
GumGeneratorContext * gc,
GumCodeContext cc)
{
...
}
This set of rules apply to all code in frida-core, regardless of the language.
Lines should not exceed 140 characters.
This set of rules apply to C code belonging to frida-core.
Function names should be “namespaced” by having a frida_
prefix,
even if static.
static gboolean
find_libsystem (const GumModuleDetails * details, gpointer user_data)
{
...
}
static gboolean
frida_find_libsystem (const GumModuleDetails * details, gpointer user_data)
{
...
}