| /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * |
| * ***** BEGIN LICENSE BLOCK ***** |
| * Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
| * |
| * The contents of this file are subject to the Mozilla Public License Version |
| * 1.1 (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * http://www.mozilla.org/MPL/ |
| * |
| * Software distributed under the License is distributed on an "AS IS" basis, |
| * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
| * for the specific language governing rights and limitations under the |
| * License. |
| * |
| * The Original Code is maptsvdifftool.c code, released |
| * Oct 3, 2002. |
| * |
| * The Initial Developer of the Original Code is |
| * Netscape Communications Corporation. |
| * Portions created by the Initial Developer are Copyright (C) 2002 |
| * the Initial Developer. All Rights Reserved. |
| * |
| * Contributor(s): |
| * Garrett Arch Blythe, 03-October-2002 |
| * |
| * Alternatively, the contents of this file may be used under the terms of |
| * either the GNU General Public License Version 2 or later (the "GPL"), or |
| * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
| * in which case the provisions of the GPL or the LGPL are applicable instead |
| * of those above. If you wish to allow use of your version of this file only |
| * under the terms of either the GPL or the LGPL, and not to allow others to |
| * use your version of this file under the terms of the MPL, indicate your |
| * decision by deleting the provisions above and replace them with the notice |
| * and other provisions required by the GPL or the LGPL. If you do not delete |
| * the provisions above, a recipient may use your version of this file under |
| * the terms of any one of the MPL, the GPL or the LGPL. |
| * |
| * ***** END LICENSE BLOCK ***** */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <time.h> |
| #include <ctype.h> |
| |
| #define ERROR_REPORT(num, val, msg) fprintf(stderr, "error(%d):\t\"%s\"\t%s\n", (num), (val), (msg)); |
| #define CLEANUP(ptr) do { if(NULL != ptr) { free(ptr); ptr = NULL; } } while(0) |
| |
| |
| typedef struct __struct_Options |
| /* |
| ** Options to control how we perform. |
| ** |
| ** mProgramName Used in help text. |
| ** mInput File to read for input. |
| ** Default is stdin. |
| ** mInputName Name of the file. |
| ** mOutput Output file, append. |
| ** Default is stdout. |
| ** mOutputName Name of the file. |
| ** mHelp Whether or not help should be shown. |
| ** mSummaryOnly Only output a signle line. |
| ** mZeroDrift Output zero drift data. |
| ** mNegation Perform negation heuristics on the symbol drifts. |
| */ |
| { |
| const char* mProgramName; |
| FILE* mInput; |
| char* mInputName; |
| FILE* mOutput; |
| char* mOutputName; |
| int mHelp; |
| int mSummaryOnly; |
| int mZeroDrift; |
| int mNegation; |
| } |
| Options; |
| |
| |
| typedef struct __struct_Switch |
| /* |
| ** Command line options. |
| */ |
| { |
| const char* mLongName; |
| const char* mShortName; |
| int mHasValue; |
| const char* mValue; |
| const char* mDescription; |
| } |
| Switch; |
| |
| #define DESC_NEWLINE "\n\t\t" |
| |
| static Switch gInputSwitch = {"--input", "-i", 1, NULL, "Specify input file." DESC_NEWLINE "stdin is default."}; |
| static Switch gOutputSwitch = {"--output", "-o", 1, NULL, "Specify output file." DESC_NEWLINE "Appends if file exists." DESC_NEWLINE "stdout is default."}; |
| static Switch gSummarySwitch = {"--summary", "-s", 0, NULL, "Only output a single line." DESC_NEWLINE "The cumulative size changes." DESC_NEWLINE "Overrides all other output options."}; |
| static Switch gZeroDriftSwitch = {"--zerodrift", "-z", 0, NULL, "Output zero drift data." DESC_NEWLINE "Reports symbol changes even when there is no net drift."}; |
| static Switch gNegationSwitch = {"--negation", "-n", 0, NULL, "Use negation heuristics." DESC_NEWLINE "When symbol sizes are inferred by offset, order changes cause noise." DESC_NEWLINE "This helps see through the noise by eliminating equal and opposite drifts."}; |
| static Switch gHelpSwitch = {"--help", "-h", 0, NULL, "Information on usage."}; |
| |
| static Switch* gSwitches[] = { |
| &gInputSwitch, |
| &gOutputSwitch, |
| &gSummarySwitch, |
| &gZeroDriftSwitch, |
| &gNegationSwitch, |
| &gHelpSwitch |
| }; |
| |
| |
| typedef struct __struct_SizeComposition |
| /* |
| ** Used to keep which parts positive and negative resulted in the total. |
| */ |
| { |
| int mPositive; |
| int mNegative; |
| } |
| SizeComposition; |
| |
| |
| typedef struct __struct_SizeStats |
| /* |
| ** Keep track of sizes. |
| ** Use signed integers so that negatives are valid, in which case we shrunk. |
| */ |
| { |
| int mCode; |
| SizeComposition mCodeComposition; |
| |
| int mData; |
| SizeComposition mDataComposition; |
| } |
| SizeStats; |
| |
| |
| typedef enum __enum_SegmentClass |
| /* |
| ** What type of data a segment holds. |
| */ |
| { |
| CODE, |
| DATA |
| } |
| SegmentClass; |
| |
| |
| typedef struct __struct_SymbolStats |
| /* |
| ** Symbol level stats. |
| */ |
| { |
| char* mSymbol; |
| int mSize; |
| } |
| SymbolStats; |
| |
| |
| typedef struct __struct_ObjectStats |
| /* |
| ** Object level stats. |
| */ |
| { |
| char* mObject; |
| int mSize; |
| SizeComposition mComposition; |
| SymbolStats* mSymbols; |
| unsigned mSymbolCount; |
| } |
| ObjectStats; |
| |
| |
| typedef struct __struct_SegmentStats |
| /* |
| ** Segment level stats. |
| */ |
| { |
| char* mSegment; |
| SegmentClass mClass; |
| int mSize; |
| SizeComposition mComposition; |
| ObjectStats* mObjects; |
| unsigned mObjectCount; |
| } |
| SegmentStats; |
| |
| |
| typedef struct __struct_ModuleStats |
| /* |
| ** Module level stats. |
| */ |
| { |
| char* mModule; |
| SizeStats mSize; |
| SegmentStats* mSegments; |
| unsigned mSegmentCount; |
| } |
| ModuleStats; |
| |
| |
| static int moduleCompare(const void* in1, const void* in2) |
| /* |
| ** qsort helper. |
| */ |
| { |
| int retval = 0; |
| |
| ModuleStats* one = (ModuleStats*)in1; |
| ModuleStats* two = (ModuleStats*)in2; |
| |
| int oneSize = (one->mSize.mCode + one->mSize.mData); |
| int twoSize = (two->mSize.mCode + two->mSize.mData); |
| |
| if(oneSize < twoSize) |
| { |
| retval = 1; |
| } |
| else if(oneSize > twoSize) |
| { |
| retval = -1; |
| } |
| else |
| { |
| retval = strcmp(one->mModule, two->mModule); |
| if(0 > oneSize && 0 > twoSize) |
| { |
| retval *= -1; |
| } |
| } |
| |
| return retval; |
| } |
| |
| |
| static int segmentCompare(const void* in1, const void* in2) |
| /* |
| ** qsort helper. |
| */ |
| { |
| int retval = 0; |
| |
| SegmentStats* one = (SegmentStats*)in1; |
| SegmentStats* two = (SegmentStats*)in2; |
| |
| if(one->mSize < two->mSize) |
| { |
| retval = 1; |
| } |
| else if(one->mSize > two->mSize) |
| { |
| retval = -1; |
| } |
| else |
| { |
| retval = strcmp(one->mSegment, two->mSegment); |
| if(0 > one->mSize && 0 > two->mSize) |
| { |
| retval *= -1; |
| } |
| } |
| |
| return retval; |
| } |
| |
| |
| static int objectCompare(const void* in1, const void* in2) |
| /* |
| ** qsort helper. |
| */ |
| { |
| int retval = 0; |
| |
| ObjectStats* one = (ObjectStats*)in1; |
| ObjectStats* two = (ObjectStats*)in2; |
| |
| if(one->mSize < two->mSize) |
| { |
| retval = 1; |
| } |
| else if(one->mSize > two->mSize) |
| { |
| retval = -1; |
| } |
| else |
| { |
| retval = strcmp(one->mObject, two->mObject); |
| if(0 > one->mSize && 0 > two->mSize) |
| { |
| retval *= -1; |
| } |
| } |
| |
| return retval; |
| } |
| |
| |
| static int symbolCompare(const void* in1, const void* in2) |
| /* |
| ** qsort helper. |
| */ |
| { |
| int retval = 0; |
| |
| SymbolStats* one = (SymbolStats*)in1; |
| SymbolStats* two = (SymbolStats*)in2; |
| |
| if(one->mSize < two->mSize) |
| { |
| retval = 1; |
| } |
| else if(one->mSize > two->mSize) |
| { |
| retval = -1; |
| } |
| else |
| { |
| retval = strcmp(one->mSymbol, two->mSymbol); |
| if(0 > one->mSize && 0 > two->mSize) |
| { |
| retval *= -1; |
| } |
| } |
| |
| return retval; |
| } |
| |
| |
| void trimWhite(char* inString) |
| /* |
| ** Remove any whitespace from the end of the string. |
| */ |
| { |
| int len = strlen(inString); |
| |
| while(len) |
| { |
| len--; |
| |
| if(isspace(*(inString + len))) |
| { |
| *(inString + len) = '\0'; |
| } |
| else |
| { |
| break; |
| } |
| } |
| } |
| |
| |
| int difftool(Options* inOptions) |
| /* |
| ** Read a diff file and spit out relevant information. |
| */ |
| { |
| int retval = 0; |
| char lineBuffer[0x500]; |
| SizeStats overall; |
| ModuleStats* modules = NULL; |
| unsigned moduleCount = 0; |
| unsigned moduleLoop = 0; |
| ModuleStats* theModule = NULL; |
| unsigned segmentLoop = 0; |
| SegmentStats* theSegment = NULL; |
| unsigned objectLoop = 0; |
| ObjectStats* theObject = NULL; |
| unsigned symbolLoop = 0; |
| SymbolStats* theSymbol = NULL; |
| unsigned allSymbolCount = 0; |
| |
| memset(&overall, 0, sizeof(overall)); |
| |
| /* |
| ** Read the entire diff file. |
| ** We're only interested in lines beginning with < or > |
| */ |
| while(0 == retval && NULL != fgets(lineBuffer, sizeof(lineBuffer), inOptions->mInput)) |
| { |
| trimWhite(lineBuffer); |
| |
| if(('<' == lineBuffer[0] || '>' == lineBuffer[0]) && ' ' == lineBuffer[1]) |
| { |
| int additive = 0; |
| char* theLine = &lineBuffer[2]; |
| int scanRes = 0; |
| int size; |
| char segClass[0x10]; |
| char scope[0x10]; |
| char module[0x100]; |
| char segment[0x40]; |
| char object[0x100]; |
| char* symbol = NULL; |
| |
| /* |
| ** Figure out if the line adds or subtracts from something. |
| */ |
| if('>' == lineBuffer[0]) |
| { |
| additive = __LINE__; |
| } |
| |
| |
| /* |
| ** Scan the line for information. |
| */ |
| scanRes = sscanf(theLine, |
| "%x\t%s\t%s\t%s\t%s\t%s\t", |
| (unsigned*)&size, |
| segClass, |
| scope, |
| module, |
| segment, |
| object); |
| |
| if(6 == scanRes) |
| { |
| SegmentClass segmentClass = DATA; |
| |
| symbol = strrchr(theLine, '\t') + 1; |
| |
| if(0 == strcmp(segClass, "CODE")) |
| { |
| segmentClass = CODE; |
| } |
| else if(0 == strcmp(segClass, "DATA")) |
| { |
| segmentClass = DATA; |
| } |
| else |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, segClass, "Unable to determine segment class."); |
| } |
| |
| if(0 == retval) |
| { |
| unsigned moduleIndex = 0; |
| |
| /* |
| ** Find, in succession, the following things: |
| ** the module |
| ** the segment |
| ** the object |
| ** the symbol |
| ** Failure to find any one of these means to create it. |
| */ |
| |
| for(moduleIndex = 0; moduleIndex < moduleCount; moduleIndex++) |
| { |
| if(0 == strcmp(modules[moduleIndex].mModule, module)) |
| { |
| break; |
| } |
| } |
| |
| if(moduleIndex == moduleCount) |
| { |
| void* moved = NULL; |
| |
| moved = realloc(modules, sizeof(ModuleStats) * (1 + moduleCount)); |
| if(NULL != moved) |
| { |
| modules = (ModuleStats*)moved; |
| moduleCount++; |
| memset(modules + moduleIndex, 0, sizeof(ModuleStats)); |
| |
| modules[moduleIndex].mModule = strdup(module); |
| if(NULL == modules[moduleIndex].mModule) |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, module, "Unable to duplicate string."); |
| } |
| } |
| else |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, inOptions->mProgramName, "Unable to increase module array."); |
| } |
| } |
| |
| if(0 == retval) |
| { |
| unsigned segmentIndex = 0; |
| theModule = (modules + moduleIndex); |
| |
| for(segmentIndex = 0; segmentIndex < theModule->mSegmentCount; segmentIndex++) |
| { |
| if(0 == strcmp(segment, theModule->mSegments[segmentIndex].mSegment)) |
| { |
| break; |
| } |
| } |
| |
| if(segmentIndex == theModule->mSegmentCount) |
| { |
| void* moved = NULL; |
| |
| moved = realloc(theModule->mSegments, sizeof(SegmentStats) * (theModule->mSegmentCount + 1)); |
| if(NULL != moved) |
| { |
| theModule->mSegments = (SegmentStats*)moved; |
| theModule->mSegmentCount++; |
| memset(theModule->mSegments + segmentIndex, 0, sizeof(SegmentStats)); |
| |
| theModule->mSegments[segmentIndex].mClass = segmentClass; |
| theModule->mSegments[segmentIndex].mSegment = strdup(segment); |
| if(NULL == theModule->mSegments[segmentIndex].mSegment) |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, segment, "Unable to duplicate string."); |
| } |
| } |
| else |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, inOptions->mProgramName, "Unable to increase segment array."); |
| } |
| } |
| |
| if(0 == retval) |
| { |
| unsigned objectIndex = 0; |
| theSegment = (theModule->mSegments + segmentIndex); |
| |
| for(objectIndex = 0; objectIndex < theSegment->mObjectCount; objectIndex++) |
| { |
| if(0 == strcmp(object, theSegment->mObjects[objectIndex].mObject)) |
| { |
| break; |
| } |
| } |
| |
| if(objectIndex == theSegment->mObjectCount) |
| { |
| void* moved = NULL; |
| |
| moved = realloc(theSegment->mObjects, sizeof(ObjectStats) * (1 + theSegment->mObjectCount)); |
| if(NULL != moved) |
| { |
| theSegment->mObjects = (ObjectStats*)moved; |
| theSegment->mObjectCount++; |
| memset(theSegment->mObjects + objectIndex, 0, sizeof(ObjectStats)); |
| |
| theSegment->mObjects[objectIndex].mObject = strdup(object); |
| if(NULL == theSegment->mObjects[objectIndex].mObject) |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, object, "Unable to duplicate string."); |
| } |
| } |
| else |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, inOptions->mProgramName, "Unable to increase object array."); |
| } |
| } |
| |
| if(0 == retval) |
| { |
| unsigned symbolIndex = 0; |
| theObject = (theSegment->mObjects + objectIndex); |
| |
| for(symbolIndex = 0; symbolIndex < theObject->mSymbolCount; symbolIndex++) |
| { |
| if(0 == strcmp(symbol, theObject->mSymbols[symbolIndex].mSymbol)) |
| { |
| break; |
| } |
| } |
| |
| if(symbolIndex == theObject->mSymbolCount) |
| { |
| void* moved = NULL; |
| |
| moved = realloc(theObject->mSymbols, sizeof(SymbolStats) * (1 + theObject->mSymbolCount)); |
| if(NULL != moved) |
| { |
| theObject->mSymbols = (SymbolStats*)moved; |
| theObject->mSymbolCount++; |
| allSymbolCount++; |
| memset(theObject->mSymbols + symbolIndex, 0, sizeof(SymbolStats)); |
| |
| theObject->mSymbols[symbolIndex].mSymbol = strdup(symbol); |
| if(NULL == theObject->mSymbols[symbolIndex].mSymbol) |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, symbol, "Unable to duplicate string."); |
| } |
| } |
| else |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, inOptions->mProgramName, "Unable to increase symbol array."); |
| } |
| } |
| |
| if(0 == retval) |
| { |
| theSymbol = (theObject->mSymbols + symbolIndex); |
| |
| /* |
| ** Update our various totals. |
| */ |
| if(additive) |
| { |
| if(CODE == segmentClass) |
| { |
| overall.mCode += size; |
| theModule->mSize.mCode += size; |
| } |
| else if(DATA == segmentClass) |
| { |
| overall.mData += size; |
| theModule->mSize.mData += size; |
| } |
| |
| theSegment->mSize += size; |
| theObject->mSize += size; |
| theSymbol->mSize += size; |
| } |
| else |
| { |
| if(CODE == segmentClass) |
| { |
| overall.mCode -= size; |
| theModule->mSize.mCode -= size; |
| } |
| else if(DATA == segmentClass) |
| { |
| overall.mData -= size; |
| theModule->mSize.mData -= size; |
| } |
| |
| theSegment->mSize -= size; |
| theObject->mSize -= size; |
| theSymbol->mSize -= size; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| else |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, inOptions->mInputName, "Unable to scan line data."); |
| } |
| } |
| } |
| |
| if(0 == retval && 0 != ferror(inOptions->mInput)) |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, inOptions->mInputName, "Unable to read file."); |
| } |
| |
| /* |
| ** Next, it is time to perform revisionist history of sorts. |
| ** If the negation switch is in play, we perfrom the following |
| ** aggressive steps: |
| ** |
| ** For each section, find size changes which have an equal and |
| ** opposite change, and set them both to zero. |
| ** However, you can only do this if the number of negating changes |
| ** is even, as if it is odd, then any one of the many could be |
| ** at fault for the actual change. |
| ** |
| ** This orginally exists to make the win32 codesighs reports more |
| ** readable/meaningful. |
| */ |
| if(0 == retval && 0 != inOptions->mNegation) |
| { |
| ObjectStats** objArray = NULL; |
| SymbolStats** symArray = NULL; |
| |
| /* |
| ** Create arrays big enough to hold all symbols. |
| ** As well as an array to keep the owning object at the same index. |
| ** We will keep the object around as we may need to modify the size. |
| */ |
| objArray = (ObjectStats**)malloc(allSymbolCount * sizeof(ObjectStats*)); |
| symArray = (SymbolStats**)malloc(allSymbolCount * sizeof(SymbolStats*)); |
| if(NULL == objArray || NULL == symArray) |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, inOptions->mProgramName, "Unable to allocate negation array memory."); |
| } |
| else |
| { |
| unsigned arrayCount = 0; |
| unsigned arrayLoop = 0; |
| |
| /* |
| ** Go through and perform the steps on each section/segment. |
| */ |
| for(moduleLoop = 0; moduleLoop < moduleCount; moduleLoop++) |
| { |
| theModule = modules + moduleLoop; |
| |
| for(segmentLoop = 0; segmentLoop < theModule->mSegmentCount; segmentLoop++) |
| { |
| theSegment = theModule->mSegments + segmentLoop; |
| |
| /* |
| ** Collect all symbols under this section. |
| ** The symbols are spread out between all the objects, |
| ** so keep track of both independently at the |
| ** same index. |
| */ |
| arrayCount = 0; |
| |
| for(objectLoop = 0; objectLoop < theSegment->mObjectCount; objectLoop++) |
| { |
| theObject = theSegment->mObjects + objectLoop; |
| |
| for(symbolLoop = 0; symbolLoop < theObject->mSymbolCount; symbolLoop++) |
| { |
| theSymbol = theObject->mSymbols + symbolLoop; |
| |
| objArray[arrayCount] = theObject; |
| symArray[arrayCount] = theSymbol; |
| arrayCount++; |
| } |
| } |
| |
| /* |
| ** Now that we have a list of symbols, go through each |
| ** and see if there is a chance of negation. |
| */ |
| for(arrayLoop = 0; arrayLoop < arrayCount; arrayLoop++) |
| { |
| /* |
| ** If the item is NULL, it was already negated. |
| ** Don't do this for items with a zero size. |
| */ |
| if(NULL != symArray[arrayLoop] && 0 != symArray[arrayLoop]->mSize) |
| { |
| unsigned identicalValues = 0; |
| unsigned oppositeValues = 0; |
| unsigned lookLoop = 0; |
| const int lookingFor = symArray[arrayLoop]->mSize; |
| |
| /* |
| ** Count the number of items with this value. |
| ** Count the number of items with the opposite equal value. |
| ** If they are equal, go through and negate all sizes. |
| */ |
| for(lookLoop = arrayLoop; lookLoop < arrayCount; lookLoop++) |
| { |
| /* |
| ** Skip negated items. |
| ** Skip zero length items. |
| */ |
| if(NULL == symArray[lookLoop] || 0 == symArray[lookLoop]->mSize) |
| { |
| continue; |
| } |
| |
| if(lookingFor == symArray[lookLoop]->mSize) |
| { |
| identicalValues++; |
| } |
| else if((-1 * lookingFor) == symArray[lookLoop]->mSize) |
| { |
| oppositeValues++; |
| } |
| } |
| |
| if(0 != identicalValues && identicalValues == oppositeValues) |
| { |
| unsigned negationLoop = 0; |
| |
| for(negationLoop = arrayLoop; 0 != identicalValues || 0 != oppositeValues; negationLoop++) |
| { |
| /* |
| ** Skip negated items. |
| ** Skip zero length items. |
| */ |
| if(NULL == symArray[negationLoop] || 0 == symArray[negationLoop]->mSize) |
| { |
| continue; |
| } |
| |
| /* |
| ** Negate any size matches. |
| ** Reflect the change in the object as well. |
| ** Clear the symbol. |
| */ |
| if(lookingFor == symArray[negationLoop]->mSize) |
| { |
| objArray[negationLoop]->mSize -= lookingFor; |
| symArray[negationLoop]->mSize = 0; |
| symArray[negationLoop] = NULL; |
| |
| identicalValues--; |
| } |
| else if((-1 * lookingFor) == symArray[negationLoop]->mSize) |
| { |
| objArray[negationLoop]->mSize += lookingFor; |
| symArray[negationLoop]->mSize = 0; |
| symArray[negationLoop] = NULL; |
| |
| oppositeValues--; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| CLEANUP(objArray); |
| CLEANUP(symArray); |
| } |
| |
| |
| /* |
| ** If all went well, time to report. |
| */ |
| if(0 == retval) |
| { |
| /* |
| ** Loop through our data once more, so that the symbols can |
| ** propigate their changes upwards in a positive/negative |
| ** fashion. |
| ** This will help give the composite change more meaning. |
| */ |
| for(moduleLoop = 0; moduleLoop < moduleCount; moduleLoop++) |
| { |
| theModule = modules + moduleLoop; |
| |
| /* |
| ** Skip if there is zero drift, or no net change. |
| */ |
| if(0 == inOptions->mZeroDrift && 0 == (theModule->mSize.mCode + theModule->mSize.mData)) |
| { |
| continue; |
| } |
| |
| for(segmentLoop = 0; segmentLoop < theModule->mSegmentCount; segmentLoop++) |
| { |
| theSegment = theModule->mSegments + segmentLoop; |
| |
| /* |
| ** Skip if there is zero drift, or no net change. |
| */ |
| if(0 == inOptions->mZeroDrift && 0 == theSegment->mSize) |
| { |
| continue; |
| } |
| |
| for(objectLoop = 0; objectLoop < theSegment->mObjectCount; objectLoop++) |
| { |
| theObject = theSegment->mObjects + objectLoop; |
| |
| /* |
| ** Skip if there is zero drift, or no net change. |
| */ |
| if(0 == inOptions->mZeroDrift && 0 == theObject->mSize) |
| { |
| continue; |
| } |
| |
| for(symbolLoop = 0; symbolLoop < theObject->mSymbolCount; symbolLoop++) |
| { |
| theSymbol = theObject->mSymbols + symbolLoop; |
| |
| /* |
| ** Propagate the composition all the way to the top. |
| ** Sizes of zero change are skipped. |
| */ |
| if(0 < theSymbol->mSize) |
| { |
| theObject->mComposition.mPositive += theSymbol->mSize; |
| theSegment->mComposition.mPositive += theSymbol->mSize; |
| if(CODE == theSegment->mClass) |
| { |
| overall.mCodeComposition.mPositive += theSymbol->mSize; |
| theModule->mSize.mCodeComposition.mPositive += theSymbol->mSize; |
| } |
| else if(DATA == theSegment->mClass) |
| { |
| overall.mDataComposition.mPositive += theSymbol->mSize; |
| theModule->mSize.mDataComposition.mPositive += theSymbol->mSize; |
| } |
| } |
| else if(0 > theSymbol->mSize) |
| { |
| theObject->mComposition.mNegative += theSymbol->mSize; |
| theSegment->mComposition.mNegative += theSymbol->mSize; |
| if(CODE == theSegment->mClass) |
| { |
| overall.mCodeComposition.mNegative += theSymbol->mSize; |
| theModule->mSize.mCodeComposition.mNegative += theSymbol->mSize; |
| } |
| else if(DATA == theSegment->mClass) |
| { |
| overall.mDataComposition.mNegative += theSymbol->mSize; |
| theModule->mSize.mDataComposition.mNegative += theSymbol->mSize; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| |
| if(inOptions->mSummaryOnly) |
| { |
| fprintf(inOptions->mOutput, "%+d (%+d/%+d)\n", overall.mCode + overall.mData, overall.mCodeComposition.mPositive + overall.mDataComposition.mPositive, overall.mCodeComposition.mNegative + overall.mDataComposition.mNegative); |
| } |
| else |
| { |
| fprintf(inOptions->mOutput, "Overall Change in Size\n"); |
| fprintf(inOptions->mOutput, "\tTotal:\t%+11d (%+d/%+d)\n", overall.mCode + overall.mData, overall.mCodeComposition.mPositive + overall.mDataComposition.mPositive, overall.mCodeComposition.mNegative + overall.mDataComposition.mNegative); |
| fprintf(inOptions->mOutput, "\tCode:\t%+11d (%+d/%+d)\n", overall.mCode, overall.mCodeComposition.mPositive, overall.mCodeComposition.mNegative); |
| fprintf(inOptions->mOutput, "\tData:\t%+11d (%+d/%+d)\n", overall.mData, overall.mDataComposition.mPositive, overall.mDataComposition.mNegative); |
| } |
| |
| /* |
| ** Check what else we should output. |
| */ |
| if(0 == inOptions->mSummaryOnly && NULL != modules && moduleCount) |
| { |
| const char* segmentType = NULL; |
| |
| /* |
| ** We're going to sort everything. |
| */ |
| qsort(modules, moduleCount, sizeof(ModuleStats), moduleCompare); |
| for(moduleLoop = 0; moduleLoop < moduleCount; moduleLoop++) |
| { |
| theModule = modules + moduleLoop; |
| |
| qsort(theModule->mSegments, theModule->mSegmentCount, sizeof(SegmentStats), segmentCompare); |
| |
| for(segmentLoop = 0; segmentLoop < theModule->mSegmentCount; segmentLoop++) |
| { |
| theSegment = theModule->mSegments + segmentLoop; |
| |
| qsort(theSegment->mObjects, theSegment->mObjectCount, sizeof(ObjectStats), objectCompare); |
| |
| for(objectLoop = 0; objectLoop < theSegment->mObjectCount; objectLoop++) |
| { |
| theObject = theSegment->mObjects + objectLoop; |
| |
| qsort(theObject->mSymbols, theObject->mSymbolCount, sizeof(SymbolStats), symbolCompare); |
| } |
| } |
| } |
| |
| /* |
| ** Loop through for output. |
| */ |
| for(moduleLoop = 0; moduleLoop < moduleCount; moduleLoop++) |
| { |
| theModule = modules + moduleLoop; |
| |
| /* |
| ** Skip if there is zero drift, or no net change. |
| */ |
| if(0 == inOptions->mZeroDrift && 0 == (theModule->mSize.mCode + theModule->mSize.mData)) |
| { |
| continue; |
| } |
| |
| fprintf(inOptions->mOutput, "\n"); |
| fprintf(inOptions->mOutput, "%s\n", theModule->mModule); |
| fprintf(inOptions->mOutput, "\tTotal:\t%+11d (%+d/%+d)\n", theModule->mSize.mCode + theModule->mSize.mData, theModule->mSize.mCodeComposition.mPositive + theModule->mSize.mDataComposition.mPositive, theModule->mSize.mCodeComposition.mNegative + theModule->mSize.mDataComposition.mNegative); |
| fprintf(inOptions->mOutput, "\tCode:\t%+11d (%+d/%+d)\n", theModule->mSize.mCode, theModule->mSize.mCodeComposition.mPositive, theModule->mSize.mCodeComposition.mNegative); |
| fprintf(inOptions->mOutput, "\tData:\t%+11d (%+d/%+d)\n", theModule->mSize.mData, theModule->mSize.mDataComposition.mPositive, theModule->mSize.mDataComposition.mNegative); |
| |
| for(segmentLoop = 0; segmentLoop < theModule->mSegmentCount; segmentLoop++) |
| { |
| theSegment = theModule->mSegments + segmentLoop; |
| |
| /* |
| ** Skip if there is zero drift, or no net change. |
| */ |
| if(0 == inOptions->mZeroDrift && 0 == theSegment->mSize) |
| { |
| continue; |
| } |
| |
| if(CODE == theSegment->mClass) |
| { |
| segmentType = "CODE"; |
| } |
| else if(DATA == theSegment->mClass) |
| { |
| segmentType = "DATA"; |
| } |
| |
| fprintf(inOptions->mOutput, "\t%+11d (%+d/%+d)\t%s (%s)\n", theSegment->mSize, theSegment->mComposition.mPositive, theSegment->mComposition.mNegative, theSegment->mSegment, segmentType); |
| |
| for(objectLoop = 0; objectLoop < theSegment->mObjectCount; objectLoop++) |
| { |
| theObject = theSegment->mObjects + objectLoop; |
| |
| /* |
| ** Skip if there is zero drift, or no net change. |
| */ |
| if(0 == inOptions->mZeroDrift && 0 == theObject->mSize) |
| { |
| continue; |
| } |
| |
| fprintf(inOptions->mOutput, "\t\t%+11d (%+d/%+d)\t%s\n", theObject->mSize, theObject->mComposition.mPositive, theObject->mComposition.mNegative, theObject->mObject); |
| |
| for(symbolLoop = 0; symbolLoop < theObject->mSymbolCount; symbolLoop++) |
| { |
| theSymbol = theObject->mSymbols + symbolLoop; |
| |
| /* |
| ** Skip if there is zero drift, or no net change. |
| */ |
| if(0 == inOptions->mZeroDrift && 0 == theSymbol->mSize) |
| { |
| continue; |
| } |
| |
| fprintf(inOptions->mOutput, "\t\t\t%+11d\t%s\n", theSymbol->mSize, theSymbol->mSymbol); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /* |
| ** Cleanup time. |
| */ |
| for(moduleLoop = 0; moduleLoop < moduleCount; moduleLoop++) |
| { |
| theModule = modules + moduleLoop; |
| |
| for(segmentLoop = 0; segmentLoop < theModule->mSegmentCount; segmentLoop++) |
| { |
| theSegment = theModule->mSegments + segmentLoop; |
| |
| for(objectLoop = 0; objectLoop < theSegment->mObjectCount; objectLoop++) |
| { |
| theObject = theSegment->mObjects + objectLoop; |
| |
| for(symbolLoop = 0; symbolLoop < theObject->mSymbolCount; symbolLoop++) |
| { |
| theSymbol = theObject->mSymbols + symbolLoop; |
| |
| CLEANUP(theSymbol->mSymbol); |
| } |
| |
| CLEANUP(theObject->mSymbols); |
| CLEANUP(theObject->mObject); |
| } |
| |
| CLEANUP(theSegment->mObjects); |
| CLEANUP(theSegment->mSegment); |
| } |
| |
| CLEANUP(theModule->mSegments); |
| CLEANUP(theModule->mModule); |
| } |
| CLEANUP(modules); |
| |
| return retval; |
| } |
| |
| |
| int initOptions(Options* outOptions, int inArgc, char** inArgv) |
| /* |
| ** returns int 0 if successful. |
| */ |
| { |
| int retval = 0; |
| int loop = 0; |
| int switchLoop = 0; |
| int match = 0; |
| const int switchCount = sizeof(gSwitches) / sizeof(gSwitches[0]); |
| Switch* current = NULL; |
| |
| /* |
| ** Set any defaults. |
| */ |
| memset(outOptions, 0, sizeof(Options)); |
| outOptions->mProgramName = inArgv[0]; |
| outOptions->mInput = stdin; |
| outOptions->mInputName = strdup("stdin"); |
| outOptions->mOutput = stdout; |
| outOptions->mOutputName = strdup("stdout"); |
| |
| if(NULL == outOptions->mOutputName || NULL == outOptions->mInputName) |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, "stdin/stdout", "Unable to strdup."); |
| } |
| |
| /* |
| ** Go through and attempt to do the right thing. |
| */ |
| for(loop = 1; loop < inArgc && 0 == retval; loop++) |
| { |
| match = 0; |
| current = NULL; |
| |
| for(switchLoop = 0; switchLoop < switchCount && 0 == retval; switchLoop++) |
| { |
| if(0 == strcmp(gSwitches[switchLoop]->mLongName, inArgv[loop])) |
| { |
| match = __LINE__; |
| } |
| else if(0 == strcmp(gSwitches[switchLoop]->mShortName, inArgv[loop])) |
| { |
| match = __LINE__; |
| } |
| |
| if(match) |
| { |
| if(gSwitches[switchLoop]->mHasValue) |
| { |
| /* |
| ** Attempt to absorb next option to fullfill value. |
| */ |
| if(loop + 1 < inArgc) |
| { |
| loop++; |
| |
| current = gSwitches[switchLoop]; |
| current->mValue = inArgv[loop]; |
| } |
| } |
| else |
| { |
| current = gSwitches[switchLoop]; |
| } |
| |
| break; |
| } |
| } |
| |
| if(0 == match) |
| { |
| outOptions->mHelp = __LINE__; |
| retval = __LINE__; |
| ERROR_REPORT(retval, inArgv[loop], "Unknown command line switch."); |
| } |
| else if(NULL == current) |
| { |
| outOptions->mHelp = __LINE__; |
| retval = __LINE__; |
| ERROR_REPORT(retval, inArgv[loop], "Command line switch requires a value."); |
| } |
| else |
| { |
| /* |
| ** Do something based on address/swtich. |
| */ |
| if(current == &gInputSwitch) |
| { |
| CLEANUP(outOptions->mInputName); |
| if(NULL != outOptions->mInput && stdin != outOptions->mInput) |
| { |
| fclose(outOptions->mInput); |
| outOptions->mInput = NULL; |
| } |
| |
| outOptions->mInput = fopen(current->mValue, "r"); |
| if(NULL == outOptions->mInput) |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, current->mValue, "Unable to open input file."); |
| } |
| else |
| { |
| outOptions->mInputName = strdup(current->mValue); |
| if(NULL == outOptions->mInputName) |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, current->mValue, "Unable to strdup."); |
| } |
| } |
| } |
| else if(current == &gOutputSwitch) |
| { |
| CLEANUP(outOptions->mOutputName); |
| if(NULL != outOptions->mOutput && stdout != outOptions->mOutput) |
| { |
| fclose(outOptions->mOutput); |
| outOptions->mOutput = NULL; |
| } |
| |
| outOptions->mOutput = fopen(current->mValue, "a"); |
| if(NULL == outOptions->mOutput) |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, current->mValue, "Unable to open output file."); |
| } |
| else |
| { |
| outOptions->mOutputName = strdup(current->mValue); |
| if(NULL == outOptions->mOutputName) |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, current->mValue, "Unable to strdup."); |
| } |
| } |
| } |
| else if(current == &gHelpSwitch) |
| { |
| outOptions->mHelp = __LINE__; |
| } |
| else if(current == &gSummarySwitch) |
| { |
| outOptions->mSummaryOnly = __LINE__; |
| } |
| else if(current == &gZeroDriftSwitch) |
| { |
| outOptions->mZeroDrift = __LINE__; |
| } |
| else if(current == &gNegationSwitch) |
| { |
| outOptions->mNegation = __LINE__; |
| } |
| else |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, current->mLongName, "No handler for command line switch."); |
| } |
| } |
| } |
| |
| return retval; |
| } |
| |
| |
| void cleanOptions(Options* inOptions) |
| /* |
| ** Clean up any open handles. |
| */ |
| { |
| CLEANUP(inOptions->mInputName); |
| if(NULL != inOptions->mInput && stdin != inOptions->mInput) |
| { |
| fclose(inOptions->mInput); |
| } |
| CLEANUP(inOptions->mOutputName); |
| if(NULL != inOptions->mOutput && stdout != inOptions->mOutput) |
| { |
| fclose(inOptions->mOutput); |
| } |
| |
| memset(inOptions, 0, sizeof(Options)); |
| } |
| |
| |
| void showHelp(Options* inOptions) |
| /* |
| ** Show some simple help text on usage. |
| */ |
| { |
| int loop = 0; |
| const int switchCount = sizeof(gSwitches) / sizeof(gSwitches[0]); |
| const char* valueText = NULL; |
| |
| printf("usage:\t%s [arguments]\n", inOptions->mProgramName); |
| printf("\n"); |
| printf("arguments:\n"); |
| |
| for(loop = 0; loop < switchCount; loop++) |
| { |
| if(gSwitches[loop]->mHasValue) |
| { |
| valueText = " <value>"; |
| } |
| else |
| { |
| valueText = ""; |
| } |
| |
| printf("\t%s%s\n", gSwitches[loop]->mLongName, valueText); |
| printf("\t %s%s", gSwitches[loop]->mShortName, valueText); |
| printf(DESC_NEWLINE "%s\n\n", gSwitches[loop]->mDescription); |
| } |
| |
| printf("This tool takes the diff of two sorted tsv files to form a summary report\n"); |
| printf("of code and data size changes which is hoped to be human readable.\n"); |
| } |
| |
| |
| int main(int inArgc, char** inArgv) |
| { |
| int retval = 0; |
| Options options; |
| |
| retval = initOptions(&options, inArgc, inArgv); |
| if(options.mHelp) |
| { |
| showHelp(&options); |
| } |
| else if(0 == retval) |
| { |
| retval = difftool(&options); |
| } |
| |
| cleanOptions(&options); |
| return retval; |
| } |
| |