1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Collections.Generic;
6 using System.Diagnostics;
7 using System.IO;
8 
9 using Internal.TypeSystem;
10 
11 namespace ILCompiler
12 {
13     public class UtcStackTraceEmissionPolicy : StackTraceEmissionPolicy
14     {
15         /// <summary>
16         /// List of exception files to load.
17         /// </summary>
18         List<string> _stackTraceExceptionFiles = new List<string>();
19 
20         /// <summary>
21         /// Explicitly blacklisted namespaces.
22         /// </summary>
23         HashSet<string> _namespaceBlacklist = new HashSet<string>();
24 
25         /// <summary>
26         /// Explicitly whitelisted namespaces.
27         /// </summary>
28         HashSet<string> _namespaceWhitelist = new HashSet<string>();
29 
30         /// <summary>
31         /// Explicitly blacklisted types.
32         /// </summary>
33         HashSet<string> _typeBlacklist = new HashSet<string>();
34 
35         /// <summary>
36         /// Explicitly whitelisted types.
37         /// </summary>
38         HashSet<string> _typeWhitelist = new HashSet<string>();
39 
40         /// <summary>
41         /// Cache of explicitly enabled / disabled types after their eligibility has been resolved.
42         /// </summary>
43         Dictionary<TypeDesc, bool> _cachedTypeEligibility = new Dictionary<TypeDesc, bool>();
44 
UtcStackTraceEmissionPolicy()45         public UtcStackTraceEmissionPolicy()
46         {
47             // load the default exception file next to the NUTC app
48             LoadExceptionFile(Path.Combine(GetAppExeDirectory(), "StackTraceExceptions.txt"));
49         }
50 
ShouldIncludeMethod(MethodDesc method)51         public override bool ShouldIncludeMethod(MethodDesc method)
52         {
53             DefType type = method.GetTypicalMethodDefinition().OwningType as DefType;
54             if (type != null)
55                 return !IsTypeExplicitlyDisabled(type);
56             return false;
57         }
58 
59         /// <summary>
60         /// Check explicit type / namespace blacklist and update the cache.
61         /// </summary>
IsTypeExplicitlyDisabled(DefType type)62         bool IsTypeExplicitlyDisabled(DefType type)
63         {
64             bool result;
65             if (_cachedTypeEligibility.TryGetValue(type, out result))
66             {
67                 return result;
68             }
69 
70             string typeName;
71             result = IsTypeExplicitlyDisabledInner(type, out typeName);
72             _cachedTypeEligibility.Add(type, result);
73             return result;
74         }
75 
76         /// <summary>
77         /// Check explicit type / namespace blacklist ignoring cache management. Optionally output the type name.
78         /// </summary>
IsTypeExplicitlyDisabledInner(DefType type, out string outputTypeName)79         bool IsTypeExplicitlyDisabledInner(DefType type, out string outputTypeName)
80         {
81             string typeName;
82             bool isTypeDisabled = false;
83             MetadataType metaDataType = type as MetadataType;
84 
85             if (metaDataType != null && metaDataType.ContainingType != null)
86             {
87                 // Nested type
88                 isTypeDisabled = IsTypeExplicitlyDisabledInner(metaDataType.ContainingType, out typeName);
89                 typeName = typeName + '+' + metaDataType.Name;
90             }
91             else
92             {
93                 // Namespace type
94                 typeName = type.Name;
95                 int lastPeriod = typeName.LastIndexOf('.');
96                 string namespaceName = null;
97 
98                 if (lastPeriod != -1)
99                 {
100                     namespaceName = typeName.Substring(0, lastPeriod);
101                 }
102 
103                 isTypeDisabled = _namespaceBlacklist.Contains(namespaceName) && _namespaceWhitelist.Contains(namespaceName);
104             }
105 
106             if (_typeBlacklist.Contains(typeName))
107             {
108                 isTypeDisabled = true;
109             }
110 
111             if (_typeWhitelist.Contains(typeName))
112             {
113                 isTypeDisabled = false;
114             }
115 
116             outputTypeName = typeName;
117             return isTypeDisabled;
118         }
119 
120         /// <summary>
121         /// Identify the directory where the main executable resides.
122         /// </summary>
GetAppExeDirectory()123         string GetAppExeDirectory()
124         {
125 #if PROJECTN
126             var process = Process.GetCurrentProcess();
127             string fullPath = process.MainModule.FileName;
128             return Path.GetDirectoryName(fullPath);
129 #else
130             Debug.Assert(false);
131             return null;
132 #endif
133         }
134 
135         /// <summary>
136         /// Load the default exception file and possibly additional custom stack trace exception files.
137         /// </summary>
LoadExceptionFile(string exceptionFileName)138         void LoadExceptionFile(string exceptionFileName)
139         {
140 #if PROJECTN
141             if (!File.Exists(exceptionFileName))
142                 return;
143 
144             const int LeaderCharacterCount = 2;
145 
146             using (TextReader tr = File.OpenText(exceptionFileName))
147             {
148                 string line = tr.ReadLine();
149                 while (line != null)
150                 {
151                     // Currently supported leader character sequences are:
152                     // N+nnn .. explicitly whitelist namespace nnn
153                     // N-nnn .. explicitly blacklist namespace nnn
154                     // T+ttt .. explicitly whitelist namespace-qualified type ttt (nested types separated by +)
155                     // T-ttt .. explicitly blacklist namespace-qualified type ttt
156                     // Type specifications override namespace specifications.
157                     // Whenever both whitelist and blacklist is specified for an element, whitelist wins.
158 
159                     // Reserve empty lines and lines starting with # for comments
160                     if (line.Length > LeaderCharacterCount && line[0] != '#')
161                     {
162                         char char1 = line[0];
163                         char char2 = line[1];
164                         string arg = line.Substring(2);
165 
166                         if (char1 == 'N' && char2 == '+')
167                         {
168                             _namespaceWhitelist.Add(arg);
169                         }
170                         else if (char1 == 'N' && char2 == '-')
171                         {
172                             _namespaceBlacklist.Add(arg);
173                         }
174                         else if (char1 == 'T' && char2 == '+')
175                         {
176                             _typeWhitelist.Add(arg);
177                         }
178                         else if (char1 == 'T' && char2 == '-')
179                         {
180                             _typeBlacklist.Add(arg);
181                         }
182                         else
183                         {
184                             // unexpected pattern
185                             Debug.Assert(false);
186                         }
187                     }
188 
189                     line = tr.ReadLine();
190                 }
191             }
192 #endif
193         }
194     }
195 }
196