/*++

Copyright (c) 1998  Microsoft Corporation

Module Name:

    ohcmp.cpp

Abstract:

    This module reports the differences between two oh output files.

Author:

    Matt Bandy (t-mattba) 23-Jul-1998

Revision History:

    24-Jul-1998     t-mattba
        
        Modified module to conform to coding standards.
        
    11-Jun-2001     silviuc
    
        Deal with handles that are recreated with a different value
        and other simple output improvements (sorted output etc.).    
    
--*/

#include <windows.h>
#include <common.ver>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <tchar.h>
#include "MAPSTRINGINT.h"

LPTSTR HelpText =
    TEXT("ohcmp - Display difference between two OH output files --") BUILD_MACHINE_TAG TEXT("\n")
    VER_LEGALCOPYRIGHT_STR TEXT("\n") 
    TEXT("                                                                       \n")
    TEXT("ohcmp [OPTION ...] BEFORE_OH_FILE AFTER_OH_FILE                        \n")
    TEXT("                                                                       \n")
    TEXT("/h     Print most interesting increases in a separate initial section. \n")
    TEXT("/t     Do not add TRACE id to the names if files contain traces.       \n")
    TEXT("/all   Report decreases as well as increases.                          \n")
    TEXT("                                                                       \n")
    TEXT("If the OH files have been created with -h option (they contain traces) \n")
    TEXT("then ohcmp will print Names having this syntax: (TRACEID) NAME.        \n")
    TEXT("In case of a potential leak just search for the TRACEID in the original\n")
    TEXT("OH file to find the stack trace.                                       \n")
    TEXT("                                                                       \n");

LPTSTR
SearchStackTrace (
    LPTSTR FileName,
    LPTSTR TraceId
    );

PSTRINGTOINTASSOCIATION
MAPSTRINGTOINT::GetStartPosition(
    VOID
    )
/*++

Routine Description:

    This routine retrieves the first association in the list for iteration with the
    MAPSTRINGTOINT::GetNextAssociation function.
    
Arguments:

    None.

Return value:

    The first association in the list, or NULL if the map is empty.

--*/

{
    return Associations;
}


VOID
MAPSTRINGTOINT::GetNextAssociation(
    IN OUT PSTRINGTOINTASSOCIATION & Position,
    OUT LPTSTR & Key,
    OUT LONG & Value)
/*++

Routine Description:

    This routine retrieves the data for the current association and sets Position to
    point to the next association (or NULL if this is the last association.)
    
Arguments:

    Position - Supplies the current association and returns the next association.
    
    Key - Returns the key for the current association.

    Value - Returns the value for the current association.

Return value:

    None.

--*/

{
    Key = Position->Key;
    Value = Position->Value;
    Position = Position->Next;
}


MAPSTRINGTOINT::MAPSTRINGTOINT(
    )
/*++

Routine Description:

    This routine initializes a MAPSTRINGTOINT to be empty.
    
Arguments:

    None.

Return value:

    None.

--*/

{
    Associations = NULL;
}


MAPSTRINGTOINT::~MAPSTRINGTOINT(
    )
/*++

Routine Description:

    This routine cleans up memory used by a MAPSTRINGTOINT.
    
Arguments:

    None.
    
Return value:

    None.

--*/

{
    PSTRINGTOINTASSOCIATION Deleting;
    
    // clean up associations
    
    while (Associations != NULL) {
        
        // save pointer to first association
        
        Deleting = Associations;
        
        // remove first association from list
        
        Associations = Deleting->Next;
        
        // free removed association
        
        free (Deleting->Key);
        delete Deleting;
    }
}


LONG & 
MAPSTRINGTOINT::operator [] (
    IN LPTSTR Key
    )
/*++

Routine Description:

    This routine retrieves an l-value for the value associated with a given key.
    
Arguments:

    Key - The key for which the value is to be retrieved.

Return value:

    A reference to the value associated with the provided key.

--*/

