Global: fixes issues found by AI

This commit is contained in:
李通洲
2026-09-24 21:21:02 +08:00
committed by Carter Li
parent 00ccd8be12
commit bba45429ae
24 changed files with 387 additions and 80 deletions
+41 -1
View File
@@ -197,7 +197,13 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, c
.read_consumed = 0,
.read_buffer = 0,
};
ioctl(binder->fd, BINDER_WRITE_READ, &release);
// This one ioctl carries the whole tail, so its failure is not cosmetic: without
// BC_ACQUIRE the handles handed to the caller are only weakly referenced and every
// later transaction on them fails with BR_FAILED_REPLY, which says nothing about the
// real cause. Reported, but only when nothing more specific is already at hand.
if (ioctl(binder->fd, BINDER_WRITE_READ, &release) != 0 && error == nullptr) {
error = "Releasing the binder reply failed";
}
return error;
}
@@ -235,3 +241,37 @@ const char* ffBinderLookupService(FFBinder* binder, const char* name, uint32_t t
*handle = reply.handles[0];
return nullptr;
}
void ffBinderServiceHandleRelease(FFBinderServiceHandle* service) {
if (service->binder == nullptr || service->handle == 0) {
return;
}
// The mirror of the acquire at the end of ffBinderTransact(): one strong and one weak reference
// each, dropped in the same order they were taken. The kernel keeps the service node alive until
// the last one goes, so this is what stops a short-lived fastfetch from holding one for the whole
// of its life.
const uint32_t commands[] = { BC_RELEASE, BC_DECREFS };
uint8_t tail[2 * 2 * sizeof(uint32_t)];
size_t tailSize = 0;
for (size_t i = 0; i < 2; i++) {
memcpy(tail + tailSize, &commands[i], sizeof(uint32_t));
tailSize += sizeof(uint32_t);
memcpy(tail + tailSize, &service->handle, sizeof(uint32_t));
tailSize += sizeof(uint32_t);
}
struct binder_write_read release = {
.write_size = tailSize,
.write_consumed = 0,
.write_buffer = (binder_uintptr_t) (uintptr_t) tail,
.read_size = 0,
.read_consumed = 0,
.read_buffer = 0,
};
// Not reported: this runs from a cleanup attribute, where there is no caller left to report to,
// and the only way for it to fail is a binder that is already dead -- which every earlier call on
// the same binder would have said already.
ioctl(service->binder->fd, BINDER_WRITE_READ, &release);
service->handle = 0;
}
+13
View File
@@ -222,3 +222,16 @@ typedef struct FFBinderReply {
// Resolves a service name to a handle through the service manager. Returns nullptr on success.
[[gnu::nonnull(1, 2, 4), nodiscard]] const char* ffBinderLookupService(FFBinder* binder, const char* name, uint32_t transactionCode, uint32_t* handle);
// A handle resolved through the service manager, held together with the binder it came from so that a
// single cleanup attribute can give it back on every path out of the function that took it. Zeroed --
// or left at handle 0 by a lookup that has not run or did not succeed -- it is a no-op, which is what
// makes it safe to declare above the lookup itself.
typedef struct FFBinderServiceHandle {
FFBinder* binder;
uint32_t handle;
} FFBinderServiceHandle;
// Returns the strong and weak references ffBinderLookupService() acquired on the handle. Usable as a
// cleanup attribute; safe on a zeroed or already released FFBinderServiceHandle.
[[gnu::nonnull(1)]] void ffBinderServiceHandleRelease(FFBinderServiceHandle* service);
+68 -22
View File
@@ -55,10 +55,37 @@ static uint32_t dexU32(const uint8_t* p) {
return (uint32_t) p[0] | ((uint32_t) p[1] << 8) | ((uint32_t) p[2] << 16) | ((uint32_t) p[3] << 24);
}
// LEB128, at most five bytes for the 32-bit values a dex stores this way.
static uint32_t dexUleb128(const uint8_t** p) {
// The dex header points at its tables with offsets that a corrupt or hostile file is free to set
// anywhere, including past the end of the mapping. Testing `end - p` alone does not catch that: once
// `p` is past `end` the subtraction is negative and casting it to size_t turns it into a huge value,
// so the comparison passes and the read goes off the end. Both directions have to be one test.
static bool dexInRange(const uint8_t* dex, const uint8_t* end, const uint8_t* p, size_t size) {
return p >= dex && p <= end && (size_t) (end - p) >= size;
}
// Same, for a table of `count` elements of `elementSize` bytes. `count` is 64-bit because the
// callers add one to an index that came out of the file, and doing that in 32 bits can wrap to zero
// and make the check pass for an index that is not in the table. Dividing rather than multiplying is
// what keeps a hostile `count` from wrapping the size_t on a 32-bit build -- armeabi-v7a is one, and
// there `count * 4` overflows for any count above 0x3FFFFFFF and the check passes again.
static bool dexTableInRange(const uint8_t* dex, const uint8_t* end, const uint8_t* p, uint64_t count, size_t elementSize) {
if (p < dex || p > end) {
return false;
}
return (size_t) (end - p) / elementSize >= count;
}
// LEB128, at most five bytes for the 32-bit values a dex stores this way. `end` bounds every read:
// the lengths and indexes decoded here come out of the file, so a truncated one must not walk off
// the end. A read that runs into `end` yields what was decoded so far, and the callers treat the
// resulting value as untrusted -- it is either compared against another count or used as an index
// that is range checked in turn.
static uint32_t dexUleb128(const uint8_t** p, const uint8_t* end) {
uint32_t value = 0;
for (int shift = 0; shift <= 28; shift += 7) {
if (*p >= end) {
break;
}
const uint8_t byte = *(*p)++;
value |= (uint32_t) (byte & 0x7f) << shift;
if ((byte & 0x80) == 0) {
@@ -71,12 +98,18 @@ static uint32_t dexUleb128(const uint8_t** p) {
// encoded_value: one header byte holding ((size - 1) << 5) | type, then that many payload bytes. A
// `static final int` constant is written as VALUE_INT, whose payload is a sign-extended
// little-endian integer. Reading through 64 bits keeps every shift in range even if the size field
// is nonsense; the caller's length check is what rejects such a dex.
static int32_t dexEncodedInt(const uint8_t** p) {
// is nonsense; `end` is what keeps a truncated payload from running off the end of the mapping.
static int32_t dexEncodedInt(const uint8_t** p, const uint8_t* end) {
if (*p >= end) {
return 0;
}
const uint8_t header = *(*p)++;
const uint32_t size = (uint32_t) (header >> 5) + 1;
uint64_t value = 0;
for (uint32_t i = 0; i < size && i < 8; ++i) {
if (*p >= end) {
break;
}
value |= (uint64_t) (*(*p)++) << (i * 8);
}
const uint32_t bits = size * 8;
@@ -91,14 +124,17 @@ static int32_t dexEncodedInt(const uint8_t** p) {
// bytes can be used as they are.
static const char* dexString(const uint8_t* dex, const uint8_t* end, uint32_t index) {
const uint8_t* ids = dex + dexU32(dex + FF_DEX_OFF_STRING_IDS);
if (ids < dex || (size_t) (end - ids) < (size_t) index * 4 + 4) {
// `string_ids_size` is not read: the check is that the entry `index` names lies inside the
// mapping, which is what the read below needs, and the table is the only thing that can say it
// without trusting a second field out of the same file.
if (!dexTableInRange(dex, end, ids, (uint64_t) index + 1, 4)) {
return nullptr;
}
const uint8_t* p = dex + dexU32(ids + (size_t) index * 4);
if (p < dex || p >= end) {
return nullptr;
}
(void) dexUleb128(&p);
(void) dexUleb128(&p, end);
if (p >= end) {
return nullptr;
}
@@ -123,7 +159,7 @@ static const char* dexStaticInt(const uint8_t* dex, size_t size, const char* cla
// The class's type index, so that the class_def_item and the field ids can be filtered by class.
const uint8_t* types = dex + dexU32(dex + FF_DEX_OFF_TYPE_IDS);
const uint32_t typeCount = dexU32(dex + FF_DEX_OFF_TYPE_IDS_SIZE);
if (types < dex || (size_t) (end - types) < (size_t) typeCount * 4) {
if (!dexTableInRange(dex, end, types, typeCount, 4)) {
return "The dex type table is out of range";
}
uint32_t typeIndex = UINT32_MAX;
@@ -140,7 +176,7 @@ static const char* dexStaticInt(const uint8_t* dex, size_t size, const char* cla
const uint8_t* classes = dex + dexU32(dex + FF_DEX_OFF_CLASS_DEFS);
const uint32_t classCount = dexU32(dex + FF_DEX_OFF_CLASS_DEFS_SIZE);
if (classes < dex || (size_t) (end - classes) < (size_t) classCount * FF_DEX_CLASS_DEF_SIZE) {
if (!dexTableInRange(dex, end, classes, classCount, FF_DEX_CLASS_DEF_SIZE)) {
return "The dex class table is out of range";
}
@@ -152,15 +188,16 @@ static const char* dexStaticInt(const uint8_t* dex, size_t size, const char* cla
const uint8_t* fields = dex + dexU32(classDef + FF_DEX_OFF_CLASS_DEF_DATA);
const uint8_t* values = dex + dexU32(classDef + FF_DEX_OFF_CLASS_DEF_STATIC_VALUES);
if (fields < dex || fields >= end || values < dex || values >= end) {
// One byte each, so that the first uleb128 below has something to read.
if (!dexInRange(dex, end, fields, 1) || !dexInRange(dex, end, values, 1)) {
return "The dex class data is out of range";
}
const uint32_t staticFields = dexUleb128(&fields);
(void) dexUleb128(&fields); // instance_fields_size
(void) dexUleb128(&fields); // direct_methods_size
(void) dexUleb128(&fields); // virtual_methods_size
const uint32_t staticValues = dexUleb128(&values);
const uint32_t staticFields = dexUleb128(&fields, end);
(void) dexUleb128(&fields, end); // instance_fields_size
(void) dexUleb128(&fields, end); // direct_methods_size
(void) dexUleb128(&fields, end); // virtual_methods_size
const uint32_t staticValues = dexUleb128(&values, end);
if (staticFields != staticValues) {
return "The dex static fields and values do not pair up";
}
@@ -168,14 +205,20 @@ static const char* dexStaticInt(const uint8_t* dex, size_t size, const char* cla
const uint8_t* fieldIds = dex + dexU32(dex + FF_DEX_OFF_FIELD_IDS);
uint32_t fieldIndex = 0;
for (uint32_t j = 0; j < staticFields; ++j) {
if (fields >= end) {
// The two arrays are walked in step, so running out of either one is a truncation.
if (fields >= end || values >= end) {
return "The dex static field list is truncated";
}
fieldIndex += dexUleb128(&fields); // field_idx_diff
(void) dexUleb128(&fields); // access_flags
const int32_t value = dexEncodedInt(&values);
if ((size_t) (end - fieldIds) < ((size_t) fieldIndex + 1) * FF_DEX_FIELD_ID_SIZE) {
const uint32_t fieldIndexDiff = dexUleb128(&fields, end); // field_idx_diff
if (fieldIndexDiff > UINT32_MAX - fieldIndex) {
return "The dex field index is out of range";
}
fieldIndex += fieldIndexDiff;
(void) dexUleb128(&fields, end); // access_flags
const int32_t value = dexEncodedInt(&values, end);
if (!dexTableInRange(dex, end, fieldIds, (uint64_t) fieldIndex + 1, FF_DEX_FIELD_ID_SIZE)) {
return "The dex field table is out of range";
}
const char* name = dexString(dex, end, dexU32(fieldIds + (size_t) fieldIndex * FF_DEX_FIELD_ID_SIZE + FF_DEX_OFF_FIELD_ID_NAME));
@@ -335,8 +378,6 @@ const char* ffDexStaticInt(const char* jarPath, const char* classDescriptor, con
if (data == nullptr) {
return "No dex in the jar";
}
} else if (method == FF_ZIP_METHOD_STORED) {
dataSize = uncompressedSize;
} else if (method == FF_ZIP_METHOD_DEFLATED) {
#ifdef FF_HAVE_ZLIB
const char* error = inflateDex(data, dataSize, uncompressedSize, &mapping.inflated);
@@ -348,9 +389,14 @@ const char* ffDexStaticInt(const char* jarPath, const char* classDescriptor, con
#else
return "The jar deflates classes.dex and fastfetch was built without zlib";
#endif
} else {
} else if (method != FF_ZIP_METHOD_STORED) {
return "classes.dex uses an unsupported compression method";
}
// A STORED entry needs no further work: `dataSize` already holds the `compressedSize` that
// `findDexEntry` checked against the mapping, and for a STORED entry the payload is the file
// itself. The header's `uncompressedSize` is deliberately not used for it -- nothing bounds that
// value, so a corrupt one reaches past the end of the mapping, which is exactly what the dex
// header would then be validated against.
mapping.data = data;
mapping.size = dataSize;
+16 -2
View File
@@ -8,6 +8,20 @@
#include <windows.h>
// TODO: long paths on Windows.
//
// Every path that crosses into the wide API from this file is converted into a fixed
// `wchar_t[MAX_PATH]` buffer, and `RtlUTF8ToUnicodeN` refuses to fill it for a path of MAX_PATH
// characters or more (STATUS_BUFFER_TOO_SMALL) -- which makes each of those call sites a silent
// failure above that length rather than an error anyone sees. Raising a buffer on its own does not
// help: the wide APIs accept a longer path only either with a `\\?\` prefix on every absolute path
// (which also switches off the normalization the rest of the code relies on) or with a
// `longPathAware` manifest plus the LongPathsEnabled registry value.
//
// The plan is the second route: define MAX_PATH as 32768 program-wide, add the manifest flag, and
// size these buffers from the UTF-8 input rather than from the constant. It has to be done in one
// pass, which is why it is recorded here instead of being applied at the call sites the review
// happened to look at.
static bool createSubfolders(wchar_t* fileName) {
HANDLE hRoot = ffGetPeb()->ProcessParameters->CurrentDirectory.Handle;
bool closeRoot = false;
@@ -512,7 +526,7 @@ bool ffIsTerminal(int fd) {
}
bool ffRemoveFile(const char* fileName) {
wchar_t fileNameW[MAX_PATH];
wchar_t fileNameW[MAX_PATH]; // see the long path TODO at the top of this file
ULONG len;
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG) sizeof(fileNameW), &len, fileName, (ULONG) strlen(fileName) + 1))) {
return false;
@@ -524,7 +538,7 @@ bool ffRemoveFile(const char* fileName) {
}
uint64_t ffPathGetMtime(const char* path) {
wchar_t fileNameW[MAX_PATH];
wchar_t fileNameW[MAX_PATH]; // see the long path TODO at the top of this file
ULONG len;
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG) sizeof(fileNameW), &len, path, (ULONG) strlen(path) + 1))) {
return 0;
+27 -1
View File
@@ -149,7 +149,10 @@ static bool parseModuleJsonObject(const char* type, yyjson_val* jsonVal, yyjson_
static void prepareModuleJsonObject(const char* type, yyjson_val* module) {
switch (type[0]) {
#if !FF_MODULE_DISABLE_CPUUSAGE
// Both `cpuusage` and `command` start with a c, and they are disabled by separate options,
// so the block has to survive either one being off -- otherwise a build with only the other
// one enabled would never prepare this module (see the same guard in `commandoption.c`).
#if !FF_MODULE_DISABLE_CPUUSAGE || !FF_MODULE_DISABLE_COMMAND
case 'c':
case 'C': {
if (ffStrEqualsIgnCase(type, ffCPUUsageModuleInfo.name)) {
@@ -292,6 +295,7 @@ static const char* printJsonConfig(FFdata* data, bool prepare) {
}
yyjson_val* module = item;
bool gatedOnSucceeded = false;
const char* type = yyjson_get_str(module);
if (type) {
module = nullptr;
@@ -327,6 +331,8 @@ static const char* printJsonConfig(FFdata* data, bool prepare) {
if (!unsafe_yyjson_is_bool(previousSucceeded)) {
return "Property 'succeeded' in 'condition' must be a boolean";
}
gatedOnSucceeded = true;
if (prepare) {
// Whether this module is printed at all depends on the result of the previous
// one, which is only known in the print pass. Preparing it here would leave a
@@ -337,6 +343,26 @@ static const char* printJsonConfig(FFdata* data, bool prepare) {
// to be printed after all.
continue;
}
#if !FF_MODULE_DISABLE_COMMAND
// A `command` module that runs in parallel is exactly the pairing above, so this
// combination cannot be left to the print pass: the module would be handed the
// result prepared for the next `command` module and print that instead of its
// own output. Refused here rather than in the prepare pass, because returning
// from there would leave every later module unprepared -- which the print pass
// would then report as a failure of theirs.
// `parallel` is on by default, so leaving the key out is the parallel case too.
if (gatedOnSucceeded && ffStrEqualsIgnCase(yyjson_get_str(yyjson_obj_get(module, "type")), ffCommandModuleInfo.name)) {
yyjson_val* parallel = yyjson_obj_get(module, "parallel");
if (parallel == nullptr || yyjson_get_bool(parallel)) {
return "Module \"command\" cannot be combined with \"condition.succeeded\" while it runs in "
"parallel: whether it is printed is only known in the print pass, so it cannot be "
"prepared, and the prepared results are matched to modules by position. Set "
"\"parallel\": false on it, or drop the condition.";
}
}
#endif
if (succeeded != unsafe_yyjson_get_bool(previousSucceeded)) {
continue;
}
+17 -6
View File
@@ -325,7 +325,12 @@ int ffNetworkingChunkedComplete(const char* body, uint32_t bodyLen, uint32_t* co
// Keeps the status line and every header except `dropHeader` and `Content-Length`,
// then emits a `Content-Length` matching the (already decoded) body.
// `body` may point into `buffer->chars`; it is copied before `buffer` is released.
static void rebuildResponse(FFstrbuf* buffer, uint32_t headerEnd, const char* body, uint32_t bodyLen, const char* dropHeader) {
//
// Returns the offset of the `\r` that opens the terminating CRLF CRLF of the new response. The
// rebuild drops a header line and rewrites another, so the header it produces is shorter than the
// one that went in: the offset the caller held before is no longer the one that describes this
// buffer, and handing it on would point into the middle of the body.
static uint32_t rebuildResponse(FFstrbuf* buffer, uint32_t headerEnd, const char* body, uint32_t bodyLen, const char* dropHeader) {
FF_STRBUF_AUTO_DESTROY newBuffer = ffStrbufCreateA(headerEnd + bodyLen + 64);
uint32_t pos = 0;
@@ -349,18 +354,24 @@ static void rebuildResponse(FFstrbuf* buffer, uint32_t headerEnd, const char* bo
ffStrbufDestroy(buffer);
ffStrbufInitMove(buffer, &newBuffer);
// The terminating CRLF CRLF is the four bytes in front of the body
return buffer->length - bodyLen - 4;
}
bool ffNetworkingDecodeChunked(FFstrbuf* buffer, uint32_t headerEnd) {
bool ffNetworkingDecodeChunked(FFstrbuf* buffer, uint32_t* headerEnd) {
assert(buffer->allocated > 0);
// Held in a local because the rebuild below hands a different value back
const uint32_t headerLength = *headerEnd;
// The header block is terminated by CR LF CR LF
if (headerEnd + 4 > buffer->length) {
if (headerLength + 4 > buffer->length) {
return false;
}
char* body = buffer->chars + headerEnd + 4;
uint32_t bodyLen = buffer->length - headerEnd - 4;
char* body = buffer->chars + headerLength + 4;
uint32_t bodyLen = buffer->length - headerLength - 4;
uint32_t consumed = 0;
if (ffNetworkingChunkedComplete(body, bodyLen, &consumed) != 1) {
@@ -391,6 +402,6 @@ bool ffNetworkingDecodeChunked(FFstrbuf* buffer, uint32_t headerEnd) {
}
FF_DEBUG("Decoded chunked body: %u bytes encoded, %u bytes decoded", bodyLen, outLen);
rebuildResponse(buffer, headerEnd, out, outLen, "Transfer-Encoding:");
*headerEnd = rebuildResponse(buffer, headerLength, out, outLen, "Transfer-Encoding:");
return true;
}
+9 -3
View File
@@ -466,7 +466,10 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
return "Response too large";
}
FF_DEBUG("Receive buffer is full, extending it");
ffStrbufEnsureFreeNoCheck(buffer, buffer->allocated);
// Asking for exactly the room that is left under the cap, rather than for as much as
// the buffer holds again: the allocation is rounded up to a power of two, so a doubling
// from a size that is not one of those lands past the cap the check above just cleared.
ffStrbufEnsureFreeNoCheck(buffer, FF_NETWORKING_MAX_RESPONSE_SIZE - buffer->length - 1);
}
// When the remaining length is known, ask for exactly that much. MSG_WAITALL then
@@ -589,7 +592,7 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
return "No HTTP header end found";
}
if (chunked && !ffNetworkingDecodeChunked(buffer, headerEnd)) {
if (chunked && !ffNetworkingDecodeChunked(buffer, &headerEnd)) {
return "Failed to decode chunked response";
}
@@ -599,7 +602,10 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
}
FF_DEBUG("Received valid HTTP 200 response, content %u bytes, total %u bytes", contentLength, buffer->length);
if (contentLength > 0 && buffer->length != contentLength + headerEnd + 4) {
// A chunked response was framed by its last-chunk and has been rewritten around it, so any
// `Content-Length` the server also sent describes a body that no longer exists. RFC 9112 6.3
// says not to send both; when one does, the chunked framing is the one that was acted on.
if (!chunked && contentLength > 0 && buffer->length != contentLength + headerEnd + 4) {
FF_DEBUG("Received content length mismatches: %u != %u", buffer->length, contentLength + headerEnd + 4);
return "Content length mismatch";
}
+9 -3
View File
@@ -298,7 +298,10 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
return "Response too large";
}
FF_DEBUG("Receive buffer is full, extending it");
ffStrbufEnsureFreeNoCheck(buffer, buffer->allocated);
// Asking for exactly the room that is left under the cap, rather than for as much as
// the buffer holds again: the allocation is rounded up to a power of two, so a doubling
// from a size that is not one of those lands past the cap the check above just cleared.
ffStrbufEnsureFreeNoCheck(buffer, FF_NETWORKING_MAX_RESPONSE_SIZE - buffer->length - 1);
}
FF_DEBUG("Data reception loop #%d, current buffer size: %u, available space: %u",
@@ -413,7 +416,7 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
return "No HTTP header end found";
}
if (chunked && !ffNetworkingDecodeChunked(buffer, headerEnd)) {
if (chunked && !ffNetworkingDecodeChunked(buffer, &headerEnd)) {
return "Failed to decode chunked response";
}
@@ -425,7 +428,10 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
contentLength,
buffer->length);
if (contentLength > 0 && buffer->length != contentLength + headerEnd + 4) {
// A chunked response was framed by its last-chunk and has been rewritten around it, so any
// `Content-Length` the server also sent describes a body that no longer exists. RFC 9112 6.3
// says not to send both; when one does, the chunked framing is the one that was acted on.
if (!chunked && contentLength > 0 && buffer->length != contentLength + headerEnd + 4) {
FF_DEBUG("Received content length mismatches: %u != %u", buffer->length, contentLength + headerEnd + 4);
return "Content length mismatch";
}
+10 -4
View File
@@ -132,12 +132,18 @@ static inline ssize_t ffReadFDData(FFNativeFD fd, size_t dataSize, void* data) {
return ffReadFDData(fd, dataSize, data);
}
// `CreateFileA` reads the path in the ANSI code page, so a UTF-8 path holding anything outside ASCII
// names a different file, or none at all. The wide API is the one that takes what UTF-8 decodes to,
// and the conversion is done the same way ffPathExists() below does it.
[[gnu::nonnull(1, 2)]] static inline bool ffAppendFileBuffer(const char* fileName, FFstrbuf* buffer) {
FF_AUTO_CLOSE_FD FFNativeFD fd =
#ifndef _WIN32
open(fileName, O_RDONLY | O_CLOEXEC);
#ifdef _WIN32
wchar_t fileNameW[MAX_PATH];
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(fileNameW, (ULONG) sizeof(fileNameW), nullptr, fileName, (ULONG) strlen(fileName) + 1))) {
return false;
}
FF_AUTO_CLOSE_FD FFNativeFD fd = CreateFileW(fileNameW, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
#else
CreateFileA(fileName, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
FF_AUTO_CLOSE_FD FFNativeFD fd = open(fileName, O_RDONLY | O_CLOEXEC);
#endif
if (!ffIsValidNativeFD(fd)) {
+4 -1
View File
@@ -60,7 +60,10 @@ typedef struct FFNetworkingState {
// Decodes a `Transfer-Encoding: chunked` body in place and rewrites the response with a
// `Content-Length` header in place of `Transfer-Encoding`.
[[gnu::nonnull(1), nodiscard]] bool ffNetworkingDecodeChunked(FFstrbuf* buffer, uint32_t headerEnd);
// `headerEnd` is updated in place: the rewrite changes the header length, so the value the caller
// passed in no longer describes the buffer afterwards. Callers must use the new one for anything
// that addresses the body, such as ffNetworkingDecompressGzip().
[[gnu::nonnull(1, 2), nodiscard]] bool ffNetworkingDecodeChunked(FFstrbuf* buffer, uint32_t* headerEnd);
// Result of parsing a `Transfer-Encoding` header value
typedef enum FFNetworkingTransferEncoding {
+12 -1
View File
@@ -230,7 +230,15 @@ std::int32_t STDMETHODCALLTYPE FFWinrtHstringIterator::GetMany(std::uint32_t cap
HSTRING* hstrings = reinterpret_cast<HSTRING*>(values);
for (std::uint32_t i = 0; i < taken; ++i) {
WindowsDuplicateString(*(HSTRING*) ffListGet(&owner->strings, sizeof(HSTRING), index + i), &hstrings[i]);
HRESULT hr = WindowsDuplicateString(*(HSTRING*) ffListGet(&owner->strings, sizeof(HSTRING), index + i), &hstrings[i]);
if (FAILED(hr)) {
// Whatever was handed out already belongs to the caller, so a half filled array has to
// be taken back before the error goes out: the caller has no way to free a partial one.
for (std::uint32_t j = 0; j < i; ++j) {
WindowsDeleteString(hstrings[j]);
}
return hr;
}
}
index += taken;
@@ -259,6 +267,9 @@ HRESULT ffWinrtCreateHstringIterable(const wchar_t* const* items, uint32_t count
for (uint32_t i = 0; i < count; ++i) {
HSTRING* slot = (HSTRING*) ffListAdd(&iterable->strings, sizeof(HSTRING));
// ffListAdd does not zero the slot it hands out, and Release() walks every one of them, so
// the slot has to hold a usable HSTRING before WindowsCreateString gets a chance to fail.
*slot = nullptr;
HRESULT hr = WindowsCreateString(items[i], (UINT32) ::wcslen(items[i]), slot);
if (FAILED(hr)) {
iterable->Release();
+2
View File
@@ -99,6 +99,8 @@ static HRESULT ffWaitForAsyncOperation(TOperationAbi* operation, TResultAbi** re
template <typename TResultProjection, typename TOperation>
static HRESULT ffRunAndWait(TOperation&& operation, abi_t<TResultProjection>** result) {
*result = nullptr;
FF_AUTO_RELEASE_COM_OBJECT abi_t<winrt::Windows::Foundation::IAsyncOperation<TResultProjection>>* opResult = nullptr;
HRESULT hr = operation(reinterpret_cast<void**>(&opResult));
if (FAILED(hr) || !opResult) {