11using System ;
22using System . Collections . Generic ;
3+ using System . Linq ;
34using System . Runtime . CompilerServices ;
45using AsmResolver . PE . DotNet . Cil ;
56
@@ -10,20 +11,36 @@ internal readonly ref struct CilMaxStackCalculator
1011 private readonly CilMethodBody _body ;
1112 private readonly Stack < StackState > _agenda ;
1213 private readonly int ? [ ] _recordedStackSizes ;
14+ private readonly Dictionary < int , List < CilExceptionHandler > > ? _handlers ;
1315
1416 public CilMaxStackCalculator ( CilMethodBody body )
1517 {
1618 _body = body ?? throw new ArgumentNullException ( nameof ( body ) ) ;
1719
18- if ( _body . Instructions . Count > 0 )
20+ if ( _body . Instructions . Count <= 0 )
1921 {
20- _agenda = new Stack < StackState > ( ) ;
21- _recordedStackSizes = new int ? [ _body . Instructions . Count ] ;
22+ _agenda = null ! ;
23+ _recordedStackSizes = null ! ;
2224 }
2325 else
2426 {
25- _agenda = null ! ;
26- _recordedStackSizes = null ! ;
27+ _agenda = new Stack < StackState > ( ) ;
28+ _recordedStackSizes = new int ? [ _body . Instructions . Count ] ;
29+
30+ if ( body . ExceptionHandlers . Count > 0 )
31+ {
32+ _handlers = new Dictionary < int , List < CilExceptionHandler > > ( body . ExceptionHandlers . Count ) ;
33+ foreach ( var handler in body . ExceptionHandlers )
34+ {
35+ if ( handler . TryStart is not { Offset : int startOffset } )
36+ continue ;
37+
38+ if ( ! _handlers . TryGetValue ( startOffset , out var list ) )
39+ _handlers . Add ( startOffset , list = new List < CilExceptionHandler > ( ) ) ;
40+
41+ list . Add ( handler ) ;
42+ }
43+ }
2744 }
2845 }
2946
@@ -34,14 +51,14 @@ public int Compute()
3451
3552 int result = 0 ;
3653
37- // Add entry points to agenda .
38- ScheduleEntryPoints ( ) ;
54+ // Schedule offset 0 .
55+ _agenda . Push ( new StackState ( 0 , 0 ) ) ;
3956
4057 while ( _agenda . Count > 0 )
4158 {
4259 var currentState = _agenda . Pop ( ) ;
4360
44- // Check if we got passed the end of the method body. This only happens if the CIL code is invalid.
61+ // Check if we passed the end of the method body. This only happens if the CIL code is invalid.
4562 if ( currentState . InstructionIndex >= _body . Instructions . Count )
4663 {
4764 var last = _body . Instructions [ _body . Instructions . Count - 1 ] ;
@@ -53,15 +70,18 @@ public int Compute()
5370 {
5471 // Check if previously visited state is consistent with current observation.
5572 if ( recordedStackSize . Value != currentState . StackSize )
73+ {
5674 throw new StackImbalanceException ( _body , _body . Instructions [ currentState . InstructionIndex ] . Offset ) ;
75+ }
5776 }
5877 else
5978 {
6079 // Mark instruction as visited and store current state.
6180 _recordedStackSizes [ currentState . InstructionIndex ] = currentState . StackSize ;
6281
6382 // Schedule successors of current instruction.
64- ScheduleSuccessors ( currentState ) ;
83+ ScheduleNaturalSuccessors ( in currentState ) ;
84+ ScheduleExceptionalSuccessors ( in currentState ) ;
6585 }
6686
6787 // Maintain largest found stack size.
@@ -72,39 +92,7 @@ public int Compute()
7292 return result ;
7393 }
7494
75- private void ScheduleEntryPoints ( )
76- {
77- // Schedule offset 0.
78- _agenda . Push ( new StackState ( 0 , 0 ) ) ;
79-
80- // Handler blocks are not referenced explicitly by instructions.
81- // Therefore we need to schedule them explicitly as well.
82- var instructions = _body . Instructions ;
83-
84- for ( int i = 0 ; i < _body . ExceptionHandlers . Count ; i ++ )
85- {
86- var handler = _body . ExceptionHandlers [ i ] ;
87-
88- // Determine stack size at the start of the handler block.
89- int stackDelta = handler . HandlerType switch
90- {
91- CilExceptionHandlerType . Exception => 1 ,
92- CilExceptionHandlerType . Filter => 1 ,
93- CilExceptionHandlerType . Finally => 0 ,
94- CilExceptionHandlerType . Fault => 0 ,
95- _ => throw new ArgumentOutOfRangeException ( nameof ( handler . HandlerType ) )
96- } ;
97-
98- if ( handler . TryStart is { Offset : var o1 } )
99- _agenda . Push ( new StackState ( instructions . GetIndexByOffset ( o1 ) , 0 ) ) ;
100- if ( handler . HandlerStart is { Offset : var o2 } )
101- _agenda . Push ( new StackState ( instructions . GetIndexByOffset ( o2 ) , stackDelta ) ) ;
102- if ( handler . FilterStart is { Offset : var o3 } )
103- _agenda . Push ( new StackState ( instructions . GetIndexByOffset ( o3 ) , 1 ) ) ;
104- }
105- }
106-
107- private void ScheduleSuccessors ( in StackState currentState )
95+ private void ScheduleNaturalSuccessors ( in StackState currentState )
10896 {
10997 var instruction = _body . Instructions [ currentState . InstructionIndex ] ;
11098
@@ -134,6 +122,10 @@ private void ScheduleSuccessors(in StackState currentState)
134122 // Schedule branch target.
135123 switch ( instruction . Operand )
136124 {
125+ case ICilLabel label :
126+ ScheduleLabel ( currentState . InstructionIndex , label , nextStackSize ) ;
127+ break ;
128+
137129 case sbyte delta :
138130 ScheduleDelta ( currentState . InstructionIndex , delta , nextStackSize ) ;
139131 break ;
@@ -142,10 +134,6 @@ private void ScheduleSuccessors(in StackState currentState)
142134 ScheduleDelta ( currentState . InstructionIndex , delta , nextStackSize ) ;
143135 break ;
144136
145- case ICilLabel label :
146- ScheduleLabel ( currentState . InstructionIndex , label , nextStackSize ) ;
147- break ;
148-
149137 default :
150138 throw new NotSupportedException (
151139 $ "Invalid or unsupported operand type at offset IL_{ instruction . Offset : X4} .") ;
@@ -197,6 +185,38 @@ private void ScheduleSuccessors(in StackState currentState)
197185 }
198186 }
199187
188+ private void ScheduleExceptionalSuccessors ( in StackState currentState )
189+ {
190+ var instruction = _body . Instructions [ currentState . InstructionIndex ] ;
191+
192+ // Did we just enter at least one exception handler try block?
193+ if ( _handlers is null || ! _handlers . TryGetValue ( instruction . Offset , out var handlers ) )
194+ return ;
195+
196+ // ECMA-335 Section I.12.4.2.8.1 prohibits entering try blocks with a non-zero stack size.
197+ if ( currentState . StackSize != 0 )
198+ throw new StackImbalanceException ( _body , instruction . Offset ) ;
199+
200+ // Schedule handler starts.
201+ foreach ( var handler in handlers )
202+ {
203+ // Determine stack size at the start of the handler block.
204+ int stackDelta = handler . HandlerType switch
205+ {
206+ CilExceptionHandlerType . Exception => 1 ,
207+ CilExceptionHandlerType . Filter => 1 ,
208+ CilExceptionHandlerType . Finally => 0 ,
209+ CilExceptionHandlerType . Fault => 0 ,
210+ _ => throw new ArgumentOutOfRangeException ( nameof ( handler . HandlerType ) )
211+ } ;
212+
213+ if ( handler . HandlerStart is { } handlerStart )
214+ ScheduleLabel ( currentState . InstructionIndex , handlerStart , stackDelta ) ;
215+ if ( handler . FilterStart is { } filterStart )
216+ ScheduleLabel ( currentState . InstructionIndex , filterStart , 1 ) ;
217+ }
218+ }
219+
200220 private void ScheduleLabel ( int currentIndex , ICilLabel label , int nextStackSize )
201221 {
202222 int nextIndex = _body . Instructions . GetIndexByOffset ( label . Offset ) ;
0 commit comments