File: SourceLocationProvider.cs

package info (click to toggle)
mono 6.8.0.105%2Bdfsg-3.3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 1,284,512 kB
  • sloc: cs: 11,172,132; xml: 2,850,069; ansic: 671,653; cpp: 122,091; perl: 59,366; javascript: 30,841; asm: 22,168; makefile: 20,093; sh: 15,020; python: 4,827; pascal: 925; sql: 859; sed: 16; php: 1
file content (415 lines) | stat: -rw-r--r-- 22,177 bytes parent folder | download | duplicates (7)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//----------------------------------------------------------------

namespace System.Activities.Debugger
{
    using System;
    using System.Activities.Hosting;
    using System.Activities.XamlIntegration;
    using System.Diagnostics;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Runtime;
    using System.Reflection;
    using System.Security;
    using System.Security.Permissions;
    using System.Xaml;
    using System.Xml;
    using System.IO;
    using System.Activities.Validation;
    using System.Collections.ObjectModel;
    using System.Runtime.Serialization;
    using System.Activities.Debugger.Symbol;
    using System.Globalization;

    // Provide SourceLocation information for activities in given root activity.
    // This is integration point with Workflow project system (TBD).
    // The current plan is to get SourceLocation from (in this order):
    //  1. pdb (when available)
    //  2a. parse xaml files available in the same project (or metadata store) or
    //  2b. ask user to point to the correct xaml source.
    //  3.  Publish (serialize to tmp file) and deserialize it to collect SourceLocation (for loose xaml).
    // Current code cover only step 3.

