Skip to content

Clouseau

papeh edited this page Jun 1, 2018 · 4 revisions

What is Clouseau?

Clouseau is a homegrown code analysis tool to make sure everything gets disposed that needs to It analyses the compiled (binary) code; therefore, it is possible that it will produce different results on Windows (.NET) than on Linux (Mono).

It is similar to Microsoft's FxCop, but in contrast to FxCop, it works cross-platform. It is similar to Mono's Gendarme, but in contrast to Gendarme, it works for our needs without a mountain of false positive results.

In FieldWorks development it is run as part of the build process (before running tests), currently to identify potential dispose problems as well as certain potential cross-platform issues.

Why?

In the past we had several occasions where missing Dispose calls caused the application or tests to hang. Finding the exact spots that were causing the hangs were very tedious and took several months. It became apparent that we need a tool that helps identify potential problems early on when new code gets introduced rather than trying to find the trouble spots when problems arise. Clouseau requires all types that implement IDisposable to log an error if they are not disposed properly; if any of these errors appear in our unit test logs, the build is marked as unstable.

What do I need to do?

Clouseau gets called automatically as part of the msbuild/xbuild build process. If it identifies any problems, it will log them to the console (build log) and the build will stop.

New Options for Build Process

  • you can set the property verifyFail to false to allow the build to finish so that you can look at all problems identified by Clouseau at once, e.g.

    build /t:remakefw /p:action=test /p:verifyFail=false

How to deal with problems Clouseau finds

If Clouseau reports any problems you should look at the source code for each problem it identifies. If there is a problem in the code you should fix it. Here is an example:

class TempGuidOn<T> : IDisposable where T : ICmObject
{
	public T Item { get; private set; }
	private readonly Guid m_OriginalGuid;

	public TempGuidOn(T item, Guid tempGuid)
	{
		Item = item;
		m_OriginalGuid = item.Guid;
		SetGuidOn(item, tempGuid);
	}

	~TempGuidOn()
	{
		Dispose(false); // mostly to print the error
	}

	public void Dispose()
	{
		Dispose(true); // dispose resources for this and any derived class
		GC.SuppressFinalize(this); // to suppress the error message
	}

	protected virtual void Dispose(bool disposing)
	{
		System.Diagnostics.Debug.WriteLineIf(!disposing,
			"****** Missing Dispose() call for " + GetType().Name + " ******");

		// Dispose any resources if we are disposing
		if (disposing)
			SetGuidOn(Item, m_OriginalGuid);
	}

	private static void SetGuidOn(ICmObject item, Guid newGuid)
	{
		var refGuidField = ReflectionHelper.GetField(item, "m_guid");
		ReflectionHelper.SetField(refGuidField, "m_guid", newGuid);
	}
}

Derived classes need implement only the following, and only if they have disposable resources (the base class will handle everything else, including printing the "missing" message.

protected override bool Dispose(bool Disposing)
{
	// dispose any resources here, then call:
	base.Dispose(disposing);
}

Classes derived from Windows Forms Disposables should also implement the ~Finalizer() and should print the error message in the first line of protected override bool Dispose(bool Disposing). Unfortunately, because of how Clouseau is presently implemented, it is intolerant of newer ways of assembling the string in the error message.

Clone this wiki locally