Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 57 additions & 2 deletions src/coreclr/debug/datadescriptor-shared/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,41 @@ The build system uses a two-phase approach:

## Macro Reference

### Field Type Annotation Defines

Field types in `CDAC_TYPE_FIELD` use preprocessor defines that expand to type name strings in
debug/checked builds and to nothing in release builds. This keeps the release blob compact while
providing type information for diagnostic validation in debug builds.

These defines are declared in `wrappeddatadescriptor.inc` and are available to all `.inc` files:

| Define | Debug expansion | Description |
|--------|----------------|-------------|
| `T_UINT8` | `uint8` | 8-bit unsigned integer |
| `T_UINT16` | `uint16` | 16-bit unsigned integer |
| `T_UINT32` | `uint32` | 32-bit unsigned integer |
| `T_UINT64` | `uint64` | 64-bit unsigned integer |
| `T_INT8` | `int8` | 8-bit signed integer |
| `T_INT16` | `int16` | 16-bit signed integer |
| `T_INT32` | `int32` | 32-bit signed integer |
| `T_INT64` | `int64` | 64-bit signed integer |
| `T_NUINT` | `nuint` | Native unsigned integer |
| `T_NINT` | `nint` | Native signed integer |
| `T_POINTER` | `pointer` | Target pointer |
| `T_BOOL` | `bool` | Boolean |
| `TYPE(name)` | `name` | Inline struct type declared with `CDAC_TYPE_BEGIN` in the same descriptor |
| `EXTERN_TYPE(name)` | `name` | Cross-descriptor struct type (not validated locally) |
| `T_ARRAY(type)` | `pointer` | Array of the given element type. Expands to `pointer` in the blob |

`TYPE(name)` references are validated at compile time in debug builds via
`cdactypevalidation.inc`. If the name does not match any `CDAC_TYPE_BEGIN` in the same
descriptor, a `static_assert` failure is produced. Use `EXTERN_TYPE(name)` for types
declared in a different descriptor (e.g., GC referencing a VM type).

`T_ARRAY(type)` accepts any type define as its element type (e.g., `T_ARRAY(T_UINT8)`,
`T_ARRAY(TYPE(StressLogModuleDesc))`). The inner type is validated but the array itself
expands to `pointer` in the blob since arrays are accessed via pointers.

### Structure Definition Macros

**`CDAC_BASELINE("identifier")`**
Expand All @@ -56,7 +91,7 @@ The build system uses a two-phase approach:

**`CDAC_TYPE_FIELD(typeName, fieldType, fieldName, offset)`**
- Defines a field within the type
- `fieldType`: primitive type or another defined type
- `fieldType`: a type annotation define (see table above)
- `fieldName`: diagnostic-friendly name (use managed names for managed types)
- `offset`: byte offset, usually `offsetof()` or `cdac_data<T>::FieldName`

Expand All @@ -72,7 +107,7 @@ The build system uses a two-phase approach:
**`CDAC_GLOBAL(globalName, typeName, value)`**
- Defines a global literal value
- `value` must be a compile-time constant
- `typeName` can be a primitive type or defined type
- `typeName`: a type annotation define (e.g., `T_UINT32`, `T_UINT8`, `T_NUINT`)

**`CDAC_GLOBAL_POINTER(globalName, address)`**
- Defines a global pointer value
Expand All @@ -88,6 +123,26 @@ The build system uses a two-phase approach:
- Used for multi-contract scenarios where one contract references another
- Example: `CDAC_GLOBAL_SUB_DESCRIPTOR(GC, &(g_gc_dac_vars.gc_descriptor))`

## Compile-Time Type Validation

In debug/checked builds, `cdactypevalidation.inc` validates that every `TYPE(name)` reference
in `CDAC_TYPE_FIELD` corresponds to a type declared with `CDAC_TYPE_BEGIN` in the same
descriptor. It uses two passes:

1. **Pass 1**: Each `CDAC_TYPE_BEGIN(name)` declares a tag struct `cdac_type_tag_<name>`.
2. **Pass 2**: Each `TYPE(name)` in `CDAC_TYPE_FIELD` expands to `struct cdac_type_tag_<name>`,
and `static_assert(sizeof(...))` fails if the tag struct was not declared.

Primitive types (`T_UINT32`, etc.) and `EXTERN_TYPE` references expand to `char` during
validation, so they always pass. `T_ARRAY` passes through to its inner type for validation.

This validation is included from `datadescriptor.cpp` under `#ifdef _DEBUG` and runs before
the blob construction passes. A type mismatch produces a clear compiler error, for example:

```
error C2338: static assertion failed:
'Field Thread.OSId references undeclared cDAC type TYPE(RandomNonExistentType)'
```