    [DebuggerNonUserCode]
    public static class SourceLocationProvider
    {
        [Fx.Tag.Throws(typeof(Exception), "Calls Serialize/Deserialize to temporary file")]
        [SuppressMessage(FxCop.Category.Design, FxCop.Rule.DoNotCatchGeneralExceptionTypes,
            Justification = "We catch all exceptions to avoid leaking security sensitive information.")]
        [SuppressMessage(FxCop.Category.Security, "CA2103:ReviewImperativeSecurity",
            Justification = "This is security reviewed.")]
        [SuppressMessage(FxCop.Category.Security, FxCop.Rule.SecureAsserts,
            Justification = "The Assert is only enforce while reading the file and the contents is not leaked.")]
        [SuppressMessage("Reliability", "Reliability108:IsFatalRule",
            Justification = "We catch all exceptions to avoid leaking security sensitive information.")]
        [Fx.Tag.SecurityNote(Critical = "Asserting FileIOPermission(Read) for the specified file name that is contained the attached property on the XAML.",
            Safe = "We are not exposing the contents of the file.")]
        [SecuritySafeCritical]
        static internal Dictionary<object, SourceLocation> GetSourceLocations(Activity rootActivity, out string sourcePath, out bool isTemporaryFile, out byte[] checksum)
        {
            isTemporaryFile = false;
            checksum = null;
            string symbolString = DebugSymbol.GetSymbol(rootActivity) as String;
            if (string.IsNullOrEmpty(symbolString) && rootActivity.Children != null && rootActivity.Children.Count > 0)
            { // In case of actual root is wrapped either in x:Class activity or CorrelationScope
                Activity body = rootActivity.Children[0];
                string bodySymbolString = DebugSymbol.GetSymbol(body) as String;
                if (!string.IsNullOrEmpty(bodySymbolString))
                {
                    rootActivity = body;
                    symbolString = bodySymbolString;
                }
            }

            if (!string.IsNullOrEmpty(symbolString))
            {
                try
                {
                    WorkflowSymbol wfSymbol = WorkflowSymbol.Decode(symbolString);
                    if (wfSymbol != null)
                    {
                        sourcePath = wfSymbol.FileName;
                        checksum = wfSymbol.GetChecksum();
                        // rootActivity is the activity with the attached symbol string.
                        // rootActivity.RootActivity is the workflow root activity.
                        // if they are not the same, then it must be compiled XAML, because loose XAML (i.e. XAMLX) always have the symbol attached at the root.
                        if (rootActivity.RootActivity != rootActivity)
                        {
                            Fx.Assert(rootActivity.Parent != null, "Compiled XAML implementation always have a parent.");
                            rootActivity = rootActivity.Parent;
                        }
                        return GetSourceLocations(rootActivity, wfSymbol, translateInternalActivityToOrigin: false);
                    }
                }
                catch (SerializationException)
                {
                    // Ignore invalid symbol.
                }
            }

            sourcePath = XamlDebuggerXmlReader.GetFileName(rootActivity) as string;
            Dictionary<object, SourceLocation> mapping;
            Assembly localAssembly;
            bool permissionRevertNeeded = false;

            // This may not be the local assembly since it may not be the real root for x:Class 
            localAssembly = rootActivity.GetType().Assembly;

            if (rootActivity.Parent != null)
            {
                localAssembly = rootActivity.Parent.GetType().Assembly;
            }

            if (rootActivity.Children != null && rootActivity.Children.Count > 0)
            { // In case of actual root is wrapped either in x:Class activity or CorrelationScope
                Activity body = rootActivity.Children[0];
                string bodySourcePath = XamlDebuggerXmlReader.GetFileName(body) as string;
                if (!string.IsNullOrEmpty(bodySourcePath))
                {
                    rootActivity = body;
                    sourcePath = bodySourcePath;
                }
            }

            try
            {
                Fx.Assert(!string.IsNullOrEmpty(sourcePath), "If sourcePath is null, it should have been short-circuited before reaching here.");

                SourceLocation tempSourceLocation;
                Activity tempRootActivity;

                checksum = SymbolHelper.CalculateChecksum(sourcePath);

                if (TryGetSourceLocation(rootActivity, sourcePath, checksum, out tempSourceLocation)) // already has source location.
                {
                    tempRootActivity = rootActivity;
                }
                else
                {
                    byte[] buffer;
                    // Need to store the file in memory temporary so don't have to re-read the file twice
                    // for XamlDebugXmlReader's BracketLocator.
                    // If there is a debugger attached, Assert FileIOPermission for Read access to the specific file.
                    if (System.Diagnostics.Debugger.IsAttached)
                    {
                        permissionRevertNeeded = true;
                        FileIOPermission permission = new FileIOPermission(FileIOPermissionAccess.Read, sourcePath);
                        permission.Assert();
                    }

                    try
                    {
                        FileInfo fi = new FileInfo(sourcePath);
                        buffer = new byte[fi.Length];

                        using (FileStream fs = new FileStream(sourcePath, FileMode.Open, FileAccess.Read))
                        {
                            fs.Read(buffer, 0, buffer.Length);
                        }
                    }
                    finally
                    {
                        // If we Asserted FileIOPermission, revert it.
                        if (permissionRevertNeeded)
                        {
                            CodeAccessPermission.RevertAssert();
                            permissionRevertNeeded = false;
                        }
                    }

                    object deserializedObject = Deserialize(buffer, localAssembly);
                    IDebuggableWorkflowTree debuggableWorkflowTree = deserializedObject as IDebuggableWorkflowTree;
                    if (debuggableWorkflowTree != null)
                    { // Declarative Service and x:Class case
                        tempRootActivity = debuggableWorkflowTree.GetWorkflowRoot();
                    }
                    else
                    { // Loose XAML case.
                        tempRootActivity = deserializedObject as Activity;
                    }

                    Fx.Assert(tempRootActivity != null, "Unexpected workflow xaml file");
                }

                mapping = new Dictionary<object, SourceLocation>();
                if (tempRootActivity != null)
                {
                    CollectMapping(rootActivity, tempRootActivity, mapping, sourcePath, checksum);
                }
            }
            catch (Exception)
            {
                // Only eat the exception if we were running in partial trust.
                if (!PartialTrustHelpers.AppDomainFullyTrusted)
                {
                    // Eat the exception and return an empty dictionary.
                    return new Dictionary<object, SourceLocation>();
                }
                else
                {
                    throw;
                }
            }

            return mapping;
        }

