From 58ba2668c22de5f15398a84785a0d81324754226 Mon Sep 17 00:00:00 2001 From: Florian Rival Date: Mon, 14 Oct 2024 12:06:42 +0200 Subject: [PATCH] Add clang-tidy and builds with assertions/memory sanitizers for libGD.js (#7051) Only show in developer changelog --- .circleci/config.yml | 76 +++++++++- .clang-tidy | 4 + CMakeLists.txt | 8 +- Core/GDCore/Events/Builtin/WhileEvent.h | 2 - .../Events/Parsers/ExpressionParser2Node.h | 4 +- .../Extensions/Metadata/BehaviorMetadata.cpp | 3 +- .../Extensions/Metadata/BehaviorMetadata.h | 2 +- .../Extensions/Metadata/EffectMetadata.h | 8 +- .../Extensions/Metadata/EventMetadata.cpp | 3 +- .../Extensions/Metadata/EventMetadata.h | 2 +- .../Extensions/Metadata/MetadataProvider.h | 4 +- Core/GDCore/Extensions/PlatformExtension.h | 87 +++++------ Core/GDCore/Extensions/PlatformExtension.inl | 4 + .../IDE/Events/ExpressionCompletionFinder.h | 16 +- .../IDE/Events/ExpressionsParameterMover.h | 4 +- Core/GDCore/IDE/ExportedDependencyResolver.h | 1 + .../Project/CustomObjectConfiguration.h | 6 +- Core/GDCore/Project/ExternalEvents.cpp | 6 +- Core/GDCore/Project/ExternalEvents.h | 32 ---- Core/GDCore/Project/Layout.h | 18 +-- Core/GDCore/Project/ObjectFolderOrObject.h | 2 +- Core/GDCore/Project/Project.cpp | 2 +- Core/GDCore/Project/Project.h | 87 ++++++----- Core/GDCore/Project/ResourcesManager.h | 4 +- Core/GDCore/Project/Variable.h | 6 +- Core/GDCore/Project/VariablesContainer.h | 2 +- Core/GDCore/Serialization/SerializerElement.h | 4 +- .../Serialization/rapidjson/.clang-tidy | 2 + Core/GDCore/String.cpp | 4 + Core/GDCore/String.h | 4 + Core/GDCore/Tools/PolymorphicClone.h | 1 + .../GDCore/Tools/SerializableWithNameList.inl | 1 + Core/GDCore/Tools/SystemStats.cpp | 5 + Core/GDCore/Tools/UUID/.clang-tidy | 2 + Core/GDCore/Utf8/.clang-tidy | 2 + GDevelop.js/Bindings/prejs.js | 9 ++ GDevelop.js/CMakeLists.txt | 79 ++++++---- GDevelop.js/Gruntfile.js | 24 ++- GDevelop.js/README.md | 14 +- GDevelop.js/package.json | 1 + GDevelop.js/scripts/lint-with-clang-tidy.js | 143 ++++++++++++++++++ .../scripts/utils/SimplePromisePool.js | 24 +++ 42 files changed, 507 insertions(+), 205 deletions(-) create mode 100644 .clang-tidy create mode 100644 Core/GDCore/Serialization/rapidjson/.clang-tidy create mode 100644 Core/GDCore/Tools/UUID/.clang-tidy create mode 100644 Core/GDCore/Utf8/.clang-tidy create mode 100644 GDevelop.js/Bindings/prejs.js create mode 100644 GDevelop.js/scripts/lint-with-clang-tidy.js create mode 100644 GDevelop.js/scripts/utils/SimplePromisePool.js diff --git a/.circleci/config.yml b/.circleci/config.yml index c9364387c..5d6471103 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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: diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 000000000..67d53f5c0 --- /dev/null +++ b/.clang-tidy @@ -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 \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 9fb9304d0..fe858817e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/Core/GDCore/Events/Builtin/WhileEvent.h b/Core/GDCore/Events/Builtin/WhileEvent.h index 593c71b21..e4cdadf46 100644 --- a/Core/GDCore/Events/Builtin/WhileEvent.h +++ b/Core/GDCore/Events/Builtin/WhileEvent.h @@ -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; diff --git a/Core/GDCore/Events/Parsers/ExpressionParser2Node.h b/Core/GDCore/Events/Parsers/ExpressionParser2Node.h index 621def5f9..402ea59b1 100644 --- a/Core/GDCore/Events/Parsers/ExpressionParser2Node.h +++ b/Core/GDCore/Events/Parsers/ExpressionParser2Node.h @@ -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; }; /** diff --git a/Core/GDCore/Extensions/Metadata/BehaviorMetadata.cpp b/Core/GDCore/Extensions/Metadata/BehaviorMetadata.cpp index d163588c7..15fd36bca 100644 --- a/Core/GDCore/Extensions/Metadata/BehaviorMetadata.cpp +++ b/Core/GDCore/Extensions/Metadata/BehaviorMetadata.cpp @@ -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_)); diff --git a/Core/GDCore/Extensions/Metadata/BehaviorMetadata.h b/Core/GDCore/Extensions/Metadata/BehaviorMetadata.h index f24ebabfb..5a5f536fd 100644 --- a/Core/GDCore/Extensions/Metadata/BehaviorMetadata.h +++ b/Core/GDCore/Extensions/Metadata/BehaviorMetadata.h @@ -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 instance; diff --git a/Core/GDCore/Extensions/Metadata/EffectMetadata.h b/Core/GDCore/Extensions/Metadata/EffectMetadata.h index 5dcc21e9c..f22582dbd 100644 --- a/Core/GDCore/Extensions/Metadata/EffectMetadata.h +++ b/Core/GDCore/Extensions/Metadata/EffectMetadata.h @@ -185,10 +185,10 @@ class GD_CORE_API EffectMetadata { gd::String fullname; gd::String description; std::vector includeFiles; - bool isMarkedAsNotWorkingForObjects; - bool isMarkedAsOnlyWorkingFor2D; - bool isMarkedAsOnlyWorkingFor3D; - bool isMarkedAsUnique; + bool isMarkedAsNotWorkingForObjects = false; + bool isMarkedAsOnlyWorkingFor2D = false; + bool isMarkedAsOnlyWorkingFor3D = false; + bool isMarkedAsUnique = false; std::map properties; }; diff --git a/Core/GDCore/Extensions/Metadata/EventMetadata.cpp b/Core/GDCore/Extensions/Metadata/EventMetadata.cpp index 5951e4565..d89cc8464 100644 --- a/Core/GDCore/Extensions/Metadata/EventMetadata.cpp +++ b/Core/GDCore/Extensions/Metadata/EventMetadata.cpp @@ -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_); } diff --git a/Core/GDCore/Extensions/Metadata/EventMetadata.h b/Core/GDCore/Extensions/Metadata/EventMetadata.h index 2aee848b9..5cf87f22d 100644 --- a/Core/GDCore/Extensions/Metadata/EventMetadata.h +++ b/Core/GDCore/Extensions/Metadata/EventMetadata.h @@ -83,7 +83,7 @@ class GD_CORE_API EventMetadata { gd::String group; std::shared_ptr instance; - bool hasCustomCodeGenerator; + bool hasCustomCodeGenerator = false; std::function diff --git a/Core/GDCore/Extensions/Metadata/MetadataProvider.h b/Core/GDCore/Extensions/Metadata/MetadataProvider.h index 5abb3e25b..ec292a23e 100644 --- a/Core/GDCore/Extensions/Metadata/MetadataProvider.h +++ b/Core/GDCore/Extensions/Metadata/MetadataProvider.h @@ -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 diff --git a/Core/GDCore/Extensions/PlatformExtension.h b/Core/GDCore/Extensions/PlatformExtension.h index 985603c02..9295ed063 100644 --- a/Core/GDCore/Extensions/PlatformExtension.h +++ b/Core/GDCore/Extensions/PlatformExtension.h @@ -40,8 +40,7 @@ class Object; class ObjectConfiguration; } // namespace gd -typedef std::function()> - CreateFunPtr; +typedef std::function()> 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 instance); + gd::ObjectMetadata& AddObject( + const gd::String& name_, + const gd::String& fullname_, + const gd::String& description_, + const gd::String& icon_, + std::shared_ptr 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. diff --git a/Core/GDCore/Extensions/PlatformExtension.inl b/Core/GDCore/Extensions/PlatformExtension.inl index 5457da715..82743bde9 100644 --- a/Core/GDCore/Extensions/PlatformExtension.inl +++ b/Core/GDCore/Extensions/PlatformExtension.inl @@ -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 \ No newline at end of file diff --git a/Core/GDCore/IDE/Events/ExpressionCompletionFinder.h b/Core/GDCore/IDE/Events/ExpressionCompletionFinder.h index 6eda7011e..84737572a 100644 --- a/Core/GDCore/IDE/Events/ExpressionCompletionFinder.h +++ b/Core/GDCore/IDE/Events/ExpressionCompletionFinder.h @@ -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; diff --git a/Core/GDCore/IDE/Events/ExpressionsParameterMover.h b/Core/GDCore/IDE/Events/ExpressionsParameterMover.h index bfc1e5932..3975d56b4 100644 --- a/Core/GDCore/IDE/Events/ExpressionsParameterMover.h +++ b/Core/GDCore/IDE/Events/ExpressionsParameterMover.h @@ -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; }; diff --git a/Core/GDCore/IDE/ExportedDependencyResolver.h b/Core/GDCore/IDE/ExportedDependencyResolver.h index c7173bda1..d5b76a96b 100644 --- a/Core/GDCore/IDE/ExportedDependencyResolver.h +++ b/Core/GDCore/IDE/ExportedDependencyResolver.h @@ -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 { diff --git a/Core/GDCore/Project/CustomObjectConfiguration.h b/Core/GDCore/Project/CustomObjectConfiguration.h index fd4cb17e6..acc649557 100644 --- a/Core/GDCore/Project/CustomObjectConfiguration.h +++ b/Core/GDCore/Project/CustomObjectConfiguration.h @@ -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 unfoldedChildren; - bool isMarkedAsOverridingEventsBasedObjectChildrenConfiguration; + bool isMarkedAsOverridingEventsBasedObjectChildrenConfiguration = false; mutable std::map> childObjectConfigurations; static gd::ObjectConfiguration badObjectConfiguration; diff --git a/Core/GDCore/Project/ExternalEvents.cpp b/Core/GDCore/Project/ExternalEvents.cpp index 92935770d..9d5d13973 100644 --- a/Core/GDCore/Project/ExternalEvents.cpp +++ b/Core/GDCore/Project/ExternalEvents.cpp @@ -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")); } diff --git a/Core/GDCore/Project/ExternalEvents.h b/Core/GDCore/Project/ExternalEvents.h index f186ae73b..6a29cba7f 100644 --- a/Core/GDCore/Project/ExternalEvents.h +++ b/Core/GDCore/Project/ExternalEvents.h @@ -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, - gd::String, - bool> { - bool operator()(const std::unique_ptr& externalEvents, - gd::String name) const { - return externalEvents->GetName() == name; - } -}; - } // namespace gd #endif // GDCORE_EXTERNALEVENTS_H diff --git a/Core/GDCore/Project/Layout.h b/Core/GDCore/Project/Layout.h index f6cbfd470..9d2d21611 100644 --- a/Core/GDCore/Project/Layout.h +++ b/Core/GDCore/Project/Layout.h @@ -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> 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 diff --git a/Core/GDCore/Project/ObjectFolderOrObject.h b/Core/GDCore/Project/ObjectFolderOrObject.h index b6dc9ab2c..51869511f 100644 --- a/Core/GDCore/Project/ObjectFolderOrObject.h +++ b/Core/GDCore/Project/ObjectFolderOrObject.h @@ -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: diff --git a/Core/GDCore/Project/Project.cpp b/Core/GDCore/Project/Project.cpp index 77849455f..236c0a866 100644 --- a/Core/GDCore/Project/Project.cpp +++ b/Core/GDCore/Project/Project.cpp @@ -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()), diff --git a/Core/GDCore/Project/Project.h b/Core/GDCore/Project/Project.h index aff98d3d7..e91bdd2b2 100644 --- a/Core/GDCore/Project/Project.h +++ b/Core/GDCore/Project/Project.h @@ -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 > 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 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 > 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 authorUsernames; ///< Game author usernames, from GDevelop users DB. std::vector 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 > 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 diff --git a/Core/GDCore/Project/ResourcesManager.h b/Core/GDCore/Project/ResourcesManager.h index 97b999b4b..bc6d98c13 100644 --- a/Core/GDCore/Project/ResourcesManager.h +++ b/Core/GDCore/Project/ResourcesManager.h @@ -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; }; diff --git a/Core/GDCore/Project/Variable.h b/Core/GDCore/Project/Variable.h index 43ffd9756..13e64656c 100644 --- a/Core/GDCore/Project/Variable.h +++ b/Core/GDCore/Project/Variable.h @@ -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> children; ///< Children, when the variable is considered as a structure. diff --git a/Core/GDCore/Project/VariablesContainer.h b/Core/GDCore/Project/VariablesContainer.h index 70c7bb6c6..2cd526185 100644 --- a/Core/GDCore/Project/VariablesContainer.h +++ b/Core/GDCore/Project/VariablesContainer.h @@ -187,7 +187,7 @@ class GD_CORE_API VariablesContainer { ///@} private: - SourceType sourceType; + SourceType sourceType = Unknown; std::vector>> variables; mutable gd::String persistentUuid; ///< A persistent random version 4 UUID, ///< useful for computing changesets. diff --git a/Core/GDCore/Serialization/SerializerElement.h b/Core/GDCore/Serialization/SerializerElement.h index 5ec67f245..40614eed2 100644 --- a/Core/GDCore/Serialization/SerializerElement.h +++ b/Core/GDCore/Serialization/SerializerElement.h @@ -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 attributes; std::vector > > 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 diff --git a/Core/GDCore/Serialization/rapidjson/.clang-tidy b/Core/GDCore/Serialization/rapidjson/.clang-tidy new file mode 100644 index 000000000..a928ddfc3 --- /dev/null +++ b/Core/GDCore/Serialization/rapidjson/.clang-tidy @@ -0,0 +1,2 @@ +# Disable all checks in this folder. +Checks: '-*' \ No newline at end of file diff --git a/Core/GDCore/String.cpp b/Core/GDCore/String.cpp index 5a40f46a3..f97d23400 100644 --- a/Core/GDCore/String.cpp +++ b/Core/GDCore/String.cpp @@ -4,6 +4,8 @@ * This project is released under the MIT License. */ +// NOLINTBEGIN + #include "GDCore/String.h" #include @@ -825,3 +827,5 @@ bool GD_CORE_API CaseInsensitiveEquiv( const String &lhs, const String &rhs, boo } } + +// NOLINTEND diff --git a/Core/GDCore/String.h b/Core/GDCore/String.h index ef08b9b29..6bd1bca84 100644 --- a/Core/GDCore/String.h +++ b/Core/GDCore/String.h @@ -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 \ No newline at end of file diff --git a/Core/GDCore/Tools/PolymorphicClone.h b/Core/GDCore/Tools/PolymorphicClone.h index 26b9cc01d..beadda914 100644 --- a/Core/GDCore/Tools/PolymorphicClone.h +++ b/Core/GDCore/Tools/PolymorphicClone.h @@ -2,6 +2,7 @@ #define GD_CORE_POLYMORPHICCLONE_H #include +#include namespace gd { diff --git a/Core/GDCore/Tools/SerializableWithNameList.inl b/Core/GDCore/Tools/SerializableWithNameList.inl index 998c594de..4f9ffd48d 100644 --- a/Core/GDCore/Tools/SerializableWithNameList.inl +++ b/Core/GDCore/Tools/SerializableWithNameList.inl @@ -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 #include "GDCore/Serialization/SerializerElement.h" #include "GDCore/String.h" #include "GDCore/Tools/MakeUnique.h" diff --git a/Core/GDCore/Tools/SystemStats.cpp b/Core/GDCore/Tools/SystemStats.cpp index 2dd4cb67f..0f792a1ba 100644 --- a/Core/GDCore/Tools/SystemStats.cpp +++ b/Core/GDCore/Tools/SystemStats.cpp @@ -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 \ No newline at end of file diff --git a/Core/GDCore/Tools/UUID/.clang-tidy b/Core/GDCore/Tools/UUID/.clang-tidy new file mode 100644 index 000000000..a928ddfc3 --- /dev/null +++ b/Core/GDCore/Tools/UUID/.clang-tidy @@ -0,0 +1,2 @@ +# Disable all checks in this folder. +Checks: '-*' \ No newline at end of file diff --git a/Core/GDCore/Utf8/.clang-tidy b/Core/GDCore/Utf8/.clang-tidy new file mode 100644 index 000000000..a928ddfc3 --- /dev/null +++ b/Core/GDCore/Utf8/.clang-tidy @@ -0,0 +1,2 @@ +# Disable all checks in this folder. +Checks: '-*' \ No newline at end of file diff --git a/GDevelop.js/Bindings/prejs.js b/GDevelop.js/Bindings/prejs.js new file mode 100644 index 000000000..aa0e241e9 --- /dev/null +++ b/GDevelop.js/Bindings/prejs.js @@ -0,0 +1,9 @@ +// If running under ASAN, disable the "container overflow" checks because of false positives +// with std::vector. 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; \ No newline at end of file diff --git a/GDevelop.js/CMakeLists.txt b/GDevelop.js/CMakeLists.txt index f1234bd47..4ace410de 100644 --- a/GDevelop.js/CMakeLists.txt +++ b/GDevelop.js/CMakeLists.txt @@ -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") diff --git a/GDevelop.js/Gruntfile.js b/GDevelop.js/Gruntfile.js index 62dc69a93..b5976f462 100644 --- a/GDevelop.js/Gruntfile.js +++ b/GDevelop.js/Gruntfile.js @@ -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 }, diff --git a/GDevelop.js/README.md b/GDevelop.js/README.md index 5ebe6ed46..3dd480cb0 100644 --- a/GDevelop.js/README.md +++ b/GDevelop.js/README.md @@ -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: diff --git a/GDevelop.js/package.json b/GDevelop.js/package.json index a34a913a5..cb621b2b2 100644 --- a/GDevelop.js/package.json +++ b/GDevelop.js/package.json @@ -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", diff --git a/GDevelop.js/scripts/lint-with-clang-tidy.js b/GDevelop.js/scripts/lint-with-clang-tidy.js new file mode 100644 index 000000000..110d40947 --- /dev/null +++ b/GDevelop.js/scripts/lint-with-clang-tidy.js @@ -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(); diff --git a/GDevelop.js/scripts/utils/SimplePromisePool.js b/GDevelop.js/scripts/utils/SimplePromisePool.js new file mode 100644 index 000000000..df252201f --- /dev/null +++ b/GDevelop.js/scripts/utils/SimplePromisePool.js @@ -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 };