{
    PSTRINGTOINTASSOCIATION CurrentAssociation = Associations;

    // search for key
    while(CurrentAssociation != NULL) {
        
        if(!_tcscmp(CurrentAssociation->Key, Key)) {
            
            // found key, return value
            
            return CurrentAssociation->Value;
            
        }
            
        CurrentAssociation = CurrentAssociation->Next;
        
    }
    
    // not found, create new association    
    
    CurrentAssociation = new STRINGTOINTASSOCIATION;
    
    if (CurrentAssociation == NULL) {
        
        _tprintf(_T("Memory allocation failure\n"));
        exit (0);
    }

    if (Key == NULL) {
        _tprintf(_T("Null object name\n"));
        exit (0);
    }
    else if (_tcscmp (Key, "") == 0) {
        _tprintf(_T("Invalid object name `%s'\n"), Key);
        exit (0);
    }

    CurrentAssociation->Key = _tcsdup(Key);
    
    if (CurrentAssociation->Key == NULL) {
        
        _tprintf(_T("Memory string allocation failure\n"));
        exit (0);
    }

    // add association to front of list
    
    CurrentAssociation->Next = Associations;
    Associations = CurrentAssociation;
    
    // return value for new association
    
    return CurrentAssociation->Value;
}


BOOLEAN
MAPSTRINGTOINT::Lookup(
    IN LPTSTR Key,
    OUT LONG & Value
    )
    
/*++

Routine Description:

    This routine retrieves an r-value for the value association with a given key.
    
Arguments:

    Key - The key for which the associated value is to be retrieved.

    Value - Returns the value associated with Key if Key is present in the map.

Return value:

    TRUE if the key is present in the map, FALSE otherwise.

--*/

{
    
    PSTRINGTOINTASSOCIATION CurrentAssociation = Associations;
    
    // search for key
    
    while (CurrentAssociation != NULL) {
        
        if(!_tcscmp(CurrentAssociation->Key , Key)) {

            // found key, return it
            
            Value = CurrentAssociation->Value;
            
            return TRUE;
            
        }
        
        CurrentAssociation = CurrentAssociation->Next;
        
    }
    
    // didn't find it
    return FALSE;
}


BOOLEAN
PopulateMapsFromFile(
    IN LPTSTR FileName,
    OUT MAPSTRINGTOINT & TypeMap,
    OUT MAPSTRINGTOINT & NameMap,
    BOOLEAN FileWithTraces
    )
/*++

Routine Description:

This routine parses an OH output file and fills two maps with the number of handles of
each type and the number of handles to each named object.

Arguments:

    FileName - OH output file to parse.

    TypeMap - Map to fill with handle type information.

    NameMap - Map to fill with named object information.

Return value:

    TRUE if the file was successfully parsed, FALSE otherwise.
    
--*/

