[zephyr] Allow to override default malloc with sys_heap (#16926)

* [zephyr] Allow to override default malloc with sys_heap

Add a setting that overrides default malloc/calloc/realloc/
free functions with custom implementations based on sys_heap
from Zephyr RTOS.

Update DiagnosticDataProvider methods for obtaining the heap
statistics to use the sys_heap statistics when sys_heap-baed
malloc is used. Additionally, fix the DiagnosticDataProvider
implementation using mallinfo() by taking the maximum heap
size into account.

* Restyled by whitespace

* Restyled by clang-format

* Restyled by gn

* Handle mul overflow

* Add separate switch for wrapping malloc/calloc/realloc/free symbols

* Restyled by gn

Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/config/nrfconnect/chip-module/CMakeLists.txt b/config/nrfconnect/chip-module/CMakeLists.txt
index bdc434f..ede0541 100644
--- a/config/nrfconnect/chip-module/CMakeLists.txt
+++ b/config/nrfconnect/chip-module/CMakeLists.txt
@@ -218,6 +218,7 @@
 chip_gn_arg_bool  ("chip_progress_logging"                  CONFIG_MATTER_LOG_LEVEL GREATER_EQUAL 3)
 chip_gn_arg_bool  ("chip_detail_logging"                    CONFIG_MATTER_LOG_LEVEL GREATER_EQUAL 4)
 chip_gn_arg_bool  ("chip_automation_logging"                "false")
+chip_gn_arg_bool  ("chip_malloc_sys_heap"                   CONFIG_CHIP_MALLOC_SYS_HEAP)
 
 if (CONFIG_CHIP_ROTATING_DEVICE_ID)
     chip_gn_arg_bool("chip_enable_rotating_device_id"          "true")
@@ -305,6 +306,19 @@
     target_link_options(chip INTERFACE -Wl,--whole-archive -lCHIPShell -Wl,--no-whole-archive)
 endif()
 
+if (CONFIG_CHIP_MALLOC_SYS_HEAP_OVERRIDE)
+    target_link_options(chip INTERFACE
+        -Wl,--wrap=malloc
+        -Wl,--wrap=calloc
+        -Wl,--wrap=realloc
+        -Wl,--wrap=free
+        -Wl,--wrap=_malloc_r
+        -Wl,--wrap=_calloc_r
+        -Wl,--wrap=_realloc_r
+        -Wl,--wrap=_free_r
+    )
+endif()
+
 target_link_libraries(chip INTERFACE -Wl,--start-group ${CHIP_LIBRARIES} -Wl,--end-group)
 
 add_dependencies(chip chip-gn)
diff --git a/config/zephyr/Kconfig b/config/zephyr/Kconfig
index 8152bd4..8d88467 100644
--- a/config/zephyr/Kconfig
+++ b/config/zephyr/Kconfig
@@ -175,6 +175,33 @@
 	  precisely operation time in case of device reboot and maximizing flash memory
 	  lifetime.
 
+config CHIP_MALLOC_SYS_HEAP
+	bool "Memory allocator based on Zephyr sys_heap"
+	imply SYS_HEAP_RUNTIME_STATS
+	help
+	  Enable memory allocation functions, imitating with the default malloc,
+	  calloc, realloc and free, based on sys_heap from Zephyr RTOS.
+
+if CHIP_MALLOC_SYS_HEAP
+
+config CHIP_MALLOC_SYS_HEAP_OVERRIDE
+	bool "Override default allocator with custom one based on Zephyr sys_heap"
+	default y
+	help
+	  Replace the default memory allocation functions, such as malloc, calloc,
+	  realloc, free and their reentrant versions, with their counterparts based
+	  on sys_heap from Zephyr RTOS.
+
+config CHIP_MALLOC_SYS_HEAP_SIZE
+	int "Heap size used by memory allocator based on Zephyr sys_heap"
+	default 16384 # 16kB
+	help
+	  This value controls how much of the device RAM is reserved for the heap
+	  used by the memory allocation functions based on sys_heap from Zephyr
+	  RTOS.
+
+endif
+
 config APP_LINK_WITH_CHIP
 	bool "Link 'app' with Connected Home over IP"
 	default y
diff --git a/src/platform/Zephyr/DiagnosticDataProviderImpl.cpp b/src/platform/Zephyr/DiagnosticDataProviderImpl.cpp
index 8d16ac3..c357892 100644
--- a/src/platform/Zephyr/DiagnosticDataProviderImpl.cpp
+++ b/src/platform/Zephyr/DiagnosticDataProviderImpl.cpp
@@ -26,8 +26,10 @@
 #include <lib/support/logging/CHIPLogging.h>
 #include <platform/DiagnosticDataProvider.h>
 #include <platform/Zephyr/DiagnosticDataProviderImpl.h>
+#include <platform/Zephyr/SysHeapMalloc.h>
 
 #include <drivers/hwinfo.h>
+#include <sys/util.h>
 
 #ifdef CONFIG_MCUBOOT_IMG_MANAGER
 #include <dfu/mcuboot.h>
@@ -35,6 +37,19 @@
 
 #include <malloc.h>
 
+#if CHIP_DEVICE_CONFIG_HEAP_STATISTICS_MALLINFO
+
+#ifdef CONFIG_NEWLIB_LIBC_ALIGNED_HEAP_SIZE
+const size_t kMaxHeapSize = CONFIG_NEWLIB_LIBC_ALIGNED_HEAP_SIZE;
+#elif defined(CONFIG_NEWLIB_LIBC)
+extern char _end[];
+const size_t kMaxHeapSize = CONFIG_SRAM_BASE_ADDRESS + KB(CONFIG_SRAM_SIZE) - POINTER_TO_UINT(_end);
+#else
+#pragma error "Maximum heap size is required but unknown"
+#endif
+
+#endif
+
 namespace chip {
 namespace DeviceLayer {
 
@@ -101,11 +116,15 @@
 
 CHIP_ERROR DiagnosticDataProviderImpl::GetCurrentHeapFree(uint64_t & currentHeapFree)
 {
-#ifdef CONFIG_NEWLIB_LIBC
-    // This will return the amount of memory which has been allocated from the system, but is not
-    // used right now. Ideally, this value should be increased by the amount of memory which can
-    // be allocated from the system, but Zephyr does not expose that number.
-    currentHeapFree = mallinfo().fordblks;
+#ifdef CONFIG_CHIP_MALLOC_SYS_HEAP
+    Malloc::Stats stats;
+    ReturnErrorOnFailure(Malloc::GetStats(stats));
+
+    currentHeapFree = stats.free;
+    return CHIP_NO_ERROR;
+#elif CHIP_DEVICE_CONFIG_HEAP_STATISTICS_MALLINFO
+    const auto stats = mallinfo();
+    currentHeapFree  = kMaxHeapSize - stats.arena + stats.fordblks;
     return CHIP_NO_ERROR;
 #else
     return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE;
@@ -114,7 +133,13 @@
 
 CHIP_ERROR DiagnosticDataProviderImpl::GetCurrentHeapUsed(uint64_t & currentHeapUsed)
 {
-#ifdef CONFIG_NEWLIB_LIBC
+#ifdef CONFIG_CHIP_MALLOC_SYS_HEAP
+    Malloc::Stats stats;
+    ReturnErrorOnFailure(Malloc::GetStats(stats));
+
+    currentHeapUsed = stats.used;
+    return CHIP_NO_ERROR;
+#elif CHIP_DEVICE_CONFIG_HEAP_STATISTICS_MALLINFO
     currentHeapUsed = mallinfo().uordblks;
     return CHIP_NO_ERROR;
 #else
@@ -124,7 +149,14 @@
 
 CHIP_ERROR DiagnosticDataProviderImpl::GetCurrentHeapHighWatermark(uint64_t & currentHeapHighWatermark)
 {
-#ifdef CONFIG_NEWLIB_LIBC
+#ifdef CONFIG_CHIP_MALLOC_SYS_HEAP
+    Malloc::Stats stats;
+    ReturnErrorOnFailure(Malloc::GetStats(stats));
+
+    // TODO: use the maximum usage once that is implemented in Zephyr
+    currentHeapHighWatermark = stats.used;
+    return CHIP_NO_ERROR;
+#elif CHIP_DEVICE_CONFIG_HEAP_STATISTICS_MALLINFO
     // ARM newlib does not provide a way to obtain the peak heap usage, so for now just return
     // the amount of memory allocated from the system which should be an upper bound of the peak
     // usage provided that the heap is not very fragmented.
diff --git a/src/platform/Zephyr/SysHeapMalloc.cpp b/src/platform/Zephyr/SysHeapMalloc.cpp
new file mode 100644
index 0000000..68f8d21
--- /dev/null
+++ b/src/platform/Zephyr/SysHeapMalloc.cpp
@@ -0,0 +1,180 @@
+/*
+ *
+ *    Copyright (c) 2022 Project CHIP Authors
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "SysHeapMalloc.h"
+
+#include <lib/support/CodeUtils.h>
+#include <system/SystemError.h>
+
+extern "C" {
+#include <init.h>
+#include <sys/math_extras.h>
+#include <sys/mutex.h>
+#include <sys/sys_heap.h>
+}
+
+#include <cstdint>
+#include <cstring>
+
+// Construct name of the given function wrapped with the `--wrap=symbol` GCC option.
+#define WRAP(f) __wrap_##f
+
+using namespace chip;
+
+namespace {
+
+// Alignment of memory blocks returned by malloc.
+// Choose the value that guarantees that the returned memory blocks are castable to all built-in types.
+constexpr size_t kMallocAlignment = alignof(long long);
+
+uint8_t sHeapMemory[CONFIG_CHIP_MALLOC_SYS_HEAP_SIZE] alignas(kMallocAlignment);
+sys_heap sHeap;
+SYS_MUTEX_DEFINE(sLock);
+
+// RAII helper for synchronizing access to the common heap.
+class LockGuard
+{
+public:
+    LockGuard() : mStatus(sys_mutex_lock(&sLock, K_FOREVER)) {}
+    ~LockGuard();
+
+    bool Locked() const { return mStatus == 0; }
+    CHIP_ERROR Error() const { return System::MapErrorZephyr(mStatus); }
+
+private:
+    const int mStatus;
+};
+
+LockGuard::~LockGuard()
+{
+    if (mStatus == 0)
+    {
+        sys_mutex_unlock(&sLock);
+    }
+}
+
+int initHeap(const device *)
+{
+    sys_heap_init(&sHeap, sHeapMemory, sizeof(sHeapMemory));
+    return 0;
+}
+
+} // namespace
+
+// Initialize the heap in the POST_KERNEL phase to make sure that it is ready even before
+// C++ static constructors are called (which happens prior to the APPLICATION initialization phase).
+SYS_INIT(initHeap, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
+
+namespace chip {
+namespace DeviceLayer {
+namespace Malloc {
+
+void * Malloc(size_t size)
+{
+    LockGuard lockGuard;
+
+    return lockGuard.Locked() ? sys_heap_aligned_alloc(&sHeap, kMallocAlignment, size) : nullptr;
+}
+
+void * Calloc(size_t num, size_t size)
+{
+    size_t totalSize;
+
+    if (size_mul_overflow(num, size, &totalSize))
+    {
+        return nullptr;
+    }
+
+    void * mem = malloc(totalSize);
+
+    if (mem)
+    {
+        memset(mem, 0, totalSize);
+    }
+
+    return mem;
+}
+
+void * Realloc(void * mem, size_t size)
+{
+    LockGuard lockGuard;
+
+    return lockGuard.Locked() ? sys_heap_aligned_realloc(&sHeap, mem, kMallocAlignment, size) : nullptr;
+}
+
+void Free(void * mem)
+{
+    LockGuard lockGuard;
+
+    VerifyOrReturn(lockGuard.Locked());
+    sys_heap_free(&sHeap, mem);
+}
+
+#ifdef CONFIG_SYS_HEAP_RUNTIME_STATS
+
+CHIP_ERROR GetStats(Stats & stats)
+{
+    LockGuard lockGuard;
+    ReturnErrorOnFailure(lockGuard.Error());
+
+    sys_heap_runtime_stats sysHeapStats;
+    ReturnErrorOnFailure(System::MapErrorZephyr(sys_heap_runtime_stats_get(&sHeap, &sysHeapStats)));
+
+    stats.free = sysHeapStats.free_bytes;
+    stats.used = sysHeapStats.allocated_bytes;
+
+    return CHIP_NO_ERROR;
+}
+
+#endif // CONFIG_SYS_HEAP_RUNTIME_STATS
+
+} // namespace Malloc
+} // namespace DeviceLayer
+} // namespace chip
+
+#ifdef CONFIG_CHIP_MALLOC_SYS_HEAP_OVERRIDE
+
+extern "C" {
+
+void * WRAP(malloc)(size_t size) __attribute((alias("_ZN4chip11DeviceLayer6Malloc6MallocEj")));
+void * WRAP(calloc)(size_t num, size_t size) __attribute((alias("_ZN4chip11DeviceLayer6Malloc6CallocEjj")));
+void * WRAP(realloc)(void * mem, size_t size) __attribute((alias("_ZN4chip11DeviceLayer6Malloc7ReallocEPvj")));
+void WRAP(free)(void * mem) __attribute((alias("_ZN4chip11DeviceLayer6Malloc4FreeEPv")));
+
+void * WRAP(_malloc_r)(_reent *, size_t size)
+{
+    return WRAP(malloc)(size);
+}
+
+void * WRAP(_calloc_r)(_reent *, size_t num, size_t size)
+{
+    return WRAP(calloc)(num, size);
+}
+
+void * WRAP(_realloc_r)(_reent *, void * mem, size_t size)
+{
+    return WRAP(realloc)(mem, size);
+}
+
+void WRAP(_free_r)(_reent *, void * mem)
+{
+    WRAP(free)(mem);
+}
+
+} // extern "C"
+
+#endif // CONFIG_CHIP_MALLOC_SYS_HEAP_OVERRIDE
diff --git a/src/platform/Zephyr/SysHeapMalloc.h b/src/platform/Zephyr/SysHeapMalloc.h
new file mode 100644
index 0000000..3ee6f50
--- /dev/null
+++ b/src/platform/Zephyr/SysHeapMalloc.h
@@ -0,0 +1,40 @@
+/*
+ *
+ *    Copyright (c) 2022 Project CHIP Authors
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include <cstddef>
+
+#include <lib/core/CHIPError.h>
+
+namespace chip {
+namespace DeviceLayer {
+namespace Malloc {
+
+struct Stats
+{
+    size_t free;
+    size_t used;
+};
+
+void * Malloc(size_t size);
+void * Calloc(size_t num, size_t size);
+void * Realloc(void * mem, size_t size);
+void Free(void * mem);
+CHIP_ERROR GetStats(Stats & stats);
+
+} // namespace Malloc
+} // namespace DeviceLayer
+} // namespace chip
diff --git a/src/platform/nrfconnect/BUILD.gn b/src/platform/nrfconnect/BUILD.gn
index bf342e9..b587734 100644
--- a/src/platform/nrfconnect/BUILD.gn
+++ b/src/platform/nrfconnect/BUILD.gn
@@ -15,6 +15,7 @@
 import("//build_overrides/chip.gni")
 
 import("${chip_root}/src/platform/device.gni")
+import("${chip_root}/src/platform/nrfconnect/args.gni")
 
 assert(chip_device_platform == "nrfconnect")
 
@@ -28,6 +29,7 @@
     "../Zephyr/KeyValueStoreManagerImpl.cpp",
     "../Zephyr/Logging.cpp",
     "../Zephyr/PlatformManagerImpl.cpp",
+    "../Zephyr/SysHeapMalloc.h",
     "../Zephyr/SystemTimeSupport.cpp",
     "../Zephyr/ZephyrConfig.cpp",
     "../Zephyr/ZephyrConfig.h",
@@ -77,4 +79,8 @@
       "OTAImageProcessorImpl.h",
     ]
   }
+
+  if (chip_malloc_sys_heap) {
+    sources += [ "../Zephyr/SysHeapMalloc.cpp" ]
+  }
 }
diff --git a/src/platform/nrfconnect/CHIPDevicePlatformConfig.h b/src/platform/nrfconnect/CHIPDevicePlatformConfig.h
index 3748d3b..c3d87d1 100644
--- a/src/platform/nrfconnect/CHIPDevicePlatformConfig.h
+++ b/src/platform/nrfconnect/CHIPDevicePlatformConfig.h
@@ -73,6 +73,13 @@
 #define CHIP_DEVICE_CONFIG_SERVER_SHUTDOWN_ACTIONS_SLEEP_MS 10
 #endif // CHIP_DEVICE_CONFIG_SERVER_SHUTDOWN_ACTIONS_SLEEP_MS
 
+#ifndef CHIP_DEVICE_CONFIG_HEAP_STATISTICS_MALLINFO
+#if !defined(CONFIG_CHIP_MALLOC_SYS_HEAP) && defined(CONFIG_NEWLIB_LIBC)
+/// Use mallinfo() to obtain the heap usage statistics exposed by SoftwareDiagnostics cluster attributes.
+#define CHIP_DEVICE_CONFIG_HEAP_STATISTICS_MALLINFO 1
+#endif // !defined(CONFIG_CHIP_MALLOC_SYS_HEAP) && defined(CONFIG_NEWLIB_LIBC)
+#endif // CHIP_DEVICE_CONFIG_HEAP_STATISTICS_MALLINFO
+
 // ========== Platform-specific Configuration Overrides =========
 
 #ifndef CHIP_DEVICE_CONFIG_CHIP_TASK_PRIORITY
diff --git a/src/platform/nrfconnect/args.gni b/src/platform/nrfconnect/args.gni
index 06a372e..64e4dcb 100644
--- a/src/platform/nrfconnect/args.gni
+++ b/src/platform/nrfconnect/args.gni
@@ -12,6 +12,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-chip_device_platform = "nrfconnect"
-
-chip_inet_config_enable_ipv4 = false
+declare_args() {
+  chip_malloc_sys_heap = false
+}
diff --git a/src/platform/telink/CHIPDevicePlatformConfig.h b/src/platform/telink/CHIPDevicePlatformConfig.h
index 7498c30..61de429 100644
--- a/src/platform/telink/CHIPDevicePlatformConfig.h
+++ b/src/platform/telink/CHIPDevicePlatformConfig.h
@@ -44,6 +44,13 @@
 #define CHIP_DEVICE_CONFIG_SETTINGS_KEY "mt"
 #endif // CHIP_DEVICE_CONFIG_SETTINGS_KEY
 
+#ifndef CHIP_DEVICE_CONFIG_HEAP_STATISTICS_MALLINFO
+#if !defined(CONFIG_CHIP_MALLOC_SYS_HEAP) && defined(CONFIG_NEWLIB_LIBC)
+/// Use mallinfo() to obtain the heap usage statistics exposed by SoftwareDiagnostics cluster attributes.
+#define CHIP_DEVICE_CONFIG_HEAP_STATISTICS_MALLINFO 1
+#endif // !defined(CONFIG_CHIP_MALLOC_SYS_HEAP) && defined(CONFIG_NEWLIB_LIBC)
+#endif // CHIP_DEVICE_CONFIG_HEAP_STATISTICS_MALLINFO
+
 // ========== Platform-specific Configuration Overrides =========
 
 #ifndef CHIP_DEVICE_CONFIG_CHIP_TASK_PRIORITY