Harden DFTE rendering and placeholder registration to fail safely under stalled and invalid inputs.

Add regression coverage and docs updates so these edge cases stay stable across board test runs.
This commit is contained in:
2026-04-16 13:48:25 +10:00
parent 416ab74238
commit 73fdd28f74
12 changed files with 262 additions and 38 deletions
+86 -8
View File
@@ -1,11 +1,26 @@
#include "DeviceFrameworkPlaceholderRegistry.h"
#include "DeviceFrameworkTemplateEngineDebug.h"
#include <pgmspace.h>
#include <new>
DeviceFrameworkPlaceholderRegistry::DeviceFrameworkPlaceholderRegistry(uint16_t maxPlaceholders)
: maxPlaceholders(maxPlaceholders), count(0) {
placeholders = new PlaceholderEntry[maxPlaceholders];
memset(placeholders, 0, sizeof(PlaceholderEntry) * maxPlaceholders);
: placeholders(nullptr), maxPlaceholders(maxPlaceholders), count(0) {
if (maxPlaceholders == 0) {
DFTE_LOG_ERROR("Placeholder registry size cannot be zero");
this->maxPlaceholders = 0;
return;
}
placeholders = new (std::nothrow) PlaceholderEntry[maxPlaceholders];
if (placeholders == nullptr) {
DFTE_LOG_ERROR("Failed to allocate placeholder registry");
this->maxPlaceholders = 0;
return;
}
for (uint16_t i = 0; i < maxPlaceholders; ++i) {
placeholders[i] = PlaceholderEntry();
}
}
DeviceFrameworkPlaceholderRegistry::~DeviceFrameworkPlaceholderRegistry() {
@@ -16,6 +31,11 @@ DeviceFrameworkPlaceholderRegistry::~DeviceFrameworkPlaceholderRegistry() {
}
bool DeviceFrameworkPlaceholderRegistry::registerProgmemData(const char* name, const char* progmemData) {
if (placeholders == nullptr || maxPlaceholders == 0) {
DFTE_LOG_ERROR("Placeholder registry not initialized");
return false;
}
if (count >= maxPlaceholders) {
DFTE_LOG_ERROR("Placeholder registry full, cannot register: " + String(name));
return false;
@@ -37,12 +57,19 @@ bool DeviceFrameworkPlaceholderRegistry::registerProgmemData(const char* name, c
entry.type = PlaceholderType::PROGMEM_DATA;
entry.data = progmemData;
entry.getLength = getProgmemLength;
entry.cachedLength = getProgmemLength(progmemData);
entry.hasCachedLength = true;
count++;
return true;
}
bool DeviceFrameworkPlaceholderRegistry::registerProgmemTemplate(const char* name, const char* progmemTemplate) {
if (placeholders == nullptr || maxPlaceholders == 0) {
DFTE_LOG_ERROR("Placeholder registry not initialized");
return false;
}
if (count >= maxPlaceholders) {
DFTE_LOG_ERROR("Placeholder registry full, cannot register: " + String(name));
return false;
@@ -58,12 +85,19 @@ bool DeviceFrameworkPlaceholderRegistry::registerProgmemTemplate(const char* nam
entry.type = PlaceholderType::PROGMEM_TEMPLATE;
entry.data = progmemTemplate;
entry.getLength = getProgmemLength;
entry.cachedLength = getProgmemLength(progmemTemplate);
entry.hasCachedLength = true;
count++;
return true;
}
bool DeviceFrameworkPlaceholderRegistry::registerRamData(const char* name, PlaceholderDataGetter getter) {
if (placeholders == nullptr || maxPlaceholders == 0) {
DFTE_LOG_ERROR("Placeholder registry not initialized");
return false;
}
if (count >= maxPlaceholders) {
DFTE_LOG_ERROR("Placeholder registry full, cannot register: " + String(name));
return false;
@@ -84,12 +118,19 @@ bool DeviceFrameworkPlaceholderRegistry::registerRamData(const char* name, Place
entry.type = PlaceholderType::RAM_DATA;
entry.data = (const void*)getter;
entry.getLength = getRamLength;
entry.cachedLength = 0;
entry.hasCachedLength = false;
count++;
return true;
}
bool DeviceFrameworkPlaceholderRegistry::registerDynamicTemplate(const char* name, const DynamicTemplateDescriptor* descriptor) {
if (placeholders == nullptr || maxPlaceholders == 0) {
DFTE_LOG_ERROR("Placeholder registry not initialized");
return false;
}
if (count >= maxPlaceholders) {
DFTE_LOG_ERROR("Placeholder registry full, cannot register: " + String(name));
return false;
@@ -110,12 +151,19 @@ bool DeviceFrameworkPlaceholderRegistry::registerDynamicTemplate(const char* nam
entry.type = PlaceholderType::DYNAMIC_TEMPLATE;
entry.data = descriptor;
entry.getLength = nullptr;
entry.cachedLength = 0;
entry.hasCachedLength = false;
count++;
return true;
}
bool DeviceFrameworkPlaceholderRegistry::registerConditional(const char* name, const ConditionalDescriptor* descriptor) {
if (placeholders == nullptr || maxPlaceholders == 0) {
DFTE_LOG_ERROR("Placeholder registry not initialized");
return false;
}
if (count >= maxPlaceholders) {
DFTE_LOG_ERROR("Placeholder registry full, cannot register: " + String(name));
return false;
@@ -136,12 +184,19 @@ bool DeviceFrameworkPlaceholderRegistry::registerConditional(const char* name, c
entry.type = PlaceholderType::CONDITIONAL;
entry.data = descriptor;
entry.getLength = nullptr;
entry.cachedLength = 0;
entry.hasCachedLength = false;
count++;
return true;
}
bool DeviceFrameworkPlaceholderRegistry::registerIterator(const char* name, const IteratorDescriptor* descriptor) {
if (placeholders == nullptr || maxPlaceholders == 0) {
DFTE_LOG_ERROR("Placeholder registry not initialized");
return false;
}
if (count >= maxPlaceholders) {
DFTE_LOG_ERROR("Placeholder registry full, cannot register: " + String(name));
return false;
@@ -162,6 +217,8 @@ bool DeviceFrameworkPlaceholderRegistry::registerIterator(const char* name, cons
entry.type = PlaceholderType::ITERATOR;
entry.data = descriptor;
entry.getLength = nullptr;
entry.cachedLength = 0;
entry.hasCachedLength = false;
count++;
return true;
@@ -169,7 +226,13 @@ bool DeviceFrameworkPlaceholderRegistry::registerIterator(const char* name, cons
void DeviceFrameworkPlaceholderRegistry::clear() {
count = 0;
memset(placeholders, 0, sizeof(PlaceholderEntry) * maxPlaceholders);
if (placeholders == nullptr || maxPlaceholders == 0) {
return;
}
for (uint16_t i = 0; i < maxPlaceholders; ++i) {
placeholders[i] = PlaceholderEntry();
}
}
bool DeviceFrameworkPlaceholderRegistry::validatePlaceholderName(const char* name) const {
@@ -194,7 +257,7 @@ bool DeviceFrameworkPlaceholderRegistry::validatePlaceholderName(const char* nam
}
const PlaceholderEntry* DeviceFrameworkPlaceholderRegistry::getPlaceholder(const char* name) const {
if (name == nullptr) return nullptr;
if (name == nullptr || placeholders == nullptr || count <= 0) return nullptr;
for (int i = count - 1; i >= 0; i--) {
if (strcmp(placeholders[i].name, name) == 0) {
@@ -206,14 +269,29 @@ const PlaceholderEntry* DeviceFrameworkPlaceholderRegistry::getPlaceholder(const
size_t DeviceFrameworkPlaceholderRegistry::renderPlaceholder(const PlaceholderEntry* entry, size_t offset,
uint8_t* buffer, size_t maxLen) const {
if (entry == nullptr || maxLen == 0) {
if (entry == nullptr || buffer == nullptr || maxLen == 0) {
return 0;
}
switch (entry->type) {
case PlaceholderType::PROGMEM_DATA:
case PlaceholderType::PROGMEM_TEMPLATE:
return copyProgmemData((const char*)entry->data, offset, buffer, maxLen);
case PlaceholderType::PROGMEM_TEMPLATE: {
const char* data = static_cast<const char*>(entry->data);
if (data == nullptr) {
return 0;
}
size_t dataLen = entry->hasCachedLength ? entry->cachedLength : getProgmemLength(entry->data);
if (offset >= dataLen) {
return 0;
}
size_t remaining = dataLen - offset;
constexpr size_t MAX_CHUNK = DFTE_PROGMEM_CHUNK_SIZE;
size_t chunkSize = min(min(maxLen, remaining), MAX_CHUNK);
memcpy_P(buffer, data + offset, chunkSize);
return chunkSize;
}
case PlaceholderType::RAM_DATA:
return copyRamData((PlaceholderDataGetter)entry->data, offset, buffer, maxLen);
+7 -3
View File
@@ -7,7 +7,9 @@ DeviceFrameworkTemplateContext::DeviceFrameworkTemplateContext()
registry(nullptr),
totalBytesProcessed(0), startTime(0) {
memset(placeholderName, 0, sizeof(placeholderName));
memset(renderingStack, 0, sizeof(renderingStack));
for (int i = 0; i < MAX_RENDERING_DEPTH; ++i) {
renderingStack[i] = RenderingContext();
}
}
void DeviceFrameworkTemplateContext::reset() {
@@ -20,7 +22,9 @@ void DeviceFrameworkTemplateContext::reset() {
totalBytesProcessed = 0;
startTime = millis();
memset(placeholderName, 0, sizeof(placeholderName));
memset(renderingStack, 0, sizeof(renderingStack));
for (int i = 0; i < MAX_RENDERING_DEPTH; ++i) {
renderingStack[i] = RenderingContext();
}
}
// Unified stack management methods
@@ -107,7 +111,7 @@ void DeviceFrameworkTemplateContext::popContext() {
}
// Clear the popped context
memset(&ctx, 0, sizeof(RenderingContext));
ctx = RenderingContext();
}
RenderingContext* DeviceFrameworkTemplateContext::getCurrentContext() {
+42 -3
View File
@@ -704,8 +704,21 @@ DeviceFrameworkTemplateRenderer::RenderOutcome DeviceFrameworkTemplateRenderer::
case RenderingContextType::PLACEHOLDER_DYNAMIC_TEMPLATE:
return makeState(TemplateRenderState::TEXT, true);
case RenderingContextType::PLACEHOLDER_CONDITIONAL:
return makeState(TemplateRenderState::RENDERING_CONTEXT, true);
case RenderingContextType::PLACEHOLDER_CONDITIONAL: {
RenderOutcome outcome = makeState(TemplateRenderState::RENDERING_CONTEXT, true);
outcome.popCount = 1;
RenderingContext* parent = (ctx.renderingDepth > 1) ? ctx.getContext(ctx.renderingDepth - 2) : nullptr;
if (!parent) {
outcome.nextState = TemplateRenderState::COMPLETE;
outcome.repeat = false;
outcome.finished = true;
} else if (parent->type == RenderingContextType::TEMPLATE) {
outcome.nextState = TemplateRenderState::TEXT;
}
return outcome;
}
case RenderingContextType::PLACEHOLDER_ITERATOR:
return processIteratorContext(ctx, currentCtx);
@@ -747,7 +760,16 @@ DeviceFrameworkTemplateRenderer::RenderOutcome DeviceFrameworkTemplateRenderer::
return outcome;
}
size_t totalLength = entry->getLength(entry->data);
size_t totalLength = 0;
if (entry->hasCachedLength) {
totalLength = entry->cachedLength;
} else if (entry->getLength != nullptr) {
totalLength = entry->getLength(entry->data);
} else {
DFTE_LOG_ERROR("Placeholder '" + String(entry->name) + "' missing length getter");
return makeError();
}
if (dataCtx.offset >= totalLength) {
RenderOutcome outcome = makeState(TemplateRenderState::RENDERING_CONTEXT, true);
outcome.popCount = 1;
@@ -799,12 +821,20 @@ size_t DeviceFrameworkTemplateRenderer::renderNextChunk(DeviceFrameworkTemplateC
size_t written = 0;
size_t iterations = 0;
size_t consecutiveNoProgressIterations = 0;
uint8_t* writePtr = buffer;
size_t remaining = maxLen;
while (remaining > 0 && !ctx.isComplete() && !ctx.hasError() && iterations < MAX_ITERATIONS) {
RenderOutcome outcome = renderChunk(ctx, writePtr, remaining);
bool noProgressIteration = (outcome.bytesWritten == 0 && outcome.repeat && !outcome.finished && !outcome.errored);
if (noProgressIteration) {
consecutiveNoProgressIterations++;
} else {
consecutiveNoProgressIterations = 0;
}
written += outcome.bytesWritten;
ctx.totalBytesProcessed += outcome.bytesWritten;
writePtr += outcome.bytesWritten;
@@ -822,6 +852,9 @@ size_t DeviceFrameworkTemplateRenderer::renderNextChunk(DeviceFrameworkTemplateC
if (iterations >= MAX_ITERATIONS) {
DFTE_LOG_WARN("Maximum iterations reached in renderNextChunk");
if (consecutiveNoProgressIterations >= 3 && !ctx.isComplete() && !ctx.hasError()) {
ctx.state = TemplateRenderState::ERROR;
}
}
return written;
@@ -833,6 +866,12 @@ void DeviceFrameworkTemplateRenderer::initializeContext(DeviceFrameworkTemplateC
void DeviceFrameworkTemplateRenderer::initializeContext(DeviceFrameworkTemplateContext& ctx, const char* templateData, bool templateInProgmem) {
ctx.reset();
if (templateData == nullptr) {
DFTE_LOG_ERROR("initializeContext called with null template pointer");
ctx.state = TemplateRenderState::ERROR;
return;
}
// Push initial template context
if (!ctx.pushContext(RenderingContextType::TEMPLATE, "ROOT")) {