Make shadow tessellators fail gracefully and add unit test for this.

Change-Id: I42a9d06a18928588347a6dea2f6150518ba29aa8
Reviewed-on: https://skia-review.googlesource.com/7886
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
diff --git a/gn/tests.gni b/gn/tests.gni
index 6a836f6..baf1910 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -191,6 +191,7 @@
   "$_tests/SerializationTest.cpp",
   "$_tests/ShaderOpacityTest.cpp",
   "$_tests/ShaderTest.cpp",
+  "$_tests/ShadowUtilsTest.cpp",
   "$_tests/SizeTest.cpp",
   "$_tests/Sk4x4fTest.cpp",
   "$_tests/SkBase64Test.cpp",
diff --git a/src/utils/SkShadowTessellator.cpp b/src/utils/SkShadowTessellator.cpp
index f140ceb..a0a011f 100755
--- a/src/utils/SkShadowTessellator.cpp
+++ b/src/utils/SkShadowTessellator.cpp
@@ -26,6 +26,8 @@
     int vertexCount() const { return fPositions.count(); }
     int indexCount() const { return fIndices.count(); }
 
+    bool succeeded() const { return fSucceeded; }
+
     // The casts are needed to work around a, older GCC issue where the fact that the pointers are
     // T* and not const T* causes calls to a deleted unique_ptr constructor.
     UniqueArray<SkPoint> releasePositions() {
@@ -71,6 +73,8 @@
     SkTDArray<SkPoint>  fInitPoints;
     // temporary buffer
     SkTDArray<SkPoint>  fPointBuffer;
+
+    bool                fSucceeded;
 };
 
 static bool compute_normal(const SkPoint& p0, const SkPoint& p1, SkScalar radius, SkScalar dir,
@@ -107,12 +111,12 @@
                                                        SkColor umbraColor,
                                                        SkColor penumbraColor,
                                                        bool transparent)
-    : fRadius(radius)
-    , fUmbraColor(umbraColor)
-    , fPenumbraColor(penumbraColor)
-    , fTransparent(transparent)
-    , fPrevUmbraIndex(-1) {
-
+        : fRadius(radius)
+        , fUmbraColor(umbraColor)
+        , fPenumbraColor(penumbraColor)
+        , fTransparent(transparent)
+        , fPrevUmbraIndex(-1)
+        , fSucceeded(false) {
     // Outer ring: 3*numPts
     // Middle ring: numPts
     fPositions.setReserve(4 * path.countPoints());
@@ -154,6 +158,10 @@
         }
     }
 
+    if (!this->indexCount()) {
+        return;
+    }
+
     SkVector normal;
     if (compute_normal(fPositions[fPrevUmbraIndex], fPositions[fFirstVertex], fRadius, fDirection,
                        &normal)) {
@@ -198,6 +206,7 @@
         *fIndices.push() = fPositions.count() - 1;
         *fIndices.push() = fFirstVertex + 1;
     }
+    fSucceeded = true;
 }
 
 // tesselation tolerance values, in device space pixels
@@ -388,6 +397,8 @@
         return UniqueArray<uint16_t>(static_cast<const uint16_t*>(fIndices.release()));
     }
 
+    bool succeeded() const { return fSucceeded; }
+
 private:
     void computeClipBounds(const SkPath& path);
     void checkUmbraAndTransformCentroid(SkScalar scale, const SkVector& xlate,
@@ -439,6 +450,8 @@
     SkTDArray<SkPoint>  fInitPoints;
     // temporary buffer
     SkTDArray<SkPoint>  fPointBuffer;
+
+    bool                fSucceeded;
 };
 
 
@@ -448,15 +461,16 @@
                                                  SkScalar radius,
                                                  SkColor umbraColor, SkColor penumbraColor,
                                                  bool transparent)
-    : fRadius(radius)
-    , fUmbraColor(umbraColor)
-    , fPenumbraColor(penumbraColor)
-    , fTransparent(transparent)
-    , fValidUmbra(true)
-    , fPrevUmbraIndex(-1)
-    , fCurrPolyPoint(0)
-    , fPrevUmbraOutside(false)
-    , fFirstUmbraOutside(false) {
+        : fRadius(radius)
+        , fUmbraColor(umbraColor)
+        , fPenumbraColor(penumbraColor)
+        , fTransparent(transparent)
+        , fValidUmbra(true)
+        , fPrevUmbraIndex(-1)
+        , fCurrPolyPoint(0)
+        , fPrevUmbraOutside(false)
+        , fFirstUmbraOutside(false)
+        , fSucceeded(false) {
 
     // TODO: calculate these better
     // Penumbra ring: 3*numPts
@@ -514,6 +528,10 @@
         }
     }
 
+    if (!this->indexCount()) {
+        return;
+    }
+
     SkVector normal;
     if (compute_normal(fPrevPoint, fFirstPoint, fRadius, fDirection,
                         &normal)) {
@@ -579,6 +597,7 @@
             *fIndices.push() = fFirstVertex + 1;
         }
     }
