chromium/ui/gfx/render_text_api_fuzzer.cc

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <fuzzer/FuzzedDataProvider.h>

#include <algorithm>

#include "base/at_exit.h"
#include "base/command_line.h"
#include "base/i18n/icu_util.h"
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/task_environment.h"
#include "base/test/test_discardable_memory_allocator.h"
#include "base/test/test_timeouts.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/font_util.h"
#include "ui/gfx/render_text.h"

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#include "third_party/test_fonts/fontconfig/fontconfig_util_linux.h"
#endif

namespace {

#if BUILDFLAG(IS_WIN)
const char kFontDescription[] = "Segoe UI, 13px";
#elif BUILDFLAG(IS_ANDROID)
const char kFontDescription[] = "serif, 13px";
#else
const char kFontDescription[] = "sans, 13px";
#endif

struct Environment {
  Environment()
      : task_environment((base::CommandLine::Init(0, nullptr),
                          TestTimeouts::Initialize(),
                          base::test::TaskEnvironment::MainThreadType::UI)) {
    logging::SetMinLogLevel(logging::LOGGING_FATAL);

    // Some platforms require discardable memory to use bitmap fonts.
    base::DiscardableMemoryAllocator::SetInstance(
        &discardable_memory_allocator);

    CHECK(base::i18n::InitializeICU());

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
    test_fonts::SetUpFontconfig();
#endif
    gfx::InitializeFonts();
    gfx::FontList::SetDefaultFontDescription(kFontDescription);
  }