        public static Dictionary<object, SourceLocation> GetSourceLocations(Activity rootActivity, WorkflowSymbol symbol)
        {
            return GetSourceLocations(rootActivity, symbol, translateInternalActivityToOrigin: true);
        }

        // For most of the time, we need source location for object that appear on XAML.
        // During debugging, however, we must not transform the internal activity to their origin to make sure it stop when the internal activity is about the execute
        // Therefore, in debugger scenario, translateInternalActivityToOrigin will be set to false.
        internal static Dictionary<object, SourceLocation> GetSourceLocations(Activity rootActivity, WorkflowSymbol symbol, bool translateInternalActivityToOrigin)
        {
            Activity workflowRoot = rootActivity.RootActivity ?? rootActivity;
            if (!workflowRoot.IsMetadataFullyCached)
            {
                IList<ValidationError> validationErrors = null;
                ActivityUtilities.CacheRootMetadata(workflowRoot, new ActivityLocationReferenceEnvironment(), ProcessActivityTreeOptions.ValidationOptions, null, ref validationErrors);
            }

            Dictionary<object, SourceLocation> newMapping = new Dictionary<object, SourceLocation>();

            // Make sure the qid we are using to TryGetElementFromRoot
            // are shifted appropriately such that the first digit that QID is
            // the same as the last digit of the rootActivity.QualifiedId.

            int[] rootIdArray = rootActivity.QualifiedId.AsIDArray();
            int idOffset = rootIdArray[rootIdArray.Length - 1] - 1;

            foreach (ActivitySymbol actSym in symbol.Symbols)
            {
                QualifiedId qid = new QualifiedId(actSym.QualifiedId);
                if (idOffset != 0)
                {
                    int[] idArray = qid.AsIDArray();
                    idArray[0] += idOffset;
                    qid = new QualifiedId(idArray);
                }
                Activity activity;
                if (QualifiedId.TryGetElementFromRoot(rootActivity, qid, out activity))
                {
                    object origin = activity;
                    if (translateInternalActivityToOrigin && activity.Origin != null)
                    {
                        origin = activity.Origin;
                    }

                    newMapping.Add(origin,
                        new SourceLocation(symbol.FileName, symbol.GetChecksum(), actSym.StartLine, actSym.StartColumn, actSym.EndLine, actSym.EndColumn));
                }
            }
            return newMapping;
        }

