1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
|
//========= Copyright (c), Valve LLC, All rights reserved. ============
//
// Purpose: A utility for tracking access to various systems
//
//=============================================================================
#ifndef GCSYSTEMACCESS_H
#define GCSYSTEMACCESS_H
#pragma once
namespace GCSDK
{
//behaviors that can be triggered for system access violation. Each aspect of this system has
//actions that can be enabled or suppressed. The algorithm for this is to combine all the enabled
//actions, then remove all the suppressed and perform those operations when a violation is found
enum EGCAccessAction
{
//returns failure to the verifying code so it can handle it accordingly
GCAccessAction_ReturnFail = ( 1<<0 ),
//track this into a stats overview for number of offenses
GCAccessAction_TrackFail = ( 1<<1 ),
//track this into a stats overview for number of accesses
GCAccessAction_TrackSuccess = ( 1<<2 ),
//report this violation to the console/log
GCAccessAction_Msg = ( 1<<3 ),
//generate an assert at the point this is violated
GCAccessAction_Assert = ( 1<<4 ),
//activate a breakpoint
GCAccessAction_Breakpoint = ( 1<<5 )
};
//how we identify an actual system
typedef uint32 GCAccessSystem_t;
//a structure that is inserted into a context object and holds onto GCAccessSystem index that the context can then initialize itself from. Classes can derive
//from this to initialize itself, but derived class may NOT have any members as that would change the binary layout of the object
class CGCAContextSystemRef
{
public:
CGCAContextSystemRef( GCAccessSystem_t id ) : m_SystemID( id ) {}
const GCAccessSystem_t m_SystemID;
};
//this will generate a unique system access ID that is guaranteed to be unique within a single run of the GC
GCAccessSystem_t GenerateUniqueGCAccessID();
//defines a system access object class which can be used to validate access to systems. This class can be registered with the
//GCA_REGISTER_SYSTEM macro using the same name.
#define GCA_SYSTEM( Name ) \
class CGCA##Name : public CGCAContextSystemRef { \
public: \
static const char* GetName() { return #Name; } \
static GCAccessSystem_t GetID() { static GCAccessSystem_t s_ID = GenerateUniqueGCAccessID(); return s_ID; } \
CGCA##Name() : CGCAContextSystemRef( GetID() ) {} \
};
//similar to the above, but does not auto assign an ID and instead uses the fixed specified ID
#define GCA_SYSTEM_ID( Name, ID ) \
class CGCA##Name : public CGCAContextSystemRef { \
public: \
static const char* GetName() { return #Name; } \
static GCAccessSystem_t GetID() { return (ID); } \
CGCA##Name() : CGCAContextSystemRef( GetID() ) {} \
};
//called to register the specified system with the GC access system
#define GCA_REGISTER_SYSTEM( Name ) GGCAccess().RegisterSystem( CGCA##Name::GetName(), CGCA##Name::GetID() )
//called to begin a context class which derives from (has the same rights as) a parent context. The proper setup for this is:
// GCA_BEGIN_CONTEXT_PARENT( MyContext, ParentContext )
// GCA_CONTEXT_SYSTEM( System )
// GCA_END_CONTEXT( MyContext )
#define GCA_BEGIN_CONTEXT_PARENT( ClassName, Parent ) \
class ClassName : public Parent { \
public: \
ClassName() : m_nFirstSystemMarker_##ClassName( 0 ), m_nLastSystemMarker_##ClassName( 0 ) { \
Init( #ClassName ); \
for( const GCAccessSystem_t* pSystem = &m_nFirstSystemMarker_##ClassName + 1; pSystem != &m_nLastSystemMarker_##ClassName; pSystem++ ) \
AddSystem( *pSystem ); \
} \
static ClassName& Singleton() { static ClassName s_Singleton; return s_Singleton; } \
const GCAccessSystem_t m_nFirstSystemMarker_##ClassName;
//this is the same as the above but where you don't specify a parent context
#define GCA_BEGIN_CONTEXT( ClassName ) GCA_BEGIN_CONTEXT_PARENT( ClassName, CGCAccessContext )
//called to add a context to a system. This must come between a BEGIN/END_CONTEXT block
#define GCA_CONTEXT_SYSTEM( SystemName ) \
const CGCA##SystemName SystemName;
//called to add a generic system reference to a context. The reference will use the provided class and value name and the specified ID
#define GCA_CONTEXT_SYSTEM_ID( ClassName, VarName, SystemID ) \
class ClassName : public CGCAContextSystemRef { \
public: \
ClassName() : CGCAContextSystemRef( SystemID ) {} \
} VarName;
//closes out the definition of a context
#define GCA_END_CONTEXT( ClassName ) \
const GCAccessSystem_t m_nLastSystemMarker_##ClassName; \
};
//creates a context that is basically the same as another context but aliased. Useful if you want a derived context that doesn't have any additional systems
#define GCA_ALIAS_CONTEXT( NewName, ParentName ) \
GCA_BEGIN_CONTEXT_PARENT( NewName, ParentName ) \
GCA_END_CONTEXT( NewName )
//a simple way to define an empty context
#define GCA_EMPTY_CONTEXT( Context ) GCA_BEGIN_CONTEXT( Context ) GCA_END_CONTEXT( Context )
//called to obtain the ID of a system
#define GCA_GET_SYSTEM_ID( SystemName ) ( CGCA##SystemName::GetID() )
//called to validate access to a system, either with an ID or without
#define GCA_VALIDATE_ACCESS( SystemName ) GGCAccess().ValidateAccess( GCA_GET_SYSTEM_ID( SystemName ) )
#define GCA_VALIDATE_ACCESS_STEAMID( SystemName, SteamID ) GGCAccess().ValidateSteamIDAccess( GCA_GET_SYSTEM_ID( SystemName ), ( SteamID ) )
//a context, which is a collection of systems that are valid to be accessed. Each job and GC has a context to validate
//system access
class CGCAccessContext
{
public:
CGCAccessContext();
void Init( const char* pszName, uint32 nActions = 0, uint32 nSuppressActions = 0 );
void AddSystem( GCAccessSystem_t nSystem );
const char* GetName() const { return m_sName.String(); }
uint32 GetActions() const { return m_nActions; }
uint32 GetSuppressActions() const { return m_nSuppressActions; }
void SetAction( EGCAccessAction eAction, bool bSet );
void SetSuppressAction( EGCAccessAction eAction, bool bSet );
//determines if this system has the
bool HasSystem( GCAccessSystem_t system ) const;
//given a context, this will verify that it is a proper subset of the provided context
bool IsSubsetOf( const CGCAccessContext& context ) const;
//given another context, this will add all of its systems to this context
void AddSystemsFrom( const CGCAccessContext& context );
private:
//the textual name of this context
CUtlString m_sName;
//which actions to enable or disable in particular based upon this context
uint32 m_nActions;
uint32 m_nSuppressActions;
//which systems that the context has enabled, in sorted order for fast lookup
CUtlSortVector< GCAccessSystem_t > m_nSystems;
};
class CGCAccessSystem;
//the global GC access tracker which has the systems registered and handles accumulating stats, presenting reports, etc
class CGCAccess
{
public:
CGCAccess();
//-----------------------------------------
// Setup
//called to register a system information
void RegisterSystem( const char* pszName, GCAccessSystem_t nID, uint32 nActions = 0, uint32 nSupressActions = 0 );
//-----------------------------------------
// Validation
//called to verify access to a system. The return of this will be false if it is an invalid access and the return fail action is specified
bool ValidateAccess( GCAccessSystem_t nSystem );
//verify access to a system that also requires that the active SteamID of the current job matches the provided Steam ID, and count it as a fail
bool ValidateSteamIDAccess( GCAccessSystem_t nSystem, CSteamID steamID );
//called to suppress tracking of access for the specified access type. This should typically only be accessed via the access supress utility object
void SuppressAccess( GCAccessSystem_t nSystem, bool bEnable );
//-----------------------------------------
// Report and console operations
//when displaying a report, this will determine how stats should be filtered
enum EDisplay
{
eDisplay_Referenced,
eDisplay_Violations,
eDisplay_IDViolations,
eDisplay_All
};
//called to reset all accumulated stats for all the systems
void ClearSystemStats();
//called to generate a report of every access for a specific job
void ReportJobs( const char* pszContext, EDisplay eDisplay ) const;
//called to generate a report of each system in summary
void ReportSystems( const char* pszContext, EDisplay eDisplay ) const;
//dumps all the collected stats
void FullReport( const char* pszSystemFilter, const char* pszContextFilter, const char* pszJobFilter, EDisplay eDisplay ) const;
//dumps a dependency report for a given system. Essentially for every job that depends upon the named system, what are the other
//systems that it also relies upon
void DependencyReport( const char* pszSystem, EDisplay eDisplay ) const;
//called to register a one time assert that will fire the next time the job/context/system pair is hit
bool CatchSingleAssert( const char* pszSystem, bool bContext, const char* pszContextOrJob );
//clears all registered single asserts that have not yet fired
void ClearSingleAsserts();
//if there is not a specific job in flight, this global context is what will be checked for access
CGCAccessContext m_GlobalContext;
//global options that are set for typical system violation
uint32 m_nActions;
uint32 m_nSuppressActions;
private:
//handles internally validating the system. Takes an optional parameter indicating if the steam ID check failed
bool InternalValidateAccess( GCAccessSystem_t nSystem, CSteamID steamID, CSteamID expectedID );
//the list of single fire asserts that we want to catch and report
struct SingleAssert_t
{
bool m_bContext;
GCAccessSystem_t m_System;
CUtlString m_sContextOrJob;
};
CUtlVector< SingleAssert_t* > m_SingleAsserts;
//systems that have their access suppressed
struct SuppressAccess_t
{
GCAccessSystem_t m_nSystem;
uint32 m_nCount;
};
CUtlVector< SuppressAccess_t > m_SuppressAccess;
//the registered systems
CUtlHashMapLarge< GCAccessSystem_t, CGCAccessSystem* > m_Systems;
//steam ID
};
//global singleton accessor
CGCAccess& GGCAccess();
//utility class to temporarily disable access tracking for a system while within the scope of this object
class CGCAccessSupressTracking
{
public:
CGCAccessSupressTracking( GCAccessSystem_t nSystem ) : m_nSystem( nSystem ) { GGCAccess().SuppressAccess( m_nSystem, false ); }
~CGCAccessSupressTracking() { GGCAccess().SuppressAccess( m_nSystem, true ); }
private:
GCAccessSystem_t m_nSystem;
};
} //namespace GCSDK
#endif
|