From 58463bff4ead31b6b58f5e6b181914c856a33537 Mon Sep 17 00:00:00 2001 From: Adrian Cochrane Date: Wed, 5 Jun 2024 15:01:01 +1200 Subject: [PATCH] Fuzz test roundtrip conversions between C & Haskell datastructures! --- cbits/transcode.c | 480 +++++++++++++++++------ cbits/transcode.h | 8 + fontconfig-pure.cabal | 4 +- lib/Graphics/Text/Font/Choose.hs | 6 +- lib/Graphics/Text/Font/Choose/CharSet.hs | 9 +- lib/Graphics/Text/Font/Choose/FontSet.hs | 6 +- lib/Graphics/Text/Font/Choose/LangSet.hs | 13 +- lib/Graphics/Text/Font/Choose/Pattern.hs | 31 +- lib/Graphics/Text/Font/Choose/Range.hs | 9 +- lib/Graphics/Text/Font/Choose/StrSet.hs | 7 +- lib/Graphics/Text/Font/Choose/Value.hs | 24 +- test/Main.hs | 35 ++ 12 files changed, 489 insertions(+), 143 deletions(-) diff --git a/cbits/transcode.c b/cbits/transcode.c index 5d33e6e..2ef8ab5 100644 --- a/cbits/transcode.c +++ b/cbits/transcode.c @@ -3,6 +3,72 @@ #include #include #include +#include + +struct bytes { + uint8_t *start; + size_t offset; + size_t length; +}; + +bool bytes_reader(struct cmp_ctx_s *ctx, void *data, size_t limit) { + struct bytes *bytes = ctx->buf; + if (bytes->offset + limit > bytes->length) return false; + memcpy(data, bytes->start + bytes->offset, limit); + bytes->offset += limit; + return true; +} + +bool bytes_skipper(struct cmp_ctx_s *ctx, size_t count) { + struct bytes *bytes = ctx->buf; + if (bytes->offset + count > bytes->length) return false; + bytes->offset += count; + return true; +} + +size_t bytes_writer(struct cmp_ctx_s *ctx, const void *data, size_t count) { + struct bytes *bytes = ctx->buf; + if (bytes->offset + count > bytes->length) { + bytes->start = realloc(bytes->start, 2*bytes->length); + if (bytes->start == NULL) return 0; + bytes->length = 2*bytes->length; + } + memcpy(bytes->start + bytes->offset, data, count); + bytes->offset += count; + return count; +} + +bool cmp_bytes_init(cmp_ctx_t *ctx, uint8_t *buf, size_t length) { + struct bytes *inner = malloc(sizeof(struct bytes)); + if (inner == NULL) return false; + inner->start = buf; + inner->offset = 0; + inner->length = length; + cmp_init(ctx, inner, bytes_reader, bytes_skipper, bytes_writer); + return true; +} + +bool cmp_bytes_alloc(cmp_ctx_t *ctx, size_t length) { + uint8_t *bytes = malloc(length); + if (bytes == NULL) return false; + return cmp_bytes_init(ctx, bytes, length); +} + +void cmp_bytes_free(cmp_ctx_t *ctx) { + struct bytes *bytes = ctx->buf; + free(bytes->start); + free(bytes); +} + +uint8_t *cmp_bytes_take(cmp_ctx_t *ctx, size_t *length) { + struct bytes *bytes = ctx->buf; + uint8_t *ret = bytes->start; + if (length != NULL) *length = bytes->offset; + free(bytes); + return ret; +} + +// The encoders, decoders, & (for testing) round-trip routines themselves! FcStrSet *decodeStrSet(cmp_ctx_t *bytes) { if (bytes == NULL) return NULL; @@ -72,21 +138,46 @@ bool encodeStrSet(cmp_ctx_t *bytes, FcStrSet *data) { return ret; } -FcCharSet *decodeCharSet(cmp_ctx_t *bytes) { - if (bytes == NULL) return NULL; +uint8_t *testStrSet(uint8_t *in, size_t in_length, size_t *length) { + if (in == NULL) return NULL; + + cmp_ctx_t bytes; + if (!cmp_bytes_init(&bytes, in, in_length)) return false; + FcStrSet *decoded = decodeStrSet(&bytes); + cmp_bytes_take(&bytes, NULL); + if (decoded == NULL) return NULL; - int8_t type = 'c'; uint32_t size = 0; - // Special unambiguous empty encoding! - if (cmp_read_ext_marker(bytes, &type, &size) && type == 'c' && size == 0) { - return FcCharSetCreate(); + if (!cmp_bytes_alloc(&bytes, in_length)) {FcStrSetDestroy(decoded); goto fail;} + bool ok = encodeStrSet(&bytes, decoded); + FcStrSetDestroy(decoded); + if (ok) return cmp_bytes_take(&bytes, length); + else { +fail: + cmp_bytes_free(&bytes); + return NULL; } +} - if (!cmp_read_array(bytes, &size)) return NULL; +FcCharSet *decodeObjCharSet(cmp_ctx_t *bytes, cmp_object_t *head, cmp_object_t *first) { + if (bytes == NULL || head == NULL) return NULL; + + int8_t type; uint32_t size; + if (cmp_object_as_ext(head, &type, &size) && type == 'c' && size == 0) + return FcCharSetCreate(); // Special unambiguous empty encoding! + else if (!cmp_object_as_array(head, &size)) return NULL; FcCharSet *ret = FcCharSetCreate(); if (ret == NULL) return NULL; FcChar32 prev = 0; + /* Allow peek-ahead to different based on type inside list */ + if (size > 0 && first != NULL) { + if (!cmp_object_as_uint(first, &prev)) goto fail; + if (!FcCharSetAddChar(ret, prev)) goto fail; + size--; // We've read the first entry of the array! + } + + /* The proper decoding! Diff-compressed to minimize temp-memory used. */ for (uint32_t i = 0; i < size; i++) { uint32_t x; if (!cmp_read_uint(bytes, &x)) goto fail; @@ -99,6 +190,14 @@ fail: return NULL; } +FcCharSet *decodeCharSet(cmp_ctx_t *bytes) { + if (bytes == NULL) return NULL; + + cmp_object_t head; + if (!cmp_read_object(bytes, &head)) return NULL; + return decodeObjCharSet(bytes, &head, NULL); +} + bool encodeCharSet(cmp_ctx_t *bytes, const FcCharSet *data) { if (bytes == NULL || data == NULL) return false; @@ -111,32 +210,73 @@ bool encodeCharSet(cmp_ctx_t *bytes, const FcCharSet *data) { FcChar32 map[FC_CHARSET_MAP_SIZE]; FcChar32 next; - FcChar32 c = FcCharSetFirstPage(data, map, &next); + FcChar32 base = FcCharSetFirstPage(data, map, &next); FcChar32 prev = 0; - while (c != FC_CHARSET_DONE) { - if (!cmp_write_uinteger(bytes, c - prev)) return false; - prev = c; - count++; - c = FcCharSetNextPage(data, map, &next); + while (base != FC_CHARSET_DONE) { + for (unsigned int i = 0; i < FC_CHARSET_MAP_SIZE; i++) for (unsigned int j = 0; j < 32; j++) { + if (map[i] & (1 << j)) { + FcChar32 c = base + i*32 + j; + if (!cmp_write_uinteger(bytes, c - prev)) return false; + prev = c; + count++; + } + } + base = FcCharSetNextPage(data, map, &next); } - assert(size == count); + //assert(size == count); return true; } -FcLangSet *decodeLangSet(cmp_ctx_t *bytes) { - if (bytes == NULL) return NULL; - uint32_t size; - if (!cmp_read_array(bytes, &size)) return NULL; +uint8_t *testCharSet(uint8_t *in, size_t in_length, size_t *length) { + if (in == NULL) return NULL; + + cmp_ctx_t bytes; + if (!cmp_bytes_init(&bytes, in, in_length)) return false; + FcCharSet *decoded = decodeCharSet(&bytes); + cmp_bytes_take(&bytes, NULL); + if (decoded == NULL) {return NULL;} + + if (!cmp_bytes_alloc(&bytes, in_length)) { + FcCharSetDestroy(decoded); + goto fail; + } + bool ok = encodeCharSet(&bytes, decoded); + FcCharSetDestroy(decoded); + if (ok) return cmp_bytes_take(&bytes, length); + else { +fail: + cmp_bytes_free(&bytes); + return NULL; + } +} + +FcLangSet *decodeObjLangSet(cmp_ctx_t *bytes, cmp_object_t *head, cmp_object_t *first) { + uint32_t size = 0; + if (bytes == NULL || head == NULL || !cmp_object_as_array(head, &size)) return NULL; FcLangSet *ret = FcLangSetCreate(); if (ret == NULL) return NULL; + + /* Allow peek ahead to differentiate based on type stored in list */ + if (first != NULL && size > 0) { + #define LANG_BUF_SZ 32 // If I'm reading FontConfig code right, 16's enough, but be generous... + char buf[LANG_BUF_SZ]; + if (!cmp_object_to_str(bytes, first, buf, LANG_BUF_SZ)) goto fail; + char *lang = FcLangNormalize(buf); + if (lang == NULL) goto fail; + if (!FcLangSetAdd(ret, lang)) {free(lang); goto fail;} + free(lang); + size--; + } + + /* The proper decoding! */ for (uint32_t i = 0; i < size; i++) { char lang[10]; uint32_t str_size = 10; if (!cmp_read_str(bytes, lang, &str_size)) goto fail; char *lang2 = FcLangNormalize(lang); if (lang2 == NULL) goto fail; - if (!FcLangSetAdd(ret, lang2)) goto fail; + if (!FcLangSetAdd(ret, lang2)) {free(lang2); goto fail;} free(lang2); } return ret; @@ -145,6 +285,13 @@ fail: return NULL; } +FcLangSet *decodeLangSet(cmp_ctx_t *bytes) { + if (bytes == NULL) return NULL; + cmp_object_t head; + if (!cmp_read_object(bytes, &head)) return NULL; + return decodeObjLangSet(bytes, &head, NULL); +} + bool encodeLangSet(cmp_ctx_t *bytes, const FcLangSet *data) { if (bytes == NULL || data == NULL) return false; FcStrSet *langs = FcLangSetGetLangs(data); @@ -154,6 +301,29 @@ bool encodeLangSet(cmp_ctx_t *bytes, const FcLangSet *data) { return ret; } +uint8_t *testLangSet(uint8_t *in, size_t in_length, size_t *length) { + if (in == NULL) return NULL; + + cmp_ctx_t bytes; + if (!cmp_bytes_init(&bytes, in, in_length)) return false; + FcLangSet *decoded = decodeLangSet(&bytes); + cmp_bytes_take(&bytes, NULL); + if (decoded == NULL) return NULL; + + if (!cmp_bytes_alloc(&bytes, in_length)) { + FcLangSetDestroy(decoded); + goto fail; + } + bool ok = encodeLangSet(&bytes, decoded); + FcLangSetDestroy(decoded); + if (ok) return cmp_bytes_take(&bytes, length); + else { +fail: + cmp_bytes_free(&bytes); + return NULL; + } +} + FcObjectSet *decodeObjectSet(cmp_ctx_t *bytes) { if (bytes == NULL) return NULL; @@ -175,11 +345,9 @@ fail: // No corresponding objectset encoder. -FcRange *decodeRange(cmp_ctx_t *bytes) { - if (bytes == NULL) return NULL; - - uint32_t size; - if (!cmp_read_map(bytes, &size)) return NULL; +FcRange *decodeObjRange(cmp_ctx_t *bytes, cmp_object_t *head) { + uint32_t size = 0; + if (bytes == NULL || head == NULL || !cmp_object_as_map(head, &size)) return NULL; double range[2]; for (uint32_t i = 0; i < size; i++) { @@ -190,6 +358,14 @@ FcRange *decodeRange(cmp_ctx_t *bytes) { return FcRangeCreateDouble(range[0], range[1]); } +FcRange *decodeRange(cmp_ctx_t *bytes) { + if (bytes == NULL) return NULL; + + cmp_object_t head; + if (!cmp_read_object(bytes, &head)) return NULL; + return decodeObjRange(bytes, &head); +} + bool encodeRange(cmp_ctx_t *bytes, const FcRange *data) { if (bytes == NULL || data == NULL) return false; @@ -204,14 +380,40 @@ bool encodeRange(cmp_ctx_t *bytes, const FcRange *data) { return true; } -FcMatrix *decodeMatrix(cmp_ctx_t *bytes) { - if (bytes == NULL) return NULL; +uint8_t *testRange(uint8_t *in, size_t in_length, size_t *length) { + if (in == NULL) return NULL; - uint32_t size; - if (!cmp_read_array(bytes, &size) || size != 4) return NULL; + cmp_ctx_t bytes; + if (!cmp_bytes_init(&bytes, in, in_length)) return false; + FcRange *decoded = decodeRange(&bytes); + cmp_bytes_take(&bytes, NULL); + if (decoded == NULL) return NULL; + + if (!cmp_bytes_alloc(&bytes, in_length)) {FcRangeDestroy(decoded); goto fail;} + bool ok = encodeRange(&bytes, decoded); + FcRangeDestroy(decoded); + if (ok) return cmp_bytes_take(&bytes, length); + else { +fail: + cmp_bytes_free(&bytes); + return NULL; + } +} + +FcMatrix *decodeObjMatrix(cmp_ctx_t *bytes, cmp_object_t *head, cmp_object_t *next) { + uint32_t size = 0; + if (bytes == NULL || head == NULL) return NULL; + if (!cmp_object_as_array(head, &size) || size != 4) return NULL; + + cmp_object_t first; + if (next == NULL) { + if (!cmp_read_object(bytes, &first)) return NULL; + } else first = *next; + if (!cmp_object_is_double(&first)) return NULL; FcMatrix *ret = malloc(sizeof(FcMatrix)); - if (!cmp_read_double(bytes, &ret->xx)) goto fail; + if (ret == NULL) return NULL; + if (!cmp_object_as_double(&first, &ret->xx)) goto fail; if (!cmp_read_double(bytes, &ret->xy)) goto fail; if (!cmp_read_double(bytes, &ret->yx)) goto fail; if (!cmp_read_double(bytes, &ret->yy)) goto fail; @@ -221,6 +423,14 @@ fail: return NULL; } +FcMatrix *decodeMatrix(cmp_ctx_t *bytes) { + if (bytes == NULL) return NULL; + + cmp_object_t head; + if (!cmp_read_object(bytes, &head)) return NULL; + return decodeObjMatrix(bytes, &head, NULL); +} + bool encodeMatrix(cmp_ctx_t *bytes, const FcMatrix *data) { if (bytes == NULL || data == NULL) return NULL; @@ -232,29 +442,65 @@ bool encodeMatrix(cmp_ctx_t *bytes, const FcMatrix *data) { return true; } +uint8_t *testMatrix(uint8_t *in, size_t in_length, size_t *length) { + if (in == NULL) return NULL; + + cmp_ctx_t bytes; + if (!cmp_bytes_init(&bytes, in, in_length)) return false; + FcMatrix *decoded = decodeMatrix(&bytes); + cmp_bytes_take(&bytes, NULL); + if (decoded == NULL) return NULL; + + if (!cmp_bytes_alloc(&bytes, in_length)) {free(decoded); goto fail;} + bool ok = encodeMatrix(&bytes, decoded); + free(decoded); + if (ok) return cmp_bytes_take(&bytes, length); + else { +fail: + cmp_bytes_free(&bytes); + return NULL; + } +} + bool decodeValue(cmp_ctx_t *bytes, FcValue *out) { if (bytes == NULL || out == NULL) return false; + cmp_object_t head; + if (!cmp_read_object(bytes, &head)) return NULL; bool b; - uint32_t str_size; - if (cmp_read_nil(bytes)) out->type = FcTypeVoid; - else if (cmp_read_int(bytes, &out->u.i)) out->type = FcTypeInteger; - else if (cmp_read_double(bytes, &out->u.d)) out->type = FcTypeDouble; - else if (cmp_read_str_size(bytes, &str_size)) { + uint32_t size; + if (head.type == CMP_TYPE_NIL) out->type = FcTypeVoid; + else if (cmp_object_as_int(&head, &out->u.i)) out->type = FcTypeInteger; + else if (cmp_object_as_double(&head, &out->u.d)) out->type = FcTypeDouble; + else if (cmp_object_as_str(&head, &size)) { out->type = FcTypeString; - char *str = malloc(str_size); + size++; // Include space for Nil byte! + char *str = malloc(size); out->u.s = str; - return cmp_read_str(bytes, str, &str_size); - } else if (cmp_read_bool(bytes, &b)) { + bool ok = cmp_object_to_str(bytes, &head, str, size); + if (!ok) free(str); + return ok; + } else if (cmp_object_as_bool(&head, &b)) { out->type = FcTypeBool; out->u.b = b ? FcTrue : FcFalse; // Didn't auto-convert. - } else if ((out->u.m = decodeMatrix(bytes)) != NULL) out->type = FcTypeMatrix; - // For ease of encoding lists are treated as lang sets. - // Hence LangSets take priority during decode! - else if ((out->u.l = decodeLangSet(bytes)) != NULL) out->type = FcTypeLangSet; - else if ((out->u.c = decodeCharSet(bytes)) != NULL) out->type = FcTypeCharSet; - // Not supporting FcTypeFcFace - else if ((out->u.r = decodeRange(bytes)) != NULL) out->type = FcTypeRange; + } else if (cmp_object_as_array(&head, &size)) { + cmp_object_t *first = NULL; + cmp_object_t first_; + if (size > 0) { + first = &first_; + if (!cmp_read_object(bytes, first)) return false; + } + if ((out->u.m = decodeObjMatrix(bytes, &head, first)) != NULL) + out->type = FcTypeMatrix; + // For ease of encoding empty lists are treated as lang sets. + // Hence LangSets take priority during decode! + else if ((out->u.l = decodeObjLangSet(bytes, &head, first)) != NULL) + out->type = FcTypeLangSet; + else if ((out->u.c = decodeObjCharSet(bytes, &head, first)) != NULL) + out->type = FcTypeCharSet; + } // Not supporting FcTypeFcFace + else if ((out->u.r = decodeObjRange(bytes, &head)) != NULL) + out->type = FcTypeRange; else return false; return true; } @@ -288,6 +534,27 @@ bool encodeValue(cmp_ctx_t *bytes, FcValue *data) { } } +uint8_t *testValue(uint8_t *in, size_t in_length, size_t *length) { + if (in == NULL) return NULL; + + cmp_ctx_t bytes; + if (!cmp_bytes_init(&bytes, in, in_length)) return false; + FcValue decoded; + bool ok = decodeValue(&bytes, &decoded); + cmp_bytes_take(&bytes, NULL); + if (!ok) return NULL; + + if (!cmp_bytes_alloc(&bytes, in_length)) {FcValueDestroy(decoded); goto fail;} + ok = encodeValue(&bytes, &decoded); + FcValueDestroy(decoded); + if (ok) return cmp_bytes_take(&bytes, length); + else { +fail: + cmp_bytes_free(&bytes); + return NULL; + } +} + FcPattern *decodePattern(cmp_ctx_t *bytes) { if (bytes == NULL) return NULL; @@ -295,6 +562,7 @@ FcPattern *decodePattern(cmp_ctx_t *bytes) { if (!cmp_read_map(bytes, &size)) return NULL; FcPattern *ret = FcPatternCreate(); + if (ret == NULL) return NULL; for (uint32_t i = 0; i < size; i++) { char object[20]; uint32_t osize = 20; @@ -304,17 +572,23 @@ FcPattern *decodePattern(cmp_ctx_t *bytes) { for (uint32_t j = 0; j < vsize; j++) { uint32_t tsize; if (!cmp_read_array(bytes, &tsize) || tsize != 2) goto fail; + + cmp_object_t strength; + if (!cmp_read_object(bytes, &strength)) goto fail; bool is_strong = false; - if (cmp_read_bool(bytes, &is_strong)) {} - else if (cmp_read_nil(bytes)) {} + if (cmp_object_as_bool(&strength, &is_strong)) {} + else if (cmp_object_is_nil(&strength)) {} else goto fail; + FcValue val; if (!decodeValue(bytes, &val)) goto fail; + //FcValuePrint(val); if (is_strong) { - if (!FcPatternAdd(ret, object, val, true)) goto fail; + if (!FcPatternAdd(ret, object, val, true)) {FcValuePrint(val); goto fail;} } else { - if (!FcPatternAddWeak(ret, object, val, true)) goto fail; + if (!FcPatternAddWeak(ret, object, val, true)) {FcValuePrint(val); goto fail;} } + FcValueDestroy(val); } } return ret; @@ -332,7 +606,7 @@ bool encodePattern(cmp_ctx_t *bytes, FcPattern *data) { FcPatternIter iter; FcPatternIterStart(data, &iter); int count = 0; - while (FcPatternIterNext(data, &iter)) { + do { count++; const char *obj = FcPatternIterGetObject(data, &iter); if (!cmp_write_str(bytes, obj, strlen(obj))) return false; @@ -360,11 +634,31 @@ bool encodePattern(cmp_ctx_t *bytes, FcPattern *data) { } if (!encodeValue(bytes, &val)) return false; } - } + } while (FcPatternIterNext(data, &iter)); assert(size == count); return true; } +uint8_t *testPattern(uint8_t *in, size_t in_length, size_t *length) { + if (in == NULL) return NULL; + + cmp_ctx_t bytes; + if (!cmp_bytes_init(&bytes, in, in_length)) return false; + FcPattern *decoded = decodePattern(&bytes); + cmp_bytes_take(&bytes, NULL); + if (decoded == NULL) return NULL; + + if (!cmp_bytes_alloc(&bytes, in_length)) {FcPatternDestroy(decoded); goto fail;} + bool ok = encodePattern(&bytes, decoded); + FcPatternDestroy(decoded); + if (ok) return cmp_bytes_take(&bytes, length); + else { +fail: + cmp_bytes_free(&bytes); + return NULL; + } +} + FcFontSet *decodeFontSet(cmp_ctx_t *bytes) { if (bytes == NULL) return NULL; @@ -416,6 +710,29 @@ bool encodeFontSet(cmp_ctx_t *bytes, FcFontSet *data) { return true; } +uint8_t *testFontSet(uint8_t *in, size_t in_length, size_t *length) { + if (in == NULL) return NULL; + + cmp_ctx_t bytes; + if (!cmp_bytes_init(&bytes, in, in_length)) return false; + FcFontSet *decoded = decodeFontSet(&bytes); + cmp_bytes_take(&bytes, NULL); + if (decoded == NULL) return NULL; + + if (!cmp_bytes_alloc(&bytes, in_length)) { + FcFontSetDestroy(decoded); + goto fail; + } + bool ok = encodeFontSet(&bytes, decoded); + FcFontSetDestroy(decoded); + if (ok) return cmp_bytes_take(&bytes, length); + else { +fail: + cmp_bytes_free(&bytes); + return NULL; + } +} + bool encodeRenderableFontSet(cmp_ctx_t *bytes, FcConfig *conf, FcPattern *pat, FcFontSet *data) { if (bytes == NULL || data == NULL) return false; @@ -447,66 +764,3 @@ bool encodeResult(cmp_ctx_t *bytes, FcResult res) { return cmp_write_str(bytes, "ErrOther", strlen("ErrOther")); } } - -struct bytes { - uint8_t *start; - size_t offset; - size_t length; -}; - -bool bytes_reader(struct cmp_ctx_s *ctx, void *data, size_t limit) { - struct bytes *bytes = ctx->buf; - if (bytes->offset + limit > bytes->length) return false; - memcpy(data, bytes->start + bytes->offset, limit); - bytes->offset += limit; - return true; -} - -bool bytes_skipper(struct cmp_ctx_s *ctx, size_t count) { - struct bytes *bytes = ctx->buf; - if (bytes->offset + count > bytes->length) return false; - bytes->offset += count; - return true; -} - -size_t bytes_writer(struct cmp_ctx_s *ctx, const void *data, size_t count) { - struct bytes *bytes = ctx->buf; - if (bytes->offset + count > bytes->length) { - bytes->start = realloc(bytes->start, 2*bytes->length); - if (bytes->start == NULL) return 0; - bytes->length = 2*bytes->length; - } - memcpy(bytes->start + bytes->offset, data, count); - bytes->offset += count; - return count; -} - -bool cmp_bytes_init(cmp_ctx_t *ctx, uint8_t *buf, size_t length) { - struct bytes *inner = malloc(sizeof(struct bytes)); - if (inner == NULL) return false; - inner->start = buf; - inner->offset = 0; - inner->length = length; - cmp_init(ctx, inner, bytes_reader, bytes_skipper, bytes_writer); - return true; -} - -bool cmp_bytes_alloc(cmp_ctx_t *ctx, size_t length) { - uint8_t *bytes = malloc(length); - if (bytes == NULL) return false; - return cmp_bytes_init(ctx, bytes, length); -} - -void cmp_bytes_free(cmp_ctx_t *ctx) { - struct bytes *bytes = ctx->buf; - free(bytes->start); - free(bytes); -} - -uint8_t *cmp_bytes_take(cmp_ctx_t *ctx, size_t *length) { - struct bytes *bytes = ctx->buf; - uint8_t *ret = bytes->start; - if (length != NULL) *length = bytes->offset; - free(bytes); - return ret; -} diff --git a/cbits/transcode.h b/cbits/transcode.h index b978b91..018b169 100644 --- a/cbits/transcode.h +++ b/cbits/transcode.h @@ -4,23 +4,31 @@ FcStrSet *decodeStrSet(cmp_ctx_t *bytes); FcStrSet *decodeFileSet(cmp_ctx_t *bytes); bool encodeStrSet(cmp_ctx_t *bytes, FcStrSet *data); +uint8_t *testStrSet(uint8_t *in, size_t in_length, size_t *length); FcCharSet *decodeCharSet(cmp_ctx_t *bytes); bool encodeStrList(cmp_ctx_t *bytes, const FcStrList *data); bool encodeCharSet(cmp_ctx_t *bytes, const FcCharSet *data); +uint8_t *testCharSet(uint8_t *in, size_t in_length, size_t *length); FcLangSet *decodeLangSet(cmp_ctx_t *bytes); bool encodeLangSet(cmp_ctx_t *bytes, const FcLangSet *data); +uint8_t *testLangSet(uint8_t *in, size_t in_length, size_t *length); FcObjectSet *decodeObjectSet(cmp_ctx_t *bytes); FcRange *decodeRange(cmp_ctx_t *bytes); bool encodeRange(cmp_ctx_t *bytes, const FcRange *data); +uint8_t *testRange(uint8_t *in, size_t in_length, size_t *length); FcMatrix *decodeMatrix(cmp_ctx_t *bytes); bool encodeMatrix(cmp_ctx_t *bytes, const FcMatrix *data); +uint8_t *testMatrix(uint8_t *in, size_t in_length, size_t *length); bool decodeValue(cmp_ctx_t *bytes, FcValue *out); bool encodeValue(cmp_ctx_t *bytes, FcValue *data); +uint8_t *testValue(uint8_t *in, size_t in_length, size_t *length); FcPattern *decodePattern(cmp_ctx_t *bytes); bool encodePattern(cmp_ctx_t *bytes, FcPattern *data); +uint8_t *testPattern(uint8_t *in, size_t in_length, size_t *length); FcFontSet *decodeFontSet(cmp_ctx_t *bytes); FcFontSet **decodeFontSets(cmp_ctx_t *bytes, size_t *nsets); bool encodeFontSet(cmp_ctx_t *bytes, FcFontSet *data); +uint8_t *testFontSet(uint8_t *in, size_t in_length, size_t *length); bool encodeRenderableFontSet(cmp_ctx_t *bytes, FcFontSet *data); bool encodeResult(cmp_ctx_t *bytes, FcResult res); diff --git a/fontconfig-pure.cabal b/fontconfig-pure.cabal index de54bc4..c7cc2c6 100644 --- a/fontconfig-pure.cabal +++ b/fontconfig-pure.cabal @@ -70,7 +70,7 @@ library Graphics.Text.Font.Choose.Config, Graphics.Text.Font.Choose.Result, Graphics.Text.Font.Choose.Internal.FFI, FreeType.FontConfig, Graphics.Text.Font.Choose.Config.Accessors, Graphics.Text.Font.Choose.Weight, - Graphics.Text.Font.Choose + Graphics.Text.Font.Choose.Internal.Test, Graphics.Text.Font.Choose c-sources: cbits/cmp.c, cbits/transcode.c, cbits/fontconfig-wrap.c include-dirs: cbits @@ -147,4 +147,4 @@ test-suite fontconfig-pure-test base ^>=4.17.0.0, fontconfig-pure, hspec, QuickCheck, - msgpack + msgpack, containers, text diff --git a/lib/Graphics/Text/Font/Choose.hs b/lib/Graphics/Text/Font/Choose.hs index fa9e3da..953c738 100644 --- a/lib/Graphics/Text/Font/Choose.hs +++ b/lib/Graphics/Text/Font/Choose.hs @@ -1,15 +1,15 @@ module Graphics.Text.Font.Choose( module Graphics.Text.Font.Choose.Config.Accessors, Config', fini, version, initLoadConfig, initLoadConfigAndFonts, initFonts, reinit, bringUptoDate, - CharSet, ord, chr, parseCharSet, CharSet'(..), + CharSet, ord, chr, parseCharSet, CharSet'(..), validCharSet', module Graphics.Text.Font.Choose.FontSet, module Graphics.Text.Font.Choose.LangSet, module Graphics.Text.Font.Choose.ObjectSet, - Pattern, Pattern'(..), Binding(..), + Pattern, Pattern'(..), Binding(..), validPattern, validPattern', setValue, setValues, getValue, getValues, equalSubset, defaultSubstitute, nameParse, nameUnparse, nameFormat, module Graphics.Text.Font.Choose.Range, - FcException(..), StrSet(..), + FcException(..), StrSet(..), validStrSet, module Graphics.Text.Font.Choose.Value, module Graphics.Text.Font.Choose.Weight ) where diff --git a/lib/Graphics/Text/Font/Choose/CharSet.hs b/lib/Graphics/Text/Font/Choose/CharSet.hs index 8530ae5..a8de4cb 100644 --- a/lib/Graphics/Text/Font/Choose/CharSet.hs +++ b/lib/Graphics/Text/Font/Choose/CharSet.hs @@ -1,5 +1,6 @@ module Graphics.Text.Font.Choose.CharSet( - CharSet, ord, chr, module IntSet, parseCharSet, CharSet'(..)) where + CharSet, ord, chr, module IntSet, parseCharSet, CharSet'(..), validCharSet' + ) where import Data.IntSet (IntSet, union) import qualified Data.IntSet as IntSet @@ -59,4 +60,8 @@ instance MessagePack CharSet' where fromObject msg = CharSet' <$> IntSet.fromAscList <$> diffDecompress 0 <$> fromObject msg instance Arbitrary CharSet' where - arbitrary = CharSet' <$> arbitrary + arbitrary = CharSet' <$> IntSet.fromList <$> map (succ . abs) <$> arbitrary + +validCharSet' :: CharSet' -> Bool +validCharSet' (CharSet' self) = + not (IntSet.null self) && all (> 0) (IntSet.toList self) diff --git a/lib/Graphics/Text/Font/Choose/FontSet.hs b/lib/Graphics/Text/Font/Choose/FontSet.hs index b0ff0e1..a3d15fc 100644 --- a/lib/Graphics/Text/Font/Choose/FontSet.hs +++ b/lib/Graphics/Text/Font/Choose/FontSet.hs @@ -1,6 +1,7 @@ {-# LANGUAGE CApiFFI, OverloadedStrings #-} module Graphics.Text.Font.Choose.FontSet( - FontSet, fontSetList, fontSetMatch, fontSetSort, FontFaceParser(..)) where + FontSet, validFontSet, fontSetList, fontSetMatch, fontSetSort, FontFaceParser(..) + ) where import Graphics.Text.Font.Choose.Pattern hiding (map) import Graphics.Text.Font.Choose.Config @@ -24,6 +25,9 @@ import Graphics.Text.Font.Choose.Value (ToValue(..), Value) type FontSet = [Pattern] +validFontSet :: FontSet -> Bool +validFontSet = all validPattern + fontSetList :: Config -> [FontSet] -> Pattern -> ObjectSet -> FontSet fontSetList a b c d = fromMessage0 $ arg d $ arg c $ arg b $ withForeignPtr' fcFontSetList a diff --git a/lib/Graphics/Text/Font/Choose/LangSet.hs b/lib/Graphics/Text/Font/Choose/LangSet.hs index 197b1e5..a16f1f5 100644 --- a/lib/Graphics/Text/Font/Choose/LangSet.hs +++ b/lib/Graphics/Text/Font/Choose/LangSet.hs @@ -1,14 +1,14 @@ {-# LANGUAGE CApiFFI #-} module Graphics.Text.Font.Choose.LangSet( - LangSet, LangSet'(..), module S, LangComparison(..), + LangSet, LangSet'(..), module S, LangComparison(..), validLangSet, validLangSet', cmp, has, defaultLangs, langs, normalize, langCharSet) where import Data.Set (Set) import qualified Data.Set as S import Data.MessagePack (MessagePack(..)) -import Test.QuickCheck (Arbitrary(..)) -import Graphics.Text.Font.Choose.StrSet (StrSet) +import Test.QuickCheck (Arbitrary(..), elements, listOf) +import Graphics.Text.Font.Choose.StrSet (StrSet(..)) import Graphics.Text.Font.Choose.CharSet (CharSet') import Foreign.C.String (CString) @@ -20,11 +20,16 @@ import Control.Exception (throw) type LangSet = Set String newtype LangSet' = LangSet' { unLangSet :: LangSet } deriving (Eq, Show, Read) +validLangSet :: LangSet -> Bool +validLangSet x = all (`elem` unStrSet langs) x && not (null x) +validLangSet' :: LangSet' -> Bool +validLangSet' = validLangSet . unLangSet + instance MessagePack LangSet' where toObject = toObject . S.toList . unLangSet fromObject msg = LangSet' <$> S.fromList <$> fromObject msg instance Arbitrary LangSet' where - arbitrary = LangSet' <$> arbitrary + arbitrary = LangSet' <$> S.fromList <$> listOf (elements $ S.toList $ unStrSet langs) data LangComparison = SameLang | SameTerritory | DifferentLang i2cmp :: Int -> LangComparison diff --git a/lib/Graphics/Text/Font/Choose/Pattern.hs b/lib/Graphics/Text/Font/Choose/Pattern.hs index 4c3410f..35f4fef 100644 --- a/lib/Graphics/Text/Font/Choose/Pattern.hs +++ b/lib/Graphics/Text/Font/Choose/Pattern.hs @@ -2,13 +2,13 @@ {-# LANGUAGE OverloadedStrings #-} module Graphics.Text.Font.Choose.Pattern(Pattern, Pattern'(..), module M, Binding(..), setValue, setValues, getValue, getValues, equalSubset, defaultSubstitute, - nameParse, nameUnparse, nameFormat, + nameParse, nameUnparse, nameFormat, validPattern, validPattern', -- For Graphics.Text.Font.Choose.FontSet parseFontStretch, parseFontWeight, parseFontFeatures, parseFontVars) where import Data.Map as M import Data.MessagePack (MessagePack(..), Object(..)) -import Test.QuickCheck (Arbitrary(..), chooseEnum) +import Test.QuickCheck (Arbitrary(..), elements) import Data.Hashable (Hashable(..)) import GHC.Generics (Generic) @@ -29,8 +29,10 @@ import qualified Data.Text as Txt import Data.Scientific (toRealFloat) import Data.List (intercalate) import Data.Maybe as Mb (listToMaybe, fromMaybe, mapMaybe) +import Data.Char (isAscii) +import Prelude as L -type Pattern = Map Text [(Binding, Value)] +type Pattern = M.Map Text [(Binding, Value)] data Pattern' = Pattern' { unPattern :: Pattern } deriving (Eq, Read, Show, Generic) data Binding = Strong | Weak | Same deriving (Eq, Ord, Enum, Read, Show, Generic) @@ -51,9 +53,26 @@ instance MessagePack Pattern' where toObject = toObject . unPattern instance Arbitrary Pattern' where - arbitrary = Pattern' <$> M.mapKeys Txt.pack <$> arbitrary + -- FIXME: Stop enforcing singletons, without incurring too many invalid patterns! + arbitrary = Pattern' <$> M.mapKeys normKey <$> M.map (:[]) <$> arbitrary + where + normKey = Txt.pack . L.filter (/= '\0') . L.map toAscii . L.take 17 + toAscii :: Char -> Char + toAscii ch = toEnum $ fromEnum ch `mod` 128 instance Arbitrary Binding where - arbitrary = chooseEnum (Strong, Same) + arbitrary = elements [Strong, Weak] -- Same doesn't roundtrip! + +validPattern :: Pattern -> Bool +validPattern self = not (M.null self) && + all (validValue . snd) (concat $ M.elems self) && + all (not . L.null) (M.elems self) && + all (not . Txt.null) (M.keys self) && + all ((/= Same) . fst) (concat $ M.elems self) && + all (not . Txt.elem '\0') (M.keys self) && + all (Txt.all isAscii) (M.keys self) && + all (\k -> Txt.length k < 18) (M.keys self) +validPattern' :: Pattern' -> Bool +validPattern' = validPattern . unPattern setValue :: ToValue v => Text -> Binding -> v -> Pattern -> Pattern setValue key strength v self = setValues key strength [v] self @@ -225,7 +244,7 @@ instance PropertyParser Pattern' where | k `elem` ["initial", "normal"] = Pattern' <$> unset' "fontfeatures" self longhand _ (Pattern' self) "font-feature-settings" toks | (features, True, []) <- parseFontFeatures toks = Pattern' <$> - set "fontfeatures" Strong (intercalate "," $ Prelude.map fst features) self + set "fontfeatures" Strong (intercalate "," $ L.map fst features) self longhand _ (Pattern' self) "font-variation-settings" [Ident k] | k `elem` ["initial", "normal"] = Pattern' <$> unset' "variable" self diff --git a/lib/Graphics/Text/Font/Choose/Range.hs b/lib/Graphics/Text/Font/Choose/Range.hs index 1d36597..55a7ea1 100644 --- a/lib/Graphics/Text/Font/Choose/Range.hs +++ b/lib/Graphics/Text/Font/Choose/Range.hs @@ -1,5 +1,5 @@ {-# LANGUAGE DeriveGeneric #-} -module Graphics.Text.Font.Choose.Range(Range(..), iRange) where +module Graphics.Text.Font.Choose.Range(Range(..), iRange, validRange) where import Data.MessagePack (MessagePack(..), Object(..)) import Test.QuickCheck (Arbitrary(..)) @@ -24,5 +24,10 @@ instance MessagePack Range where Just (IM.findWithDefault 0 0 msg' `Range` IM.findWithDefault 0 1 msg') | otherwise = Nothing instance Arbitrary Range where - arbitrary = uncurry Range <$> arbitrary + arbitrary = do + (a, b) <- arbitrary + return $ Range a $ a + abs b + 1 instance Hashable Range + +validRange :: Range -> Bool +validRange (Range start end) = start < end diff --git a/lib/Graphics/Text/Font/Choose/StrSet.hs b/lib/Graphics/Text/Font/Choose/StrSet.hs index 4812a53..2a192a3 100644 --- a/lib/Graphics/Text/Font/Choose/StrSet.hs +++ b/lib/Graphics/Text/Font/Choose/StrSet.hs @@ -1,4 +1,4 @@ -module Graphics.Text.Font.Choose.StrSet(StrSet(..), module S) where +module Graphics.Text.Font.Choose.StrSet(StrSet(..), module S, validStrSet) where import Data.Set (Set) import qualified Data.Set as S @@ -12,4 +12,7 @@ instance MessagePack StrSet where toObject = toObject . S.toList . unStrSet fromObject msg = StrSet <$> S.fromList <$> fromObject msg instance Arbitrary StrSet where - arbitrary = StrSet <$> arbitrary + arbitrary = StrSet <$> S.map (filter (/= '\0')) <$> arbitrary + +validStrSet :: StrSet -> Bool +validStrSet (StrSet self) = notElem '\0' `all` self diff --git a/lib/Graphics/Text/Font/Choose/Value.hs b/lib/Graphics/Text/Font/Choose/Value.hs index b0a002d..28cd420 100644 --- a/lib/Graphics/Text/Font/Choose/Value.hs +++ b/lib/Graphics/Text/Font/Choose/Value.hs @@ -1,14 +1,14 @@ {-# LANGUAGE TypeSynonymInstances, FlexibleInstances, DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-} -module Graphics.Text.Font.Choose.Value(Value(..), ToValue(..)) where +module Graphics.Text.Font.Choose.Value(Value(..), validValue, ToValue(..)) where import Linear.Matrix (M22) import Linear.V2 (V2(..)) -import Graphics.Text.Font.Choose.CharSet (CharSet, CharSet'(..)) +import Graphics.Text.Font.Choose.CharSet (CharSet, CharSet'(..), validCharSet') import qualified Data.IntSet as S --import FreeType.Core.Base (FT_Face(..)) -import Graphics.Text.Font.Choose.LangSet (LangSet, LangSet'(..)) -import Graphics.Text.Font.Choose.Range (Range) +import Graphics.Text.Font.Choose.LangSet (LangSet, LangSet'(..), validLangSet) +import Graphics.Text.Font.Choose.Range (Range, validRange) import Data.MessagePack (MessagePack(..), Object(..)) import Test.QuickCheck (Arbitrary(..), oneof) @@ -59,19 +59,27 @@ instance MessagePack Value where | otherwise = Nothing instance Arbitrary Value where arbitrary = oneof [ - return ValueVoid, + --return ValueVoid, ValueInt <$> arbitrary, ValueDouble <$> arbitrary, - ValueString <$> arbitrary, + ValueString <$> Prelude.filter (/= '\0') <$> arbitrary, ValueBool <$> arbitrary, do (a, b, c, d) <- arbitrary return $ ValueMatrix $ V2 (V2 a b) (V2 c d), - ValueCharSet <$> arbitrary, - ValueLangSet <$> arbitrary, + ValueCharSet <$> unCharSet <$> arbitrary, + ValueLangSet <$> unLangSet <$> arbitrary, ValueRange <$> arbitrary ] +validValue :: Value -> Bool +validValue (ValueString "") = False +validValue (ValueString x) = '\0' `notElem` x +validValue (ValueCharSet x) = validCharSet' $ CharSet' x +validValue (ValueLangSet x) = validLangSet x +validValue (ValueRange x) = validRange x +validValue _ = True + -- | Coerces compiletime types to runtime types. class ToValue x where toValue :: x -> Value diff --git a/test/Main.hs b/test/Main.hs index b6bd147..cf345b6 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -1,10 +1,17 @@ +{-# LANGUAGE OverloadedStrings #-} + module Main (main) where import Test.Hspec import Test.Hspec.QuickCheck +import Test.QuickCheck import Data.MessagePack as MP +import qualified Data.Map as M +import qualified Data.Text as Txt + import Graphics.Text.Font.Choose +import Graphics.Text.Font.Choose.Internal.Test main :: IO () main = hspec $ do @@ -29,3 +36,31 @@ main = hspec $ do MP.unpack (MP.pack x) `shouldBe` Just (x :: StrSet) prop "Value" $ \x -> MP.unpack (MP.pack x) `shouldBe` Just (x :: Value) + describe "through C datastructures" $ do + prop "StrSet" $ \x -> validStrSet x ==> + roundtrip testStrSet x `shouldBe` Just (x :: StrSet) + prop "CharSet" $ \x -> validCharSet' x ==> + roundtrip testCharSet x `shouldBe` Just (x :: CharSet') + prop "LangSet" $ \x -> validLangSet' x ==> + roundtrip testLangSet x `shouldBe` Just (x :: LangSet') + prop "Range" $ \x -> validRange x ==> + roundtrip testRange x `shouldBe` Just (x :: Range) + prop "Matrix" $ \x -> roundtrip testMatrix x `shouldBe` + Just (x :: (Double, Double, Double, Double)) + prop "Value" $ \x -> validValue x ==> + roundtrip testValue x `shouldBe` Just (x :: Value) + prop "Trivial Pattern" $ \x -> validValue x ==> + let pat = Pattern' $ M.fromList [("test", [(Strong, x)])] + in roundtrip testPattern pat `shouldBe` Just pat + prop "Tuple Pattern" $ \(x, y) -> validValue x && validValue y ==> + let pat = Pattern' $ M.fromList [("a", [(Strong, x)]), ("b", [(Strong, y)])] + in roundtrip testPattern pat `shouldBe` Just pat + let toAscii :: Char -> Char + toAscii ch = toEnum $ fromEnum ch `mod` 128 + prop "Random-key pattern" $ \x -> all (\y -> toAscii y /= '\0') x ==> + let pat = Pattern' $ M.fromList [(Txt.pack $ map toAscii $ take 17 x, [(Strong, ValueBool True)])] + in roundtrip testPattern pat `shouldBe` Just pat + prop "Pattern" $ \x -> validPattern' x ==> + roundtrip testPattern x `shouldBe` Just (x :: Pattern') + prop "FontSet" $ \x -> let y = filter validPattern $ Prelude.map unPattern x + in validFontSet y ==> roundtrip testFontSet y `shouldBe` Just y -- 2.30.2