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