## Current Implementation

Expand Down
65 changes: 65 additions & 0 deletions src/coreclr/debug/datadescriptor-shared/cdactypevalidation.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

// No include guards. This file is included multiple times.
// Validates that every TYPE(name) and T_ARRAY(TYPE(name)) reference in CDAC_TYPE_FIELD
// matches a cDAC type declared with CDAC_TYPE_BEGIN(name) within the same descriptor.
// EXTERN_TYPE references are not validated (cross-descriptor types).
//
// Included from datadescriptor.cpp under #ifdef _DEBUG before the blob construction.
// This file must be included BEFORE any wrappeddatadescriptor.inc inclusion so that
// the type defines (T_*, TYPE, etc.) have not yet been set. After validation, all
// overrides are undef'd so the real defines get established by the first
// wrappeddatadescriptor.inc inclusion via the #ifndef T_UINT8 guard.
//
// Uses two passes over the descriptor:
// Pass 1: CDAC_TYPE_BEGIN(name) declares a tag struct for each cDAC type.
// Pass 2: TYPE(name) and T_ARRAY inner types are validated via static_assert.
// T_* primitives and EXTERN_TYPE expand to char (always valid for sizeof).

// TYPE(name) expands to a tag struct type -- sizeof() fails if undeclared.
// EXTERN_TYPE and T_* primitives expand to char -- always valid.
// T_ARRAY passes through to its inner type for validation.
#define TYPE(name) struct cdac_type_tag_##name
#define EXTERN_TYPE(name) char
#define T_UINT8 char
#define T_UINT16 char
#define T_UINT32 char
#define T_UINT64 char
#define T_INT8 char
#define T_INT16 char
#define T_INT32 char
#define T_INT64 char
#define T_NUINT char
#define T_NINT char
#define T_POINTER char
#define T_BOOL char
#define T_ARRAY(name) name

// Pass 1: Declare a tag struct for each CDAC_TYPE_BEGIN in this descriptor.
#define CDAC_TYPE_BEGIN(name) struct cdac_type_tag_##name { char dummy; };
#include "wrappeddatadescriptor.inc"