{
    LONG HowMany;
    LPTSTR Name, Type, Process, Pid;
    LPTSTR NewLine;
    TCHAR LineBuffer[512];
    TCHAR ObjectName[512];
    TCHAR TypeName[512];
    FILE *InputFile;
    ULONG LineNumber;

    BOOLEAN rc;

    LineNumber = 0;

    // open file
    
    InputFile = _tfopen(FileName, _T("rt"));

    if (InputFile == NULL) {
        
        _ftprintf(stderr, _T("Error opening oh file %s.\n"), FileName);
        return FALSE;
        
    }

    rc = TRUE;
    
    // loop through lines in oh output
    
    while (_fgetts(LineBuffer, sizeof(LineBuffer), InputFile)
        && !( feof(InputFile) || ferror(InputFile) ) ) {
        
        LineNumber += 1;

        // trim off newline
        
        if((NewLine = _tcschr(LineBuffer, _T('\n'))) != NULL) {
            *NewLine = _T('\0');
        }

        // ignore lines that start with white space or are empty.
        if (LineBuffer[0] == _T('\0') ||
            LineBuffer[0] == _T('\t') || 
            LineBuffer[0] == _T(' ')) {
           continue;
        }

        // ignore lines that start with a comment
        if( LineBuffer[0] == _T('/') && LineBuffer[1] == _T('/') ) {
           continue;
        }

        // skip pid
        
        if((Pid = _tcstok(LineBuffer, _T(" \t"))) == NULL) {
            rc = FALSE;
            break;
        }

        // skip process name
        
        if((Process = _tcstok(NULL, _T(" \t"))) == NULL) {
            rc = FALSE;
            break;
        }

        // Type points to the type of handle
        
        if ((Type = _tcstok(NULL, _T(" \t"))) == NULL) {
            rc = FALSE;
            break;
        }

        // HowMany = number of previous handles with this type
        
        _stprintf (TypeName, 
                   TEXT("<%s/%s/%s>"),
                   Process,
                   Pid,
                   Type);

        if (TypeMap.Lookup(TypeName, HowMany) == FALSE) {
            HowMany = 0;
        }

        // add another handle of this type
        TypeMap[TypeName] = (HowMany + 1);
        
        //
        // Name points to the name. These are magic numbers based on the way
        // OH formats output. The output is a little bit different if the
        // `-h' option of OH was used (this dumps stack traces too).
        //

        Name = LineBuffer + 39 + 5;

        if (FileWithTraces) {
            Name += 7;
        }

        if (_tcscmp (Name, "") == 0) {

            _stprintf (ObjectName, 
                       TEXT("<%s/%s/%s>::<<noname>>"),
                       Process,
                       Pid,
                       Type);
        }
        else {

            _stprintf (ObjectName, 
                       TEXT("<%s/%s/%s>::%s"),
                       Process,
                       Pid,
                       Type,
                       Name);
        }

        // HowMany = number of previous handles with this name
        
        // printf("name --> `%s' \n", ObjectName);

        if (NameMap.Lookup(ObjectName, HowMany) == FALSE) {
            HowMany = 0;
        }

        // add another handle with this name and read the next line
        // note -- NameMap[] is a class operator, not an array.

        NameMap[ObjectName] = (HowMany + 1);
    }

    // done, close file
    
    fclose(InputFile);

    return rc;
}


int
__cdecl
KeyCompareAssociation (
    const void * Left,
    const void * Right
    )
{
    PSTRINGTOINTASSOCIATION X;
    PSTRINGTOINTASSOCIATION Y;

    X = (PSTRINGTOINTASSOCIATION)Left;
    Y = (PSTRINGTOINTASSOCIATION)Right;

    return _tcscmp (X->Key, Y->Key);
}


int
__cdecl
ValueCompareAssociation (
    const void * Left,
    const void * Right
    )
{
    PSTRINGTOINTASSOCIATION X;
    PSTRINGTOINTASSOCIATION Y;

    X = (PSTRINGTOINTASSOCIATION)Left;
    Y = (PSTRINGTOINTASSOCIATION)Right;

    return Y->Value - X->Value;
}


VOID 
PrintIncreases(
    IN MAPSTRINGTOINT & BeforeMap,
    IN MAPSTRINGTOINT & AfterMap,
    IN BOOLEAN ReportIncreasesOnly,
    IN BOOLEAN PrintHighlights,
    IN LPTSTR AfterLogName
    )
/*++

Routine Description:

This routine compares two maps and prints out the differences between them.

Arguments:

    BeforeMap - First map to compare.

    AfterMap - Second map to compare.

    ReportIncreasesOnly - TRUE for report only increases from BeforeMap to AfterMap, 
                          FALSE for report all differences.

Return value:

    None.
    
--*/

