Skip to content

Commit a63f05c

Browse files
committed
Merge branch 'next' into bugfix
2 parents 6811bdc + 2a47a4d commit a63f05c

File tree

11 files changed

+434
-76
lines changed

11 files changed

+434
-76
lines changed

Rubberduck.Core/UI/Command/MenuItems/CommandBars/SerializeProjectsCommandMenuItem.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ protected override void OnExecute(object parameter)
6666
if (!toSerialize.ContainsKey(type.Guid))
6767
{
6868
toSerialize.Add(type.Guid, type);
69-
}
69+
}
7070
}
7171
}
7272
}
@@ -76,6 +76,20 @@ protected override void OnExecute(object parameter)
7676
Logger.Warn($"Serializing {library.Path}.");
7777
_serializationProvider.SerializeProject(library);
7878
}
79+
80+
#if DEBUG
81+
//This block must be inside a DEBUG block because the Serialize method
82+
//called is conditionally compiled and available only for a DEBUG build.
83+
var path = !string.IsNullOrWhiteSpace(_serializationProvider.Target)
84+
? Path.GetDirectoryName(_serializationProvider.Target)
85+
: Path.GetTempPath();
86+
var traceDirectory = Path.Combine(path, "COM Trace");
87+
if (!Directory.Exists(traceDirectory))
88+
{
89+
Directory.CreateDirectory(traceDirectory);
90+
}
91+
Rubberduck.VBEditor.ComManagement.ComSafeManager.GetCurrentComSafe().Serialize(traceDirectory);
92+
#endif
7993
}
8094
}
8195
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Rubberduck.VBEditor;
2+
3+
namespace Rubberduck.Parsing.ComReflection
4+
{
5+
public interface IComProjectDeserializer
6+
{
7+
ComProject DeserializeProject(ReferenceInfo reference);
8+
bool SerializedVersionExists(ReferenceInfo reference);
9+
}
10+
}
Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
using Rubberduck.VBEditor;
2-
3-
namespace Rubberduck.Parsing.ComReflection
1+
namespace Rubberduck.Parsing.ComReflection
42
{
5-
public interface IComProjectSerializationProvider
3+
public interface IComProjectSerializationProvider : IComProjectDeserializer
64
{
75
string Target { get; }
86
void SerializeProject(ComProject project);
9-
ComProject DeserializeProject(ReferenceInfo reference);
10-
bool SerializedVersionExists(ReferenceInfo reference);
117
}
128
}

