Click here to Skip to main content
15,891,951 members
Articles / Desktop Programming / MFC

LintProject - Improving the Usability of PC-Lint with Visual C++ Solutions and Projects

,
Rate me:
Please Sign up or sign in to vote.
4.83/5 (36 votes)
29 Jan 2009CPOL13 min read 280.8K   1.7K   52  
Utility to run PC-Lint on Visual C++ solutions and projects, generating XML and HTML reports of the results.
/************************************************************************
 *
 *  Description : CVc6ProjectFileReaderImpl - implementation class to parse
 *                Visual C++ project files for Visual C+ 6.0 and eMbedded
 *                Visual C++ 4.0.
 *
 *     (c) Copyright 2000-2008 by Anna-Jayne Metcalfe (anna@riverblade.co.uk)
 *     and Beth Mackenzie (beth@riverblade.co.uk) / Riverblade Limited
 *
 *  Licence Terms:
 *
 *     This code may be freely reused, subject to the licence terms below.
 *     Please do let us know of any bugs you find or improvements you make,
 *     so that we can pass them on to the rest of the development community.
 *
 *     This code is free software; you can redistribute it and/or
 *     modify it under the terms of the Code Project Open License (CPOL)
 *     version 1.0 (http://www.codeproject.com/info/cpol10.aspx).
 *
 *     This code is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     Code Project Open Licence for further details.
 *
 ************************************************************************
 *    $Archive: /Projects/AddIns/Visual Lint/Development/Libraries/AddInSolutionModel/src/Vc6ProjectFileReaderImpl.h $
 *   $Revision: 7 $
 *       $Date: 14/04/08 11:31 $
 *     $Author: Anna $
 * 
 * $Nokeywords: $
 ************************************************************************/

/// \file	
/// \brief Implementation class for CProjectFileReader.

#pragma once


#include "ProjectFileReaderImpl.h"


#define PLACEHOLDER_CONFIG _T("VL_placeholder_config")


namespace Riverblade
{
	namespace Libraries
	{
		namespace AddInSolutionModel
		{
			/// \brief Implementation class to read Visual C++ 6.0 and eMbedded Visual C++ 4.0 project files.
			///
			class CVc6ProjectFileReaderImpl : public CProjectFileReaderImpl
			{
				public:
					/// \brief Default constructor.
					///
					CVc6ProjectFileReaderImpl(void)
						:	CProjectFileReaderImpl(),
							m_bParsingFile(false),
							m_sLastConfiguration(_T("") )
					{
					}


					/// \brief Class destructor
					///
					virtual ~CVc6ProjectFileReaderImpl(void)
					{
					}


				// Data members
				private:
					bool			m_bParsingFile;			///< Implementation var set to indicate that we are currently parsing a file definition.
					CString			m_sLastConfiguration;	///< Implementation var holding the name of the last configuration we parsed.



				// Virtual overrides
				public:
					/// \brief Parse the given Visual C++ 6.0 project file contents.
					/// 
					/// \param	sFileText			The text of the project file.
					/// \return						\em true if the text was parsed successfully;
					///								\em false otherwise.
					///
					virtual bool Parse(const CString& sFileText)
					{
						CAtlArray<CString> arrayLines;
						(void)Utils::StrToArray(sFileText, arrayLines);

						m_sLastConfiguration = PLACEHOLDER_CONFIG;

						if (arrayLines.GetCount() > 0)
						{
							// The file definitely exists and has been read successfully
							// From here on in its up to the parser...
							for (size_t nLineNo = 0; nLineNo < arrayLines.GetCount(); nLineNo++)
							{
								const CString& sLine = arrayLines[nLineNo];

								(void)ParseLine(sLine);
							}
							return true;
						}
						return false;
					}


					/// \brief Return the "configuration string" for a given configuration within the project.
					///
					/// \param	sConfig				The name of the configuration for which the configuration
					///								string should be returned (e.g "Win32 Debug").
					/// \return						The corresponding configuration string, or an empty string
					///								if the configuration name is not recognised.
					///
					virtual CString GetConfigurationString(const CString& sConfig) const
					{
						CString sUnused;
						if (m_mapAvailableConfigurations.Lookup(sConfig, sUnused) )
						{
							CString sConfigurationString;
							sConfigurationString.Format(_T("%s - %s"), m_sProjectName, sConfig);

							return sConfigurationString;
						}
						return _T("");
					}