{
    PSTRINGTOINTASSOCIATION Association = NULL;
    LONG HowManyBefore = 0;
    LONG HowManyAfter = 0;
    LPTSTR Key = NULL;
    PSTRINGTOINTASSOCIATION SortBuffer;
    ULONG SortBufferSize;
    ULONG SortBufferIndex;
    
    //
    // Loop through associations in map and figure out how many output lines
    // we will have.
    //
    
    SortBufferSize = 0;

    for (Association = AfterMap.GetStartPosition(),
         AfterMap.GetNextAssociation(Association, Key, HowManyAfter);
         Association != NULL;
         AfterMap.GetNextAssociation(Association, Key, HowManyAfter)) {
            
        // look up value for this key in BeforeMap
        if(BeforeMap.Lookup(Key, HowManyBefore) == FALSE) {
            
            HowManyBefore = 0;
            
        }

        // should we report this?
        if((HowManyAfter > HowManyBefore) || 
            ((!ReportIncreasesOnly) && (HowManyAfter != HowManyBefore))) {
                
            SortBufferSize += 1;
            
        }
    }
    
    //
    // Loop through associations in map again this time filling the output buffer.
    //
    
    SortBufferIndex = 0;

    SortBuffer = new STRINGTOINTASSOCIATION[SortBufferSize];

    if (SortBuffer == NULL) {
        _ftprintf(stderr, _T("Failed to allocate internal buffer of %u bytes.\n"), 
                  SortBufferSize);
        return;
    }

    for (Association = AfterMap.GetStartPosition(),
         AfterMap.GetNextAssociation(Association, Key, HowManyAfter);
         Association != NULL;
         AfterMap.GetNextAssociation(Association, Key, HowManyAfter)) {
            
        // look up value for this key in BeforeMap
        if(BeforeMap.Lookup(Key, HowManyBefore) == FALSE) {
            
            HowManyBefore = 0;
        }

        // should we report this?
        if((HowManyAfter > HowManyBefore) || 
            ((!ReportIncreasesOnly) && (HowManyAfter != HowManyBefore))) {
                
            ZeroMemory (&(SortBuffer[SortBufferIndex]), 
                        sizeof (STRINGTOINTASSOCIATION));

            SortBuffer[SortBufferIndex].Key = Key;
            SortBuffer[SortBufferIndex].Value = HowManyAfter - HowManyBefore;
            SortBufferIndex += 1;
        }
    }

    //
    // Sort the output buffer using the Key.
    //

    if (PrintHighlights) {

        qsort (SortBuffer,
               SortBufferSize,
               sizeof (STRINGTOINTASSOCIATION),
               ValueCompareAssociation);
    }
    else {

        qsort (SortBuffer,
               SortBufferSize,
               sizeof (STRINGTOINTASSOCIATION),
               KeyCompareAssociation);
    }

    //
    // Dump the buffer.
    //

    for (SortBufferIndex = 0; SortBufferIndex < SortBufferSize; SortBufferIndex += 1) {
        
        if (PrintHighlights) {

            if (SortBuffer[SortBufferIndex].Value >= 1) {

                TCHAR TraceId[7];
                LPTSTR Start;

                _tprintf(_T("%d\t%s\n"), 
                         SortBuffer[SortBufferIndex].Value,
                         SortBuffer[SortBufferIndex].Key);

                Start = _tcsstr (SortBuffer[SortBufferIndex].Key, "(");

                if (Start == NULL) {
                    
                    TraceId[0] = 0;
                }
                else {

                    _tcsncpy (TraceId,
                              Start,
                              6);

                    TraceId[6] = 0;
                }

                _tprintf (_T("%s"), SearchStackTrace (AfterLogName, TraceId));
            }
        }
        else {

            _tprintf(_T("%d\t%s\n"), 
                     SortBuffer[SortBufferIndex].Value,
                     SortBuffer[SortBufferIndex].Key);
        }
    }

    //
    // Clean up memory.
    //

    if (SortBuffer) {
        delete[] SortBuffer;
    }
}


VOID 
PrintUsage(
    VOID
    )