Rubberduck.Parsing/ComReflection/SerializedReferencedDeclarationsCollector.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ namespace Rubberduck.Parsing.ComReflection
66
{
77
public class SerializedReferencedDeclarationsCollector : ReferencedDeclarationsCollectorBase
88
{
9-
private readonly IComProjectSerializationProvider _serializer;
9+
private readonly IComProjectDeserializer _deserializer;
1010

11-
public SerializedReferencedDeclarationsCollector(string serializedDeclarationsPath = null)
11+
public SerializedReferencedDeclarationsCollector(IComProjectDeserializer deserializer)
1212
{
13-
_serializer = new XmlComProjectSerializer(serializedDeclarationsPath);
13+
_deserializer = deserializer;
1414
}
1515

1616
public override IReadOnlyCollection<Declaration> CollectedDeclarations(ReferenceInfo reference)
1717
{
18-
if (!_serializer.SerializedVersionExists(reference))
18+
if (!_deserializer.SerializedVersionExists(reference))
1919
{
2020
return new List<Declaration>();
2121
}
@@ -25,7 +25,7 @@ public override IReadOnlyCollection<Declaration> CollectedDeclarations(Reference
2525

2626
private IReadOnlyCollection<Declaration> LoadDeclarationsFromProvider(ReferenceInfo reference)
2727
{
28-
var type = _serializer.DeserializeProject(reference);
28+
var type = _deserializer.DeserializeProject(reference);
2929
return LoadDeclarationsFromComProject(type);
3030
}
3131
}
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Runtime.CompilerServices;
4+
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
5+
6+
#if DEBUG
7+
using System.Diagnostics;
8+
using System.IO;
9+
using System.Runtime.InteropServices;
10+
#endif
11+
12+
namespace Rubberduck.VBEditor.ComManagement
13+
{
14+
public abstract class ComSafeBase : IComSafe
15+
{
16+
public abstract void Add(ISafeComWrapper comWrapper);
17+
18+
public abstract bool TryRemove(ISafeComWrapper comWrapper);
19+
20+
//We do not use GetHashCode because subclasses of SafeComWrapper<T> overwrite this method
21+
//and we need to distinguish between individual instances.
22+
protected int GetComWrapperObjectHashCode(ISafeComWrapper comWrapper)
23+
{
24+
return RuntimeHelpers.GetHashCode(comWrapper);
25+
}
26+
27+
private bool _disposed;
28+
public void Dispose()
29+
{
30+
Dispose(true);
31+
32+
#if DEBUG
33+
if (_disposed)
34+
{
35+
return;
36+
}
37+
38+
_disposed = true;
39+
40+
lock (_streamLock)
41+
{
42+
try
43+
{
44+
if (_traceStream == null)
45+
{
46+
return;
47+
}
48+
49+
_traceStream.Close();
50+
if (string.IsNullOrWhiteSpace(_directory))
51+
{
52+
File.Delete(_traceFilePath);
53+
}
54+
else
55+
{
56+
File.Move(_traceFilePath,
57+
Path.Combine(_directory,
58+
Path.GetFileNameWithoutExtension(_traceFilePath) + " final.csv"));
59+
}
60+
}
61+
finally
62+
{
63+
_traceStream?.Dispose();
64+
_traceStream = null;
65+
}
66+
}
67+
#endif
68+
}
69+
70+
protected abstract void Dispose(bool disposing);
71+
72+
#if DEBUG
73+
private struct TraceData
74+
{
75+
internal int HashCode { get; set; }
76+
internal string IUnknownAddress { get; set; }
77+
internal IEnumerable<string> StackTrace { get; set; }
78+
}
79+
private StreamWriter _traceStream;
80+
private string _traceFilePath;
81+
private string _directory;
82+
private readonly object _streamLock = new object();
83+
84+
/// <summary>
85+
/// The first few stack frames come from the ComSafe and thus are not
86+
/// particularly interesting. Typically, we want to look at the frames
87+
/// outside the ComSafe.
88+
/// </summary>
89+
private const int StackTraceNumberOfElementsToSkipOnRemoval = 6;
90+
private const int StackTrackNumberOfElementsToSkipOnAddUpdate = 8;
91+
private const int StackTraceDepth = 5;
92+
93+
/// <inheritdoc cref="IComSafe.Serialize"/>
94+
public void Serialize(string targetDirectory)
95+
{
96+
lock (_streamLock)
97+
{
98+
_directory = targetDirectory;
99+
var serializeTime = DateTime.UtcNow;
100+
using (var stream = File.AppendText(Path.Combine(_directory,
101+
$"COM Safe Content Snapshot {serializeTime:yyyyMMddhhmmss}.csv")))
102+
{
103+
stream.WriteLine(
104+
$"Ordinal\tKey\tCOM Wrapper Type\tWrapping Null?\tIUnknown Pointer Address");
105+
var i = 0;
106+
foreach (var kvp in GetWrappers())
107+
{
108+
var line = kvp.Value != null
109+
? $"{i++}\t{kvp.Key}\t\"{kvp.Value.GetType().FullName}\"\t\"{kvp.Value.IsWrappingNullReference}\"\t\"{(kvp.Value.IsWrappingNullReference ? "null" : GetPtrAddress(kvp.Value.Target))}\""
110+
: $"{i++}\t{kvp.Key}\t\"null\"\t\"null\"\t\"null\"";
111+
stream.WriteLine(line);
112+
}
113+
}
114+
115+
if (_traceStream == null)
116+
{
117+
return;
118+
}
119+
120+
_traceStream.Flush();
121+
File.Copy(_traceFilePath, Path.Combine(_directory, $"COM Safe Stack Trace {serializeTime:yyyyMMddhhmmss}.csv"));
122+
}
123+
}
124+
125+
protected void TraceAdd(ISafeComWrapper comWrapper)
126+
{
127+
Trace("Add", comWrapper, StackTrackNumberOfElementsToSkipOnAddUpdate);
128+
}
129+
130+
protected void TraceUpdate(ISafeComWrapper comWrapper)
131+
{
132+
Trace("Update", comWrapper, StackTrackNumberOfElementsToSkipOnAddUpdate);
133+
}
134+
135+
protected void TraceRemove(ISafeComWrapper comWrapper, bool wasRemoved)
136+
{
137+
var activity = wasRemoved ? "Removed" : "Not removed";
138+
Trace(activity, comWrapper, StackTraceNumberOfElementsToSkipOnRemoval);
139+
}
140+
141+
private readonly object _idLock = new object();
142+
private int _id;
143+
private void Trace(string activity, ISafeComWrapper comWrapper, int framesToSkip)
144+
{
145+
lock (_streamLock)
146+
{
147+
if (_disposed)
148+
{
149+
return;
150+
}
151+
152+
if (_traceStream == null)
153+
{
154+
var directory = Path.GetTempPath();
155+
_traceFilePath = Path.Combine(directory,
156+
$"COM Safe Stack Trace {DateTime.UtcNow:yyyyMMddhhmmss}.{GetHashCode()}.csv");
157+
_traceStream = File.AppendText(_traceFilePath);
158+
_traceStream.WriteLine(
159+
$"Ordinal\tTimestamp\tActivity\tKey\tIUnknown Pointer Address\t{FrameHeaders()}");
160+
}
161+
162+
int id;
163+
lock (_idLock)
164+
{
165+
id = _id++;
166+
}
167+
168+
var traceData = new TraceData
169+
{
170+
HashCode = GetComWrapperObjectHashCode(comWrapper),
171+
IUnknownAddress = comWrapper.IsWrappingNullReference ? "null" : GetPtrAddress(comWrapper.Target),
172+
StackTrace = GetStackTrace(StackTraceDepth, framesToSkip)
173+
};
174+
175+
var line =
176+
$"{id}\t{DateTime.UtcNow}\t\"{activity}\"\t{traceData.HashCode}\t{traceData.IUnknownAddress}\t\"{string.Join("\"\t\"", traceData.StackTrace)}\"";
177+
_traceStream.WriteLine(line);
178+
}
179+
}
180+
181+
private static string FrameHeaders()
182+
{
183+
var headers = new System.Text.StringBuilder();
184+
for(var i = 1; i <= StackTraceDepth; i++)
185+
{
186+
headers.Append($"Frame {i}\t");
187+
}
188+
189+
return headers.ToString();
190+
}
191+
192+
protected abstract IDictionary<int, ISafeComWrapper> GetWrappers();
193+
194+
private static IEnumerable<string> GetStackTrace(int frames, int framesToSkip)
195+
{
196+
var list = new List<string>();
197+
var trace = new StackTrace();
198+
if (trace.FrameCount < (frames + framesToSkip))
199+
{
200+
frames = trace.FrameCount;
201+
}
202+
else
203+
{
204+
frames += framesToSkip;
205+
}
206+
207+
framesToSkip -= 1;
208+
frames -= 1;
209+
210+
for (var i = framesToSkip; i < frames; i++)
211+
{
212+
var frame = trace.GetFrame(i);
213+
var type = frame.GetMethod().DeclaringType;
214+
215+
var typeName = type?.FullName ?? string.Empty;
216+
var methodName = frame.GetMethod().Name;
217+
218+
var qualifiedName = $"{typeName}{(typeName.Length > 0 ? "::" : string.Empty)}{methodName}";
219+
list.Add(qualifiedName);
220+
}
221+
222+
return list;
223+
}
224+
225+
protected static string GetPtrAddress(object target)
226+
{
227+
if (target == null)
228+
{
229+
return IntPtr.Zero.ToString();
230+
}
231+
232+
if (!Marshal.IsComObject(target))
233+
{
234+
return "Not a COM object";
235+
}
236+
237+
var pointer = IntPtr.Zero;
238+
try
239+
{
240+
pointer = Marshal.GetIUnknownForObject(target);
241+
}
242+
finally
243+
{
244+
if (pointer != IntPtr.Zero)
245+
{
246+
Marshal.Release(pointer);
247+
}
248+
}
249+
250+
return pointer.ToString();
251+
}
252+
#endif
253+
}
254+
}

Rubberduck.VBEEditor/ComManagement/IComSafe.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,14 @@ public interface IComSafe: IDisposable
77
{
88
void Add(ISafeComWrapper comWrapper);
99
bool TryRemove(ISafeComWrapper comWrapper);
10+
#if DEBUG
11+
/// <summary>
12+
/// Available in DEBUG build only. Provide a mechanism for serializing both
13+
/// a snapshot of the COM safe at the instant and a historical activity log
14+
/// with a limited stack trace for each entry.
15+
/// </summary>
16+
/// <param name="targetDirectory">The path to a directory to place the serialized files in</param>
17+
void Serialize(string targetDirectory);
18+
#endif
1019
}
1120
}

0 commit comments

Comments
 (0)