				// Implementation
				public:		// For unit testing only

					/// \brief Implementation method to parse the given line
					///
					/// \param	sLine			The text line to parse
					/// \return					\em true if parsed successfully; \em false otherwise.
					///
					bool ParseLine(const CString& sLine)
					{
						if (!sLine.IsEmpty())
						{
							if (sLine.Find( _T(" Name=") ) > 1)
							{
								// e.g. "# Microsoft Developer Studio Project File - Name="ResOrgCore" - Package Owner=<4>"
								m_sProjectName = Utils::Before(Utils::After(sLine, _T("Name=") ), _T("-") );

								(void)m_sProjectName.TrimLeft();
								(void)m_sProjectName.TrimRight();

								(void)m_sProjectName.TrimLeft( _T("\"") );
								(void)m_sProjectName.TrimRight( _T("\"") );
							}
							else if (sLine.Find( _T("\"$(CFG)\"") ) > 0)
							{
								// Store details of the configurations we find in this project file
								// The configurations are identified by lines such as:
								//
								//	!IF  "$(CFG)" == "ResOrgCore - Win32 Release"
								//
								// in the .dsp file.
								CString sConfigurationString = Utils::After(sLine,  _T("==") );
								(void)sConfigurationString.TrimLeft();
								(void)sConfigurationString.TrimLeft( _T("\"") );
								(void)sConfigurationString.TrimRight( _T("\"") );

								CString sConfiguration = Utils::After(sConfigurationString,  _T("-") );
								(void)sConfiguration.TrimLeft();

								CString sUnused;
								if (!m_mapAvailableConfigurations.Lookup(sConfiguration, sUnused) )
								{
									(void)m_mapAvailableConfigurations.SetAt(sConfiguration, _T("") );

								}
								m_sLastConfiguration = sConfiguration;
							}
							else if (sLine.Find( _T("# Name") ) >= 0)
							{
								// e.g. # Name "Quant_BE - Win32 Unicode Debug"
								if (PLACEHOLDER_CONFIG == m_sLastConfiguration)
								{
									// These directives come at the end of the settings, just before the file list. We only
									// need to use them if we have not already seen a $(CFG) directive - e.g. if the project 
									// has only a single configuration [Anna 9.2.2008].
									CString sConfiguration = Utils::After(sLine,  _T("-") );
									sConfiguration.TrimRight(_T("\"") );
									sConfiguration.Trim();

									// Move the configuration settings from the placeholder configuration
									CProjectConfiguration info;
									ATLVERIFY(m_mapConfigToSettings.Lookup(m_sLastConfiguration, info) );

									info.m_sName = sConfiguration;
									(void)m_mapConfigToSettings.SetAt(sConfiguration, info);
									(void)m_mapAvailableConfigurations.SetAt(sConfiguration, info.m_sIntermediateFolder);

									ATLVERIFY(m_mapConfigToSettings.RemoveKey(m_sLastConfiguration) );
									ATLVERIFY(m_mapAvailableConfigurations.RemoveKey(m_sLastConfiguration) );

									m_sLastConfiguration = sConfiguration;		// Just in case
								}
							}
							else if (sLine.Find( _T("Intermediate_Dir") ) > 0)
							{
								CString sIntermediateFolder = Utils::After(sLine,  _T("Intermediate_Dir") );

								(void)sIntermediateFolder.TrimLeft();
								(void)sIntermediateFolder.TrimLeft( _T("\"") );
								(void)sIntermediateFolder.TrimRight( _T("\"") );

								(void)m_mapAvailableConfigurations.SetAt(m_sLastConfiguration, sIntermediateFolder);
							}
							else if (!m_bParsingFile && (sLine.Find( _T("# ADD CPP") ) >= 0) )
							{
								// e.g. # ADD CPP /nologo /MDd /W3 /GR /GX /Z7 /Od /Gy /I "$(ES_INCLUDE)" /I "$(ESBASEDIR)/h" /I "$(ESBASEDIR)/../exp/idl/gen" /I "$(ESBASEDIR)/gen/idl/generated" /I "$(ESBASEDIR)/../exp/inc" /I "$(ESBASEDIR)\..\exp_oem\exp\idl\gen" /I "$(ESBASEDIR)/is" /I "$(ESBASEDIR)/pm" /I "$(ESBASEDIR)/../interf/exph" /I "$(ESBASEDIR)/base/stg" /I "$(ESBASEDIR)/base/AdoHelp" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "WIN32" /D "PMDLL_IMPLEMENTATION" /D "AUTO_INCLUDE_SMARTS" /FR /Yu"PmDllAfx.h" /c

								// Parse /I and /D directives
								// Expand env vars in each
								// Concatenate each into a composite path
								// Set CProjectConfiguration::m_sAdditionalIncludeDirectories
								CProjectConfiguration info;
								ATLVERIFY(ParseCppConfiguration(sLine, info.m_sAdditionalIncludeDirectories, info.m_sPreprocessorDefinitions) );

								ATLVERIFY(m_mapAvailableConfigurations.Lookup(m_sLastConfiguration, info.m_sIntermediateFolder) );

								if (0 == info.m_sIntermediateFolder.Find(_T(".\\") ) )
								{
									// Remove ".\" from the start of any pathnames, as they can confuse PC-Lint [Anna 12.2.2008].
									info.m_sIntermediateFolder = Utils::After(info.m_sIntermediateFolder, _T(".\\") );
								}

								//					info.m_eConfigurationType	= ::_tstoi(GetAttribute(sConfigTag, _T("ConfigurationType") ) );
								//					info.m_eCharacterSet		= ::_tstoi(GetAttribute(sConfigTag, _T("CharacterSet") ) );

								//					CString sMinimiseRTL		= GetAttribute(sConfigTag, _T("ATLMinimizesCRunTimeLibraryUsage") );
								//					info.m_bMinimiseRTL			= ( (0 == sMinimiseRTL.CompareNoCase( _T("true") ) ) || (_T("1") == sMinimiseRTL) );
								//					info.m_eUseOfMfc			= ::_tstoi(GetAttribute(sConfigTag, _T("UseOfMFC") ) );
								//					info.m_eUseOfAtl			= ::_tstoi(GetAttribute(sConfigTag, _T("UseOfAtl") ) );

								//					info.m_eRuntimeLibrary		= ::_tstoi(GetAttribute(sCompilerElement, _T("RuntimeLibrary") ) );

								info.m_bForceConformanceInForLoopScope	= false;

								m_mapConfigToSettings[m_sLastConfiguration] = info;
							}
							else if (sLine.Find( _T("# Begin Source File") ) == 0)
							{
								m_bParsingFile = true;
							}
							else if (sLine.Find( _T("# End Source File") ) == 0)
							{
								m_bParsingFile = false;
							}
							else if (m_bParsingFile)
							{
								if (sLine.Find( _T("SOURCE") ) == 0)
								{
									CString sFile = Utils::After( sLine, _T("=") );

									// Strip quotes
									(void)sFile.TrimLeft( _T("\"") );
									(void)sFile.TrimRight( _T("\"") );

									if (!sFile.IsEmpty() )
									{
										ATLVERIFY(ExpandEnvironmentVars(sFile) );

										const CString sPathName = GetPathName(sFile);
										if (!sPathName.IsEmpty())
										{
											(void)m_arrayFiles.Add(sPathName);
										}
									}
								}
								if (sLine.Find( _T("# ADD CPP") ) >= 0)
								{
									//# ADD CPP /X /I ".\wibble" /U "_CONSOLE"

									// U = undefine; /X = ignore standard include paths
									// /u=$(NOINHERIT)

									CFileConfiguration fileconfig;
									ATLVERIFY(ParseCppConfiguration(sLine, fileconfig.m_sAdditionalIncludeDirectories, fileconfig.m_sPreprocessorDefinitions) );

									ATLASSERT(!m_sLastConfiguration.IsEmpty() );

									CProjectConfiguration& rconfig = m_mapConfigToSettings[m_sLastConfiguration];

									ATLASSERT(m_arrayFiles.GetCount() >= 1);
									const CString sPathName = m_arrayFiles[m_arrayFiles.GetCount() - 1];

									ATLVERIFY(rconfig.SetFileConfiguration(sPathName, fileconfig) );
								}
							}
							return true;
						}
						return false;
					}


