Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / MFC

JesInclAnalyzer

4.81/5 (9 votes)
11 Mar 2009CPOL5 min read 20.5K   214  
Include Analyzer (Unwanted header file inclusion removal)
Image 1

Introduction

JesInclAnalyzer is a simple application designed to filter out unwanted header files included in a C++ source file. I do not claim that this is the best solution for this particular problem. The basic idea is to compile each source file after commenting included header files in source, one by one, and to check the compiler output to determine if a particular header file is needed or not. Techniques used for the implementation are simple file operations and text processing. But it gives a fair result, I believe.

Background

As projects go through many phases, normally, its size increases. As new engineers come in and update source or add new features, they may add all header files which were used to add traditionally. In fact all those headers may not be needed. After a long time, build time for such modules will increase considerably and will tease developers by taking away major portion of productive time. I guess, even waiting 15 to 20 minutes to complete the build will not be a pleasant experience. The situation becomes worse when one is asked to fix a bug, urgently.

I was checking for solutions of this issue. I came across some commercial tools like Gimpel, Pro-Factor IncludeManager, etc. Another source of information was a paper submitted by AT&T Lab engineers; "Incl: A tool to analyze include files". This paper directly handles this problem; but for "C". However, the algorithm they proposed may be used for implementation. They have used a Program Database from AT&T Labs, called CIA (C Information Abstractor) which will provide sufficient information for "incl". I started analyzing if Program DataBase(PDB) generated by VC++ 6.0 compiler can provide same kind of information that "incl" needs. I needed an API set to get access to PDB files which I could achieve using DIA (Debug Interface Access) SDK that comes with Visual Studio 2005. I had to register "msdia80.dll" and modify some header files of this SDK so that I can use that in my development machine which is Windows 2000 (and VC++ 6.0 SP5). I could parse PDB and get information on all symbols and source files. But I could not find a way to get information about header files, global constants, etc. from PDB (PDB is generated by Post-compiler phase, I think). Also, pre-processor will expand all header files in its phase.

I could find another paper related to C++ Program Database from AT&T Labs. CIA++ is an enhancement to CIA; Program Database for C++. I analyzed that possibility also; i.e., to implement CIA++ algorithm and prepare input for "incl". But this also did not work for me due to lack of sufficient implementation details documentation or due to my lack of knowledge. Finally, I decided to go for a straighter method; that is to iterate through compilations after commenting each header file included in source files.

Using the Tool/Code

The tool (JesInclAnalyzer) can be used as such. The tool requires that environment be set for "CL (VC++ 6.0 SP5 compiler)" to be able to execute from command line. Browse project folder containing source files and select command file for "CL". Command file should contain flags required for compilation and other parameters required by compiler. Click on "Analyze" button. The tool will analyze source files and will create copies of updated files with suffix "_Copy". Processing status will be updated in main dialog. Please note that the tool will not process "Read-only" files. It will give "Access denied" error message for "Read-only" files.

At first, the tool creates a list of source files available in given path with the help of JesCPPIterator class. Method bool JesCPPIterator::PopulateFileList( const CString& csPath_i ) uses CFileFind to create list of source files. Source files are kept in CStringArray member and method bool JesCPPIterator::GetNextFile( CString& csSrcFile_o ) returns filenames one by one. I have no good reason to give you why I did not derive this class from CStringArray instead of wrapping it up. What I did may not be a good design.

Class JesFileProcessor creates copies of source files and is responsible for updating files; like commenting header file includes one by one, reverting changes, deleting unwanted copies, etc.

Class JesCompilerProxy is responsible for communicating with compiler. Constructor of this class prepares pipe to be set as standard output and standard error of compiler. Method bool JesCompilerProxy::Compile() creates compiler process after setting standard output and standard error handles with custom pipe handle. This code may be of some interest though it is not a complex or rare thing.

C++
JesCompilerProxy::JesCompilerProxy( const CString& csCompiland_i )
                  : m_bObjCreated( true ),
                    m_bOPFileNeeded( false )
{
    m_hReadPipe = 0;
    m_hWritePipe = 0;
    m_csCompiland = csCompiland_i;

    HANDLE hTmpReadPipe;
    SECURITY_ATTRIBUTES stSecAttribs;
    stSecAttribs.nLength = sizeof( SECURITY_ATTRIBUTES );
    stSecAttribs.bInheritHandle = TRUE;
    stSecAttribs.lpSecurityDescriptor = 0;
    if( !CreatePipe( &hTmpReadPipe, &m_hWritePipe, &stSecAttribs, BUFF_SIZE ))
    {
        AfxMessageBox( _T( "JesCompilerProxy creation failed..." ));
        CloseHandles();
        m_bObjCreated = false;
        return;
    }
    // Create duplicate Non-inheritable Read Pipe
    if( !DuplicateHandle( GetCurrentProcess(), hTmpReadPipe,
                          GetCurrentProcess(), &m_hReadPipe,
                          0, FALSE, DUPLICATE_SAME_ACCESS ))
    {
        AfxMessageBox( _T( "JesCompilerProxy creation failed..." ));
        CloseHandles();
        m_bObjCreated = false;
        return;
    }
    CloseHandle( hTmpReadPipe );
}