/*++

Routine Description:

This routine prints out a message describing the proper usage of OHCMP.

Arguments:

    None.

Return value:

    None.
    
--*/
{
    _ftprintf (stderr, HelpText);
}


LONG _cdecl
_tmain(
    IN LONG argc,
    IN LPTSTR argv[]
    )
    
/*++

Routine Description:

This routine parses program arguments, reads the two input files, and prints out the
differences.

Arguments:

    argc - Number of command-line arguments.

    argv - Command-line arguments.

Return value:

    0 if comparison is successful, 1 otherwise.
    
--*/

{
    
    try {
    
        MAPSTRINGTOINT TypeMapBefore, TypeMapAfter;
        MAPSTRINGTOINT NameMapBefore, NameMapAfter;
        LPTSTR BeforeFileName=NULL;
        LPTSTR AfterFileName=NULL;
        BOOLEAN ReportIncreasesOnly = TRUE;
        BOOLEAN Interpreted = FALSE;
        BOOLEAN Result;
        BOOLEAN FileWithTraces;
        BOOLEAN PrintHighlights;

        // parse arguments

        FileWithTraces = FALSE;
        PrintHighlights = FALSE;

        for (LONG n = 1; n < argc; n++) {

            Interpreted = FALSE;

            switch(argv[n][0]) {

            case _T('-'):
            case _T('/'):

                // the argument is a switch

                if(_tcsicmp(argv[n]+1, _T("all")) == 0) {

                    ReportIncreasesOnly = FALSE;
                    Interpreted = TRUE;

                }
                else if (_tcsicmp(argv[n]+1, _T("t")) == 0) {

                    FileWithTraces = TRUE;
                    Interpreted = TRUE;
                }
                else if (_tcsicmp(argv[n]+1, _T("h")) == 0) {

                    PrintHighlights = TRUE;
                    Interpreted = TRUE;
                }

                break;

            default:

                // the argument is a file name

                if(BeforeFileName == NULL) {

                    BeforeFileName = argv[n];
                    Interpreted = TRUE;

                } else {

                    if(AfterFileName == NULL) {

                        AfterFileName = argv[n];
                        Interpreted = TRUE;

                    } else {

                        // too many file arguments
                        PrintUsage();
                        return 1;

                    }

                }

                break;
            }

            if(!Interpreted) {

                // user specified a bad argument
                PrintUsage();
                return 1;

            }
        }

        // did user specify required arguments?

        if((BeforeFileName == NULL) || (AfterFileName == NULL))
        {

            PrintUsage();
            return 1;

        }

        // read oh1 file

        Result = PopulateMapsFromFile (BeforeFileName, 
                                       TypeMapBefore, 
                                       NameMapBefore,
                                       FileWithTraces);

        if(Result == FALSE) {

            _ftprintf(stderr, _T("Failed to read first OH output file.\n"));
            return 1;
        }

        // read oh2 file

        Result = PopulateMapsFromFile (AfterFileName, 
                                       TypeMapAfter, 
                                       NameMapAfter,
                                       FileWithTraces);

        if(Result == FALSE) {

            _ftprintf(stderr, _T("Failed to read second OH output file.\n"));
            return 1;

        }

        // print out increases by handle name

        if (PrintHighlights) {

            _putts (TEXT ("\n")
                    TEXT("//                                              \n")
                    TEXT("// Possible leaks (DELTA <PROCESS/PID/TYPE>::NAME):  \n")
                    TEXT("//                                              \n")
                    TEXT("// Note that the NAME can appear as `(TRACEID) NAME' if output \n")
                    TEXT("// is generated by comparing OH files containing traces. In this case  \n")
                    TEXT("// just search in the `AFTER' OH log file for the trace id to \n")
                    TEXT("// find the stack trace creating the handle possibly leaked. \n")
                    TEXT("//                                              \n\n"));

            PrintIncreases (NameMapBefore, 
                            NameMapAfter, 
                            ReportIncreasesOnly,
                            TRUE,
                            AfterFileName);
        }

        // print out increases by handle type

        _putts (TEXT ("\n")
                TEXT("//                                              \n")
                TEXT("// Handle types (DELTA <PROCESS/PID/TYPE>):     \n")
                TEXT("//                                              \n")
                TEXT("// DELTA is the additional number of handles found in the `AFTER' log. \n")
                TEXT("// PROCESS is the process name having a handle increase.        \n")
                TEXT("// PID is the process PID having a handle increase.   \n")
                TEXT("// TYPE is the type of the handle               \n")
                TEXT("//                                              \n\n"));
        
        PrintIncreases (TypeMapBefore, 
                        TypeMapAfter, 
                        ReportIncreasesOnly, 
                        FALSE,
                        NULL);

        // print out increases by handle name

        _putts (TEXT ("\n")
                TEXT("//                                              \n")
                TEXT("// Objects (named and anonymous) (DELTA <PROCESS/PID/TYPE>::NAME):  \n")
                TEXT("//                                              \n")
                TEXT("// DELTA is the additional number of handles found in the `AFTER' log. \n")
                TEXT("// PROCESS is the process name having a handle increase.        \n")
                TEXT("// PID is the process PID having a handle increase.   \n")
                TEXT("// TYPE is the type of the handle               \n")
                TEXT("// NAME is the name of the handle. Anonymous handles appear with name <<noname>>.\n")
                TEXT("//                                              \n")
                TEXT("// Note that the NAME can appear as `(TRACEID) NAME' if output \n")
                TEXT("// is generated by comparing OH files containing traces. In this case  \n")
                TEXT("// just search in the `AFTER' OH log file for the trace id to \n")
                TEXT("// find the stack trace creating the handle possibly leaked. \n")
                TEXT("//                                              \n\n"));
        
        PrintIncreases (NameMapBefore, 
                        NameMapAfter, 
                        ReportIncreasesOnly,
                        FALSE,
                        NULL);

        return 0;
        
    } catch (...) {

        // this is mostly intended to catch out of memory conditions
        
        _tprintf(_T("\nAn exception has been detected.  OHCMP aborted.\n"));
        return 1;
        
    }
    
}