					/// \brief Implementation method to parse "# ADD CPP" directives
					///
					/// The following actions are performed on "# ADD CPP" directives:
					///
					///		- Parse /I and /D directives
					///		- Expand any environment variables within each
					///		- Concatenate each into a composite path
					///		- Set CProjectConfiguration::m_sAdditionalIncludeDirectories
					///
					/// e.g. # ADD CPP /nologo /MDd /W3 /GR /GX /Z7 /Od /Gy /I "$(ES_INCLUDE)" /I "$(ESBASEDIR)/h" /I "$(ESBASEDIR)/../exp/idl/gen" /I "$(ESBASEDIR)/gen/idl/generated" /I "$(ESBASEDIR)/../exp/inc" /I "$(ESBASEDIR)\..\exp_oem\exp\idl\gen" /I "$(ESBASEDIR)/is" /I "$(ESBASEDIR)/pm" /I "$(ESBASEDIR)/../interf/exph" /I "$(ESBASEDIR)/base/stg" /I "$(ESBASEDIR)/base/AdoHelp" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "WIN32" /D "PMDLL_IMPLEMENTATION" /D "AUTO_INCLUDE_SMARTS" /FR /Yu"PmDllAfx.h" /c
					///
					/// The following options are handled:
					///
					///		- /I defines include folders
					///		- /D defines preprocessor symbols
					///		- /MD defines _MT and _DLL
					///		- /MDd Defines _DEBUG, _MT, and _DLL  
					///		- /MLd Defines _DEBUG  
					///		- /MT Defines _MT  
					///		- /MTd Defines _DEBUG and _MT.  
					///
					/// \param	sLine						The source line to parse.
					/// \param	rsAdditionalIncludeFolders	A reference to a string which will hold any additional
					///										include folder definitions within the line on exit.
					/// \param	rsPreprocessorDefinitions	A reference to a string which will hold any preprocessor
					///										directives within the line on exit.
					/// \return								\em true if the line was processed correctly; \em false otherwise.
					///
					bool ParseCppConfiguration(	const CString& sLine,
												CString& rsAdditionalIncludeFolders,
												CString& rsPreprocessorDefinitions) const
					{
						CString sTemp = Utils::After(sLine, _T("CPP") );

						while (!sTemp.IsEmpty() )
						{
							CString sNextDirective = Utils::Before(sTemp.Mid(1), _T(" /") );

							sTemp = Utils::After(sTemp, sNextDirective);
							sNextDirective.TrimRight();

							const CString sDirective	= Utils::Before(sNextDirective, _T(" ") );
							sNextDirective.Trim(_T("\"") );

							CString sValue		= Utils::After(sNextDirective, sDirective);;
							sValue.Trim();
							sValue.Trim(_T("\"") );

							if (0 == sDirective.CompareNoCase( _T("/I") ) )
							{
								rsAdditionalIncludeFolders += sValue + _T(";");
							}
							else if (0 == sDirective.CompareNoCase( _T("/D") ) )
							{
								AddDefinitionIfNotPresent(rsPreprocessorDefinitions, sValue);
							}
							else if (0 == sDirective.CompareNoCase( _T("/MD") ) )
							{
								// /MD defines _MT and _DLL
								AddDefinitionIfNotPresent(rsPreprocessorDefinitions, _T("_MT") );
								AddDefinitionIfNotPresent(rsPreprocessorDefinitions, _T("_DLL") );
							}
							else if (0 == sDirective.CompareNoCase( _T("/MDd") ) )
							{
								// /MDd Defines _DEBUG, _MT, and _DLL
								AddDefinitionIfNotPresent(rsPreprocessorDefinitions, _T("_DEBUG") );
								AddDefinitionIfNotPresent(rsPreprocessorDefinitions, _T("_MT") );
								AddDefinitionIfNotPresent(rsPreprocessorDefinitions, _T("_DLL") );
							}
							else if (0 == sDirective.CompareNoCase( _T("/MLd") ) )
							{
								// /MLd Defines _DEBUG  
								AddDefinitionIfNotPresent(rsPreprocessorDefinitions, _T("_DEBUG") );
							}
							else if (0 == sDirective.CompareNoCase( _T("/MT") ) )
							{
								// /MT Defines _MT 
								AddDefinitionIfNotPresent(rsPreprocessorDefinitions, _T("_MT") );
							}
							else if (0 == sDirective.CompareNoCase( _T("/MTd") ) )
							{
								// /MTd Defines _DEBUG and _MT 
								AddDefinitionIfNotPresent(rsPreprocessorDefinitions, _T("_DEBUG") );
								AddDefinitionIfNotPresent(rsPreprocessorDefinitions, _T("_MT") );
							}
							else if (0 == sDirective.CompareNoCase( _T("/GR") ) )
							{
								// /GR Defines _CPPRTTI
								AddDefinitionIfNotPresent(rsPreprocessorDefinitions, _T("_CPPRTTI") );
							}
							else if (_T("/u") == sDirective)	// Case sensitive
							{
								// /u undefines all project symbols - mark its presence using $(NOINHERIT)
								AddDefinitionIfNotPresent(rsPreprocessorDefinitions, NOINHERIT);
							}
						}

						ATLVERIFY(ExpandEnvironmentVars(rsAdditionalIncludeFolders) );

						rsAdditionalIncludeFolders.TrimRight( _T(";") );
						rsPreprocessorDefinitions.TrimRight( _T(";") );

						return true;
					}