// Pass 2: Validate TYPE() references via static_assert on the tag structs.
#define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) \
static_assert(sizeof(membertyname) > 0, \
"Field " #tyname "." #membername " references undeclared cDAC type " #membertyname);
#include "wrappeddatadescriptor.inc"

// Clean up all validation overrides. The real T_*/TYPE defines will be
// established by the next wrappeddatadescriptor.inc inclusion via #ifndef T_UINT8.
#undef TYPE
#undef EXTERN_TYPE
#undef T_UINT8
#undef T_UINT16
#undef T_UINT32
#undef T_UINT64
#undef T_INT8
#undef T_INT16
#undef T_INT32
#undef T_INT64
#undef T_NUINT
#undef T_NINT
#undef T_POINTER
#undef T_BOOL
#undef T_ARRAY
19 changes: 15 additions & 4 deletions src/coreclr/debug/datadescriptor-shared/datadescriptor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@

// begin blob definition

// In debug/checked builds, validate that TYPE() references match declared cDAC types.
#ifdef _DEBUG
#include "cdactypevalidation.inc"
#endif

extern "C"
{

Expand Down Expand Up @@ -70,6 +75,12 @@ struct GlobalContractSpec
// __VA_ARGS__ is the argument list comma separated
#define STRINGIFY(...) #__VA_ARGS__

// Double-indirection stringify: expands macros in x before stringifying.
// This is needed because the T_* type annotation defines (T_UINT32, TYPE(GCHandle), etc.)
// must be expanded before stringification. Direct #x does not expand macros.
#define CDAC_STRINGIFY_IMPL(x) #x
#define CDAC_STRINGIFY(x) CDAC_STRINGIFY_IMPL(x)

// define a struct where the size of each field is the length of some string. we will use offsetof to get
// the offset of each struct element, which will be equal to the offset of the beginning of that string in the
// string pool.
Expand All @@ -80,14 +91,14 @@ struct CDacStringPoolSizes
#define CDAC_BASELINE(name) DECL_LEN(cdac_string_pool_baseline_, (sizeof(name)))
#define CDAC_TYPE_BEGIN(name) DECL_LEN(MAKE_TYPELEN_NAME(name), sizeof(#name))
#define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) DECL_LEN(MAKE_FIELDLEN_NAME(tyname,membername), sizeof(#membername)) \
DECL_LEN(MAKE_FIELDTYPELEN_NAME(tyname,membername), sizeof(#membertyname))
DECL_LEN(MAKE_FIELDTYPELEN_NAME(tyname,membername), sizeof(CDAC_STRINGIFY(membertyname)))
#define CDAC_GLOBAL_STRING(name, stringval) DECL_LEN(MAKE_GLOBALLEN_NAME(name), sizeof(#name)) \
DECL_LEN(MAKE_GLOBALVALUELEN_NAME(name), sizeof(STRINGIFY(stringval)))
#define CDAC_GLOBAL_POINTER(name,value) DECL_LEN(MAKE_GLOBALLEN_NAME(name), sizeof(#name))
#define CDAC_GLOBAL_SUB_DESCRIPTOR(name,value) DECL_LEN(MAKE_GLOBALLEN_NAME(name), sizeof(#name))
#define CDAC_GLOBAL_CONTRACT(name,value) DECL_LEN(MAKE_GLOBALCONTRACTLEN_NAME(name), sizeof(#name))
#define CDAC_GLOBAL(name,tyname,value) DECL_LEN(MAKE_GLOBALLEN_NAME(name), sizeof(#name)) \
DECL_LEN(MAKE_GLOBALTYPELEN_NAME(name), sizeof(#tyname))
DECL_LEN(MAKE_GLOBALTYPELEN_NAME(name), sizeof(CDAC_STRINGIFY(tyname)))
#include "wrappeddatadescriptor.inc"
char cdac_string_pool_trailing_nil;
#undef DECL_LEN
Expand Down Expand Up @@ -368,12 +379,12 @@ struct MagicAndBlob BlobDataDescriptor = {
/* .NamesPool = */ ("\0" // starts with a nul
#define CDAC_BASELINE(name) name "\0"
#define CDAC_TYPE_BEGIN(name) #name "\0"
#define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) #membername "\0" #membertyname "\0"
#define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) #membername "\0" CDAC_STRINGIFY(membertyname) "\0"
#define CDAC_GLOBAL_STRING(name,value) #name "\0" STRINGIFY(value) "\0"
#define CDAC_GLOBAL_POINTER(name,value) #name "\0"
#define CDAC_GLOBAL_SUB_DESCRIPTOR(name,value) #name "\0"
#define CDAC_GLOBAL_CONTRACT(name,value) #name "\0"
#define CDAC_GLOBAL(name,tyname,value) #name "\0" #tyname "\0"
#define CDAC_GLOBAL(name,tyname,value) #name "\0" CDAC_STRINGIFY(tyname) "\0"
#include "wrappeddatadescriptor.inc"
),

Expand Down
47 changes: 47 additions & 0 deletions src/coreclr/debug/datadescriptor-shared/wrappeddatadescriptor.inc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,53 @@
// No include guards. This file is included multiple times.
// Wraps datadescriptor.inc to define and undefine macros used in the file.

// Field type annotation defines. In debug/checked builds, these expand to type
// names that appear in the data descriptor blob's string pool. In release builds,
// they expand to nothing, keeping the blob compact.
// Primitive types use T_<TYPE> (e.g., T_UINT32, T_POINTER).
// Inline struct types use TYPE(<name>) (e.g., TYPE(GCHandle), TYPE(CodePointer)).
// Cross-descriptor struct types use EXTERN_TYPE(<name>) -- same as TYPE() but not
// validated within this descriptor (the type is declared in a different descriptor).
// Array types use T_ARRAY(<type>) where <type> is another type define
// (e.g., T_ARRAY(T_UINT8), T_ARRAY(TYPE(StressLogModuleDesc))).
// T_ARRAY always expands to pointer in the blob since arrays are accessed via pointers.
// These are defined once here and persist across multiple inclusions.
#ifndef T_UINT8
#ifdef _DEBUG
#define T_UINT8 uint8
#define T_UINT16 uint16
#define T_UINT32 uint32
#define T_UINT64 uint64
#define T_INT8 int8
#define T_INT16 int16
#define T_INT32 int32
#define T_INT64 int64
#define T_NUINT nuint
#define T_NINT nint
#define T_POINTER pointer
#define T_BOOL bool
#define TYPE(name) name
#define EXTERN_TYPE(name) name
#define T_ARRAY(name) pointer
#else
#define T_UINT8
#define T_UINT16
#define T_UINT32
#define T_UINT64
#define T_INT8
#define T_INT16
#define T_INT32
#define T_INT64
#define T_NUINT
#define T_NINT
#define T_POINTER
#define T_BOOL
#define TYPE(name)
#define EXTERN_TYPE(name)
#define T_ARRAY(name)
#endif
#endif

#ifndef CDAC_BASELINE
#define CDAC_BASELINE(identifier)
#endif
Expand Down
Loading
Loading