/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////

TCHAR StackTraceBuffer [0x10000];

LPTSTR
SearchStackTrace (
    LPTSTR FileName,
    LPTSTR TraceId
    )
{
    TCHAR LineBuffer[512];
    FILE *InputFile;

    StackTraceBuffer[0] = 0;

    //
    // Open file.
    //
    
    InputFile = _tfopen(FileName, _T("rt"));

    if (InputFile == NULL) {
        
        _ftprintf(stderr, _T("Error opening oh file %s.\n"), FileName);
        return NULL;
    }
    
    //
    // Loop through lines in oh output.
    //
    
    while (_fgetts(LineBuffer, sizeof(LineBuffer), InputFile)
        && !( feof(InputFile) || ferror(InputFile) ) ) {
        
        //
        // Skip line if it does not contain trace ID.
        //

        if (_tcsstr (LineBuffer, TraceId) == NULL) {
            continue;
        }

        //
        // We have got a trace ID. We need now to copy everything
        // to a trace buffer until we get a line containing a character
        // in column zero.
        //

        while (_fgetts(LineBuffer, sizeof(LineBuffer), InputFile)
               && !( feof(InputFile) || ferror(InputFile) ) ) {

            if (LineBuffer[0] == _T(' ') ||
                LineBuffer[0] == _T('\0') ||
                LineBuffer[0] == _T('\n') ||
                LineBuffer[0] == _T('\t')) {

                _tcscat (StackTraceBuffer, LineBuffer);
            }
            else {

                break;
            }
        }

        break;
    }

    //
    // Close file.
    
    fclose(InputFile);

    return StackTraceBuffer;
}