Cutter Development Guidelines¶
Note
New to Cutter development? Check out our tutorial for new developers.
Common Usage¶
CutterCore Class¶
This is the main class where every link with Rizin is made. It is unique
across the whole process. To access it, simply call Core().
Example:
Core()->getOffset();
Seek the Current File¶
To modify Rizin seek use CutterCore::seek(const RVA offset). This
is important because it will emit a
CutterCore::seekChanged(RVA offset) signal. Never ever call
cmd("s offset");
Example:
Core()->seek(0x00C0FFEE);
Note
Cutter also supports a silent seek which doesn’t trigger the seekChanged event and doesn’t add new entries to the seek history.
Creating a Widget¶
Make sure to connect the CutterCore::seekChanged(RVA offset) signal
so your widget refreshes its output when Rizin seek is modified
(switching to another function, etc.).
Coding Style¶
clang-format¶
In general, we follow a slightly customized version of the official Qt guidelines to format the code. Before sending a pull request, you will need to use clang-format (version 8 or newer) to format the code. The command line for formatting the code according to the style is:
clang-format -style=file -i src/filename.cpp
If your changes were done on many files across the codebase, you can use this oneliner to run clang-format on the entire src directory:
find ./src -regex '.*\.\(cpp\|h\)' -exec clang-format -style=file -i {} \;
clang-tidy¶
Beyond formatting, we use clang-tidy (version 13 or newer) to catch potential style violations.
To run clang-tidy, first configure your build to generate compile_commands.json and run the autogen tools:
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCUTTER_QT=6 -DCUTTER_USE_BUNDLED_RIZIN=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build build --target Cutter_autogen
Run clang-tidy on modified files relative to the latest commit
git diff -U0 --no-color HEAD~1 | clang-tidy-diff.py -p1 -path build/
Similar to clang-format, If your changes were done on many files across the codebase, you can use this oneliner to run clang-tidy on the entire ‘src’ directory. The following command excludes third-party and auto generated files:
run-clang-tidy -p build ".*src/(?!(themes|bindings|fonts|img|translations|Cutter_autogen)).*\.(cpp|h)$"
clang-tidy can also attempt to fix style violations using the -fix flag. However these may not always be perfect. Make sure to verify the fixes before opening a pull request:
git diff -U0 --no-color HEAD~1 | clang-tidy-diff.py -fix -p1 -path build/
Python scripts¶
If you don’t want to run manual commands, cutter also provides scripts for running clang-format and clang-tidy in the scripts directory
python scripts/clang-format.py -h
usage: clang-format.py [-h] [-C CLANG_FORMAT] [-c] [-v] [-f FILE] [-d DIFF]
Clang format the cutter project
options:
-h, --help show this help message and exit
-C, --clang-format CLANG_FORMAT
path of clang-format
-c, --check enable the check mode
-v, --verbose use verbose output
-f, --file FILE formats (or checks) only the given file
-d, --diff DIFF format all modified file related to branch
usage: clang-tidy.py [-h] [-T RUN_CLANG_TIDY] [-p BUILD_PATH] [-j JOBS] [-r REGEX] [-i] [-q]
clang-tidy regex wrapper
options:
-h, --help show this help message and exit
-T, --run-clang-tidy RUN_CLANG_TIDY
Path of run-clang-tidy binary
-p, --build-path BUILD_PATH
Path to the build directory
-j, --jobs JOBS Number of parallel execution jobs
-r, --regex REGEX Regex pattern for filtering files
-i, --fix Apply fixes automatically
-q, --quiet Suppress configuration logs
Below are some of the low level coding conventions that we follow
Variables¶
Variable names start with a lowercase letter with each consecutive word starting with an uppercase letter. (camelBack case)
int Height // Wrong
string name_of_widget // Wrong
int height; // Correct
string nameOfWidget // Correct
Avoid meaningless variable names
int a, b; // Wrong
string c; // Wrong
int height, width; // Correct
string nameOfThis; // Correct
Object obj; // Also Correct
Only first letter of an acronym is uppercase
CutterJsonWigdet cutterJSONWidget; // Wrong
CutterJsonWigdet cutterJsonWigdet; // Correct
Variable names don’t use leading/trailing underscores, including prefixes like m_
class Example {
private:
int index_; // Wrong
int m_index; // Wrong
int index; // Correct
};
Global variables follow the same naming conventions
Anonymous Namespace¶
Global entities (variables, functions, structs…) private to a source file (internal linkage) are defined in an anonymous namespace
namespace {
int globalCounter = 0;
bool doSomething() {
...
return true;
}
};
Functions¶
Function names follow the same naming convention as variables. (camelBack casing)
void do_something() {} // Wrong
void DoSomething() {} // Wrong
void doSomething() {} // Correct
Unused parameters inside a function are omitted in the definition either by commenting out the parameter name or not writing the name at all.
Avoid using Q_UNUSED
// Bad
void doSomething(int one, int two) {
Q_UNUSED(one)
...
}
// Good
void doSomething(int /*one*/, int two) {
...
}
// Good
void doSomething(int, int two) {
...
}
Avoid the use of automatic name based connections. See Connecting Qt Signals.
Classes¶
First letter of each word is uppercased. (CamelCase)
class memory_dock_widget; // Wrong
class memoryDockWidget; // Wrong
class MemoryDockWidget; // Correct
Only first letter of an acronym is uppercase
class CutterJSONWidget // Wrong
class CutterJsonWidget // Correct
Member variables follow the same naming conventions defined in variables that means no leading/trailing underscores or m_ prefixes
Casting¶
Avoid the use of C style casts, prefer C++ casts (static_cast, const_cast …)
Use qobject_cast for QObjects
Smart Pointers¶
Prefer the use of C++ smart pointers (unique_ptr, shared_ptr…)
RegisterProfileDialog *ui; // Bad
std::unique_ptr<Ui::RegisterProfileDialog> ui; // Good
Braces¶
In contrast to the official guidelines of Qt, in Cutter we always use curly braces in conditional statements, even if the body of a conditional statement contains only one line.
// Wrong
if (address.isEmpty())
return false;
// Correct
if (address.isEmpty()) {
return false;
}
// Wrong
for (int i = 0; i < 10; ++i)
qDebug("%i", i);
// Correct
for (int i = 0; i < 10; ++i) {
qDebug("%i", i);
}
Loops¶
We use the C++11 foreach loop style, which means any “foreach” loop should look like:
// Good - If a copy of each element is required
for (auto import : importsArray) {
doSomething(import);
}
// Good - If copy is not required
for (auto &import : importsArray) {
doSomething(import);
}
// Good - If no modification is required
for (const auto &import : importsArray) {
doSomething(import);
}
auto¶
Prefer the use of auto keyword when working with the following:
// Iterators
auto it = myMap.find(key);
for (const auto &import : importsArray) { ... }
// Lambdas
auto multiply = [](int a, int b) -> int { return a * b; };
// Casting
auto myFloat = static_cast<float>(myInt);
// Initializing using new
auto *item = new QListWidgetItem(text);
nullptr¶
Please do not use 0 nor Q_NULLPTR, only use nullptr.
Example:
std::unique_ptr<QObject> obj = nullptr;
// Note that this is just an example, unique_ptr constructor initializes the internal pointer to nullptr by default
Connecting Qt Signals¶
Use one of the following methods for connecting signals to slots:
// typically you will make connection in the constructor to a member of current class
connect(this->ui->button1, &QPushButton::clicked,
this, &MyObject::buttonClicked); // Good
// you can also connect directly other object slots
connect(checkbox, &QCheckBox::toggled, widget, &QWidget::setEnabled); // Good
// use lambda for passing extra arguments
connect(button1, &QPushButton::clicked, this, [this](){ foo(getBar()); }); // Good
This syntax performs compile-time type checks and allows the use of lambda functions. Other approaches for connecting signals can silently break at runtime.
Don’t use the older macro based syntax or automatic name based connections.
// SIGNAL and SLOT macros
connect(sender, SIGNAL(clicked), this, SLOT(buttonClicked)); // BAD
// automatic name based connection
slot:
void on_actionNew_triggered(); // BAD
// 3 argument connect without receiver object
connect(sender, &SomeObject::signal, [this](){ this->foo(getBar()); }); // BAD
Includes¶
Strive to include only required definitions inside header files. This will avoid triggering additional unnecessary compilations.
If you only need to know that a class exists but don’t need the prototype, you can declare the class like this:
class MyClassThatExists;
/** ... **/
private:
std::unqiue_ptr<MyClassThatExists> classInstance;
And then include the class header inside your .cpp so you can use that class.
If you need something in the source file (.cpp) that is not a class member, then add the include in the source file.
The includes must be ordered from local to global. That is, first include any local header file (with double quotes like #include “common/Helpers.h”. Then, after an empty newline, include Qt definitions like #include <QShortcut>. Finally, include the standard C++ headers you need.
Includes must be sorted by alphabetical order.
This is automatically handled by running clang-format
Docstrings¶
Our API reference is generated using Doxygen, so when it comes to function documentation, please use the following format:
/**
* @brief Add new parameters to the accumulator
* @param params The parameters to add
* @return True if the parameters were added, false otherwise
*/
bool accumulate(RefreshDeferrerParams params) { ... };
Documenting every function is generally not required. Only document those functions whose purpose is not immediately understandable just by looking at the function name.
// No need for docs
int getCount() const;
// Should provide docs
bool syncRemote();
It is preferred for classes to have documentation just above the definition, explaining their purpose.
/**
* @brief A short description about SomeDialog
*/
class SomeDialog {
...
};
General Coding Advices¶
Functions Documentation¶
You can find the class documentation in the API Reference menu item.
Updating the Git Submodules¶
Git submodules play a major part in Cutter. This, because Cutter is powered by Rizin, its parent project, and it tries to stay up-to-date with its recent version, which allows us to implement new features, and enjoy bug fixes and performance improvements on Rizin. Often, we need to update the Rizin submodule or the others, to push their most recent version to Cutter.
You can view the list of all the submodules from the cutter root folder with:
git config --file .gitmodules --get-regexp path | awk '{ print $2 }'
To update all the submodules at once, run these commands from the cutter root folder:
git submodule foreach git pull origin master
git add submodule_name_1 submodule_name_2
git commit -m "Update submodules"
More likely, you’ll only need to update the rizin submodule. In order to update one submodule individually, use the following code:
cd rizin
git checkout dev && git pull
cd ..
git add rizin
git commit -m "Update rizin submodule"
Useful Resources (Qt Development)¶
Model/View Programming - read this if you are going to work with a list or table-like widgets