[libc-commits] [libc] [libc][stdio] Add support for the %m modifier (PR #218312)

via libc-commits libc-commits at lists.llvm.org
Tue Aug 25 17:57:45 PDT 2026


https://github.com/afnrow updated https://github.com/llvm/llvm-project/pull/218312

>From 18114006584b959928a829a4f38af08ded970834 Mon Sep 17 00:00:00 2001
From: yahia ahmed <yahia.a.abdrabou at gmail.com>
Date: Mon, 24 Aug 2026 07:00:01 +0300
Subject: [PATCH] [libc][stdio] Add support for the %m modifier

Add support for the %m modifier in scanf by allocating memory
and recursively allocate more till the object is.
---
 libc/config/config.json                       |  4 ++
 libc/src/stdio/scanf_core/CMakeLists.txt      |  4 ++
 libc/src/stdio/scanf_core/parser.h            | 11 +--
 libc/src/stdio/scanf_core/string_converter.h  | 67 +++++++++++++++++--
 libc/test/src/stdio/scanf_core/CMakeLists.txt |  1 +
 5 files changed, 77 insertions(+), 10 deletions(-)

diff --git a/libc/config/config.json b/libc/config/config.json
index fd7784d3d3e55..9aaf56b2d8c5d 100644
--- a/libc/config/config.json
+++ b/libc/config/config.json
@@ -77,6 +77,10 @@
     "LIBC_CONF_SCANF_PROVIDE_ISOC99_ALIASES": {
       "value": false,
       "doc": "Add __isoc99_* aliases for scanf's functions."
+    },
+    "LIBC_CONF_SCANF_DISABLE_ALLOCATION": {
+      "value": false,
+      "doc": "Disable %m flag for allocating heap memory for the caller as per POSIX"
     }
   },
   "str_to_float": {
diff --git a/libc/src/stdio/scanf_core/CMakeLists.txt b/libc/src/stdio/scanf_core/CMakeLists.txt
index fd68f5d21ae7f..47eef3b9badb6 100644
--- a/libc/src/stdio/scanf_core/CMakeLists.txt
+++ b/libc/src/stdio/scanf_core/CMakeLists.txt
@@ -4,6 +4,9 @@ endif()
 if(LIBC_CONF_SCANF_DISABLE_INDEX_MODE)
   libc_add_definition(scanf_config_copts "LIBC_COPT_SCANF_DISABLE_INDEX_MODE")
 endif()
+if(LIBC_CONF_SCANF_DISABLE_ALLOCATION)
+  libc_add_definition(scanf_config_copts "LIBC_COPT_SCANF_DISABLE_ALLOCATION")
+endif()
 if(scanf_config_copts)
   list(PREPEND scanf_config_copts "COMPILE_OPTIONS")
 endif()
@@ -105,6 +108,7 @@ add_header_library(
     libc.src.__support.CPP.limits
     libc.src.__support.char_vector
     libc.src.__support.str_to_float
+    libc.src.__support.CPP.new
     ${use_system_file}
 )
 
diff --git a/libc/src/stdio/scanf_core/parser.h b/libc/src/stdio/scanf_core/parser.h
index 1e2f26e0d3fdd..01d477f9f7789 100644
--- a/libc/src/stdio/scanf_core/parser.h
+++ b/libc/src/stdio/scanf_core/parser.h
@@ -81,11 +81,12 @@ template <typename ArgProvider> class Parser {
         cur_pos = cur_pos + static_cast<size_t>(result.parsed_len);
       }
 
-      // TODO(michaelrj): add posix allocate flag support.
-      // if (str[cur_pos] == 'm') {
-      //   ++cur_pos;
-      //   section.flags = FormatFlags::ALLOCATE;
-      // }
+#ifndef LIBC_COPT_SCANF_DISABLE_ALLOCATION
+      if (str[cur_pos] == 'm') {
+        ++cur_pos;
+        section.flags = static_cast<FormatFlags>(FormatFlags::ALLOCATE);
+      }
+#endif
 
       LengthModifier lm = parse_length_modifier(&cur_pos);
       section.length_modifier = lm;
diff --git a/libc/src/stdio/scanf_core/string_converter.h b/libc/src/stdio/scanf_core/string_converter.h
index 3879f8c995899..79d39b55ec16a 100644
--- a/libc/src/stdio/scanf_core/string_converter.h
+++ b/libc/src/stdio/scanf_core/string_converter.h
@@ -10,13 +10,19 @@
 #define LLVM_LIBC_SRC_STDIO_SCANF_CORE_STRING_CONVERTER_H
 
 #include "src/__support/CPP/limits.h"
+#include "src/__support/CPP/new.h"
+#include "src/__support/alloc-checker.h"
 #include "src/__support/ctype_utils.h"
 #include "src/__support/macros/config.h"
 #include "src/stdio/scanf_core/core_structs.h"
 #include "src/stdio/scanf_core/reader.h"
+#include "src/string/memory_utils/inline_memcpy.h"
 
 #include <stddef.h>
 
+constexpr int ALLOCATION_SCALE = 2;
+constexpr int ALLOCATION_BASE = 32;
+
 namespace LIBC_NAMESPACE_DECL {
 namespace scanf_core {
 
@@ -40,8 +46,26 @@ int convert_string(Reader<T> *reader, const FormatSection &to_conv) {
     }
   }
 
-  char *output = reinterpret_cast<char *>(to_conv.output_ptr);
-
+  char *output;
+ AllocChecker ac;
+#ifndef LIBC_COPT_SCANF_DISABLE_ALLOCATION
+  size_t alloc_size;
+  if ((to_conv.flags & NO_WRITE) == 0 && (to_conv.flags & ALLOCATE) != 0) {
+    if (to_conv.conv_name == 'c')
+      alloc_size = max_width + 1;
+    else
+      alloc_size =
+          (max_width < ALLOCATION_BASE) ? max_width + 1 : ALLOCATION_BASE;
+    output = new (ac) char[alloc_size];
+    if (!ac) {
+      return ALLOCATION_FAILURE;
+    }
+  } else {
+    output = reinterpret_cast<char *>(to_conv.output_ptr);
+  }
+#else
+  output = reinterpret_cast<char *>(to_conv.output_ptr);
+#endif
   char cur_char = reader->getc();
   size_t i = 0;
   for (; i < max_width && cur_char != '\0'; ++i) {
@@ -52,8 +76,26 @@ int convert_string(Reader<T> *reader, const FormatSection &to_conv) {
       break;
     }
     // if the NO_WRITE flag is not set, write to the output.
-    if ((to_conv.flags & NO_WRITE) == 0)
+    if ((to_conv.flags & NO_WRITE) == 0) {
       output[i] = cur_char;
+#ifndef LIBC_COPT_SCANF_DISABLE_ALLOCATION
+      if (((to_conv.flags & ALLOCATE) != 0) && (i + 1) == alloc_size &&
+          alloc_size < max_width) {
+        alloc_size *= ALLOCATION_SCALE;
+        if (alloc_size > max_width + 1)
+          alloc_size = max_width + 1;
+        char *tmp = new (ac) char[alloc_size];
+        if (!ac) {
+          delete[] output;
+          reader->ungetc(cur_char);
+          return ALLOCATION_FAILURE;
+        }
+        inline_memcpy(tmp, output, i + 1);
+        delete[] output;
+        output = tmp;
+      }
+#endif
+    }
     cur_char = reader->getc();
   }
 
@@ -61,8 +103,11 @@ int convert_string(Reader<T> *reader, const FormatSection &to_conv) {
   // last one back.
   reader->ungetc(cur_char);
 
+  bool null_terminate =
+      (to_conv.conv_name != 'c') || ((to_conv.flags & ALLOCATE) != 0);
+
   // If this is %s or %[]
-  if (to_conv.conv_name != 'c' && (to_conv.flags & NO_WRITE) == 0) {
+  if (null_terminate && (to_conv.flags & NO_WRITE) == 0) {
     // Always null terminate the string. This may cause a write to the
     // (max_width + 1) byte, which is correct. The max width describes the max
     // number of characters read from the input string, and doesn't necessarily
@@ -70,8 +115,20 @@ int convert_string(Reader<T> *reader, const FormatSection &to_conv) {
     output[i] = '\0';
   }
 
-  if (i == 0)
+  if (i == 0) {
+#ifndef LIBC_COPT_SCANF_DISABLE_ALLOCATION
+    if ((to_conv.flags & ALLOCATE) != 0 && output)
+      delete[] output;
+#endif
     return MATCHING_FAILURE;
+  }
+
+#ifndef LIBC_COPT_SCANF_DISABLE_ALLOCATION
+  if ((to_conv.flags & ALLOCATE) != 0) {
+    *reinterpret_cast<char **>(to_conv.output_ptr) = output;
+  }
+#endif
+
   return READ_OK;
 }
 
diff --git a/libc/test/src/stdio/scanf_core/CMakeLists.txt b/libc/test/src/stdio/scanf_core/CMakeLists.txt
index 6fdee7b3d5d51..0b234c6054721 100644
--- a/libc/test/src/stdio/scanf_core/CMakeLists.txt
+++ b/libc/test/src/stdio/scanf_core/CMakeLists.txt
@@ -47,6 +47,7 @@ add_libc_test(
     libc.src.stdio.scanf_core.converter
     libc.src.stdio.scanf_core.string_reader
     libc.src.__support.CPP.string_view
+    libc.src.__support.CPP.new
   COMPILE_OPTIONS
     ${use_system_file}
 )



More information about the libc-commits mailing list