clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -O2 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name lc_embeddedtextmodel.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=none -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/home/runner/work/LibreCAD/LibreCAD/librecad/src -fcoverage-compilation-dir=/home/runner/work/LibreCAD/LibreCAD/librecad/src -resource-dir /usr/lib/llvm-21/lib/clang/21 -D _REENTRANT -D DWGSUPPORT -D MUPARSER_STATIC -D LC_WITH_OPENTYPE=0 -D QC_APPDIR=librecad -D LC_VERSION=2.2.2_alpha1-853-ge2a8c2931 -D LC_PRERELEASE=true; -D QT_NO_DEBUG -D QT_SVG_LIB -D QT_PRINTSUPPORT_LIB -D QT_WIDGETS_LIB -D QT_GUI_LIB -D QT_NETWORK_LIB -D QT_CORE_LIB -I . -I /usr/include -I ../../libraries/lciconengine -I ../../libraries/libdxfrw/src -I ../../libraries/libdxfrw/src/intern -I ../../libraries/jwwlib/src -I ../../libraries/shapelib/src -I ../../libraries/qtbidi -I cmd -I lib/actions -I lib/actions/visual_snap -I lib/actions/options -I lib/creation -I lib/debug -I lib/engine -I lib/engine/document -I lib/engine/document/blocks -I lib/engine/document/container -I lib/engine/document/dimstyles -I lib/engine/document/entities -I lib/engine/document/entities/support -I lib/engine/document/fonts -I lib/engine/document/io -I lib/engine/document/layers -I lib/engine/document/linetypes -I lib/engine/document/patterns -I lib/engine/document/selection -I lib/engine/document/textstyles -I lib/engine/document/ucs -I lib/engine/document/variables -I lib/engine/document/views -I lib/engine/clipboard -I lib/engine/overlays -I lib/engine/overlays/angles_base -I lib/engine/overlays/highlight -I lib/engine/overlays/preview -I lib/engine/overlays/references -I lib/engine/overlays/crosshair -I lib/engine/overlays/info_cursor -I lib/engine/overlays/overlay_box -I lib/engine/overlays/ucs_mark -I lib/engine/undo -I lib/engine/utils -I lib/engine/settings -I lib/fileio -I lib/filters -I lib/generators -I lib/generators/makercamsvg -I lib/generators/layers -I lib/generators/image -I lib/gui -I lib/gui/grid -I lib/gui/render -I lib/gui/render/headless -I lib/gui/render/widget -I lib/information -I lib/math -I lib/modification -I lib/selection -I lib/selection/metaentity -I lib/selection/metaentity/entities -I lib/printing -I lib/properties -I actions -I actions/dock_widgets -I actions/dock_widgets/block -I actions/dock_widgets/entity_info -I actions/dock_widgets/layer -I actions/dock_widgets/library -I actions/dock_widgets/ucs_list -I actions/drawing -I actions/drawing/draw -I actions/drawing/draw/arc -I actions/drawing/draw/circle -I actions/drawing/draw/curve -I actions/drawing/draw/spline -I actions/drawing/draw/dimensions -I actions/drawing/draw/ellipse -I actions/drawing/draw/hatch -I actions/drawing/draw/image -I actions/drawing/draw/line -I actions/drawing/draw/point -I actions/drawing/draw/rect -I actions/drawing/draw/polygon -I actions/drawing/draw/misc -I actions/drawing/draw/line/shapes -I actions/drawing/draw/line/misc -I actions/drawing/draw/line/shapes/rect -I actions/drawing/draw/line/shapes/polygon -I actions/drawing/draw/polyline -I actions/drawing/draw/text -I actions/drawing/edit -I actions/drawing/info -I actions/drawing/pick -I actions/drawing/modify -I actions/drawing/pen -I actions/drawing/rel_zero -I actions/drawing/selection -I actions/drawing/snap -I actions/drawing/zoom -I actions/file -I actions/options -I actions/print_preview -I ui -I ui/action_options -I ui/action_options/circle -I ui/action_options/curve -I ui/action_options/spline -I ui/action_options/dimensions -I ui/action_options/edit -I ui/action_options/image -I ui/action_options/info -I ui/action_options/insert -I ui/action_options/line -I ui/action_options/rect -I ui/action_options/polygon -I ui/action_options/misc -I ui/action_options/modify -I ui/action_options/ellipse -I ui/action_options/other -I ui/action_options/polyline -I ui/action_options/point -I ui/action_options/print_preview -I ui/action_options/selection -I ui/action_options/snap -I ui/action_options/text -I ui/actions -I ui/components -I ui/components/relative_position_assistant -I ui/components/comboboxes -I ui/components/containers -I ui/components/creators -I ui/components/layouts -I ui/components/pen -I ui/components/status_bar -I ui/components/toolbars -I ui/components/utils -I ui/dialogs -I ui/dialogs/actions -I ui/dialogs/actions/modify -I ui/dialogs/actions/quick_selection -I ui/dialogs/modify -I ui/dialogs/entity -I ui/dialogs/creators -I ui/dialogs/file -I ui/dialogs/file/export -I ui/dialogs/file/export/layers -I ui/dialogs/file/export/image -I ui/dialogs/file/export/makercam -I ui/dialogs/main -I ui/dialogs/settings -I ui/dialogs/settings/dimstyles -I ui/dialogs/settings/dimstyles/dimstyle_manager -I ui/dialogs/settings/dimstyles/dimstyle_manager/support -I ui/dialogs/settings/options_device -I ui/dialogs/settings/options_drawing -I ui/dialogs/settings/options_general -I ui/dialogs/settings/options_widget -I ui/dialogs/settings/shortcuts -I ui/dock_widgets -I ui/dock_widgets/block_widget -I ui/dock_widgets/command_line -I ui/dock_widgets/entity_info -I ui/dock_widgets/layer_widget -I ui/dock_widgets/layers_tree -I ui/dock_widgets/library_widget -I ui/dock_widgets/pen_palette -I ui/dock_widgets/pen_wizard -I ui/dock_widgets/property_sheet -I ui/dock_widgets/property_sheet/lib -I ui/dock_widgets/property_sheet/lib/properties -I ui/dock_widgets/property_sheet/lib/view -I ui/dock_widgets/property_sheet/lib/view/edit -I ui/dock_widgets/property_sheet/lib/widgets -I ui/dock_widgets/property_sheet/lib/widgets/sheet -I ui/dock_widgets/property_sheet/metaentity -I ui/dock_widgets/property_sheet/metaentity/entities -I ui/dock_widgets/property_sheet/metaentity/entities/document -I ui/dock_widgets/property_sheet/properties -I ui/dock_widgets/property_sheet/properties/action -I ui/dock_widgets/property_sheet/properties/bool -I ui/dock_widgets/property_sheet/properties/color -I ui/dock_widgets/property_sheet/properties/double -I ui/dock_widgets/property_sheet/properties/enum -I ui/dock_widgets/property_sheet/properties/int -I ui/dock_widgets/property_sheet/properties/layer -I ui/dock_widgets/property_sheet/properties/linetype -I ui/dock_widgets/property_sheet/properties/linewidth -I ui/dock_widgets/property_sheet/properties/rect -I ui/dock_widgets/property_sheet/properties/rsvector -I ui/dock_widgets/property_sheet/properties/string -I ui/dock_widgets/views_list -I ui/dock_widgets/ucs_list -I ui/dock_widgets/workspaces -I ui/dock_widgets/cad -I ui/main -I ui/main/init -I ui/main/persistence -I ui/main/release_check -I ui/main/support -I ui/main/fontviewer -I ui/main/workspaces -I ui/view -I main -I main/console_dxf2pdf -I test -I plugins -I ../res -I ../res/arrows -I ../res/controls -I ../res/dxf -I ../res/gdt -I ../res/icons -I ../res/images -I ../../../Qt/6.9.0/gcc_64/include -I ../../../Qt/6.9.0/gcc_64/include/QtSvg -I ../../../Qt/6.9.0/gcc_64/include/QtPrintSupport -I ../../../Qt/6.9.0/gcc_64/include/QtWidgets -I ../../../Qt/6.9.0/gcc_64/include/QtGui -I ../../../Qt/6.9.0/gcc_64/include/QtNetwork -I ../../../Qt/6.9.0/gcc_64/include/QtCore -I ../../generated/librecad/moc -I ../../generated/librecad/ui -I ../../../Qt/6.9.0/gcc_64/mkspecs/linux-g++ -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/15/../../../../include/c++/15 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/15/../../../../include/x86_64-linux-gnu/c++/15 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/15/../../../../include/c++/15/backward -internal-isystem /usr/lib/llvm-21/lib/clang/21/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/15/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -std=gnu++1z -fdeprecated-macro -ferror-limit 19 -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fcxx-exceptions -fexceptions -vectorize-loops -vectorize-slp -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/runner/work/LibreCAD/LibreCAD/out/2026-11-06-031751-4837-1 -x c++ lib/filters/lc_embeddedtextmodel.cpp
| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | #include "lc_embeddedtextmodel.h" |
| 8 | #include "lc_embeddedtextxdata.h" |
| 9 | #include "lc_textprofile.h" |
| 10 | #include <QCryptographicHash> |
| 11 | #include <algorithm> |
| 12 | #include <cstring> |
| 13 | #include <limits> |
| 14 | #include <new> |
| 15 | #include <stdexcept> |
| 16 | |
| 17 | namespace { |
| 18 | using C = LC_EmbeddedTextModel; |
| 19 | using K = C::Kind; |
| 20 | using E = C::ErrorCode; |
| 21 | using Bytes = C::Bytes; |
| 22 | constexpr std::size_t headerSize = 64, fieldHeaderSize = 12; |
| 23 | static_assert(sizeof(double) == 8 && std::numeric_limits<double>::is_iec559, |
| 24 | "Embedded models require IEEE754 binary64"); |
| 25 | enum class Schema { |
| 26 | Top, Source, Imported, EntityProfile, StyleProfile, HistoricalProfile, |
| 27 | EntityValue, StyleValue, Selection, Identity, CurrentStyle, |
| 28 | Outer, Embedded, Legacy, Baseline, Pending, OpaqueExtra, Deformation |
| 29 | }; |
| 30 | struct Spec { std::uint16_t id; K kind; bool required = true; Schema child = Schema::Top; }; |
| 31 | struct Slice { const std::uint8_t *data = nullptr; std::size_t size = 0; }; |
| 5 | | Null pointer value stored to 'magic.data' | |
|
| 32 | struct View { std::uint16_t id; K kind; Slice value; }; |
| 33 | using Views = std::vector<View>; |
| 34 | C::Error failure(E code, const char *message) { return {code, QString::fromLatin1(message), {}}; } |
| 35 | bool valid(const C::Limits &l) { |
| 36 | return l.maximumBytes >= headerSize && l.maximumBytes <= 64u * 1024u * 1024u && |
| 37 | l.maximumItems <= 1000000u && l.maximumDepth >= 1 && l.maximumDepth <= 16; |
| 38 | } |
| 39 | bool add(std::size_t &total, std::size_t amount, std::size_t maximum) { |
| 40 | if (total > maximum || amount > maximum - total) return false; |
| 41 | total += amount; return true; |
| 42 | } |
| 43 | void put(Bytes &out, std::uint64_t value, unsigned width) { |
| 44 | for (unsigned i = 0; i < width; ++i) out.push_back(std::uint8_t(value >> (8u * i))); |
| 45 | } |
| 46 | struct Reader { |
| 47 | Slice input; |
| 48 | std::size_t position = 0; |
| 49 | bool integer(std::uint64_t &value, unsigned width) { |
| 50 | if (position > input.size || width > input.size - position) return false; |
| 51 | value = 0; |
| 52 | for (unsigned i = 0; i < width; ++i) value |= std::uint64_t(input.data[position++]) << (8u * i); |
| 53 | return true; |
| 54 | } |
| 55 | bool slice(Slice &out, std::uint64_t size) { |
| 56 | if (position > input.size || size > input.size - position) return false; |
| 8 | | Assuming field 'position' is <= field 'size' | |
|
| 9 | | Assuming the condition is false | |
|
| |
| 57 | out = {input.data ? input.data + position : nullptr, std::size_t(size)}; |
| 11 | | Assuming field 'data' is null | |
|
| |
| 58 | position += std::size_t(size); return true; |
| 13 | | Returning without writing to 'out.data' | |
|
| 59 | } |
| 60 | bool finished() const { return position == input.size; } |
| 61 | }; |
| 62 | std::uint64_t number(Slice s) { |
| 63 | Reader reader{s}; std::uint64_t result = 0; |
| 64 | reader.integer(result, unsigned(s.size)); return result; |
| 65 | } |
| 66 | const View *get(const Views &views, std::uint16_t id) { |
| 67 | const auto it = std::lower_bound(views.begin(), views.end(), id, |
| 68 | [](const View &v, std::uint16_t key) { return v.id < key; }); |
| 69 | return it != views.end() && it->id == id ? &*it : nullptr; |
| 70 | } |
| 71 | std::uint64_t number(const Views &v, std::uint16_t id) { return number(get(v, id)->value); } |
| 72 | bool present(const Views &v, std::uint16_t id) { return get(v, id) != nullptr; } |
| 73 | bool emptyText(Slice s) { return s.size == 9; } |
| 74 | QByteArray nullableBytes(Slice s) { |
| 75 | if (!s.data[0]) return {}; |
| 76 | return QByteArray(reinterpret_cast<const char *>(s.data + 9), qsizetype(s.size - 9)); |
| 77 | } |
| 78 | QString text(Slice s) { |
| 79 | if (!s.data[0]) return {}; |
| 80 | const qsizetype count = qsizetype((s.size - 9) / 2); |
| 81 | QString result(count, Qt::Uninitialized); |
| 82 | for (qsizetype i = 0; i < count; ++i) |
| 83 | result[i] = QChar(std::uint16_t(s.data[9 + 2 * std::size_t(i)]) | |
| 84 | (std::uint16_t(s.data[10 + 2 * std::size_t(i)]) << 8)); |
| 85 | return result; |
| 86 | } |
| 87 | |
| 88 | std::vector<Spec> native(bool embedded) { |
| 89 | std::vector<Spec> s = { |
| 90 | {1,K::I32},{2,K::Bytes},{3,K::Bytes},{4,K::U32},{5,K::I32},{6,K::I32}, |
| 91 | {7,K::Real},{8,K::Bool},{9,K::I32},{10,K::Bytes},{11,K::I32},{12,K::Bytes}, |
| 92 | {13,K::I32},{14,K::I32},{15,K::I32},{16,K::U32},{17,K::U32},{18,K::U32}, |
| 93 | {19,K::U32},{20,K::U32Array},{21,K::U32},{22,K::Bool},{23,K::RawTags}, |
| 94 | {24,K::RawTagArrays},{25,K::Bool},{26,K::U64},{27,K::Bytes},{28,K::Bytes}, |
| 95 | {29,K::U32},{30,K::U32},{31,K::Bool},{32,K::U32},{33,K::U32},{34,K::Bytes}, |
| 96 | {35,K::Bool},{36,K::Bool},{37,K::U32},{38,K::U32},{39,K::U32}, |
| 97 | {40,K::RecordArray,true,Schema::Pending},{41,K::RecordArray,true,Schema::Pending}, |
| 98 | {42,K::U8},{43,K::U8},{44,K::U64},{45,K::U8},{46,K::U8},{47,K::U64}, |
| 99 | {48,K::U32},{49,K::U32},{50,K::U8},{51,K::U32},{52,K::U32},{53,K::Bool}, |
| 100 | {54,K::U8},{55,K::I32},{56,K::U8},{57,K::U8},{58,K::U8},{59,K::U8}, |
| 101 | {60,K::U8},{61,K::U8},{62,K::U8},{63,K::U8},{64,K::I32}, |
| 102 | {100,K::Coord},{101,K::Coord},{102,K::Coord},{103,K::Real},{104,K::Real}, |
| 103 | {105,K::Real},{106,K::Bytes},{107,K::Real},{108,K::Real},{109,K::Real}, |
| 104 | {110,K::Bytes},{111,K::I32},{112,K::I32},{113,K::I32},{114,K::U8}, |
| 105 | {115,K::U8},{116,K::U32},{117,K::U64} |
| 106 | }; |
| 107 | if (embedded) { |
| 108 | const std::vector<Spec> extra = { |
| 109 | {200,K::Real},{201,K::U16},{202,K::I32},{203,K::Real},{204,K::I32}, |
| 110 | {205,K::I32},{206,K::Bool},{207,K::Bool},{208,K::U16},{209,K::Bool}, |
| 111 | {210,K::U32},{211,K::I32},{212,K::Coord},{213,K::Coord},{214,K::Real}, |
| 112 | {215,K::Real},{216,K::Real},{217,K::Real},{218,K::U16},{219,K::I32}, |
| 113 | {220,K::Real},{221,K::Real},{222,K::Bool},{223,K::Bool}, |
| 114 | {224,K::RealArray},{225,K::Bytes},{226,K::U16},{227,K::U32},{228,K::Bool} |
| 115 | }; |
| 116 | s.insert(s.end(), extra.begin(), extra.end()); |
| 117 | } else { |
| 118 | const std::vector<Spec> extra = { |
| 119 | {200,K::Bytes},{201,K::U8},{202,K::U8},{203,K::U8},{204,K::U8}, |
| 120 | {205,K::I32},{206,K::Bool},{207,K::Bytes,false} |
| 121 | }; |
| 122 | s.insert(s.end(), extra.begin(), extra.end()); |
| 123 | } |
| 124 | return s; |
| 125 | } |
| 126 | const std::vector<Spec> &specs(Schema schema) { |
| 127 | static const std::vector<Spec> top = { |
| 128 | {1,K::Record,true,Schema::Source},{2,K::Record,false,Schema::Imported}, |
| 129 | {3,K::Record,true,Schema::Outer},{4,K::Record,true,Schema::Embedded}, |
| 130 | {5,K::Record,true,Schema::Legacy},{6,K::Record,true,Schema::Baseline}, |
| 131 | {7,K::RecordArray,false,Schema::OpaqueExtra} |
| 132 | }; |
| 133 | static const std::vector<Spec> source = { |
| 134 | {1,K::Utf16},{2,K::U8},{3,K::Bool},{4,K::Bool},{5,K::NullableBytes,false}, |
| 135 | {6,K::U8},{7,K::U8},{8,K::Utf16},{9,K::Utf16},{10,K::U8},{11,K::U8}, |
| 136 | {12,K::Utf16},{13,K::Utf16},{14,K::Utf16},{15,K::Bool}, |
| 137 | {16,K::Record,true,Schema::EntityProfile},{17,K::Record,true,Schema::CurrentStyle}, |
| 138 | {18,K::Record,true,Schema::Deformation} |
| 139 | }; |
| 140 | static const std::vector<Spec> imported = { |
| 141 | {1,K::Utf16},{2,K::U8},{3,K::Bool},{4,K::Bool},{5,K::NullableBytes,false}, |
| 142 | {6,K::U8},{7,K::U8},{8,K::Utf16},{9,K::Utf16},{10,K::U8},{11,K::U8}, |
| 143 | {12,K::Utf16},{13,K::Utf16},{14,K::Utf16}, |
| 144 | {16,K::Record,true,Schema::HistoricalProfile},{18,K::Record,false,Schema::Deformation} |
| 145 | }; |
| 146 | static const std::vector<Spec> entityProfile = { |
| 147 | {1,K::U8},{2,K::U8},{3,K::Record,false,Schema::EntityValue}, |
| 148 | {4,K::RawTags,false},{5,K::U8},{6,K::Utf16} |
| 149 | }; |
| 150 | static const std::vector<Spec> styleProfile = { |
| 151 | {1,K::U8},{2,K::U8},{3,K::Record,false,Schema::StyleValue}, |
| 152 | {4,K::RawTags,false},{5,K::U8},{6,K::Utf16} |
| 153 | }; |
| 154 | static const std::vector<Spec> historical = { |
| 155 | {1,K::U8},{2,K::U8},{3,K::Utf16},{4,K::RawTags,false} |
| 156 | }; |
| 157 | static const std::vector<Spec> value = { |
| 158 | {1,K::Record,false,Schema::Selection},{2,K::NullableBytes,false},{3,K::U8,false} |
| 159 | }; |
| 160 | static const std::vector<Spec> selection = {{1,K::U8},{2,K::Record,false,Schema::Identity}}; |
| 161 | static const std::vector<Spec> identity = { |
| 162 | {1,K::Utf16},{2,K::NullableBytes},{3,K::U32},{4,K::Utf16},{5,K::Utf16},{6,K::Utf16} |
| 163 | }; |
| 164 | static const std::vector<Spec> currentStyle = { |
| 165 | {1,K::Utf16},{2,K::Record,true,Schema::StyleProfile},{3,K::RawTags,false} |
| 166 | }; |
| 167 | static const auto outer = native(false), embedded = native(true); |
| 168 | static const std::vector<Spec> legacy = {{1,K::U8},{2,K::Bool},{3,K::Utf16}}; |
| 169 | static const std::vector<Spec> baseline = { |
| 170 | {1,K::U8},{2,K::U32},{3,K::U8},{4,K::Bool},{5,K::Bool},{6,K::Bool}, |
| 171 | {7,K::Record,true,Schema::Outer},{8,K::Record,false,Schema::Embedded} |
| 172 | }; |
| 173 | static const std::vector<Spec> pending = {{1,K::U64},{2,K::U32},{3,K::U64},{4,K::Bool}}; |
| 174 | static const std::vector<Spec> opaque = {{1,K::Utf16},{2,K::U32},{3,K::Bytes}}; |
| 175 | static const std::vector<Spec> deformation = {{1,K::Real},{2,K::Real},{3,K::Real},{4,K::Real}}; |
| 176 | switch (schema) { |
| 177 | case Schema::Top:return top; case Schema::Source:return source; |
| 178 | case Schema::Imported:return imported; case Schema::EntityProfile:return entityProfile; |
| 179 | case Schema::StyleProfile:return styleProfile; case Schema::HistoricalProfile:return historical; |
| 180 | case Schema::EntityValue:case Schema::StyleValue:return value; |
| 181 | case Schema::Selection:return selection; case Schema::Identity:return identity; |
| 182 | case Schema::CurrentStyle:return currentStyle; case Schema::Outer:return outer; |
| 183 | case Schema::Embedded:return embedded; case Schema::Legacy:return legacy; |
| 184 | case Schema::Baseline:return baseline; case Schema::Pending:return pending; |
| 185 | case Schema::OpaqueExtra:return opaque; |
| 186 | case Schema::Deformation:return deformation; |
| 187 | } |
| 188 | return top; |
| 189 | } |
| 190 | |
| 191 | struct Validator { |
| 192 | const C::Limits &limits; |
| 193 | std::size_t items = 0; |
| 194 | C::Error error; |
| 195 | bool fail(E code, const char *message) { error = failure(code,message); return false; } |
| 196 | bool consume(std::uint64_t count) { |
| 197 | if (count > limits.maximumItems || !add(items, std::size_t(count), limits.maximumItems)) |
| 198 | return fail(E::ResourceLimit,"Aggregate embedded model item limit exceeded"); |
| 199 | return true; |
| 200 | } |
| 201 | bool fields(Slice input, std::uint64_t count, Views &out) { |
| 202 | if (count > input.size / fieldHeaderSize) |
| 203 | return fail(E::Malformed,"Embedded model field count exceeds remaining bytes"); |
| 204 | if (!consume(count)) return false; |
| 205 | out.reserve(std::size_t(count)); Reader reader{input}; std::uint16_t previous = 0; |
| 206 | for (std::uint64_t index = 0; index < count; ++index) { |
| 207 | std::uint64_t id, kind, flags, size; Slice payload; |
| 208 | if (!reader.integer(id,2) || !reader.integer(kind,1) || !reader.integer(flags,1) || |
| 209 | !reader.integer(size,8) || !reader.slice(payload,size)) |
| 210 | return fail(E::Malformed,"Truncated embedded model field"); |
| 211 | if (!id || id <= previous || flags) |
| 212 | return fail(E::InvalidField,"Embedded model IDs must be ascending unique and flags zero"); |
| 213 | previous = std::uint16_t(id); |
| 214 | out.push_back({std::uint16_t(id),static_cast<K>(kind),payload}); |
| 215 | } |
| 216 | if (!reader.finished()) return fail(E::Malformed,"Trailing embedded model record bytes"); |
| 217 | return true; |
| 218 | } |
| 219 | bool record(Slice s, Schema schema, unsigned depth, Views *views = nullptr) { |
| 220 | if (depth > limits.maximumDepth) return fail(E::ResourceLimit,"Embedded model nesting limit exceeded"); |
| 221 | Reader r{s}; std::uint64_t count; Slice rest; |
| 222 | if (!r.integer(count,8) || !r.slice(rest,s.size-r.position)) |
| 223 | return fail(E::Malformed,"Truncated embedded model record count"); |
| 224 | Views local; |
| 225 | if (!fields(rest,count,local) || !validate(local,schema,depth)) return false; |
| 226 | if (views) *views = std::move(local); |
| 227 | return true; |
| 228 | } |
| 229 | bool raw(Slice s) { |
| 230 | Reader r{s}; std::uint64_t magic,version,count; |
| 231 | if (!r.integer(magic,4) || magic != 0x5843544c || !r.integer(version,1)) |
| 232 | return fail(E::Malformed,"Invalid retained raw-tag capsule"); |
| 233 | if (version != 2) return fail(E::UnknownVersion,"Unknown retained raw-tag capsule version"); |
| 234 | if (!r.integer(count,4)) return fail(E::Malformed,"Truncated retained raw-tag capsule count"); |
| 235 | if (!consume(count)) return false; |
| 236 | const Bytes input(s.data,s.data+s.size); |
| 237 | const auto result = LC_EmbeddedTextXData::decode(input,{std::size_t(count),limits.maximumBytes}); |
| 238 | if (!result) return fail(result.error.code == LC_EmbeddedTextXData::ErrorCode::ResourceLimit ? |
| 239 | E::ResourceLimit:E::Malformed,"Malformed retained raw-tag capsule"); |
| 240 | return true; |
| 241 | } |
| 242 | bool field(const View &v, const Spec &spec, unsigned depth) { |
| 243 | const Slice s = v.value; |
| 244 | switch (v.kind) { |
| 245 | case K::Bool:return s.size == 1 && s.data[0] <= 1 ? true : fail(E::InvalidField,"Invalid Boolean storage"); |
| 246 | case K::U8:return s.size == 1 ? true : fail(E::InvalidField,"Invalid U8 storage"); |
| 247 | case K::U16:return s.size == 2 ? true : fail(E::InvalidField,"Invalid U16 storage"); |
| 248 | case K::U32:case K::I32:return s.size == 4 ? true : fail(E::InvalidField,"Invalid 32-bit storage"); |
| 249 | case K::U64:case K::I64:case K::Real:return s.size == 8 ? true : fail(E::InvalidField,"Invalid 64-bit storage"); |
| 250 | case K::Coord:return s.size == 24 ? true : fail(E::InvalidField,"Invalid coordinate storage"); |
| 251 | case K::Bytes:return true; |
| 252 | case K::Utf16:case K::NullableBytes: { |
| 253 | Reader r{s}; std::uint64_t nullness,count; |
| 254 | if (!r.integer(nullness,1) || nullness > 1 || !r.integer(count,8) || |
| 255 | (!nullness && count) || count > (s.size-r.position)/(v.kind==K::Utf16?2u:1u) || |
| 256 | count*(v.kind==K::Utf16?2u:1u) != s.size-r.position) |
| 257 | return fail(E::InvalidField,"Invalid null-aware string storage"); |
| 258 | return true; |
| 259 | } |
| 260 | case K::Record:return record(s,spec.child,depth+1); |
| 261 | case K::U32Array:case K::RealArray: { |
| 262 | Reader r{s}; std::uint64_t count; const unsigned width = v.kind==K::U32Array?4:8; |
| 263 | if (!r.integer(count,8) || count > (s.size-r.position)/width || |
| 264 | count*width != s.size-r.position) |
| 265 | return fail(E::Malformed,"Invalid embedded model scalar array"); |
| 266 | return consume(count); |
| 267 | } |
| 268 | case K::RawTags:return raw(s); |
| 269 | case K::RecordArray:case K::RawTagArrays: { |
| 270 | Reader r{s}; std::uint64_t count; |
| 271 | if (!r.integer(count,8) || count > (s.size-r.position)/8) |
| 272 | return fail(E::Malformed,"Invalid embedded model record array count"); |
| 273 | if (!consume(count)) return false; |
| 274 | if (depth+1 > limits.maximumDepth) return fail(E::ResourceLimit,"Embedded model nesting limit exceeded"); |
| 275 | for (std::uint64_t i=0;i<count;++i) { |
| 276 | std::uint64_t size; Slice element; |
| 277 | if (!r.integer(size,8) || !r.slice(element,size)) |
| 278 | return fail(E::Malformed,"Truncated embedded model array element"); |
| 279 | if (!(v.kind==K::RecordArray ? record(element,spec.child,depth+1) : raw(element))) return false; |
| 280 | } |
| 281 | return r.finished() ? true : fail(E::Malformed,"Trailing embedded model array bytes"); |
| 282 | } |
| 283 | } |
| 284 | return fail(E::UnknownKind,"Unknown embedded model field kind"); |
| 285 | } |
| 286 | bool parseChild(const Views &v, std::uint16_t id, Views &out) { |
| 287 | |
| 288 | Reader r{get(v,id)->value}; std::uint64_t count; r.integer(count,8); |
| 289 | out.reserve(std::size_t(count)); |
| 290 | for (std::uint64_t i=0;i<count;++i) { |
| 291 | std::uint64_t fieldId,kind,flags,size; Slice payload; |
| 292 | r.integer(fieldId,2);r.integer(kind,1);r.integer(flags,1);r.integer(size,8);r.slice(payload,size); |
| 293 | out.push_back({std::uint16_t(fieldId),static_cast<K>(kind),payload}); |
| 294 | } |
| 295 | return true; |
| 296 | } |
| 297 | bool value(const Views &v, bool style) { |
| 298 | LC_TextProfile::Value result; |
| 299 | result.scope = style ? LC_TextProfile::Scope::StyleDefaults:LC_TextProfile::Scope::EntityOverride; |
| 300 | if (present(v,1)) { |
| 301 | Views selection; parseChild(v,1,selection); |
| 302 | LC_TextProfile::Selection selected; |
| 303 | selected.backend = number(selection,1) ? LC_TextProfile::Backend::HarfBuzzOpenType: |
| 304 | LC_TextProfile::Backend::LegacyLff; |
| 305 | if (present(selection,2)) { |
| 306 | Views identity; parseChild(selection,2,identity); |
| 307 | selected.identity = {text(get(identity,1)->value),nullableBytes(get(identity,2)->value), |
| 308 | unsigned(number(identity,3)),text(get(identity,4)->value), |
| 309 | text(get(identity,5)->value),text(get(identity,6)->value)}; |
| 310 | } |
| 311 | result.selection = std::move(selected); |
| 312 | } |
| 313 | if (present(v,2)) result.language = nullableBytes(get(v,2)->value); |
| 314 | if (present(v,3)) result.direction = static_cast<LC_TextProfile::Direction>(number(v,3)); |
| 315 | if (LC_TextProfile::validate(result)) return fail(E::InvalidField,"Invalid materialized current profile value"); |
| 316 | return true; |
| 317 | } |
| 318 | bool semantics(const Views &v, Schema schema) { |
| 319 | switch (schema) { |
| 320 | case Schema::Source:case Schema::Imported: { |
| 321 | if (number(v,2)>1 || number(v,6)>2 || number(v,7)>2 || number(v,10)>2 || number(v,11)>2 || |
| 322 | (!number(v,10) && number(v,11)) || (number(v,10) && !number(v,11)) || |
| 323 | (number(v,11)==2 && number(v,7)!=2)) |
| 324 | return fail(E::InvalidField,"Invalid source convention or marker provenance"); |
| 325 | if (present(v,5)) { |
| 326 | const auto bytes = nullableBytes(get(v,5)->value); |
| 327 | if (QString::fromUtf8(bytes.constData(),bytes.size()) != text(get(v,1)->value)) |
| 328 | return fail(E::InvalidField,"Current/source UTF8 bytes do not decode to source"); |
| 329 | } |
| 330 | return true; |
| 331 | } |
| 332 | case Schema::EntityProfile:case Schema::StyleProfile: { |
| 333 | const auto status = number(v,1), transport = number(v,5); |
| 334 | if (status>4 || number(v,2)!=(schema==Schema::StyleProfile?0u:1u) || transport>2 || |
| 335 | present(v,3)!=(status==1) || present(v,4)!=(status>=2) || |
| 336 | (status==4 ? (!transport || emptyText(get(v,6)->value)) : transport!=0)) |
| 337 | return fail(E::InvalidField,"Inconsistent materialized profile presence/scope/transport"); |
| 338 | return true; |
| 339 | } |
| 340 | case Schema::HistoricalProfile: { |
| 341 | const auto status=number(v,1), transport=number(v,2); |
| 342 | if (status>4 || transport>2 || present(v,4)!=(status!=0) || |
| 343 | (status==4 ? (!transport || emptyText(get(v,3)->value)) : transport!=0)) |
| 344 | return fail(E::InvalidField,"Inconsistent historical profile provenance"); |
| 345 | return true; |
| 346 | } |
| 347 | case Schema::EntityValue:return value(v,false); |
| 348 | case Schema::StyleValue:return value(v,true); |
| 349 | case Schema::Selection: |
| 350 | return number(v,1)<=1 && present(v,2)==(number(v,1)==1) ? true : |
| 351 | fail(E::InvalidField,"Backend selection and identity must be atomic"); |
| 352 | case Schema::Legacy: |
| 353 | return number(v,1)<=2 && (!number(v,2) || (number(v,1)==2 && !emptyText(get(v,3)->value))) ? true : |
| 354 | fail(E::InvalidField,"Invalid legacy embedded marker provenance"); |
| 355 | case Schema::Baseline: |
| 356 | return number(v,1)<=1 && number(v,3)<=1 && present(v,8)==bool(number(v,6)) ? true : |
| 357 | fail(E::InvalidField,"Inconsistent target baseline native-body presence"); |
| 358 | default:return true; |
| 359 | } |
| 360 | } |
| 361 | bool validate(const Views &v, Schema schema, unsigned depth) { |
| 362 | const auto ®istry = specs(schema); |
| 363 | for (const auto &entry : v) { |
| 364 | const auto rawKind = static_cast<unsigned>(entry.kind); |
| 365 | if (rawKind<1 || rawKind>18) return fail(E::UnknownKind,"Unknown embedded model field kind"); |
| 366 | const auto it=std::lower_bound(registry.begin(),registry.end(),entry.id, |
| 367 | [](const Spec &s,std::uint16_t id){return s.id<id;}); |
| 368 | if (it==registry.end() || it->id!=entry.id) { |
| 369 | error = failure(E::UnknownField,"Unknown embedded model field");error.path.push_back(entry.id);return false; |
| 370 | } |
| 371 | if (it->kind != entry.kind) { |
| 372 | error=failure(E::InvalidField,"Embedded model field has the wrong registered kind"); |
| 373 | error.path.push_back(entry.id);return false; |
| 374 | } |
| 375 | if (!field(entry,*it,depth)) {error.path.insert(error.path.begin(),entry.id);return false;} |
| 376 | } |
| 377 | for (const auto &spec:registry) if (spec.required && !present(v,spec.id)) { |
| 378 | error=failure(E::MissingField,"Missing required embedded model field"); |
| 379 | error.path.push_back(spec.id);return false; |
| 380 | } |
| 381 | return semantics(v,schema); |
| 382 | } |
| 383 | }; |
| 384 | |
| 385 | C::Encoded pack(const C::Record &record,const C::Limits &limits,bool counted) { |
| 386 | if (!valid(limits)) return {std::nullopt,failure(E::InvalidLimits,"Invalid embedded model limits")}; |
| 387 | if (record.size()>limits.maximumItems) |
| 388 | return {std::nullopt,failure(E::ResourceLimit,"Embedded model field count exceeds limit")}; |
| 389 | std::size_t size=counted?8:0; |
| 390 | std::uint16_t previous=0; |
| 391 | for (const auto &f:record) { |
| 392 | if (!f.id || f.id<=previous) return {std::nullopt,failure(E::InvalidField,"Field IDs must be ascending unique")}; |
| 393 | previous=f.id; |
| 394 | if (!add(size,fieldHeaderSize,limits.maximumBytes) || !add(size,f.payload.size(),limits.maximumBytes)) |
| 395 | return {std::nullopt,failure(E::ResourceLimit,"Embedded model byte limit exceeded")}; |
| 396 | } |
| 397 | Bytes result;result.reserve(size);if(counted)put(result,record.size(),8); |
| 398 | for (const auto &f:record) { |
| 399 | put(result,f.id,2);put(result,static_cast<std::uint8_t>(f.kind),1);put(result,0,1); |
| 400 | put(result,f.payload.size(),8);result.insert(result.end(),f.payload.begin(),f.payload.end()); |
| 401 | } |
| 402 | return {std::move(result),{}}; |
| 403 | } |
| 404 | bool opaque(E code) { return code==E::UnknownVersion || code==E::UnknownField || code==E::UnknownKind; } |
| 405 | C::Decoded retained(const Bytes &input,C::Error error) { |
| 406 | return {opaque(error.code)?C::Status::Opaque:C::Status::Invalid,std::nullopt,input,std::move(error)}; |
| 407 | } |
| 408 | C::Error allocationFailure() { return failure(E::ResourceLimit,"Embedded model allocation unavailable; input not accepted"); } |
| 409 | } |
| 410 | |
| 411 | bool LC_EmbeddedTextModel::Field::operator==(const Field &o) const { |
| 412 | return id==o.id && kind==o.kind && payload==o.payload; |
| 413 | } |
| 414 | LC_EmbeddedTextModel::Encoded LC_EmbeddedTextModel::packRecord(const Record &input,const Limits &limits) { |
| 415 | try {return pack(input,limits,true);} catch(const std::bad_alloc &) {return {std::nullopt,allocationFailure()};} |
| 416 | catch(const std::length_error &) {return {std::nullopt,allocationFailure()};} |
| 417 | } |
| 418 | LC_EmbeddedTextModel::Encoded LC_EmbeddedTextModel::packRecordArray(const std::vector<Record> &input,const Limits &limits) { |
| 419 | try { |
| 420 | if (!valid(limits)) return {std::nullopt,failure(E::InvalidLimits,"Invalid embedded model limits")}; |
| 421 | if(input.size()>limits.maximumItems) return {std::nullopt,failure(E::ResourceLimit,"Record array item limit exceeded")}; |
| 422 | std::size_t size=8,items=input.size();std::vector<Bytes> payloads;payloads.reserve(input.size()); |
| 423 | for(const auto &record:input) { |
| 424 | if(!add(items,record.size(),limits.maximumItems)) |
| 425 | return {std::nullopt,failure(E::ResourceLimit,"Record array field limit exceeded")}; |
| 426 | auto payload=pack(record,limits,true);if(!payload)return payload; |
| 427 | if(!add(size,8,limits.maximumBytes)||!add(size,payload.bytes->size(),limits.maximumBytes)) |
| 428 | return {std::nullopt,failure(E::ResourceLimit,"Record array byte limit exceeded")}; |
| 429 | payloads.push_back(std::move(*payload.bytes)); |
| 430 | } |
| 431 | Bytes result;result.reserve(size);put(result,input.size(),8); |
| 432 | for(const auto &payload:payloads){put(result,payload.size(),8);result.insert(result.end(),payload.begin(),payload.end());} |
| 433 | return {std::move(result),{}}; |
| 434 | } catch(const std::bad_alloc &) {return {std::nullopt,allocationFailure()};} |
| 435 | catch(const std::length_error &) {return {std::nullopt,allocationFailure()};} |
| 436 | } |
| 437 | LC_EmbeddedTextModel::Encoded LC_EmbeddedTextModel::encode(const Record &input,const Limits &limits) { |
| 438 | try { |
| 439 | auto body=pack(input,limits,false);if(!body)return body; |
| 440 | std::size_t size=headerSize; |
| 441 | if(!add(size,body.bytes->size(),limits.maximumBytes)) |
| 442 | return {std::nullopt,failure(E::ResourceLimit,"Complete embedded model byte limit exceeded")}; |
| 443 | Validator validator{limits,0,{}};Views views; |
| 444 | if(!validator.fields({body.bytes->data(),body.bytes->size()},input.size(),views) || |
| 445 | !validator.validate(views,Schema::Top,1)) return {std::nullopt,std::move(validator.error)}; |
| 446 | const auto digest=QCryptographicHash::hash(QByteArrayView(reinterpret_cast<const char *>(body.bytes->data()), |
| 447 | qsizetype(body.bytes->size())),QCryptographicHash::Sha256); |
| 448 | Bytes output;output.reserve(size);output.insert(output.end(),{'L','C','E','M'}); |
| 449 | put(output,EnvelopeVersion,2);put(output,ModelSchemaVersion,2); |
| 450 | put(output,headerSize,4);put(output,body.bytes->size(),8); |
| 451 | put(output,input.size(),4);output.insert(output.end(),digest.begin(),digest.end());put(output,0,8); |
| 452 | output.insert(output.end(),body.bytes->begin(),body.bytes->end());return {std::move(output),{}}; |
| 453 | } catch(const std::bad_alloc &) {return {std::nullopt,allocationFailure()};} |
| 454 | catch(const std::length_error &) {return {std::nullopt,allocationFailure()};} |
| 455 | } |
| 456 | LC_EmbeddedTextModel::Decoded LC_EmbeddedTextModel::decode(const Bytes &input,const Limits &limits) { |
| 457 | try { |
| 458 | if(!valid(limits)) return {Status::Refused,std::nullopt,std::nullopt,failure(E::InvalidLimits,"Invalid embedded model limits")}; |
| |
| 459 | if(input.size()>limits.maximumBytes) |
| 2 | | Assuming the condition is false | |
|
| |
| 460 | return {Status::Refused,std::nullopt,std::nullopt,failure(E::ResourceLimit,"Complete embedded model byte limit exceeded")}; |
| 461 | Reader r{{input.data(),input.size()}};Slice magic,digest,body; |
| 4 | | Calling implicit default constructor for 'Slice' | |
|
| 6 | | Returning from default constructor for 'Slice' | |
|
| 462 | std::uint64_t envelope,schema,header,bodyBytes,count,reserved; |
| 463 | if(!r.slice(magic,4) || std::memcmp(magic.data,"LCEM",4) || |
| |
| 14 | | Returning from 'Reader::slice' | |
|
| 15 | | Null pointer passed to 1st parameter expecting 'nonnull' |
|
| 464 | !r.integer(envelope,2)||!r.integer(schema,2)||!r.integer(header,4)|| |
| 465 | !r.integer(bodyBytes,8)||!r.integer(count,4)||!r.slice(digest,32)||!r.integer(reserved,8)) |
| 466 | return retained(input,failure(E::Malformed,"Truncated or invalid embedded model header")); |
| 467 | if(envelope!=EnvelopeVersion || schema!=ModelSchemaVersion) |
| 468 | return retained(input,failure(E::UnknownVersion,"Unsupported embedded model version; retained without migration")); |
| 469 | if(header!=headerSize || reserved || !r.slice(body,bodyBytes) || !r.finished()) |
| 470 | return retained(input,failure(E::Malformed,"Invalid embedded model length/reserved/trailing storage")); |
| 471 | const auto actual=QCryptographicHash::hash(QByteArrayView(reinterpret_cast<const char *>(body.data),qsizetype(body.size)), |
| 472 | QCryptographicHash::Sha256); |
| 473 | if(std::memcmp(actual.constData(),digest.data,32)) |
| 474 | return retained(input,failure(E::DigestMismatch,"Embedded model body digest mismatch")); |
| 475 | Validator validator{limits,0,{}};Views views; |
| 476 | if(!validator.fields(body,count,views)||!validator.validate(views,Schema::Top,1)) |
| 477 | return retained(input,std::move(validator.error)); |
| 478 | Record result;result.reserve(views.size()); |
| 479 | for(const auto &view:views) result.push_back({view.id,view.kind,Bytes(view.value.data,view.value.data+view.value.size)}); |
| 480 | return {Status::Known,std::move(result),std::nullopt,{}}; |
| 481 | } catch(const std::bad_alloc &) {return {Status::Refused,std::nullopt,std::nullopt,allocationFailure()};} |
| 482 | catch(const std::length_error &) {return {Status::Refused,std::nullopt,std::nullopt,allocationFailure()};} |
| 483 | } |