					/// \brief Implementation method to add the given definition to the given string if not already present.
					///
					/// \param	rsPreprocessorDefinitions		A reference to a string which will receive the preprocessor directives.
					/// \param	sDefinitionToAdd				A string containing the preprocessor directive to add.
					///
					static void AddDefinitionIfNotPresent(CString& rsPreprocessorDefinitions, const CString& sDefinitionToAdd)
					{
						if (rsPreprocessorDefinitions.Find(sDefinitionToAdd + _T(";") ) < 0)
						{
							rsPreprocessorDefinitions += sDefinitionToAdd + _T(";");
						}
					}


					/*	TODO: Handle per-file configurations
						
						e.g.:
							# Begin Source File
							
								SOURCE=.\configtest.cpp
								!IF  "$(CFG)" == "configtest - Win32 Release"
								!ELSEIF  "$(CFG)" == "configtest - Win32 Debug"
									# ADD CPP /I "..\\" /I ".\Include" /D "FOOBLE" /D "_BOOBLE" /D "_BLETCH"
								!ENDIF 
					*/

			};


		};	// namespace AddInSolutionModel
	};	// namespace Libraries
};	//namespace Riverblade

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Founder Riverblade Limited
United Kingdom United Kingdom
I haven't always written software for a living. When I graduated from Surrey University in 1989, it was with an Electronic Engineering degree, but unfortunately that never really gave me the opportunity to do anything particularly interesting (with the possible exception of designing Darth Vader's Codpiece * for the UK Army in 1990).
    * Also known as the Standard Army Bootswitch. But that's another story...
