- drop INTROSPECTION conjunctions (CMake dep table already forces THREADS+SOCKET)
- remove whole-file guards in client_server/process tests (CMake only compiles them when flags are ON)
- normalize single-flag '#if defined(PIP_HAS_X)' to '#ifdef PIP_HAS_X'
Guard PIConnection, thread primitives, PICodeParser and other feature
code behind PIP_HAS_THREADS/PIP_HAS_FILESYSTEM. In CMake generate the
feature flags summary from the single PIP_HAS_FLAGS list (used by the
add_definitions loop as well), default ICU to OFF and make introspection
also require PIP_HAS_SOCKET. Build system/thread test suites only when
the corresponding feature flags are enabled.
Replace the ad-hoc if-blocks with a PIP_HAS_DEPS_<feature> dependency table
(analogous to __deps_* module dependencies in FindPIP.cmake) resolved to a
fixpoint. The table now also covers http_server (threads+socket) and fftw
(has_fft), and adds the hard client_server/cloud -> io_utils module
dependency. Restore client_server/cloud under the sodium_FOUND guard, put
-ftemplate-depth-32 back before the CMAKE_C_FLAGS copy, and fix the console
block indentation.
- Rename __PIP_TYPENAME_DECLARE to PIP_REGISTER_TYPENAME across all headers
- Add PIP_DECLARE_TYPENAME(name) macro for in-class type name declaration
- Replace all static const char * __pip_typename__() member functions with PIP_DECLARE_TYPENAME
- move __PIP_TYPENAME_DECLARE() calls inside PIP_HAS_* guards so -fno-rtti
builds compile with arbitrary reduced flag combinations
- guard PISerial declaration by PIP_HAS_SERIAL like other feature-gated classes
- reduce threaded read buffers to 1 KiB on embedded targets (PIIODevice,
PIPacketExtractor, PIEthernet)
- CMake: disable thread/socket-dependent modules (http_client, mqtt_client,
client_server) and fftw when the required feature flags are off;
client_server/cloud no longer tied to the crypt option
- host utils gated on filesystem and the modules they need
- Pico keeps the minimal set; generic FreeRTOS (ESP32) keeps filesystem,
sockets and serial as in master, module set limited to crypt/compress/io_utils
- PICAN, PIBroadcast, PIPackedTCP vanish without socket+threads
- PISerial: restore PISERIAL_NO_PINS (auto-enabled on PIP_EMBEDDED)
- PIFile: FREERTOS uses fopen/stat (no fopen64 in ESP-IDF newlib)
- drop invalid PICout buffer guards, guard PIObject::deleteLater,
remove (void)destroying hack, fake PIHIDevice non-Linux stub and the
redundant PICO_SDK guard in code_model_generator
- guard console subsystem (PIScreen/PIScreenTile/tiles/PITerminal/
PIKbdListener) by PIP_HAS_THREADS plus a TTY or Windows console
(new PIP_HAS_TTY platform macro) - termios is not available on
bare-metal newlib
- guard cloud subsystem (PICloudBase/Client/Server/TCP) by
PIP_HAS_SOCKET and PIP_HAS_THREADS
- add PIP_BUILD_CONSOLE/PIP_BUILD_CLOUD to the auto-disable flag
dependencies in CMake
- PIP_CAN (SocketCAN) is Linux-only, exclude other platforms
- on Pico keep sockets/MQTT off by default: pico-sdk lwip provides
only a partial POSIX socket layer (no netinet/in.h, poll.h)
- PITranslator: inline no-op definitions when PIP_HAS_FILESYSTEM is
off so the _tr literal links
- PIIODevice: guard read_func/write_func EVENT_HANDLERs (no-threads)
- piwaitevent_p.cpp: drop unused sys/ioctl.h include (missing on
arm-none-eabi, broke Pico builds)
Found while verifying -DPIP_HAS_THREADS=OFF / -DPIP_HAS_FILESYSTEM=OFF
smoke builds (pre-existing gaps of the branch):
- piinit.cpp: full implementation now requires PIP_HAS_THREADS as well
(the PIInit class is declared in piinit.h only with threads; without
threads PIInit is absent altogether, stub/absent branches unchanged)
- PIWaitEvent: drop the PIP_HAS_THREADS guard - it is a plain
pipe/select/WaitForMultipleObjects wrapper with no thread usage
(master had no guard), restores unistd.h include for PICAN etc.
- PIEthernet: guard server_thread_ member, server_func(), threaded
listen/stop parts and writeThreaded() calls by PIP_HAS_THREADS
- PIPeer: guard class/impl by PIP_HAS_THREADS (uses PIThread/PITimer/
PIDiagnostics), openDevice() /etc/pip.conf read by PIP_HAS_FILESYSTEM
- PIBinaryLog::restart(): guard threaded-read restart by PIP_HAS_THREADS
- PIIODevice::configure(config_file,...), PIConnection::configureFromConfig,
PIConfig include resolution: guard path-based bodies by PIP_HAS_FILESYSTEM
- PISerial::availableDevicesInfo(): guard /dev and /proc/tty scan by
PIP_HAS_FILESYSTEM
- PIHIDevice impl: requires filesystem (sysfs scan) in addition to threads
- PIPluginLoader::pluginsDirectories/findLibrary: guard PIFile/PIDir use
by PIP_HAS_FILESYSTEM (fallback: empty list / path as-is)
- pifile.cpp: drop '|| !defined(PIP_HAS_FILESYSTEM)' from the
_fopen_call_ condition and the redundant inner
'#ifdef PIP_HAS_FILESYSTEM' - the whole file is already inside
PIP_HAS_FILESYSTEM
- remove PISERIAL_NO_PINS (piplatform.h, piserial.cpp): the whole
piserial.cpp is inside PIP_HAS_SERIAL which is forced OFF for
Pico/FREERTOS, so the macro could never be active where it mattered;
keep the Windows/POSIX branches as-is
Declarations of PIConfig(PIString, DeviceMode), PIConfig(PIString,
PIStringList) and open(PIString, DeviceMode) now match their
filesystem-guarded definitions in piconfig.cpp (link error otherwise);
the path constructor definition is guarded as well since it calls the
guarded open(). String/device based overloads stay unguarded.
- merge the PICO_BOARD and PIP_FREERTOS blocks (90% duplicate) into one
embedded block; Pico+PICO_FREERTOS now adds -DPIP_FREERTOS so
pithread.cpp takes the FreeRTOS path instead of POSIX pthreads;
restore set(ICU OFF)/set(LOCAL ON) for FreeRTOS/embedded targets
- auto-disable dependent flags when PIP_HAS_THREADS=OFF:
PIP_HAS_PROCESS, PIP_HAS_SERIAL (PIProcess derives from PIThread,
PISerial/PIIODevice use PIThread) and INTROSPECTION (walks threads)
- LIBS_MAIN: link dl only with PIP_HAS_DYNLIB, pthread/util/rt only with
PIP_HAS_THREADS (rt was never linked on APPLE), socket on QNX_HOST only
with PIP_HAS_SOCKET; WIN32 list untouched - arm-none-eabi has no
libdl/libpthread/librt so Pico firmware links again
- collapse repeated 'NOT DEFINED PICO_SDK_PATH' utils checks into
_PIP_HOST_TOOLS variable
- FindPIP.cmake: check PIP_FREERTOS instead of the never-set
PIP_EMBEDDED CMake variable (as in master)
- drop unused set(PIP_MICRO ON) from esp-pip
- wrap PIGrabberBase/PIPipelineThread in #ifdef PIP_HAS_THREADS like
pithread.h so 'pip.h' compiles in no-threads builds
- drop the ANDROID_PLATFORM block forcing PROCESS/DYNLIB/FFT off:
Android has /proc and dlopen and these features worked there in master
- __sighandler__: wrap PIProcess::currentPID() by PIP_HAS_PROCESS (0
fallback), the dumpApplicationToFile() call itself is already guarded
by PIP_INTROSPECTION && PIP_HAS_FILESYSTEM
- PISystemMonitor: guard /proc-related members (proc_dir/file/filem) and
code paths (startOnProcess, run() non-Windows part, gatherThread /proc
reader) by PIP_HAS_PROCESS/PIP_HAS_FILESYSTEM so the file compiles for
any flag combination, e.g. -DPIP_HAS_PROCESS=OFF -DPIP_HAS_FILESYSTEM=OFF
- pistatemachine_base.h checked non-existent macros __GXX_RTTI__/__RTTI__,
so on GCC/Clang desktop builds formatHash() always returned 0 and the
guard signature check in testGuard() never fired (UB on incompatible
signatures). Now the real PIP_HAS_RTTI (pibase_macros.h) is used.
- no-RTTI fallback: each Function<Args...> instantiation takes a unique
hash from a process-wide counter (a per-instantiation counter would
give every signature the same hash on first call).
- __pip_typename__ primary template: replace hard static_assert with a
generic fallback; containers/stream operators instantiate it for
arbitrary T in no-RTTI builds (Pico/arm-none-eabi has no RTTI), so the
assert made any no-RTTI build impossible. Explicit
__PIP_TYPENAME_DECLARE specializations still take precedence.
The stub member definitions lived in the header and were picked up by
every TU including piinit.h, causing multiple-definition link errors in
embedded (PIP_EMBEDDED) builds. Definitions now live only in piinit.cpp;
the stub implementation is compiled in the '#else' branch guarded by
PIP_HAS_THREADS (no-PIInit builds are unchanged).
1. Missing break in readDevice() Bulk case: Bulk read result was
overwritten by interrupt read, silently corrupting all bulk reads.
2. Wrong variable in writeDevice(): switch checked ep_read instead
of ep_write; Interrupt case used ep_read.address for writes.
3. Interface mismatch in closeDevice(): always released interface 0
(intefrace_) instead of the actually claimed interface
(interface_claimed), leaking non-zero interface claims.
4. Wrong index in interface enumeration: used outer loop variable c
(configuration index) instead of i (interface index), reading
wrong interface descriptors or causing OOB access.
destroy() aborted and deleted clients in the queue but did not
delete clients that were currently being processed (clients_in_proc).
If a thread was terminated while processing a client, that client
object and its CURL handle were leaked.
If clCreateContext succeeded but clCreateCommandQueue failed,
the context handle was returned without being released, leaking
the OpenCL context and all associated GPU resources.
luaL_newstate() allocates a Lua state (~2-4 MB with libraries)
in the constructor, but there was no destructor to call lua_close().
Every PILuaProgram instance leaked its entire Lua state.
Add ~PILuaProgram() that calls lua_close(PRIVATE->lua_state).
The decrypt() function for crypto_box (public-key) used
crypto_secretbox_NONCEBYTES and crypto_secretbox_MACBYTES
(secret-key constants) instead of crypto_box_* constants.
These happen to be equal in libsodium but are semantically
different APIs. Fix ensures consistency with the encrypt()
counterpart.
piDecompress() appended original size as 8-byte ullong at the end
of compressed data, but passed zba.size() (including the 8 bytes)
to uncompress(). This fed trailing metadata as part of the zlib
stream. Fix: subtract sizeof(ullong) from source length.
received() acquired mutex_header and mutex_session in different orders
depending on packet type:
- pt_Data path: mutex_header → mutex_session
- pt_Start path: mutex_session → mutex_header
send_process() acquires mutex_session independently. When running
concurrently with received(), the reversed lock ordering creates a
classic ABBA deadlock scenario.
Fix: enforce consistent ordering (mutex_header → mutex_session) in
all code paths. Restructured pt_Start case and buildSession() to
always acquire mutex_header before mutex_session.
The background run() thread copies allConnections() then iterates
each connection's diags_ map. If a PIConnection is deleted from
another thread between the copy and the iteration, dereferencing
the pointer is UAF. Add null check and protect diags_ iteration
with __device_pool__ lock.
Flags break_, is_sending, is_receiving, is_pause are accessed from
both the send thread (send_process) and the read thread (received)
without synchronization. Plain bool reads/writes from multiple threads
is undefined behavior per C++ standard. Convert to std::atomic<bool>
with proper initializers.
piMini(max_size, buff.size()) implicitly narrows size_t to int,
which is UB for large buffers. Replace with explicit piMin<ssize_t>
matching the correct pattern used in picloudclient.cpp.
The buffer was sized as floor(sz/4)*3 but the loop ran ceil(sz/4)
times, writing 3 bytes per iteration. For sz%4!=0, this wrote past
the buffer end. Also guarded sz<4 to avoid processing trivially
short or malformed input.
The function called unlock() on thread_mutex without a matching
lock(), causing UB on Windows (releasing an unowned critical
section) or silently dropping a user-held lock.
When elapsed time exceeded the timeout, the remaining milliseconds
expression went negative and was implicitly converted to DWORD,
wrapping to ~0xFFFFFFFF (5 days) — effectively INFINITE.
Now clamps to 0 and returns false when time has elapsed.
dirent** list was uninitialized, so scandir failure (returning -1)
left it with indeterminate value. The unconditional free(list) at
the end was then UB — typically heap corruption or crash.
pop_back() called resize(size_ - 1) without checking for empty.
On empty array, uint underflow produced UINT_MAX, causing
bytesInBits(UINT_MAX) overflow and subsequent OOB access.
pop_front() already had this guard; pop_back() was missing it.
Three complex type cases (pivComplexf, pivComplexd, pivComplexld)
lacked break statements, causing fallthrough to pivTime. Setting
a complex variant from string silently overwrote the value with
PITime::fromString(), corrupting the variant's type and data.
When mmap() returned MAP_FAILED, data stayed as (void*)-1, causing
subsequent read/write/close to pass the null guard and crash.
Now resets data=nullptr, and calls shm_unlink when owner=true to
avoid orphaned shared memory objects.
The float FFT plan generator wrote 8 ints per entry (indices +0..+7)
but declared entrysize=4, causing heap overwrite into the next entry
and incorrect plan array sizing.
processFile() accessed files_[id - 1] without validating id.
When id == 0 (from a crafted network packet), id - 1 = -1 caused
out-of-bounds access. Added bounds check: id must be in range
[1, files_.size()]. Stops receive and logs error on invalid id.
fork() can return -1 on error (too many processes, out of memory).
The old code treated pid_ == -1 as a successful fork, entering the
parent branch and calling waitpid(-1, ...) which waits for ANY
child process. Added explicit check: close pipes, free memory,
and return on fork failure.
In the blocking read path (timeout_ms <= 0), the while loop had no
exit condition for readDevice() returning 0 (EOF) or -1 (error).
This caused an infinite busy-loop at 100% CPU. Added 'else break'
to exit the loop on read failure, matching the timeout branch
which already handles this case.
readDevice() did pos += size instead of pos += ret. The clamping
on the next line (pos = min(pos, data_->size_s())) masked the
issue in practice, but the semantics were wrong. Aligns with
writeDevice() which correctly uses pos += rs.size_s().
open() returns -1 on error. The check fd != 0 treated -1 as a
valid fd (since -1 != 0 is true), causing devctl(-1, ...) and
close(-1) on QNX. Fixed to fd >= 0.
File descriptor 0 is stdin (a valid fd). Using 0 as the 'not-open'
sentinel caused isCreate() to return false and destroy() to skip
close() when pipe() happened to allocate fds {0, 1} (e.g. in daemons
with closed stdio). POSIX convention: -1 = invalid fd.
When the custom client_factory() returned nullptr, the incoming
PIEthernet pointer was leaked. Matches existing pattern used in the
max_clients overflow branch.
- toChar(): char v was uninitialized, causing UB when
sscanf fails to match (empty string). Initialize to 0.
- operator+=(PIConstChars): loop iterated l < d.size() instead of
l < str.size(), reading past the end of str after d.enlarge().
This is a heap buffer overread with undefined behavior.