private static bool InternalTryParse <TOptions>(string[] args, ParserOptions parserOptions, out TOptions options, out Exception ex) where TOptions : new() { options = default; ex = null; TypeArgumentInfo arguments = null; try { // build a list of properties for the type passed in. // this will throw for cases where the type is incorrecly annotated with attributes TypeHelpers.ScanTypeForProperties <TOptions>(out arguments); // before we do anything, let's expand the response files (if any). args = ExpandResponseFiles(args); // short circuit the request for help! if (args.Length == 1) { if (args[0] == HelpGenerator.RequestShortHelpParameter || args[0] == "/?") { HelpGenerator.DisplayHelp(HelpFormat.Short, arguments, ColorScheme.Get()); ex = new HelpRequestedException(); return(false); } else if (args[0] == HelpGenerator.RequestLongHelpParameter) { HelpGenerator.DisplayHelp(HelpFormat.Full, arguments, ColorScheme.Get()); ex = new HelpRequestedException(); return(false); } } // we have groups! if (!arguments.ArgumentGroups.ContainsKey(string.Empty)) { return(ParseCommandGroups(args, ref options, arguments, parserOptions)); } // parse the arguments and build the options object options = InternalParse <TOptions>(args, 0, arguments.ArgumentGroups[string.Empty], parserOptions); return(true); } catch (Exception innerParserException) { ex = new ParserException(innerParserException.Message, innerParserException); if (parserOptions.LogParseErrorToConsole) { string errorFormat = $"[{ColorScheme.Get().ErrorColor}!Error]: {{0}} {{1}}"; Colorizer.WriteLine(errorFormat, ex.Message, Environment.NewLine); HelpGenerator.DisplayHelp(HelpFormat.Short, arguments, ColorScheme.Get()); } return(false); } }
private static void DisplayDetailedHelp(TypeArgumentInfo type, IColors colors) { string exeName = Assembly.GetEntryAssembly()?.GetName()?.Name; Colorizer.WriteLine("Usage: "); foreach (var item in type.ArgumentGroups.Keys) { DisplayDetailedArgumentHelp(exeName, item, type.ArgumentGroups[item], colors); } }
private static void DisplayShortHelp(TypeArgumentInfo type) { string exeName = Assembly.GetEntryAssembly()?.GetName()?.Name; Colorizer.WriteLine("Usage: "); DisplayCommandLine(exeName, type); Colorizer.WriteLine(string.Empty); Colorizer.WriteLine("For detailed information run '[White!{0} --help]'.", exeName); }
private static void DisplayHelp(string helpFormat, TypeArgumentInfo arguments) { if (helpFormat == RequestShortHelpParameter || helpFormat == "/?") { DisplayShortHelp(arguments); } else if (helpFormat == RequestLongHelpParameter) { DisplayDetailedHelp(arguments); } }
private static void DisplayCommandLine(string exeName, TypeArgumentInfo type) { foreach (var group in type.ArgumentGroups) { Colorizer.Write(" [White!{0}.exe] ", exeName); if (!string.IsNullOrEmpty(group.Key)) { Colorizer.Write($"[Green!{group.Key}] "); } DisplayCommandLine(group.Value); } }
private static void DisplayShortHelp(TypeArgumentInfo type, IColors colors) { string exeName = Assembly.GetEntryAssembly()?.GetName()?.Name; Colorizer.WriteLine("Usage: "); DisplayCommandLine(exeName, type, colors); Colorizer.WriteLine(string.Empty); string errorFormat = $"For detailed information run '[{colors.AssemblyNameColor}!{{0}} --help]'."; Colorizer.WriteLine(errorFormat, exeName); }
internal static void DisplayHelp(HelpFormat helpFormat, TypeArgumentInfo arguments, IColors colors) { switch (helpFormat) { case HelpFormat.Short: DisplayShortHelp(arguments, colors); break; case HelpFormat.Full: DisplayDetailedHelp(arguments, colors); break; } }
private static void DisplayCommandLine(string exeName, TypeArgumentInfo type, IColors colors) { foreach (var group in type.ArgumentGroups) { string assemblyNameFormat = $" [{colors.AssemblyNameColor}!{{0}}.exe] "; // " [White!{{0}}.exe] " Colorizer.Write(assemblyNameFormat, exeName); if (!string.IsNullOrEmpty(group.Key)) { string groupFormat = $" [{colors.ArgumentGroupColor}!{{0}}] "; // " [Green!{{0}}] " Colorizer.Write(string.Format(groupFormat, group.Key)); } DisplayCommandLine(group.Value, colors); } }
public static bool TryParse <TOptions>(string[] args, out TOptions options) where TOptions : new() { options = default(TOptions); TypeArgumentInfo arguments = null; try { // build a list of properties for the type passed in. // this will throw for cases where the type is incorrecly annotated with attributes TypeHelpers.ScanTypeForProperties <TOptions>(out arguments); // before we do anything, let's expand the response files (if any). args = ExpandResponseFiles(args); // short circuit the request for help! if (args.Length == 1) { if (args[0] == HelpGenerator.RequestShortHelpParameter || args[0] == "/?") { HelpGenerator.DisplayHelp(HelpFormat.Short, arguments); return(false); } else if (args[0] == HelpGenerator.RequestLongHelpParameter) { HelpGenerator.DisplayHelp(HelpFormat.Full, arguments); return(false); } } // we have groups! if (!arguments.ArgumentGroups.ContainsKey(string.Empty)) { return(ParseCommandGroups(args, ref options, arguments)); } // parse the arguments and build the options object options = InternalParse <TOptions>(args, 0, arguments.ArgumentGroups[string.Empty]); return(true); } catch (Exception ex) { Colorizer.WriteLine($"[Red!Error]: {ex.Message} {Environment.NewLine}"); HelpGenerator.DisplayHelp(HelpFormat.Short, arguments); return(false); } }
internal static void DisplayHelp(HelpFormat helpFormat, TypeArgumentInfo arguments) { switch (helpFormat) { case HelpFormat.Short: DisplayShortHelp(arguments); break; case HelpFormat.Full: DisplayDetailedHelp(arguments); break; default: throw new ArgumentException("Unrecognized help format", nameof(helpFormat)); } }
internal TypeCache(MetadataType type, Logger?logger, ILProvider ilProvider) { Debug.Assert(type == type.GetTypeDefinition()); Debug.Assert(!CompilerGeneratedNames.IsGeneratedMemberName(type.Name)); Type = type; var callGraph = new CompilerGeneratedCallGraph(); var userDefinedMethods = new HashSet <MethodDesc>(); void ProcessMethod(MethodDesc method) { Debug.Assert(method == method.GetTypicalMethodDefinition()); bool isStateMachineMember = CompilerGeneratedNames.IsStateMachineType(((MetadataType)method.OwningType).Name); if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(method.Name)) { if (!isStateMachineMember) { // If it's not a nested function, track as an entry point to the call graph. var added = userDefinedMethods.Add(method); Debug.Assert(added); } } else { // We don't expect lambdas or local functions to be emitted directly into // state machine types. Debug.Assert(!isStateMachineMember); } // Discover calls or references to lambdas or local functions. This includes // calls to local functions, and lambda assignments (which use ldftn). var methodBody = ilProvider.GetMethodIL(method); if (methodBody != null) { ILReader reader = new ILReader(methodBody.GetILBytes()); while (reader.HasNext) { ILOpcode opcode = reader.ReadILOpcode(); switch (opcode) { case ILOpcode.ldftn: case ILOpcode.ldtoken: case ILOpcode.call: case ILOpcode.callvirt: case ILOpcode.newobj: { MethodDesc?referencedMethod = methodBody.GetObject(reader.ReadILToken(), NotFoundBehavior.ReturnNull) as MethodDesc; if (referencedMethod == null) { continue; } referencedMethod = referencedMethod.GetTypicalMethodDefinition(); if (referencedMethod.IsConstructor && referencedMethod.OwningType is MetadataType generatedType && // Don't consider calls in the same type, like inside a static constructor method.OwningType != generatedType && CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name)) { Debug.Assert(generatedType.IsTypeDefinition); // fill in null for now, attribute providers will be filled in later _generatedTypeToTypeArgumentInfo ??= new Dictionary <MetadataType, TypeArgumentInfo>(); if (!_generatedTypeToTypeArgumentInfo.TryAdd(generatedType, new TypeArgumentInfo(method, null))) { var alreadyAssociatedMethod = _generatedTypeToTypeArgumentInfo[generatedType].CreatingMethod; logger?.LogWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithUserMethod, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), generatedType.GetDisplayName()); } continue; } if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(referencedMethod.Name)) { continue; } if (isStateMachineMember) { callGraph.TrackCall((MetadataType)method.OwningType, referencedMethod); } else { callGraph.TrackCall(method, referencedMethod); } } break; case ILOpcode.stsfld: { // Same as above, but stsfld instead of a call to the constructor FieldDesc?field = methodBody.GetObject(reader.ReadILToken()) as FieldDesc; if (field == null) { continue; } field = field.GetTypicalFieldDefinition(); if (field.OwningType is MetadataType generatedType && // Don't consider field accesses in the same type, like inside a static constructor method.OwningType != generatedType && CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name)) { Debug.Assert(generatedType.IsTypeDefinition); _generatedTypeToTypeArgumentInfo ??= new Dictionary <MetadataType, TypeArgumentInfo>(); if (!_generatedTypeToTypeArgumentInfo.TryAdd(generatedType, new TypeArgumentInfo(method, null))) { // It's expected that there may be multiple methods associated with the same static closure environment. // All of these methods will substitute the same type arguments into the closure environment // (if it is generic). Don't warn. } continue; } } break; default: reader.Skip(opcode); break; } } } if (TryGetStateMachineType(method, out MetadataType? stateMachineType)) { Debug.Assert(stateMachineType.ContainingType == type || (CompilerGeneratedNames.IsGeneratedMemberName(stateMachineType.ContainingType.Name) && stateMachineType.ContainingType.ContainingType == type)); Debug.Assert(stateMachineType == stateMachineType.GetTypeDefinition()); callGraph.TrackCall(method, stateMachineType); _compilerGeneratedTypeToUserCodeMethod ??= new Dictionary <MetadataType, MethodDesc>(); if (!_compilerGeneratedTypeToUserCodeMethod.TryAdd(stateMachineType, method)) { var alreadyAssociatedMethod = _compilerGeneratedTypeToUserCodeMethod[stateMachineType]; logger?.LogWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithStateMachine, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), stateMachineType.GetDisplayName()); } // Already warned above if multiple methods map to the same type // Fill in null for argument providers now, the real providers will be filled in later _generatedTypeToTypeArgumentInfo ??= new Dictionary <MetadataType, TypeArgumentInfo>(); _generatedTypeToTypeArgumentInfo[stateMachineType] = new TypeArgumentInfo(method, null); } } // Look for state machine methods, and methods which call local functions. foreach (MethodDesc method in type.GetMethods()) { ProcessMethod(method); } // Also scan compiler-generated state machine methods (in case they have calls to nested functions), // and nested functions inside compiler-generated closures (in case they call other nested functions). // State machines can be emitted into lambda display classes, so we need to go down at least two // levels to find calls from iterator nested functions to other nested functions. We just recurse into // all compiler-generated nested types to avoid depending on implementation details. foreach (var nestedType in GetCompilerGeneratedNestedTypes(type)) { foreach (var method in nestedType.GetMethods()) { ProcessMethod(method); } } // Now we've discovered the call graphs for calls to nested functions. // Use this to map back from nested functions to the declaring user methods. // Note: This maps all nested functions back to the user code, not to the immediately // declaring local function. The IL doesn't contain enough information in general for // us to determine the nesting of local functions and lambdas. // Note: this only discovers nested functions which are referenced from the user // code or its referenced nested functions. There is no reliable way to determine from // IL which user code an unused nested function belongs to. foreach (var userDefinedMethod in userDefinedMethods) { var callees = callGraph.GetReachableMembers(userDefinedMethod); if (!callees.Any()) { continue; } _compilerGeneratedMembers ??= new Dictionary <MethodDesc, List <TypeSystemEntity> >(); _compilerGeneratedMembers.Add(userDefinedMethod, new List <TypeSystemEntity>(callees)); foreach (var compilerGeneratedMember in callees) { switch (compilerGeneratedMember) { case MethodDesc nestedFunction: Debug.Assert(CompilerGeneratedNames.IsLambdaOrLocalFunction(nestedFunction.Name)); // Nested functions get suppressions from the user method only. _compilerGeneratedMethodToUserCodeMethod ??= new Dictionary <MethodDesc, MethodDesc>(); if (!_compilerGeneratedMethodToUserCodeMethod.TryAdd(nestedFunction, userDefinedMethod)) { var alreadyAssociatedMethod = _compilerGeneratedMethodToUserCodeMethod[nestedFunction]; logger?.LogWarning(new MessageOrigin(userDefinedMethod), DiagnosticId.MethodsAreAssociatedWithUserMethod, userDefinedMethod.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), nestedFunction.GetDisplayName()); } break; case MetadataType stateMachineType: // Types in the call graph are always state machine types // For those all their methods are not tracked explicitly in the call graph; instead, they // are represented by the state machine type itself. // We are already tracking the association of the state machine type to the user code method // above, so no need to track it here. Debug.Assert(CompilerGeneratedNames.IsStateMachineType(stateMachineType.Name)); break; default: throw new InvalidOperationException(); } } } // Now that we have instantiating methods fully filled out, walk the generated types and fill in the attribute // providers if (_generatedTypeToTypeArgumentInfo != null) { foreach (var generatedType in _generatedTypeToTypeArgumentInfo.Keys) { Debug.Assert(generatedType == generatedType.GetTypeDefinition()); if (HasGenericParameters(generatedType)) { MapGeneratedTypeTypeParameters(generatedType); } } }
private static bool ParseCommandGroups <TOptions>(string[] args, ref TOptions options, TypeArgumentInfo arguments) where TOptions : new() { if (arguments.ActionArgument == null) { throw new ArgumentException("Cannot have groups unless Command argument has been specified"); } if (args.Length == 0) { throw new ArgumentException("Required parameters have not been specified"); } // parse based on the command passed in (the first arg). if (!arguments.ArgumentGroups.ContainsKey(args[0])) { throw new ArgumentException($"Unknown command [Cyan!{args[0]}]"); } // short circuit the request for help! if (args.Length == 2 && (args[1] == "/?" || args[1] == "-?")) { HelpGenerator.DisplayHelpForCommmand(args[0], arguments.ArgumentGroups[args[0]]); return(false); } options = InternalParse <TOptions>(args, 1, arguments.ArgumentGroups[args[0]]); arguments.ActionArgument.SetValue(options, PropertyHelpers.GetValueAsType(args[0], arguments.ActionArgument)); return(true); }