Since the opportunity arose to lead a software team developing C++ software for Avionic Test Systems in 1996, I've not looked back. More recently I've been involved in the development of subsea acoustic navigation systems, digital TV broadcast systems, port security/tracking systems, and most recently software development tools with my own company, Riverblade Ltd.

One of my personal specialities is IDE plug-in development. ResOrg was my first attempt at a plug-in, but my day to day work is with Visual Lint, an interactive code analysis tool environment with works within the Visual Studio and Eclipse IDEs or on build servers.

I love lots of things, but particularly music, photography and anything connected with history or engineering. I despise ignorant, intolerant and obstructive people - and it shows...I can be a bolshy cow if you wind me up the wrong way...Laugh | :laugh:

I'm currently based 15 minutes walk from the beach in Bournemouth on the south coast of England. Since I moved here I've grown to love the place - even if it is full of grockles in Summer!

Written By
Software Developer Riverblade Ltd
United Kingdom United Kingdom
I'm a software developer and/or tester with Riverblade Ltd (www.riverblade.co.uk) developing our core product range including our Visual Lint integration product and Lint Project Professional.

I incorporate a number of technologies into a daily basis including Windows API, C++ (VS2008), Managed C++, CLI, Databases, Java, JNI, Eclipse Framework, CDT and of course Visual Studio Extensibility (VSIP VSX).

In my spare time I enjoy cooking (prepping ingredients from scratch!), running, cycling, swimming, reading, interested in experimental electronic music (such as ClockDVA), movies, volunteering my IT skills where I can.

Comments and Discussions