        [Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - We are deserializing XAML from a file. The file may have been read under and Assert for FileIOPermission. The data hould be validated and not cached.")]
        internal static object Deserialize(byte[] buffer, Assembly localAssembly)
        {
            using (MemoryStream memoryStream = new MemoryStream(buffer))
            {
                using (TextReader streamReader = new StreamReader(memoryStream))
                {
                    using (XamlDebuggerXmlReader xamlDebuggerReader = new XamlDebuggerXmlReader(streamReader, new XamlSchemaContext(), localAssembly))
                    {
                        xamlDebuggerReader.SourceLocationFound += XamlDebuggerXmlReader.SetSourceLocation;

                        using (XamlReader activityBuilderReader = ActivityXamlServices.CreateBuilderReader(xamlDebuggerReader))
                        {
                            return XamlServices.Load(activityBuilderReader);
                        }
                    }
                }
            }
        }

        public static void CollectMapping(Activity rootActivity1, Activity rootActivity2, Dictionary<object, SourceLocation> mapping, string path)
        {
            CollectMapping(rootActivity1, rootActivity2, mapping, path, null, requirePrepareForRuntime: true);
        }        

        // Collect mapping for activity1 and its descendants to their corresponding source location.
        // activity2 is the shadow of activity1 but with SourceLocation information.
        [Fx.Tag.SecurityNote(Miscellaneous = "RequiresReview - We are dealing with activity and SourceLocation information that came from the user, possibly under an Assert for FileIOPermission. The data hould be validated and not cached.")]
        static void CollectMapping(Activity rootActivity1, Activity rootActivity2, Dictionary<object, SourceLocation> mapping, string path, byte[] checksum, bool requirePrepareForRuntime)
        {
            // For x:Class, the rootActivity here may not be the real root, but it's the first child of the x:Class activity.
            Activity realRoot1 = (rootActivity1.RootActivity != null) ? rootActivity1.RootActivity : rootActivity1;
            if ((requirePrepareForRuntime && !realRoot1.IsRuntimeReady) || (!requirePrepareForRuntime && !realRoot1.IsMetadataFullyCached))
            {
                IList<ValidationError> validationErrors = null;
                ActivityUtilities.CacheRootMetadata(realRoot1, new ActivityLocationReferenceEnvironment(), ProcessActivityTreeOptions.ValidationOptions, null, ref validationErrors);
            }

            // Similarly for rootActivity2.
            Activity realRoot2 = (rootActivity2.RootActivity != null) ? rootActivity2.RootActivity : rootActivity2;
            if (rootActivity1 != rootActivity2 && (requirePrepareForRuntime && !realRoot2.IsRuntimeReady) || (!requirePrepareForRuntime && !realRoot2.IsMetadataFullyCached))
            {
                IList<ValidationError> validationErrors = null;
                ActivityUtilities.CacheRootMetadata(realRoot2, new ActivityLocationReferenceEnvironment(), ProcessActivityTreeOptions.ValidationOptions, null, ref validationErrors);
            }

            Queue<KeyValuePair<Activity, Activity>> pairsRemaining = new Queue<KeyValuePair<Activity, Activity>>();

            pairsRemaining.Enqueue(new KeyValuePair<Activity, Activity>(rootActivity1, rootActivity2));
            KeyValuePair<Activity, Activity> currentPair;
            HashSet<Activity> visited = new HashSet<Activity>();

            while (pairsRemaining.Count > 0)
            {
                currentPair = pairsRemaining.Dequeue();
                Activity activity1 = currentPair.Key;
                Activity activity2 = currentPair.Value;

                visited.Add(activity1);

                SourceLocation sourceLocation;
                if (TryGetSourceLocation(activity2, path, checksum, out sourceLocation))
                {
                    mapping.Add(activity1, sourceLocation);
                }
                else if (!((activity2 is IExpressionContainer) || (activity2 is IValueSerializableExpression))) // Expression is known not to have source location.
                {
                    //Some activities may not have corresponding Xaml node, e.g. ActivityFaultedOutput.                    
                    Trace.WriteLine("WorkflowDebugger: Does not have corresponding Xaml node for: " + activity2.DisplayName + "\n");
                }

                // This to avoid comparing any value expression with DesignTimeValueExpression (in designer case).
                if (!((activity1 is IExpressionContainer) || (activity2 is IExpressionContainer) ||
                      (activity1 is IValueSerializableExpression) || (activity2 is IValueSerializableExpression)))
                {
                    IEnumerator<Activity> enumerator1 = WorkflowInspectionServices.GetActivities(activity1).GetEnumerator();
                    IEnumerator<Activity> enumerator2 = WorkflowInspectionServices.GetActivities(activity2).GetEnumerator();
                    bool hasNextItem1 = enumerator1.MoveNext();
                    bool hasNextItem2 = enumerator2.MoveNext();
                    while (hasNextItem1 && hasNextItem2)
                    {
                        if (!visited.Contains(enumerator1.Current))  // avoid adding the same activity (e.g. some default implementation).
                        {
                            if (enumerator1.Current.GetType() != enumerator2.Current.GetType())
                            {
                                // Give debugger log instead of just asserting; to help user find out mismatch problem.
                                Trace.WriteLine(
                                    "Unmatched type: " + enumerator1.Current.GetType().FullName +
                                    " vs " + enumerator2.Current.GetType().FullName + "\n");
                            }
                            pairsRemaining.Enqueue(new KeyValuePair<Activity, Activity>(enumerator1.Current, enumerator2.Current));
                        }
                        hasNextItem1 = enumerator1.MoveNext();
                        hasNextItem2 = enumerator2.MoveNext();
                    }

                    // If enumerators do not finish at the same time, then they have unmatched number of activities.
                    // Give debugger log instead of just asserting; to help user find out mismatch problem.
                    if (hasNextItem1 || hasNextItem2)
                    {
                        Trace.WriteLine("Unmatched number of children\n");
                    }
                }
            }
        }

        static void CollectMapping(Activity rootActivity1, Activity rootActivity2, Dictionary<object, SourceLocation> mapping, string path, byte[] checksum)
        {
            CollectMapping(rootActivity1, rootActivity2, mapping, path, checksum, requirePrepareForRuntime: true);
        }
        // Get SourceLocation for object deserialized with XamlDebuggerXmlReader in deserializer stack.
        static bool TryGetSourceLocation(object obj, string path, byte[] checksum, out SourceLocation sourceLocation)
        {
            sourceLocation = null;
            int startLine, startColumn, endLine, endColumn;

            if (AttachablePropertyServices.TryGetProperty<int>(obj, XamlDebuggerXmlReader.StartLineName, out startLine) &&
                AttachablePropertyServices.TryGetProperty<int>(obj, XamlDebuggerXmlReader.StartColumnName, out startColumn) &&
                AttachablePropertyServices.TryGetProperty<int>(obj, XamlDebuggerXmlReader.EndLineName, out endLine) &&
                AttachablePropertyServices.TryGetProperty<int>(obj, XamlDebuggerXmlReader.EndColumnName, out endColumn) &&
                SourceLocation.IsValidRange(startLine, startColumn, endLine, endColumn))
            {
                sourceLocation = new SourceLocation(path, checksum, startLine, startColumn, endLine, endColumn);
                return true;
            }
            return false;
        }

        public static ICollection<ActivitySymbol> GetSymbols(Activity rootActivity, Dictionary<object, SourceLocation> sourceLocations)
        {
            List<ActivitySymbol> symbols = new List<ActivitySymbol>();
            Activity realRoot = (rootActivity.RootActivity != null) ? rootActivity.RootActivity : rootActivity;
            if (!realRoot.IsMetadataFullyCached)
            {
                IList<ValidationError> validationErrors = null;
                ActivityUtilities.CacheRootMetadata(realRoot, new ActivityLocationReferenceEnvironment(), ProcessActivityTreeOptions.ValidationOptions, null, ref validationErrors);
            }
            Queue<Activity> activitiesRemaining = new Queue<Activity>();
            activitiesRemaining.Enqueue(realRoot);
            HashSet<Activity> visited = new HashSet<Activity>();
            while (activitiesRemaining.Count > 0)
            {
                Activity currentActivity = activitiesRemaining.Dequeue();
                SourceLocation sourceLocation;
                object origin = currentActivity.Origin == null ? currentActivity : currentActivity.Origin;
                if (!visited.Contains(currentActivity) && sourceLocations.TryGetValue(origin, out sourceLocation))
                {
                    symbols.Add(new ActivitySymbol
                    {
                        QualifiedId = currentActivity.QualifiedId.AsByteArray(),
                        StartLine = sourceLocation.StartLine,
                        StartColumn = sourceLocation.StartColumn,
                        EndLine = sourceLocation.EndLine,
                        EndColumn = sourceLocation.EndColumn
                    });
                }
                visited.Add(currentActivity);
                foreach (Activity childActivity in WorkflowInspectionServices.GetActivities(currentActivity))
                {
                    activitiesRemaining.Enqueue(childActivity);
                }
            }
            return symbols;
        }
    }
}