-
Notifications
You must be signed in to change notification settings - Fork 1
Home
This fork of LLVM/Clang allows you to define static analysis rules in .clang-rules
files and have them automatically run during compilation. You do not need to update any other tooling, and it can be used as a drop-in replacement for normal Clang in Unreal Engine builds.
This version of Clang also understands the Unreal Engine UCLASS
etc. specifiers so you can match against them. See the AST Matcher Reference page which provides a list of available matchers you can use.
You can download the latest version of Clang for Unreal Engine from GitHub.
Download the llvm-win64-18.x
artifact, which will give you a ZIP file. Extract this ZIP file to C:\Program Files\LLVM
such that C:\Program Files\LLVM\bin\clang.exe
exists.
To use Clang instead of MSVC in the Unreal build system, open %appdata%\Unreal Engine\UnrealBuildTool\BuildConfiguration.xml
(create it if it does not exist), and set the WindowsPlatform/Compiler
setting like so:
<?xml version="1.0" encoding="utf-8"?>
<Configuration xmlns="https://www.unrealengine.com/BuildConfiguration">
<WindowsPlatform>
<Compiler>Clang</Compiler>
</WindowsPlatform>
</Configuration>
After installing this fork of Clang, in any directory that you want to have static analysis rules run, create a .clang-rules
file. The rules and rulesets you define in this file will apply to all header and source files within and under the directory that the .clang-rules
file is in.
When you're creating a .clang-rules
file, you'll need to pick a namespace. This should be something unique to your organisation. If you need to reference rules across namespaces in rulesets, use the form namespace/rule
.
An example .clang-rules
file that we use internally for EOS Online Subsystem looks as follows:
Namespace: redpoint.games
Rulesets:
Name: eos-online-subsystem
Severity: Error
Rules:
- using-namespace-in-non-file-namespace
- using-in-file-namespace
- incorrectly-nested-namespace
- field-not-initialized
- performance-unnecessary-value-param
- make-shared-with-events
- use-designated-init
Rules:
- # Find function parameters that could be 'const &'.
# @note: We exclude functions starting with 'On' because we assume they might
# have delegate captures that must not be passed by reference.
Name: performance-unnecessary-value-param
Matcher: |
functionDecl(
hasBody(stmt()),
isDefinition(),
unless(isImplicit()),
unless(matchesName("::On.*")),
unless(cxxMethodDecl(anyOf(isOverride(), isFinal()))),
has(typeLoc(forEach(
parmVarDecl(
hasType(qualType(
hasCanonicalType(isExpensiveToCopy()),
unless(hasCanonicalType(referenceType())))),
decl().bind("param")
)
))),
unless(isInstantiated()), decl().bind("functionDecl")
)
ErrorMessage: |
parameter should be made 'const &' to avoid unnecessary copy
Callsite: param
- # Find initializers that are undesignated (requires C++20)
Name: use-designated-init
Matcher: |
initListExpr(
hasType(
cxxRecordDecl(
isAggregate(),
anyOf(
hasName("::Redpoint::EOS::API::Lobby::FJoinLobby::Options"),
hasName("::Redpoint::EOS::API::Lobby::FJoinLobbyById::Options"),
hasName("::Redpoint::EOS::API::Lobby::FCreateLobby::Options")
),
unless(hasAnyBase(hasType(cxxRecordDecl(has(fieldDecl())))))
).bind("type")
),
unless(isFullyDesignated())
).bind("init")
ErrorMessage: |
initializer list should be fully designated
Callsite: init
- # Find MakeShared<T> where T inherits from IHasEventRegistration
Name: make-shared-with-events
Matcher: |
callExpr(
callee(
functionDecl(
hasName("MakeShared"),
hasTemplateArgument(0, refersToType(recordType(
hasDeclaration(cxxRecordDecl(isDerivedFrom("IHasEventRegistration")))
)))
)
)
).bind("call")
ErrorMessage: |
must use MakeSharedWithEvents instead of MakeShared for this type
Callsite: call
- # Disallow 'using namespace' outside of non-file namespaces.
Name: using-namespace-in-non-file-namespace
Matcher: |
namespaceDecl(
unless(isAnonymous()),
unless(hasAncestor(namespaceDecl(matchesName("::__File([0-9]+)_Redpoint")))),
has(usingDirectiveDecl(unless(isImplicit())).bind("using_decl"))
)
ErrorMessage: |
'using namespace' is only permitted inside 'REDPOINT_EOS_FILE_NS_ID()'
Callsite: using_decl
- # 'Redpoint' namespace underneath 'EOS', which usually means that the exporting
# namespace block is underneath the file-isolated namespace
Name: incorrectly-nested-namespace
Matcher: |
namespaceDecl(
hasName("Redpoint"),
hasAncestor(namespaceDecl(hasName("EOS")).bind("containing_decl"))
).bind("export_decl")
ErrorMessage: |
detected export namespace underneath file-isolated namespace
Callsite: export_decl
Hints:
containing_decl: containing namespace found here
- # Disallow 'using =' in file namespaces, as this usually indicates an
# error in the namespace export.
Name: using-in-file-namespace
Matcher: |
namespaceDecl(
unless(isAnonymous()),
hasAncestor(namespaceDecl(matchesName("::__File([0-9]+)_Redpoint"))),
has(usingDecl(unless(isImplicit())).bind("using_decl"))
)
ErrorMessage: |
'using <type>' is not permitted inside 'REDPOINT_EOS_FILE_NS_ID()' (was this meant to be an export namespace?)
Callsite: using_decl
- # Detects if a field in a class or struct is not initialized in the
# constructor's initialization list when at least one member is initialized
# via the initializer list.
Name: field-not-initialized
Matcher: |
cxxConstructorDecl(
unless(isImplicit()),
unless(isDelegatingConstructor()),
unless(isDeleted()),
unless(isDefaulted()),
hasBody(stmt()),
unless(ofClass(cxxRecordDecl(isUClass()))),
unless(ofClass(cxxRecordDecl(isUInterface()))),
ofClass(cxxRecordDecl(forEach(fieldDecl().bind("declared_field")))),
forNone(cxxCtorInitializer(forField(fieldDecl(equalsBoundNode("declared_field")).bind("referenced_field"))))
).bind("bad_constructor")
ErrorMessage: |
one or more fields will be uninitialized when class or struct is constructed; please add the field to the initializer list.
Callsite: bad_constructor
Hints:
declared_field: this field must be initialized