File: System\Activities\Debugger\State.cs
Project: ndp\cdf\src\NetFx40\System.Activities\System.Activities.csproj (System.Activities)
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//-----------------------------------------------------------------------------
 
namespace System.Activities.Debugger
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Reflection;
    using System.Runtime;
    using System.Diagnostics.CodeAnalysis;
    using System.Security;
    using System.Text;
    using System.IO;
    using System.Globalization;
 
    // Describes a "state" in the interpretter. A state is any source location that
    // a breakpoint could be set on or that could be stepped to.
    [DebuggerNonUserCode]
    [Fx.Tag.XamlVisible(false)]
    public class State
    {
        [Fx.Tag.SecurityNote(Critical = "This value is used in IL generation performed under an assert. It gets validated before setting in partial trust.")]
        [SecurityCritical]
        SourceLocation location;
        [Fx.Tag.SecurityNote(Critical = "This value is used in IL generation performed under an assert. It gets validated before setting in partial trust.")]
        [SecurityCritical]
        string name;
        IEnumerable<LocalsItemDescription> earlyLocals;
        int numberOfEarlyLocals;
 
        // Calling Type.GetMethod() is slow (10,000 calls can take ~1 minute).
        // So we stash extra fields to be able to make the call lazily (as we Enter the state).
        // this.type.GetMethod
        Type type;
        [Fx.Tag.SecurityNote(Critical = "This value is used in IL generation performed under an assert. It gets validated before setting in partial trust.")]
        [SecurityCritical]
        string methodName;
 
        [Fx.Tag.SecurityNote(Critical = "This value is used in IL generation performed under an assert. Used to determine if we should invoke the generated code for this state.")]
        [SecurityCritical]
        bool debuggingEnabled = true;
 
        [Fx.Tag.SecurityNote(Critical = "Sets SecurityCritical name member.",
            Safe = "We validate the SourceLocation and name before storing it in the member when running in Partial Trust.")]
        [SecuritySafeCritical]
        internal State(SourceLocation location, string name, IEnumerable<LocalsItemDescription> earlyLocals, int numberOfEarlyLocals)
        {
            // If we are running in Partial Trust, validate the name string. We only do this in partial trust for backward compatability.
            // We are doing the validation because we want to prevent anything passed to us by non-critical code from affecting the generation
            // of the code to the dynamic assembly we are creating.
            if (!PartialTrustHelpers.AppDomainFullyTrusted)
            {
                this.name = ValidateIdentifierString(name);
                this.location = ValidateSourceLocation(location);
            }
            else
            {
                this.location = location;
                this.name = name;
            }
 
            this.earlyLocals = earlyLocals;
            Fx.Assert(earlyLocals != null || numberOfEarlyLocals == 0,
                "If earlyLocals is null then numberOfEarlyLocals should be 0");
            // Ignore the passed numberOfEarlyLocals if earlyLocal is null.
            this.numberOfEarlyLocals = (earlyLocals == null) ? 0 : numberOfEarlyLocals;
        }
 
        // Location in source file associated with this state.
        internal SourceLocation Location
        {
            [Fx.Tag.SecurityNote(Critical = "Accesses the SecurityCritical location member. We validated the location when this object was constructed.",
                Safe = "SourceLocation is immutable and we validated it in the constructor.")]
            [SecuritySafeCritical]
            get { return this.location; }
        }
 
 
        // Friendly name of the state. May be null if state is not named.
        // States need unique names.
        internal string Name
        {
            [Fx.Tag.SecurityNote(Critical = "Sets SecurityCritical name member.",
                Safe = "We are only reading it, not setting it.")]
            [SecuritySafeCritical]
            get { return this.name; }
        }
 
 
        // Type definitions for early bound locals. This list is ordered.
        // Names should be unique.
        internal IEnumerable<LocalsItemDescription> EarlyLocals
        {
            get { return this.earlyLocals; }
        }
 
        internal int NumberOfEarlyLocals
        {
            get { return this.numberOfEarlyLocals; }
        }
 
        internal bool DebuggingEnabled
        {
            [Fx.Tag.SecurityNote(Critical = "Accesses SecurityCritical debuggingEnabled member.",
                Safe = "We don't change anyting. We only return the value.")]
            [SecuritySafeCritical]
            get
            {
                return this.debuggingEnabled;
            }
 
            [Fx.Tag.SecurityNote(Critical = "Sets SecurityCritical debuggingEnabled member.")]
            [SecuritySafeCritical]
            set
            {
                this.debuggingEnabled = value;
            }
        }
 
        [Fx.Tag.SecurityNote(Critical = "Sets SecurityCritical methodName member.")]
        [SecurityCritical]
        internal void CacheMethodInfo(Type type, string methodName)
        {
            this.type = type;
            this.methodName = methodName;
        }
 
        // Helper to lazily get the MethodInfo. This is expensive, so caller should cache it.
        [Fx.Tag.SecurityNote(Critical = "Generates and returns a MethodInfo that is used to generate the dynamic module and accesses Critical member methodName.")]
        [SecurityCritical]
        internal MethodInfo GetMethodInfo(bool withPriming)
        {
            MethodInfo methodInfo = this.type.GetMethod(withPriming ? StateManager.MethodWithPrimingPrefix + this.methodName : this.methodName);
            return methodInfo;
        }
 
        // internal because it is used from StateManager, too for the assembly name, type name, and type name prefix.
        internal static string ValidateIdentifierString(string input)
        {
            string result = input.Normalize(NormalizationForm.FormC);
 
            if (result.Length > 255)
            {
                result = result.Substring(0, 255);
            }
 
            // Make the identifier conform to Unicode programming language identifer specification.
            char[] chars = result.ToCharArray();
            for (int i = 0; i < chars.Length; i++)
            {
                UnicodeCategory category = char.GetUnicodeCategory(chars[i]);
                // Check for identifier_start
                if ((category == UnicodeCategory.UppercaseLetter) ||
                    (category == UnicodeCategory.LowercaseLetter) ||
                    (category == UnicodeCategory.TitlecaseLetter) ||
                    (category == UnicodeCategory.ModifierLetter) ||
                    (category == UnicodeCategory.OtherLetter) ||
                    (category == UnicodeCategory.LetterNumber))
                {
                    continue;
                }
                // If it's not the first character, also check for identifier_extend
                if ((i != 0) &&
                    ((category == UnicodeCategory.NonSpacingMark) ||
                     (category == UnicodeCategory.SpacingCombiningMark) || 
                     (category == UnicodeCategory.DecimalDigitNumber) ||
                     (category == UnicodeCategory.ConnectorPunctuation) ||
                     (category == UnicodeCategory.Format)))
                {
                    continue;
                }
 
                // Not valid for identifiers - change it to an underscore.
                chars[i] = '_';
            }
 
            result = new string(chars);
 
            return result;
        }
 
        [Fx.Tag.SecurityNote(Critical = "Calls SecurityCritical method StateManager.DisableCodeGeneration.")]
        [SecurityCritical]
        SourceLocation ValidateSourceLocation(SourceLocation input)
        {
            bool returnNewLocation = false;
            string newFileName = input.FileName;
 
            if (string.IsNullOrWhiteSpace(newFileName))
            {
                this.DebuggingEnabled = false;
                Trace.WriteLine(SR.DebugInstrumentationFailed(SR.InvalidFileName(this.name)));
                return input;
            }
 
            // There was some validation of the column and line number already done in the SourceLocation constructor.
            // We are going to limit line and column numbers to Int16.MaxValue
            if ((input.StartLine > Int16.MaxValue) || (input.EndLine > Int16.MaxValue))
            {
                this.DebuggingEnabled = false;
                Trace.WriteLine(SR.DebugInstrumentationFailed(SR.LineNumberTooLarge(this.name)));
                return input;
            }
 
            if ((input.StartColumn > Int16.MaxValue) || (input.EndColumn > Int16.MaxValue))
            {
                this.DebuggingEnabled = false;
                Trace.WriteLine(SR.DebugInstrumentationFailed(SR.ColumnNumberTooLarge(this.name)));
                return input;
            }
 
            // Truncate at 255 characters.
            if (newFileName.Length > 255)
            {
                newFileName = newFileName.Substring(0, 255);
                returnNewLocation = true;
            }
 
            if (ReplaceInvalidCharactersWithUnderscore(ref newFileName, Path.GetInvalidPathChars()))
            {
                returnNewLocation = true;
            }
 
            string fileNameOnly = Path.GetFileName(newFileName);
            if (ReplaceInvalidCharactersWithUnderscore(ref fileNameOnly, Path.GetInvalidFileNameChars()))
            {
                // The filename portion has been munged. We need to make a new full name.
                string path = Path.GetDirectoryName(newFileName);
                newFileName = path + "\\" + fileNameOnly;
                returnNewLocation = true;
            }
 
            if (returnNewLocation)
            {
                return new SourceLocation(newFileName, input.StartLine, input.StartColumn, input.EndLine, input.EndColumn);
            }
 
            return input;
        }
 
        static bool ReplaceInvalidCharactersWithUnderscore(ref string input, char[] invalidChars)
        {
            bool modified = false;
            int invalidIndex = 0;
            while ((invalidIndex = input.IndexOfAny(invalidChars)) != -1)
            {
                char[] charArray = input.ToCharArray();
                charArray[invalidIndex] = '_';
                input = new string(charArray);
                modified = true;
 
            }
 
            return modified;
        }
    }
}