Fix GC hole when exception filter throws unhandled exception (#9785)
authorJan Vorlicek <janvorli@microsoft.com>
Sat, 25 Feb 2017 10:08:35 +0000 (11:08 +0100)
committerGitHub <noreply@github.com>
Sat, 25 Feb 2017 10:08:35 +0000 (11:08 +0100)
The extra Unix specific piece of code in the StackFrameIterator::Filter that
handles the difference in the exception stack unwinding on Unix was not
skipping exception trackers belonging to filter clauses. But that was not
right, since filter funclet stack frames behave the same way on Windows and
Unix. They can be present on the stack when we reach their parent frame if
the filter hasn't finished running yet or they can be gone if the filter
completed running, either succesfully or with unhandled exception.

This change adds skipping of filter funclet related exception trackers at
that place so that the common code processes them.

This fixes the GC hole mentioned in the title that was discovered when
running some tests with GCStress mode 2.

src/vm/exceptionhandling.h
src/vm/stackwalk.cpp

index 4dba095..6bf58d1 100644 (file)
@@ -653,6 +653,11 @@ public:
     {
         return !m_ExceptionFlags.UnwindHasStarted();
     }
+
+    EHClauseInfo* GetEHClauseInfo()
+    {
+        return &m_EHClauseInfo;
+    }
     
 private: ;
 
index ffcd453..d6b116b 100644 (file)
@@ -1727,10 +1727,12 @@ ProcessFuncletsForGCReporting:
                         // was a caller of an already executed exception handler based on the previous exception trackers.
                         // The handler funclet frames are already gone from the stack, so the exception trackers are the
                         // only source of evidence about it.
-                        // The filter funclet is different though, its frame is always present on the stack when its parent
-                        // frame is reached by the stack walker, because no exception can escape a filter funclet.
                         // This is different from Windows where the full stack is preserved until an exception is fully handled
                         // and so we can detect it just from walking the stack.
+                        // The filter funclet frames are different, they behave the same way on Windows and Unix. They can be present
+                        // on the stack when we reach their parent frame if the filter hasn't finished running yet or they can be
+                        // gone if the filter completed running, either succesfully or with unhandled exception.
+                        // So the special handling below ignores trackers belonging to filter clauses.
                         bool fProcessingFilterFunclet = !m_sfFuncletParent.IsNull() && !(m_fProcessNonFilterFunclet || m_fProcessIntermediaryNonFilterFunclet);
                         if (!fRecheckCurrentFrame && !fSkippingFunclet && (pTracker != NULL) && !fProcessingFilterFunclet)
                         {
@@ -1739,25 +1741,30 @@ ProcessFuncletsForGCReporting:
                             // frame matching the current frame, it means that the funclet stack frame was reclaimed.
                             StackFrame sfFuncletParent;
                             ExceptionTracker* pCurrTracker = pTracker;
-                            bool hasFuncletStarted = m_crawl.pThread->GetExceptionState()->GetCurrentEHClauseInfo()->IsManagedCodeEntered();
+
+                            bool hasFuncletStarted = pTracker->GetEHClauseInfo()->IsManagedCodeEntered();
 
                             while (pCurrTracker != NULL)
                             {
-                                if (hasFuncletStarted)
+                                // Ignore exception trackers for filter clauses, since their frames are handled the same way as on Windows
+                                if (pCurrTracker->GetEHClauseInfo()->GetClauseType() != COR_PRF_CLAUSE_FILTER)
                                 {
-                                    sfFuncletParent = pCurrTracker->GetCallerOfEnclosingClause();
+                                    if (hasFuncletStarted)
+                                    {
+                                        sfFuncletParent = pCurrTracker->GetCallerOfEnclosingClause();
+                                        if (!sfFuncletParent.IsNull() && ExceptionTracker::IsUnwoundToTargetParentFrame(&m_crawl, sfFuncletParent))
+                                        {
+                                            break;
+                                        }
+                                    }
+
+                                    sfFuncletParent = pCurrTracker->GetCallerOfCollapsedEnclosingClause();
                                     if (!sfFuncletParent.IsNull() && ExceptionTracker::IsUnwoundToTargetParentFrame(&m_crawl, sfFuncletParent))
                                     {
                                         break;
                                     }
                                 }
 
-                                sfFuncletParent = pCurrTracker->GetCallerOfCollapsedEnclosingClause();
-                                if (!sfFuncletParent.IsNull() && ExceptionTracker::IsUnwoundToTargetParentFrame(&m_crawl, sfFuncletParent))
-                                {
-                                    break;
-                                }
-
                                 // Funclets handling exception for trackers older than the current one were always started,
                                 // since the current tracker was created due to an exception in the funclet belonging to 
                                 // the previous tracker.