// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.CSharp.Emit; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Emit; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { internal sealed class MethodCompiler : CSharpSymbolVisitor { private readonly CSharpCompilation _compilation; private readonly bool _emittingPdb; private readonly CancellationToken _cancellationToken; private readonly DiagnosticBag _diagnostics; private readonly bool _hasDeclarationErrors; private readonly PEModuleBuilder _moduleBeingBuiltOpt; // Null if compiling for diagnostics private readonly Predicate _filterOpt; // If not null, limit analysis to specific symbols private readonly DebugDocumentProvider _debugDocumentProvider; // // MethodCompiler employs concurrency by following flattened fork/join pattern. // // For every item that we want to compile in parallel a new task is forked. // compileTaskQueue is used to track and observe all the tasks. // Once compileTaskQueue is empty, we know that there are no more tasks (and no more can be created) // and that means we are done compiling. WaitForWorkers ensures this condition. // // Note that while tasks may fork more tasks (nested types, lambdas, whatever else that may introduce more types), // we do not want any child/parent relationship between spawned tasks and their creators. // Creator has no real dependencies on the completion of its children and should finish and release any resources // as soon as it can regardless of the tasks it may have spawned. // // Stack is used so that the wait would observe the most recently added task and have // more chances to do inlined execution. private ConcurrentStack _compilerTasks; // This field tracks whether any bound method body had hasErrors set or whether any constant field had a bad value. // We track it so that we can abort emission in the event that an error occurs without a corresponding diagnostic // (e.g. if this module depends on a bad type or constant from another module). // CONSIDER: instead of storing a flag, we could track the first member symbol with an error (to improve the diagnostic). // NOTE: once the flag is set to true, it should never go back to false!!! // Do not use this as a short-circuiting for stages that might produce diagnostics. // That would make diagnostics to depend on the random order in which methods are compiled. private bool _globalHasErrors; private void SetGlobalErrorIfTrue(bool arg) { //NOTE: this is not a volatile write // for correctness we need only single threaded consistency. // Within a single task - if we have got an error it may not be safe to continue with some lowerings. // It is ok if other tasks will see the change after some delay or does not observe at all. // Such races are unavoidable and will just result in performing some work that is safe to do // but may no longer be needed. // The final Join of compiling tasks cannot happen without interlocked operations and that // will ensure that any write of the flag is globally visible. if (arg) { _globalHasErrors = true; } } // Internal for testing only. internal MethodCompiler(CSharpCompilation compilation, PEModuleBuilder moduleBeingBuiltOpt, bool emittingPdb, bool hasDeclarationErrors, DiagnosticBag diagnostics, Predicate filterOpt, CancellationToken cancellationToken) { Debug.Assert(compilation != null); Debug.Assert(diagnostics != null); _compilation = compilation; _moduleBeingBuiltOpt = moduleBeingBuiltOpt; _emittingPdb = emittingPdb; _cancellationToken = cancellationToken; _diagnostics = diagnostics; _filterOpt = filterOpt; _hasDeclarationErrors = hasDeclarationErrors; SetGlobalErrorIfTrue(hasDeclarationErrors); if (emittingPdb || moduleBeingBuiltOpt?.EmitOptions.EmitDynamicAnalysisData == true) { _debugDocumentProvider = (path, basePath) => moduleBeingBuiltOpt.DebugDocumentsBuilder.GetOrAddDebugDocument(path, basePath, CreateDebugDocumentForFile); } } public static void CompileMethodBodies( CSharpCompilation compilation, PEModuleBuilder moduleBeingBuiltOpt, bool generateDebugInfo, bool hasDeclarationErrors, DiagnosticBag diagnostics, Predicate filterOpt, CancellationToken cancellationToken) { Debug.Assert(compilation != null); Debug.Assert(diagnostics != null); if (compilation.PreviousSubmission != null) { // In case there is a previous submission, we should ensure // it has already created anonymous type/delegates templates // NOTE: if there are any errors, we will pick up what was created anyway compilation.PreviousSubmission.EnsureAnonymousTypeTemplates(cancellationToken); // TODO: revise to use a loop instead of a recursion } MethodCompiler methodCompiler = new MethodCompiler( compilation, moduleBeingBuiltOpt, generateDebugInfo, hasDeclarationErrors, diagnostics, filterOpt, cancellationToken); if (compilation.Options.ConcurrentBuild) { methodCompiler._compilerTasks = new ConcurrentStack(); } // directly traverse global namespace (no point to defer this to async) methodCompiler.CompileNamespace(compilation.SourceModule.GlobalNamespace); methodCompiler.WaitForWorkers(); // compile additional and anonymous types if any if (moduleBeingBuiltOpt != null) { var additionalTypes = moduleBeingBuiltOpt.GetAdditionalTopLevelTypes(); if (!additionalTypes.IsEmpty) { methodCompiler.CompileSynthesizedMethods(additionalTypes, diagnostics); } // By this time we have processed all types reachable from module's global namespace compilation.AnonymousTypeManager.AssignTemplatesNamesAndCompile(methodCompiler, moduleBeingBuiltOpt, diagnostics); methodCompiler.WaitForWorkers(); var privateImplClass = moduleBeingBuiltOpt.PrivateImplClass; if (privateImplClass != null) { // all threads that were adding methods must be finished now, we can freeze the class: privateImplClass.Freeze(); methodCompiler.CompileSynthesizedMethods(privateImplClass, diagnostics); } } // If we are trying to emit and there's an error without a corresponding diagnostic (e.g. because // we depend on an invalid type or constant from another module), then explicitly add a diagnostic. // This diagnostic is not very helpful to the user, but it will prevent us from emitting an invalid // module or crashing. if (moduleBeingBuiltOpt != null && (methodCompiler._globalHasErrors || moduleBeingBuiltOpt.SourceModule.HasBadAttributes) && !diagnostics.HasAnyErrors() && !hasDeclarationErrors) { diagnostics.Add(ErrorCode.ERR_ModuleEmitFailure, NoLocation.Singleton, ((Cci.INamedEntity)moduleBeingBuiltOpt).Name); } diagnostics.AddRange(compilation.AdditionalCodegenWarnings); // we can get unused field warnings only if compiling whole compilation. if (filterOpt == null) { WarnUnusedFields(compilation, diagnostics, cancellationToken); MethodSymbol entryPoint = GetEntryPoint(compilation, moduleBeingBuiltOpt, hasDeclarationErrors, diagnostics, cancellationToken); if (moduleBeingBuiltOpt != null && entryPoint != null && compilation.Options.OutputKind.IsApplication()) { moduleBeingBuiltOpt.SetPEEntryPoint(entryPoint, diagnostics); } } } private static MethodSymbol GetEntryPoint(CSharpCompilation compilation, PEModuleBuilder moduleBeingBuilt, bool hasDeclarationErrors, DiagnosticBag diagnostics, CancellationToken cancellationToken) { var entryPointAndDiagnostics = compilation.GetEntryPointAndDiagnostics(cancellationToken); if (entryPointAndDiagnostics == null) { return null; } Debug.Assert(!entryPointAndDiagnostics.Diagnostics.IsDefault); diagnostics.AddRange(entryPointAndDiagnostics.Diagnostics); var entryPoint = entryPointAndDiagnostics.MethodSymbol; var synthesizedEntryPoint = entryPoint as SynthesizedEntryPointSymbol; if (((object)synthesizedEntryPoint != null) && (moduleBeingBuilt != null) && !hasDeclarationErrors && !diagnostics.HasAnyErrors()) { var body = synthesizedEntryPoint.CreateBody(); const int methodOrdinal = -1; var emittedBody = GenerateMethodBody( moduleBeingBuilt, synthesizedEntryPoint, methodOrdinal, body, ImmutableArray.Empty, ImmutableArray.Empty, stateMachineTypeOpt: null, variableSlotAllocatorOpt: null, diagnostics: diagnostics, debugDocumentProvider: null, importChainOpt: null, emittingPdb: false, dynamicAnalysisSpans: ImmutableArray.Empty); moduleBeingBuilt.SetMethodBody(synthesizedEntryPoint, emittedBody); } Debug.Assert((object)entryPoint != null || entryPointAndDiagnostics.Diagnostics.HasAnyErrors() || !compilation.Options.Errors.IsDefaultOrEmpty); return entryPoint; } private void WaitForWorkers() { var tasks = _compilerTasks; if (tasks == null) { return; } Task curTask; while (tasks.TryPop(out curTask)) { curTask.GetAwaiter().GetResult(); } } private static void WarnUnusedFields(CSharpCompilation compilation, DiagnosticBag diagnostics, CancellationToken cancellationToken) { SourceAssemblySymbol assembly = (SourceAssemblySymbol)compilation.Assembly; diagnostics.AddRange(assembly.GetUnusedFieldWarnings(cancellationToken)); } public override object VisitNamespace(NamespaceSymbol symbol, TypeCompilationState arg) { if (!PassesFilter(_filterOpt, symbol)) { return null; } arg = null; // do not use compilation state of outer type. _cancellationToken.ThrowIfCancellationRequested(); if (_compilation.Options.ConcurrentBuild) { Task worker = CompileNamespaceAsTask(symbol); _compilerTasks.Push(worker); } else { CompileNamespace(symbol); } return null; } private Task CompileNamespaceAsTask(NamespaceSymbol symbol) { return Task.Run(UICultureUtilities.WithCurrentUICulture(() => { try { CompileNamespace(symbol); } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } }), _cancellationToken); } private void CompileNamespace(NamespaceSymbol symbol) { foreach (var s in symbol.GetMembersUnordered()) { s.Accept(this, null); } } public override object VisitNamedType(NamedTypeSymbol symbol, TypeCompilationState arg) { if (!PassesFilter(_filterOpt, symbol)) { return null; } arg = null; // do not use compilation state of outer type. _cancellationToken.ThrowIfCancellationRequested(); if (_compilation.Options.ConcurrentBuild) { Task worker = CompileNamedTypeAsTask(symbol); _compilerTasks.Push(worker); } else { CompileNamedType(symbol); } return null; } private Task CompileNamedTypeAsTask(NamedTypeSymbol symbol) { return Task.Run(UICultureUtilities.WithCurrentUICulture(() => { try { CompileNamedType(symbol); } catch (Exception e) when (FatalError.ReportUnlessCanceled(e)) { throw ExceptionUtilities.Unreachable; } }), _cancellationToken); } private void CompileNamedType(NamedTypeSymbol containingType) { var compilationState = new TypeCompilationState(containingType, _compilation, _moduleBeingBuiltOpt); _cancellationToken.ThrowIfCancellationRequested(); // Find the constructor of a script class. SynthesizedInstanceConstructor scriptCtor = null; SynthesizedInteractiveInitializerMethod scriptInitializer = null; SynthesizedEntryPointSymbol scriptEntryPoint = null; int scriptCtorOrdinal = -1; if (containingType.IsScriptClass) { // The field initializers of a script class could be arbitrary statements, // including blocks. Field initializers containing blocks need to // use a MethodBodySemanticModel to build up the appropriate tree of binders, and // MethodBodySemanticModel requires an "owning" method. That's why we're digging out // the constructor - it will own the field initializers. scriptCtor = containingType.GetScriptConstructor(); scriptInitializer = containingType.GetScriptInitializer(); scriptEntryPoint = containingType.GetScriptEntryPoint(); Debug.Assert((object)scriptCtor != null); Debug.Assert((object)scriptInitializer != null); } var synthesizedSubmissionFields = containingType.IsSubmissionClass ? new SynthesizedSubmissionFields(_compilation, containingType) : null; var processedStaticInitializers = new Binder.ProcessedFieldInitializers(); var processedInstanceInitializers = new Binder.ProcessedFieldInitializers(); var sourceTypeSymbol = containingType as SourceMemberContainerTypeSymbol; if ((object)sourceTypeSymbol != null) { _cancellationToken.ThrowIfCancellationRequested(); Binder.BindFieldInitializers(_compilation, scriptInitializer, sourceTypeSymbol.StaticInitializers, _diagnostics, ref processedStaticInitializers); _cancellationToken.ThrowIfCancellationRequested(); Binder.BindFieldInitializers(_compilation, scriptInitializer, sourceTypeSymbol.InstanceInitializers, _diagnostics, ref processedInstanceInitializers); if (compilationState.Emitting) { CompileSynthesizedExplicitImplementations(sourceTypeSymbol, compilationState); } } // Indicates if a static constructor is in the member, // so we can decide to synthesize a static constructor. bool hasStaticConstructor = false; var members = containingType.GetMembers(); for (int memberOrdinal = 0; memberOrdinal < members.Length; memberOrdinal++) { var member = members[memberOrdinal]; //When a filter is supplied, limit the compilation of members passing the filter. if (!PassesFilter(_filterOpt, member)) { continue; } switch (member.Kind) { case SymbolKind.NamedType: member.Accept(this, compilationState); break; case SymbolKind.Method: { MethodSymbol method = (MethodSymbol)member; if (method.IsScriptConstructor) { Debug.Assert(scriptCtorOrdinal == -1); Debug.Assert((object)scriptCtor == method); scriptCtorOrdinal = memberOrdinal; continue; } if ((object)method == scriptEntryPoint) { continue; } if (IsFieldLikeEventAccessor(method)) { continue; } if (method.IsPartialDefinition()) { method = method.PartialImplementationPart; if ((object)method == null) { continue; } } Binder.ProcessedFieldInitializers processedInitializers = (method.MethodKind == MethodKind.Constructor || method.IsScriptInitializer) ? processedInstanceInitializers : method.MethodKind == MethodKind.StaticConstructor ? processedStaticInitializers : default(Binder.ProcessedFieldInitializers); CompileMethod(method, memberOrdinal, ref processedInitializers, synthesizedSubmissionFields, compilationState); // Set a flag to indicate that a static constructor is created. if (method.MethodKind == MethodKind.StaticConstructor) { hasStaticConstructor = true; } break; } case SymbolKind.Property: { SourcePropertySymbol sourceProperty = member as SourcePropertySymbol; if ((object)sourceProperty != null && sourceProperty.IsSealed && compilationState.Emitting) { CompileSynthesizedSealedAccessors(sourceProperty, compilationState); } break; } case SymbolKind.Event: { SourceEventSymbol eventSymbol = member as SourceEventSymbol; if ((object)eventSymbol != null && eventSymbol.HasAssociatedField && !eventSymbol.IsAbstract && compilationState.Emitting) { CompileFieldLikeEventAccessor(eventSymbol, isAddMethod: true); CompileFieldLikeEventAccessor(eventSymbol, isAddMethod: false); } break; } case SymbolKind.Field: { SourceMemberFieldSymbol fieldSymbol = member as SourceMemberFieldSymbol; if ((object)fieldSymbol != null) { if (fieldSymbol.IsConst) { // We check specifically for constant fields with bad values because they never result // in bound nodes being inserted into method bodies (in which case, they would be covered // by the method-level check). ConstantValue constantValue = fieldSymbol.GetConstantValue(ConstantFieldsInProgress.Empty, earlyDecodingWellKnownAttributes: false); SetGlobalErrorIfTrue(constantValue == null || constantValue.IsBad); } if (fieldSymbol.IsFixed && compilationState.Emitting) { // force the generation of implementation types for fixed-size buffers TypeSymbol discarded = fieldSymbol.FixedImplementationType(compilationState.ModuleBuilderOpt); } } break; } } } Debug.Assert(containingType.IsScriptClass == (scriptCtorOrdinal >= 0)); // process additional anonymous type members if (AnonymousTypeManager.IsAnonymousTypeTemplate(containingType)) { var processedInitializers = default(Binder.ProcessedFieldInitializers); foreach (var method in AnonymousTypeManager.GetAnonymousTypeHiddenMethods(containingType)) { CompileMethod(method, -1, ref processedInitializers, synthesizedSubmissionFields, compilationState); } } // In the case there are field initializers but we haven't created an implicit static constructor (.cctor) for it, // (since we may not add .cctor implicitly created for decimals into the symbol table) // it is necessary for the compiler to generate the static constructor here if we are emitting. if (_moduleBeingBuiltOpt != null && !hasStaticConstructor && !processedStaticInitializers.BoundInitializers.IsDefaultOrEmpty) { Debug.Assert(processedStaticInitializers.BoundInitializers.All((init) => (init.Kind == BoundKind.FieldInitializer) && !((BoundFieldInitializer)init).Field.IsMetadataConstant)); MethodSymbol method = new SynthesizedStaticConstructor(sourceTypeSymbol); if (PassesFilter(_filterOpt, method)) { CompileMethod(method, -1, ref processedStaticInitializers, synthesizedSubmissionFields, compilationState); // If this method has been successfully built, we emit it. if (_moduleBeingBuiltOpt.GetMethodBody(method) != null) { _moduleBeingBuiltOpt.AddSynthesizedDefinition(sourceTypeSymbol, method); } } } // compile submission constructor last so that synthesized submission fields are collected from all script methods: if (scriptCtor != null && compilationState.Emitting) { Debug.Assert(scriptCtorOrdinal >= 0); var processedInitializers = new Binder.ProcessedFieldInitializers() { BoundInitializers = ImmutableArray.Empty }; CompileMethod(scriptCtor, scriptCtorOrdinal, ref processedInitializers, synthesizedSubmissionFields, compilationState); if (synthesizedSubmissionFields != null) { synthesizedSubmissionFields.AddToType(containingType, compilationState.ModuleBuilderOpt); } } // Emit synthesized methods produced during lowering if any if (_moduleBeingBuiltOpt != null) { CompileSynthesizedMethods(compilationState); } compilationState.Free(); } private void CompileSynthesizedMethods(PrivateImplementationDetails privateImplClass, DiagnosticBag diagnostics) { Debug.Assert(_moduleBeingBuiltOpt != null); var compilationState = new TypeCompilationState(null, _compilation, _moduleBeingBuiltOpt); foreach (MethodSymbol method in privateImplClass.GetMethods(new EmitContext(_moduleBeingBuiltOpt, null, diagnostics))) { Debug.Assert(method.SynthesizesLoweredBoundBody); method.GenerateMethodBody(compilationState, diagnostics); } CompileSynthesizedMethods(compilationState); compilationState.Free(); } private void CompileSynthesizedMethods(ImmutableArray additionalTypes, DiagnosticBag diagnostics) { foreach (var additionalType in additionalTypes) { var compilationState = new TypeCompilationState(additionalType, _compilation, _moduleBeingBuiltOpt); foreach (var method in additionalType.GetMethodsToEmit()) { method.GenerateMethodBody(compilationState, diagnostics); } if (!diagnostics.HasAnyErrors()) { CompileSynthesizedMethods(compilationState); } compilationState.Free(); } } private void CompileSynthesizedMethods(TypeCompilationState compilationState) { Debug.Assert(_moduleBeingBuiltOpt != null); Debug.Assert(compilationState.ModuleBuilderOpt == _moduleBeingBuiltOpt); var synthesizedMethods = compilationState.SynthesizedMethods; if (synthesizedMethods == null) { return; } var oldImportChain = compilationState.CurrentImportChain; try { foreach (var methodWithBody in synthesizedMethods) { var importChain = methodWithBody.ImportChainOpt; compilationState.CurrentImportChain = importChain; // We make sure that an asynchronous mutation to the diagnostic bag does not // confuse the method body generator by making a fresh bag and then loading // any diagnostics emitted into it back into the main diagnostic bag. var diagnosticsThisMethod = DiagnosticBag.GetInstance(); var method = methodWithBody.Method; var lambda = method as SynthesizedLambdaMethod; var variableSlotAllocatorOpt = ((object)lambda != null) ? _moduleBeingBuiltOpt.TryCreateVariableSlotAllocator(lambda, lambda.TopLevelMethod, diagnosticsThisMethod) : _moduleBeingBuiltOpt.TryCreateVariableSlotAllocator(method, method, diagnosticsThisMethod); // Synthesized methods have no ordinal stored in custom debug information (only user-defined methods have ordinals). // In case of async lambdas, which synthesize a state machine type during the following rewrite, the containing method has already been uniquely named, // so there is no need to produce a unique method ordinal for the corresponding state machine type, whose name includes the (unique) containing method name. const int methodOrdinal = -1; MethodBody emittedBody = null; try { // Local functions can be iterators as well as be async (lambdas can only be async), so we need to lower both iterators and async IteratorStateMachine iteratorStateMachine; BoundStatement loweredBody = IteratorRewriter.Rewrite(methodWithBody.Body, method, methodOrdinal, variableSlotAllocatorOpt, compilationState, diagnosticsThisMethod, out iteratorStateMachine); StateMachineTypeSymbol stateMachine = iteratorStateMachine; if (!loweredBody.HasErrors) { AsyncStateMachine asyncStateMachine; loweredBody = AsyncRewriter.Rewrite(loweredBody, method, methodOrdinal, variableSlotAllocatorOpt, compilationState, diagnosticsThisMethod, out asyncStateMachine); Debug.Assert(iteratorStateMachine == null || asyncStateMachine == null); stateMachine = stateMachine ?? asyncStateMachine; } if (!diagnosticsThisMethod.HasAnyErrors() && !_globalHasErrors) { emittedBody = GenerateMethodBody( _moduleBeingBuiltOpt, method, methodOrdinal, loweredBody, ImmutableArray.Empty, ImmutableArray.Empty, stateMachine, variableSlotAllocatorOpt, diagnosticsThisMethod, _debugDocumentProvider, method.GenerateDebugInfo ? importChain : null, emittingPdb: _emittingPdb, dynamicAnalysisSpans: ImmutableArray.Empty); } } catch (BoundTreeVisitor.CancelledByStackGuardException ex) { ex.AddAnError(_diagnostics); } _diagnostics.AddRange(diagnosticsThisMethod); diagnosticsThisMethod.Free(); // error while generating IL if (emittedBody == null) { break; } _moduleBeingBuiltOpt.SetMethodBody(method, emittedBody); } } finally { compilationState.CurrentImportChain = oldImportChain; } } private static bool IsFieldLikeEventAccessor(MethodSymbol method) { Symbol associatedPropertyOrEvent = method.AssociatedSymbol; return (object)associatedPropertyOrEvent != null && associatedPropertyOrEvent.Kind == SymbolKind.Event && ((EventSymbol)associatedPropertyOrEvent).HasAssociatedField; } /// /// In some circumstances (e.g. implicit implementation of an interface method by a non-virtual method in a /// base type from another assembly) it is necessary for the compiler to generate explicit implementations for /// some interface methods. They don't go in the symbol table, but if we are emitting, then we should /// generate code for them. /// private void CompileSynthesizedExplicitImplementations(SourceMemberContainerTypeSymbol sourceTypeSymbol, TypeCompilationState compilationState) { // we are not generating any observable diagnostics here so it is ok to short-circuit on global errors. if (!_globalHasErrors) { foreach (var synthesizedExplicitImpl in sourceTypeSymbol.GetSynthesizedExplicitImplementations(_cancellationToken)) { Debug.Assert(synthesizedExplicitImpl.SynthesizesLoweredBoundBody); var discardedDiagnostics = DiagnosticBag.GetInstance(); synthesizedExplicitImpl.GenerateMethodBody(compilationState, discardedDiagnostics); Debug.Assert(!discardedDiagnostics.HasAnyErrors()); discardedDiagnostics.Free(); _moduleBeingBuiltOpt.AddSynthesizedDefinition(sourceTypeSymbol, synthesizedExplicitImpl); } } } private void CompileSynthesizedSealedAccessors(SourcePropertySymbol sourceProperty, TypeCompilationState compilationState) { SynthesizedSealedPropertyAccessor synthesizedAccessor = sourceProperty.SynthesizedSealedAccessorOpt; // we are not generating any observable diagnostics here so it is ok to short-circuit on global errors. if ((object)synthesizedAccessor != null && !_globalHasErrors) { Debug.Assert(synthesizedAccessor.SynthesizesLoweredBoundBody); var discardedDiagnostics = DiagnosticBag.GetInstance(); synthesizedAccessor.GenerateMethodBody(compilationState, discardedDiagnostics); Debug.Assert(!discardedDiagnostics.HasAnyErrors()); discardedDiagnostics.Free(); _moduleBeingBuiltOpt.AddSynthesizedDefinition(sourceProperty.ContainingType, synthesizedAccessor); } } private void CompileFieldLikeEventAccessor(SourceEventSymbol eventSymbol, bool isAddMethod) { MethodSymbol accessor = isAddMethod ? eventSymbol.AddMethod : eventSymbol.RemoveMethod; var diagnosticsThisMethod = DiagnosticBag.GetInstance(); try { BoundBlock boundBody = MethodBodySynthesizer.ConstructFieldLikeEventAccessorBody(eventSymbol, isAddMethod, _compilation, diagnosticsThisMethod); var hasErrors = diagnosticsThisMethod.HasAnyErrors(); SetGlobalErrorIfTrue(hasErrors); // we cannot rely on GlobalHasErrors since that can be changed concurrently by other methods compiling // we however do not want to continue with generating method body if we have errors in this particular method - generating may crash // or if had declaration errors - we will fail anyways, but if some types are bad enough, generating may produce duplicate errors about that. if (!hasErrors && !_hasDeclarationErrors) { const int accessorOrdinal = -1; MethodBody emittedBody = GenerateMethodBody( _moduleBeingBuiltOpt, accessor, accessorOrdinal, boundBody, ImmutableArray.Empty, ImmutableArray.Empty, stateMachineTypeOpt: null, variableSlotAllocatorOpt: null, diagnostics: diagnosticsThisMethod, debugDocumentProvider: _debugDocumentProvider, importChainOpt: null, emittingPdb: false, dynamicAnalysisSpans: ImmutableArray.Empty); _moduleBeingBuiltOpt.SetMethodBody(accessor, emittedBody); // Definition is already in the symbol table, so don't call moduleBeingBuilt.AddCompilerGeneratedDefinition } } finally { _diagnostics.AddRange(diagnosticsThisMethod); diagnosticsThisMethod.Free(); } } public override object VisitMethod(MethodSymbol symbol, TypeCompilationState arg) { throw ExceptionUtilities.Unreachable; } public override object VisitProperty(PropertySymbol symbol, TypeCompilationState argument) { throw ExceptionUtilities.Unreachable; } public override object VisitEvent(EventSymbol symbol, TypeCompilationState argument) { throw ExceptionUtilities.Unreachable; } public override object VisitField(FieldSymbol symbol, TypeCompilationState argument) { throw ExceptionUtilities.Unreachable; } private void CompileMethod( MethodSymbol methodSymbol, int methodOrdinal, ref Binder.ProcessedFieldInitializers processedInitializers, SynthesizedSubmissionFields previousSubmissionFields, TypeCompilationState compilationState) { _cancellationToken.ThrowIfCancellationRequested(); SourceMethodSymbol sourceMethod = methodSymbol as SourceMethodSymbol; if (methodSymbol.IsAbstract) { if ((object)sourceMethod != null) { bool diagsWritten; sourceMethod.SetDiagnostics(ImmutableArray.Empty, out diagsWritten); if (diagsWritten && !methodSymbol.IsImplicitlyDeclared && _compilation.EventQueue != null) { _compilation.SymbolDeclaredEvent(methodSymbol); } } return; } // get cached diagnostics if not building and we have 'em if (_moduleBeingBuiltOpt == null && (object)sourceMethod != null) { var cachedDiagnostics = sourceMethod.Diagnostics; if (!cachedDiagnostics.IsDefault) { _diagnostics.AddRange(cachedDiagnostics); return; } } ImportChain oldImportChain = compilationState.CurrentImportChain; bool instrumentForDynamicAnalysis = _moduleBeingBuiltOpt?.EmitOptions.EmitDynamicAnalysisData == true; // In order to avoid generating code for methods with errors, we create a diagnostic bag just for this method. DiagnosticBag diagsForCurrentMethod = DiagnosticBag.GetInstance(); try { // if synthesized method returns its body in lowered form if (methodSymbol.SynthesizesLoweredBoundBody) { if (_moduleBeingBuiltOpt != null) { methodSymbol.GenerateMethodBody(compilationState, diagsForCurrentMethod); _diagnostics.AddRange(diagsForCurrentMethod); } return; } // no need to emit the default ctor, we are not emitting those if (methodSymbol.IsDefaultValueTypeConstructor()) { return; } bool includeInitializersInBody = false; BoundBlock body; bool originalBodyNested = false; // initializers that have been analyzed but not yet lowered. BoundStatementList analyzedInitializers = null; ImportChain importChain = null; var hasTrailingExpression = false; if (methodSymbol.IsScriptConstructor) { body = new BoundBlock(methodSymbol.GetNonNullSyntaxNode(), ImmutableArray.Empty, ImmutableArray.Empty, ImmutableArray.Empty) { WasCompilerGenerated = true }; } else if (methodSymbol.IsScriptInitializer) { // rewrite top-level statements and script variable declarations to a list of statements and assignments, respectively: var initializerStatements = InitializerRewriter.RewriteScriptInitializer(processedInitializers.BoundInitializers, (SynthesizedInteractiveInitializerMethod)methodSymbol, out hasTrailingExpression); // the lowered script initializers should not be treated as initializers anymore but as a method body: body = BoundBlock.SynthesizedNoLocals(initializerStatements.Syntax, initializerStatements.Statements); var unusedDiagnostics = DiagnosticBag.GetInstance(); DataFlowPass.Analyze(_compilation, methodSymbol, initializerStatements, unusedDiagnostics, requireOutParamsAssigned: false); DiagnosticsPass.IssueDiagnostics(_compilation, initializerStatements, unusedDiagnostics, methodSymbol); unusedDiagnostics.Free(); } else { // Do not emit initializers if we are invoking another constructor of this class. includeInitializersInBody = !processedInitializers.BoundInitializers.IsDefaultOrEmpty && !HasThisConstructorInitializer(methodSymbol); body = BindMethodBody(methodSymbol, compilationState, diagsForCurrentMethod, out importChain, out originalBodyNested); // lower initializers just once. the lowered tree will be reused when emitting all constructors // with field initializers. Once lowered, these initializers will be stashed in processedInitializers.LoweredInitializers // (see later in this method). Don't bother lowering _now_ if this particular ctor won't have the initializers // appended to its body. if (includeInitializersInBody && processedInitializers.LoweredInitializers == null) { analyzedInitializers = InitializerRewriter.RewriteConstructor(processedInitializers.BoundInitializers, methodSymbol); processedInitializers.HasErrors = processedInitializers.HasErrors || analyzedInitializers.HasAnyErrors; if (body != null && ((methodSymbol.ContainingType.IsStructType() && !methodSymbol.IsImplicitConstructor) || instrumentForDynamicAnalysis)) { // In order to get correct diagnostics, we need to analyze initializers and the body together. body = body.Update(body.Locals, body.LocalFunctions, body.Statements.Insert(0, analyzedInitializers)); includeInitializersInBody = false; analyzedInitializers = null; } else { // These analyses check for diagnostics in lambdas. // Control flow analysis and implicit return insertion are unnecessary. DataFlowPass.Analyze(_compilation, methodSymbol, analyzedInitializers, diagsForCurrentMethod, requireOutParamsAssigned: false); DiagnosticsPass.IssueDiagnostics(_compilation, analyzedInitializers, diagsForCurrentMethod, methodSymbol); } } } #if DEBUG // If the method is a synthesized static or instance constructor, then debugImports will be null and we will use the value // from the first field initializer. if ((methodSymbol.MethodKind == MethodKind.Constructor || methodSymbol.MethodKind == MethodKind.StaticConstructor) && methodSymbol.IsImplicitlyDeclared && body == null) { // There was no body to bind, so we didn't get anything from BindMethodBody. Debug.Assert(importChain == null); } // Either there were no field initializers or we grabbed debug imports from the first one. Debug.Assert(processedInitializers.BoundInitializers.IsDefaultOrEmpty || processedInitializers.FirstImportChain != null); #endif importChain = importChain ?? processedInitializers.FirstImportChain; // Associate these debug imports with all methods generated from this one. compilationState.CurrentImportChain = importChain; if (body != null) { DiagnosticsPass.IssueDiagnostics(_compilation, body, diagsForCurrentMethod, methodSymbol); } BoundBlock flowAnalyzedBody = null; if (body != null) { flowAnalyzedBody = FlowAnalysisPass.Rewrite(methodSymbol, body, diagsForCurrentMethod, hasTrailingExpression: hasTrailingExpression, originalBodyNested: originalBodyNested); } bool hasErrors = _hasDeclarationErrors || diagsForCurrentMethod.HasAnyErrors() || processedInitializers.HasErrors; // Record whether or not the bound tree for the lowered method body (including any initializers) contained any // errors (note: errors, not diagnostics). SetGlobalErrorIfTrue(hasErrors); bool diagsWritten = false; var actualDiagnostics = diagsForCurrentMethod.ToReadOnly(); if (sourceMethod != null) { actualDiagnostics = sourceMethod.SetDiagnostics(actualDiagnostics, out diagsWritten); } if (diagsWritten && !methodSymbol.IsImplicitlyDeclared && _compilation.EventQueue != null) { var lazySemanticModel = body == null ? null : new Lazy(() => { var syntax = body.Syntax; var semanticModel = (CSharpSemanticModel)_compilation.GetSemanticModel(syntax.SyntaxTree); var memberModel = semanticModel.GetMemberModel(syntax); if (memberModel != null) { memberModel.UnguardedAddBoundTreeForStandaloneSyntax(syntax, body); } return semanticModel; }); MethodSymbol symbolToProduce = methodSymbol.PartialDefinitionPart ?? methodSymbol; _compilation.EventQueue.TryEnqueue(new SymbolDeclaredCompilationEvent(_compilation, symbolToProduce, lazySemanticModel)); } // Don't lower if we're not emitting or if there were errors. // Methods that had binding errors are considered too broken to be lowered reliably. if (_moduleBeingBuiltOpt == null || hasErrors) { _diagnostics.AddRange(actualDiagnostics); return; } // ############################ // LOWERING AND EMIT // Any errors generated below here are considered Emit diagnostics // and will not be reported to callers Compilation.GetDiagnostics() ImmutableArray dynamicAnalysisSpans = ImmutableArray.Empty; bool hasBody = flowAnalyzedBody != null; VariableSlotAllocator lazyVariableSlotAllocator = null; StateMachineTypeSymbol stateMachineTypeOpt = null; var lambdaDebugInfoBuilder = ArrayBuilder.GetInstance(); var closureDebugInfoBuilder = ArrayBuilder.GetInstance(); BoundStatement loweredBodyOpt = null; try { if (hasBody) { loweredBodyOpt = LowerBodyOrInitializer( methodSymbol, methodOrdinal, flowAnalyzedBody, previousSubmissionFields, compilationState, instrumentForDynamicAnalysis, _debugDocumentProvider, ref dynamicAnalysisSpans, diagsForCurrentMethod, ref lazyVariableSlotAllocator, lambdaDebugInfoBuilder, closureDebugInfoBuilder, out stateMachineTypeOpt); Debug.Assert(loweredBodyOpt != null); } else { loweredBodyOpt = null; } hasErrors = hasErrors || (hasBody && loweredBodyOpt.HasErrors) || diagsForCurrentMethod.HasAnyErrors(); SetGlobalErrorIfTrue(hasErrors); // don't emit if the resulting method would contain initializers with errors if (!hasErrors && (hasBody || includeInitializersInBody)) { // Fields must be initialized before constructor initializer (which is the first statement of the analyzed body, if specified), // so that the initialization occurs before any method overridden by the declaring class can be invoked from the base constructor // and access the fields. ImmutableArray boundStatements; if (methodSymbol.IsScriptConstructor) { boundStatements = MethodBodySynthesizer.ConstructScriptConstructorBody(loweredBodyOpt, methodSymbol, previousSubmissionFields, _compilation); } else { boundStatements = ImmutableArray.Empty; if (analyzedInitializers != null) { // For dynamic analysis, field initializers are instrumented as part of constructors, // and so are never instrumented here. Debug.Assert(!instrumentForDynamicAnalysis); StateMachineTypeSymbol initializerStateMachineTypeOpt; processedInitializers.LoweredInitializers = (BoundStatementList)LowerBodyOrInitializer( methodSymbol, methodOrdinal, analyzedInitializers, previousSubmissionFields, compilationState, instrumentForDynamicAnalysis, _debugDocumentProvider, ref dynamicAnalysisSpans, diagsForCurrentMethod, ref lazyVariableSlotAllocator, lambdaDebugInfoBuilder, closureDebugInfoBuilder, out initializerStateMachineTypeOpt); // initializers can't produce state machines Debug.Assert((object)initializerStateMachineTypeOpt == null); Debug.Assert(processedInitializers.LoweredInitializers.Kind == BoundKind.StatementList); Debug.Assert(!hasErrors); hasErrors = processedInitializers.LoweredInitializers.HasAnyErrors || diagsForCurrentMethod.HasAnyErrors(); SetGlobalErrorIfTrue(hasErrors); if (hasErrors) { _diagnostics.AddRange(diagsForCurrentMethod); return; } } // initializers for global code have already been included in the body if (includeInitializersInBody) { boundStatements = boundStatements.Concat(processedInitializers.LoweredInitializers.Statements); } if (hasBody) { boundStatements = boundStatements.Concat(ImmutableArray.Create(loweredBodyOpt)); } } // generated struct constructors should ensure that all fields are assigned (even those that do not have initializers) var container = methodSymbol.ContainingType as SourceMemberContainerTypeSymbol; if (container != null && container.IsStructType() && methodSymbol.IsImplicitInstanceConstructor) { StateMachineTypeSymbol ctorStateMachineTypeOpt; const bool instrumentChainedConstructorsForDynamicAnalysis = false; var chain = ChainImplicitStructConstructor(methodSymbol, container); chain = LowerBodyOrInitializer( methodSymbol, methodOrdinal, chain, previousSubmissionFields, compilationState, instrumentChainedConstructorsForDynamicAnalysis, _debugDocumentProvider, ref dynamicAnalysisSpans, diagsForCurrentMethod, ref lazyVariableSlotAllocator, lambdaDebugInfoBuilder, closureDebugInfoBuilder, out ctorStateMachineTypeOpt); // constructor can't produce state machine Debug.Assert((object)ctorStateMachineTypeOpt == null); boundStatements = boundStatements.Insert(0, chain); } CSharpSyntaxNode syntax = methodSymbol.GetNonNullSyntaxNode(); var boundBody = BoundStatementList.Synthesized(syntax, boundStatements); var emittedBody = GenerateMethodBody( _moduleBeingBuiltOpt, methodSymbol, methodOrdinal, boundBody, lambdaDebugInfoBuilder.ToImmutable(), closureDebugInfoBuilder.ToImmutable(), stateMachineTypeOpt, lazyVariableSlotAllocator, diagsForCurrentMethod, _debugDocumentProvider, importChain, _emittingPdb, dynamicAnalysisSpans); _moduleBeingBuiltOpt.SetMethodBody(methodSymbol.PartialDefinitionPart ?? methodSymbol, emittedBody); } _diagnostics.AddRange(diagsForCurrentMethod); } finally { lambdaDebugInfoBuilder.Free(); closureDebugInfoBuilder.Free(); } } finally { diagsForCurrentMethod.Free(); compilationState.CurrentImportChain = oldImportChain; } } /// /// Synthesized parameterless constructors in structs chain to the "default" constructor /// private static BoundStatement ChainImplicitStructConstructor(MethodSymbol methodSymbol, SourceMemberContainerTypeSymbol containingType) { CSharpSyntaxNode syntax = methodSymbol.GetNonNullSyntaxNode(); // TODO: can we skip this if we have as many initializers as instance fields? // there could be an observable difference if initializer crashes // and constructor is invoked in-place and the partially initialized // instance escapes. (impossible in C#, I believe) // // add "this = default(T)" at the beginning of implicit struct ctor return new BoundExpressionStatement(syntax, new BoundAssignmentOperator( syntax, new BoundThisReference(syntax, containingType), new BoundDefaultOperator(syntax, containingType), RefKind.None, containingType)); } // internal for testing internal static BoundStatement LowerBodyOrInitializer( MethodSymbol method, int methodOrdinal, BoundStatement body, SynthesizedSubmissionFields previousSubmissionFields, TypeCompilationState compilationState, bool instrumentForDynamicAnalysis, DebugDocumentProvider debugDocumentProvider, ref ImmutableArray dynamicAnalysisSpans, DiagnosticBag diagnostics, ref VariableSlotAllocator lazyVariableSlotAllocator, ArrayBuilder lambdaDebugInfoBuilder, ArrayBuilder closureDebugInfoBuilder, out StateMachineTypeSymbol stateMachineTypeOpt) { Debug.Assert(compilationState.ModuleBuilderOpt != null); stateMachineTypeOpt = null; if (body.HasErrors) { return body; } try { bool sawLambdas; bool sawLocalFunctions; bool sawAwaitInExceptionHandler; var loweredBody = LocalRewriter.Rewrite( method.DeclaringCompilation, method, methodOrdinal, method.ContainingType, body, compilationState, previousSubmissionFields: previousSubmissionFields, allowOmissionOfConditionalCalls: true, instrumentForDynamicAnalysis: instrumentForDynamicAnalysis, debugDocumentProvider:debugDocumentProvider, dynamicAnalysisSpans: ref dynamicAnalysisSpans, diagnostics: diagnostics, sawLambdas: out sawLambdas, sawLocalFunctions: out sawLocalFunctions, sawAwaitInExceptionHandler: out sawAwaitInExceptionHandler); if (loweredBody.HasErrors) { return loweredBody; } if (sawAwaitInExceptionHandler) { // If we have awaits in handlers, we need to // replace handlers with synthetic ones which can be consumed by async rewriter. // The reason why this rewrite happens before the lambda rewrite // is that we may need access to exception locals and it would be fairly hard to do // if these locals are captured into closures (possibly nested ones). Debug.Assert(!method.IsIterator); loweredBody = AsyncExceptionHandlerRewriter.Rewrite( method, method.ContainingType, loweredBody, compilationState, diagnostics); } if (loweredBody.HasErrors) { return loweredBody; } if (lazyVariableSlotAllocator == null) { lazyVariableSlotAllocator = compilationState.ModuleBuilderOpt.TryCreateVariableSlotAllocator(method, method, diagnostics); } BoundStatement bodyWithoutLambdas = loweredBody; if (sawLambdas || sawLocalFunctions) { bodyWithoutLambdas = LambdaRewriter.Rewrite( loweredBody, method.ContainingType, method.ThisParameter, method, methodOrdinal, null, lambdaDebugInfoBuilder, closureDebugInfoBuilder, lazyVariableSlotAllocator, compilationState, diagnostics, assignLocals: false); } if (bodyWithoutLambdas.HasErrors) { return bodyWithoutLambdas; } IteratorStateMachine iteratorStateMachine; BoundStatement bodyWithoutIterators = IteratorRewriter.Rewrite(bodyWithoutLambdas, method, methodOrdinal, lazyVariableSlotAllocator, compilationState, diagnostics, out iteratorStateMachine); if (bodyWithoutIterators.HasErrors) { return bodyWithoutIterators; } AsyncStateMachine asyncStateMachine; BoundStatement bodyWithoutAsync = AsyncRewriter.Rewrite(bodyWithoutIterators, method, methodOrdinal, lazyVariableSlotAllocator, compilationState, diagnostics, out asyncStateMachine); Debug.Assert(iteratorStateMachine == null || asyncStateMachine == null); stateMachineTypeOpt = (StateMachineTypeSymbol)iteratorStateMachine ?? asyncStateMachine; return bodyWithoutAsync; } catch (BoundTreeVisitor.CancelledByStackGuardException ex) { ex.AddAnError(diagnostics); return new BoundBadStatement(body.Syntax, ImmutableArray.Create(body), hasErrors: true); } } private static MethodBody GenerateMethodBody( PEModuleBuilder moduleBuilder, MethodSymbol method, int methodOrdinal, BoundStatement block, ImmutableArray lambdaDebugInfo, ImmutableArray closureDebugInfo, StateMachineTypeSymbol stateMachineTypeOpt, VariableSlotAllocator variableSlotAllocatorOpt, DiagnosticBag diagnostics, DebugDocumentProvider debugDocumentProvider, ImportChain importChainOpt, bool emittingPdb, ImmutableArray dynamicAnalysisSpans) { // Note: don't call diagnostics.HasAnyErrors() in release; could be expensive if compilation has many warnings. Debug.Assert(!diagnostics.HasAnyErrors(), "Running code generator when errors exist might be dangerous; code generator not expecting errors"); var compilation = moduleBuilder.Compilation; var localSlotManager = new LocalSlotManager(variableSlotAllocatorOpt); var optimizations = compilation.Options.OptimizationLevel; ILBuilder builder = new ILBuilder(moduleBuilder, localSlotManager, optimizations); DiagnosticBag diagnosticsForThisMethod = DiagnosticBag.GetInstance(); try { Cci.AsyncMethodBodyDebugInfo asyncDebugInfo = null; var codeGen = new CodeGen.CodeGenerator(method, block, builder, moduleBuilder, diagnosticsForThisMethod, optimizations, emittingPdb); if (diagnosticsForThisMethod.HasAnyErrors()) { // we are done here. Since there were errors we should not emit anything. return null; } // We need to save additional debugging information for MoveNext of an async state machine. var stateMachineMethod = method as SynthesizedStateMachineMethod; bool isStateMachineMoveNextMethod = stateMachineMethod != null && method.Name == WellKnownMemberNames.MoveNextMethodName; if (isStateMachineMoveNextMethod && stateMachineMethod.StateMachineType.KickoffMethod.IsAsync) { int asyncCatchHandlerOffset; ImmutableArray asyncYieldPoints; ImmutableArray asyncResumePoints; codeGen.Generate(out asyncCatchHandlerOffset, out asyncYieldPoints, out asyncResumePoints); var kickoffMethod = stateMachineMethod.StateMachineType.KickoffMethod; // The exception handler IL offset is used by the debugger to treat exceptions caught by the marked catch block as "user unhandled". // This is important for async void because async void exceptions generally result in the process being terminated, // but without anything useful on the call stack. Async Task methods on the other hand return exceptions as the result of the Task. // So it is undesirable to consider these exceptions "user unhandled" since there may well be user code that is awaiting the task. // This is a heuristic since it's possible that there is no user code awaiting the task. asyncDebugInfo = new Cci.AsyncMethodBodyDebugInfo(kickoffMethod, kickoffMethod.ReturnsVoid ? asyncCatchHandlerOffset : -1, asyncYieldPoints, asyncResumePoints); } else { codeGen.Generate(); } // Translate the imports even if we are not writing PDBs. The translation has an impact on generated metadata // and we don't want to emit different metadata depending on whether or we emit with PDB stream. // TODO (https://github.com/dotnet/roslyn/issues/2846): This will need to change for member initializers in partial class. var importScopeOpt = importChainOpt?.Translate(moduleBuilder, diagnosticsForThisMethod); var localVariables = builder.LocalSlotManager.LocalsInOrder(); if (localVariables.Length > 0xFFFE) { diagnosticsForThisMethod.Add(ErrorCode.ERR_TooManyLocals, method.Locations.First()); } if (diagnosticsForThisMethod.HasAnyErrors()) { // we are done here. Since there were errors we should not emit anything. return null; } // We will only save the IL builders when running tests. if (moduleBuilder.SaveTestData) { moduleBuilder.SetMethodTestData(method, builder.GetSnapshot()); } // Only compiler-generated MoveNext methods have iterator scopes. See if this is one. var stateMachineHoistedLocalScopes = default(ImmutableArray); if (isStateMachineMoveNextMethod) { stateMachineHoistedLocalScopes = builder.GetHoistedLocalScopes(); } var stateMachineHoistedLocalSlots = default(ImmutableArray); var stateMachineAwaiterSlots = default(ImmutableArray); if (optimizations == OptimizationLevel.Debug && stateMachineTypeOpt != null) { Debug.Assert(method.IsAsync || method.IsIterator); GetStateMachineSlotDebugInfo(moduleBuilder, moduleBuilder.GetSynthesizedFields(stateMachineTypeOpt), variableSlotAllocatorOpt, diagnosticsForThisMethod, out stateMachineHoistedLocalSlots, out stateMachineAwaiterSlots); Debug.Assert(!diagnostics.HasAnyErrors()); } DynamicAnalysisMethodBodyData dynamicAnalysisDataOpt = null; if (moduleBuilder.EmitOptions.EmitDynamicAnalysisData) { Debug.Assert(debugDocumentProvider != null); dynamicAnalysisDataOpt = new DynamicAnalysisMethodBodyData(dynamicAnalysisSpans); } return new MethodBody( builder.RealizedIL, builder.MaxStack, method.PartialDefinitionPart ?? method, variableSlotAllocatorOpt?.MethodId ?? new DebugId(methodOrdinal, moduleBuilder.CurrentGenerationOrdinal), localVariables, builder.RealizedSequencePoints, debugDocumentProvider, builder.RealizedExceptionHandlers, builder.GetAllScopes(), builder.HasDynamicLocal, importScopeOpt, lambdaDebugInfo, closureDebugInfo, stateMachineTypeOpt?.Name, stateMachineHoistedLocalScopes, stateMachineHoistedLocalSlots, stateMachineAwaiterSlots, asyncDebugInfo, dynamicAnalysisDataOpt); } finally { // Basic blocks contain poolable builders for IL and sequence points. Free those back // to their pools. builder.FreeBasicBlocks(); // Remember diagnostics. diagnostics.AddRange(diagnosticsForThisMethod); diagnosticsForThisMethod.Free(); } } private static void GetStateMachineSlotDebugInfo( PEModuleBuilder moduleBuilder, IEnumerable fieldDefs, VariableSlotAllocator variableSlotAllocatorOpt, DiagnosticBag diagnostics, out ImmutableArray hoistedVariableSlots, out ImmutableArray awaiterSlots) { var hoistedVariables = ArrayBuilder.GetInstance(); var awaiters = ArrayBuilder.GetInstance(); foreach (StateMachineFieldSymbol field in fieldDefs) { int index = field.SlotIndex; if (field.SlotDebugInfo.SynthesizedKind == SynthesizedLocalKind.AwaiterField) { Debug.Assert(index >= 0); while (index >= awaiters.Count) { awaiters.Add(null); } awaiters[index] = moduleBuilder.EncTranslateLocalVariableType(field.Type, diagnostics); } else if (!field.SlotDebugInfo.Id.IsNone) { Debug.Assert(index >= 0 && field.SlotDebugInfo.SynthesizedKind.IsLongLived()); while (index >= hoistedVariables.Count) { // Empty slots may be present if variables were deleted during EnC. hoistedVariables.Add(new EncHoistedLocalInfo(true)); } hoistedVariables[index] = new EncHoistedLocalInfo(field.SlotDebugInfo, moduleBuilder.EncTranslateLocalVariableType(field.Type, diagnostics)); } } // Fill in empty slots for variables deleted during EnC that are not followed by an existing variable: if (variableSlotAllocatorOpt != null) { int previousAwaiterCount = variableSlotAllocatorOpt.PreviousAwaiterSlotCount; while (awaiters.Count < previousAwaiterCount) { awaiters.Add(null); } int previousAwaiterSlotCount = variableSlotAllocatorOpt.PreviousHoistedLocalSlotCount; while (hoistedVariables.Count < previousAwaiterSlotCount) { hoistedVariables.Add(new EncHoistedLocalInfo(true)); } } hoistedVariableSlots = hoistedVariables.ToImmutableAndFree(); awaiterSlots = awaiters.ToImmutableAndFree(); } // NOTE: can return null if the method has no body. internal static BoundBlock BindMethodBody(MethodSymbol method, TypeCompilationState compilationState, DiagnosticBag diagnostics) { ImportChain importChain; bool originalBodyNested; return BindMethodBody(method, compilationState, diagnostics, out importChain, out originalBodyNested); } // NOTE: can return null if the method has no body. private static BoundBlock BindMethodBody(MethodSymbol method, TypeCompilationState compilationState, DiagnosticBag diagnostics, out ImportChain importChain, out bool originalBodyNested) { originalBodyNested = false; importChain = null; BoundBlock body; var sourceMethod = method as SourceMethodSymbol; if ((object)sourceMethod != null) { if (sourceMethod.IsExtern) { if (sourceMethod.BodySyntax == null && (sourceMethod.SyntaxNode as ConstructorDeclarationSyntax)?.Initializer == null) { // Generate warnings only if we are not generating ERR_ExternHasBody or ERR_ExternHasConstructorInitializer errors GenerateExternalMethodWarnings(sourceMethod, diagnostics); } return null; } if (sourceMethod.IsDefaultValueTypeConstructor()) { // No body for default struct constructor. return null; } var compilation = method.DeclaringCompilation; var factory = compilation.GetBinderFactory(sourceMethod.SyntaxTree); var blockSyntax = sourceMethod.BodySyntax as BlockSyntax; if (blockSyntax != null) { var inMethodBinder = factory.GetBinder(blockSyntax); var binder = new ExecutableCodeBinder(blockSyntax, sourceMethod, inMethodBinder); body = (BoundBlock)binder.BindEmbeddedBlock(blockSyntax, diagnostics); importChain = binder.ImportChain; if (method.MethodKind == MethodKind.Destructor) { return MethodBodySynthesizer.ConstructDestructorBody(method, body); } foreach (var iterator in binder.MethodSymbolsWithYield) { foreach (var parameter in iterator.Parameters) { if (parameter.RefKind != RefKind.None) { diagnostics.Add(ErrorCode.ERR_BadIteratorArgType, parameter.Locations[0]); } else if (parameter.Type.IsUnsafe()) { diagnostics.Add(ErrorCode.ERR_UnsafeIteratorArgType, parameter.Locations[0]); } } if (iterator.IsVararg) { // error CS1636: __arglist is not allowed in the parameter list of iterators diagnostics.Add(ErrorCode.ERR_VarargsIterator, iterator.Locations[0]); } if (((iterator as SourceMethodSymbol)?.IsUnsafe == true || (iterator as LocalFunctionSymbol)?.IsUnsafe == true) && compilation.Options.AllowUnsafe) // Don't cascade { diagnostics.Add(ErrorCode.ERR_IllegalInnerUnsafe, iterator.Locations[0]); } } } else if (sourceMethod.IsExpressionBodied) { var methodSyntax = sourceMethod.SyntaxNode; var arrowExpression = methodSyntax.GetExpressionBodySyntax(); Binder binder = factory.GetBinder(arrowExpression); binder = new ExecutableCodeBinder(arrowExpression, sourceMethod, binder); importChain = binder.ImportChain; // Add locals return binder.BindExpressionBodyAsBlock(arrowExpression, diagnostics); } else { var property = sourceMethod.AssociatedSymbol as SourcePropertySymbol; if ((object)property != null && property.IsAutoProperty) { return MethodBodySynthesizer.ConstructAutoPropertyAccessorBody(sourceMethod); } return null; } } else { // synthesized methods should return their bound bodies body = null; } var constructorInitializer = BindConstructorInitializerIfAny(method, compilationState, diagnostics); ImmutableArray statements; if (constructorInitializer == null) { if (body != null) { return body; } statements = ImmutableArray.Empty; } else if (body == null) { statements = ImmutableArray.Create(constructorInitializer); } else { statements = ImmutableArray.Create(constructorInitializer, body); originalBodyNested = true; } return BoundBlock.SynthesizedNoLocals(method.GetNonNullSyntaxNode(), statements); } private static BoundStatement BindConstructorInitializerIfAny(MethodSymbol method, TypeCompilationState compilationState, DiagnosticBag diagnostics) { // delegates have constructors but not constructor initializers if (method.MethodKind == MethodKind.Constructor && !method.ContainingType.IsDelegateType() && !method.IsExtern) { var compilation = method.DeclaringCompilation; var initializerInvocation = BindConstructorInitializer(method, diagnostics, compilation); if (initializerInvocation != null) { var ctorCall = initializerInvocation as BoundCall; if (ctorCall != null && !ctorCall.HasAnyErrors && ctorCall.Method != method && ctorCall.Method.ContainingType == method.ContainingType) { // Detect and report indirect cycles in the ctor-initializer call graph. compilationState.ReportCtorInitializerCycles(method, ctorCall.Method, ctorCall.Syntax, diagnostics); } // Base WasCompilerGenerated state off of whether constructor is implicitly declared, this will ensure proper instrumentation. var constructorInitializer = new BoundExpressionStatement(initializerInvocation.Syntax, initializerInvocation) { WasCompilerGenerated = method.IsImplicitlyDeclared }; Debug.Assert(initializerInvocation.HasAnyErrors || constructorInitializer.IsConstructorInitializer(), "Please keep this bound node in sync with BoundNodeExtensions.IsConstructorInitializer."); return constructorInitializer; } } return null; } /// /// Bind the (implicit or explicit) constructor initializer of a constructor symbol. /// /// Constructor method. /// Accumulates errors (e.g. access "this" in constructor initializer). /// Used to retrieve binder. /// A bound expression for the constructor initializer call. internal static BoundExpression BindConstructorInitializer(MethodSymbol constructor, DiagnosticBag diagnostics, CSharpCompilation compilation) { // Note that the base type can be null if we're compiling System.Object in source. NamedTypeSymbol baseType = constructor.ContainingType.BaseTypeNoUseSiteDiagnostics; SourceMethodSymbol sourceConstructor = constructor as SourceMethodSymbol; ConstructorDeclarationSyntax constructorSyntax = null; ArgumentListSyntax initializerArgumentListOpt = null; if ((object)sourceConstructor != null) { constructorSyntax = (ConstructorDeclarationSyntax)sourceConstructor.SyntaxNode; if (constructorSyntax.Initializer != null) { initializerArgumentListOpt = constructorSyntax.Initializer.ArgumentList; } } // The common case is that we have no constructor initializer and the type inherits directly from object. // Also, we might be trying to generate a constructor for an entirely compiler-generated class such // as a closure class; in that case it is vexing to try to find a suitable binder for the non-existing // constructor syntax so that we can do unnecessary overload resolution on the non-existing initializer! // Simply take the early out: bind directly to the parameterless object ctor rather than attempting // overload resolution. if (initializerArgumentListOpt == null && (object)baseType != null) { if (baseType.SpecialType == SpecialType.System_Object) { return GenerateObjectConstructorInitializer(constructor, diagnostics); } else if (baseType.IsErrorType() || baseType.IsStatic) { // If the base type is bad and there is no initializer then we can just bail. // We have no expressions we need to analyze to report errors on. return null; } } // Either our base type is not object, or we have an initializer syntax, or both. We're going to // need to do overload resolution on the set of constructors of the base type, either on // the provided initializer syntax, or on an implicit ": base()" syntax. // SPEC ERROR: The specification states that if you have the situation // SPEC ERROR: class B { ... } class D1 : B {} then the default constructor // SPEC ERROR: generated for D1 must call an accessible *parameterless* constructor // SPEC ERROR: in B. However, it also states that if you have // SPEC ERROR: class B { ... } class D2 : B { D2() {} } or // SPEC ERROR: class B { ... } class D3 : B { D3() : base() {} } then // SPEC ERROR: the compiler performs *overload resolution* to determine // SPEC ERROR: which accessible constructor of B is called. Since B might have // SPEC ERROR: a ctor with all optional parameters, overload resolution might // SPEC ERROR: succeed even if there is no parameterless constructor. This // SPEC ERROR: is unintentionally inconsistent, and the native compiler does not // SPEC ERROR: implement this behavior. Rather, we should say in the spec that // SPEC ERROR: if there is no ctor in D1, then a ctor is created for you exactly // SPEC ERROR: as though you'd said "D1() : base() {}". // SPEC ERROR: This is what we now do in Roslyn. // Now, in order to do overload resolution, we're going to need a binder. There are // three possible situations: // // class D1 : B { } // class D2 : B { D2(int x) { } } // class D3 : B { D3(int x) : base(x) { } } // // In the first case the binder needs to be the binder associated with // the *body* of D1 because if the base class ctor is protected, we need // to be inside the body of a derived class in order for it to be in the // accessibility domain of the protected base class ctor. // // In the second case the binder could be the binder associated with // the body of D2; since the implicit call to base() will have no arguments // there is no need to look up "x". // // In the third case the binder must be the binder that knows about "x" // because x is in scope. Binder outerBinder; if ((object)sourceConstructor == null) { // The constructor is implicit. We need to get the binder for the body // of the enclosing class. CSharpSyntaxNode containerNode = constructor.GetNonNullSyntaxNode(); SyntaxToken bodyToken = GetImplicitConstructorBodyToken(containerNode); outerBinder = compilation.GetBinderFactory(containerNode.SyntaxTree).GetBinder(containerNode, bodyToken.Position); } else if (initializerArgumentListOpt == null) { // We have a ctor in source but no explicit constructor initializer. We can't just use the binder for the // type containing the ctor because the ctor might be marked unsafe. Use the binder for the parameter list // as an approximation - the extra symbols won't matter because there are no identifiers to bind. outerBinder = compilation.GetBinderFactory(sourceConstructor.SyntaxTree).GetBinder(constructorSyntax.ParameterList); } else { outerBinder = compilation.GetBinderFactory(sourceConstructor.SyntaxTree).GetBinder(initializerArgumentListOpt); } // wrap in ConstructorInitializerBinder for appropriate errors // Handle scoping for possible pattern variables declared in the initializer Binder initializerBinder = outerBinder.WithAdditionalFlagsAndContainingMemberOrLambda(BinderFlags.ConstructorInitializer, constructor); if (initializerArgumentListOpt != null) { initializerBinder = new ExecutableCodeBinder(initializerArgumentListOpt, constructor, initializerBinder); } return initializerBinder.BindConstructorInitializer(initializerArgumentListOpt, constructor, diagnostics); } private static SyntaxToken GetImplicitConstructorBodyToken(CSharpSyntaxNode containerNode) { var kind = containerNode.Kind(); switch (kind) { case SyntaxKind.ClassDeclaration: return ((ClassDeclarationSyntax)containerNode).OpenBraceToken; case SyntaxKind.StructDeclaration: return ((StructDeclarationSyntax)containerNode).OpenBraceToken; case SyntaxKind.EnumDeclaration: // We're not going to find any non-default ctors, but we'll look anyway. return ((EnumDeclarationSyntax)containerNode).OpenBraceToken; default: throw ExceptionUtilities.UnexpectedValue(kind); } } internal static BoundCall GenerateObjectConstructorInitializer(MethodSymbol constructor, DiagnosticBag diagnostics) { NamedTypeSymbol objectType = constructor.ContainingType.BaseTypeNoUseSiteDiagnostics; Debug.Assert(objectType.SpecialType == SpecialType.System_Object); MethodSymbol objectConstructor = null; LookupResultKind resultKind = LookupResultKind.Viable; foreach (MethodSymbol objectCtor in objectType.InstanceConstructors) { if (objectCtor.ParameterCount == 0) { objectConstructor = objectCtor; break; } } // UNDONE: If this happens then something is deeply wrong. Should we give a better error? if ((object)objectConstructor == null) { diagnostics.Add(ErrorCode.ERR_BadCtorArgCount, constructor.Locations[0], objectType, /*desired param count*/ 0); return null; } // UNDONE: If this happens then something is deeply wrong. Should we give a better error? bool hasErrors = false; HashSet useSiteDiagnostics = null; if (!AccessCheck.IsSymbolAccessible(objectConstructor, constructor.ContainingType, ref useSiteDiagnostics)) { diagnostics.Add(ErrorCode.ERR_BadAccess, constructor.Locations[0], objectConstructor); resultKind = LookupResultKind.Inaccessible; hasErrors = true; } if (!useSiteDiagnostics.IsNullOrEmpty()) { diagnostics.Add(constructor.Locations.IsEmpty ? NoLocation.Singleton : constructor.Locations[0], useSiteDiagnostics); } CSharpSyntaxNode syntax = constructor.GetNonNullSyntaxNode(); BoundExpression receiver = new BoundThisReference(syntax, constructor.ContainingType) { WasCompilerGenerated = true }; return new BoundCall( syntax: syntax, receiverOpt: receiver, method: objectConstructor, arguments: ImmutableArray.Empty, argumentNamesOpt: ImmutableArray.Empty, argumentRefKindsOpt: ImmutableArray.Empty, isDelegateCall: false, expanded: false, invokedAsExtensionMethod: false, argsToParamsOpt: ImmutableArray.Empty, resultKind: resultKind, type: objectType, hasErrors: hasErrors) { WasCompilerGenerated = true }; } private static void GenerateExternalMethodWarnings(SourceMethodSymbol methodSymbol, DiagnosticBag diagnostics) { if (methodSymbol.GetAttributes().IsEmpty && !methodSymbol.ContainingType.IsComImport) { // external method with no attributes var errorCode = (methodSymbol.MethodKind == MethodKind.Constructor || methodSymbol.MethodKind == MethodKind.StaticConstructor) ? ErrorCode.WRN_ExternCtorNoImplementation : ErrorCode.WRN_ExternMethodNoImplementation; diagnostics.Add(errorCode, methodSymbol.Locations[0], methodSymbol); } } /// /// Returns true if the method is a constructor and has a this() constructor initializer. /// private static bool HasThisConstructorInitializer(MethodSymbol method) { if ((object)method != null && method.MethodKind == MethodKind.Constructor) { SourceMethodSymbol sourceMethod = method as SourceMethodSymbol; if ((object)sourceMethod != null) { ConstructorDeclarationSyntax constructorSyntax = sourceMethod.SyntaxNode as ConstructorDeclarationSyntax; if (constructorSyntax != null) { ConstructorInitializerSyntax initializerSyntax = constructorSyntax.Initializer; if (initializerSyntax != null) { return initializerSyntax.Kind() == SyntaxKind.ThisConstructorInitializer; } } } } return false; } private static Cci.DebugSourceDocument CreateDebugDocumentForFile(string normalizedPath) { return new Cci.DebugSourceDocument(normalizedPath, Cci.DebugSourceDocument.CorSymLanguageTypeCSharp); } private static bool PassesFilter(Predicate filterOpt, Symbol symbol) { return (filterOpt == null) || filterOpt(symbol); } } }