[fuzzing] Add first pass SkParagraph fuzzing.
Rough first pass at SkParagraph fuzzing. Lots of things not yet fuzzed.
--FontCollection cribbed from SkParagraphTest
--Current flow:
---Fuzz ParagraphStyle
---Add text and style some random small number of times.
---Text is either ASCII, unicode, or 'Zalgo'.
Although there are many todos, want to go ahead and submit this
~unchanged so the existing test cases that have found bugs are
not invalidated by a changing binary.
Change-Id: I38adca5fa79cfb20068fdf2fb431f90de55a2afc
Bug: skia:10894
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/336438
Commit-Queue: Weston Tracey <westont@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
diff --git a/fuzz/FuzzSkParagraph.cpp b/fuzz/FuzzSkParagraph.cpp
new file mode 100644
index 0000000..f3f396c
--- /dev/null
+++ b/fuzz/FuzzSkParagraph.cpp
@@ -0,0 +1,274 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "fuzz/Fuzz.h"
+#include "fuzz/FuzzCommon.h"
+#include "include/core/SkBitmap.h"
+#include "include/core/SkCanvas.h"
+#include "include/core/SkColor.h"
+#include "include/core/SkEncodedImageFormat.h"
+#include "include/core/SkFontMgr.h"
+#include "include/core/SkFontStyle.h"
+#include "include/core/SkImageEncoder.h"
+#include "include/core/SkPaint.h"
+#include "include/core/SkPoint.h"
+#include "include/core/SkRect.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkScalar.h"
+#include "include/core/SkStream.h"
+#include "include/core/SkString.h"
+#include "include/core/SkTypeface.h"
+#include "include/core/SkTypes.h"
+#include "modules/skparagraph/include/DartTypes.h"
+#include "modules/skparagraph/include/FontCollection.h"
+#include "modules/skparagraph/include/Paragraph.h"
+#include "modules/skparagraph/include/ParagraphCache.h"
+#include "modules/skparagraph/include/ParagraphStyle.h"
+#include "modules/skparagraph/include/TextShadow.h"
+#include "modules/skparagraph/include/TextStyle.h"
+#include "modules/skparagraph/include/TypefaceFontProvider.h"
+#include "modules/skparagraph/src/ParagraphBuilderImpl.h"
+#include "modules/skparagraph/src/ParagraphImpl.h"
+#include "modules/skparagraph/src/Run.h"
+#include "modules/skparagraph/src/TextLine.h"
+#include "modules/skparagraph/utils/TestFontCollection.h"
+#include "src/core/SkOSFile.h"
+#include "src/core/SkSpan.h"
+#include "src/utils/SkOSPath.h"
+#include "src/utils/SkShaperJSONWriter.h"
+#include "tests/Test.h"
+#include "tools/Resources.h"
+
+
+#include <string.h>
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+using namespace skia::textlayout;
+namespace {
+const uint8_t MAX_TEXT_LENGTH = 255;
+const uint8_t MAX_TEXT_ADDITIONS = 4;
+const uint16_t TEST_CANVAS_WIDTH = 1000;
+
+class ResourceFontCollection : public FontCollection {
+public:
+ ResourceFontCollection(bool testOnly = false)
+ : fFontsFound(false)
+ , fResolvedFonts(0)
+ , fResourceDir(GetResourcePath("fonts").c_str())
+ , fFontProvider(sk_make_sp<TypefaceFontProvider>()) {
+ std::vector<SkString> fonts;
+ SkOSFile::Iter iter(fResourceDir.c_str());
+
+ SkString path;
+ while (iter.next(&path)) {
+ if (path.endsWith("Roboto-Italic.ttf")) {
+ fFontsFound = true;
+ }
+ fonts.emplace_back(path);
+ }
+
+ if (!fFontsFound) {
+ // SkDebugf("Fonts not found, skipping all the tests\n");
+ return;
+ }
+ // Only register fonts if we have to
+ for (auto& font : fonts) {
+ SkString file_path;
+ file_path.printf("%s/%s", fResourceDir.c_str(), font.c_str());
+ fFontProvider->registerTypeface(SkTypeface::MakeFromFile(file_path.c_str()));
+ }
+
+ if (testOnly) {
+ this->setTestFontManager(std::move(fFontProvider));
+ } else {
+ this->setAssetFontManager(std::move(fFontProvider));
+ }
+ this->disableFontFallback();
+ }
+
+ size_t resolvedFonts() const { return fResolvedFonts; }
+
+ // TODO: temp solution until we check in fonts
+ bool fontsFound() const { return fFontsFound; }
+
+private:
+ bool fFontsFound;
+ size_t fResolvedFonts;
+ std::string fResourceDir;
+ sk_sp<TypefaceFontProvider> fFontProvider;
+};
+
+// buffer must be at least MAX_TEXT_LENGTH in length.
+// Returns size of text placed in buffer.
+template <typename T>
+uint8_t RandomText(T* buffer, Fuzz* fuzz) {
+ uint8_t text_length;
+ fuzz->nextRange(&text_length, 0, MAX_TEXT_LENGTH);
+ fuzz->nextN(buffer, text_length);
+ return text_length;
+}
+
+// Add random bytes to the paragraph.
+void AddASCIIText(ParagraphBuilder* builder,Fuzz* fuzz) {
+ char text[MAX_TEXT_LENGTH];
+ const auto text_length = RandomText(text, fuzz);
+ builder->addText(text, text_length);
+}
+// Add random bytes to the paragraph.
+void AddUnicodeText(ParagraphBuilder* builder,Fuzz* fuzz) {
+ char16_t text[MAX_TEXT_LENGTH];
+ const auto text_length = RandomText(text, fuzz);
+ builder->addText(std::u16string(text, text_length));
+}
+
+// Combining characters to produce 'Zalgo' text.
+const std::u16string COMBINING_DOWN = u"\u0316\u0317\u0318\u0319\u031c\u031d\u031e\u031f\u0320\u0324\u0325\u0326\u0329\u032a\u032b\u032c\u032d\u032e\u032f\u0330\u0331\u0332\u0333\u0339\u033a\u033b\u033c\u0345\u0347\u0348\u0349\u034d\u034e\u0353\u0354\u0355\u0356\u0359\u035a\u0323";
+const std::u16string COMBINING_UP = u"\u030d\u030e\u0304\u0305\u033f\u0311\u0306\u0310\u0352\u0357\u0351\u0307\u0308\u030a\u0342\u0343\u0344\u034a\u034b\u034c\u0303\u0302\u030c\u0350\u0300\u0301\u030b\u030f\u0312\u0313\u0314\u033d\u0309\u0363\u0364\u0365\u0366\u0367\u0368\u0369\u036a\u036b\u036c\u036d\u036e\u035b\u0346\u031a";
+const std::u16string COMBINING_MIDDLE = u"\u0315\u031b\u0340\u0341\u0358\u0321\u0322\u0327\u0328\u0334\u0335\u0336\u034f\u035c\u035d\u035e\u035f\u0360\u0362\u0338\u0337\u0361\u0489";
+// Add random Zalgo text to the paragraph.
+void AddZalgoText(ParagraphBuilder* builder, Fuzz* fuzz) {
+ char text[MAX_TEXT_LENGTH];
+ const auto text_length = RandomText(text, fuzz);
+ std::u16string result;
+
+ for (auto& c : std::string(text, text_length)) {
+ result += c;
+ uint8_t mark_count;
+ fuzz->next(&mark_count);
+ for (int i = 0; i < mark_count; i++) {
+ uint8_t mark_type, mark_index;
+ fuzz->next(&mark_type, &mark_index);
+ switch (mark_type % 3) {
+ case 0:
+ result += COMBINING_UP[mark_index % COMBINING_UP.size()];
+ break;
+ case 1:
+ result += COMBINING_MIDDLE[mark_index % COMBINING_MIDDLE.size()];
+ break;
+ case 2:
+ default:
+ result += COMBINING_DOWN[mark_index % COMBINING_DOWN.size()];
+ break;
+ }
+ }
+ }
+ builder->addText(result);
+}
+
+void AddStyle(ParagraphBuilder* builder, Fuzz* fuzz) {
+ // TODO(westont): Make this probabilistic, and fill in the remaining TextStyle fields.
+ TextStyle ts;
+ ts.setFontFamilies({SkString("Roboto")});
+ //ts.setColor(SK_ColorBLACK);
+ //ts.setForegroundColor
+ //ts.setBackgroundColor
+ //ts.setDecoration(TextDecoration decoration);
+ //ts.setDecorationMode(TextDecorationMode mode);
+ //ts.setDecorationStyle(TextDecorationStyle style);
+ //ts.setDecorationColor(SkColor color);
+ //ts.setDecorationThicknessMultiplier(SkScalar m);
+ //ts.setFontStyle
+ //ts.addShadow
+ //ts.addFontFeature
+ //ts.setFontSize
+ //ts.setHeight
+ //ts.setHeightOverride
+ //ts.setletterSpacing
+ //ts.setWordSpacing
+ //ts.setTypeface
+ //ts.setLocale
+ //ts.setTextBaseline
+ //ts.setPlaceholder
+
+ builder->pushStyle(ts);
+}
+void RemoveStyle(ParagraphBuilder* builder, Fuzz* fuzz) {
+ bool pop;
+ fuzz->next(&pop);
+ if (pop) {
+ builder->pop();
+ }
+}
+
+void AddStyleAndText(ParagraphBuilder* builder, Fuzz* fuzz) {
+ AddStyle(builder, fuzz);
+ uint8_t text_type;
+ fuzz->next(&text_type);
+ switch (text_type % 3) {
+ case 0:
+ AddASCIIText(builder, fuzz);
+ break;
+ case 1:
+ AddUnicodeText(builder, fuzz);
+ break;
+ case 2:
+ AddZalgoText(builder, fuzz);
+ break;
+ }
+ RemoveStyle(builder, fuzz);
+
+}
+
+ParagraphStyle BuildParagraphStyle(Fuzz* fuzz) {
+ ParagraphStyle ps;
+ bool hinting;
+ fuzz->next(&hinting);
+ if (hinting) {
+ ps.turnHintingOff();
+ }
+ StrutStyle ss;
+ // TODO(westont): Fuzz this object.
+ ps.setStrutStyle(ss);
+ TextDirection td;
+ fuzz->nextEnum(&td, TextDirection::kRtl);
+ ps.setTextDirection(td);
+ TextAlign ta;
+ fuzz->nextEnum(&ta, TextAlign::kEnd);
+ ps.setTextAlign(ta);
+ size_t ml;
+ fuzz->next(&ml);
+ ps.setMaxLines(ml);
+ // TODO(westont): Randomize with other values and no value.
+ ps.setEllipsis(u"\u2026");
+ SkScalar h;
+ fuzz->next(&h);
+ ps.setHeight(h);
+ TextHeightBehavior thb = TextHeightBehavior::kAll;
+ // TODO(westont): This crashes our seed test case, why?
+ //fuzz->nextEnum(&thb, TextHeightBehavior::kDisableAll);
+ ps.setTextHeightBehavior(thb);
+
+ return ps;
+}
+
+} // namespace
+
+DEF_FUZZ(api_skparagraph, fuzz) {
+ static sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
+ ParagraphStyle paragraph_style = BuildParagraphStyle(fuzz);
+ ParagraphBuilderImpl builder(paragraph_style, fontCollection);
+
+ uint8_t iterations;
+ fuzz->nextRange(&iterations, 1, MAX_TEXT_ADDITIONS);
+ for (int i = 0; i < iterations; i++) {
+ AddStyleAndText(&builder, fuzz);
+ }
+ // TODO(westont): Figure out if we can get more converage by having fontsFound, current
+ // they're not.
+ // if (!fontCollection->fontsFound()) return;
+
+ builder.pop();
+ auto paragraph = builder.Build();
+
+ paragraph->layout(TEST_CANVAS_WIDTH);
+
+}