|
16 | 16 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
17 | 17 | // DEALINGS IN THE SOFTWARE. |
18 | 18 |
|
| 19 | +using System; |
19 | 20 | using System.Collections.Generic; |
20 | 21 | using System.Diagnostics; |
| 22 | +using System.Diagnostics.CodeAnalysis; |
21 | 23 | using System.Linq; |
22 | 24 |
|
23 | 25 | using ICSharpCode.Decompiler.FlowAnalysis; |
@@ -216,12 +218,118 @@ void ProcessBlock(Block block, ref bool blockContainerNeedsCleanup) |
216 | 218 | // (1st pass was in ControlFlowSimplification) |
217 | 219 | SimplifySwitchInstruction(block, context); |
218 | 220 | } |
| 221 | + |
| 222 | + InlineSwitchExpressionDefaultCaseThrowHelper(block, context); |
| 223 | + } |
| 224 | + |
| 225 | + // call ThrowInvalidOperationException(...) |
| 226 | + // leave IL_0000 (ldloc temp) |
| 227 | + // |
| 228 | + // -or- |
| 229 | + // |
| 230 | + // call ThrowSwitchExpressionException(...) |
| 231 | + // leave IL_0000 (ldloc temp) |
| 232 | + // |
| 233 | + // -to- |
| 234 | + // |
| 235 | + // throw(newobj SwitchExpressionException(...)) |
| 236 | + internal static void InlineSwitchExpressionDefaultCaseThrowHelper(Block block, ILTransformContext context) |
| 237 | + { |
| 238 | +#nullable enable |
| 239 | + // due to our of of basic blocks at this point, |
| 240 | + // switch instructions can only appear as last instruction |
| 241 | + var sw = block.Instructions.LastOrDefault() as SwitchInstruction; |
| 242 | + if (sw == null) |
| 243 | + return; |
| 244 | + |
| 245 | + IMethod[] exceptionCtorTable = new IMethod[2]; |
| 246 | + |
| 247 | + exceptionCtorTable[0] = FindConstructor("System.InvalidOperationException")!; |
| 248 | + exceptionCtorTable[1] = FindConstructor("System.Runtime.CompilerServices.SwitchExpressionException", typeof(object))!; |
| 249 | + |
| 250 | + if (exceptionCtorTable[0] == null && exceptionCtorTable[1] == null) |
| 251 | + return; |
| 252 | + |
| 253 | + if (sw.GetDefaultSection() is not { Body: Branch { TargetBlock: Block defaultBlock } } defaultSection) |
| 254 | + return; |
| 255 | + if (defaultBlock is { Instructions: [var call, Branch or Leave] }) |
| 256 | + { |
| 257 | + if (!MatchThrowHelperCall(call, out IMethod? exceptionCtor, out ILInstruction? value)) |
| 258 | + return; |
| 259 | + context.Step("SwitchExpressionDefaultCaseTransform", block.Instructions[0]); |
| 260 | + var newObj = new NewObj(exceptionCtor); |
| 261 | + if (value != null) |
| 262 | + newObj.Arguments.Add(value); |
| 263 | + defaultBlock.Instructions[0] = new Throw(newObj).WithILRange(defaultBlock.Instructions[0]).WithILRange(defaultBlock.Instructions[1]); |
| 264 | + defaultBlock.Instructions.RemoveAt(1); |
| 265 | + defaultSection.IsCompilerGeneratedDefaultSection = true; |
| 266 | + } |
| 267 | + else if (defaultBlock is { Instructions: [Throw { Argument: NewObj { Method: var ctor, Arguments: [var arg] } newObj }] } |
| 268 | + && ctor.Equals(exceptionCtorTable[1])) |
| 269 | + { |
| 270 | + defaultSection.IsCompilerGeneratedDefaultSection = true; |
| 271 | + } |
| 272 | + else |
| 273 | + { |
| 274 | + return; |
| 275 | + } |
| 276 | + |
| 277 | + bool MatchThrowHelperCall(ILInstruction inst, [NotNullWhen(true)] out IMethod? exceptionCtor, out ILInstruction? value) |
| 278 | + { |
| 279 | + exceptionCtor = null; |
| 280 | + value = null; |
| 281 | + if (inst is not Call call) |
| 282 | + return false; |
| 283 | + if (call.Method.DeclaringType.FullName != "<PrivateImplementationDetails>") |
| 284 | + return false; |
| 285 | + switch (call.Arguments.Count) |
| 286 | + { |
| 287 | + case 0: |
| 288 | + if (call.Method.Name != "ThrowInvalidOperationException") |
| 289 | + return false; |
| 290 | + exceptionCtor = exceptionCtorTable[0]; |
| 291 | + break; |
| 292 | + case 1: |
| 293 | + if (call.Method.Name != "ThrowSwitchExpressionException") |
| 294 | + return false; |
| 295 | + exceptionCtor = exceptionCtorTable[1]; |
| 296 | + value = call.Arguments[0]; |
| 297 | + break; |
| 298 | + default: |
| 299 | + return false; |
| 300 | + } |
| 301 | + return exceptionCtor != null; |
| 302 | + } |
| 303 | + |
| 304 | + IMethod? FindConstructor(string fullTypeName, params Type[] argumentTypes) |
| 305 | + { |
| 306 | + IType exceptionType = context.TypeSystem.FindType(new FullTypeName(fullTypeName)); |
| 307 | + var types = argumentTypes.SelectArray(context.TypeSystem.FindType); |
| 308 | + |
| 309 | + foreach (var ctor in exceptionType.GetConstructors(m => !m.IsStatic && m.Parameters.Count == argumentTypes.Length)) |
| 310 | + { |
| 311 | + bool found = true; |
| 312 | + foreach (var pair in ctor.Parameters.Select(p => p.Type).Zip(types)) |
| 313 | + { |
| 314 | + if (!NormalizeTypeVisitor.IgnoreNullability.EquivalentTypes(pair.Item1, pair.Item2)) |
| 315 | + { |
| 316 | + found = false; |
| 317 | + break; |
| 318 | + } |
| 319 | + } |
| 320 | + if (found) |
| 321 | + return ctor; |
| 322 | + } |
| 323 | + |
| 324 | + return null; |
| 325 | + } |
| 326 | +#nullable restore |
219 | 327 | } |
220 | 328 |
|
221 | 329 | internal static void SimplifySwitchInstruction(Block block, ILTransformContext context) |
222 | 330 | { |
223 | 331 | // due to our of of basic blocks at this point, |
224 | | - // switch instructions can only appear as last insturction |
| 332 | + // switch instructions can only appear as last instruction |
225 | 333 | var sw = block.Instructions.LastOrDefault() as SwitchInstruction; |
226 | 334 | if (sw == null) |
227 | 335 | return; |
@@ -445,7 +553,9 @@ private void AddNullCase(List<ControlFlowNode> flowNodes, List<ControlFlowNode> |
445 | 553 | !nullableBlock.Instructions.SecondToLastOrDefault().MatchIfInstruction(out var cond, out var trueInst) || |
446 | 554 | !cond.MatchLogicNot(out var getHasValue) || |
447 | 555 | !NullableLiftingTransform.MatchHasValueCall(getHasValue, out ILInstruction nullableInst)) |
| 556 | + { |
448 | 557 | return; |
| 558 | + } |
449 | 559 |
|
450 | 560 | // could check that nullableInst is ldloc or ldloca and that the switch variable matches a GetValueOrDefault |
451 | 561 | // but the effect of adding an incorrect block to the flowBlock list would only be disasterous if it branched directly |
|
0 commit comments