Add clang-tidy and builds with assertions/memory sanitizers for libGD.js (#7051)

Only show in developer changelog
This commit is contained in:
Florian Rival
2024-10-14 12:06:42 +02:00
committed by GitHub
parent b34e802dcb
commit 58ba2668c2
42 changed files with 507 additions and 205 deletions

View File

@@ -176,6 +176,7 @@ jobs:
# Build the WebAssembly library only (so that it's cached on a S3 and easy to re-use).
build-gdevelop_js-wasm-only:
resource_class: medium+ # Compilation time decrease linearly with the number of CPUs, but not linking (so "large" does not speedup total build time).
docker:
- image: cimg/node:16.13
@@ -232,10 +233,83 @@ jobs:
name: Deploy to S3 (latest)
command: aws s3 sync Binaries/embuild/GDevelop.js s3://gdevelop-gdevelop.js/$(git rev-parse --abbrev-ref HEAD)/latest/
# Build the WebAssembly library with clang-tidy and memory sanitizers.
build-gdevelop_js-debug-sanitizers-and-extra-checks:
resource_class: xlarge # Total time decrease linearly with the number of CPUs.
docker:
- image: cimg/node:16.13
working_directory: ~/GDevelop
steps:
- checkout
- aws-cli/setup
# System dependencies (for Emscripten)
- run:
name: Install dependencies for Emscripten
command: sudo apt-get update && sudo apt install cmake
- run:
name: Install dependencies for clang-tidy v19
command: wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && sudo ./llvm.sh 19 && sudo apt install clang-tidy-19
- run:
name: Install Python3 dependencies for Emscripten
command: sudo apt install python-is-python3 python3-distutils -y
- run:
name: Install Emscripten (for GDevelop.js)
command: git clone https://github.com/juj/emsdk.git && cd emsdk && ./emsdk install 3.1.21 && ./emsdk activate 3.1.21 && cd ..
# GDevelop.js dependencies
- restore_cache:
keys:
- gdevelop.js-linux-nodejs-dependencies-{{ checksum "GDevelop.js/package-lock.json" }}
# fallback to using the latest cache if no exact match is found
- gdevelop.js-linux-nodejs-dependencies-
- run:
name: Install GDevelop.js dependencies and build it
command: cd GDevelop.js && npm install && cd ..
# Build GDevelop.js
- run:
name: Build GDevelop.js ('debug-sanitizers' variant)
command: cd GDevelop.js && source ../emsdk/emsdk_env.sh && npm run build -- --variant=debug-sanitizers
- run:
name: Run clang-tidy
command: cd GDevelop.js && npm run lint
- run:
name: Run tests
command: cd GDevelop.js && npm run test -- --maxWorkers=4
# Upload artifacts (CircleCI)
- store_artifacts:
path: Binaries/embuild/GDevelop.js
# Upload artifacts (AWS)
- run:
name: Deploy to S3 (specific commit)
command: aws s3 sync Binaries/embuild/GDevelop.js s3://gdevelop-gdevelop.js/$(git rev-parse --abbrev-ref HEAD)/variant/debug-sanitizers/commit/$(git rev-parse HEAD)/
workflows:
builds:
gdevelop_js-wasm:
jobs:
- build-gdevelop_js-wasm-only
gdevelop_js-wasm-extra-checks:
jobs:
- build-gdevelop_js-debug-sanitizers-and-extra-checks:
# Extra checks are resource intensive so don't all run them.
filters:
branches:
only:
- master
- /experimental-build.*/
builds:
jobs:
- build-macos:
filters:
branches:

4
.clang-tidy Normal file
View File

@@ -0,0 +1,4 @@
Checks: 'clang-diagnostic-*,clang-analyzer-*,cppcoreguidelines-*,-cppcoreguidelines-explicit-virtual-functions,-cppcoreguidelines-avoid-const-or-ref-data-members,-cppcoreguidelines-special-member-functions,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-non-private-member-variables-in-classes,-cppcoreguidelines-owning-memory,-cppcoreguidelines-virtual-class-destructor,-clang-analyzer-optin.performance.Padding,-cppcoreguidelines-narrowing-conversions'
WarningsAsErrors: 'cppcoreguidelines-pro-type-member-init, clang-analyzer-optin.cplusplus.UninitializedObject'
HeaderFilterRegex: '.*'
FormatStyle: none

View File

@@ -69,12 +69,18 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# uninitialized variables or other hard to debug bugs.
add_compile_options(
-Wall
-Wextra
-Wuninitialized
-Wconditional-uninitialized
-Wno-unknown-warning-option
-Wno-reorder-ctor
-Wno-reorder
-Wno-unused-parameter
-Wno-pessimizing-move
-Wno-unused-variable
-Wno-unused-variable # Not a good style, but not a risk
-Wno-unused-private-field
-Wno-ignored-qualifiers # Not a risk
-Wno-sign-compare # Not a big risk
# Make as much warnings considered as errors as possible (only one for now).
-Werror=return-stack-address

View File

@@ -72,8 +72,6 @@ class GD_CORE_API WhileEvent : public gd::BaseEvent {
///< de/activate infinite loop warning when the
///< user create the event
mutable unsigned int whileConditionsHeight;
int GetConditionsHeight() const;
int GetActionsHeight() const;
int GetWhileConditionsHeight() const;

View File

@@ -36,8 +36,8 @@ struct GD_CORE_API ExpressionParserLocation {
private:
bool isValid;
size_t startPosition;
size_t endPosition;
size_t startPosition = 0;
size_t endPosition = 0;
};
/**

View File

@@ -37,8 +37,7 @@ BehaviorMetadata::BehaviorMetadata(
className(className_),
iconFilename(icon24x24),
instance(instance_),
sharedDatasInstance(sharedDatasInstance_),
quickCustomizationVisibility(QuickCustomization::Visibility::Default) {
sharedDatasInstance(sharedDatasInstance_) {
SetFullName(gd::String(fullname_));
SetDescription(gd::String(description_));
SetDefaultName(gd::String(defaultName_));

View File

@@ -394,7 +394,7 @@ class GD_CORE_API BehaviorMetadata : public InstructionOrExpressionContainerMeta
bool isPrivate = false;
bool isHidden = false;
gd::String openFullEditorLabel;
QuickCustomization::Visibility quickCustomizationVisibility;
QuickCustomization::Visibility quickCustomizationVisibility = QuickCustomization::Visibility::Default;
// TODO: Nitpicking: convert these to std::unique_ptr to clarify ownership.
std::shared_ptr<gd::Behavior> instance;

View File

@@ -185,10 +185,10 @@ class GD_CORE_API EffectMetadata {
gd::String fullname;
gd::String description;
std::vector<gd::String> includeFiles;
bool isMarkedAsNotWorkingForObjects;
bool isMarkedAsOnlyWorkingFor2D;
bool isMarkedAsOnlyWorkingFor3D;
bool isMarkedAsUnique;
bool isMarkedAsNotWorkingForObjects = false;
bool isMarkedAsOnlyWorkingFor2D = false;
bool isMarkedAsOnlyWorkingFor3D = false;
bool isMarkedAsUnique = false;
std::map<gd::String, gd::PropertyDescriptor> properties;
};

View File

@@ -19,7 +19,8 @@ EventMetadata::EventMetadata(const gd::String &name_,
: fullname(fullname_),
description(description_),
group(group_),
instance(instance_) {
instance(instance_),
hasCustomCodeGenerator(false) {
ClearCodeGenerationAndPreprocessing();
if (instance) instance->SetType(name_);
}

View File

@@ -83,7 +83,7 @@ class GD_CORE_API EventMetadata {
gd::String group;
std::shared_ptr<gd::BaseEvent> instance;
bool hasCustomCodeGenerator;
bool hasCustomCodeGenerator = false;
std::function<gd::String(gd::BaseEvent& event,
gd::EventsCodeGenerator& codeGenerator,
gd::EventsCodeGenerationContext& context)>

View File

@@ -288,8 +288,8 @@ class GD_CORE_API MetadataProvider {
static EffectMetadata badEffectMetadata;
static gd::InstructionMetadata badInstructionMetadata;
static gd::ExpressionMetadata badExpressionMetadata;
int useless; // Useless member to avoid emscripten "must have a positive
// integer typeid pointer" runtime error.
int useless = 0; // Useless member to avoid emscripten "must have a positive
// integer typeid pointer" runtime error.
};
} // namespace gd

View File

@@ -40,8 +40,7 @@ class Object;
class ObjectConfiguration;
} // namespace gd
typedef std::function<std::unique_ptr<gd::ObjectConfiguration>()>
CreateFunPtr;
typedef std::function<std::unique_ptr<gd::ObjectConfiguration>()> CreateFunPtr;
namespace gd {
@@ -51,25 +50,25 @@ namespace gd {
*/
class GD_CORE_API CompilationInfo {
public:
CompilationInfo() : informationCompleted(false){};
virtual ~CompilationInfo(){};
CompilationInfo() {};
virtual ~CompilationInfo() {};
bool informationCompleted;
bool informationCompleted = false;
bool runtimeOnly; ///< True if the extension was compiled for a runtime use
///< only
bool runtimeOnly = false; ///< True if the extension was compiled for a
///< runtime use only
#if defined(__GNUC__)
int gccMajorVersion;
int gccMinorVersion;
int gccPatchLevel;
int gccMajorVersion = 0;
int gccMinorVersion = 0;
int gccPatchLevel = 0;
#endif
int sfmlMajorVersion;
int sfmlMinorVersion;
int sfmlMajorVersion = 0;
int sfmlMinorVersion = 0;
gd::String gdCoreVersion;
int sizeOfpInt;
int sizeOfpInt = 0;
};
struct GD_CORE_API DuplicatedInstructionOptions {
@@ -239,11 +238,12 @@ class GD_CORE_API PlatformExtension {
* \param instance The "blueprint" object to be copied when a new object is
asked for.
*/
gd::ObjectMetadata& AddObject(const gd::String& name_,
const gd::String& fullname_,
const gd::String& description_,
const gd::String& icon_,
std::shared_ptr<gd::ObjectConfiguration> instance);
gd::ObjectMetadata& AddObject(
const gd::String& name_,
const gd::String& fullname_,
const gd::String& description_,
const gd::String& icon_,
std::shared_ptr<gd::ObjectConfiguration> instance);
/**
* \brief Declare a new events based object as being part of the extension.
@@ -253,11 +253,10 @@ class GD_CORE_API PlatformExtension {
* \param description The user friendly description of the object
* \param icon The icon of the object.
*/
gd::ObjectMetadata& AddEventsBasedObject(
const gd::String& name_,
const gd::String& fullname_,
const gd::String& description_,
const gd::String& icon_);
gd::ObjectMetadata& AddEventsBasedObject(const gd::String& name_,
const gd::String& fullname_,
const gd::String& description_,
const gd::String& icon_);
/**
* \brief Declare a new behavior as being part of the extension.
@@ -420,8 +419,7 @@ class GD_CORE_API PlatformExtension {
PlatformExtension& SetTags(const gd::String& csvTags) {
tags.clear();
tags = csvTags.Split(',');
for (size_t i = 0; i < tags.size(); i++)
{
for (size_t i = 0; i < tags.size(); i++) {
tags[i] = tags[i].Trim().LowerCase();
}
return *this;
@@ -634,31 +632,30 @@ class GD_CORE_API PlatformExtension {
*/
static gd::String GetNamespaceSeparator() { return "::"; }
static gd::String GetEventsFunctionFullType(const gd::String &extensionName,
const gd::String &functionName);
static gd::String GetEventsFunctionFullType(const gd::String& extensionName,
const gd::String& functionName);
static gd::String
GetBehaviorEventsFunctionFullType(const gd::String &extensionName,
const gd::String &behaviorName,
const gd::String &functionName);
static gd::String GetBehaviorEventsFunctionFullType(
const gd::String& extensionName,
const gd::String& behaviorName,
const gd::String& functionName);
static gd::String GetBehaviorFullType(const gd::String &extensionName,
const gd::String &behaviorName);
static gd::String GetBehaviorFullType(const gd::String& extensionName,
const gd::String& behaviorName);
static gd::String
GetObjectEventsFunctionFullType(const gd::String &extensionName,
const gd::String &objectName,
const gd::String &functionName);
static gd::String GetObjectFullType(const gd::String &extensionName,
const gd::String &objectName);
static gd::String GetObjectEventsFunctionFullType(
const gd::String& extensionName,
const gd::String& objectName,
const gd::String& functionName);
static gd::String GetObjectFullType(const gd::String& extensionName,
const gd::String& objectName);
static gd::String GetExtensionFromFullObjectType(const gd::String& type);
static gd::String GetObjectNameFromFullObjectType(const gd::String& type);
private:
private:
/**
* Set the namespace (the string all actions/conditions/expressions start
* with).
@@ -673,10 +670,10 @@ private:
gd::String fullname; ///< Name displayed to users in the editor.
gd::String informations; ///< Description displayed to users in the editor.
gd::String category;
gd::String author; ///< Author displayed to users in the editor.
gd::String license; ///< License name displayed to users in the editor.
bool deprecated; ///< true if the extension is deprecated and shouldn't be
///< shown in IDE.
gd::String author; ///< Author displayed to users in the editor.
gd::String license; ///< License name displayed to users in the editor.
bool deprecated; ///< true if the extension is deprecated and shouldn't be
///< shown in IDE.
gd::String helpPath; ///< The relative path to the help for this extension in
///< the documentation.
gd::String iconUrl; ///< The URL to the icon to be shown for this extension.

View File

@@ -5,6 +5,8 @@
* project is released under the MIT License.
*/
// NOLINTBEGIN
#ifndef GDCORE_PLATFORMEXTENSION_INL
#define GDCORE_PLATFORMEXTENSION_INL
@@ -36,3 +38,5 @@ gd::ObjectMetadata& PlatformExtension::AddObject(const gd::String& name,
} // namespace gd
#endif
// NOLINTEND

View File

@@ -337,19 +337,19 @@ struct GD_CORE_API ExpressionCompletionDescription {
private:
CompletionKind completionKind;
gd::Variable::Type variableType;
gd::VariablesContainer::SourceType variableScope;
gd::Variable::Type variableType = gd::Variable::Unknown;
gd::VariablesContainer::SourceType variableScope = gd::VariablesContainer::Unknown;
gd::String type;
gd::String prefix;
gd::String completion;
size_t replacementStartPosition;
size_t replacementEndPosition;
size_t replacementStartPosition = 0;
size_t replacementEndPosition = 0;
gd::String objectName;
gd::String behaviorName;
bool isExact;
bool isLastParameter;
const gd::ParameterMetadata* parameterMetadata;
const gd::ObjectConfiguration* objectConfiguration;
bool isExact = false;
bool isLastParameter = false;
const gd::ParameterMetadata* parameterMetadata = &badParameterMetadata;
const gd::ObjectConfiguration* objectConfiguration = &badObjectConfiguration;
static const gd::ParameterMetadata badParameterMetadata;
static const gd::ObjectConfiguration badObjectConfiguration;

View File

@@ -74,8 +74,8 @@ class GD_CORE_API ExpressionsParameterMover
const gd::Platform &platform;
gd::String functionName;
std::size_t oldIndex;
std::size_t newIndex;
std::size_t oldIndex = 0;
std::size_t newIndex = 0;
gd::String behaviorType;
gd::String objectType;
};

View File

@@ -4,6 +4,7 @@
#include "GDCore/Extensions/Metadata/DependencyMetadata.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Extensions/Platform.h"
namespace gd {

View File

@@ -140,12 +140,12 @@ protected:
bool IsOverridingEventsBasedObjectChildrenConfiguration() const;
const Project* project; ///< The project is used to get the
///< EventBasedObject from the fullType.
const Project* project = nullptr; ///< The project is used to get the
///< EventBasedObject from the fullType.
gd::SerializerElement objectContent;
std::unordered_set<gd::String> unfoldedChildren;
bool isMarkedAsOverridingEventsBasedObjectChildrenConfiguration;
bool isMarkedAsOverridingEventsBasedObjectChildrenConfiguration = false;
mutable std::map<gd::String, std::unique_ptr<gd::ObjectConfiguration>> childObjectConfigurations;
static gd::ObjectConfiguration badObjectConfiguration;

View File

@@ -7,7 +7,7 @@
namespace gd {
ExternalEvents::ExternalEvents() : lastChangeTimeStamp(0) {
ExternalEvents::ExternalEvents() {
// ctor
}
@@ -24,14 +24,12 @@ ExternalEvents& ExternalEvents::operator=(const ExternalEvents& rhs) {
void ExternalEvents::Init(const ExternalEvents& externalEvents) {
name = externalEvents.GetName();
associatedScene = externalEvents.GetAssociatedLayout();
lastChangeTimeStamp = externalEvents.GetLastChangeTimeStamp();
events = externalEvents.events;
}
void ExternalEvents::SerializeTo(SerializerElement& element) const {
element.SetAttribute("name", name);
element.SetAttribute("associatedLayout", associatedScene);
element.SetAttribute("lastChangeTimeStamp", (int)lastChangeTimeStamp);
gd::EventsListSerialization::SerializeEventsTo(events,
element.AddChild("events"));
}
@@ -41,8 +39,6 @@ void ExternalEvents::UnserializeFrom(gd::Project& project,
name = element.GetStringAttribute("name", "", "Name");
associatedScene =
element.GetStringAttribute("associatedLayout", "", "AssociatedScene");
lastChangeTimeStamp =
element.GetIntAttribute("lastChangeTimeStamp", 0, "LastChangeTimeStamp");
gd::EventsListSerialization::UnserializeEventsFrom(
project, events, element.GetChild("events", 0, "Events"));
}

View File

@@ -67,24 +67,6 @@ class GD_CORE_API ExternalEvents {
associatedScene = name_;
};
/**
* Get the latest time of the build.
* Used when the IDE found that the external events can be compiled separately
* from scene's events.
*
* \todo This is specific to GD C++ Platform
*/
time_t GetLastChangeTimeStamp() const { return lastChangeTimeStamp; };
/**
* Change the latest time of the build of the external events.
*
* \todo This is specific to GD C++ Platform
*/
void SetLastChangeTimeStamp(time_t newTimeStamp) {
lastChangeTimeStamp = newTimeStamp;
};
/**
* \brief Get the events.
*/
@@ -109,7 +91,6 @@ class GD_CORE_API ExternalEvents {
private:
gd::String name;
gd::String associatedScene;
time_t lastChangeTimeStamp; ///< Time of the last build
gd::EventsList events; ///< List of events
/**
@@ -119,19 +100,6 @@ class GD_CORE_API ExternalEvents {
void Init(const ExternalEvents& externalEvents);
};
/**
* \brief Functor testing ExternalEvents' name
*/
struct ExternalEventsHasName
: public std::binary_function<std::unique_ptr<gd::ExternalEvents>,
gd::String,
bool> {
bool operator()(const std::unique_ptr<gd::ExternalEvents>& externalEvents,
gd::String name) const {
return externalEvents->GetName() == name;
}
};
} // namespace gd
#endif // GDCORE_EXTERNALEVENTS_H

View File

@@ -369,9 +369,9 @@ class GD_CORE_API Layout {
private:
gd::String name; ///< Scene name
gd::String mangledName; ///< The scene name mangled by SceneNameMangler
unsigned int backgroundColorR; ///< Background color Red component
unsigned int backgroundColorG; ///< Background color Green component
unsigned int backgroundColorB; ///< Background color Blue component
unsigned int backgroundColorR = 0; ///< Background color Red component
unsigned int backgroundColorG = 0; ///< Background color Green component
unsigned int backgroundColorB = 0; ///< Background color Blue component
gd::String title; ///< Title displayed in the window
gd::VariablesContainer variables; ///< Variables list
gd::ObjectsContainer objectsContainer;
@@ -379,12 +379,12 @@ class GD_CORE_API Layout {
gd::LayersContainer layers;
std::map<gd::String, std::unique_ptr<gd::BehaviorsSharedData>>
behaviorsSharedData; ///< Initial shared datas of behaviors
bool stopSoundsOnStartup; ///< True to make the scene stop all sounds at
///< startup.
bool standardSortMethod; ///< True to sort objects using standard sort.
bool disableInputWhenNotFocused; /// If set to true, the input must be
/// disabled when the window do not have the
/// focus.
bool stopSoundsOnStartup = true; ///< True to make the scene stop all sounds at
///< startup.
bool standardSortMethod = true; ///< True to sort objects using standard sort.
bool disableInputWhenNotFocused = true; /// If set to true, the input must be
/// disabled when the window do not have the
/// focus.
static gd::BehaviorsSharedData
badBehaviorSharedData; ///< Null object, returned when
///< GetBehaviorSharedData can not find the

View File

@@ -193,7 +193,7 @@ class GD_CORE_API ObjectFolderOrObject {
static gd::ObjectFolderOrObject badObjectFolderOrObject;
gd::ObjectFolderOrObject*
parent; // nullptr if root folder, points to the parent folder otherwise.
parent = nullptr; // nullptr if root folder, points to the parent folder otherwise.
QuickCustomization::Visibility quickCustomizationVisibility;
// Representing an object:

View File

@@ -71,7 +71,7 @@ Project::Project()
isPlayableWithKeyboard(false),
isPlayableWithGamepad(false),
isPlayableWithMobile(false),
currentPlatform(NULL),
currentPlatform(nullptr),
gdMajorVersion(gd::VersionWrapper::Major()),
gdMinorVersion(gd::VersionWrapper::Minor()),
gdBuildVersion(gd::VersionWrapper::Build()),

View File

@@ -990,16 +990,12 @@ class GD_CORE_API Project {
/**
* \brief return the objects of the project.
*/
gd::ObjectsContainer& GetObjects() {
return objectsContainer;
}
gd::ObjectsContainer& GetObjects() { return objectsContainer; }
/**
* \brief Return the objects of the project.
*/
const gd::ObjectsContainer& GetObjects() const {
return objectsContainer;
}
const gd::ObjectsContainer& GetObjects() const { return objectsContainer; }
///@}
/** \name Identifier names
@@ -1080,32 +1076,35 @@ class GD_CORE_API Project {
*/
void Init(const gd::Project& project);
gd::String name; ///< Game name
gd::String description; ///< Game description
gd::String version; ///< Game version number (used for some exports)
unsigned int windowWidth; ///< Window default width
unsigned int windowHeight; ///< Window default height
int maxFPS; ///< Maximum Frame Per Seconds, -1 for unlimited
unsigned int minFPS; ///< Minimum Frame Per Seconds ( slow down game if FPS
///< are below this number )
bool verticalSync; ///< If true, must activate vertical synchronization.
gd::String name; ///< Game name
gd::String description; ///< Game description
gd::String version; ///< Game version number (used for some exports)
unsigned int windowWidth = 0; ///< Window default width
unsigned int windowHeight = 0; ///< Window default height
int maxFPS = 0; ///< Maximum Frame Per Seconds, -1 for unlimited
unsigned int minFPS = 0; ///< Minimum Frame Per Seconds ( slow down game if
///< FPS are below this number )
bool verticalSync =
false; ///< If true, must activate vertical synchronization.
gd::String scaleMode;
bool pixelsRounding; ///< If true, the rendering should stop pixel
///< interpolation of rendered objects.
bool adaptGameResolutionAtRuntime; ///< Should the game resolution be adapted
///< to the window size at runtime
bool pixelsRounding = false; ///< If true, the rendering should stop pixel
///< interpolation of rendered objects.
bool adaptGameResolutionAtRuntime =
true; ///< Should the game resolution be adapted
///< to the window size at runtime
gd::String
sizeOnStartupMode; ///< How to adapt the game size to the screen. Can be
///< "adaptWidth", "adaptHeight" or empty
gd::String antialiasingMode;
bool isAntialisingEnabledOnMobile;
bool isAntialisingEnabledOnMobile = false;
gd::String projectUuid; ///< UUID useful to identify the game in online
///< services or database that would require it.
bool useDeprecatedZeroAsDefaultZOrder; ///< If true, objects created from
///< events will have 0 as Z order,
///< instead of the highest Z order
///< found on the layer at the scene
///< startup.
bool useDeprecatedZeroAsDefaultZOrder =
false; ///< If true, objects created from
///< events will have 0 as Z order,
///< instead of the highest Z order
///< found on the layer at the scene
///< startup.
std::vector<std::unique_ptr<gd::Layout> > scenes; ///< List of all scenes
gd::VariablesContainer variables; ///< Initial global variables
gd::ObjectsContainer objectsContainer;
@@ -1118,7 +1117,8 @@ class GD_CORE_API Project {
std::vector<gd::Platform*>
platforms; ///< Pointers to the platforms this project supports.
gd::String firstLayout;
bool useExternalSourceFiles; ///< True if game used external source files.
bool useExternalSourceFiles =
false; ///< True if game used external source files.
std::vector<std::unique_ptr<gd::SourceFile> >
externalSourceFiles; ///< List of external source files used.
gd::String author; ///< Game author name, for publishing purpose.
@@ -1127,35 +1127,40 @@ class GD_CORE_API Project {
std::vector<gd::String>
authorUsernames; ///< Game author usernames, from GDevelop users DB.
std::vector<gd::String> categories; ///< Game categories
bool isPlayableWithKeyboard; ///< The project is playable with a keyboard.
bool isPlayableWithGamepad; ///< The project is playable with a gamepad.
bool isPlayableWithMobile; ///< The project is playable on a mobile.
gd::String packageName; ///< Game package name
bool isPlayableWithKeyboard =
false; ///< The project is playable with a keyboard.
bool isPlayableWithGamepad =
false; ///< The project is playable with a gamepad.
bool isPlayableWithMobile = false; ///< The project is playable on a mobile.
gd::String packageName; ///< Game package name
gd::String templateSlug; ///< The slug of the template from which the game is
///< created.
gd::String orientation; ///< Lock game orientation (on mobile devices).
///< "default", "landscape" or "portrait".
bool
folderProject; ///< True if folder project, false if single file project.
bool folderProject =
false; ///< True if folder project, false if single file project.
gd::String
projectFile; ///< Path to the project file - when editing a local file.
gd::String latestCompilationDirectory;
gd::Platform*
currentPlatform; ///< The platform being used to edit the project.
gd::Platform* currentPlatform =
nullptr; ///< The platform being used to edit the project.
gd::PlatformSpecificAssets platformSpecificAssets;
gd::LoadingScreen loadingScreen;
gd::Watermark watermark;
std::vector<std::unique_ptr<gd::ExternalEvents> >
externalEvents; ///< List of all externals events
ExtensionProperties
extensionProperties; ///< The properties of the extensions.
extensionProperties; ///< The properties of the extensions.
gd::WholeProjectDiagnosticReport wholeProjectDiagnosticReport;
mutable unsigned int gdMajorVersion; ///< The GD major version used the last
///< time the project was saved.
mutable unsigned int gdMinorVersion; ///< The GD minor version used the last
///< time the project was saved.
mutable unsigned int gdBuildVersion; ///< The GD build version used the last
///< time the project was saved.
mutable unsigned int gdMajorVersion =
0; ///< The GD major version used the last
///< time the project was saved.
mutable unsigned int gdMinorVersion =
0; ///< The GD minor version used the last
///< time the project was saved.
mutable unsigned int gdBuildVersion =
0; ///< The GD build version used the last
///< time the project was saved.
};
} // namespace gd

View File

@@ -152,8 +152,8 @@ class GD_CORE_API Resource {
gd::String metadata;
gd::String originName;
gd::String originIdentifier;
bool userAdded; ///< True if the resource was added by the user, and not
///< automatically by GDevelop.
bool userAdded = false; ///< True if the resource was added by the user, and not
///< automatically by GDevelop.
static gd::String badStr;
};

View File

@@ -33,7 +33,7 @@ class GD_CORE_API Variable {
Unknown,
/** Used when objects of a group have different types for a variable. */
MixedTypes,
// Primitive types
String,
Number,
@@ -393,11 +393,11 @@ class GD_CORE_API Variable {
*/
static Type StringAsType(const gd::String& str);
bool folded;
bool folded = false;
mutable Type type;
mutable gd::String str;
mutable double value;
mutable bool boolVal;
mutable bool boolVal = false;
mutable bool hasMixedValues;
mutable std::map<gd::String, std::shared_ptr<Variable>>
children; ///< Children, when the variable is considered as a structure.

View File

@@ -187,7 +187,7 @@ class GD_CORE_API VariablesContainer {
///@}
private:
SourceType sourceType;
SourceType sourceType = Unknown;
std::vector<std::pair<gd::String, std::shared_ptr<gd::Variable>>> variables;
mutable gd::String persistentUuid; ///< A persistent random version 4 UUID,
///< useful for computing changesets.

View File

@@ -456,13 +456,13 @@ class GD_CORE_API SerializerElement {
*/
void Init(const gd::SerializerElement &other);
bool valueUndefined; ///< If true, the element does not have a value.
bool valueUndefined = true; ///< If true, the element does not have a value.
SerializerValue elementValue;
std::map<gd::String, SerializerValue> attributes;
std::vector<std::pair<gd::String, std::shared_ptr<SerializerElement> > >
children;
mutable bool isArray; ///< true if element is considered as an array
mutable bool isArray = false; ///< true if element is considered as an array
mutable gd::String arrayOf; ///< The name of the children (was useful for XML
///< parsed elements).
mutable gd::String deprecatedArrayOf; ///< Alternate name for children

View File

@@ -0,0 +1,2 @@
# Disable all checks in this folder.
Checks: '-*'

View File

@@ -4,6 +4,8 @@
* This project is released under the MIT License.
*/
// NOLINTBEGIN
#include "GDCore/String.h"
#include <algorithm>
@@ -825,3 +827,5 @@ bool GD_CORE_API CaseInsensitiveEquiv( const String &lhs, const String &rhs, boo
}
}
// NOLINTEND

View File

@@ -4,6 +4,8 @@
* This project is released under the MIT License.
*/
// NOLINTBEGIN
#ifndef GDCORE_UTF8_STRING_H
#define GDCORE_UTF8_STRING_H
@@ -898,3 +900,5 @@ namespace std
* In Unicode, uppercasing/lowercasing strings to compare them in a case-insensitive way is not recommended.
* That's why the function gd::CaseInsensitiveEquiv exists to compare two strings in a case-insensitive way.
*/
// NOLINTEND

View File

@@ -2,6 +2,7 @@
#define GD_CORE_POLYMORPHICCLONE_H
#include <memory>
#include <vector>
namespace gd {

View File

@@ -3,6 +3,7 @@
* Copyright 2008-present Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include <algorithm>
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/String.h"
#include "GDCore/Tools/MakeUnique.h"

View File

@@ -3,6 +3,9 @@
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
// NOLINTBEGIN
#include "SystemStats.h"
#include "stdio.h"
#include "stdlib.h"
@@ -49,3 +52,5 @@ size_t SystemStats::GetUsedVirtualMemory() {
}
} // namespace gd
// NOLINTEND

View File

@@ -0,0 +1,2 @@
# Disable all checks in this folder.
Checks: '-*'

View File

@@ -0,0 +1,2 @@
# Disable all checks in this folder.
Checks: '-*'

View File

@@ -0,0 +1,9 @@
// If running under ASAN, disable the "container overflow" checks because of false positives
// with std::vector<gd::String>. See https://github.com/google/sanitizers/wiki/AddressSanitizerContainerOverflow.
if (Module['ASAN_OPTIONS'] === undefined)
Module['ASAN_OPTIONS'] ='detect_container_overflow=0';
// Prevent calling process["exit"] when there is a abort/runtime crash
// (useful for tests when running ASAN, to see the logs).
if (Module.noExitRuntime === undefined)
Module.noExitRuntime = true;

View File

@@ -12,18 +12,29 @@ if(NOT EMSCRIPTEN)
endif()
# Compilation flags (https://emscripten.org/docs/tools_reference/emcc.html):
add_compile_options(-O3 -flto) # Optimizations during compilation
# add_compile_options(-fwasm-exceptions) # Enable exceptions
if(NOT DISABLE_EMSCRIPTEN_LINK_OPTIMIZATIONS)
add_compile_options(-flto) # The compiler needs to know if there will be link time optimisations
if("${GDEVELOPJS_BUILD_VARIANT}" STREQUAL "dev")
# Development: full optimization but no link time optimization.
add_compile_options(-O3)
elseif("${GDEVELOPJS_BUILD_VARIANT}" STREQUAL "debug")
# Debug: optimization but that will be reduced by the debugging "-g" flag.
add_compile_options(-O3)
SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g --profiling") # Debugging + profiling support
elseif("${GDEVELOPJS_BUILD_VARIANT}" STREQUAL "debug-assertions")
# Debug with assertions: no optimization.
SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g --profiling") # Debugging + profiling support
elseif("${GDEVELOPJS_BUILD_VARIANT}" STREQUAL "debug-sanitizers")
# Debug with sanitizers: no optimization.
SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g --profiling") # Debugging + profiling support
add_compile_options(-fsanitize=address) # Uncomment to auto-detect occurences of memory bugs (memory leak, use after free, overflows, ...) - also enable linking below!
# add_compile_options(-fsanitize=undefined) # Uncomment to auto-detect occurences of undefined behavior - also enable linking below!
add_compile_options(-fsanitize=return) # Uncomment to auto-detect occurences of undefined behavior - also enable linking below!
add_compile_options(-fsanitize=null) # Uncomment to auto-detect occurences of undefined behavior - also enable linking below!
else()
# Production: full optimization.
# The compiler needs to know if there will be link time optimisations.
add_compile_options(-O3 -flto)
endif()
# Compiler debugging options
#
# add_compile_options(-fsanitize=address) # Uncomment to auto-detect occurences of memory bugs (memory leak, use after free, overflows, ...) - also enable linking below!
# add_compile_options(-fsanitize=undefined) # Uncomment to auto-detect occurences of undefined behavior - also enable linking below!
# SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g --profiling") # Uncomment for debugging + profiling support
# SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --profiling") # Uncomment for profiling support
# add_compile_options(-fwasm-exceptions) # Enable exceptions
# Common directories:
#
@@ -68,13 +79,10 @@ add_executable(
"Bindings/ObjectJsImplementation.cpp"
"Bindings/Wrapper.cpp")
if(DISABLE_EMSCRIPTEN_LINK_OPTIMIZATIONS)
# Disable optimizations at linking time for much faster builds.
message(STATUS "Disabling optimization at link time for (slightly) faster build")
target_link_libraries(GD "-O0")
else()
target_link_libraries(GD "-O3 -flto")
endif()
# Linker options
#
target_link_libraries(GD "--pre-js ${GD_base_dir}/GDevelop.js/Bindings/prejs.js")
target_link_libraries(GD "--post-js ${GD_base_dir}/GDevelop.js/Bindings/glue.js")
target_link_libraries(GD "--post-js ${GD_base_dir}/GDevelop.js/Bindings/postjs.js")
target_link_libraries(GD "-s MODULARIZE=1")
@@ -85,15 +93,32 @@ target_link_libraries(GD "-s NODEJS_CATCH_EXIT=0") # Don't print the entire GDCo
target_link_libraries(GD "-s ERROR_ON_UNDEFINED_SYMBOLS=0")
target_link_libraries(GD "-s \"EXPORTED_FUNCTIONS=['_free']\"")
# Linker debugging options
#
# target_link_libraries(GD "-s DEMANGLE_SUPPORT=1") # Demangle stack traces
# target_link_libraries(GD "-s ASSERTIONS=1") # Basic runtime memory allocation checks (necessary for wasm exceptions stack traces)
# target_link_libraries(GD "-s ASSERTIONS=2 -s SAFE_HEAP=1") # Uncomment to do runtime checks for memory allocations and access errors
# target_link_libraries(GD "-fsanitize=address") # Uncomment to auto-detect occurences of memory bugs (memory leak, use after free, overflows, ...) - also enable compiling above!
# target_link_libraries(GD "-fsanitize=undefined") # Uncomment to auto-detect occurences of undefined behavior - also enable compiling above!
# target_link_libraries(--cpuprofiler) # Uncomment for interactive performance profiling
# target_link_libraries(--memoryprofiler) # Uncomment for interactive memory profiling
if("${GDEVELOPJS_BUILD_VARIANT}" STREQUAL "dev")
# Disable optimizations at linking time for slightly faster builds.
# This is safe (https://emscripten.org/docs/optimizing/Optimizing-Code.html#link-times):
# "Note that it is ok to link with those flags even if the source files were compiled with a different optimization level"
message(STATUS "'dev' variant: disabling optimization at link time for (slightly) faster build")
target_link_libraries(GD "-O0")
elseif("${GDEVELOPJS_BUILD_VARIANT}" STREQUAL "debug")
message(STATUS "'debug' variant: keeping debugging information and disabling link time optimizations")
target_link_libraries(GD "-O0")
elseif("${GDEVELOPJS_BUILD_VARIANT}" STREQUAL "debug-assertions")
message(STATUS "'debug-assertions' variant: enabling Emscripten assertions and SAFE_HEAP")
# target_link_libraries(GD "-s DEMANGLE_SUPPORT=1") # Demangle stack traces
# target_link_libraries(GD "-s ASSERTIONS=1") # Basic runtime memory allocation checks (necessary for wasm exceptions stack traces)
target_link_libraries(GD "-s ASSERTIONS=2 -s SAFE_HEAP=1") # Uncomment to do runtime checks for memory allocations and access errors
# target_link_libraries(--cpuprofiler) # Uncomment for interactive performance profiling
# target_link_libraries(--memoryprofiler) # Uncomment for interactive memory profiling
elseif("${GDEVELOPJS_BUILD_VARIANT}" STREQUAL "debug-sanitizers")
message(STATUS "'debug-sanitizers' variant: enabling ASAN and other sanitizers")
target_link_libraries(GD "-fsanitize=address") # Uncomment to auto-detect occurences of memory bugs (memory leak, use after free, overflows, ...) - also enable compiling above!
# target_link_libraries(GD "-fsanitize=undefined") # Uncomment to auto-detect occurences of undefined behavior - also enable compiling above!
target_link_libraries(GD "-fsanitize=null") # Uncomment to auto-detect occurences of undefined behavior - also enable compiling above!
target_link_libraries(GD "-fsanitize=return") # Uncomment to auto-detect occurences of undefined behavior - also enable compiling above!
else()
# Production: link time optimizations and full optimization.
target_link_libraries(GD "-O3 -flto")
endif()
# Even if we're building an "executable", prefix it by lib as it's used as a library.
set_target_properties(GD PROPERTIES PREFIX "lib")

View File

@@ -4,9 +4,25 @@ module.exports = function (grunt) {
const fs = require('fs');
const path = require('path');
const isWin = /^win/.test(process.platform);
const isDev = grunt.option('dev') || false;
const useMinGW = grunt.option('use-MinGW') || false;
const possibleVariants = [
'dev',
'debug',
'debug-assertions',
'debug-sanitizers',
];
const variant = grunt.option('variant') || (grunt.option('dev') ? 'dev' : '');
if (variant && possibleVariants.indexOf(variant) === -1) {
console.error(
`Invalid build variant: ${variant}. Possible values are: ${possibleVariants.join(
', '
)}.`
);
process.exit(1);
}
const buildOutputPath = '../Binaries/embuild/GDevelop.js/';
const buildPath = '../Binaries/embuild';
@@ -75,9 +91,7 @@ module.exports = function (grunt) {
...cmakeGeneratorArgs,
'../..',
// Disable link time optimizations for slightly faster build time.
isDev
? '-DDISABLE_EMSCRIPTEN_LINK_OPTIMIZATIONS=TRUE'
: '-DDISABLE_EMSCRIPTEN_LINK_OPTIMIZATIONS=FALSE',
variant ? '-DGDEVELOPJS_BUILD_VARIANT=' + variant : '',
].join(' '),
options: {
execOptions: {
@@ -135,7 +149,7 @@ module.exports = function (grunt) {
cwd: __dirname,
},
},
}
},
},
clean: {
options: { force: true },

View File

@@ -39,7 +39,7 @@ These are the bindings of GDevelop core classes to WebAssembly+JavaScript. This
npm run build # After any C++ changes.
```
-> ⏱ The linking (last step) of the build can be made a few seconds faster by specifying `-- --dev`.
> ⏱ The linking (last step) of the build can be made a few seconds faster, useful for development: `npm run build -- --variant=dev`.
- You can then launch GDevelop 5 that will use your build of GDevelop.js:
@@ -58,6 +58,18 @@ More information in [GDevelop 5 README](https://github.com/4ian/GDevelop/blob/ma
npm test
```
### Debugging
You can build the library with various level of debugging and memory checks.
```bash
npm run build -- --variant=debug # Build with debugging information (useful for stacktraces)
npm run build -- --variant=debug-assertions # Build with assertions and "SAFE_HEAP=1", useful to find memory bugs.
npm run build -- --variant=debug-sanitizers # Build with memory sanitizers. Will be very slow.
```
It's then recommended to run the tests (`npm test`) to check if there are any obvious memory bugs found.
### About the internal steps of compilation
The npm _build_ task:

View File

@@ -16,6 +16,7 @@
"build": "grunt build",
"build-with-MinGW": "grunt build --use-MinGW",
"clean": "grunt clean",
"lint": "node scripts/lint-with-clang-tidy.js",
"test": "jest"
},
"license": "MIT",

View File

@@ -0,0 +1,143 @@
const { spawn } = require('child_process');
const fs = require('fs');
const path = require('path');
const { makeSimplePromisePool } = require('./utils/SimplePromisePool');
const gdevelopRootPath = path.resolve(__dirname, '../../');
const sourcesRootPath = path.join(gdevelopRootPath, 'Core/GDCore');
const excludedPaths = [
'Tools/Localization.cpp', // emscripten code which can't be linted
'Serialization/Serializer.cpp', // Diagnostic that can't be ignored in rapidjson.
];
async function findClangTidy() {
const tryClangTidy = (clangTidyCommandName) =>
new Promise((resolve, reject) => {
const process = spawn(clangTidyCommandName, ['--version'], {
stdio: 'inherit',
});
process.on('error', (error) => {
resolve(false);
});
process.on('close', (code) => {
if (code === 0) {
resolve(true);
} else {
resolve(false);
}
});
});
const hasClangTidy19 = await tryClangTidy('clang-tidy-19');
if (hasClangTidy19) {
return 'clang-tidy-19';
}
const hasClangTidy = await tryClangTidy('clang-tidy');
if (hasClangTidy) {
return 'clang-tidy';
}
return null;
}
function runClangTidy(commandName, filePath) {
return new Promise((resolve, reject) => {
const process = spawn(
commandName,
[
filePath,
`-p=${gdevelopRootPath}/Binaries/embuild/compile_commands.json`,
`-header-filter=".*"`,
`--allow-no-checks`,
`--quiet`,
],
{ stdio: 'inherit' }
);
process.on('error', (error) => {
reject({ hasErrors: false });
});
process.on('close', (code) => {
if (code === 0) {
resolve({ hasErrors: false });
} else {
resolve({ hasErrors: true });
}
});
});
}
// Function to find all files in directory recursively excluding specified paths
function findFiles(directoryPath) {
let results = [];
const list = fs.readdirSync(directoryPath);
list.forEach((file) => {
const filePath = path.resolve(directoryPath, file);
const relativePath = path.relative(sourcesRootPath, filePath);
const stat = fs.statSync(filePath);
if (stat && stat.isDirectory() && !excludedPaths.includes(relativePath)) {
results = results.concat(findFiles(filePath));
} else {
if (
path.extname(filePath) === '.inl' ||
path.basename(filePath) === '.gitignore' ||
excludedPaths.includes(relativePath)
) {
// Ignore .inl files
} else {
results.push(filePath);
}
}
});
return results;
}
// Main function to run clang-tidy
async function main() {
console.log('Checking if clang-tidy is installed and works:');
const clangTidyCommand = await findClangTidy();
if (!clangTidyCommand) {
console.error(`❌ clang-tidy is not installed or not working.`);
process.exit(1);
}
const filesToCheck = findFiles(sourcesRootPath);
// Run clang-tidy on each file.
const filesWithErrors = [];
let fileIndex = 0;
await makeSimplePromisePool(
filesToCheck.map((filePath) => async () => {
const { hasErrors } = await runClangTidy(clangTidyCommand, filePath);
if (hasErrors) {
filesWithErrors.push(filePath);
}
fileIndex++;
if (fileIndex % 10 === 0) {
console.log(
` Checked ${fileIndex} out of ${filesToCheck.length} files.`
);
}
}),
30
);
if (filesWithErrors.length > 0) {
console.error(`❌ clang-tidy found errors in the following files:`);
for (let filePath of filesWithErrors) {
console.error(` - ${filePath}`);
}
process.exit(1);
} else {
console.log(`✅ All files passed clang-tidy checks.`);
}
}
main();

View File

@@ -0,0 +1,24 @@
const makeSimplePromisePool = async function (functions, n) {
return new Promise((resolve) => {
let inProgress = 0,
index = 0;
function helper() {
// base case
if (index >= functions.length) {
if (inProgress === 0) resolve();
return;
}
while (inProgress < n && index < functions.length) {
inProgress++;
functions[index++]().then(() => {
inProgress--;
helper();
});
}
}
helper();
});
};
module.exports = { makeSimplePromisePool };