Available built-in checks
This document lists all of the built-in checks you can enable in ClangTidy.lua
through an enable_checks()
call.
General checks
clang-tidy for Unreal Engine supports all of the checks listed in the official clang-tidy documentation. However, only a subset of these checks are relevant for Unreal Engine development.
The list of checks that are suitable to enable for Unreal Engine code are shown below.
Check name | What it detects |
---|---|
bugprone-bool-pointer-implicit-conversion | Usage of bool* as a boolean expression, where you don't also dereference it. |
bugprone-branch-clone | Scenarios where multiple if / else if / else branches contain the exact same code. |
bugprone-copy-constructor-init | Copy constructors that don't call the copy constructor of the base class. |
bugprone-forwarding-reference-overload | Perfect forwarding constructors that hide copy or move constructors. |
bugprone-implicit-widening-of-multiplication-result | Potential overflows that can occur when multiplying integers. |
bugprone-incorrect-roundings | Incorrect rounding logic in the form of (int)(double_val + 0.5) . |
bugprone-infinite-loop | Obvious infinite loops where the loop is based on a local variable that is never updated by the loop body. |
bugprone-integer-division | Cases of integer division being performed in a context that uses floating-point numbers. |
bugprone-macro-parentheses | Macros that can have unexpected behaviour due to missing parentheses. |
bugprone-macro-repeated-side-effects | Macro invocations that can cause repeated side effects when passed e.g. a function expression instead of a variable expression. |
bugprone-misplaced-widening-cast | Casts to a wider type which are incorrectly placed on the outer expression. For example, (long)(x * 1000) should be (long)x * 1000 instead. |
bugprone-parent-virtual-call | Calls to grandparent virtual methods instead of the immediate parent's virtual method implementation. |
bugprone-redundant-branch-condition | Conditional variables in if statements that were checked by an outer if statement and were not changed. |
bugprone-signed-char-misuse | signed char -> integer conversions which might indicate a programming error. |
bugprone-sizeof-expression | sizeof expressions which are most likely errors. |
bugprone-suspicious-enum-usage | Cases where an enum is probably misused as a bitmask. |
bugprone-suspicious-semicolon | Semicolons which are likely to be misplaced, such as: if (x < y); { x++; } |
bugprone-swapped-arguments | Potentially swapped arguments by looking at implicit conversions. |
bugprone-terminating-continue | do while loops with a condition always evaluating to false that have a continue statement, as this continue terminates the loop. |
bugprone-too-small-loop-variable | for loops where the size of the loop variable is too small to represent all values that are part of the iteration range. |
bugprone-undelegated-constructor | Creation of temporary objects in constructors that look like a function call to another constructor of the same class. |
bugprone-unhandled-self-assignment | User-defined copy assignment operators which do not protect the code against self-assignment either by checking self-assignment explicitly or by using the copy-and-swap or the copy-and-move method. |
bugprone-virtual-near-miss | Virtual methods in a base class that have a similar, but not same, name as a virtual method in a parent class. |
performance-for-range-copy | For range loops where the loop variable is copied each iteration but could be a const reference instead. |
performance-implicit-conversion-in-loop | For range loops that resulted in an implicit conversion (and thus a copy) of a const reference loop variable. |
performance-move-constructor-init | User-defined move constructors that have a ctor-initializer initializing a member or base class through a copy constructor instead of a move constructor. |
performance-no-automatic-move | Local variables that cannot be automatically moved due to constness, such as const variables which are used as the return values from functions. |
performance-trivially-destructible | Types that could be made trivially-destructible by removing out-of-line defaulted destructor declarations. |
performance-unnecessary-copy-initialization | Local variables declarations that are initialized using the copy constructor of a non-trivially-copyable type, but it would suffice to obtain a const reference. |
performance-unnecessary-value-param | Value parameter declarations of expensive to copy types that are copied for each invocation, but it would suffice to pass them by const reference. |
Unreal Engine specific checks
The full list of Unreal Engine specific checks that the clang-tidy plugin defines are shown below.
Check name | What it detects |
---|---|
unreal-broken-array-call | Calls to TArray<> member functions where the parameter is a const reference pointing to data inside the array from a for range loop. These calls would cause a crash at runtime. |
unreal-bad-reference-capture-in-delegate | When you capture a reference as a user parameter to a delegate. This type of capture will cause a crash if the referenced data is on the stack, and the stack has since been unwound by the time the delegate is invoked. |
unreal-missing-super-call-begindestroy | When you forget to call Super::BeginDestroy() in an overridden BeginDestroy function. |
unreal-missing-super-call-lifetimeprops | When you forget to call Super::GetLifetimeReplicatedProps(Props) in an overridden GetLifetimeReplicatedProps function. |
unreal-ionlinesubsystem-get | Calls to IOnlineSubsystem::Get() when you should call Online::GetSubsystem() instead. |
unreal-unsafe-storage-of-oss-pointer | Storage of online subsystem shared pointers as fields in classes or structs. These are not safe to store in the editor as the online subsystem can be destroyed in response to "Play in editor", and if you have live shared references when the online subsystem shuts down, the editor will crash. |
unreal-missing-uproperty | When you forget to add UPROPERTY() to a field in a UCLASS() where the field type is a UObject pointer. |
unreal-missing-doreplifetime-for-replicated-property | When you forget to call DOREPLIFETIME() or equivalent for a replicated property. |
unreal-incorrect-interface-invocation | When you call a UINTERFACE() directly instead of through the ::Execute_ pattern. |
unreal-ustruct-field-not-initialized-in-class | When a field in a USTRUCT() is a Plain Old Data (POD) type and is missing an in-class initializer. |
unreal-broken-array-call
Detects usages of array functions where the item parameter is a reference back into the array, and where the reference originates from a for-range loop.
This detects bad code like this:
for (const auto& Val : Arr)
{
Arr.Remove(Val);
}
which is invalid because the array will free the memory that Val is pointing to. Unreal checks this at runtime and will assert; this clang-tidy rule detects it at compile time.
To fix this code, make the iteration value a copy of the value from the array, or index into the array instead of using a for-range loop:
for (int i = 0; i < Arr.Num(); i++)
{
const auto& Val = Arr[i];
Arr.RemoveAt(i);
// NOTE: Once you call RemoveAt, Val will point to invalid memory.
// You can use `auto Val = Arr[i];` if you need to continue using
// it after the RemoveAt call.
}
unreal-bad-reference-capture-in-delegate
Detects when you capture a reference as a user parameter to a delegate. This is almost always incorrect, as the delegate will capture the reference, not the value. When the referenced memory goes out of scope, the delegate can be invoked with arguments that point to an invalid memory space.
For example:
DECLARE_DELEGATE(FSomeFunctionHandler)
void FunctionHandler(const int& UserParam) {};
void BadImpl()
{
int A = 5;
FSomeFunctionHandler::CreateStatic(&FunctionHandler, A);
// If CreateStatic was passed to an event to be called after BadImpl
// returns, it would be invoked with UserParam pointing to invalid memory.
}
You should always pass user parameters by value.
unreal-missing-super-call-begindestroy
Detects when you forget to call Super::BeginDestroy();
in an overridden BeginDestroy
function.
unreal-missing-super-call-lifetimeprops
Detects when you forget to call Super::GetLifetimeReplicatedProps(Props);
in an overridden GetLifetimeReplicatedProps
function.
unreal-ionlinesubsystem-get
Detects when you use IOnlineSubsystem::Get()
. You should always use Online::GetSubsystem()
instead.
unreal-unsafe-storage-of-oss-pointer
Detects when you try to store an IOnlineSubsystemPtr
or IOnline*Ptr
as a field in a class or struct.
You can not safely store these values, as they will prevent the online subsystem from releasing it's resources, which it may do when the editor is open and play-in-editor is started or stopped.
You should use weak pointers e.g. TWeakPtr<IOnlineIdentity, ESPMode::ThreadSafe>
instead.
unreal-missing-uproperty
Detects when you forget to add UPROPERTY()
to fields in a UCLASS()
, where those fields point to other garbage collected objects.
unreal-missing-doreplifetime-for-replicated-property
Detects when you forget to call DOREPLIFETIME()
for a replicated property. For example, it would detect B
in this code:
UCLASS()
class ABadActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(Replicated)
int A;
// This property is marked as Replicated, but doesn't have a DOREPLIFETIME call.
UPROPERTY(Replicated)
int B;
};
void ABadActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty> &OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ABadActor, A);
}
unreal-incorrect-interface-invocation
Detects when you call a UINTERFACE()
directly instead of through the ::Execute_
pattern. For example this code is incorrect:
UINTERFACE()
class UTheInterface : public UInterface
{
GENERATED_BODY()
};
class ITheInterface
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
bool TheMethod(int Val1);
};
ITheInterface* Ptr = /* ... */;
Ptr->TheMethod(5);
It should instead be:
ITheInterface* Ptr = /* ... */;
ITheInterface::Execute_TheMethod(Ptr, 5);
unreal-ustruct-field-not-initialized-in-class
Detects when a field inside a USTRUCT()
is a Plain Old Data (POD) type and does not have an in-class initializer. This is a runtime warning in Unreal Engine 5, and is marked to be upgraded to a runtime error in a future release of Unreal Engine. This check catches the issue at compile time instead of runtime.
For example, the following code:
USTRUCT()
class FMyStruct {
GENERATED_BODY()
UPROPERTY()
int A;
UPROPERTY()
FString B;
UPROPERTY()
float C;
UPROPERTY()
AActor* D;
}
should be the following code instead:
USTRUCT()
class FMyStruct {
GENERATED_BODY()
UPROPERTY()
int A = 0;
UPROPERTY()
FString B;
UPROPERTY()
float C = 0.0f;
UPROPERTY()
AActor* D = nullptr;
}