Skip to main content

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 nameWhat it detects
bugprone-bool-pointer-implicit-conversionUsage of bool* as a boolean expression, where you don't also dereference it.
bugprone-branch-cloneScenarios where multiple if / else if / else branches contain the exact same code.
bugprone-copy-constructor-initCopy constructors that don't call the copy constructor of the base class.
bugprone-forwarding-reference-overloadPerfect forwarding constructors that hide copy or move constructors.
bugprone-implicit-widening-of-multiplication-resultPotential overflows that can occur when multiplying integers.
bugprone-incorrect-roundingsIncorrect rounding logic in the form of (int)(double_val + 0.5).
bugprone-infinite-loopObvious infinite loops where the loop is based on a local variable that is never updated by the loop body.
bugprone-integer-divisionCases of integer division being performed in a context that uses floating-point numbers.
bugprone-macro-parenthesesMacros that can have unexpected behaviour due to missing parentheses.
bugprone-macro-repeated-side-effectsMacro invocations that can cause repeated side effects when passed e.g. a function expression instead of a variable expression.
bugprone-misplaced-widening-castCasts 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-callCalls to grandparent virtual methods instead of the immediate parent's virtual method implementation.
bugprone-redundant-branch-conditionConditional variables in if statements that were checked by an outer if statement and were not changed.
bugprone-signed-char-misusesigned char -> integer conversions which might indicate a programming error.
bugprone-sizeof-expressionsizeof expressions which are most likely errors.
bugprone-suspicious-enum-usageCases where an enum is probably misused as a bitmask.
bugprone-suspicious-semicolonSemicolons which are likely to be misplaced, such as: if (x < y); { x++; }
bugprone-swapped-argumentsPotentially swapped arguments by looking at implicit conversions.
bugprone-terminating-continuedo while loops with a condition always evaluating to false that have a continue statement, as this continue terminates the loop.
bugprone-too-small-loop-variablefor loops where the size of the loop variable is too small to represent all values that are part of the iteration range.
bugprone-undelegated-constructorCreation of temporary objects in constructors that look like a function call to another constructor of the same class.
bugprone-unhandled-self-assignmentUser-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-missVirtual methods in a base class that have a similar, but not same, name as a virtual method in a parent class.
performance-for-range-copyFor range loops where the loop variable is copied each iteration but could be a const reference instead.
performance-implicit-conversion-in-loopFor range loops that resulted in an implicit conversion (and thus a copy) of a const reference loop variable.
performance-move-constructor-initUser-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-moveLocal variables that cannot be automatically moved due to constness, such as const variables which are used as the return values from functions.
performance-trivially-destructibleTypes that could be made trivially-destructible by removing out-of-line defaulted destructor declarations.
performance-unnecessary-copy-initializationLocal 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-paramValue 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 nameWhat it detects
unreal-broken-array-callCalls 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-delegateWhen 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-begindestroyWhen you forget to call Super::BeginDestroy() in an overridden BeginDestroy function.
unreal-missing-super-call-lifetimepropsWhen you forget to call Super::GetLifetimeReplicatedProps(Props) in an overridden GetLifetimeReplicatedProps function.
unreal-ionlinesubsystem-getCalls to IOnlineSubsystem::Get() when you should call Online::GetSubsystem() instead.
unreal-unsafe-storage-of-oss-pointerStorage 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-upropertyWhen you forget to add UPROPERTY() to a field in a UCLASS() where the field type is a UObject pointer.
unreal-missing-doreplifetime-for-replicated-propertyWhen you forget to call DOREPLIFETIME() or equivalent for a replicated property.
unreal-incorrect-interface-invocationWhen you call a UINTERFACE() directly instead of through the ::Execute_ pattern.
unreal-ustruct-field-not-initialized-in-classWhen 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;
}