[esp32] Trim the sscanf wrap comments and docstrings

This commit is contained in:
J. Nick Koston
2026-09-11 09:53:38 -05:00
parent c6062a73c3
commit 3c8c511e12
3 changed files with 25 additions and 58 deletions
+9 -18
View File
@@ -199,11 +199,8 @@ PSRAM_XIP_VARIANTS = {
# neither, so the engine is already in the image and the wrap saves nothing.
ROM_VSNPRINTF_WITHOUT_VASPRINTF_VARIANTS = {VARIANT_ESP32C6}
# Variants whose ROM exports sscanf under the newlib format ESPHome links
# against: the ESP32-C6 normal-format ROM (esp32c6.rom.newlib-normal.ld) and
# the ESP32-C2 nano-format ROM, nano being the IDF default on the C2. There
# the sscanf wrap saves nothing; see sscanf_stubs.cpp. The classic ESP32 ROM
# only exports it under nano format, which ESPHome does not use.
# Variants whose ROM exports sscanf with the newlib format in use (C6 normal
# format, C2 nano format by IDF default), so the sscanf wrap saves nothing.
ROM_SSCANF_VARIANTS = {VARIANT_ESP32C2, VARIANT_ESP32C6}
# NVS encryption (HMAC peripheral scheme) is only available on variants that
@@ -2327,12 +2324,10 @@ async def _set_libc_picolibc_newlib_compat() -> None:
def _newlib_wraps_apply() -> bool:
"""Whether the printf, vasprintf and sscanf linker wraps belong in this build.
"""Whether the printf, vasprintf and sscanf wraps belong in this build.
They need an ESP-IDF framework build (their stubs compile out on Arduino)
on newlib: IDF 5.x defaults to newlib on every variant, IDF 6.0+ switches
to picolibc on every variant, and the wraps are only measured against
newlib.
ESP-IDF framework (the stubs compile out on Arduino) on newlib, which
IDF 6.0+ replaces with picolibc.
"""
return not CORE.using_arduino and idf_version() < cv.Version(6, 0, 0)
@@ -2340,9 +2335,8 @@ def _newlib_wraps_apply() -> bool:
def _add_wrap_stub(symbol: str, define: str) -> None:
"""Emit a linker wrap for ``symbol`` served by a stub compiled under ``define``.
The --undefined flag is needed because libsrc.a is scanned before the IDF
libraries that reference the symbol, so the stub would otherwise never be
pulled from the archive.
--undefined is needed because libsrc.a is scanned before the IDF library
that references the symbol.
"""
cg.add_define(define)
cg.add_build_flag(f"-Wl,--wrap={symbol}")
@@ -2353,11 +2347,8 @@ def _add_wrap_stub(symbol: str, define: str) -> None:
async def _add_sscanf_stub(enable_full_scanf: bool | None) -> None:
"""Wrap sscanf when bluedroid is the only reason newlib's scanf engine links.
Runs at FINAL priority so every request_bluetooth() call has happened;
see sscanf_stubs.cpp for what the stub covers. A lambda that scans a float
keeps the libc sscanf unless enable_full_scanf is set explicitly, since the
stub has no floating-point conversions and the wrap applies to the whole
image.
FINAL priority so every request_bluetooth() call has happened. A lambda
that scans a float keeps the libc sscanf unless enable_full_scanf is set.
"""
if (
enable_full_scanf
+8 -18
View File
@@ -1,23 +1,13 @@
/*
* Linker wrap stub for sscanf on ESP-IDF, newlib only.
* Linker wrap for sscanf on ESP-IDF with newlib.
*
* Nothing in ESPHome calls sscanf, but bluedroid does in two places:
* btc_config_get_bin decodes stored bonding keys with "%02x" and
* string_to_bdaddr parses "%02x:%02x:%02x:%02x:%02x:%02x". Those two
* calls are the only reference to newlib's scanf engine (__ssvfscanf_r,
* _strtod_l and their helpers, ~13 KB) in a Bluetooth build on variants
* whose ROM does not export sscanf.
*
* This stub routes sscanf through the scanner in core/sscanf_no_float.h,
* so any other caller in the image (user lambdas included) keeps working.
* Floating-point conversions are the one gap: supporting them would pull
* _strtod_l straight back, so a format using %f and friends aborts with a
* message pointing at enable_full_scanf: true. Codegen already keeps the
* libc sscanf when a lambda scans a float.
*
* Only compiled in when codegen defines USE_ESP32_SSCANF_STUB, which is
* gated on a Bluetooth component being present, on the variant's ROM and
* on the same newlib condition as printf_stubs.cpp.
* bluedroid's two "%02x" parses (btc_config_get_bin, string_to_bdaddr) are
* the only sscanf callers in an ESPHome image and link newlib's whole scanf
* engine (~13 KB with _strtod_l). Routing them through core/sscanf_no_float.h
* drops it; floats would pull _strtod_l back, so those abort with a message
* naming enable_full_scanf. Codegen skips the wrap when a lambda scans a float
* and only defines USE_ESP32_SSCANF_STUB for Bluetooth builds on variants
* whose ROM lacks sscanf.
*/
#include "esphome/core/defines.h"
+8 -22
View File
@@ -1,18 +1,9 @@
#pragma once
/*
* A small sscanf without floating-point conversions.
*
* Backs the ESP-IDF linker wrap in components/esp32/sscanf_stubs.cpp, where
* bluedroid's "%02x" parses would otherwise link newlib's whole scanf engine
* (~13 KB with _strtod_l). Integer, character, string and scanset conversions
* behave like libc; a conversion this scanner does not implement (floats,
* %p, wide characters) returns SSCANF_UNSUPPORTED so the caller can fail
* loudly instead of misparsing.
*
* Header only so the host unit tests can exercise it without compiling a
* target platform's sources.
*/
// sscanf without floating-point conversions, backing the linker wrap in
// components/esp32/sscanf_stubs.cpp. Integer, character, string and scanset
// conversions behave like libc; anything else returns SSCANF_UNSUPPORTED.
// Header only so the host unit tests can cover it.
#include <cstdarg>
#include <cstddef>
@@ -40,9 +31,7 @@ enum class SscanfLength : uint8_t {
inline bool is_space(char c) { return c == ' ' || (c >= '\t' && c <= '\r'); }
// The bit pattern is what the signed conversions want as well. All object
// pointers share one representation, so the caller fetches the argument
// once as void * rather than once per pointee type.
// The unsigned bit pattern is what the signed conversions want as well
inline void store_int(void *dest, SscanfLength length, uint64_t value) {
switch (length) {
case SscanfLength::SSCANF_LENGTH_HH:
@@ -80,8 +69,7 @@ struct Scanset {
}
};
// Parses a "[set]" directive starting just after the '[' and returns the
// position after the closing ']'.
// Parses "[set]" from just after the '[' and returns the position after ']'
inline const char *parse_scanset(const char *f, Scanset &set) {
if (*f == '^') {
set.negate = true;
@@ -103,8 +91,7 @@ inline const char *parse_scanset(const char *f, Scanset &set) {
return *f == ']' ? f + 1 : f;
}
// Radix of an integer conversion: 0 means "detect from the prefix" (%i),
// 0xFF marks a conversion this scanner does not implement.
// Radix of an integer conversion: 0 detects from the prefix (%i), 0xFF is unsupported
inline unsigned int_base(char conv) {
switch (conv) {
case 'd':
@@ -261,8 +248,7 @@ inline int vsscanf_no_float(const char *str, const char *fmt, va_list ap) { //
Scanset set;
if (conv == '[')
f = parse_scanset(f + 1, set) - 1; // the loop's f++ steps past the ']'
// %c reads exactly width characters (default 1); like newlib, a short
// read still assigns whatever was available.
// %c reads width characters (default 1); a short read still assigns, like newlib
const size_t limit = conv == 'c' && width == SIZE_MAX ? 1 : width;
size_t n = 0;
while (n < limit && in[n] != '\0' && (conv == 'c' || (conv == '[' ? set.contains(in[n]) : !is_space(in[n])))) {