  base::TestDiscardableMemoryAllocator discardable_memory_allocator;
  base::AtExitManager at_exit_manager;
  base::test::TaskEnvironment task_environment;
};

// Commands recognized to drive the API calls on RenderText.
enum class RenderTextAPI {
  kDraw,
  kSetText,
  kAppendText,
  kSetHorizontalAlignment,
  kSetVerticalAlignment,
  kSetCursorEnabled,
  kSetSelectionColor,
  kSetSelectionBackgroundFocusedColor,
  kSetSymmetricSelectionVisualBounds,
  kSetFocused,
  kSetClipToDisplayRect,
  kSetObscured,
  kSetObscuredRevealIndex,
  kSetMultiline,
  kSetMaxLines,
  kSetWordWrapBehavior,
  kSetWhitespaceElision,
  kSetSubpixelRenderingSuppressed,
  kSetColor,
  kApplyColor,
  kSetStyle,
  kApplyStyle,
  kSetWeight,
  kApplyWeight,
  kSetDirectionalityMode,
  kSetElideBehavior,
  kIsGraphemeBoundary,
  kIndexOfAdjacentGrapheme,
  kSetObscuredGlyphSpacing,
  kSetDisplayRect,
  kGetSubstringBounds,
  kGetCursorSpan,
  kSetTruncateLength,
  kSetFillStyle,
  kSetStrokeWidth,
  kMaxValue = kSetStrokeWidth
};

gfx::DirectionalityMode ConsumeDirectionalityMode(FuzzedDataProvider* fdp) {
  if (fdp->ConsumeBool())
    return gfx::DIRECTIONALITY_FORCE_RTL;
  return gfx::DIRECTIONALITY_FORCE_LTR;
}

gfx::HorizontalAlignment ConsumeHorizontalAlignment(FuzzedDataProvider* fdp) {
  switch (fdp->ConsumeIntegralInRange(0, 4)) {
    case 0:
      return gfx::ALIGN_LEFT;
    case 1:
      return gfx::ALIGN_CENTER;
    case 2:
      return gfx::ALIGN_RIGHT;
    case 3:
      return gfx::ALIGN_TO_HEAD;
    default:
      return gfx::ALIGN_LEFT;
  }
}

gfx::VerticalAlignment ConsumeVerticalAlignment(FuzzedDataProvider* fdp) {
  switch (fdp->ConsumeIntegralInRange(0, 3)) {
    case 0:
      return gfx::ALIGN_TOP;
    case 1:
      return gfx::ALIGN_MIDDLE;
    case 2:
      return gfx::ALIGN_BOTTOM;
    default:
      return gfx::ALIGN_TOP;
  }
}

gfx::TextStyle ConsumeStyle(FuzzedDataProvider* fdp) {
  switch (fdp->ConsumeIntegralInRange(0, 4)) {
    case 0:
      return gfx::TEXT_STYLE_ITALIC;
    case 1:
      return gfx::TEXT_STYLE_STRIKE;
    case 2:
      return gfx::TEXT_STYLE_UNDERLINE;
    case 3:
      return gfx::TEXT_STYLE_HEAVY_UNDERLINE;
    default:
      return gfx::TEXT_STYLE_ITALIC;
  }
}

gfx::WordWrapBehavior ConsumeWordWrap(FuzzedDataProvider* fdp) {
  // TODO(crbug.com/40157791): ELIDE_LONG_WORDS is not supported.
  switch (fdp->ConsumeIntegralInRange(0, 3)) {
    case 0:
      return gfx::IGNORE_LONG_WORDS;
    case 1:
      return gfx::TRUNCATE_LONG_WORDS;
    case 2:
      return gfx::WRAP_LONG_WORDS;
    default:
      return gfx::IGNORE_LONG_WORDS;
  }
}

gfx::ElideBehavior ConsumeElideBehavior(FuzzedDataProvider* fdp,
                                        bool generate_only_homogeneous_styles) {
  if (generate_only_homogeneous_styles) {
    // The styles are guaranteed to be homogenous and it is safe to generate
    // any eliding behavior.
    switch (fdp->ConsumeIntegralInRange(0, 7)) {
      case 0:
        return gfx::NO_ELIDE;
      case 1:
        return gfx::TRUNCATE;
      case 2:
        return gfx::ELIDE_HEAD;
      case 3:
        return gfx::ELIDE_MIDDLE;
      case 4:
        return gfx::ELIDE_TAIL;
      case 5:
        return gfx::ELIDE_EMAIL;
      case 6:
        return gfx::FADE_TAIL;
      default:
        return gfx::NO_ELIDE;
    }
  } else {
    // Only generate eliding behaviors that are compatible with non homogeneous
    // text. Remove this when http://crbug.com/1085014 is fixed.
    switch (fdp->ConsumeIntegralInRange(0, 4)) {
      case 0:
        return gfx::NO_ELIDE;
      case 1:
        return gfx::TRUNCATE;
      case 2:
        return gfx::ELIDE_TAIL;
      case 3:
        return gfx::FADE_TAIL;
      default:
        return gfx::NO_ELIDE;
    }
  }
}

gfx::LogicalCursorDirection ConsumeLogicalCursorDirection(
    FuzzedDataProvider* fdp) {
  switch (fdp->ConsumeIntegralInRange(0, 1)) {
    case 0:
      return gfx::CURSOR_BACKWARD;
    default:
      return gfx::CURSOR_FORWARD;
  }
}

gfx::Font::Weight ConsumeWeight(FuzzedDataProvider* fdp) {
  if (fdp->ConsumeBool())
    return gfx::Font::Weight::BOLD;
  return gfx::Font::Weight::NORMAL;
}

SkColor ConsumeSkColor(FuzzedDataProvider* fdp) {
  return static_cast<SkColor>(fdp->ConsumeIntegral<uint32_t>());
}

gfx::Range ConsumeRange(FuzzedDataProvider* fdp, size_t max) {
  size_t start = fdp->ConsumeIntegralInRange<size_t>(0, max);
  size_t end = fdp->ConsumeIntegralInRange<size_t>(start, max);
  return gfx::Range(start, end);
}

cc::PaintFlags::Style ConsumeFillStyle(FuzzedDataProvider* fdp) {
  switch (fdp->ConsumeIntegralInRange(0, 2)) {
    case 0:
      return cc::PaintFlags::kFill_Style;
    case 1:
      return cc::PaintFlags::kStroke_Style;
    default:
      return cc::PaintFlags::kFill_Style;
  }
}

// Eliding behaviors are not all fully supported by RenderText. Ignore
// unsupported cases. This is causing clusterfuzz to fail with invalid
// tests (http://crbug.com/1185542). Remove when https://crbug.com/1085014 is
// fixed.
bool DoesDisplayRangeSupportElideBehavior(const gfx::RenderText* render_text) {
  const gfx::ElideBehavior behavior = render_text->elide_behavior();
  return behavior != gfx::ELIDE_HEAD && behavior != gfx::ELIDE_MIDDLE &&
         behavior != gfx::ELIDE_EMAIL;
}

const int kMaxStringLength = 128;

}  // anonymous namespace

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
  static Environment env;

  std::unique_ptr<gfx::RenderText> render_text =
      gfx::RenderText::CreateRenderText();
  gfx::Canvas canvas;

  FuzzedDataProvider fdp(data, size);
  if (size == 0)
    return 0;

  // Eliding and Styles are not well supported by RenderText. DCHECKs are
  // present in RenderText code to avoid any incorrect uses but the fuzzer
  // should not generate them until full support (http://crbug.com/1283159).
  const bool generate_only_homogeneous_styles = fdp.ConsumeBool();

  while (fdp.remaining_bytes() != 0) {
    const RenderTextAPI command = fdp.ConsumeEnum<RenderTextAPI>();
    switch (command) {
      case RenderTextAPI::kDraw:
        render_text->Draw(&canvas);
        break;

      case RenderTextAPI::kSetText:
        render_text->SetText(
            base::UTF8ToUTF16(fdp.ConsumeRandomLengthString(kMaxStringLength)));
        break;

      case RenderTextAPI::kAppendText:
        render_text->AppendText(
            base::UTF8ToUTF16(fdp.ConsumeRandomLengthString(kMaxStringLength)));
        break;

      case RenderTextAPI::kSetHorizontalAlignment:
        render_text->SetHorizontalAlignment(ConsumeHorizontalAlignment(&fdp));
        break;

      case RenderTextAPI::kSetVerticalAlignment:
        render_text->SetVerticalAlignment(ConsumeVerticalAlignment(&fdp));
        break;

      case RenderTextAPI::kSetCursorEnabled:
        render_text->SetCursorEnabled(fdp.ConsumeBool());
        break;

      case RenderTextAPI::kSetSelectionColor:
        render_text->set_selection_color(ConsumeSkColor(&fdp));
        break;

      case RenderTextAPI::kSetSelectionBackgroundFocusedColor:
        render_text->set_selection_background_focused_color(
            ConsumeSkColor(&fdp));
        break;

      case RenderTextAPI::kSetSymmetricSelectionVisualBounds:
        render_text->set_symmetric_selection_visual_bounds(fdp.ConsumeBool());
        break;

      case RenderTextAPI::kSetFocused:
        render_text->set_focused(fdp.ConsumeBool());
        break;

      case RenderTextAPI::kSetClipToDisplayRect:
        render_text->set_clip_to_display_rect(fdp.ConsumeBool());
        break;

      case RenderTextAPI::kSetObscured:
        render_text->SetObscured(fdp.ConsumeBool());
        break;

      case RenderTextAPI::kSetObscuredRevealIndex:
        render_text->SetObscuredRevealIndex(fdp.ConsumeIntegralInRange<size_t>(
            0, render_text->text().length()));
        break;

      case RenderTextAPI::kSetMultiline:
        if (generate_only_homogeneous_styles) {
          render_text->SetMultiline(fdp.ConsumeBool());
        }
        break;

      case RenderTextAPI::kSetMaxLines:
        render_text->SetMaxLines(fdp.ConsumeIntegralInRange<size_t>(0, 5));
        break;

      case RenderTextAPI::kSetWordWrapBehavior:
        render_text->SetWordWrapBehavior(ConsumeWordWrap(&fdp));
        break;

      case RenderTextAPI::kSetWhitespaceElision:
        render_text->SetWhitespaceElision(fdp.ConsumeBool());
        break;

      case RenderTextAPI::kSetSubpixelRenderingSuppressed:
        render_text->set_subpixel_rendering_suppressed(fdp.ConsumeBool());
        break;

      case RenderTextAPI::kSetColor:
        render_text->SetColor(ConsumeSkColor(&fdp));
        break;

      case RenderTextAPI::kApplyColor:
        if (!generate_only_homogeneous_styles) {
          render_text->ApplyColor(
              ConsumeSkColor(&fdp),
              ConsumeRange(&fdp, render_text->text().length()));
        }
        break;

      case RenderTextAPI::kSetStyle:
        render_text->SetStyle(ConsumeStyle(&fdp), fdp.ConsumeBool());
        break;

      case RenderTextAPI::kApplyStyle:
        if (!generate_only_homogeneous_styles) {
          render_text->ApplyStyle(
              ConsumeStyle(&fdp), fdp.ConsumeBool(),
              ConsumeRange(&fdp, render_text->text().length()));
        }
        break;

      case RenderTextAPI::kSetWeight:
        render_text->SetWeight(ConsumeWeight(&fdp));
        break;

      case RenderTextAPI::kApplyWeight:
        if (!generate_only_homogeneous_styles) {
          render_text->ApplyWeight(
              ConsumeWeight(&fdp),
              ConsumeRange(&fdp, render_text->text().length()));
        }
        break;

      case RenderTextAPI::kSetDirectionalityMode:
        render_text->SetDirectionalityMode(ConsumeDirectionalityMode(&fdp));
        break;

      case RenderTextAPI::kSetElideBehavior:
        render_text->SetElideBehavior(
            ConsumeElideBehavior(&fdp, generate_only_homogeneous_styles));
        break;

      case RenderTextAPI::kIsGraphemeBoundary:
        render_text->IsGraphemeBoundary(fdp.ConsumeIntegralInRange<size_t>(
            0, render_text->text().length()));
        break;

      case RenderTextAPI::kIndexOfAdjacentGrapheme: {
        size_t index = render_text->IndexOfAdjacentGrapheme(
            fdp.ConsumeIntegralInRange<size_t>(0, render_text->text().length()),
            ConsumeLogicalCursorDirection(&fdp));
        bool is_grapheme = render_text->IsGraphemeBoundary(index);
        DCHECK(is_grapheme);
        break;
      }
      case RenderTextAPI::kSetObscuredGlyphSpacing:
        render_text->SetObscuredGlyphSpacing(
            fdp.ConsumeIntegralInRange<size_t>(0, 10));
        break;
      case RenderTextAPI::kSetDisplayRect:
        render_text->SetDisplayRect(
            gfx::Rect(fdp.ConsumeIntegralInRange<int>(-30, 30),
                      fdp.ConsumeIntegralInRange<int>(-30, 30),
                      fdp.ConsumeIntegralInRange<int>(0, 200),
                      fdp.ConsumeIntegralInRange<int>(0, 30)));
        break;
      case RenderTextAPI::kGetSubstringBounds:
        // RenderText doesn't support that case (https://crbug.com/1085014).
        if (!DoesDisplayRangeSupportElideBehavior(render_text.get()))
          break;

        render_text->GetSubstringBounds(
            ConsumeRange(&fdp, render_text->text().length()));
        break;
      case RenderTextAPI::kGetCursorSpan:
        // RenderText doesn't support that case (https://crbug.com/1085014).
        if (!DoesDisplayRangeSupportElideBehavior(render_text.get()))
          break;

        render_text->GetCursorSpan(
            ConsumeRange(&fdp, render_text->text().length()));
        break;
      case RenderTextAPI::kSetTruncateLength:
        render_text->set_truncate_length(fdp.ConsumeIntegral<uint32_t>());
        break;
      case RenderTextAPI::kSetFillStyle:
        render_text->SetFillStyle(ConsumeFillStyle(&fdp));
        break;
      case RenderTextAPI::kSetStrokeWidth:
        render_text->SetStrokeWidth(
            fdp.ConsumeFloatingPointInRange(0.0f, 5.0f));
        break;
    }
  }

  return 0;
}