1 //----------------------------------------------------------------------------- 2 // Copyright (c) Microsoft Corporation. All rights reserved. 3 //----------------------------------------------------------------------------- 4 5 namespace System.Activities.Debugger 6 { 7 using System; 8 using System.Collections.Generic; 9 using System.Diagnostics; 10 using System.Reflection; 11 using System.Runtime; 12 using System.Diagnostics.CodeAnalysis; 13 using System.Security; 14 using System.Text; 15 using System.IO; 16 using System.Globalization; 17 18 // Describes a "state" in the interpretter. A state is any source location that 19 // a breakpoint could be set on or that could be stepped to. 20 [DebuggerNonUserCode] 21 [Fx.Tag.XamlVisible(false)] 22 public class State 23 { 24 [Fx.Tag.SecurityNote(Critical = "This value is used in IL generation performed under an assert. It gets validated before setting in partial trust.")] 25 [SecurityCritical] 26 SourceLocation location; 27 [Fx.Tag.SecurityNote(Critical = "This value is used in IL generation performed under an assert. It gets validated before setting in partial trust.")] 28 [SecurityCritical] 29 string name; 30 IEnumerable<LocalsItemDescription> earlyLocals; 31 int numberOfEarlyLocals; 32 33 // Calling Type.GetMethod() is slow (10,000 calls can take ~1 minute). 34 // So we stash extra fields to be able to make the call lazily (as we Enter the state). 35 // this.type.GetMethod 36 Type type; 37 [Fx.Tag.SecurityNote(Critical = "This value is used in IL generation performed under an assert. It gets validated before setting in partial trust.")] 38 [SecurityCritical] 39 string methodName; 40 41 [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.")] 42 [SecurityCritical] 43 bool debuggingEnabled = true; 44 45 [Fx.Tag.SecurityNote(Critical = "Sets SecurityCritical name member.", 46 Safe = "We validate the SourceLocation and name before storing it in the member when running in Partial Trust.")] 47 [SecuritySafeCritical] State(SourceLocation location, string name, IEnumerable<LocalsItemDescription> earlyLocals, int numberOfEarlyLocals)48 internal State(SourceLocation location, string name, IEnumerable<LocalsItemDescription> earlyLocals, int numberOfEarlyLocals) 49 { 50 // If we are running in Partial Trust, validate the name string. We only do this in partial trust for backward compatability. 51 // We are doing the validation because we want to prevent anything passed to us by non-critical code from affecting the generation 52 // of the code to the dynamic assembly we are creating. 53 if (!PartialTrustHelpers.AppDomainFullyTrusted) 54 { 55 this.name = ValidateIdentifierString(name); 56 this.location = ValidateSourceLocation(location); 57 } 58 else 59 { 60 this.location = location; 61 this.name = name; 62 } 63 64 this.earlyLocals = earlyLocals; 65 Fx.Assert(earlyLocals != null || numberOfEarlyLocals == 0, 66 "If earlyLocals is null then numberOfEarlyLocals should be 0"); 67 // Ignore the passed numberOfEarlyLocals if earlyLocal is null. 68 this.numberOfEarlyLocals = (earlyLocals == null) ? 0 : numberOfEarlyLocals; 69 } 70 71 // Location in source file associated with this state. 72 internal SourceLocation Location 73 { 74 [Fx.Tag.SecurityNote(Critical = "Accesses the SecurityCritical location member. We validated the location when this object was constructed.", 75 Safe = "SourceLocation is immutable and we validated it in the constructor.")] 76 [SecuritySafeCritical] 77 get { return this.location; } 78 } 79 80 81 // Friendly name of the state. May be null if state is not named. 82 // States need unique names. 83 internal string Name 84 { 85 [Fx.Tag.SecurityNote(Critical = "Sets SecurityCritical name member.", 86 Safe = "We are only reading it, not setting it.")] 87 [SecuritySafeCritical] 88 get { return this.name; } 89 } 90 91 92 // Type definitions for early bound locals. This list is ordered. 93 // Names should be unique. 94 internal IEnumerable<LocalsItemDescription> EarlyLocals 95 { 96 get { return this.earlyLocals; } 97 } 98 99 internal int NumberOfEarlyLocals 100 { 101 get { return this.numberOfEarlyLocals; } 102 } 103 104 internal bool DebuggingEnabled 105 { 106 [Fx.Tag.SecurityNote(Critical = "Accesses SecurityCritical debuggingEnabled member.", 107 Safe = "We don't change anyting. We only return the value.")] 108 [SecuritySafeCritical] 109 get 110 { 111 return this.debuggingEnabled; 112 } 113 114 [Fx.Tag.SecurityNote(Critical = "Sets SecurityCritical debuggingEnabled member.")] 115 [SecuritySafeCritical] 116 set 117 { 118 this.debuggingEnabled = value; 119 } 120 } 121 122 [Fx.Tag.SecurityNote(Critical = "Sets SecurityCritical methodName member.")] 123 [SecurityCritical] CacheMethodInfo(Type type, string methodName)124 internal void CacheMethodInfo(Type type, string methodName) 125 { 126 this.type = type; 127 this.methodName = methodName; 128 } 129 130 // Helper to lazily get the MethodInfo. This is expensive, so caller should cache it. 131 [Fx.Tag.SecurityNote(Critical = "Generates and returns a MethodInfo that is used to generate the dynamic module and accesses Critical member methodName.")] 132 [SecurityCritical] GetMethodInfo(bool withPriming)133 internal MethodInfo GetMethodInfo(bool withPriming) 134 { 135 MethodInfo methodInfo = this.type.GetMethod(withPriming ? StateManager.MethodWithPrimingPrefix + this.methodName : this.methodName); 136 return methodInfo; 137 } 138 139 // internal because it is used from StateManager, too for the assembly name, type name, and type name prefix. ValidateIdentifierString(string input)140 internal static string ValidateIdentifierString(string input) 141 { 142 string result = input.Normalize(NormalizationForm.FormC); 143 144 if (result.Length > 255) 145 { 146 result = result.Substring(0, 255); 147 } 148 149 // Make the identifier conform to Unicode programming language identifer specification. 150 char[] chars = result.ToCharArray(); 151 for (int i = 0; i < chars.Length; i++) 152 { 153 UnicodeCategory category = char.GetUnicodeCategory(chars[i]); 154 // Check for identifier_start 155 if ((category == UnicodeCategory.UppercaseLetter) || 156 (category == UnicodeCategory.LowercaseLetter) || 157 (category == UnicodeCategory.TitlecaseLetter) || 158 (category == UnicodeCategory.ModifierLetter) || 159 (category == UnicodeCategory.OtherLetter) || 160 (category == UnicodeCategory.LetterNumber)) 161 { 162 continue; 163 } 164 // If it's not the first character, also check for identifier_extend 165 if ((i != 0) && 166 ((category == UnicodeCategory.NonSpacingMark) || 167 (category == UnicodeCategory.SpacingCombiningMark) || 168 (category == UnicodeCategory.DecimalDigitNumber) || 169 (category == UnicodeCategory.ConnectorPunctuation) || 170 (category == UnicodeCategory.Format))) 171 { 172 continue; 173 } 174 175 // Not valid for identifiers - change it to an underscore. 176 chars[i] = '_'; 177 } 178 179 result = new string(chars); 180 181 return result; 182 } 183 184 [Fx.Tag.SecurityNote(Critical = "Calls SecurityCritical method StateManager.DisableCodeGeneration.")] 185 [SecurityCritical] ValidateSourceLocation(SourceLocation input)186 SourceLocation ValidateSourceLocation(SourceLocation input) 187 { 188 bool returnNewLocation = false; 189 string newFileName = input.FileName; 190 191 if (string.IsNullOrWhiteSpace(newFileName)) 192 { 193 this.DebuggingEnabled = false; 194 Trace.WriteLine(SR.DebugInstrumentationFailed(SR.InvalidFileName(this.name))); 195 return input; 196 } 197 198 // There was some validation of the column and line number already done in the SourceLocation constructor. 199 // We are going to limit line and column numbers to Int16.MaxValue 200 if ((input.StartLine > Int16.MaxValue) || (input.EndLine > Int16.MaxValue)) 201 { 202 this.DebuggingEnabled = false; 203 Trace.WriteLine(SR.DebugInstrumentationFailed(SR.LineNumberTooLarge(this.name))); 204 return input; 205 } 206 207 if ((input.StartColumn > Int16.MaxValue) || (input.EndColumn > Int16.MaxValue)) 208 { 209 this.DebuggingEnabled = false; 210 Trace.WriteLine(SR.DebugInstrumentationFailed(SR.ColumnNumberTooLarge(this.name))); 211 return input; 212 } 213 214 // Truncate at 255 characters. 215 if (newFileName.Length > 255) 216 { 217 newFileName = newFileName.Substring(0, 255); 218 returnNewLocation = true; 219 } 220 221 if (ReplaceInvalidCharactersWithUnderscore(ref newFileName, Path.GetInvalidPathChars())) 222 { 223 returnNewLocation = true; 224 } 225 226 string fileNameOnly = Path.GetFileName(newFileName); 227 if (ReplaceInvalidCharactersWithUnderscore(ref fileNameOnly, Path.GetInvalidFileNameChars())) 228 { 229 // The filename portion has been munged. We need to make a new full name. 230 string path = Path.GetDirectoryName(newFileName); 231 newFileName = path + "\\" + fileNameOnly; 232 returnNewLocation = true; 233 } 234 235 if (returnNewLocation) 236 { 237 return new SourceLocation(newFileName, input.StartLine, input.StartColumn, input.EndLine, input.EndColumn); 238 } 239 240 return input; 241 } 242 ReplaceInvalidCharactersWithUnderscore(ref string input, char[] invalidChars)243 static bool ReplaceInvalidCharactersWithUnderscore(ref string input, char[] invalidChars) 244 { 245 bool modified = false; 246 int invalidIndex = 0; 247 while ((invalidIndex = input.IndexOfAny(invalidChars)) != -1) 248 { 249 char[] charArray = input.ToCharArray(); 250 charArray[invalidIndex] = '_'; 251 input = new string(charArray); 252 modified = true; 253 254 } 255 256 return modified; 257 } 258 } 259 } 260