fix contour-measure for move-line-move

Use RawIter, so we can dependably peek() (since consumeDegenerates in
Iter() make peeking unreliable), which caused us to think there were two
real contours in the test case.

Bug: oss-fuzz:13013
Change-Id: I0d85f3e6a83cb972c4d801dd9b17f0e388b926d0
Reviewed-on: https://skia-review.googlesource.com/c/192025
Commit-Queue: Mike Reed <reed@google.com>
Reviewed-by: Kevin Lubick <kjlubick@google.com>
diff --git a/include/core/SkContourMeasure.h b/include/core/SkContourMeasure.h
index f3ec519..7e4a95d 100644
--- a/include/core/SkContourMeasure.h
+++ b/include/core/SkContourMeasure.h
@@ -121,7 +121,7 @@
     sk_sp<SkContourMeasure> next();
 
 private:
-    SkPath::Iter    fIter;
+    SkPath::RawIter fIter;
     SkPath          fPath;
     SkScalar        fTolerance;
     bool            fForceClosed;
@@ -132,6 +132,7 @@
 
     SkContourMeasure* buildSegments();
 
+    SkScalar compute_line_seg(SkPoint p0, SkPoint p1, SkScalar distance, unsigned ptIndex);
     SkScalar compute_quad_segs(const SkPoint pts[3], SkScalar distance,
                                int mint, int maxt, unsigned ptIndex);
     SkScalar compute_conic_segs(const SkConic& conic, SkScalar distance,
diff --git a/include/core/SkPath.h b/include/core/SkPath.h
index f19bae4..183f1c3 100644
--- a/include/core/SkPath.h
+++ b/include/core/SkPath.h
@@ -1485,9 +1485,6 @@
         */
         bool isClosedContour() const;
 
-        // Returns the next verb, or kDone, without changing the state of the iterator
-        Verb peekVerb() const;
-
     private:
         const SkPoint*  fPts;
         const uint8_t*  fVerbs;
diff --git a/src/core/SkContourMeasure.cpp b/src/core/SkContourMeasure.cpp
index 79b0ca8..cd4dc93 100644
--- a/src/core/SkContourMeasure.cpp
+++ b/src/core/SkContourMeasure.cpp
@@ -181,6 +181,7 @@
         SkScalar prevD = distance;
         distance += d;
         if (distance > prevD) {
+            SkASSERT(ptIndex < (unsigned)fPts.count());
             SkContourMeasure::Segment* seg = fSegments.append();
             seg->fDistance = distance;
             seg->fPtIndex = ptIndex;
@@ -208,6 +209,7 @@
         SkScalar prevD = distance;
         distance += d;
         if (distance > prevD) {
+            SkASSERT(ptIndex < (unsigned)fPts.count());
             SkContourMeasure::Segment* seg = fSegments.append();
             seg->fDistance = distance;
             seg->fPtIndex = ptIndex;
@@ -232,6 +234,7 @@
         SkScalar prevD = distance;
         distance += d;
         if (distance > prevD) {
+            SkASSERT(ptIndex < (unsigned)fPts.count());
             SkContourMeasure::Segment* seg = fSegments.append();
             seg->fDistance = distance;
             seg->fPtIndex = ptIndex;
@@ -242,13 +245,29 @@
     return distance;
 }
 
+SkScalar SkContourMeasureIter::compute_line_seg(SkPoint p0, SkPoint p1, SkScalar distance,
+                                                unsigned ptIndex) {
+    SkScalar d = SkPoint::Distance(p0, p1);
+    SkASSERT(d >= 0);
+    SkScalar prevD = distance;
+    distance += d;
+    if (distance > prevD) {
+        SkASSERT((unsigned)ptIndex < (unsigned)fPts.count());
+        SkContourMeasure::Segment* seg = fSegments.append();
+        seg->fDistance = distance;
+        seg->fPtIndex = ptIndex;
+        seg->fType = kLine_SegType;
+        seg->fTValue = kMaxTValue;
+    }
+    return distance;
+}
+
 SkContourMeasure* SkContourMeasureIter::buildSegments() {
-    SkPoint         pts[4];
-    int             ptIndex = -1;
-    SkScalar        distance = 0;
-    bool            isClosed = fForceClosed;
-    bool            firstMoveTo = true;
-    SkContourMeasure::Segment*        seg;
+    SkPoint     pts[4];
+    int         ptIndex = -1;
+    SkScalar    distance = 0;
+    bool        haveSeenClose = fForceClosed;
+    bool        haveSeenMoveTo = false;
 
     /*  Note:
      *  as we accumulate distance, we have to check that the result of +=
@@ -257,37 +276,35 @@
      *
      *  We do this check below, and in compute_quad_segs and compute_cubic_segs
      */
+
     fSegments.reset();
+    fPts.reset();
+
     bool done = false;
     do {
-        if (!firstMoveTo && fIter.peekVerb() == SkPath::kMove_Verb) {
+        if (haveSeenMoveTo && fIter.peek() == SkPath::kMove_Verb) {
             break;
         }
         switch (fIter.next(pts)) {
             case SkPath::kMove_Verb:
                 ptIndex += 1;
                 fPts.append(1, pts);
-                SkASSERT(firstMoveTo);
-                firstMoveTo = false;
+                SkASSERT(!haveSeenMoveTo);
+                haveSeenMoveTo = true;
                 break;
 
             case SkPath::kLine_Verb: {
-                SkScalar d = SkPoint::Distance(pts[0], pts[1]);
-                SkASSERT(d >= 0);
+                SkASSERT(haveSeenMoveTo);
                 SkScalar prevD = distance;
-                distance += d;
+                distance = this->compute_line_seg(pts[0], pts[1], distance, ptIndex);
                 if (distance > prevD) {
-                    seg = fSegments.append();
-                    seg->fDistance = distance;
-                    seg->fPtIndex = ptIndex;
-                    seg->fType = kLine_SegType;
-                    seg->fTValue = kMaxTValue;
                     fPts.append(1, pts + 1);
                     ptIndex++;
                 }
             } break;
 
             case SkPath::kQuad_Verb: {
+                SkASSERT(haveSeenMoveTo);
                 SkScalar prevD = distance;
                 distance = this->compute_quad_segs(pts, distance, 0, kMaxTValue, ptIndex);
                 if (distance > prevD) {
@@ -297,6 +314,7 @@
             } break;
 
             case SkPath::kConic_Verb: {
+                SkASSERT(haveSeenMoveTo);
                 const SkConic conic(pts, fIter.conicWeight());
                 SkScalar prevD = distance;
                 distance = this->compute_conic_segs(conic, distance, 0, conic.fPts[0],
@@ -312,6 +330,7 @@
             } break;
 
             case SkPath::kCubic_Verb: {
+                SkASSERT(haveSeenMoveTo);
                 SkScalar prevD = distance;
                 distance = this->compute_cubic_segs(pts, distance, 0, kMaxTValue, ptIndex);
                 if (distance > prevD) {
@@ -321,7 +340,7 @@
             } break;
 
             case SkPath::kClose_Verb:
-                isClosed = true;
+                haveSeenClose = true;
                 break;
 
             case SkPath::kDone_Verb:
@@ -338,6 +357,16 @@
         return nullptr;
     }
 
+    // Handle the close segment ourselves, since we're using RawIter
+    if (haveSeenClose) {
+        SkScalar prevD = distance;
+        SkPoint firstPt = fPts[0];
+        distance = this->compute_line_seg(fPts[ptIndex], firstPt, distance, ptIndex);
+        if (distance > prevD) {
+            *fPts.append() = firstPt;
+        }
+    }
+
 #ifdef SK_DEBUG
     {
         const SkContourMeasure::Segment* seg = fSegments.begin();
@@ -366,7 +395,7 @@
     }
 #endif
 
-    return new SkContourMeasure(std::move(fSegments), std::move(fPts), distance, isClosed);
+    return new SkContourMeasure(std::move(fSegments), std::move(fPts), distance, haveSeenClose);
 }
 
 static void compute_pos_tan(const SkPoint pts[], unsigned segType,
@@ -419,7 +448,7 @@
     fTolerance = CHEAP_DIST_LIMIT * SkScalarInvert(resScale);
     fForceClosed = forceClosed;
 
-    fIter.setPath(fPath, forceClosed);
+    fIter.setPath(fPath);
 }
 
 SkContourMeasureIter::~SkContourMeasureIter() {}
@@ -434,13 +463,13 @@
     }
     fForceClosed = forceClosed;
 
-    fIter.setPath(fPath, forceClosed);
+    fIter.setPath(fPath);
     fSegments.reset();
     fPts.reset();
 }
 
 sk_sp<SkContourMeasure> SkContourMeasureIter::next() {
-    while (fIter.peekVerb() != SkPath::kDone_Verb) {
+    while (fIter.peek() != SkPath::kDone_Verb) {
         auto cm = this->buildSegments();
         if (cm) {
             return sk_sp<SkContourMeasure>(cm);
@@ -541,6 +570,7 @@
         return false;
     }
 
+    SkASSERT((unsigned)seg->fPtIndex < (unsigned)fPts.count());
     compute_pos_tan(&fPts[seg->fPtIndex], seg->fType, t, pos, tangent);
     return true;
 }
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp
index 9f87e5a..23a479d 100644
--- a/src/core/SkPath.cpp
+++ b/src/core/SkPath.cpp
@@ -1959,13 +1959,6 @@
     return false;
 }
 
-SkPath::Verb SkPath::Iter::peekVerb() const {
-    if (fVerbs == nullptr || fVerbs == fVerbStop) {
-        return kDone_Verb;
-    }
-    return (Verb)fVerbs[-1];
-}
-
 SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
     SkASSERT(pts);
     if (fLastPt != fMoveTo) {
diff --git a/tests/PathMeasureTest.cpp b/tests/PathMeasureTest.cpp
index c944f86..166fe44 100644
--- a/tests/PathMeasureTest.cpp
+++ b/tests/PathMeasureTest.cpp
@@ -265,6 +265,20 @@
     REPORTER_ASSERT(reporter, !fact.next());
 }
 
+static void test_MLM_contours(skiatest::Reporter* reporter) {
+    SkPath path;
+
+    // This odd sequence (with a trailing moveTo) used to return a 2nd contour, which is
+    // wrong, since the contract for a measure is to only return non-zero length contours.
+    path.moveTo(10, 10).lineTo(20, 20).moveTo(30, 30);
+
+    for (bool forceClosed : {false, true}) {
+        SkContourMeasureIter fact(path, forceClosed);
+        REPORTER_ASSERT(reporter, fact.next());
+        REPORTER_ASSERT(reporter, !fact.next());
+    }
+}
+
 DEF_TEST(contour_measure, reporter) {
     SkPath path;
     path.addCircle(0, 0, 100);
@@ -290,4 +304,5 @@
     REPORTER_ASSERT(reporter, !cm2);
 
     test_empty_contours(reporter);
+    test_MLM_contours(reporter);
 }