+    fSucceeded = true;
 }
 
 void SkSpotShadowTessellator::computeClipBounds(const SkPath& path) {
@@ -1008,6 +1027,9 @@
                                                       SkColor umbraColor, SkColor penumbraColor,
                                                       bool transparent) {
     SkAmbientShadowTessellator ambientTess(path, radius, umbraColor, penumbraColor, transparent);
+    if (!ambientTess.succeeded()) {
+        return nullptr;
+    }
     int vcount = ambientTess.vertexCount();
     int icount = ambientTess.indexCount();
     return sk_sp<SkShadowVertices>(new SkShadowVertices(ambientTess.releasePositions(),
@@ -1022,6 +1044,9 @@
                                                    bool transparent) {
     SkSpotShadowTessellator spotTess(path, scale, translate, radius, umbraColor, penumbraColor,
                                      transparent);
+    if (!spotTess.succeeded()) {
+        return nullptr;
+    }
     int vcount = spotTess.vertexCount();
     int icount = spotTess.indexCount();
     return sk_sp<SkShadowVertices>(new SkShadowVertices(spotTess.releasePositions(),
diff --git a/src/utils/SkShadowTessellator.h b/src/utils/SkShadowTessellator.h
index 13924f2..c9abd72 100755
--- a/src/utils/SkShadowTessellator.h
+++ b/src/utils/SkShadowTessellator.h
@@ -61,7 +61,8 @@
             , fPositions(std::move(positions))
             , fColors(std::move(colors))
             , fIndices(std::move(indices)) {
-        SkASSERT(SkToBool(fIndices) == SkToBool(indexCnt));
+        SkASSERT(SkToBool(fPositions) && SkToBool(fColors) && SkToBool(vertexCnt) &&
+                 SkToBool(fIndices) && SkToBool(indexCnt));
     }
 
     int fVertexCnt;
diff --git a/src/utils/SkShadowUtils.cpp b/src/utils/SkShadowUtils.cpp
index 32f1c6f..83699c5 100755
--- a/src/utils/SkShadowUtils.cpp
+++ b/src/utils/SkShadowUtils.cpp
@@ -267,6 +267,9 @@
     } else {
         // TODO: handle transforming the path as part of the tessellator
         vertices = factory.makeVertices(path.transformedPath());
+        if (!vertices) {
+            return;
+        }
         translate = &kZeroTranslate;
     }
 
diff --git a/tests/ShadowUtilsTest.cpp b/tests/ShadowUtilsTest.cpp
new file mode 100644
index 0000000..adcb503
--- /dev/null
+++ b/tests/ShadowUtilsTest.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkCanvas.h"
+#include "SkPath.h"
+#include "SkShadowTessellator.h"
+#include "SkShadowUtils.h"
+#include "Test.h"
+
+void tessellate_shadow(skiatest::Reporter* reporter, const SkPath& path, bool expectSuccess) {
+    static constexpr SkScalar kRadius = 2.f;
+    static constexpr SkColor kUmbraColor = 0xFFFFFFFF;
+    static constexpr SkColor kPenumbraColor = 0x20202020;
+    auto verts = SkShadowVertices::MakeAmbient(path, kRadius, kUmbraColor, kPenumbraColor, true);
+    if (expectSuccess != SkToBool(verts)) {
+        ERRORF(reporter, "Expected shadow tessellation to % but it did not.",
+               expectSuccess ? "succeed" : "fail");
+    }
+    verts = SkShadowVertices::MakeAmbient(path, kRadius, kUmbraColor, kPenumbraColor, false);
+    if (expectSuccess != SkToBool(verts)) {
+        ERRORF(reporter, "Expected shadow tessellation to % but it did not.",
+               expectSuccess ? "succeed" : "fail");
+    }
+    verts = SkShadowVertices::MakeSpot(path, 1.5f, {0, 0}, kRadius, kUmbraColor, kPenumbraColor,
+                                       false);
+    if (expectSuccess != SkToBool(verts)) {
+        ERRORF(reporter, "Expected shadow tessellation to % but it did not.",
+               expectSuccess ? "succeed" : "fail");
+    }
+    verts = SkShadowVertices::MakeSpot(path, 1.5f, {0, 0}, kRadius, kUmbraColor, kPenumbraColor,
+                                       true);
+    if (expectSuccess != SkToBool(verts)) {
+        ERRORF(reporter, "Expected shadow tessellation to % but it did not.",
+               expectSuccess ? "succeed" : "fail");
+    }
+}
+
+DEF_TEST(ShadowUtils, reporter) {
+    SkCanvas canvas(100, 100);
+    // Currently SkShadowUtils doesn't really stupport cubics when compiled without SK_SUPPORT_GPU.
+    // However, this should now crash.
+    SkPath path;
+    path.cubicTo(100, 50, 20, 100, 0, 0);
+    tessellate_shadow(reporter, path, (bool)SK_SUPPORT_GPU);
+
+    // This line segment has no area and no shadow.
+    path.reset();
+    path.lineTo(10.f, 10.f);
+    tessellate_shadow(reporter, path, false);
+
+    // A series of colinear line segments
+    path.reset();
+    for (int i = 0; i < 10; ++i) {
+        path.lineTo((SkScalar)i, (SkScalar)i);
+    }
+    tessellate_shadow(reporter, path, false);
+}