JesCompilerProxy::~JesCompilerProxy()
{
    CloseHandles();
}

bool JesCompilerProxy::Compile()
{
    if( !m_bObjCreated )
    {
        return false;
    }

    STARTUPINFO stStartupInfo;
    ::ZeroMemory( &stStartupInfo, sizeof( STARTUPINFO ));
    stStartupInfo.cb = sizeof( STARTUPINFO );
    stStartupInfo.dwFlags = STARTF_USESTDHANDLES;
    stStartupInfo.hStdOutput = m_hWritePipe;
    stStartupInfo.hStdError = m_hWritePipe;

    PROCESS_INFORMATION stProcInfo;
    ::ZeroMemory( &stProcInfo, sizeof( PROCESS_INFORMATION ));

    CString csCmdLine( "cl.exe /c " );
    if( !m_csCLCmdFile.IsEmpty())
    {
        static const CString CMD_FILE_TAG = _T( "@" );
        static const CString PARAMETER_DELIMITER = _T( " " );
        csCmdLine += CMD_FILE_TAG + m_csCLCmdFile + PARAMETER_DELIMITER;
    }
    csCmdLine += m_csCompiland;
    if( !CreateProcess( 0, csCmdLine.GetBuffer( csCmdLine.GetLength()),
                        0, 0, TRUE, CREATE_NO_WINDOW, 0, 0,
                        &stStartupInfo, &stProcInfo ))
    {
        AfxMessageBox( _T( "CreateProcess() failed..." ));
        csCmdLine.ReleaseBuffer();
        return false;
    }
    csCmdLine.ReleaseBuffer();

    CString& csResult = ReadResultInPipe();
    // Dump compiler output
    if( m_bOPFileNeeded )
    {
        DumpResult( csResult );
    }

    static const CString csErrMsg( _T( " error " ));
    static const CString csWarnMsg( _T( " warning " ));
    if( ( -1 != csResult.Find( csErrMsg, 0 )))// || 
			( -1 != csResult.Find( csWarnMsg, 0 )))
    {
        return false;
    }

    return true;
}

Method CString JesCompilerProxy::ReadResultInPipe() reads and returns output of compiler from pipe with the code shown below:

C++
CString JesCompilerProxy::ReadResultInPipe()
{
    // Close Child's write pipe before reading
    CloseHandle( m_hWritePipe );
    m_hWritePipe = 0;

    CString csResult;

    // ReadFile problem in UNICODE build
#ifdef _UNICODE
    char szBuff[ BUFF_SIZE ] = { 0 };
#else
    TCHAR szBuff[ BUFF_SIZE ] = { 0 };
#endif

    DWORD dwReadBytesCnt = 0;
    while( true )
    {
        if( !ReadFile( m_hReadPipe, szBuff, BUFF_SIZE, &dwReadBytesCnt, 0 )
                       || ( 0 == dwReadBytesCnt ))
        {
            return csResult;
        }
        else
        {
            // ReadFile problem in UNICODE build
#ifdef _UNICODE
            WCHAR wszResult[ BUFF_SIZE ] = { 0 };
            if( MultiByteToWideChar( CP_ACP, 0, szBuff, strlen( szBuff ) + 1, 
			wszResult, sizeof( wszResult ) / sizeof( wszResult[ 0 ])))
            {
                csResult += wszResult;
                continue;
            }
            else
            {
                continue;
            }
#endif
            csResult += szBuff;
        }
    }
}

Preparing CL (Compiler) Command Files from Project Settings

Preparing command files for compiler from Visual Studio 6.0 Project settings may not be that much difficult. Please check the steps given below:

  1. Copy "Project Settings"->"C/C++" tab->"Project Options:" to a text file.
  2. Remove flags/switches for Pre-Compilation/PDBs/OBJs/IDE/Optimization (E.g. :- /Fo"Debug/" /Fd"Debug/" /FD).
  3. Add include file paths needed for compilation with /I (Absolute paths).
  4. Please refer CL-Command line options in MSDN for details.

Points of Interest

Initially, Unicode build was not working properly. I found that ReadFile() API does not fill WCHAR buffer properly. I could not find any relevant documentation for this issue. Finally, I made a wild fix. Please check CString JesCompilerProxy::ReadResultInPipe() code to meet that wild fox.

History

  • Version 1.0 - 11-Mar-2009

License

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