diff options
Diffstat (limited to 'utils/classcheck')
| -rw-r--r-- | utils/classcheck/class.cpp | 1581 | ||||
| -rw-r--r-- | utils/classcheck/class.h | 196 | ||||
| -rw-r--r-- | utils/classcheck/classcheck.cpp | 216 | ||||
| -rw-r--r-- | utils/classcheck/classcheck.vpc | 39 | ||||
| -rw-r--r-- | utils/classcheck/classcheck_util.cpp | 369 | ||||
| -rw-r--r-- | utils/classcheck/classcheck_util.h | 29 | ||||
| -rw-r--r-- | utils/classcheck/codeprocessor.h | 129 | ||||
| -rw-r--r-- | utils/classcheck/icodeprocessor.h | 59 | ||||
| -rw-r--r-- | utils/classcheck/processmodule.cpp | 1866 | ||||
| -rw-r--r-- | utils/classcheck/stdafx.cpp | 15 | ||||
| -rw-r--r-- | utils/classcheck/stdafx.h | 26 |
11 files changed, 4525 insertions, 0 deletions
diff --git a/utils/classcheck/class.cpp b/utils/classcheck/class.cpp new file mode 100644 index 0000000..369aa74 --- /dev/null +++ b/utils/classcheck/class.cpp @@ -0,0 +1,1581 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include <assert.h> +#include "stdafx.h" +#include <stdio.h> +#include <windows.h> +#include "classcheck_util.h" +#include "class.h" +#include "icodeprocessor.h" + +CClass::CClass( const char *name ) +{ + m_nVarCount = 0; + m_nMemberCount = 0; + m_nTDCount = 0; + m_nPredTDCount = 0; + + strcpy( m_szName, name ); + m_szBaseClass[0]=0; + m_pBaseClass = NULL; + m_szTypedefBaseClass[0]=0; + + m_bDerivedFromCBaseEntity = false; + m_bHasSaveRestoreData = false; + m_bHasPredictionData = false; + m_bConstructPredictableCalled = false; + m_bHasRecvTableData = false; + + m_nClassDataSize = 0; +} + +CClass::~CClass( void ) +{ + int i; + + for ( i = 0; i < m_nVarCount; i++ ) + { + delete m_Variables[ i ]; + } + m_nVarCount = 0; + + for ( i = 0; i < m_nMemberCount; i++ ) + { + delete m_Members[ i ]; + } + m_nMemberCount = 0; + for ( i = 0; i < m_nTDCount; i++ ) + { + delete m_TDFields[ i ]; + } + m_nTDCount = 0; + for ( i = 0; i < m_nPredTDCount; i++ ) + { + delete m_PredTDFields[ i ]; + } + m_nPredTDCount = 0; +} + +CTypeDescriptionField *CClass::FindTD( const char *name ) +{ + for ( int i = 0; i < m_nTDCount; i++ ) + { + if ( !strcmp( m_TDFields[ i ]->m_szVariableName, name ) ) + return m_TDFields[ i ]; + } + return NULL; +} + +CTypeDescriptionField *CClass::FindPredTD( const char *name ) +{ + for ( int i = 0; i < m_nPredTDCount; i++ ) + { + if ( !strcmp( m_PredTDFields[ i ]->m_szVariableName, name ) ) + return m_PredTDFields[ i ]; + } + return NULL; +} + +CClassVariable *CClass::FindVar( const char *name, bool checkbaseclasses /*= false*/ ) +{ + CClass *cl = this; + while ( cl ) + { + for ( int i = 0; i < cl->m_nVarCount; i++ ) + { + if ( !strcmp( cl->m_Variables[ i ]->m_szName, name ) ) + return cl->m_Variables[ i ]; + } + + if ( !checkbaseclasses ) + break; + + if ( !cl->m_pBaseClass ) + { + cl->m_pBaseClass = processor->FindClass( cl->m_szBaseClass ); + } + + cl = cl->m_pBaseClass; + if ( !cl ) + break; + } + return NULL; +} + +CClassMemberFunction *CClass::FindMember( const char *name ) +{ + for ( int i = 0; i < m_nMemberCount; i++ ) + { + if ( !strcmp( m_Members[ i ]->m_szName, name ) ) + return m_Members[ i ]; + } + return NULL; +} + +CTypeDescriptionField *CClass::AddTD( const char *name, const char *type, const char *definetype, bool incomments ) +{ + CTypeDescriptionField *td = FindTD( name ); + if ( !td ) + { + td = new CTypeDescriptionField(); + strcpy( td->m_szVariableName, name ); + strcpy( td->m_szType, type ); + strcpy( td->m_szDefineType, definetype ); + td->m_bCommentedOut = incomments; + + m_TDFields[ m_nTDCount++ ] = td; + if ( m_nTDCount >= MAX_TDFIELDS ) + { + vprint( 0, "too many typedescription fields\n" ); + exit( 1 ); + } + } + return td; +} + +CTypeDescriptionField *CClass::AddPredTD( const char *name, const char *type, const char *definetype, bool incomments, bool inrecvtable ) +{ + CTypeDescriptionField *td = FindPredTD( name ); + if ( !td ) + { + td = new CTypeDescriptionField(); + strcpy( td->m_szVariableName, name ); + strcpy( td->m_szType, type ); + strcpy( td->m_szDefineType, definetype ); + td->m_bCommentedOut = incomments; + td->m_bRepresentedInRecvTable = inrecvtable; + + m_PredTDFields[ m_nPredTDCount++ ] = td; + if ( m_nPredTDCount >= MAX_TDFIELDS ) + { + vprint( 0, "too many prediction typedescription fields\n" ); + exit( 1 ); + } + } + return td; +} + +CClassVariable *CClass::AddVar( const char *name ) +{ + CClassVariable *var = FindVar( name ); + if ( !var ) + { + var = new CClassVariable(); + strcpy( var->m_szName, name ); + + m_Variables[ m_nVarCount++ ] = var; + if ( m_nVarCount >= MAX_VARIABLES ) + { + vprint( 0, "too many variables\n" ); + exit( 1 ); + } + } + return var; +} + +CClassMemberFunction *CClass::AddMember( const char *name ) +{ + CClassMemberFunction *member = FindMember( name ); + if ( !member ) + { + member = new CClassMemberFunction(); + strcpy( member->m_szName, name ); + + m_Members[ m_nMemberCount++ ] = member; + if ( m_nMemberCount >= MAX_MEMBERS ) + { + vprint( 0, "too many members\n" ); + exit( 1 ); + } + } + return member; +} + +void CClass::SetBaseClass( const char *name ) +{ + if ( !m_szBaseClass[ 0 ] ) + { + strcpy( m_szBaseClass, name ); + } + else if ( stricmp( m_szBaseClass, name ) ) + { + vprint( 0, "Base class differs for %s %s vs %s\n", m_szName, m_szBaseClass, name ); + } +} + +void CClass::CheckChildOfBaseEntity( const char *baseentityclass ) +{ + m_bDerivedFromCBaseEntity = false; + + if ( !stricmp( m_szName, baseentityclass ) ) + { + m_bDerivedFromCBaseEntity = true; + return; + } + + CClass *base = m_pBaseClass; + while ( base ) + { + // Early out? + if ( base->m_bDerivedFromCBaseEntity ) + { + m_bDerivedFromCBaseEntity = true; + return; + } + + // Check name + if ( !stricmp( base->m_szName, baseentityclass ) ) + { + m_bDerivedFromCBaseEntity = true; + return; + } + + // Keep going up hierarchy + base = base->m_pBaseClass; + } +} + +static bool IsType( char *input, char *test ) +{ + char *pMatch = strstr( input, test ); + if ( !pMatch ) + return false; + + size_t nLen = strlen(test); + if ( ( pMatch[nLen] != 0 ) && (pMatch[nLen] != ' ') ) + return false; + + if ( input != pMatch && (*(pMatch-1) != ' ') ) + return false; + + return true; +} + +static char const *TranslateSimpleType( CClassVariable *var ) +{ + static char out[ 256 ]; + out[ 0 ] = 0; + + char *input = var->m_szType; + + // Don't know how to handle templatized things yet + if ( strstr( input, "<" ) ) + { + return out; + } + + if ( IsType( input, "bool" ) ) + { + return "FIELD_BOOLEAN"; + } + else if ( IsType( input, "short" ) ) + { + return "FIELD_SHORT"; + } + else if ( IsType( input, "int" ) ) + { + return "FIELD_INTEGER"; + } + else if ( IsType( input, "byte" ) ) + { + return "FIELD_CHARACTER"; + } + else if ( IsType( input, "float" ) ) + { + return "FIELD_FLOAT"; + } + else if ( IsType( input, "EHANDLE" ) || IsType( input, "CHandle" ) ) + { + return "FIELD_EHANDLE"; + } + else if ( IsType( input, "color32" ) ) + { + return "FIELD_COLOR32"; + } + else if ( IsType( input, "Vector" ) || IsType( input, "QAngle" ) ) + { + return "FIELD_VECTOR"; + } + else if ( IsType( input, "Quaternion" ) ) + { + return "FIELD_QUATERNION"; + } + else if ( IsType( input, "VMatrix" ) ) + { + return "FIELD_VMATRIX"; + } + else if ( IsType( input, "string_t" ) ) + { + return "FIELD_STRING"; + } + else if ( IsType( input, "char" ) ) + { + return "FIELD_CHARACTER"; + } + + return out; +} + +void CClass::ReportTypeMismatches( CClassVariable *var, CTypeDescriptionField *td ) +{ + char const *t = TranslateSimpleType( var ); + if ( !t[0] ) + return; + + // Special cases + if ( td->m_bCommentedOut ) + return; + + if ( !strcmp( td->m_szType, "FIELD_TIME" ) ) + { + if ( !strcmp( t, "FIELD_FLOAT" ) ) + return; + } + + if ( !strcmp( td->m_szType, "FIELD_TICK" ) ) + { + if ( !strcmp( t, "FIELD_INTEGER" ) ) + return; + } + + if ( !strcmp( td->m_szType, "FIELD_MODELNAME" ) || !strcmp( td->m_szType, "FIELD_SOUNDNAME" ) ) + { + if ( !strcmp( t, "FIELD_STRING" ) ) + return; + } + + if ( !strcmp( td->m_szType, "FIELD_MODELINDEX" ) || !strcmp( td->m_szType, "FIELD_MATERIALINDEX" ) ) + { + if ( !strcmp( t, "FIELD_INTEGER" ) ) + return; + } + + if ( !strcmp( td->m_szType, "FIELD_POSITION_VECTOR" ) ) + { + if ( !strcmp( t, "FIELD_VECTOR" ) ) + return; + } + + if ( !strcmp( td->m_szType, "FIELD_VMATRIX_WORLDSPACE" ) ) + { + if ( !strcmp( t, "FIELD_VMATRIX" ) ) + return; + } + + if ( strcmp( t, td->m_szType ) ) + { + vprint( 0, "class %s has an incorrect FIELD_ type for variable '%s (%s, %s)'\n", + m_szName, var->m_szName, var->m_szType, td->m_szType ); + } +} + + +bool CClass::CheckForMissingTypeDescriptionFields( int& missingcount, bool createtds ) +{ + bool bret = false; + missingcount = 0; + // Didn't specify a TYPEDESCRIPTION at all + if ( !m_bHasSaveRestoreData ) + return bret; + + for ( int i = 0; i < m_nVarCount; i++ ) + { + CClassVariable *var = m_Variables[ i ]; + + bool isstatic = false; + char *p = var->m_szType; + while ( 1 ) + { + p = CC_ParseToken( p ); + if ( strlen( com_token ) <= 0 ) + break; + if ( !stricmp( com_token, "static" ) ) + { + isstatic = true; + break; + } + } + + // Statics aren't encoded + if ( isstatic ) + continue; + + char *goodname = var->m_szName; + + // Skip * pointer modifier + while ( *goodname && *goodname == '*' ) + { + goodname++; + } + + CTypeDescriptionField *td = FindTD( goodname ); + if ( td ) + { + ReportTypeMismatches( var, td ); + continue; + } + + bret = true; + missingcount++; + + if ( !createtds ) + { + vprint( 0, "class %s missing typedescription_t field for variable '%s %s'\n", + m_szName, var->m_szType, var->m_szName ); + continue; + } + + char const *t = TranslateSimpleType( var ); + + vprint( 0, "//\tClass %s:\n", m_szName ); + if ( var->m_bIsArray && + ( + stricmp( var->m_szType, "char" ) || + stricmp( t, "FIELD_STRING" ) + ) ) + { + if ( *t ) + { + vprint( 0, "\tDEFINE_ARRAY( %s, %s, %s ),\n", goodname, t, var->m_szArraySize ); + } + else + { + vprint( 0, "\t// DEFINE_ARRAY( %s, %s, %s ),\n", goodname, var->m_szType, var->m_szArraySize ); + } + } + else + { + if ( *t ) + { + vprint( 0, "\tDEFINE_FIELD( %s, %s ),\n", goodname, t ); + } + else + { + vprint( 0, "\t// DEFINE_FIELD( %s, %s ),\n", goodname, var->m_szType ); + } + } + } + + return bret; +} + +bool CClass::CheckForPredictionFieldsInRecvTableNotMarkedAsSuchCorrectly( int &missingcount ) +{ + bool bret = false; + missingcount = 0; + // Didn't specify a TYPEDESCRIPTION at all + if ( !m_bHasPredictionData ) + return bret; + + if ( !m_bHasRecvTableData ) + return bret; + + for ( int i = 0; i < m_nVarCount; i++ ) + { + CClassVariable *var = m_Variables[ i ]; + bool inreceivetable = var->m_bInRecvTable; + + bool isstatic = false; + char *p = var->m_szType; + while ( 1 ) + { + p = CC_ParseToken( p ); + if ( strlen( com_token ) <= 0 ) + break; + if ( !stricmp( com_token, "static" ) ) + { + isstatic = true; + break; + } + } + + // Statics aren't encoded + if ( isstatic ) + continue; + + char *goodname = var->m_szName; + + // Skip * pointer modifier + while ( *goodname && *goodname == '*' ) + goodname++; + + CTypeDescriptionField *td = FindPredTD( goodname ); + // Missing variables are caught in a different routine + td = FindPredTD( goodname ); + if ( !td ) + continue; + + // These are implicitly ok + if ( !strcmp( td->m_szDefineType, "DEFINE_PRED_TYPEDESCRIPTION" ) ) + { + CClass *cl2 = processor->FindClass( td->m_szType ); + if ( cl2 ) + { + bret = cl2->CheckForPredictionFieldsInRecvTableNotMarkedAsSuchCorrectly( missingcount ); + } + continue; + } + + // Looks good (either in or out!) + // Check for appripriate flags + if ( inreceivetable == td->m_bRepresentedInRecvTable ) + continue; + + bret = true; + missingcount++; + + if ( inreceivetable && !td->m_bRepresentedInRecvTable ) + { + vprint( 0, "%s::%s: Missing FTYPEDESC_INSENDTABLE flag in prediction typedescription\n", m_szName, var->m_szName ); + } + else + { + vprint( 0, "%s::%s: Field marked as FTYPEDESC_INSENDTABLE in prediction typedescription missing from RecvTable\n", m_szName, var->m_szName ); + } + } + + return bret; +} + +bool CClass::CheckForMissingPredictionFields( int& missingcount, bool createtds ) +{ + bool bret = false; + missingcount = 0; + // Didn't specify a TYPEDESCRIPTION at all + if ( !m_bHasPredictionData ) + return bret; + + for ( int i = 0; i < m_nVarCount; i++ ) + { + CClassVariable *var = m_Variables[ i ]; + + // private and protected variables can't be referenced in data tables right now + //if ( var->m_Type != CClassVariable::TPUBLIC ) + // continue; + + bool isstatic = false; + char *p = var->m_szType; + while ( 1 ) + { + p = CC_ParseToken( p ); + if ( strlen( com_token ) <= 0 ) + break; + if ( !stricmp( com_token, "static" ) ) + { + isstatic = true; + break; + } + } + + // Statics aren't encoded + if ( !isstatic ) + { + char *goodname = var->m_szName; + + // Skip * pointer modifier + while ( *goodname && *goodname == '*' ) + goodname++; + + CTypeDescriptionField *td = FindPredTD( goodname ); + td = FindPredTD( goodname ); + if ( !td ) + { + bret = true; + missingcount++; + + if ( !createtds ) + { + vprint( 0, "class %s missing prediction typedescription_t field for variable '%s %s'\n", + m_szName, var->m_szType, var->m_szName ); + } + else + { + char const *t = TranslateSimpleType( var ); + + vprint( 0, "//\tClass %s:\n", m_szName ); + if ( var->m_bIsArray && + ( + stricmp( var->m_szType, "char" ) || + stricmp( t, "FIELD_STRING" ) + ) ) + { + if ( *t ) + { + vprint( 0, "\tDEFINE_ARRAY( %s, %s, %s ),\n", goodname, t, var->m_szArraySize ); + } + else + { + vprint( 0, "\t// DEFINE_ARRAY( %s, %s, %s ),\n", goodname, var->m_szType, var->m_szArraySize ); + } + } + else + { + if ( *t ) + { + vprint( 0, "\tDEFINE_FIELD( %s, %s ),\n", goodname, t ); + } + else + { + vprint( 0, "\t// DEFINE_FIELD( %s, %s ),\n", goodname, var->m_szType ); + } + } + } + } + } + } + + return bret; +} + +void AppendType( const char *token, char *type ) +{ + strcat( type, token ); + strcat( type, " " ); +} + +class MissingType +{ +public: + CClass *owning_class; + CClassVariable *var; +}; + +#include "utldict.h" +CUtlDict< MissingType, unsigned short > missing_types; + +bool IsMissingType( CClass *cl, CClassVariable *var ) +{ + unsigned short lookup; + + lookup = missing_types.Find( var->m_szType ); + if ( lookup != missing_types.InvalidIndex() ) + return true; + + MissingType t; + t.owning_class = cl; + t.var = var; + missing_types.Insert( var->m_szType, t ); + return true; +} + +void ReportMissingTypes( void ) +{ + int c = missing_types.Count(); + for ( int i= 0; i < c; i++ ) + { + MissingType *t = &missing_types[ i ]; + if ( !t ) + continue; + + if ( !t->owning_class ) + continue; + + if ( !t->owning_class->m_bDerivedFromCBaseEntity ) + continue; + + if ( !t->var ) + continue; + + vprint( 0, "Can't compute size of %s %s %s\n", + t->owning_class->m_szName, t->var->m_szType, t->var->m_szName ); + } +} + +void ClearMissingTypes() +{ + missing_types.Purge(); +} + +static int GetTypeSize( CClass *cl, CClassVariable *var ) +{ + int out = 0; + + char *input = var->m_szType; + + // Don't know how to handle templatized things yet + if ( strstr( input, "<" ) ) + { + IsMissingType( cl, var ); + return out; + } + + if ( strstr( var->m_szName, "*" ) ) + { + return sizeof( void * ); + } + + if ( strstr( input, "bool" ) ) + { + return sizeof( bool ); + } + else if ( strstr( input, "int64" ) ) + { + return sizeof( __int64 ); + } + else if ( strstr( input, "short" ) ) + { + return sizeof( short ); + } + else if ( strstr( input, "unsigned short" ) ) + { + return sizeof( unsigned short ); + } + else if ( strstr( input, "int" ) ) + { + return sizeof( int ); + } + else if ( strstr( input, "float" ) ) + { + return sizeof( float ); + } + else if ( strstr( input, "vec_t" ) ) + { + return sizeof( float ); + } + else if ( strstr( input, "Vector" ) || strstr( input, "QAngle" ) ) + { + return 3 * sizeof( float ); + } + else if ( strstr( input, "vec3_t" ) ) + { + return 3 * sizeof( float ); + } + else if ( strstr( input, "char" ) ) + { + return sizeof( char ); + } + else if ( strstr( input, "unsigned char" ) ) + { + return sizeof( unsigned char ); + } + else if ( strstr( input, "BYTE" ) ) + { + return sizeof( char ); + } + else if ( strstr( input, "byte" ) ) + { + return sizeof( char ); + } + else if ( !strcmp( input, "unsigned" ) ) + { + return sizeof(unsigned int); + } + else if ( strstr( input, "long" ) ) + { + return sizeof( int ); + } + else if ( strstr( input, "color32" ) ) + { + return sizeof( int ); + } + // It's a pointer + else if ( strstr( input, "*" ) ) + { + return sizeof( void * ); + } + // Static data doesn't count + else if ( strstr( input, "static" ) ) + { + return 0; + } + + + // Okay, see if it's a classname + CClass *base = processor->FindClass( input ); + if ( base ) + { + return base->m_nClassDataSize; + } + + IsMissingType( cl, var ); + + return out; +} + + +void CClass::AddVariable( int protection, char *type, char *name, bool array, char *arraysize ) +{ + CClassVariable *var = AddVar( name ); + if ( !var ) + return; + + strcpy( var->m_szType, type ); + var->m_Type = (CClassVariable::VARTYPE)protection; + var->m_TypeSize = GetTypeSize( this, var ); + + m_nClassDataSize += var->m_TypeSize; + + if ( array ) + { + var->m_bIsArray = true; + strcpy( var->m_szArraySize, arraysize ); + } + else + { + var->m_bIsArray = false; + } +} + + +//----------------------------------------------------------------------------- +// Parses information to determine the base class of this class +//----------------------------------------------------------------------------- +bool CClass::ParseBaseClass( char *&input ) +{ + if ( !strcmp( com_token, "DECLARE_CLASS" ) + || !strcmp( com_token, "DECLARE_CLASS_GAMEROOT" ) + || !strcmp( com_token, "DECLARE_CLASS_NOFRIEND" ) ) + { + input = CC_ParseToken( input ); + Assert( !strcmp( com_token, "(") ); + input = CC_ParseToken( input ); + + do + { + input = CC_ParseToken( input ); + } while( strcmp( com_token, ",") ); + + m_szTypedefBaseClass[0] = 0; + input = CC_ParseToken( input ); + do + { + strcat( m_szTypedefBaseClass, com_token ); + input = CC_ParseToken( input ); + } while( strcmp( com_token, ")") ); + return true; + } + else if ( !strcmp( com_token, "DECLARE_CLASS_NOBASE" ) ) + { + input = CC_ParseToken( input ); + Assert( !strcmp( com_token, "(") ); + input = CC_DiscardUntilMatchingCharIncludingNesting( input, "()" ); + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Parses networkvars +//----------------------------------------------------------------------------- +bool CClass::ParseNetworkVar( char *&input, int protection ) +{ + MemberVarParse_t var; + + if ( !strcmp( com_token, "CNetworkVar" ) || + !strcmp( com_token, "CNetworkVarForDerived" ) || + !strcmp( com_token, "CNetworkVarEmbedded" ) ) + { + input = CC_ParseToken( input ); + Assert( !strcmp( com_token, "(") ); + + input = CC_ParseToken( input ); + do + { + strcat( var.m_pType, com_token ); + strcat( var.m_pType, " " ); + input = CC_ParseToken( input ); + } while( strcmp( com_token, ",") ); + + input = CC_ParseToken( input ); + do + { + strcat( var.m_pName, com_token ); + input = CC_ParseToken( input ); + } while( strcmp( com_token, ")") ); + + AddVariable( protection, var.m_pType, var.m_pName, false ); + return true; + } + + if ( !strcmp( com_token, "CNetworkHandle" ) || !strcmp( com_token, "CNetworkHandleForDerived" ) ) + { + input = CC_ParseToken( input ); + Assert( !strcmp( com_token, "(") ); + + input = CC_ParseToken( input ); + strcpy( var.m_pType, "CHandle<" ); + do + { + strcat( var.m_pType, com_token ); + strcat( var.m_pType, " " ); + input = CC_ParseToken( input ); + } while( strcmp( com_token, ",") ); + strcat( var.m_pType, ">" ); + + input = CC_ParseToken( input ); + do + { + strcat( var.m_pName, com_token ); + input = CC_ParseToken( input ); + } while( strcmp( com_token, ")") ); + + AddVariable( protection, "EHANDLE", var.m_pName, false ); + return true; + } + + if ( !strcmp( com_token, "CNetworkVector" ) || + !strcmp( com_token, "CNetworkVectorForDerived" ) || + !strcmp( com_token, "CNetworkQAngle" ) ) + { + input = CC_ParseToken( input ); + Assert( !strcmp( com_token, "(") ); + + input = CC_ParseToken( input ); + do + { + strcat( var.m_pName, com_token ); + input = CC_ParseToken( input ); + } while( strcmp( com_token, ")") ); + + AddVariable( protection, "Vector", var.m_pName, false ); + return true; + } + + if ( !strcmp( com_token, "CNetworkColor32" ) ) + { + input = CC_ParseToken( input ); + Assert( !strcmp( com_token, "(") ); + + input = CC_ParseToken( input ); + do + { + strcat( var.m_pName, com_token ); + input = CC_ParseToken( input ); + } while( strcmp( com_token, ")") ); + + AddVariable( protection, "color32", var.m_pName, false ); + return true; + } + + if ( !strcmp( com_token, "CNetworkString" ) ) + { + input = CC_ParseToken( input ); + Assert( !strcmp( com_token, "(") ); + + input = CC_ParseToken( input ); + do + { + strcat( var.m_pName, com_token ); + input = CC_ParseToken( input ); + } while( strcmp( com_token, ",") ); + + input = CC_ParseToken( input ); + do + { + strcat( var.m_pArraySize, com_token ); + input = CC_ParseToken( input ); + } while( strcmp( com_token, ")") ); + + AddVariable( protection, "char *", var.m_pName, true, var.m_pArraySize ); + return true; + } + + if ( !strcmp( com_token, "CNetworkArray" ) || !strcmp( com_token, "CNetworkArrayForDerived" ) ) + { + input = CC_ParseToken( input ); + Assert( !strcmp( com_token, "(") ); + + input = CC_ParseToken( input ); + do + { + strcat( var.m_pType, com_token ); + strcat( var.m_pType, " " ); + input = CC_ParseToken( input ); + } while( strcmp( com_token, ",") ); + + input = CC_ParseToken( input ); + do + { + strcat( var.m_pName, com_token ); + input = CC_ParseToken( input ); + } while( strcmp( com_token, ",") ); + + input = CC_ParseToken( input ); + do + { + strcat( var.m_pArraySize, com_token ); + input = CC_ParseToken( input ); + } while( strcmp( com_token, ")") ); + + AddVariable( protection, var.m_pType, var.m_pName, true, var.m_pArraySize ); + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Parses a class member definition +//----------------------------------------------------------------------------- +bool CClass::ParseClassMember( char *&input, int protection ) +{ + MemberVarParse_t var; + + bool isfunction = false; + bool wascomma = false; + bool skipvar = false; + + if ( ParseNetworkVar( input, protection ) ) + return true; + + strcpy( var.m_pName, com_token ); + if ( !stricmp( var.m_pName, "SHARED_CLASSNAME" ) ) + { + input = CC_ParseToken( input ); + if ( !stricmp( com_token, "(" ) ) + { + char inside[ 256 ]; + char *saveinput = input; + input = CC_DiscardUntilMatchingCharIncludingNesting( input, "()" ); + + int len = input - saveinput; + strncpy( inside, saveinput, len ); + inside[ len ] =0; + + strcat( var.m_pName, "(" ); + strcat( var.m_pName, inside ); + } + } + + do + { + input = CC_ParseToken( input ); + if ( strlen( com_token ) <= 0 ) + break; + + if ( !stricmp( com_token, "(" ) ) + { + char *saveinput = input; + + isfunction = true; + + input = CC_DiscardUntilMatchingCharIncludingNesting( input, "()" ); + + // see if the function is being declared in line here + input = CC_ParseToken( input ); + + if ( !stricmp( com_token, "const" ) ) + { + // Swallow const if we see it + input = CC_ParseToken( input ); + } + + if ( !stricmp( com_token, "{" ) ) + { + input = CC_DiscardUntilMatchingCharIncludingNesting( input, "{}" ); + } + // pure virtual function? + else if ( !stricmp( com_token, "=" ) ) + { + char ch; + input = CC_RawParseChar( input, ";", &ch ); + } + // this was a pointer to a base function + else if ( !stricmp( com_token, "(" ) ) + { + char *end = input - 2; + input = saveinput; + + char pfn[ 256 ]; + int len = end - saveinput; + strncpy( pfn, input, len ); + pfn[ len ] = 0; + + do + { + input = CC_ParseToken( input ); + if ( strlen( com_token ) <= 0 ) + break; + + if ( com_token[0] == '*' ) + { + break; + } + } while ( 1 ); + + if ( com_token[0] == '*' ) + { + // com_token is the variable name + sprintf( var.m_pType, "%s (%s)", var.m_pName, pfn ); + strcpy( var.m_pName, com_token ); + input = end + 1; + } + + if ( *input == '(' ) + input++; + input = CC_DiscardUntilMatchingCharIncludingNesting( input, "()" ); + + isfunction = false; + } + + break; + } + else if ( !stricmp( com_token, "[" ) ) + { + // It's an array + var.m_bArray = true; + char ch; + char *oldinput = input; + do + { + input = CC_RawParseChar( input, "]", &ch ); + if ( *input && ( *input == '[' ) ) + { + input++; + continue; + } + + break; + } while ( 1 ); + int len = input-oldinput - 1; + if ( len > 0 ) + { + strncpy( var.m_pArraySize, oldinput, len ); + } + var.m_pArraySize[ len ] = 0; + break; + } + else if ( !stricmp( com_token, ";" ) ) + { + break; + } + else if ( !stricmp( com_token, ":" ) && !isfunction ) + { + // Eliminate the length specification + input = CC_ParseToken( input ); + continue; + } + else if ( !stricmp( com_token, "," ) ) + { + wascomma = true; + break; + } + // It's a templatized var + else if (( com_token[ strlen( com_token ) - 1 ] == '<' ) && strcmp(var.m_pName, "operator") ) + { + do + { + AppendType( var.m_pName, var.m_pType ); + strcpy( var.m_pName, com_token ); + + input = CC_ParseToken( input ); + if ( strlen( com_token ) <= 0 ) + break; + } + while ( strcmp( com_token, ">" ) ); + + AppendType( var.m_pName, var.m_pType ); + strcpy( var.m_pName, com_token ); + } + else + { + if ( !stricmp( var.m_pName, "typedef" ) || + !stricmp( var.m_pName, "enum" ) || + !stricmp( var.m_pName, "friend" ) ) + { + skipvar = true; + } + AppendType( var.m_pName, var.m_pType ); + strcpy( var.m_pName, com_token ); + continue; + } + + } while ( 1 ); + + if ( strlen( var.m_pType ) >= 1 ) + { + var.m_pType[ strlen( var.m_pType ) - 1 ] = 0; + } + + if ( var.m_pType[0]==0 && + ( !strcmp( var.m_pName, "CUSTOM_SCHEDULES" ) || + !strcmp( var.m_pName, "DEFINE_CUSTOM_SCHEDULE_PROVIDER" ) || + !strcmp( var.m_pName, "DEFINE_CUSTOM_AI" ) || + !strcmp( var.m_pName, "DECLARE_DATADESC" ) || + !strcmp( var.m_pName, "DECLARE_EMBEDDED_DATADESC" ) || + !strcmp( var.m_pName, "DECLARE_SERVERCLASS" ) || + !strcmp( var.m_pName, "DECLARE_CLIENTCLASS" ) || + !strcmp( var.m_pName, "DECLARE_ENTITY_PANEL" ) || + !strcmp( var.m_pName, "DECLARE_MINIMAP_PANEL" ) || + !strcmp( var.m_pName, "MANUALMODE_GETSET_PROP" ) ) ) + { + return true; + } + + if ( var.m_pType[0]==0 && + ( !strcmp( var.m_pName, "DECLARE_PREDICTABLE" ) || + !strcmp( var.m_pName, "DECLARE_EMBEDDED_PREDDESC" ) ) ) + { + m_bHasPredictionData = true; + return true; + } + + /* + if ( var.m_pName[0] == '*' ) + { + strcat( type, " *" ); + + char newname[ 256 ]; + strcpy( newname, &var.m_pName[1] ); + strcpy( var.m_pName, newname ); + } + */ + + if ( isfunction ) + { + CClassMemberFunction *member = AddMember( var.m_pName ); + if ( member ) + { + strcpy( member->m_szType, var.m_pType ); + member->m_Type = (CClassMemberFunction::MEMBERTYPE)protection; + } + } + else + { + // It's a variable + do + { + if ( !skipvar ) + { + AddVariable( protection, var.m_pType, var.m_pName, var.m_bArray, var.m_pArraySize ); + } + else if ( !stricmp( var.m_pName, "BaseClass" ) ) + { + if ( !m_szTypedefBaseClass[0] ) + { + char *p = var.m_pType; + p = CC_ParseToken( p ); + p = CC_ParseToken( p ); + strcpy( m_szTypedefBaseClass, com_token ); + } + } + + if ( !wascomma ) + break; + + input = CC_ParseToken( input ); + if ( strlen( com_token ) <= 0 ) + break; + + // Remove length specifiers + if ( !stricmp( com_token, ":" ) ) + { + input = CC_ParseToken( input ); + input = CC_ParseToken( input ); + } + + if ( !stricmp( com_token, "," ) ) + { + input = CC_ParseToken( input ); + } + + if ( !stricmp( com_token, ";" ) ) + break; + + strcpy( var.m_pName, com_token ); + + } while ( 1 ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Parses a nested class definition +//----------------------------------------------------------------------------- +bool CClass::ParseNestedClass( char *&input ) +{ + if ( stricmp( com_token, "struct" ) && stricmp( com_token, "class" ) ) + return false; + + input = CC_ParseToken( input ); + if ( strlen( com_token ) > 0 ) + { + //vprint( depth, "class %s\n", com_token ); + char decorated[ 256 ]; + sprintf( decorated, "%s::%s", m_szName, com_token ); + + CClass *cl = processor->AddClass( decorated ); + + // Now see if there's a base class + input = CC_ParseToken( input ); + if ( !stricmp( com_token, ":" ) ) + { + // Parse out public and then classname an + input = CC_ParseToken( input ); + if ( !stricmp( com_token, "public" ) ) + { + input = CC_ParseToken( input ); + if ( strlen( com_token ) > 0 ) + { + cl->SetBaseClass( com_token ); + + do + { + input = CC_ParseToken( input ); + } while ( strlen( com_token ) && stricmp( com_token, "{" ) ); + + if ( !stricmp( com_token, "{" ) ) + { + input = cl->ParseClassDeclaration( input ); + } + } + } + } + else if ( !stricmp( com_token, "{" ) ) + { + input = cl->ParseClassDeclaration( input ); + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Parses public/protected/private +//----------------------------------------------------------------------------- +bool CClass::ParseProtection( char *&input, int &protection ) +{ + if ( !stricmp( com_token, "public" ) ) + { + protection = 0; + input = CC_ParseToken( input ); + Assert( !stricmp( com_token, ":" ) ); + return true; + } + else if ( !stricmp( com_token, "protected" ) ) + { + protection = 1; + input = CC_ParseToken( input ); + Assert( !stricmp( com_token, ":" ) ); + return true; + } + else if ( !stricmp( com_token, "private" ) ) + { + protection = 2; + input = CC_ParseToken( input ); + Assert( !stricmp( com_token, ":" ) ); + return true; + } + + return false; +} + + +// parse until } found +// public:, private:, protected: set protection mode, private is initial default +// if token is not one of those, then parse and concatenate all tokens up to the first +// ; or ( +// + +char *CClass::ParseClassDeclaration( char *input ) +{ + int nestcount = 1; + + // public = 0, protected = 1, private = 2; + int protection = 2; + + do + { + input = CC_ParseToken( input ); + if ( strlen( com_token ) <= 0 ) + break; + + if ( com_token[ 1 ] == 0 ) + { + if ( com_token[ 0 ] == '{' ) + { + nestcount++; + } + else if ( com_token[ 0 ] == '}' ) + { + nestcount--; + } + } + + if ( ParseProtection( input, protection ) ) + continue; + + if ( !stricmp( com_token, ";" ) ) + continue; + + if ( com_token[0] == '#' ) + { + // swallow rest of line + input = CC_ParseUntilEndOfLine( input ); + continue; + } + + if ( ParseNestedClass( input ) ) + continue; + + if ( nestcount == 1 ) + { + // See if we found a line that describes the base class + if ( ParseBaseClass( input ) ) + continue; + + ParseClassMember( input, protection ); + } + + } while ( nestcount != 0 && ( strlen( com_token ) >= 0 ) ); + + return input; +} + +static bool ShouldHungarianCheck( char const *name ) +{ + if ( !Q_strncmp( name, "m_", 2 ) || + !Q_strncmp( name, "g_", 2 ) || + !Q_strncmp( name, "s_", 2 ) ) + { + return true; + } + + return false; +} + +enum Required +{ + NEVER = 0, + ALWAYS +}; + +struct Impermissible +{ + char const *prefix; + char const *mustinclude; + int required; // if true, then must match to be permitted +}; + +static Impermissible g_Permissibles[] = +{ + { "fl", "float", ALWAYS }, + { "b", "bool", ALWAYS }, + { "n", "int", ALWAYS }, + { "isz", "string_t", ALWAYS }, + { "i", "float", NEVER }, + { "i", "bool", NEVER }, + { "i", "short", NEVER }, + { "i", "long", NEVER }, + { "ui", "int", ALWAYS }, + { "sz", "char", ALWAYS }, + { "ch", "char", ALWAYS }, + { "uch", "float", NEVER }, + { "uch", "int", NEVER }, + { "uch", "short", NEVER }, + { "uch", "long", NEVER }, + { "s", "short", ALWAYS }, + { "us", "short", ALWAYS }, + { "l", "long", ALWAYS }, + { "ul", "long", ALWAYS }, +// { "f", "int", NEVER }, +// { "f", "short", NEVER }, +// { "f", "int", NEVER }, + { "a", "UtlVector", ALWAYS }, + { "h", "handle", ALWAYS }, + { "p", "*", ALWAYS }, +}; + +void CClass::CheckForHungarianErrors( int& warnings ) +{ + int testcount = sizeof( g_Permissibles ) / sizeof( g_Permissibles[ 0 ] ); + + for ( int i = 0; i < m_nVarCount; i++ ) + { + CClassVariable *var = m_Variables[ i ]; + + // Only check m_, s_, and g_ variables for now + if ( !ShouldHungarianCheck( var->m_szName ) ) + { + continue; + } + + bool isstatic = false; + char *p = var->m_szType; + while ( 1 ) + { + p = CC_ParseToken( p ); + if ( strlen( com_token ) <= 0 ) + break; + if ( !stricmp( com_token, "static" ) ) + { + isstatic = true; + break; + } + } + + // Check for errors + for ( int j = 0; j < testcount; ++j ) + { + Impermissible *tst = &g_Permissibles[ j ]; + + bool match = !Q_strncmp( var->m_szName + 2, tst->prefix, Q_strlen( tst->prefix ) ) ? true : false; + if ( !match ) + continue; + + // The first character after the prefix must be upper case or we skip... + int nextchar = 2 + Q_strlen( tst->prefix ); + + if ( !isupper( var->m_szName[ nextchar ] ) ) + continue; + + bool typeFound = Q_stristr( var->m_szType, tst->mustinclude ) ? true : false; + + switch ( tst->required ) + { + default: + case ALWAYS: + { + if ( !typeFound ) + { + vprint( 1, "%s might have wrong type %s\n", var->m_szName, var->m_szType ); + ++warnings; + } + else + { + return; + } + } + break; + case NEVER: + { + if ( typeFound ) + { + vprint( 1, "%s might have wrong type %s\n", var->m_szName, var->m_szType ); + ++warnings; + } + else + { + return; + } + } + break; + } + } + + + if ( !Q_strncmp( var->m_szName, "m_f", 3 ) && + Q_strncmp( var->m_szName, "m_fl", 4 ) && isupper( var->m_szName[3] ) ) + { + // If it's a "flag" and not a "float" type, it better be a bool or an int + if ( !Q_stristr( var->m_szType, "bool" ) && + !Q_strstr( var->m_szType, "int" ) ) + { + vprint( 1, "%s might have wrong type %s\n", var->m_szName, var->m_szType ); + ++warnings; + return; + } + } + } + +} diff --git a/utils/classcheck/class.h b/utils/classcheck/class.h new file mode 100644 index 0000000..ff1935c --- /dev/null +++ b/utils/classcheck/class.h @@ -0,0 +1,196 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#if !defined( CLASS_H ) +#define CLASS_H +#ifdef _WIN32 +#pragma once +#endif + + +class CTypeDescriptionField +{ +public: + CTypeDescriptionField() + { + m_szVariableName[ 0 ] = 0; + m_szType[ 0 ] = 0; + m_szDefineType[ 0 ] = 0; + m_bCommentedOut = false; + m_bRepresentedInRecvTable = false; + } + + char m_szVariableName[ 128 ]; + char m_szType[ 128 ]; + char m_szDefineType[ 128 ]; + bool m_bCommentedOut; + bool m_bRepresentedInRecvTable; +}; + +class CClassVariable +{ +public: + CClassVariable() + { + m_szName[ 0 ] = 0; + m_szType[ 0 ] = 0; + + m_Type = TPUBLIC; + + m_bKnownType = false; + m_nTypeSize = 0; + + m_bIsArray = false; + m_szArraySize[ 0 ] = 0; + + m_bInRecvTable = false; + + m_TypeSize = 0; + } + + typedef enum + { + TPUBLIC = 0, + TPROTECTED, + TPRIVATE + } VARTYPE; + + char m_szName[ 128 ]; + char m_szType[ 128 ]; + + VARTYPE m_Type; + + bool m_bKnownType; + int m_nTypeSize; + + bool m_bIsArray; + char m_szArraySize[ 128 ]; + + bool m_bInRecvTable; + + int m_TypeSize; +}; + +class CClassMemberFunction +{ +public: + typedef enum + { + TPUBLIC = 0, + TPROTECTED, + TPRIVATE + } MEMBERTYPE; + + char m_szName[ 128 ]; + // Return type + char m_szType[ 128 ]; + + MEMBERTYPE m_Type; + +}; + +class CClassTypedef +{ +public: + char m_szTypeName[ 128 ]; + char m_szAlias[ 128 ]; + +// bool m_bIsTypedefForBaseClass; +}; + +class CClass +{ +public: + enum + { + MAX_VARIABLES = 1024, + MAX_MEMBERS = 1024, + MAX_TDFIELDS = 1024, + }; + + CClass( const char *name ); + ~CClass( void ); + + char *ParseClassDeclaration( char *input ); + + + void SetBaseClass( const char *name ); + void CheckChildOfBaseEntity( const char *baseentityclass ); + bool CheckForMissingTypeDescriptionFields( int& missingcount, bool createtds = false ); + bool CheckForMissingPredictionFields( int& missingcount, bool createtds = false ); + bool CheckForPredictionFieldsInRecvTableNotMarkedAsSuchCorrectly( int& missingcount ); + void AddVariable( int protection, char *type, char *name, bool array, char *arraysize = 0 ); + + // Parsing helper methods + bool ParseProtection( char *&input, int &protection ); + bool ParseNestedClass( char *&input ); + bool ParseBaseClass( char *&input ); + bool ParseClassMember( char *&input, int protection ); + bool ParseNetworkVar( char *&input, int protection ); + void ReportTypeMismatches( CClassVariable *var, CTypeDescriptionField *td ); + + void CheckForHungarianErrors( int& warnings ); + + char m_szName[ 128 ]; + char m_szBaseClass[ 128 ]; + char m_szTypedefBaseClass[ 128 ]; + + CClassVariable *FindVar( const char *name, bool checkbaseclasses = false ); + CClassVariable *AddVar( const char *name ); + int m_nVarCount; + CClassVariable *m_Variables[ MAX_VARIABLES ]; + + CClassMemberFunction *FindMember( const char *name ); + CClassMemberFunction *AddMember( const char *name ); + int m_nMemberCount; + CClassMemberFunction *m_Members[ MAX_MEMBERS ]; + + CTypeDescriptionField *FindTD( const char *name ); + CTypeDescriptionField *AddTD( const char *name, const char *type, const char *definetype, bool incomments ); + int m_nTDCount; + CTypeDescriptionField *m_TDFields[ MAX_TDFIELDS ]; + + CTypeDescriptionField *FindPredTD( const char *name ); + CTypeDescriptionField *AddPredTD( const char *name, const char *type, const char *definetype, bool incomments, bool inrecvtable ); + int m_nPredTDCount; + CTypeDescriptionField *m_PredTDFields[ MAX_TDFIELDS ]; + + + CClass *m_pBaseClass; + + CClass *m_pNext; + + bool m_bDerivedFromCBaseEntity; + bool m_bHasSaveRestoreData; + bool m_bHasPredictionData; + bool m_bHasRecvTableData; + bool m_bConstructPredictableCalled; + + int m_nClassDataSize; + +private: + struct MemberVarParse_t + { + char m_pType[256]; + char m_pTypeModifier[256]; + char m_pName[256]; + char m_pArraySize[ 128 ]; + bool m_bArray; + + MemberVarParse_t() { Reset(); } + + void Reset() + { + m_pType[0] = 0; + m_pTypeModifier[0] = 0; + m_pName[0] = 0; + m_pArraySize[0] = 0; + m_bArray = false; + } + }; +}; + +#endif // CLASS_H
\ No newline at end of file diff --git a/utils/classcheck/classcheck.cpp b/utils/classcheck/classcheck.cpp new file mode 100644 index 0000000..4326065 --- /dev/null +++ b/utils/classcheck/classcheck.cpp @@ -0,0 +1,216 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "stdafx.h" +#include <stdio.h> +#include <windows.h> +#include "classcheck_util.h" +#include "icodeprocessor.h" +#include "tier1/strtools.h" +#include "tier0/dbg.h" + + +SpewRetval_t SpewFunc( SpewType_t type, char const *pMsg ) +{ + printf( "%s", pMsg ); + OutputDebugString( pMsg ); + + if ( type == SPEW_ERROR ) + { + printf( "\n" ); + OutputDebugString( "\n" ); + } + + return SPEW_CONTINUE; +} + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void printusage( void ) +{ + vprint( 0, "usage: classcheck -q -h -m -t <root source directory> <game: hl2 | tf2>\n\ + \t-q = quiet\n\ + \t-h = print class hierarchy\n\ + \t-m = don't print member functions/variables of class\n\ + \t-t = don't print type description errors\n\ + \t-p = don't print prediction description errors\n\ + \t-c = create missing save descriptions\n\ + \t-x = create missing prediction descriptions\n\ + \t-i = specify specific input file to parse\n\ + \t-b = similar to -i, allows specifying files outside client\\server directories\n\ + \t-j = check for Crazy Jay Stelly's mismatched Hungarian notation errors\n\ + \t-l = log to file log.txt\n" ); + + // Exit app + exit( 1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *sourcetreebase - +// *subdir - +// *baseentityclass - +//----------------------------------------------------------------------------- +void ProcessDirectory( const char *game, const char *sourcetreebase, const char *subdir, const char *baseentityclass ) +{ + char rootdirectory[ 256 ]; + sprintf( rootdirectory, "%s\\%s", sourcetreebase, subdir ); + + // check for existence + if ( COM_DirectoryExists( rootdirectory ) ) + { + processor->Process( baseentityclass, game, sourcetreebase, subdir ); + } + else + { + vprint( 0, "Couldn't find directory %s, check path %s\n", rootdirectory, sourcetreebase ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *sourcetreebase - +// *subdir - +// *baseentityclass - +//----------------------------------------------------------------------------- +void ProcessFile( const char *game, const char *sourcetreebase, const char *subdir, const char *baseentityclass, const char *pFileName ) +{ + char rootdirectory[ 256 ]; + sprintf( rootdirectory, "%s\\%s", sourcetreebase, subdir ); + + // check for existence + if ( COM_DirectoryExists( rootdirectory ) ) + { + processor->Process( baseentityclass, game, sourcetreebase, subdir, pFileName ); + } + else + { + vprint( 0, "Couldn't find directory %s, check path %s\n", rootdirectory, sourcetreebase ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CheckLogFile( void ) +{ + if ( processor->GetLogFile() ) + { + _unlink( "log.txt" ); + vprint( 0, " Outputting to log.txt\n" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : argc - +// argv[] - +// Output : int +//----------------------------------------------------------------------------- +int main( int argc, char* argv[] ) +{ + SpewOutputFunc( SpewFunc ); + + vprint( 0, "Valve Software - classcheck.exe (%s)\n", __DATE__ ); + vprint( 0, "--- Game Code Static Analysis ---\n" ); + char *pSpecificFile = NULL; + bool bOutsideGamedir = false; + + int i = 1; + for ( i ; i<argc ; i++) + { + if ( argv[ i ][ 0 ] == '-' ) + { + switch( argv[ i ][ 1 ] ) + { + case 'q': + processor->SetQuiet( true ); + break; + case 'h': + processor->SetPrintHierarchy( true ); + break; + case 'm': + processor->SetPrintMembers( false ); + break; + case 't': + processor->SetPrintTDs( false ); + break; + case 'p': + processor->SetPrintPredTDs( false ); + break; + case 'c': + processor->SetPrintCreateMissingTDs( true ); + break; + case 'x': + processor->SetPrintCreateMissingPredTDs( true ); + break; + case 'i': + if (i < argc-1) + { + pSpecificFile = argv[i+1]; + ++i; + } + break; + case 'b': + if (i < argc-1) + { + pSpecificFile = argv[i+1]; + bOutsideGamedir = true; + ++i; + } + break; + case 'l': + processor->SetLogFile( true ); + break; + case 'j': + processor->SetCheckHungarian( true ); + break; + default: + printusage(); + break; + } + } + } + + if ( argc < 3 || ( i != argc ) ) + { + printusage(); + } + + CheckLogFile(); + + vprint( 0, " Looking for obvious screwups and boneheaded mistakes...\n" ); + + char sourcetreebase[ 256 ]; + strcpy( sourcetreebase, argv[i-2] ); + + Q_StripTrailingSlash( sourcetreebase ); + + if ( !pSpecificFile ) + { + ProcessDirectory( argv[ i-1 ], sourcetreebase, "server", "CBaseEntity" ); + ProcessDirectory( argv[ i-1 ], sourcetreebase, "client", "C_BaseEntity" ); + } + else + { + if ( bOutsideGamedir ) + { + ProcessFile( argv[ i-1 ], sourcetreebase, "", "", pSpecificFile ); + } + else + { + ProcessFile( argv[ i-1 ], sourcetreebase, "server", "CBaseEntity", pSpecificFile ); + ProcessFile( argv[ i-1 ], sourcetreebase, "client", "C_BaseEntity", pSpecificFile ); + } + } + + return 0; +} diff --git a/utils/classcheck/classcheck.vpc b/utils/classcheck/classcheck.vpc new file mode 100644 index 0000000..9019f56 --- /dev/null +++ b/utils/classcheck/classcheck.vpc @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------------- +// CLASSCHECK.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,..\common" + } +} + +$Project "Classcheck" +{ + $Folder "Source Files" + { + $File "class.cpp" + $File "classcheck.cpp" + $File "classcheck_util.cpp" + $File "processmodule.cpp" + } + + $Folder "Header Files" + { + $File "$SRCDIR\public\tier0\basetypes.h" + $File "class.h" + $File "classcheck_util.h" + $File "codeprocessor.h" + $File "icodeprocessor.h" + $File "$SRCDIR\public\tier1\utldict.h" + } +} diff --git a/utils/classcheck/classcheck_util.cpp b/utils/classcheck/classcheck_util.cpp new file mode 100644 index 0000000..e8f4838 --- /dev/null +++ b/utils/classcheck/classcheck_util.cpp @@ -0,0 +1,369 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "stdafx.h" +#include <io.h> +#include <stdio.h> +#include <windows.h> +#include "classcheck_util.h" +#include "icodeprocessor.h" +#include "tier0/dbg.h" + +//----------------------------------------------------------------------------- +// Purpose: +// Input : depth - +// *fmt - +// ... - +//----------------------------------------------------------------------------- +void vprint( int depth, const char *fmt, ... ) +{ + char string[ 8192 ]; + va_list va; + va_start( va, fmt ); + vsprintf( string, fmt, va ); + va_end( va ); + + FILE *fp = NULL; + + if ( processor->GetLogFile() ) + { + fp = fopen( "log.txt", "ab" ); + } + + while ( depth-- > 0 ) + { + printf( " " ); + OutputDebugString( " " ); + if ( fp ) + { + fprintf( fp, " " ); + } + } + + ::printf( "%s", string ); + OutputDebugString( string ); + + if ( fp ) + { + char *p = string; + while ( *p ) + { + if ( *p == '\n' ) + { + fputc( '\r', fp ); + } + fputc( *p, fp ); + p++; + } + fclose( fp ); + } +} + + +bool com_ignorecolons = false; // YWB: Ignore colons as token separators in COM_Parse +bool com_ignoreinlinecomment = false; +static bool s_com_token_unget = false; +char com_token[1024]; +int linesprocessed = 0; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CC_UngetToken( void ) +{ + s_com_token_unget = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : ch - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CC_IsBreakChar( char ch ) +{ + bool brk = false; + switch ( ch ) + { + case '{': + case '}': + case ')': + case '(': + case '[': + case ']': + case '\'': + case '/': + case ',': + case ';': + case '<': + case '>': + brk = true; + break; + + case ':': + if ( !com_ignorecolons ) + brk = true; + break; + default: + break; + } + return brk; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *data - +// Output : char +//----------------------------------------------------------------------------- +char *CC_ParseToken(char *data) +{ + int c; + int len; + + if ( s_com_token_unget ) + { + s_com_token_unget = false; + return data; + } + + len = 0; + com_token[0] = 0; + + if (!data) + return NULL; + +// skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + return NULL; // end of file; + if ( c== '\n' ) + { + linesprocessed++; + } + data++; + } + +// skip // comments + if ( !com_ignoreinlinecomment ) + { + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + } + + if ( c == '/' && data[1] == '*' ) + { + while (data[0] && data[1] && !( data[0] == '*' && data[1] == '/' ) ) + { + if ( *data == '\n' ) + { + linesprocessed++; + } + data++; + } + + if ( data[0] == '*' && data[1] == '/' ) + { + data+=2; + } + goto skipwhite; + } + +// handle quoted strings specially + bool isLstring = data[0] == 'L' && (data[1] == '\"' ); + if ( isLstring ) + { + com_token[len++] = (char)c; + return data+1; + } + + if ( c == '\"' ) + { + data++; + bool bEscapeSequence = false; + while (1) + { + Assert( len < 1024 ); + if ( len >= 1024 ) + { + com_token[ len -1 ] = 0; + return data; + } + + c = *data++; + if ( (c=='\"' && !bEscapeSequence) || !c && len < sizeof( com_token ) - 1 ) + { + com_token[len] = 0; + return data; + } + bEscapeSequence = ( c == '\\' ); + com_token[len] = (char)c; + len++; + } + } + +// parse single characters + if ( CC_IsBreakChar( (char)c ) ) + { + Assert( len < 1024 ); + + com_token[len] = (char)c; + len++; + com_token[len] = 0; + return data+1; + } + +// parse a regular word + do + { + Assert( len < 1024 ); + + com_token[len] = (char)c; + data++; + len++; + c = *data; + + if ( CC_IsBreakChar( (char)c ) ) + break; + } while (c>32 && len < sizeof( com_token ) - 1); + + com_token[len] = 0; + return data; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +// *len - +// Output : unsigned char +//----------------------------------------------------------------------------- +unsigned char *COM_LoadFile( const char *name, int *len) +{ + FILE *fp; + fp = fopen( name, "rb" ); + if ( !fp ) + { + *len = 0; + return NULL; + } + + fseek( fp, 0, SEEK_END ); + *len = ftell( fp ); + fseek( fp, 0, SEEK_SET ); + + unsigned char *buffer = new unsigned char[ *len + 1 ]; + fread( buffer, *len, 1, fp ); + fclose( fp ); + buffer[ *len ] = 0; + + return buffer; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *buffer - +//----------------------------------------------------------------------------- +void COM_FreeFile( unsigned char *buffer ) +{ + delete[] buffer; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *dir - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool COM_DirectoryExists( const char *dir ) +{ + if ( !_access( dir, 0 ) ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *input - +// Output : char +//----------------------------------------------------------------------------- +char *CC_ParseUntilEndOfLine( char *input ) +{ + while (*input && *input != '\n') + input++; + + return input; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *input - +// *ch - +// *breakchar - +// Output : char +//----------------------------------------------------------------------------- +char *CC_RawParseChar( char *input, const char *ch, char *breakchar ) +{ + bool done = false; + int listlen = strlen( ch ); + + do + { + input = CC_ParseToken( input ); + if ( strlen( com_token ) <= 0 ) + break; + + if ( strlen( com_token ) == 1 ) + { + for ( int i = 0; i < listlen; i++ ) + { + if ( com_token[ 0 ] == ch[ i ] ) + { + *breakchar = ch [ i ]; + done = true; + break; + } + } + } + } while ( !done ); + + return input; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *input - +// *pairing - +// Output : char +//----------------------------------------------------------------------------- +char *CC_DiscardUntilMatchingCharIncludingNesting( char *input, const char *pairing ) +{ + int nestcount = 1; + + do + { + input = CC_ParseToken( input ); + if ( strlen( com_token ) <= 0 ) + break; + + if ( strlen( com_token ) == 1 ) + { + if ( com_token[ 0 ] == pairing[ 0 ] ) + { + nestcount++; + } + else if ( com_token[ 0 ] == pairing[ 1 ] ) + { + nestcount--; + } + } + } while ( nestcount != 0 ); + + return input; +}
\ No newline at end of file diff --git a/utils/classcheck/classcheck_util.h b/utils/classcheck/classcheck_util.h new file mode 100644 index 0000000..cc95225 --- /dev/null +++ b/utils/classcheck/classcheck_util.h @@ -0,0 +1,29 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#ifndef CLASSCHECK_UTIL_H +#define CLASSCHECK_UTIL_H +#ifdef _WIN32 +#pragma once +#endif + + +void vprint( int depth, const char *fmt, ... ); +void CC_UngetToken( void ); +char *CC_ParseToken(char *data); +char *CC_DiscardUntilMatchingCharIncludingNesting( char *input, const char *pairing ); +char *CC_RawParseChar( char *input, const char *ch, char *breakchar ); +char *CC_ParseUntilEndOfLine( char *input ); + +extern char com_token[1024]; +extern bool com_ignoreinlinecomment; +extern int linesprocessed; + +unsigned char *COM_LoadFile( const char *name, int *len); +void COM_FreeFile( unsigned char *buffer ); +bool COM_DirectoryExists( const char *dir ); + +#endif // CLASSCHECK_UTIL_H diff --git a/utils/classcheck/codeprocessor.h b/utils/classcheck/codeprocessor.h new file mode 100644 index 0000000..d50b8d2 --- /dev/null +++ b/utils/classcheck/codeprocessor.h @@ -0,0 +1,129 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#if !defined( CODEPROCESSOR_H ) +#define CODEPROCESSOR_H +#ifdef _WIN32 +#pragma once +#endif + +#include "class.h" +#include "icodeprocessor.h" + +#include "utldict.h" + +class CCodeProcessor : public ICodeProcessor +{ +public: + CCodeProcessor( void ); + ~CCodeProcessor( void ); + + void Process( const char *baseentityclass, const char *gamespecific, const char *sourcetreebase, const char *subdir ); + void Process( const char *baseentityclass, const char *gamespecific, const char *sourcetreebase, const char *subdir, const char *pFileName ); + CClass *AddClass( const char *classname ); + + void SetQuiet( bool quiet ); + bool GetQuiet( void ) const; + + void SetPrintHierarchy( bool print ); + bool GetPrintHierarchy( void ) const; + + void SetPrintMembers( bool print ); + bool GetPrintMembers( void ) const; + + void SetPrintTDs( bool print ); + bool GetPrintTDs( void ) const; + + void SetPrintPredTDs( bool print ); + bool GetPrintPredTDs( void ) const; + + void SetPrintCreateMissingTDs( bool print ); + bool GetPrintCreateMissingTDs( void ) const; + + void SetPrintCreateMissingPredTDs( bool print ); + bool GetPrintCreateMissingPredTDs( void ) const; + + void SetLogFile( bool log ); + bool GetLogFile( void ) const; + + void SetCheckHungarian( bool check ); + bool GetCheckHungarian() const; + + CClass *FindClass( const char *name ) const; + + void ResolveBaseClasses( const char *baseentityclass ); + void Clear( void ); + void PrintClassList( void ) const; + void PrintMissingTDFields( void ) const; + void ReportHungarianNotationErrors(); + + int Count( void ) const; + + void SortClassList( void ); + +private: + char *ParseTypeDescription( char *current, bool fIsMacroized ); + char *ParsePredictionTypeDescription( char *current ); + char *ParseReceiveTable( char *current ); + + void ProcessModule( bool forcequiet, int depth, int& maxdepth, int& numheaders, int& skippedfiles, + const char *srcroot, const char *baseroot, const char *root, const char *module ); + void ProcessModules( const char *srcroot, const char *root, const char *rootmodule ); + void PrintResults( const char *baseentityclass ); + void ConstructModuleList_R( int level, const char *baseentityclass, const char *gamespecific, const char *root, char const *srcroot ); + + void AddHeader( int depth, const char *filename, const char *rootmodule ); + + bool CheckShouldSkip( bool forcequiet, int depth, char const *filename, int& numheaders, int& skippedfiles); + bool LoadFile( char **buffer, char *filename, char const *module, bool forcequiet, + int depth, int& filelength, int& numheaders, int& skippedfiles, + char const *srcroot, char const *root, char const *baseroot ); + + // include file path + void CleanupIncludePath(); + void AddIncludePath( const char *pPath ); + void SetupIncludePath( const char *sourcetreebase, const char *subdir, const char *gamespecific ); + + CClass *m_pClassList; + + typedef struct + { + bool skipped; + } CODE_MODULE; + + CUtlDict< CODE_MODULE, int > m_Modules; + CUtlDict< CODE_MODULE, int > m_Headers; + + CUtlVector< char * > m_IncludePath; + + bool m_bQuiet; + bool m_bPrintHierarchy; + bool m_bPrintMembers; + bool m_bPrintTypedescriptionErrors; + bool m_bPrintPredictionDescErrors; + bool m_bCreateMissingTDs; + bool m_bCreateMissingPredTDs; + bool m_bLogToFile; + bool m_bCheckHungarian; + + int m_nFilesProcessed; + int m_nHeadersProcessed; + int m_nClassesParsed; + int m_nOffset; + int m_nBytesProcessed; + int m_nLinesOfCode; + + double m_flStart; + double m_flEnd; + + char m_szCurrentCPP[ 128 ]; + char m_szBaseEntityClass[ 256 ]; + +}; + +extern ICodeProcessor *processor; + +#endif // CODEPROCESSOR_H
\ No newline at end of file diff --git a/utils/classcheck/icodeprocessor.h b/utils/classcheck/icodeprocessor.h new file mode 100644 index 0000000..b110c1a --- /dev/null +++ b/utils/classcheck/icodeprocessor.h @@ -0,0 +1,59 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#if !defined( ICODEPROCESSOR_H ) +#define ICODEPROCESSOR_H +#ifdef _WIN32 +#pragma once +#endif + +#pragma warning( disable : 4127 ) + +class CClass; + +class ICodeProcessor +{ +public: + // Process all files in a directory + virtual void Process( const char *baseentityclass, const char *gamespecific, const char *sourcetreebase, const char *subdir ) = 0; + + // Process a single file + virtual void Process( const char *baseentityclass, const char *gamespecific, const char *sourcetreebase, const char *subdir, const char *pFileName ) = 0; + + virtual CClass *AddClass( const char *classname ) = 0; + virtual CClass *FindClass( const char *name ) const = 0; + + virtual void SetQuiet( bool quiet ) = 0; + virtual bool GetQuiet( void ) const = 0; + + virtual void SetPrintHierarchy( bool print ) = 0; + virtual bool GetPrintHierarchy( void ) const = 0; + + virtual void SetPrintMembers( bool print ) = 0; + virtual bool GetPrintMembers( void ) const = 0; + + virtual void SetPrintTDs( bool print ) = 0; + virtual bool GetPrintTDs( void ) const = 0; + + virtual void SetPrintPredTDs( bool print ) = 0; + virtual bool GetPrintPredTDs( void ) const = 0; + + virtual void SetPrintCreateMissingTDs( bool print ) = 0; + virtual bool GetPrintCreateMissingTDs( void ) const = 0; + + virtual void SetPrintCreateMissingPredTDs( bool print ) = 0; + virtual bool GetPrintCreateMissingPredTDs( void ) const = 0; + + virtual void SetLogFile( bool log ) = 0; + virtual bool GetLogFile( void ) const = 0; + + virtual void SetCheckHungarian( bool check ) = 0; + virtual bool GetCheckHungarian() const = 0; +}; + +extern ICodeProcessor *processor; + +#endif // ICODEPROCESSOR_H
\ No newline at end of file diff --git a/utils/classcheck/processmodule.cpp b/utils/classcheck/processmodule.cpp new file mode 100644 index 0000000..fd5c2b1 --- /dev/null +++ b/utils/classcheck/processmodule.cpp @@ -0,0 +1,1866 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include <assert.h> +#include <time.h> +#include "stdafx.h" +#include <stdio.h> +#include <windows.h> +#include "classcheck_util.h" +#include "codeprocessor.h" + +/* +================ +UTIL_FloatTime +================ +*/ +double UTIL_FloatTime (void) +{ +// more precise, less portable + clock_t current; + static clock_t base; + static bool first = true; + + current = clock(); + + if ( first ) + { + first = false; + + base = current; + } + + return (double)(current - base)/(double)CLOCKS_PER_SEC; +} + +CClass *CCodeProcessor::FindClass( const char *name ) const +{ + CClass *cl = m_pClassList; + while ( cl ) + { + if ( !stricmp( cl->m_szName, name ) ) + return cl; + + cl = cl->m_pNext; + } + return NULL; +} + +void ClearMissingTypes(); + +void CCodeProcessor::Clear( void ) +{ + ClearMissingTypes(); + + CClass *cl = m_pClassList, *next; + while ( cl ) + { + next = cl->m_pNext; + delete cl; + cl = next; + } + m_pClassList = NULL; +} + + + +int CCodeProcessor::Count( void ) const +{ + int c = 0; + CClass *cl = m_pClassList; + while ( cl ) + { + c++; + cl = cl->m_pNext; + } + return c; +} + +int FnClassSortCompare( const void *elem1, const void *elem2 ) +{ + CClass *c1 = *(CClass **)elem1; + CClass *c2 = *(CClass **)elem2; + + return ( stricmp( c1->m_szName, c2->m_szName ) ); +} + +void CCodeProcessor::SortClassList( void ) +{ + int n = Count(); + if ( n <= 1 ) + return; + + CClass **ppList = new CClass *[ n ]; + if ( ppList ) + { + CClass *cl; + int i; + for ( i = 0, cl = m_pClassList; i < n; i++, cl = cl->m_pNext ) + { + ppList[ i ] = cl; + } + + qsort( ppList, n, sizeof( CClass * ), FnClassSortCompare ); + + for ( i = 0; i < n - 1; i++ ) + { + ppList[ i ]->m_pNext = ppList[ i + 1 ]; + } + ppList[ i ]->m_pNext = NULL; + m_pClassList = ppList[ 0 ]; + } + delete[] ppList; +} + +void CCodeProcessor::ResolveBaseClasses( const char *baseentityclass ) +{ + SortClassList(); + + CClass *cl = m_pClassList; + while ( cl ) + { + if ( cl->m_szBaseClass[0] ) + { + cl->m_pBaseClass = FindClass( cl->m_szBaseClass ); + if ( !cl->m_pBaseClass ) + { + //vprint( 0, "couldn't find base class %s for %s\n", cl->m_szBaseClass, cl->m_szName ); + } + } + + cl = cl->m_pNext; + } + + cl = m_pClassList; + while ( cl ) + { + cl->CheckChildOfBaseEntity( baseentityclass ); + cl = cl->m_pNext; + } +} + +void CCodeProcessor::PrintMissingTDFields( void ) const +{ + int classcount; + int fieldcount; + int c; + + CClass *cl; + + if ( GetPrintTDs() ) + { + classcount = 0; + fieldcount = 0; + cl = m_pClassList; + while ( cl ) + { + if ( cl->m_bDerivedFromCBaseEntity || cl->m_bHasSaveRestoreData ) + { + if ( cl->CheckForMissingTypeDescriptionFields( c ) ) + { + classcount++; + fieldcount += c; + } + } + cl = cl->m_pNext; + } + + if ( fieldcount ) + { + vprint( 0, "\nSummary: %i fields missing from %i classes\n", fieldcount, classcount ); + } + else + { + if ( !classcount ) + { + vprint( 0, "\nSummary: no saverestore info present\n"); + } + else + { + vprint( 0, "\nSummary: no errors for %i classes\n", classcount ); + } + } + + vprint( 0, "\n" ); + } + + if ( GetPrintPredTDs() ) + { + //Now check prediction stuff + classcount = 0; + fieldcount = 0; + cl = m_pClassList; + while ( cl ) + { + if ( cl->m_bDerivedFromCBaseEntity || cl->m_bHasPredictionData ) + { + if ( cl->CheckForMissingPredictionFields( c, false ) ) + { + classcount++; + fieldcount += c; + } + } + cl = cl->m_pNext; + } + + if ( fieldcount ) + { + vprint( 0, "\nSummary: %i prediction fields missing from %i classes\n", fieldcount, classcount ); + } + else + { + if ( !classcount ) + { + vprint( 0, "\nSummary: no prediction info present\n"); + } + else + { + vprint( 0, "\nSummary: no errors for %i predictable classes\n", classcount ); + } + } + + vprint( 0, "\n" ); + } + + if ( GetPrintCreateMissingTDs() ) + { + //Now check prediction stuff + classcount = 0; + fieldcount = 0; + cl = m_pClassList; + while ( cl ) + { + if ( cl->m_bDerivedFromCBaseEntity || cl->m_bHasSaveRestoreData ) + { + if ( cl->CheckForMissingTypeDescriptionFields( c, true ) ) + { + classcount++; + fieldcount += c; + } + } + cl = cl->m_pNext; + } + + if ( fieldcount ) + { + vprint( 0, "\nSummary: %i saverestore fields missing from %i classes\n", fieldcount, classcount ); + } + else + { + if ( !classcount ) + { + vprint( 0, "\nSummary: no saverestore info present\n"); + } + else + { + vprint( 0, "\nSummary: no errors for %i classes\n", classcount ); + } + } + + vprint( 0, "\n" ); + } + + if ( GetPrintCreateMissingPredTDs() ) + { + //Now check prediction stuff + classcount = 0; + fieldcount = 0; + cl = m_pClassList; + while ( cl ) + { + if ( cl->m_bDerivedFromCBaseEntity || cl->m_bHasPredictionData ) + { + if ( cl->CheckForMissingPredictionFields( c, true ) ) + { + classcount++; + fieldcount += c; + } + } + cl = cl->m_pNext; + } + + if ( fieldcount ) + { + vprint( 0, "\nSummary: %i prediction fields missing from %i classes\n", fieldcount, classcount ); + } + else + { + if ( !classcount ) + { + vprint( 0, "\nSummary: no prediction info present\n"); + } + else + { + vprint( 0, "\nSummary: no errors for %i predictable classes\n", classcount ); + } + } + + vprint( 0, "\n" ); + } + + // Now check for things that are in the prediction TD but not marked correctly as being part of the sendtable + { + //Now check prediction stuff + classcount = 0; + fieldcount = 0; + cl = m_pClassList; + while ( cl ) + { + if ( cl->m_bDerivedFromCBaseEntity || cl->m_bHasPredictionData ) + { + if ( cl->CheckForPredictionFieldsInRecvTableNotMarkedAsSuchCorrectly( c ) ) + { + classcount++; + fieldcount += c; + } + } + cl = cl->m_pNext; + } + + vprint( 0, "\n" ); + } + + // Print stuff derived from CBaseEntity that doesn't have save/restore data + vprint( 0, "\nMissing DATADESC tables:\n\n" ); + cl = m_pClassList; + while ( cl ) + { + if ( cl->m_bDerivedFromCBaseEntity && !cl->m_bHasSaveRestoreData && cl->m_nVarCount ) + { + vprint( 0, "\t%s\n", cl->m_szName ); + } + cl = cl->m_pNext; + } + vprint( 0, "\n" ); +} + +void CCodeProcessor::ReportHungarianNotationErrors() +{ + if ( !GetCheckHungarian() ) + return; + + vprint( 0, "\tChecking for hungarian notation issues\n" ); + + CClass *cl = m_pClassList; + int classcount = 0; + int warningcount = 0; + while ( cl ) + { + int c = 0; + + cl->CheckForHungarianErrors( c ); + + classcount++; + warningcount += c; + + cl = cl->m_pNext; + } + + vprint( 0, "\tFound %i notation errors across %i classes\n", classcount, warningcount ); +} + +void CCodeProcessor::PrintClassList( void ) const +{ + if ( GetPrintHierarchy() ) + { + vprint( 0, "\nClass Summary\n\n" ); + } + + CClass *cl = m_pClassList; + + while ( cl ) + { + if ( cl->m_bDerivedFromCBaseEntity ) + { + bool missing = false; + char missingwarning[ 128 ]; + + missingwarning[0]=0; + if ( cl->m_szTypedefBaseClass[0] ) + { + if ( stricmp( cl->m_szBaseClass, cl->m_szTypedefBaseClass ) ) + { + vprint( 0, "class %s has incorrect typedef %s BaseClass\n", cl->m_szName, cl->m_szTypedefBaseClass ); + } + } + else if ( cl->m_szBaseClass[ 0 ] ) + { + missing = true; + sprintf( missingwarning, ", missing typedef %s BaseClass", cl->m_szBaseClass ); + } + + if ( GetPrintHierarchy() || missing ) + { + vprint( 0, "class %s%s\n", cl->m_szName, missing ? missingwarning : "" ); + } + + int level = 1; + CClass *base = cl->m_pBaseClass; + while ( base ) + { + if ( GetPrintHierarchy() ) + { + vprint( level++, "public %s\n", base->m_szName ); + } + base = base->m_pBaseClass; + } + + int i; + + if ( GetPrintHierarchy() && GetPrintMembers() ) + { + + if ( cl->m_nMemberCount ) + { + vprint( 1, "\nMember functions:\n\n" ); + } + + for ( i = 0; i < cl->m_nMemberCount; i++ ) + { + CClassMemberFunction *member = cl->m_Members[ i ]; + + if ( member->m_szType[0] ) + { + vprint( 1, "%s %s();\n", member->m_szType, member->m_szName ); + } + else + { + char *p = member->m_szName; + if ( *p == '~' ) + p++; + + if ( stricmp( p, cl->m_szName ) ) + { + vprint( 0, "class %s has member function %s with no return type!!!\n", + cl->m_szName, member->m_szName ); + } + vprint( 1, "%s();\n", member->m_szName ); + } + } + + if ( cl->m_nVarCount ) + { + vprint( 1, "\nMember Variables\n\n" ); + } + + + for ( i = 0; i < cl->m_nVarCount; i++ ) + { + CClassVariable *var = cl->m_Variables[ i ]; + + if ( var->m_bIsArray ) + { + if ( var->m_szArraySize[0]==0 ) + { + vprint( 1, "%s %s[];\n", var->m_szType, var->m_szName ); + } + else + { + vprint( 1, "%s %s[ %s ];\n", var->m_szType, var->m_szName, var->m_szArraySize ); + } + } + else + { + vprint( 1, "%s %s;\n", var->m_szType, var->m_szName ); + } + } + + if ( cl->m_nTDCount ) + { + vprint( 1, "\nSave/Restore TYPEDESCRIPTION\n\n" ); + } + + for ( i = 0; i < cl->m_nTDCount; i++ ) + { + CTypeDescriptionField *td = cl->m_TDFields[ i ]; + if ( td->m_bCommentedOut ) + { + vprint( 1, "// " ); + } + else + { + vprint( 1, "" ); + } + + vprint( 0, "%s( %s, %s, %s, ... )\n", td->m_szDefineType, cl->m_szName, td->m_szVariableName, td->m_szType ); + } + + if ( !cl->m_bHasSaveRestoreData ) + { + // vprint( 1, "\nSave/Restore TYPEDESCRIPTION not specified for class\n\n" ); + } + + if ( cl->m_nPredTDCount ) + { + vprint( 1, "\nPrediction TYPEDESCRIPTION\n\n" ); + } + + for ( i = 0; i < cl->m_nPredTDCount; i++ ) + { + CTypeDescriptionField *td = cl->m_PredTDFields[ i ]; + if ( td->m_bCommentedOut ) + { + vprint( 1, "// " ); + } + else + { + vprint( 1, "" ); + } + + vprint( 0, "%s( %s, %s, %s, ... )\n", td->m_szDefineType, cl->m_szName, td->m_szVariableName, td->m_szType ); + } + + if ( !cl->m_bHasPredictionData ) + { + // vprint( 1, "\nPrediction TYPEDESCRIPTION not specified for class\n\n" ); + } + } + + if ( GetPrintHierarchy() ) + { + vprint( 0, "\n" ); + } + } + + cl = cl->m_pNext; + } +} + +CClass *CCodeProcessor::AddClass( const char *classname ) +{ + CClass *cl = FindClass( classname ); + if ( !cl ) + { + cl = new CClass( classname ); + + m_nClassesParsed++; + + cl->m_pNext = m_pClassList; + m_pClassList = cl; + } + return cl; +} + + + +char *CCodeProcessor::ParseTypeDescription( char *current, bool fIsMacroized ) +{ + // Next token is classname then :: then variablename then braces then = then { + char classname[ 256 ]; + char variablename[ 256 ]; + + if ( !fIsMacroized ) + { + current = CC_ParseToken( current ); + if ( strlen( com_token ) <= 0 ) + return current; + + strcpy( classname, com_token ); + if ( classname[0]=='*' ) + return current; + + current = CC_ParseToken( current ); + if (stricmp( com_token, ":" ) ) + { + return current; + } + + current = CC_ParseToken( current ); + Assert( !stricmp( com_token, ":" ) ); + + current = CC_ParseToken( current ); + if ( strlen( com_token ) <= 0 ) + return current; + + strcpy( variablename, com_token ); + } + else + { + current = CC_ParseToken( current ); + if (stricmp( com_token, "(" ) ) + { + return current; + } + + current = CC_ParseToken( current ); + if ( strlen( com_token ) <= 0 ) + return current; + + strcpy( classname, com_token ); + if ( classname[0]=='*' ) + return current; + + current = CC_ParseToken( current ); + if (stricmp( com_token, ")" ) ) + { + return current; + } + + // It's macro-ized + strcpy( variablename, "m_DataDesc" ); + } + if ( !fIsMacroized ) + { + char ch; + current = CC_RawParseChar( current, "{", &ch ); + Assert( ch == '{' ); + if ( strlen( com_token ) <= 0 ) + return current; + } + + com_ignoreinlinecomment = true; + bool insidecomment = false; + + // Now parse typedescription line by line + while ( 1 ) + { + current = CC_ParseToken( current ); + if ( strlen( com_token ) <= 0 ) + break; + + // Go to next line + if ( !stricmp( com_token, "," ) ) + continue; + + // end + if ( !fIsMacroized ) + { + if ( !stricmp( com_token, "}" ) ) + break; + } + else + { + if ( !stricmp( com_token, "END_DATADESC" ) || + !stricmp( com_token, "END_BYTESWAP_DATADESC" ) ) + break; + } + + // skip #ifdef's inside of typedescs + if ( com_token[0]=='#' ) + { + current = CC_ParseUntilEndOfLine( current ); + continue; + } + + if ( !stricmp( com_token, "/" ) ) + { + current = CC_ParseToken( current ); + if ( !stricmp( com_token, "/" ) ) + { + // There are two styles supported. One is to have the member definition present but commented out: + // DEFINE_FIELD( m_member, FIELD_INTEGER ), + // the other is to have a comment where the first token of the comment is a member name: + // m_member + current = CC_ParseToken( current ); + if ( !strnicmp( com_token, "DEFINE_", 7 ) ) + { + CC_UngetToken(); + insidecomment = true; + } + else + { + char commentedvarname[ 256 ]; + strcpy( commentedvarname, com_token ); + + CClass *cl = FindClass( classname ); + if ( cl ) + { + if ( !cl->FindTD( commentedvarname ) ) + { + cl->AddTD( commentedvarname, "", "", true ); + } + // Mark that it has a data table + cl->m_bHasSaveRestoreData = true; + } + current = CC_ParseUntilEndOfLine( current ); + } + continue; + } + } + + com_ignoreinlinecomment = false; + + // Parse a typedescription line + char definetype[ 256 ]; + strcpy( definetype, com_token ); + + current = CC_ParseToken( current ); + if ( stricmp( com_token, "(" ) ) + break; + + char varname[ 256 ]; + current = CC_ParseToken( current ); + + strcpy( varname, com_token ); + + + char vartype[ 256 ]; + + vartype[0]=0; + + if ( !stricmp( definetype, "DEFINE_FUNCTION" ) || + !stricmp( definetype, "DEFINE_THINKFUNC" ) || + !stricmp( definetype, "DEFINE_ENTITYFUNC" ) || + !stricmp( definetype, "DEFINE_USEFUNC" ) || + !stricmp( definetype, "DEFINE_OUTPUT" ) || + !stricmp( definetype, "DEFINE_INPUTFUNC" ) ) + { + strcpy( vartype, "funcptr" ); + } + else if ( !stricmp(definetype, "DEFINE_FIELD") || + !stricmp(definetype, "DEFINE_INDEX") || + !stricmp(definetype, "DEFINE_KEYFIELD") || + !stricmp(definetype, "DEFINE_KEYFIELD_NOT_SAVED") || + !stricmp(definetype, "DEFINE_UTLVECTOR") || + !stricmp(definetype, "DEFINE_GLOBAL_FIELD") || + !stricmp(definetype, "DEFINE_GLOBAL_KEYFIELD") || + !stricmp(definetype, "DEFINE_CUSTOM_FIELD") || + !stricmp(definetype, "DEFINE_INPUT") || + !stricmp(definetype, "DEFINE_AUTO_ARRAY") || + !stricmp(definetype, "DEFINE_AUTO_ARRAY_KEYFIELD") || + !stricmp(definetype, "DEFINE_AUTO_ARRAY2D") || + !stricmp(definetype, "DEFINE_ARRAY") ) + { + // skip comma + current = CC_ParseToken( current ); + if (!strcmp( com_token, "[" )) + { + // Read array... + current = CC_ParseToken( current ); + strcat( varname, "[" ); + strcat( varname, com_token ); + current = CC_ParseToken( current ); + + // eat everything until the next "]" + while (strcmp( com_token, "]") != 0) + { + strcat( varname, com_token ); + current = CC_ParseToken( current ); + } + + if ( strcmp( com_token, "]" )) + { + current = current; + } + + strcat( varname, "]" ); + + // skip comma + current = CC_ParseToken( current ); + } + + current = CC_ParseToken( current ); + + strcpy( vartype, com_token ); + } + + // Jump to end of definition + int nParenCount = 1; + do + { + current = CC_ParseToken( current ); + if ( strlen( com_token ) <= 0 ) + break; + + if ( !stricmp( com_token, "(" ) ) + { + ++nParenCount; + } + else if ( !stricmp( com_token, ")" ) ) + { + if ( --nParenCount == 0 ) + { + break; + } + } + + } while ( 1 ); + +// vprint( 2, "%s%s::%s %s %s %s\n", +// insidecomment ? "// " : "", +// classname, variablename, +// definetype, varname, vartype ); + + CClass *cl = FindClass( classname ); + if ( cl ) + { + if ( strcmp( vartype, "funcptr" ) && cl->FindTD( varname ) ) + { + vprint( 0, "class %s::%s already has typedescription entry for field %s\n", classname, variablename, varname ); + } + else + { + cl->AddTD( varname, vartype, definetype, insidecomment ); + } + // Mark that it has a data table + cl->m_bHasSaveRestoreData = true; + } + insidecomment = false; + com_ignoreinlinecomment = true; + } + + com_ignoreinlinecomment = false; + + return current; +} + +char *CCodeProcessor::ParseReceiveTable( char *current ) +{ + // Next token is open paren, then classname close paren, then { + char classname[ 256 ]; + + current = CC_ParseToken( current ); + if (stricmp( com_token, "(" ) ) + { + return current; + } + + current = CC_ParseToken( current ); + if ( strlen( com_token ) <= 0 ) + return current; + + strcpy( classname, com_token ); + if ( classname[0]=='*' ) + return current; + if ( !strcmp( classname, "className" ) ) + return current; + if ( !strcmp( classname, "clientClassName" ) ) + return current; + + CClass *cl = FindClass( classname ); + if ( cl ) + { + cl->m_bHasRecvTableData = true; + } + + CClass *leafClass = cl; + + // parse until end of line + current = CC_ParseUntilEndOfLine( current ); + + // Now parse recvtable entries line by line + while ( 1 ) + { + cl = leafClass; + + current = CC_ParseToken( current ); + if ( strlen( com_token ) <= 0 ) + break; + + // Go to next line + if ( !stricmp( com_token, "," ) ) + continue; + + // end + if ( !stricmp( com_token, "END_RECV_TABLE" ) ) + break; + + // skip #ifdef's inside of recv tables + if ( com_token[0]=='#' ) + { + current = CC_ParseUntilEndOfLine( current ); + continue; + } + + // Parse recproxy line + char recvproptype[ 256 ]; + strcpy( recvproptype, com_token ); + + if ( strnicmp( recvproptype, "RecvProp", strlen( "RecvProp" ) ) ) + { + current = CC_ParseUntilEndOfLine( current ); + continue; + } + + if ( !strcmp( recvproptype, "RecvPropArray" ) ) + { + current = CC_ParseToken( current ); + if ( stricmp( com_token, "(" ) ) + break; + + current = CC_ParseToken( current ); + if ( strnicmp( recvproptype, "RecvProp", strlen( "RecvProp" ) ) ) + { + current = CC_ParseUntilEndOfLine( current ); + continue; + } + } + + current = CC_ParseToken( current ); + if ( stricmp( com_token, "(" ) ) + break; + + // Read macro or fieldname + current = CC_ParseToken( current ); + + char varname[ 256 ]; + + if ( !strnicmp( com_token, "RECVINFO", strlen( "RECVINFO" ) ) ) + { + current = CC_ParseToken( current ); + if ( stricmp( com_token, "(" ) ) + break; + current = CC_ParseToken( current ); + } + else + { + current = CC_ParseUntilEndOfLine( current ); + continue; + } + + strcpy( varname, com_token ); + + current = CC_ParseUntilEndOfLine( current ); + + if ( cl ) + { + // Look up the var + CClassVariable *classVar = cl->FindVar( varname, true ); + if ( classVar ) + { + classVar->m_bInRecvTable = true; + } + else + { + char cropped[ 256 ]; + char root[ 256 ]; + strcpy( cropped, varname ); + + while ( 1 ) + { + // See if varname is an embedded var + char *spot = strstr( cropped, "." ); + if ( spot ) + { + strcpy( root, cropped ); + root[ spot - cropped ] = 0; + strcpy( cropped, spot + 1 ); + + classVar = cl->FindVar( root, true ); + } + else + { + classVar = cl->FindVar( cropped, true ); + break; + } + + if ( classVar ) + break; + } + + if ( !classVar ) + { + vprint( 0, "class %s::%s missing, but referenced by RecvTable!!!\n", classname, varname ); + } + else + { + classVar->m_bInRecvTable = true; + } + } + } + else + { + vprint( 0, "class %s::%s found in RecvTable, but no such class is known!!!\n", classname, varname ); + } + } + + return current; +} + +char *CCodeProcessor::ParsePredictionTypeDescription( char *current ) +{ + // Next token is open paren, then classname close paren, then { + char classname[ 256 ]; + char variablename[ 256 ]; + + current = CC_ParseToken( current ); + if (stricmp( com_token, "(" ) ) + { + return current; + } + + current = CC_ParseToken( current ); + if ( strlen( com_token ) <= 0 ) + return current; + + strcpy( classname, com_token ); + if ( classname[0]=='*' ) + return current; + + CClass *cl = FindClass( classname ); + if ( cl ) + { + cl->m_bHasPredictionData = true; + } + + current = CC_ParseToken( current ); + if (stricmp( com_token, ")" ) ) + { + return current; + } + + // It's macro-ized + strcpy( variablename, "m_PredDesc" ); + + com_ignoreinlinecomment = true; + bool insidecomment = false; + + // Now parse typedescription line by line + while ( 1 ) + { + current = CC_ParseToken( current ); + if ( strlen( com_token ) <= 0 ) + break; + + // Go to next line + if ( !stricmp( com_token, "," ) ) + continue; + + // end + if ( !stricmp( com_token, "END_PREDICTION_DATA" ) ) + break; + + // skip #ifdef's inside of typedescs + if ( com_token[0]=='#' ) + { + current = CC_ParseUntilEndOfLine( current ); + continue; + } + + if ( !stricmp( com_token, "/" ) ) + { + current = CC_ParseToken( current ); + if ( !stricmp( com_token, "/" ) ) + { + current = CC_ParseToken( current ); + if ( !strnicmp( com_token, "DEFINE_", 7 ) ) + { + CC_UngetToken(); + insidecomment = true; + } + else + { + current = CC_ParseUntilEndOfLine( current ); + } + continue; + } + } + + com_ignoreinlinecomment = false; + + // Parse a typedescription line + char definetype[ 256 ]; + strcpy( definetype, com_token ); + + current = CC_ParseToken( current ); + if ( stricmp( com_token, "(" ) ) + break; + + char varname[ 256 ]; + current = CC_ParseToken( current ); + + strcpy( varname, com_token ); + + + char vartype[ 256 ]; + + vartype[0]=0; + + if ( stricmp( definetype, "DEFINE_FUNCTION" ) ) + { + // skip comma + current = CC_ParseToken( current ); + + current = CC_ParseToken( current ); + + strcpy( vartype, com_token ); + } + else + { + strcpy( vartype, "funcptr" ); + } + + bool inrecvtable = false; + // Jump to end of definition + int nParenCount = 1; + do + { + current = CC_ParseToken( current ); + if ( strlen( com_token ) <= 0 ) + break; + + if ( !stricmp( com_token, "(" ) ) + { + ++nParenCount; + } + else if ( !stricmp( com_token, ")" ) ) + { + if ( --nParenCount == 0 ) + { + break; + } + } + + if ( !stricmp( com_token, "FTYPEDESC_INSENDTABLE" ) ) + { + inrecvtable = true; + } + + } while ( 1 ); + + /* + vprint( 2, "%s%s::%s %s %s %s\n", + insidecomment ? "// " : "", + classname, variablename, + definetype, varname, vartype ); + */ + + if ( cl ) + { + if ( cl->FindPredTD( varname ) ) + { + vprint( 0, "class %s::%s already has prediction typedescription entry for field %s\n", classname, variablename, varname ); + } + else + { + cl->AddPredTD( varname, vartype, definetype, insidecomment, inrecvtable ); + } + } + insidecomment = false; + com_ignoreinlinecomment = true; + } + + com_ignoreinlinecomment = false; + + return current; +} + +void CCodeProcessor::AddHeader( int depth, const char *filename, const char *rootmodule ) +{ +// if ( depth < 1 ) +// return; + if ( depth != 1 ) + return; + + // Check header list + int idx = m_Headers.Find( filename ); + if ( idx != m_Headers.InvalidIndex() ) + { + vprint( 0, "%s included twice in module %s\n", filename, rootmodule ); + return; + } + + CODE_MODULE module; + module.skipped = false; + + m_Headers.Insert( filename, module ); +} + +bool CCodeProcessor::CheckShouldSkip( bool forcequiet, int depth, char const *filename, int& numheaders, int& skippedfiles) +{ + int idx = m_Modules.Find( filename ); + if ( idx == m_Modules.InvalidIndex() ) + return false; + + CODE_MODULE *module = &m_Modules[ idx ]; + + if ( forcequiet ) + { + m_nHeadersProcessed++; + numheaders++; + + if ( module->skipped ) + { + skippedfiles++; + } + } + + AddHeader( depth, filename, m_szCurrentCPP ); + return true; +} + +bool CCodeProcessor::LoadFile( char **buffer, char *filename, char const *module, bool forcequiet, + int depth, int& filelength, int& numheaders, int& skippedfiles, + char const *srcroot, char const *root, char const *baseroot ) +{ + for ( int i = 0; i < m_IncludePath.Count(); ++i ) + { + // Load the base module + sprintf( filename, "%s\\%s", m_IncludePath[i], module ); + strlwr( filename ); + + if ( CheckShouldSkip( forcequiet, depth, filename, numheaders, skippedfiles ) ) + { + return false; + } + + *buffer = (char *)COM_LoadFile( filename, &filelength ); + if ( *buffer ) + return true; + } + + return false; +} + +static bool SkipFile( char const *module ) +{ + if ( !stricmp( module, "predictable_entity.h" ) ) + return true; + if ( !stricmp( module, "baseentity_shared.h" ) ) + return true; + if ( !stricmp( module, "baseplayer_shared.h" ) ) + return true; + if ( !stricmp( module, "tf_tacticalmap.cpp" ) ) + return true; + if ( !stricmp( module, "techtree.cpp" ) ) + return true; + if ( !stricmp( module, "techtree_parse.cpp" ) ) + return true; + + return false; +} + +void CCodeProcessor::ProcessModule( bool forcequiet, int depth, int& maxdepth, int& numheaders, int& skippedfiles, + const char *srcroot, const char *baseroot, const char *root, const char *module ) +{ + char filename[ 256 ]; + + if ( depth > maxdepth ) + { + maxdepth = depth; + } + int filelength; + char *buffer = NULL; + + // Always skip these particular modules/headers + if ( SkipFile( module ) ) + { + CODE_MODULE module; + module.skipped = true; + + m_Modules.Insert( filename, module ); + + skippedfiles++; + return; + } + + if ( !LoadFile( &buffer, filename, module, forcequiet, depth, filelength, numheaders, skippedfiles, + srcroot, root, baseroot ) ) + { + CODE_MODULE module; + module.skipped = true; + m_Modules.Insert( filename, module ); + skippedfiles++; + return; + } + + Assert( buffer ); + + m_nBytesProcessed += filelength; + + CODE_MODULE m; + m.skipped = false; + m_Modules.Insert( filename, m ); + + if ( !forcequiet ) + { + strcpy( m_szCurrentCPP, filename ); + } + + AddHeader( depth, filename, m_szCurrentCPP ); + + bool onclient = !strnicmp( m_szBaseEntityClass, "C_", 2 ) ? true : false; + + // Parse tokens looking for #include directives or class starts + char *current = buffer; + + current = CC_ParseToken( current ); + while ( 1 ) + { + // No more tokens + if ( !current ) + break; + + if ( !stricmp( com_token, "#include" ) ) + { + current = CC_ParseToken( current ); + if ( strlen( com_token ) > 0 && + com_token[ 0 ] != '<' ) + { + //vprint( "#include %s\n", com_token ); + m_nHeadersProcessed++; + numheaders++; + ProcessModule( true, depth + 1, maxdepth, numheaders, skippedfiles, srcroot, baseroot, root, com_token ); + } + } + else if ( !stricmp( com_token, "class" ) || + !stricmp( com_token, "struct" ) ) + { + current = CC_ParseToken( current ); + if ( strlen( com_token ) > 0 ) + { + //vprint( depth, "class %s\n", com_token ); + + CClass *cl = AddClass( com_token ); + + // Now see if there's a base class + current = CC_ParseToken( current ); + if ( !stricmp( com_token, ":" ) ) + { + // Parse out public and then classname an + current = CC_ParseToken( current ); + if ( !stricmp( com_token, "public" ) ) + { + current = CC_ParseToken( current ); + if ( strlen( com_token ) > 0 ) + { + cl->SetBaseClass( com_token ); + + do + { + current = CC_ParseToken( current ); + } while ( strlen( com_token ) && stricmp( com_token, "{" ) ); + + if ( !stricmp( com_token, "{" ) ) + { + current = cl->ParseClassDeclaration( current ); + } + } + } + } + else if ( !stricmp( com_token, "{" ) ) + { + current = cl->ParseClassDeclaration( current ); + } + } + } + else if ( !strnicmp( com_token, "PREDICTABLE_CLASS", strlen( "PREDICTABLE_CLASS" ) ) ) + { + char prefix[ 32 ]; + prefix[ 0 ] = 0; + int type = 0; + int bases = 1; + int usebase = 0; + + if ( !stricmp( com_token, "PREDICTABLE_CLASS_ALIASED" ) ) + { + type = 2; + bases = 2; + if ( onclient ) + { + strcpy( prefix, "C_" ); + } + else + { + strcpy( prefix, "C" ); + usebase = 1; + } + } + else if ( !stricmp( com_token, "PREDICTABLE_CLASS_SHARED" ) ) + { + type = 1; + bases = 1; + } + else if ( !stricmp( com_token, "PREDICTABLE_CLASS" ) ) + { + type = 0; + bases = 1; + if ( onclient ) + { + strcpy( prefix, "C_" ); + } + else + { + strcpy( prefix, "C" ); + } + } + else if ( !stricmp( com_token, "PREDICTABLE_CLASS_ALIASED_PREFIXED" ) ) + { + // Nothing + } + else + { + vprint( 0, "PREDICTABLE_CLASS of unknown type!!! %s\n", com_token ); + } + + // parse the ( + current = CC_ParseToken( current ); + if ( !strcmp( com_token, "(" ) ) + { + // Now the classname + current = CC_ParseToken( current ); + if ( strlen( com_token ) > 0 ) + { + //vprint( depth, "class %s\n", com_token ); + + CClass *cl = AddClass( com_token ); + + // Now see if there's a base class + current = CC_ParseToken( current ); + if ( !stricmp( com_token, "," ) ) + { + // Parse out public and then classname an + current = CC_ParseToken( current ); + if ( strlen( com_token ) > 0 ) + { + char basename[ 256 ]; + sprintf( basename, "%s%s", prefix, com_token ); + + bool valid = true; + + if ( bases == 2 ) + { + valid = false; + + current = CC_ParseToken( current ); + if ( !stricmp( com_token, "," ) ) + { + current = CC_ParseToken( current ); + if ( strlen( com_token ) > 0 ) + { + valid = true; + if ( usebase == 1 ) + { + sprintf( basename, "%s%s", prefix, com_token ); + } + } + } + } + + if ( valid ) + { + cl->SetBaseClass( basename ); + strcpy( cl->m_szTypedefBaseClass, basename ); + } + + do + { + current = CC_ParseToken( current ); + } while ( strlen( com_token ) && stricmp( com_token, ")" ) ); + + if ( !stricmp( com_token, ")" ) ) + { + current = cl->ParseClassDeclaration( current ); + } + } + } + else if ( !stricmp( com_token, ")" ) ) + { + current = cl->ParseClassDeclaration( current ); + } + } + } + } + else if ( !strcmp( com_token, "TYPEDESCRIPTION" ) || + !strcmp( com_token, "typedescription_t" ) ) + { + current = ParseTypeDescription( current, false ); + } + else if ( !strcmp( com_token, "BEGIN_DATADESC" ) || + !strcmp( com_token, "BEGIN_DATADESC_NO_BASE" ) || + !strcmp( com_token, "BEGIN_SIMPLE_DATADESC" ) || + !strcmp( com_token, "BEGIN_BYTESWAP_DATADESC" ) ) + { + current = ParseTypeDescription( current, true ); + } + else if ( !strcmp( com_token, "BEGIN_PREDICTION_DATA" ) || + !strcmp( com_token, "BEGIN_EMBEDDED_PREDDESC" ) ) + { + current = ParsePredictionTypeDescription( current ); + } + else if ( !strcmp( com_token, "BEGIN_RECV_TABLE" ) || + !strcmp( com_token, "BEGIN_RECV_TABLE_NOBASE" ) || + !strcmp( com_token, "IMPLEMENT_CLIENTCLASS_DT" ) || + !strcmp( com_token, "IMPLEMENT_CLIENTCLASS_DT_NOBASE" ) ) + { + current = ParseReceiveTable( current ); + } + else if ( !strcmp( com_token, "IMPLEMENT_PREDICTABLE_NODATA" ) ) + { + current = CC_ParseToken( current ); + if ( !strcmp( com_token, "(" ) ) + { + current = CC_ParseToken( current ); + + CClass *cl = FindClass( com_token ); + if ( cl ) + { + if ( cl->m_bHasPredictionData ) + { + if ( !forcequiet ) + { + vprint( 0, "Class %s declared predictable and implemented with IMPLEMENT_PREDICTABLE_NODATA in typedescription\n", + cl->m_szName ); + } + + cl->m_bHasPredictionData = false; + } + } + + current = CC_ParseToken( current ); + } + } + + current = CC_ParseToken( current ); + } + + COM_FreeFile( (unsigned char *)buffer ); + + if ( !forcequiet && !GetQuiet() ) + { + vprint( 0, " %s: headers (%i game / %i total)", (char *)&filename[ m_nOffset ], numheaders - skippedfiles, numheaders ); + if ( maxdepth > 1 ) + { + vprint( 0, ", depth %i", maxdepth ); + } + vprint( 0, "\n" ); + } + + m_nLinesOfCode += linesprocessed; + linesprocessed = 0; +} + +void CCodeProcessor::ProcessModules( const char *srcroot, const char *root, const char *rootmodule ) +{ + m_nFilesProcessed++; + + // Reset header list per module + m_Headers.RemoveAll(); + + int numheaders = 0; + int maxdepth = 0; + int skippedfiles = 0; + ProcessModule( false, 0, maxdepth, numheaders, skippedfiles, srcroot, root, root, rootmodule ); +} + +void ReportMissingTypes(); + +void CCodeProcessor::PrintResults( const char *baseentityclass ) +{ + vprint( 0, "\nChecking for errors and totaling...\n\n" ); + + ResolveBaseClasses( baseentityclass ); + PrintClassList(); + PrintMissingTDFields(); + ReportMissingTypes(); + ReportHungarianNotationErrors(); + + vprint( 0, "%i total classes parsed from %i files ( %i headers parsed )\n", + m_nClassesParsed, + m_nFilesProcessed, + m_nHeadersProcessed ); + + vprint( 0, "%.3f K lines of code processed\n", + (double)m_nLinesOfCode / 1024.0 ); + + double elapsed = ( m_flEnd - m_flStart ); + + if ( elapsed > 0.0 ) + { + vprint( 0, "%.2f K processed in %.3f seconds, throughput %.2f KB/sec\n\n", + (double)m_nBytesProcessed / 1024.0, elapsed, (double)m_nBytesProcessed / ( 1024.0 * elapsed ) ); + } + + Clear(); +} + +CCodeProcessor::CCodeProcessor( void ) +{ + m_pClassList = NULL; + + m_Modules.RemoveAll(); + + m_bQuiet = false; + m_bPrintHierarchy = false; + m_bPrintMembers = true; + m_bPrintTypedescriptionErrors = true; + m_bPrintPredictionDescErrors = true; + m_bCreateMissingTDs = false; + m_bLogToFile = false; + m_bCheckHungarian = false; + + m_nFilesProcessed = 0; + m_nHeadersProcessed = 0; + m_nClassesParsed = 0; + m_nOffset = 0; + m_nBytesProcessed = 0; + m_nLinesOfCode = 0; + m_flStart = 0.0; + m_flEnd = 0.0; + + m_szCurrentCPP[ 0 ] = 0; + m_szBaseEntityClass[ 0 ] = 0; +} + +CCodeProcessor::~CCodeProcessor( void ) +{ +} + +void CCodeProcessor::ConstructModuleList_R( int level, const char *baseentityclass, + const char *gamespecific, const char *root, const char *srcroot ) +{ + char directory[ 256 ]; + char filename[ 256 ]; + WIN32_FIND_DATA wfd; + HANDLE ff; + + sprintf( directory, "%s\\*.*", root ); + + if ( ( ff = FindFirstFile( directory, &wfd ) ) == INVALID_HANDLE_VALUE ) + return; + + do + { + if ( wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) + { + + if ( wfd.cFileName[ 0 ] == '.' ) + continue; + + // Once we descend down a branch, don't keep looking for hl2/tf2 in name, just recurse through all children + if ( level == 0 && !strstr( wfd.cFileName, gamespecific ) ) + continue; + + // Recurse down directory + sprintf( filename, "%s\\%s", root, wfd.cFileName ); + ConstructModuleList_R( level+1, baseentityclass, gamespecific, filename, srcroot ); + } + else + { + if ( strstr( wfd.cFileName, ".cpp" ) ) + { + ProcessModules( srcroot, root, wfd.cFileName ); + } + } + } while ( FindNextFile( ff, &wfd ) ); +} + +void CCodeProcessor::CleanupIncludePath() +{ + for ( int i = m_IncludePath.Count(); --i >= 0; ) + { + delete [] m_IncludePath[i]; + } + m_IncludePath.RemoveAll(); +} + +void CCodeProcessor::AddIncludePath( const char *pPath ) +{ + int i = m_IncludePath.AddToTail(); + int nLen = strlen(pPath) + 1; + m_IncludePath[i] = new char[nLen]; + memcpy( m_IncludePath[i], pPath, nLen ); +} + +void CCodeProcessor::SetupIncludePath( const char *sourcetreebase, const char *subdir, const char *gamespecific ) +{ + CleanupIncludePath(); + + char path[MAX_PATH]; + sprintf( path, "%s\\%s", sourcetreebase, subdir ); + strlwr( path ); + AddIncludePath( path ); + + char modsubdir[128]; + if ( !stricmp(subdir, "dlls") ) + { + sprintf(modsubdir,"%s\\%s_dll", subdir, gamespecific ); + } + else if ( !stricmp(subdir, "cl_dll") ) + { + sprintf(modsubdir,"%s\\%s_hud", subdir, gamespecific ); + } + else + { + sprintf(modsubdir,"%s\\%s", subdir, gamespecific ); + } + + sprintf( path, "%s\\%s", sourcetreebase, modsubdir ); + strlwr( path ); + AddIncludePath( path ); + + // Game shared + sprintf( path, "%s\\game_shared", sourcetreebase ); + strlwr( path ); + AddIncludePath( path ); + + sprintf( path, "%s\\game_shared\\%s", sourcetreebase, gamespecific ); + strlwr( path ); + AddIncludePath( path ); + + sprintf( path, "%s\\public", sourcetreebase ); + strlwr( path ); + AddIncludePath( path ); +} + + +void CCodeProcessor::Process( const char *baseentityclass, const char *gamespecific, const char *sourcetreebase, const char *subdir ) +{ + SetupIncludePath( sourcetreebase, subdir, gamespecific ); + + strcpy( m_szBaseEntityClass, baseentityclass ); + + m_nBytesProcessed = 0; + m_nFilesProcessed = 0; + m_nHeadersProcessed = 0; + m_nClassesParsed = 0; + m_nLinesOfCode = 0; + + linesprocessed = 0; + + m_Modules.RemoveAll(); + m_Headers.RemoveAll(); + + m_flStart = UTIL_FloatTime(); + + char rootdirectory[ 256 ]; + sprintf( rootdirectory, "%s\\%s", sourcetreebase, subdir ); + + vprint( 0, "--- Processing %s\n\n", rootdirectory ); + + m_nOffset = strlen( rootdirectory ) + 1; + + ConstructModuleList_R( 0, baseentityclass, gamespecific, rootdirectory, sourcetreebase ); + + sprintf( rootdirectory, "%s\\%s", sourcetreebase, "game_shared" ); + + vprint( 0, "--- Processing %s\n\n", rootdirectory ); + + m_nOffset = strlen( rootdirectory ) + 1; + + ConstructModuleList_R( 0, baseentityclass, gamespecific, rootdirectory, sourcetreebase ); + + m_flEnd = UTIL_FloatTime(); + + PrintResults( baseentityclass ); +} + +void CCodeProcessor::Process( const char *baseentityclass, const char *gamespecific, + const char *sourcetreebase, const char *subdir, const char *pFileName ) +{ + SetupIncludePath( sourcetreebase, subdir, gamespecific ); + + strcpy( m_szBaseEntityClass, baseentityclass ); + + m_nBytesProcessed = 0; + m_nFilesProcessed = 0; + m_nHeadersProcessed = 0; + m_nClassesParsed = 0; + m_nLinesOfCode = 0; + + linesprocessed = 0; + + m_Modules.RemoveAll(); + m_Headers.RemoveAll(); + + m_flStart = UTIL_FloatTime(); + + char rootdirectory[ 256 ]; + sprintf( rootdirectory, "%s\\%s", sourcetreebase, subdir ); + + vprint( 0, "--- Processing %s\n\n", rootdirectory ); + + m_nOffset = strlen( rootdirectory ) + 1; + + ProcessModules( sourcetreebase, rootdirectory, pFileName ); + + m_flEnd = UTIL_FloatTime(); + + PrintResults( baseentityclass ); +} + +void CCodeProcessor::SetQuiet( bool quiet ) +{ + m_bQuiet = quiet; +} + +bool CCodeProcessor::GetQuiet( void ) const +{ + return m_bQuiet; +} + +void CCodeProcessor::SetPrintHierarchy( bool print ) +{ + m_bPrintHierarchy = print; +} + +bool CCodeProcessor::GetPrintHierarchy( void ) const +{ + return m_bPrintHierarchy; +} + +void CCodeProcessor::SetPrintMembers( bool print ) +{ + m_bPrintMembers = print; +} + +bool CCodeProcessor::GetPrintMembers( void ) const +{ + return m_bPrintMembers; +} + +void CCodeProcessor::SetPrintTDs( bool print ) +{ + m_bPrintTypedescriptionErrors = print; +} + +bool CCodeProcessor::GetPrintTDs( void ) const +{ + return m_bPrintTypedescriptionErrors; +} + +void CCodeProcessor::SetLogFile( bool log ) +{ + m_bLogToFile = log; +} + +bool CCodeProcessor::GetLogFile( void ) const +{ + return m_bLogToFile; +} + +void CCodeProcessor::SetPrintPredTDs( bool print ) +{ + m_bPrintPredictionDescErrors = print; +} + +bool CCodeProcessor::GetPrintPredTDs( void ) const +{ + return m_bPrintPredictionDescErrors; +} + +void CCodeProcessor::SetPrintCreateMissingTDs( bool print ) +{ + m_bCreateMissingTDs = print; +} + +bool CCodeProcessor::GetPrintCreateMissingTDs( void ) const +{ + return m_bCreateMissingTDs; +} + +void CCodeProcessor::SetPrintCreateMissingPredTDs( bool print ) +{ + m_bCreateMissingPredTDs = print; +} + +bool CCodeProcessor::GetPrintCreateMissingPredTDs( void ) const +{ + return m_bCreateMissingPredTDs; +} + +void CCodeProcessor::SetCheckHungarian( bool check ) +{ + m_bCheckHungarian = check; +} + +bool CCodeProcessor::GetCheckHungarian() const +{ + return m_bCheckHungarian; +} + +static CCodeProcessor g_Processor; +ICodeProcessor *processor = ( ICodeProcessor * )&g_Processor;
\ No newline at end of file diff --git a/utils/classcheck/stdafx.cpp b/utils/classcheck/stdafx.cpp new file mode 100644 index 0000000..1fdf9da --- /dev/null +++ b/utils/classcheck/stdafx.cpp @@ -0,0 +1,15 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.cpp : source file that includes just the standard includes +// classcheck.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/utils/classcheck/stdafx.h b/utils/classcheck/stdafx.h new file mode 100644 index 0000000..e0ae61a --- /dev/null +++ b/utils/classcheck/stdafx.h @@ -0,0 +1,26 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#if !defined(AFX_STDAFX_H__50E4147E_A508_4D85_BF0A_BA26676063F0__INCLUDED_) +#define AFX_STDAFX_H__50E4147E_A508_4D85_BF0A_BA26676063F0__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + + +// TODO: reference additional headers your program requires here + +//{{AFX_INSERT_LOCATION}} +// Microsoft Visual C++ will insert additional declarations immediately before the previous line. + +#endif // !defined(AFX_STDAFX_H__50E4147E_A508_4D85_BF0A_BA26676063F0__INCLUDED_) |