use std::collections::{BTreeMap, HashMap, HashSet};
use std::io::{self, Cursor, Read, Write};
use std::path::{Path, PathBuf};
use std::u32;
use super::ast::*;
use super::preprocessor::*;
use super::token::*;
use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum ASTRecordType {
Metadata = 0,
IdentifierOffset = 1,
IdentifierTable = 2,
SourceLocationOffset = 3,
SourceLocationTable = 4,
TypeOffset = 5,
DeclarationOffset = 6,
StatementOffset = 7,
MacroDefinitionOffset = 8,
ModuleOffset = 9,
FileTable = 10,
DirectoryTable = 11,
TargetTriple = 12,
LanguageOptions = 13,
TargetOptions = 14,
HeaderSearchOptions = 15,
PCHValidatorInfo = 16,
ModuleMap = 17,
SourceManagerBlock = 18,
InputFiles = 19,
PreprocessorOptions = 20,
OriginalFileName = 21,
OriginalFileID = 22,
VersionControl = 23,
TypeVoid = 100,
TypeChar = 101,
TypeSChar = 102,
TypeUChar = 103,
TypeShort = 104,
TypeUShort = 105,
TypeInt = 106,
TypeUInt = 107,
TypeLong = 108,
TypeULong = 109,
TypeLongLong = 110,
TypeULongLong = 111,
TypeFloat = 112,
TypeDouble = 113,
TypeLongDouble = 114,
TypeBool = 115,
TypeComplex = 116,
TypeAuto = 117,
TypeRecord = 118,
TypePointer = 120,
TypeArray = 121,
TypeFunction = 122,
TypeStruct = 123,
TypeEnum = 124,
TypeTypedef = 125,
TypeQualified = 126,
TypeReference = 127,
TypeRValueReference = 128,
TypeConstantArray = 129,
TypeIncompleteArray = 130,
TypeVariableArray = 131,
TypeDependentSizedArray = 132,
TypeVector = 133,
TypeExtVector = 134,
TypeMemberPointer = 135,
TypeBlockPointer = 136,
TypeObjCObject = 137,
TypeObjCInterface = 138,
TypeObjCObjectPointer = 139,
TypePipe = 140,
TypeAtomic = 141,
TypeDecltype = 142,
TypeDecltypeAuto = 143,
TypeTemplateSpecialization = 144,
TypeTemplateTypeParm = 145,
TypeSubstTemplateTypeParm = 146,
TypeInjectedClassName = 147,
TypeDependentName = 148,
TypeDependentTemplateSpecialization = 149,
TypePackExpansion = 150,
TypeUnaryTransform = 151,
TypeElaborated = 152,
TypeAttributed = 153,
TypeAdjusted = 154,
TypeDecayed = 155,
TypeParen = 156,
TypeMacroQualified = 157,
TypeDeducedTemplateSpecialization = 158,
TypeBTFTagAttributed = 159,
TypeUsing = 160,
TypeEnumUnderlying = 161,
DeclFunction = 200,
DeclVariable = 201,
DeclTypedef = 202,
DeclStruct = 203,
DeclEnum = 204,
DeclEnumVariant = 205,
DeclField = 206,
DeclParmVar = 207,
DeclImplicitParam = 208,
DeclTranslationUnit = 209,
DeclRecord = 210,
DeclCXXRecord = 211,
DeclCXXMethod = 212,
DeclCXXConstructor = 213,
DeclCXXDestructor = 214,
DeclCXXConversion = 215,
DeclNamespace = 216,
DeclUsingDirective = 217,
DeclUsingDecl = 218,
DeclNamespaceAlias = 219,
DeclLinkageSpec = 220,
DeclAccessSpec = 221,
DeclFriend = 222,
DeclFriendTemplate = 223,
DeclStaticAssert = 224,
DeclTemplate = 225,
DeclTemplateTypeParm = 226,
DeclTemplateNonTypeParm = 227,
DeclTemplateTemplateParm = 228,
DeclClassTemplate = 229,
DeclFunctionTemplate = 230,
DeclTypeAlias = 231,
DeclTypeAliasTemplate = 232,
DeclLabel = 233,
DeclIndirectField = 234,
DeclUnresolvedUsingValue = 235,
DeclUnresolvedUsingTypename = 236,
DeclOMPThreadPrivate = 237,
DeclOMPDeclareReduction = 238,
DeclOMPDeclareMapper = 239,
DeclOMPCapturedExpr = 240,
DeclOMPRequires = 241,
DeclOMPAllocate = 242,
DeclBuiltinTemplate = 243,
DeclConcept = 244,
DeclRequiresExprBody = 245,
DeclImport = 246,
DeclExport = 247,
DeclModule = 248,
StmtCompound = 300,
StmtReturn = 301,
StmtIf = 302,
StmtWhile = 303,
StmtDoWhile = 304,
StmtFor = 305,
StmtSwitch = 306,
StmtCase = 307,
StmtDefault = 308,
StmtBreak = 309,
StmtContinue = 310,
StmtGoto = 311,
StmtLabel = 312,
StmtExpr = 313,
StmtDecl = 314,
StmtNull = 315,
StmtDeclStmt = 316,
StmtCXXForRange = 317,
StmtCXXTry = 318,
StmtCXXCatch = 319,
StmtCXXThrow = 320,
StmtSEHTry = 321,
StmtSEHExcept = 322,
StmtSEHFinally = 323,
StmtSEHLeave = 324,
StmtMSAsm = 325,
StmtGCCAsm = 326,
StmtOMPParallel = 327,
StmtOMPSimd = 328,
StmtOMPFor = 329,
StmtOMPSections = 330,
StmtOMPSection = 331,
StmtOMPSingle = 332,
StmtOMPMaster = 333,
StmtOMPCritical = 334,
StmtOMPParallelFor = 335,
StmtOMPParallelSections = 336,
StmtOMPTask = 337,
StmtOMPOrdered = 338,
StmtOMPAtomic = 339,
StmtOMPTarget = 340,
StmtOMPTeams = 341,
StmtOMPCancellationPoint = 342,
StmtOMPCancel = 343,
StmtOMPTaskLoop = 344,
StmtOMPTaskLoopSimd = 345,
StmtOMPDistribute = 346,
StmtOMPTargetEnterData = 347,
StmtOMPTargetExitData = 348,
StmtOMPTargetParallel = 349,
StmtOMPTargetParallelFor = 350,
StmtOMPTargetUpdate = 351,
StmtOMPDistributeParallelFor = 352,
StmtOMPDistributeParallelForSimd = 353,
StmtOMPDistributeSimd = 354,
StmtOMPTargetParallelForSimd = 355,
StmtOMPTargetSimd = 356,
StmtOMPTeamsDistribute = 357,
StmtOMPTeamsDistributeSimd = 358,
StmtOMPTeamsDistributeParallelForSimd = 359,
StmtOMPTeamsDistributeParallelFor = 360,
StmtOMPTargetTeams = 361,
StmtOMPTargetTeamsDistribute = 362,
StmtOMPTargetTeamsDistributeParallelFor = 363,
StmtOMPTargetTeamsDistributeParallelForSimd = 364,
StmtOMPTargetTeamsDistributeSimd = 365,
StmtOMPTile = 366,
StmtOMPInterop = 367,
StmtOMPDispatch = 368,
StmtOMPMasked = 369,
StmtOMPGenericLoop = 370,
StmtOMPTeamsGenericLoop = 371,
StmtOMPTargetTeamsGenericLoop = 372,
StmtOMPParallelGenericLoop = 373,
StmtOMPTargetParallelGenericLoop = 374,
StmtOMPParallelMasked = 375,
StmtOMPReverse = 376,
StmtCoreturn = 377,
StmtCoawait = 378,
StmtCoyield = 379,
StmtDependentCoawait = 380,
ExprIntLiteral = 400,
ExprUIntLiteral = 401,
ExprFloatLiteral = 402,
ExprDoubleLiteral = 403,
ExprCharLiteral = 404,
ExprStringLiteral = 405,
ExprIdent = 406,
ExprUnary = 407,
ExprSizeOf = 408,
ExprSizeOfType = 409,
ExprAlignOf = 410,
ExprAlignOfType = 411,
ExprCast = 412,
ExprBinary = 413,
ExprAssign = 414,
ExprConditional = 415,
ExprCall = 416,
ExprSubscript = 417,
ExprMember = 418,
ExprPostInc = 419,
ExprPostDec = 420,
ExprPreInc = 421,
ExprPreDec = 422,
ExprCompoundLiteral = 423,
ExprAggregateLiteral = 500,
ExprCXXStaticCast = 424,
ExprCXXDynamicCast = 425,
ExprCXXReinterpretCast = 426,
ExprCXXConstCast = 427,
ExprCXXFunctionalCast = 428,
ExprCXXTypeid = 429,
ExprCXXBoolLiteral = 430,
ExprCXXNullPtrLiteral = 431,
ExprCXXThis = 432,
ExprCXXThrow = 433,
ExprCXXNew = 434,
ExprCXXDelete = 435,
ExprCXXDefaultArg = 436,
ExprCXXDefaultInit = 437,
ExprCXXTemporary = 438,
ExprCXXBindTemporary = 439,
ExprCXXConstruct = 440,
ExprCXXInheritedCtor = 441,
ExprCXXLambda = 442,
ExprCXXFold = 443,
ExprCXXNoexcept = 444,
ExprCXXScalarValueInit = 445,
ExprCXXPseudoDestructor = 446,
ExprCXXAddrspaceCast = 447,
ExprCXXBuiltinBitCast = 448,
ExprOMPArraySection = 449,
ExprOMPArrayShaping = 450,
ExprOMPIterator = 451,
ExprObjCString = 452,
ExprObjCEncode = 453,
ExprObjCSelector = 454,
ExprObjCProtocol = 455,
ExprObjCBridgedCast = 456,
ExprObjCBoolLiteral = 457,
ExprObjCAvailabilityCheck = 458,
ExprBlock = 459,
ExprGenericSelection = 460,
ExprStmtExpr = 461,
ExprChooseExpr = 462,
ExprGNUNullExpr = 463,
ExprShuffleVector = 464,
ExprConvertVector = 465,
ExprVAArg = 466,
ExprOffsetOf = 467,
ExprAddrLabel = 468,
ExprDesignatedInit = 469,
ExprDesignatedInitUpdate = 470,
ExprImplicitValueInit = 471,
ExprParenList = 472,
ExprInitList = 473,
ExprImplicitCast = 474,
ExprExplicitCast = 475,
ExprCStyleCast = 476,
ExprBuiltin = 477,
ExprPredefined = 478,
ExprIntegerLiteral = 479,
ExprFloatingLiteral = 480,
ExprImaginaryLiteral = 481,
ExprFixedPointLiteral = 482,
ExprCharacterLiteral = 483,
ExprParen = 484,
ExprUnaryOperator = 485,
ExprArraySubscript = 486,
ExprBinaryOperator = 487,
ExprCompoundAssignOperator = 488,
ExprConditionalOperator = 489,
ExprMemberExpr = 490,
ExprCallExpr = 491,
ExprMaterializeTemporary = 492,
ExprCXXOperatorCall = 493,
ExprCXXMemberCall = 494,
ExprCUDAKernelCall = 495,
ExprUserDefinedLiteral = 496,
ExprSourceLocExpr = 497,
ExprConceptSpecialization = 498,
ExprRequires = 499,
}
impl ASTRecordType {
pub fn name(&self) -> &'static str {
match self {
Self::Metadata => "METADATA",
Self::IdentifierOffset => "IDENTIFIER_OFFSET",
Self::IdentifierTable => "IDENTIFIER_TABLE",
Self::SourceLocationOffset => "SOURCE_LOCATION_OFFSET",
Self::SourceLocationTable => "SOURCE_LOCATION_TABLE",
Self::TypeOffset => "TYPE_OFFSET",
Self::DeclarationOffset => "DECLARATION_OFFSET",
Self::StatementOffset => "STATEMENT_OFFSET",
Self::MacroDefinitionOffset => "MACRO_DEFINITION_OFFSET",
Self::ModuleOffset => "MODULE_OFFSET",
Self::FileTable => "FILE_TABLE",
Self::DirectoryTable => "DIRECTORY_TABLE",
Self::TargetTriple => "TARGET_TRIPLE",
Self::LanguageOptions => "LANGUAGE_OPTIONS",
Self::TargetOptions => "TARGET_OPTIONS",
Self::HeaderSearchOptions => "HEADER_SEARCH_OPTIONS",
Self::PCHValidatorInfo => "PCH_VALIDATOR_INFO",
Self::ModuleMap => "MODULE_MAP",
Self::SourceManagerBlock => "SOURCE_MANAGER_BLOCK",
Self::InputFiles => "INPUT_FILES",
Self::PreprocessorOptions => "PREPROCESSOR_OPTIONS",
Self::OriginalFileName => "ORIGINAL_FILE_NAME",
Self::OriginalFileID => "ORIGINAL_FILE_ID",
Self::VersionControl => "VERSION_CONTROL",
Self::TypeVoid => "TYPE_VOID",
Self::TypeChar => "TYPE_CHAR",
Self::TypeSChar => "TYPE_SCHAR",
Self::TypeUChar => "TYPE_UCHAR",
Self::TypeShort => "TYPE_SHORT",
Self::TypeUShort => "TYPE_USHORT",
Self::TypeInt => "TYPE_INT",
Self::TypeUInt => "TYPE_UINT",
Self::TypeLong => "TYPE_LONG",
Self::TypeULong => "TYPE_ULONG",
Self::TypeLongLong => "TYPE_LONG_LONG",
Self::TypeULongLong => "TYPE_ULONG_LONG",
Self::TypeFloat => "TYPE_FLOAT",
Self::TypeDouble => "TYPE_DOUBLE",
Self::TypeLongDouble => "TYPE_LONG_DOUBLE",
Self::TypeBool => "TYPE_BOOL",
Self::TypeComplex => "TYPE_COMPLEX",
Self::TypeAuto => "TYPE_AUTO",
Self::TypeRecord => "TYPE_RECORD",
Self::TypePointer => "TYPE_POINTER",
Self::TypeArray => "TYPE_ARRAY",
Self::TypeFunction => "TYPE_FUNCTION",
Self::TypeStruct => "TYPE_STRUCT",
Self::TypeEnum => "TYPE_ENUM",
Self::TypeTypedef => "TYPE_TYPEDEF",
Self::TypeQualified => "TYPE_QUALIFIED",
Self::TypeReference => "TYPE_REFERENCE",
Self::TypeRValueReference => "TYPE_RVALUE_REFERENCE",
Self::TypeConstantArray => "TYPE_CONSTANT_ARRAY",
Self::TypeIncompleteArray => "TYPE_INCOMPLETE_ARRAY",
Self::TypeVariableArray => "TYPE_VARIABLE_ARRAY",
Self::TypeDependentSizedArray => "TYPE_DEPENDENT_SIZED_ARRAY",
Self::TypeVector => "TYPE_VECTOR",
Self::TypeExtVector => "TYPE_EXT_VECTOR",
Self::TypeMemberPointer => "TYPE_MEMBER_POINTER",
Self::TypeBlockPointer => "TYPE_BLOCK_POINTER",
Self::TypeObjCObject => "TYPE_OBJC_OBJECT",
Self::TypeObjCInterface => "TYPE_OBJC_INTERFACE",
Self::TypeObjCObjectPointer => "TYPE_OBJC_OBJECT_POINTER",
Self::TypePipe => "TYPE_PIPE",
Self::TypeAtomic => "TYPE_ATOMIC",
Self::TypeDecltype => "TYPE_DECLTYPE",
Self::TypeDecltypeAuto => "TYPE_DECLTYPE_AUTO",
Self::TypeTemplateSpecialization => "TYPE_TEMPLATE_SPECIALIZATION",
Self::TypeTemplateTypeParm => "TYPE_TEMPLATE_TYPE_PARM",
Self::TypeSubstTemplateTypeParm => "TYPE_SUBST_TEMPLATE_TYPE_PARM",
Self::TypeInjectedClassName => "TYPE_INJECTED_CLASS_NAME",
Self::TypeDependentName => "TYPE_DEPENDENT_NAME",
Self::TypeDependentTemplateSpecialization => "TYPE_DEPENDENT_TEMPLATE_SPECIALIZATION",
Self::TypePackExpansion => "TYPE_PACK_EXPANSION",
Self::TypeUnaryTransform => "TYPE_UNARY_TRANSFORM",
Self::TypeElaborated => "TYPE_ELABORATED",
Self::TypeAttributed => "TYPE_ATTRIBUTED",
Self::TypeAdjusted => "TYPE_ADJUSTED",
Self::TypeDecayed => "TYPE_DECAYED",
Self::TypeParen => "TYPE_PAREN",
Self::TypeMacroQualified => "TYPE_MACRO_QUALIFIED",
Self::TypeDeducedTemplateSpecialization => "TYPE_DEDUCED_TEMPLATE_SPECIALIZATION",
Self::TypeBTFTagAttributed => "TYPE_BTF_TAG_ATTRIBUTED",
Self::TypeUsing => "TYPE_USING",
Self::TypeEnumUnderlying => "TYPE_ENUM_UNDERLYING",
Self::DeclFunction => "DECL_FUNCTION",
Self::DeclVariable => "DECL_VARIABLE",
Self::DeclTypedef => "DECL_TYPEDEF",
Self::DeclStruct => "DECL_STRUCT",
Self::DeclEnum => "DECL_ENUM",
Self::DeclEnumVariant => "DECL_ENUM_VARIANT",
Self::DeclField => "DECL_FIELD",
Self::DeclParmVar => "DECL_PARM_VAR",
Self::DeclImplicitParam => "DECL_IMPLICIT_PARAM",
Self::DeclTranslationUnit => "DECL_TRANSLATION_UNIT",
Self::DeclRecord => "DECL_RECORD",
Self::DeclCXXRecord => "DECL_CXX_RECORD",
Self::DeclCXXMethod => "DECL_CXX_METHOD",
Self::DeclCXXConstructor => "DECL_CXX_CONSTRUCTOR",
Self::DeclCXXDestructor => "DECL_CXX_DESTRUCTOR",
Self::DeclCXXConversion => "DECL_CXX_CONVERSION",
Self::DeclNamespace => "DECL_NAMESPACE",
Self::DeclUsingDirective => "DECL_USING_DIRECTIVE",
Self::DeclUsingDecl => "DECL_USING_DECL",
Self::DeclNamespaceAlias => "DECL_NAMESPACE_ALIAS",
Self::DeclLinkageSpec => "DECL_LINKAGE_SPEC",
Self::DeclAccessSpec => "DECL_ACCESS_SPEC",
Self::DeclFriend => "DECL_FRIEND",
Self::DeclFriendTemplate => "DECL_FRIEND_TEMPLATE",
Self::DeclStaticAssert => "DECL_STATIC_ASSERT",
Self::DeclTemplate => "DECL_TEMPLATE",
Self::DeclTemplateTypeParm => "DECL_TEMPLATE_TYPE_PARM",
Self::DeclTemplateNonTypeParm => "DECL_TEMPLATE_NON_TYPE_PARM",
Self::DeclTemplateTemplateParm => "DECL_TEMPLATE_TEMPLATE_PARM",
Self::DeclClassTemplate => "DECL_CLASS_TEMPLATE",
Self::DeclFunctionTemplate => "DECL_FUNCTION_TEMPLATE",
Self::DeclTypeAlias => "DECL_TYPE_ALIAS",
Self::DeclTypeAliasTemplate => "DECL_TYPE_ALIAS_TEMPLATE",
Self::DeclLabel => "DECL_LABEL",
Self::DeclIndirectField => "DECL_INDIRECT_FIELD",
Self::DeclImport => "DECL_IMPORT",
Self::DeclExport => "DECL_EXPORT",
Self::DeclModule => "DECL_MODULE",
Self::StmtCompound => "STMT_COMPOUND",
Self::StmtReturn => "STMT_RETURN",
Self::StmtIf => "STMT_IF",
Self::StmtWhile => "STMT_WHILE",
Self::StmtDoWhile => "STMT_DO_WHILE",
Self::StmtFor => "STMT_FOR",
Self::StmtSwitch => "STMT_SWITCH",
Self::StmtCase => "STMT_CASE",
Self::StmtDefault => "STMT_DEFAULT",
Self::StmtBreak => "STMT_BREAK",
Self::StmtContinue => "STMT_CONTINUE",
Self::StmtGoto => "STMT_GOTO",
Self::StmtLabel => "STMT_LABEL",
Self::StmtExpr => "STMT_EXPR",
Self::StmtDecl => "STMT_DECL",
Self::StmtNull => "STMT_NULL",
Self::ExprIntLiteral => "EXPR_INT_LITERAL",
Self::ExprUIntLiteral => "EXPR_UINT_LITERAL",
Self::ExprFloatLiteral => "EXPR_FLOAT_LITERAL",
Self::ExprDoubleLiteral => "EXPR_DOUBLE_LITERAL",
Self::ExprCharLiteral => "EXPR_CHAR_LITERAL",
Self::ExprStringLiteral => "EXPR_STRING_LITERAL",
Self::ExprIdent => "EXPR_IDENT",
Self::ExprUnary => "EXPR_UNARY",
Self::ExprSizeOf => "EXPR_SIZE_OF",
Self::ExprSizeOfType => "EXPR_SIZE_OF_TYPE",
Self::ExprAlignOf => "EXPR_ALIGN_OF",
Self::ExprAlignOfType => "EXPR_ALIGN_OF_TYPE",
Self::ExprCast => "EXPR_CAST",
Self::ExprBinary => "EXPR_BINARY",
Self::ExprAssign => "EXPR_ASSIGN",
Self::ExprConditional => "EXPR_CONDITIONAL",
Self::ExprCall => "EXPR_CALL",
Self::ExprSubscript => "EXPR_SUBSCRIPT",
Self::ExprMember => "EXPR_MEMBER",
Self::ExprPostInc => "EXPR_POST_INC",
Self::ExprPostDec => "EXPR_POST_DEC",
Self::ExprPreInc => "EXPR_PRE_INC",
Self::ExprPreDec => "EXPR_PRE_DEC",
Self::ExprCompoundLiteral => "EXPR_COMPOUND_LITERAL",
Self::ExprAggregateLiteral => "EXPR_AGGREGATE_LITERAL",
_ => "UNKNOWN",
}
}
pub fn value(&self) -> u32 {
*self as u32
}
pub fn from_u32(v: u32) -> Option<Self> {
Some(match v {
0 => Self::Metadata,
1 => Self::IdentifierOffset,
2 => Self::IdentifierTable,
3 => Self::SourceLocationOffset,
4 => Self::SourceLocationTable,
5 => Self::TypeOffset,
6 => Self::DeclarationOffset,
7 => Self::StatementOffset,
8 => Self::MacroDefinitionOffset,
9 => Self::ModuleOffset,
10 => Self::FileTable,
11 => Self::DirectoryTable,
12 => Self::TargetTriple,
13 => Self::LanguageOptions,
14 => Self::TargetOptions,
15 => Self::HeaderSearchOptions,
16 => Self::PCHValidatorInfo,
17 => Self::ModuleMap,
18 => Self::SourceManagerBlock,
19 => Self::InputFiles,
20 => Self::PreprocessorOptions,
21 => Self::OriginalFileName,
22 => Self::OriginalFileID,
23 => Self::VersionControl,
100 => Self::TypeVoid,
101 => Self::TypeChar,
102 => Self::TypeSChar,
103 => Self::TypeUChar,
104 => Self::TypeShort,
105 => Self::TypeUShort,
106 => Self::TypeInt,
107 => Self::TypeUInt,
108 => Self::TypeLong,
109 => Self::TypeULong,
110 => Self::TypeLongLong,
111 => Self::TypeULongLong,
112 => Self::TypeFloat,
113 => Self::TypeDouble,
114 => Self::TypeLongDouble,
115 => Self::TypeBool,
116 => Self::TypeComplex,
117 => Self::TypeAuto,
118 => Self::TypeRecord,
120 => Self::TypePointer,
121 => Self::TypeArray,
122 => Self::TypeFunction,
123 => Self::TypeStruct,
124 => Self::TypeEnum,
125 => Self::TypeTypedef,
126 => Self::TypeQualified,
127 => Self::TypeReference,
128 => Self::TypeRValueReference,
129 => Self::TypeConstantArray,
130 => Self::TypeIncompleteArray,
131 => Self::TypeVariableArray,
132 => Self::TypeDependentSizedArray,
133 => Self::TypeVector,
134 => Self::TypeExtVector,
135 => Self::TypeMemberPointer,
136 => Self::TypeBlockPointer,
137 => Self::TypeObjCObject,
138 => Self::TypeObjCInterface,
139 => Self::TypeObjCObjectPointer,
140 => Self::TypePipe,
141 => Self::TypeAtomic,
142 => Self::TypeDecltype,
143 => Self::TypeDecltypeAuto,
144 => Self::TypeTemplateSpecialization,
145 => Self::TypeTemplateTypeParm,
146 => Self::TypeSubstTemplateTypeParm,
147 => Self::TypeInjectedClassName,
148 => Self::TypeDependentName,
149 => Self::TypeDependentTemplateSpecialization,
150 => Self::TypePackExpansion,
151 => Self::TypeUnaryTransform,
152 => Self::TypeElaborated,
153 => Self::TypeAttributed,
154 => Self::TypeAdjusted,
155 => Self::TypeDecayed,
156 => Self::TypeParen,
157 => Self::TypeMacroQualified,
158 => Self::TypeDeducedTemplateSpecialization,
159 => Self::TypeBTFTagAttributed,
160 => Self::TypeUsing,
161 => Self::TypeEnumUnderlying,
200 => Self::DeclFunction,
201 => Self::DeclVariable,
202 => Self::DeclTypedef,
203 => Self::DeclStruct,
204 => Self::DeclEnum,
205 => Self::DeclEnumVariant,
206 => Self::DeclField,
207 => Self::DeclParmVar,
208 => Self::DeclImplicitParam,
209 => Self::DeclTranslationUnit,
210 => Self::DeclRecord,
211 => Self::DeclCXXRecord,
212 => Self::DeclCXXMethod,
213 => Self::DeclCXXConstructor,
214 => Self::DeclCXXDestructor,
215 => Self::DeclCXXConversion,
216 => Self::DeclNamespace,
217 => Self::DeclUsingDirective,
218 => Self::DeclUsingDecl,
219 => Self::DeclNamespaceAlias,
220 => Self::DeclLinkageSpec,
221 => Self::DeclAccessSpec,
222 => Self::DeclFriend,
223 => Self::DeclFriendTemplate,
224 => Self::DeclStaticAssert,
225 => Self::DeclTemplate,
226 => Self::DeclTemplateTypeParm,
227 => Self::DeclTemplateNonTypeParm,
228 => Self::DeclTemplateTemplateParm,
229 => Self::DeclClassTemplate,
230 => Self::DeclFunctionTemplate,
231 => Self::DeclTypeAlias,
232 => Self::DeclTypeAliasTemplate,
233 => Self::DeclLabel,
234 => Self::DeclIndirectField,
246 => Self::DeclImport,
247 => Self::DeclExport,
248 => Self::DeclModule,
300 => Self::StmtCompound,
301 => Self::StmtReturn,
302 => Self::StmtIf,
303 => Self::StmtWhile,
304 => Self::StmtDoWhile,
305 => Self::StmtFor,
306 => Self::StmtSwitch,
307 => Self::StmtCase,
308 => Self::StmtDefault,
309 => Self::StmtBreak,
310 => Self::StmtContinue,
311 => Self::StmtGoto,
312 => Self::StmtLabel,
313 => Self::StmtExpr,
314 => Self::StmtDecl,
315 => Self::StmtNull,
400 => Self::ExprIntLiteral,
401 => Self::ExprUIntLiteral,
402 => Self::ExprFloatLiteral,
403 => Self::ExprDoubleLiteral,
404 => Self::ExprCharLiteral,
405 => Self::ExprStringLiteral,
406 => Self::ExprIdent,
407 => Self::ExprUnary,
408 => Self::ExprSizeOf,
409 => Self::ExprSizeOfType,
410 => Self::ExprAlignOf,
411 => Self::ExprAlignOfType,
412 => Self::ExprCast,
413 => Self::ExprBinary,
414 => Self::ExprAssign,
415 => Self::ExprConditional,
416 => Self::ExprCall,
417 => Self::ExprSubscript,
418 => Self::ExprMember,
419 => Self::ExprPostInc,
420 => Self::ExprPostDec,
421 => Self::ExprPreInc,
422 => Self::ExprPreDec,
423 => Self::ExprCompoundLiteral,
500 => Self::ExprAggregateLiteral,
_ => return None,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum ASTBlockID {
BlockInfo = 0,
ASTBlock = 1,
IdentifierBlock = 2,
SourceLocationBlock = 3,
TypeBlock = 4,
DeclarationBlock = 5,
StatementBlock = 6,
MacroBlock = 7,
MetadataBlock = 8,
SourceManagerBlock = 9,
ModuleExtensionBlock = 10,
PreprocessorBlock = 11,
HeaderSearchBlock = 12,
InputFilesBlock = 13,
FileTableBlock = 14,
ControlBlock = 15,
}
impl ASTBlockID {
pub fn id(&self) -> u32 {
*self as u32
}
pub fn from_u32(v: u32) -> Option<Self> {
Some(match v {
0 => Self::BlockInfo,
1 => Self::ASTBlock,
2 => Self::IdentifierBlock,
3 => Self::SourceLocationBlock,
4 => Self::TypeBlock,
5 => Self::DeclarationBlock,
6 => Self::StatementBlock,
7 => Self::MacroBlock,
8 => Self::MetadataBlock,
9 => Self::SourceManagerBlock,
10 => Self::ModuleExtensionBlock,
11 => Self::PreprocessorBlock,
12 => Self::HeaderSearchBlock,
13 => Self::InputFilesBlock,
14 => Self::FileTableBlock,
15 => Self::ControlBlock,
_ => return None,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum ASTControlCode {
EndBlock = 0,
EnterSubBlock = 1,
DefineAbbrev = 2,
UnabbreviatedRecord = 3,
}
impl ASTControlCode {
pub fn id(&self) -> u32 {
*self as u32
}
}
#[derive(Debug, Clone)]
pub struct ASTAbbreviation {
pub record_type: ASTRecordType,
pub operands: Vec<ASTAbbrevOperand>,
pub abbrev_number: u32,
}
#[derive(Debug, Clone)]
pub enum ASTAbbrevOperand {
Literal(u64),
Fixed(u32),
VBR(u32),
Char6,
Blob,
Array,
}
pub const CLANG_AST_MAGIC: u32 = 0xC1A9_A570;
pub const CLANG_AST_VERSION_MAJOR: u32 = 25;
pub const CLANG_AST_VERSION_MINOR: u32 = 0;
pub const CLANG_AST_VERSION: u32 = (CLANG_AST_VERSION_MAJOR << 16) | CLANG_AST_VERSION_MINOR;
pub const CLANG_AST_MIN_READER_VERSION: u32 = 20;
pub const PCH_SIGNATURE: [u8; 8] = *b"CPCH\0\0\0\0";
pub const MODULE_SIGNATURE: [u8; 8] = *b"CMOD\0\0\0\0";
pub const PCH_EXTENSION: &str = "pch";
pub const PCM_EXTENSION: &str = "pcm";
pub const AST_FILE_EXTENSION: &str = "ast";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct EncodedSourceLoc(pub u32);
impl EncodedSourceLoc {
pub fn new(file_id: u8, offset: u32) -> Self {
debug_assert!(offset < (1 << 24), "offset too large for encoding");
Self(((file_id as u32) << 24) | (offset & 0x00FF_FFFF))
}
pub fn file_id(&self) -> u8 {
((self.0 >> 24) & 0xFF) as u8
}
pub fn offset(&self) -> u32 {
self.0 & 0x00FF_FFFF
}
pub fn decode(&self, source_manager: &X86SourceManagerExtensions) -> Option<SourceLoc> {
source_manager.decode_location(*self)
}
}
#[derive(Debug, Clone)]
pub struct SourceLocationEncoding {
pub current_file_id: u8,
pub current_offset: u32,
pub base_offset: u32,
pub use_delta: bool,
pub encoding_map: HashMap<(u32, u32, u32), EncodedSourceLoc>,
pub next_id: u32,
}
impl Default for SourceLocationEncoding {
fn default() -> Self {
Self {
current_file_id: 0,
current_offset: 0,
base_offset: 0,
use_delta: true,
encoding_map: HashMap::new(),
next_id: 1,
}
}
}
impl SourceLocationEncoding {
pub fn encode(&mut self, loc: &SourceLoc) -> EncodedSourceLoc {
let key = (loc.line, loc.column, loc.offset as u32);
if let Some(encoded) = self.encoding_map.get(&key) {
return *encoded;
}
let file_id = 1u8; let encoded = EncodedSourceLoc::new(file_id, loc.offset as u32);
self.encoding_map.insert(key, encoded);
self.current_offset = loc.offset as u32;
encoded
}
pub fn reset_for_file(&mut self) {
self.current_file_id = 0;
self.current_offset = 0;
self.base_offset = 0;
}
}
#[derive(Debug, Clone)]
pub struct X86SourceManagerExtensions {
pub file_entries: Vec<SourceFileEntry>,
pub file_id_remap: BTreeMap<u32, u32>,
pub location_cache: HashMap<EncodedSourceLoc, SourceLoc>,
pub directories: Vec<PathBuf>,
pub filenames: Vec<String>,
pub main_file: Option<String>,
pub is_pch: bool,
pub pch_base_dir: Option<PathBuf>,
}
#[derive(Debug, Clone)]
pub struct SourceFileEntry {
pub filename: String,
pub dir_index: u32,
pub size: u64,
pub mod_time: u64,
pub file_id: u32,
pub kind: SourceFileKind,
pub is_module_header: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SourceFileKind {
User,
System,
ExternCSystem,
PCH,
ModuleMap,
}
impl Default for X86SourceManagerExtensions {
fn default() -> Self {
Self {
file_entries: Vec::new(),
file_id_remap: BTreeMap::new(),
location_cache: HashMap::new(),
directories: Vec::new(),
filenames: Vec::new(),
main_file: None,
is_pch: false,
pch_base_dir: None,
}
}
}
impl X86SourceManagerExtensions {
pub fn new() -> Self {
Self::default()
}
pub fn add_file(&mut self, entry: SourceFileEntry) -> u32 {
let id = self.file_entries.len() as u32;
self.file_entries.push(entry);
id
}
pub fn add_directory(&mut self, dir: PathBuf) -> u32 {
let idx = self.directories.len() as u32;
self.directories.push(dir);
idx
}
pub fn add_filename(&mut self, name: String) -> u32 {
let idx = self.filenames.len() as u32;
self.filenames.push(name);
idx
}
pub fn decode_location(&self, encoded: EncodedSourceLoc) -> Option<SourceLoc> {
if let Some(cached) = self.location_cache.get(&encoded) {
return Some(*cached);
}
let file_id = encoded.file_id();
let offset = encoded.offset();
if let Some(entry) = self.file_entries.get(file_id as usize) {
let line = 1u32;
let column = offset + 1;
Some(SourceLoc::new(line, column, offset as usize))
} else {
None
}
}
pub fn translate_range(
&self,
begin: EncodedSourceLoc,
end: EncodedSourceLoc,
) -> Option<(SourceLoc, SourceLoc)> {
let start = self.decode_location(begin)?;
let finish = self.decode_location(end)?;
Some((start, finish))
}
pub fn remap_file_id(&self, original: u32) -> u32 {
self.file_id_remap
.get(&original)
.copied()
.unwrap_or(original)
}
pub fn set_main_file(&mut self, filename: &str) {
self.main_file = Some(filename.to_string());
}
pub fn load_from_module(&mut self, module: &X86ModuleFile) -> Result<(), String> {
self.directories = module.directories.clone();
self.filenames = module.filenames.clone();
self.is_pch = module.is_pch;
self.pch_base_dir = module.base_dir.clone();
Ok(())
}
pub fn populate_from_tables(&mut self, dirs: Vec<PathBuf>, files: Vec<String>) {
self.directories = dirs;
self.filenames = files;
}
pub fn num_files(&self) -> usize {
self.file_entries.len()
}
pub fn is_valid_file_id(&self, id: u32) -> bool {
(id as usize) < self.file_entries.len()
}
}
#[derive(Debug, Clone)]
pub struct X86ModuleFile {
pub file_name: String,
pub file_path: PathBuf,
pub file_size: u64,
pub is_pch: bool,
pub is_module: bool,
pub is_header_unit: bool,
pub signature: [u8; 8],
pub version: u32,
pub version_major: u32,
pub version_minor: u32,
pub target_triple: String,
pub module_name: Option<String>,
pub language_standard: CLangStandard,
pub language_options: LanguageOptionsEncoding,
pub target_options: TargetOptionsEncoding,
pub header_search_paths: Vec<String>,
pub directories: Vec<PathBuf>,
pub filenames: Vec<String>,
pub original_file: Option<String>,
pub original_file_id: Option<u32>,
pub input_files: Vec<String>,
pub base_dir: Option<PathBuf>,
pub dependencies: Vec<String>,
pub is_valid: bool,
pub load_errors: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct LanguageOptionsEncoding {
pub c_standard: u8,
pub cxx: bool,
pub cxx20: bool,
pub cxx23: bool,
pub gnu_mode: bool,
pub ms_extensions: bool,
pub borland: bool,
pub trigraphs: bool,
pub digraphs: bool,
pub bool_type: bool,
pub wchar: bool,
pub char8: bool,
pub line_comment: bool,
pub long_long: bool,
pub hex_floats: bool,
pub complex: bool,
pub imaginary: bool,
pub designated_inits: bool,
pub blocks: bool,
pub openmp: bool,
pub freestanding: bool,
pub exceptions: bool,
pub rtti: bool,
pub signed_char: bool,
pub char_width: u8,
pub short_width: u8,
pub int_width: u8,
pub long_width: u8,
pub long_long_width: u8,
pub pointer_width: u8,
pub wchar_width: u8,
pub size_t_width: u8,
}
impl Default for LanguageOptionsEncoding {
fn default() -> Self {
Self {
c_standard: 17,
cxx: false,
cxx20: false,
cxx23: false,
gnu_mode: false,
ms_extensions: false,
borland: false,
trigraphs: false,
digraphs: true,
bool_type: true,
wchar: true,
char8: false,
line_comment: true,
long_long: true,
hex_floats: true,
complex: true,
imaginary: false,
designated_inits: true,
blocks: false,
openmp: false,
freestanding: false,
exceptions: true,
rtti: true,
signed_char: true,
char_width: 8,
short_width: 16,
int_width: 32,
long_width: 64,
long_long_width: 64,
pointer_width: 64,
wchar_width: 32,
size_t_width: 64,
}
}
}
#[derive(Debug, Clone)]
pub struct TargetOptionsEncoding {
pub triple: String,
pub cpu: String,
pub features: String,
pub abi: String,
pub is_x86: bool,
pub is_x86_64: bool,
pub sse: bool,
pub sse2: bool,
pub sse3: bool,
pub ssse3: bool,
pub sse4_1: bool,
pub sse4_2: bool,
pub avx: bool,
pub avx2: bool,
pub avx512: bool,
pub mmx: bool,
pub soft_float: bool,
pub data_layout: String,
pub stack_protector: bool,
pub code_model: String,
pub relocation_model: String,
pub pic: bool,
pub pie: bool,
pub pointer_width: u8,
pub size_t_width: u8,
pub long_width: u8,
}
impl Default for TargetOptionsEncoding {
fn default() -> Self {
Self {
triple: "x86_64-unknown-linux-gnu".to_string(),
cpu: "x86-64".to_string(),
features: "+sse2,+cx8".to_string(),
abi: "sysv".to_string(),
is_x86: false,
is_x86_64: true,
sse: true,
sse2: true,
sse3: false,
ssse3: false,
sse4_1: false,
sse4_2: false,
avx: false,
avx2: false,
avx512: false,
mmx: true,
soft_float: false,
data_layout:
"e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
.to_string(),
stack_protector: false,
code_model: "default".to_string(),
relocation_model: "pic".to_string(),
pic: true,
pie: false,
pointer_width: 64,
size_t_width: 64,
long_width: 64,
}
}
}
impl X86ModuleFile {
pub fn new(file_path: &Path, is_pch: bool) -> Self {
let file_name = file_path
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_default();
let file_size = std::fs::metadata(file_path).map(|m| m.len()).unwrap_or(0);
let signature = if is_pch {
PCH_SIGNATURE
} else {
MODULE_SIGNATURE
};
Self {
file_name,
file_path: file_path.to_path_buf(),
file_size,
is_pch,
is_module: !is_pch,
is_header_unit: false,
signature,
version: CLANG_AST_VERSION,
version_major: CLANG_AST_VERSION_MAJOR,
version_minor: CLANG_AST_VERSION_MINOR,
target_triple: String::new(),
module_name: None,
language_standard: CLangStandard::C17,
language_options: LanguageOptionsEncoding::default(),
target_options: TargetOptionsEncoding::default(),
header_search_paths: Vec::new(),
directories: Vec::new(),
filenames: Vec::new(),
original_file: None,
original_file_id: None,
input_files: Vec::new(),
base_dir: file_path.parent().map(|p| p.to_path_buf()),
dependencies: Vec::new(),
is_valid: true,
load_errors: Vec::new(),
}
}
pub fn is_signature_valid(&self) -> bool {
if self.is_pch {
&self.signature[0..4] == &PCH_SIGNATURE[0..4]
} else {
&self.signature[0..4] == &MODULE_SIGNATURE[0..4]
}
}
pub fn header_bytes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(64);
buf.extend_from_slice(&self.signature);
buf.extend_from_slice(&self.version.to_le_bytes());
buf.extend_from_slice(&self.version_major.to_le_bytes());
buf.extend_from_slice(&self.version_minor.to_le_bytes());
buf.extend_from_slice(&[0u8; 8]);
let mut flags: u32 = 0;
if self.is_pch {
flags |= 0x01;
}
if self.is_module {
flags |= 0x02;
}
if self.is_header_unit {
flags |= 0x04;
}
buf.extend_from_slice(&flags.to_le_bytes());
buf
}
pub fn record_error(&mut self, error: String) {
self.load_errors.push(error);
self.is_valid = false;
}
pub fn errors(&self) -> &[String] {
&self.load_errors
}
}
#[derive(Debug, Clone)]
pub struct X86PCHValidator {
pub language_options: LanguageOptionsEncoding,
pub target_options: TargetOptionsEncoding,
pub header_search_paths: Vec<String>,
pub is_compatible: bool,
pub incompatibilities: Vec<String>,
pub strict_mode: bool,
pub validate_headers: bool,
pub validate_extensions: bool,
pub validate_features: bool,
}
impl X86PCHValidator {
pub fn new() -> Self {
Self {
language_options: LanguageOptionsEncoding::default(),
target_options: TargetOptionsEncoding::default(),
header_search_paths: Vec::new(),
is_compatible: true,
incompatibilities: Vec::new(),
strict_mode: true,
validate_headers: true,
validate_extensions: true,
validate_features: true,
}
}
pub fn load_from_module(&mut self, module: &X86ModuleFile) {
self.language_options = module.language_options.clone();
self.target_options = module.target_options.clone();
self.header_search_paths = module.header_search_paths.clone();
}
pub fn validate_language_options(&mut self, current: &LanguageOptionsEncoding) -> bool {
self.incompatibilities.clear();
let pch = &self.language_options;
if pch.c_standard != current.c_standard && self.strict_mode {
self.incompatibilities.push(format!(
"C standard mismatch: PCH={}, current={}",
pch.c_standard, current.c_standard
));
}
if pch.cxx != current.cxx {
self.incompatibilities.push(format!(
"C++ mode mismatch: PCH={}, current={}",
pch.cxx, current.cxx
));
}
if pch.cxx20 && !current.cxx20 {
self.incompatibilities
.push("PCH requires C++20 but current compilation does not".to_string());
}
if pch.cxx23 && !current.cxx23 {
self.incompatibilities
.push("PCH requires C++23 but current compilation does not".to_string());
}
if self.validate_extensions && pch.gnu_mode != current.gnu_mode {
self.incompatibilities.push(format!(
"GNU extensions mismatch: PCH={}, current={}",
pch.gnu_mode, current.gnu_mode
));
}
if self.validate_extensions && pch.ms_extensions != current.ms_extensions {
self.incompatibilities.push(format!(
"MSVC extensions mismatch: PCH={}, current={}",
pch.ms_extensions, current.ms_extensions
));
}
if pch.exceptions != current.exceptions {
self.incompatibilities.push(format!(
"Exception handling mismatch: PCH={}, current={}",
pch.exceptions, current.exceptions
));
}
if pch.rtti != current.rtti {
self.incompatibilities.push(format!(
"RTTI mismatch: PCH={}, current={}",
pch.rtti, current.rtti
));
}
if pch.openmp && !current.openmp {
self.incompatibilities
.push("PCH requires OpenMP but current compilation does not".to_string());
}
if pch.freestanding != current.freestanding {
self.incompatibilities.push(format!(
"Freestanding mismatch: PCH={}, current={}",
pch.freestanding, current.freestanding
));
}
if pch.signed_char != current.signed_char {
self.incompatibilities.push(format!(
"Signed char default mismatch: PCH={}, current={}",
pch.signed_char, current.signed_char
));
}
if pch.char_width != current.char_width {
self.incompatibilities.push(format!(
"Char width mismatch: PCH={}, current={}",
pch.char_width, current.char_width
));
}
if pch.short_width != current.short_width {
self.incompatibilities.push(format!(
"Short width mismatch: PCH={}, current={}",
pch.short_width, current.short_width
));
}
if pch.int_width != current.int_width {
self.incompatibilities.push(format!(
"Int width mismatch: PCH={}, current={}",
pch.int_width, current.int_width
));
}
if pch.long_width != current.long_width {
self.incompatibilities.push(format!(
"Long width mismatch: PCH={}, current={}",
pch.long_width, current.long_width
));
}
if pch.pointer_width != current.pointer_width {
self.incompatibilities.push(format!(
"Pointer width mismatch: PCH={}, current={}",
pch.pointer_width, current.pointer_width
));
}
self.is_compatible = self.incompatibilities.is_empty();
self.is_compatible
}
pub fn validate_target_options(&mut self, current: &TargetOptionsEncoding) -> bool {
let pch = &self.target_options;
if pch.triple != current.triple {
self.incompatibilities.push(format!(
"Target triple mismatch: PCH={}, current={}",
pch.triple, current.triple
));
}
if pch.is_x86 != current.is_x86 || pch.is_x86_64 != current.is_x86_64 {
self.incompatibilities.push(format!(
"Architecture mismatch: PCH x86={}/x86_64={}, current x86={}/x86_64={}",
pch.is_x86, pch.is_x86_64, current.is_x86, current.is_x86_64
));
}
if pch.cpu != current.cpu {
self.incompatibilities.push(format!(
"CPU mismatch: PCH={}, current={}",
pch.cpu, current.cpu
));
}
if pch.abi != current.abi {
self.incompatibilities.push(format!(
"ABI mismatch: PCH={}, current={}",
pch.abi, current.abi
));
}
if pch.data_layout != current.data_layout {
self.incompatibilities.push(format!(
"Data layout mismatch: PCH={}, current={}",
pch.data_layout, current.data_layout
));
}
if self.validate_features {
if pch.sse2 && !current.sse2 {
self.incompatibilities
.push("PCH requires SSE2 but current target does not support it".to_string());
}
if pch.avx && !current.avx {
self.incompatibilities
.push("PCH requires AVX but current target does not support it".to_string());
}
if pch.avx2 && !current.avx2 {
self.incompatibilities
.push("PCH requires AVX2 but current target does not support it".to_string());
}
if pch.avx512 && !current.avx512 {
self.incompatibilities.push(
"PCH requires AVX-512 but current target does not support it".to_string(),
);
}
}
if pch.soft_float != current.soft_float {
self.incompatibilities.push(format!(
"Soft-float mismatch: PCH={}, current={}",
pch.soft_float, current.soft_float
));
}
if pch.pic != current.pic {
self.incompatibilities.push(format!(
"PIC mismatch: PCH={}, current={}",
pch.pic, current.pic
));
}
if pch.pie != current.pie {
self.incompatibilities.push(format!(
"PIE mismatch: PCH={}, current={}",
pch.pie, current.pie
));
}
self.is_compatible = self.incompatibilities.is_empty();
self.is_compatible
}
pub fn validate_header_search_paths(&mut self, current: &[String]) -> bool {
if !self.validate_headers {
return true;
}
let current_set: HashSet<&str> = current.iter().map(|s| s.as_str()).collect();
for pch_path in &self.header_search_paths {
if !current_set.contains(pch_path.as_str()) {
self.incompatibilities.push(format!(
"Header search path '{}' was used in PCH but is not in current compilation",
pch_path
));
}
}
self.is_compatible = self.incompatibilities.is_empty();
self.is_compatible
}
pub fn validate(
&mut self,
current_lang: &LanguageOptionsEncoding,
current_target: &TargetOptionsEncoding,
current_headers: &[String],
) -> bool {
let lang_ok = self.validate_language_options(current_lang);
let target_ok = self.validate_target_options(current_target);
let headers_ok = self.validate_header_search_paths(current_headers);
lang_ok && target_ok && headers_ok
}
pub fn get_incompatibilities(&self) -> &[String] {
&self.incompatibilities
}
pub fn compatible(&self) -> bool {
self.is_compatible
}
}
impl Default for X86PCHValidator {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86ASTContext {
pub type_cache: HashMap<TypeHash, QualType>,
pub type_id_map: HashMap<u32, QualType>,
pub decl_id_map: HashMap<u32, Decl>,
pub lazy_decls: BTreeMap<u32, LazyDeclData>,
pub external_sources: Vec<X86ModuleFile>,
pub dedup_types: bool,
pub next_type_id: u32,
pub next_decl_id: u32,
pub source_manager: X86SourceManagerExtensions,
pub validator: X86PCHValidator,
pub identifiers: Vec<String>,
pub identifier_ids: HashMap<String, u32>,
pub standard: CLangStandard,
pub is_x86: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TypeHash(u64);
#[derive(Debug, Clone)]
pub struct LazyDeclData {
pub bitstream_offset: u64,
pub record_type: ASTRecordType,
pub is_deserialized: bool,
pub raw_data: Vec<u8>,
}
impl LazyDeclData {
pub fn new(offset: u64, record_type: ASTRecordType, data: Vec<u8>) -> Self {
Self {
bitstream_offset: offset,
record_type,
is_deserialized: false,
raw_data: data,
}
}
}
impl X86ASTContext {
pub fn new() -> Self {
Self {
type_cache: HashMap::new(),
type_id_map: HashMap::new(),
decl_id_map: HashMap::new(),
lazy_decls: BTreeMap::new(),
external_sources: Vec::new(),
dedup_types: true,
next_type_id: 0,
next_decl_id: 0,
source_manager: X86SourceManagerExtensions::new(),
validator: X86PCHValidator::new(),
identifiers: Vec::new(),
identifier_ids: HashMap::new(),
standard: CLangStandard::C17,
is_x86: true,
}
}
pub fn x86_64() -> Self {
let mut ctx = Self::new();
ctx.is_x86 = true;
ctx.source_manager.is_pch = false;
ctx
}
pub fn register_type(&mut self, ty: &QualType) -> u32 {
let hash = compute_type_hash(ty);
if let Some(id) = self.type_cache.get(&hash).and_then(|cached| {
self.type_id_map.iter().find_map(|(id, t)| {
if std::ptr::eq(t as *const _, cached as *const _) {
Some(*id)
} else if t == cached {
Some(*id)
} else {
None
}
})
}) {
return id;
}
let id = self.next_type_id;
self.next_type_id += 1;
self.type_cache.insert(hash, ty.clone());
self.type_id_map.insert(id, ty.clone());
id
}
pub fn get_type(&self, id: u32) -> Option<&QualType> {
self.type_id_map.get(&id)
}
pub fn register_decl(&mut self, id: u32, decl: Decl) {
self.decl_id_map.insert(id, decl);
}
pub fn get_decl(&self, id: u32) -> Option<&Decl> {
self.decl_id_map.get(&id)
}
pub fn get_identifier_id(&mut self, name: &str) -> u32 {
if let Some(id) = self.identifier_ids.get(name) {
return *id;
}
let id = self.identifiers.len() as u32;
self.identifiers.push(name.to_string());
self.identifier_ids.insert(name.to_string(), id);
id
}
pub fn get_identifier(&self, id: u32) -> Option<&str> {
self.identifiers.get(id as usize).map(|s| s.as_str())
}
pub fn add_external_source(&mut self, module: X86ModuleFile) {
self.external_sources.push(module);
}
pub fn has_external_decl(&self, decl_id: u32) -> bool {
self.lazy_decls.contains_key(&decl_id) || self.external_sources.iter().any(|m| m.is_valid)
}
pub fn register_lazy_decl(&mut self, decl_id: u32, data: LazyDeclData) {
self.lazy_decls.insert(decl_id, data);
}
pub fn set_standard(&mut self, standard: CLangStandard) {
self.standard = standard;
}
}
impl Default for X86ASTContext {
fn default() -> Self {
Self::new()
}
}
pub fn compute_type_hash(ty: &QualType) -> TypeHash {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
ty.is_const.hash(&mut hasher);
ty.is_volatile.hash(&mut hasher);
ty.is_restrict.hash(&mut hasher);
hash_type_node(&ty.base, &mut hasher);
TypeHash(hasher.finish())
}
fn hash_type_node<H: std::hash::Hasher>(node: &TypeNode, hasher: &mut H) {
use std::hash::Hash;
match node {
TypeNode::Void => 0u8.hash(hasher),
TypeNode::Char => 1u8.hash(hasher),
TypeNode::SChar => 2u8.hash(hasher),
TypeNode::UChar => 3u8.hash(hasher),
TypeNode::Short => 4u8.hash(hasher),
TypeNode::UShort => 5u8.hash(hasher),
TypeNode::Int => 6u8.hash(hasher),
TypeNode::UInt => 7u8.hash(hasher),
TypeNode::Long => 8u8.hash(hasher),
TypeNode::ULong => 9u8.hash(hasher),
TypeNode::LongLong => 10u8.hash(hasher),
TypeNode::ULongLong => 11u8.hash(hasher),
TypeNode::Float => 12u8.hash(hasher),
TypeNode::Double => 13u8.hash(hasher),
TypeNode::LongDouble => 14u8.hash(hasher),
TypeNode::Bool => 15u8.hash(hasher),
TypeNode::Complex => 16u8.hash(hasher),
TypeNode::Auto => 17u8.hash(hasher),
TypeNode::Record(name) => {
18u8.hash(hasher);
name.hash(hasher);
}
TypeNode::Pointer(inner) => {
19u8.hash(hasher);
compute_type_hash(inner).0.hash(hasher);
}
TypeNode::Array { elem, size } => {
20u8.hash(hasher);
compute_type_hash(elem).0.hash(hasher);
size.hash(hasher);
}
TypeNode::Function {
ret,
params,
is_vararg,
} => {
21u8.hash(hasher);
compute_type_hash(ret).0.hash(hasher);
for p in params {
compute_type_hash(p).0.hash(hasher);
}
is_vararg.hash(hasher);
}
TypeNode::Struct {
name,
fields,
is_union,
} => {
22u8.hash(hasher);
name.hash(hasher);
for f in fields {
f.name.hash(hasher);
compute_type_hash(&f.ty).0.hash(hasher);
f.bit_width.hash(hasher);
}
is_union.hash(hasher);
}
TypeNode::Enum { name, variants } => {
23u8.hash(hasher);
name.hash(hasher);
for v in variants {
v.name.hash(hasher);
v.value.hash(hasher);
}
}
TypeNode::Typedef { name, underlying } => {
24u8.hash(hasher);
name.hash(hasher);
compute_type_hash(underlying).0.hash(hasher);
}
}
}
#[derive(Debug)]
pub struct X86ASTWriter {
pub buffer: Vec<u8>,
pub context: X86ASTContext,
pub type_records: Vec<SerializedTypeRecord>,
pub decl_records: Vec<SerializedDeclRecord>,
pub stmt_records: Vec<SerializedStmtRecord>,
pub expr_records: Vec<SerializedExprRecord>,
pub identifier_table: BTreeMap<u32, String>,
pub macro_table: Vec<SerializedMacroRecord>,
pub sloc_encoding: SourceLocationEncoding,
pub current_offset: u64,
pub validator_info: PCHValidatorInfoEncoding,
pub errors: Vec<String>,
pub stats: SerializationStats,
}
#[derive(Debug, Clone)]
pub struct SerializedTypeRecord {
pub kind: ASTRecordType,
pub type_id: u32,
pub data: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct SerializedDeclRecord {
pub kind: ASTRecordType,
pub decl_id: u32,
pub offset: u64,
pub data: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct SerializedStmtRecord {
pub kind: ASTRecordType,
pub offset: u64,
pub data: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct SerializedExprRecord {
pub kind: ASTRecordType,
pub offset: u64,
pub data: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct SerializedMacroRecord {
pub name: String,
pub macro_id: u32,
pub data: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct PCHValidatorInfoEncoding {
pub language_options: LanguageOptionsEncoding,
pub target_options: TargetOptionsEncoding,
pub header_search_paths: Vec<String>,
pub target_triple: String,
pub ast_version: u32,
pub compiler_id: String,
}
impl Default for PCHValidatorInfoEncoding {
fn default() -> Self {
Self {
language_options: LanguageOptionsEncoding::default(),
target_options: TargetOptionsEncoding::default(),
header_search_paths: Vec::new(),
target_triple: "x86_64-unknown-linux-gnu".to_string(),
ast_version: CLANG_AST_VERSION,
compiler_id: "llvm-native-core 0.1.0".to_string(),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct SerializationStats {
pub num_types: u64,
pub num_decls: u64,
pub num_stmts: u64,
pub num_exprs: u64,
pub num_identifiers: u64,
pub num_macros: u64,
pub total_bytes: u64,
pub dedup_savings: u64,
pub dedup_count: u64,
}
impl X86ASTWriter {
pub fn new() -> Self {
Self {
buffer: Vec::with_capacity(65536),
context: X86ASTContext::x86_64(),
type_records: Vec::new(),
decl_records: Vec::new(),
stmt_records: Vec::new(),
expr_records: Vec::new(),
identifier_table: BTreeMap::new(),
macro_table: Vec::new(),
sloc_encoding: SourceLocationEncoding::default(),
current_offset: 0,
validator_info: PCHValidatorInfoEncoding::default(),
errors: Vec::new(),
stats: SerializationStats::default(),
}
}
pub fn with_target(target_triple: &str) -> Self {
let mut writer = Self::new();
writer.validator_info.target_triple = target_triple.to_string();
if target_triple.contains("x86_64") {
writer.validator_info.target_options.is_x86_64 = true;
writer.validator_info.target_options.is_x86 = false;
} else if target_triple.contains("i386") || target_triple.contains("i686") {
writer.validator_info.target_options.is_x86 = true;
writer.validator_info.target_options.is_x86_64 = false;
writer.validator_info.target_options.pointer_width = 32;
writer.validator_info.target_options.size_t_width = 32;
writer.validator_info.target_options.long_width = 32;
}
writer
}
pub fn write_pch(
&mut self,
tu: &TranslationUnit,
macros: &HashMap<String, MacroDef>,
) -> Result<Vec<u8>, String> {
self.write_header()?;
self.write_metadata()?;
self.write_source_manager_block()?;
self.write_identifier_table()?;
self.write_type_table(tu)?;
self.write_declaration_table(tu)?;
self.write_statement_table(tu)?;
self.write_macro_table(macros)?;
self.write_trailer()?;
Ok(self.buffer.clone())
}
pub fn write_header(&mut self) -> Result<(), String> {
self.write_u32(CLANG_AST_MAGIC)?;
self.buffer.extend_from_slice(&PCH_SIGNATURE);
self.write_u32(CLANG_AST_VERSION_MAJOR)?;
self.write_u32(CLANG_AST_VERSION_MINOR)?;
self.write_u32(0)?;
self.write_u32(0)?;
self.stats.total_bytes = self.buffer.len() as u64;
Ok(())
}
pub fn write_metadata(&mut self) -> Result<(), String> {
let target_triple = self.validator_info.target_triple.clone();
let lang = self.validator_info.language_options.clone();
let target = self.validator_info.target_options.clone();
let header_paths = self.validator_info.header_search_paths.clone();
let compiler_id = self.validator_info.compiler_id.clone();
self.write_block_header(ASTBlockID::MetadataBlock)?;
self.write_record_u32(ASTRecordType::TargetTriple, 0)?;
self.write_string(&target_triple)?;
self.write_record_header(ASTRecordType::LanguageOptions)?;
self.write_u8(lang.c_standard)?;
self.write_bool(lang.cxx)?;
self.write_bool(lang.cxx20)?;
self.write_bool(lang.cxx23)?;
self.write_bool(lang.gnu_mode)?;
self.write_bool(lang.ms_extensions)?;
self.write_bool(lang.exceptions)?;
self.write_bool(lang.rtti)?;
self.write_bool(lang.openmp)?;
self.write_bool(lang.freestanding)?;
self.write_bool(lang.signed_char)?;
self.write_u8(lang.char_width)?;
self.write_u8(lang.short_width)?;
self.write_u8(lang.int_width)?;
self.write_u8(lang.long_width)?;
self.write_u8(lang.long_long_width)?;
self.write_u8(lang.pointer_width)?;
self.write_u8(lang.size_t_width)?;
self.write_u8(lang.wchar_width)?;
self.end_record()?;
self.write_record_header(ASTRecordType::TargetOptions)?;
self.write_string(&target.triple)?;
self.write_string(&target.cpu)?;
self.write_string(&target.features)?;
self.write_string(&target.abi)?;
self.write_string(&target.data_layout)?;
self.write_bool(target.is_x86_64)?;
self.write_bool(target.sse2)?;
self.write_bool(target.avx)?;
self.write_bool(target.avx2)?;
self.write_bool(target.avx512)?;
self.write_bool(target.pic)?;
self.write_bool(target.soft_float)?;
self.end_record()?;
self.write_record_header(ASTRecordType::HeaderSearchOptions)?;
self.write_u32(header_paths.len() as u32)?;
for path in &header_paths {
self.write_string(path)?;
}
self.end_record()?;
self.write_record_header(ASTRecordType::PCHValidatorInfo)?;
self.write_u32(CLANG_AST_VERSION)?;
self.write_string(&compiler_id)?;
self.end_record()?;
self.write_record_u32(ASTRecordType::VersionControl, CLANG_AST_VERSION)?;
self.end_record()?;
self.write_block_trailer(ASTBlockID::MetadataBlock)?;
Ok(())
}
pub fn write_source_manager_block(&mut self) -> Result<(), String> {
self.write_block_header(ASTBlockID::SourceManagerBlock)?;
let files = self.context.source_manager.filenames.clone();
let dirs = self.context.source_manager.directories.clone();
let main_file = self.context.source_manager.main_file.clone();
self.write_record_header(ASTRecordType::DirectoryTable)?;
self.write_u32(dirs.len() as u32)?;
for dir in &dirs {
self.write_string(&dir.to_string_lossy())?;
}
self.end_record()?;
self.write_record_header(ASTRecordType::FileTable)?;
self.write_u32(files.len() as u32)?;
for file in &files {
self.write_string(file)?;
}
self.end_record()?;
if let Some(ref name) = main_file {
self.write_record_header(ASTRecordType::OriginalFileName)?;
self.write_string(name)?;
self.end_record()?;
}
self.write_block_trailer(ASTBlockID::SourceManagerBlock)?;
Ok(())
}
pub fn write_identifier_table(&mut self) -> Result<(), String> {
self.write_block_header(ASTBlockID::IdentifierBlock)?;
let num_identifiers = self.context.identifiers.len() as u32;
self.write_record_u32(ASTRecordType::IdentifierOffset, num_identifiers)?;
self.write_record_header(ASTRecordType::IdentifierTable)?;
for id in 0..num_identifiers {
if let Some(name) = self.context.get_identifier(id) {
self.identifier_table.insert(id, name.to_string());
}
}
let id_table = self.identifier_table.clone();
self.write_u32(id_table.len() as u32)?;
for (id, name) in &id_table {
self.write_u32(*id)?;
self.write_string(name)?;
}
self.end_record()?;
self.stats.num_identifiers = id_table.len() as u64;
self.write_block_trailer(ASTBlockID::IdentifierBlock)?;
Ok(())
}
pub fn write_type_table(&mut self, tu: &TranslationUnit) -> Result<(), String> {
self.write_block_header(ASTBlockID::TypeBlock)?;
let mut type_set: HashMap<TypeHash, QualType> = HashMap::new();
collect_types_from_tu(tu, &mut type_set);
self.write_record_u32(ASTRecordType::TypeOffset, type_set.len() as u32)?;
for (_, ty) in &type_set {
let type_id = self.context.register_type(ty);
let record = self.serialize_type(ty, type_id)?;
self.type_records.push(record);
}
let records = self.type_records.clone();
for record in &records {
self.write_type_record(record)?;
}
self.stats.num_types = records.len() as u64;
self.write_block_trailer(ASTBlockID::TypeBlock)?;
Ok(())
}
fn serialize_type(
&mut self,
ty: &QualType,
type_id: u32,
) -> Result<SerializedTypeRecord, String> {
let mut data = Vec::new();
data.push(if ty.is_const { 1u8 } else { 0u8 });
data.push(if ty.is_volatile { 1u8 } else { 0u8 });
data.push(if ty.is_restrict { 1u8 } else { 0u8 });
let kind = self.serialize_type_node(&ty.base, &mut data)?;
Ok(SerializedTypeRecord {
kind,
type_id,
data,
})
}
fn serialize_type_node(
&mut self,
node: &TypeNode,
data: &mut Vec<u8>,
) -> Result<ASTRecordType, String> {
match node {
TypeNode::Void => {
data.push(0u8);
Ok(ASTRecordType::TypeVoid)
}
TypeNode::Char => {
data.push(1u8);
Ok(ASTRecordType::TypeChar)
}
TypeNode::SChar => {
data.push(2u8);
Ok(ASTRecordType::TypeSChar)
}
TypeNode::UChar => {
data.push(3u8);
Ok(ASTRecordType::TypeUChar)
}
TypeNode::Short => {
data.push(4u8);
Ok(ASTRecordType::TypeShort)
}
TypeNode::UShort => {
data.push(5u8);
Ok(ASTRecordType::TypeUShort)
}
TypeNode::Int => {
data.push(6u8);
Ok(ASTRecordType::TypeInt)
}
TypeNode::UInt => {
data.push(7u8);
Ok(ASTRecordType::TypeUInt)
}
TypeNode::Long => {
data.push(8u8);
Ok(ASTRecordType::TypeLong)
}
TypeNode::ULong => {
data.push(9u8);
Ok(ASTRecordType::TypeULong)
}
TypeNode::LongLong => {
data.push(10u8);
Ok(ASTRecordType::TypeLongLong)
}
TypeNode::ULongLong => {
data.push(11u8);
Ok(ASTRecordType::TypeULongLong)
}
TypeNode::Float => {
data.push(12u8);
Ok(ASTRecordType::TypeFloat)
}
TypeNode::Double => {
data.push(13u8);
Ok(ASTRecordType::TypeDouble)
}
TypeNode::LongDouble => {
data.push(14u8);
Ok(ASTRecordType::TypeLongDouble)
}
TypeNode::Bool => {
data.push(15u8);
Ok(ASTRecordType::TypeBool)
}
TypeNode::Complex => {
data.push(16u8);
Ok(ASTRecordType::TypeComplex)
}
TypeNode::Auto => {
data.push(17u8);
Ok(ASTRecordType::TypeAuto)
}
TypeNode::Record(name) => {
data.push(18u8);
let name_bytes = name.as_bytes();
Self::write_u32_to_buf(name_bytes.len() as u32, data);
data.extend_from_slice(name_bytes);
Ok(ASTRecordType::TypeRecord)
}
TypeNode::Pointer(inner) => {
data.push(19u8);
let inner_id = self.context.register_type(inner);
Self::write_u32_to_buf(inner_id, data);
Ok(ASTRecordType::TypePointer)
}
TypeNode::Array { elem, size } => {
data.push(20u8);
let elem_id = self.context.register_type(elem);
Self::write_u32_to_buf(elem_id, data);
match size {
Some(s) => {
data.push(1u8); Self::write_u32_to_buf(*s as u32, data);
Ok(ASTRecordType::TypeConstantArray)
}
None => {
data.push(0u8);
Ok(ASTRecordType::TypeIncompleteArray)
}
}
}
TypeNode::Function {
ret,
params,
is_vararg,
} => {
data.push(21u8);
let ret_id = self.context.register_type(ret);
Self::write_u32_to_buf(ret_id, data);
Self::write_u32_to_buf(params.len() as u32, data);
for param in params {
let param_id = self.context.register_type(param);
Self::write_u32_to_buf(param_id, data);
}
data.push(if *is_vararg { 1u8 } else { 0u8 });
Ok(ASTRecordType::TypeFunction)
}
TypeNode::Struct {
name,
fields,
is_union,
} => {
data.push(22u8);
match name {
Some(n) => {
data.push(1u8);
let name_bytes = n.as_bytes();
Self::write_u32_to_buf(name_bytes.len() as u32, data);
data.extend_from_slice(name_bytes);
}
None => {
data.push(0u8);
}
}
data.push(if *is_union { 1u8 } else { 0u8 });
Self::write_u32_to_buf(fields.len() as u32, data);
for field in fields {
let name_bytes = field.name.as_bytes();
Self::write_u32_to_buf(name_bytes.len() as u32, data);
data.extend_from_slice(name_bytes);
let field_ty_id = self.context.register_type(&field.ty);
Self::write_u32_to_buf(field_ty_id, data);
match field.bit_width {
Some(w) => {
data.push(1u8);
Self::write_u32_to_buf(w, data);
}
None => {
data.push(0u8);
}
}
}
Ok(ASTRecordType::TypeStruct)
}
TypeNode::Enum { name, variants } => {
data.push(23u8);
match name {
Some(n) => {
data.push(1u8);
let name_bytes = n.as_bytes();
Self::write_u32_to_buf(name_bytes.len() as u32, data);
data.extend_from_slice(name_bytes);
}
None => {
data.push(0u8);
}
}
Self::write_u32_to_buf(variants.len() as u32, data);
for variant in variants {
let name_bytes = variant.name.as_bytes();
Self::write_u32_to_buf(name_bytes.len() as u32, data);
data.extend_from_slice(name_bytes);
match variant.value {
Some(v) => {
data.push(1u8);
data.extend_from_slice(&v.to_le_bytes());
}
None => {
data.push(0u8);
}
}
}
Ok(ASTRecordType::TypeEnum)
}
TypeNode::Typedef { name, underlying } => {
data.push(24u8);
let name_bytes = name.as_bytes();
Self::write_u32_to_buf(name_bytes.len() as u32, data);
data.extend_from_slice(name_bytes);
let underlying_id = self.context.register_type(underlying);
Self::write_u32_to_buf(underlying_id, data);
Ok(ASTRecordType::TypeTypedef)
}
}
}
fn write_type_record(&mut self, record: &SerializedTypeRecord) -> Result<(), String> {
self.write_record_header(record.kind)?;
self.write_u32(record.type_id)?;
self.buffer.extend_from_slice(&record.data);
self.end_record()?;
Ok(())
}
pub fn write_declaration_table(&mut self, tu: &TranslationUnit) -> Result<(), String> {
self.write_block_header(ASTBlockID::DeclarationBlock)?;
self.write_record_u32(ASTRecordType::DeclarationOffset, tu.decls.len() as u32)?;
for (i, decl) in tu.decls.iter().enumerate() {
let decl_id = i as u32;
let record = self.serialize_decl(decl, decl_id)?;
self.decl_records.push(record);
}
for i in 0..self.decl_records.len() {
let record = self.decl_records[i].clone();
self.write_decl_record(&record)?;
}
self.stats.num_decls = self.decl_records.len() as u64;
self.write_block_trailer(ASTBlockID::DeclarationBlock)?;
Ok(())
}
fn serialize_decl(
&mut self,
decl: &Decl,
decl_id: u32,
) -> Result<SerializedDeclRecord, String> {
let mut data = Vec::new();
let kind = match decl {
Decl::Function(f) => self.serialize_function_decl(f, &mut data)?,
Decl::Variable(v) => self.serialize_var_decl(v, &mut data)?,
Decl::Typedef(t) => self.serialize_typedef_decl(t, &mut data)?,
Decl::Struct(s) => self.serialize_struct_decl(s, &mut data)?,
Decl::Enum(e) => self.serialize_enum_decl(e, &mut data)?,
Decl::EnumVariant(ev) => self.serialize_enum_variant_decl(ev, &mut data)?,
};
let offset = self.current_offset;
Ok(SerializedDeclRecord {
kind,
decl_id,
offset,
data,
})
}
fn serialize_function_decl(
&mut self,
f: &FunctionDecl,
data: &mut Vec<u8>,
) -> Result<ASTRecordType, String> {
let name_bytes = f.name.as_bytes();
Self::write_u32_to_buf(name_bytes.len() as u32, data);
data.extend_from_slice(name_bytes);
let ret_ty_id = self.context.register_type(&f.ret_ty);
Self::write_u32_to_buf(ret_ty_id, data);
Self::write_u32_to_buf(f.params.len() as u32, data);
for param in &f.params {
let param_ty_id = self.context.register_type(¶m.ty);
Self::write_u32_to_buf(param_ty_id, data);
let pname_bytes = param.name.as_bytes();
Self::write_u32_to_buf(pname_bytes.len() as u32, data);
data.extend_from_slice(pname_bytes);
}
data.push(if f.is_vararg { 1u8 } else { 0u8 });
data.push(match f.linkage {
Linkage::External => 0u8,
Linkage::Internal => 1u8,
Linkage::None => 2u8,
});
data.push(if f.is_inline { 1u8 } else { 0u8 });
data.push(if f.is_noreturn { 1u8 } else { 0u8 });
match &f.body {
Some(body) => {
data.push(1u8);
self.serialize_compound_stmt(body, data)?;
}
None => {
data.push(0u8);
}
}
Ok(ASTRecordType::DeclFunction)
}
fn serialize_var_decl(
&mut self,
v: &VarDecl,
data: &mut Vec<u8>,
) -> Result<ASTRecordType, String> {
let name_bytes = v.name.as_bytes();
Self::write_u32_to_buf(name_bytes.len() as u32, data);
data.extend_from_slice(name_bytes);
let ty_id = self.context.register_type(&v.ty);
Self::write_u32_to_buf(ty_id, data);
match &v.init {
Some(init) => {
data.push(1u8);
self.serialize_expr(init, data)?;
}
None => {
data.push(0u8);
}
}
data.push(match v.linkage {
Linkage::External => 0u8,
Linkage::Internal => 1u8,
Linkage::None => 2u8,
});
let mut flags: u8 = 0;
if v.is_global {
flags |= 0x01;
}
if v.is_extern {
flags |= 0x02;
}
if v.is_static {
flags |= 0x04;
}
data.push(flags);
Ok(ASTRecordType::DeclVariable)
}
fn serialize_typedef_decl(
&mut self,
t: &TypedefDecl,
data: &mut Vec<u8>,
) -> Result<ASTRecordType, String> {
let name_bytes = t.name.as_bytes();
Self::write_u32_to_buf(name_bytes.len() as u32, data);
data.extend_from_slice(name_bytes);
let ty_id = self.context.register_type(&t.underlying);
Self::write_u32_to_buf(ty_id, data);
Ok(ASTRecordType::DeclTypedef)
}
fn serialize_struct_decl(
&mut self,
s: &StructDecl,
data: &mut Vec<u8>,
) -> Result<ASTRecordType, String> {
match &s.name {
Some(name) => {
data.push(1u8);
let name_bytes = name.as_bytes();
Self::write_u32_to_buf(name_bytes.len() as u32, data);
data.extend_from_slice(name_bytes);
}
None => {
data.push(0u8);
}
}
data.push(if s.is_union { 1u8 } else { 0u8 });
Self::write_u32_to_buf(s.fields.len() as u32, data);
for field in &s.fields {
let name_bytes = field.name.as_bytes();
Self::write_u32_to_buf(name_bytes.len() as u32, data);
data.extend_from_slice(name_bytes);
let ty_id = self.context.register_type(&field.ty);
Self::write_u32_to_buf(ty_id, data);
match field.bit_width {
Some(w) => {
data.push(1u8);
Self::write_u32_to_buf(w, data);
}
None => {
data.push(0u8);
}
}
}
Ok(ASTRecordType::DeclStruct)
}
fn serialize_enum_decl(
&mut self,
e: &EnumDecl,
data: &mut Vec<u8>,
) -> Result<ASTRecordType, String> {
match &e.name {
Some(name) => {
data.push(1u8);
let name_bytes = name.as_bytes();
Self::write_u32_to_buf(name_bytes.len() as u32, data);
data.extend_from_slice(name_bytes);
}
None => {
data.push(0u8);
}
}
Self::write_u32_to_buf(e.variants.len() as u32, data);
for variant in &e.variants {
let name_bytes = variant.name.as_bytes();
Self::write_u32_to_buf(name_bytes.len() as u32, data);
data.extend_from_slice(name_bytes);
match variant.value {
Some(v) => {
data.push(1u8);
data.extend_from_slice(&v.to_le_bytes());
}
None => {
data.push(0u8);
}
}
}
let underlying_id = self.context.register_type(&e.underlying);
Self::write_u32_to_buf(underlying_id, data);
Ok(ASTRecordType::DeclEnum)
}
fn serialize_enum_variant_decl(
&mut self,
ev: &EnumVariantDecl,
data: &mut Vec<u8>,
) -> Result<ASTRecordType, String> {
let name_bytes = ev.name.as_bytes();
Self::write_u32_to_buf(name_bytes.len() as u32, data);
data.extend_from_slice(name_bytes);
match ev.value {
Some(v) => {
data.push(1u8);
data.extend_from_slice(&v.to_le_bytes());
}
None => {
data.push(0u8);
}
}
match &ev.enum_name {
Some(name) => {
data.push(1u8);
let name_bytes = name.as_bytes();
Self::write_u32_to_buf(name_bytes.len() as u32, data);
data.extend_from_slice(name_bytes);
}
None => {
data.push(0u8);
}
}
Ok(ASTRecordType::DeclEnumVariant)
}
fn write_decl_record(&mut self, record: &SerializedDeclRecord) -> Result<(), String> {
self.write_record_header(record.kind)?;
self.write_u32(record.decl_id)?;
self.write_u64(record.offset)?;
self.buffer.extend_from_slice(&record.data);
self.end_record()?;
Ok(())
}
pub fn write_statement_table(&mut self, tu: &TranslationUnit) -> Result<(), String> {
self.write_block_header(ASTBlockID::StatementBlock)?;
let mut stmt_data: Vec<Vec<u8>> = Vec::new();
let mut stmt_kinds: Vec<ASTRecordType> = Vec::new();
for decl in &tu.decls {
collect_stmts_from_decl(decl, &mut stmt_data, &mut stmt_kinds, self);
}
self.write_record_u32(ASTRecordType::StatementOffset, stmt_data.len() as u32)?;
for (i, (kind, data)) in stmt_kinds.iter().zip(stmt_data.iter()).enumerate() {
let record = SerializedStmtRecord {
kind: *kind,
offset: i as u64,
data: data.clone(),
};
self.stmt_records.push(record);
}
for i in 0..self.stmt_records.len() {
let record = self.stmt_records[i].clone();
self.write_stmt_record(&record)?;
}
self.stats.num_stmts = self.stmt_records.len() as u64;
self.write_block_trailer(ASTBlockID::StatementBlock)?;
Ok(())
}
fn serialize_compound_stmt(
&mut self,
stmt: &CompoundStmt,
data: &mut Vec<u8>,
) -> Result<(), String> {
Self::write_u32_to_buf(stmt.stmts.len() as u32, data);
for s in &stmt.stmts {
self.serialize_stmt(s, data)?;
}
Ok(())
}
fn serialize_stmt(&mut self, stmt: &Stmt, data: &mut Vec<u8>) -> Result<ASTRecordType, String> {
match stmt {
Stmt::Compound(c) => {
data.push(0u8); self.serialize_compound_stmt(c, data)?;
Ok(ASTRecordType::StmtCompound)
}
Stmt::Return(expr) => {
data.push(1u8);
match expr {
Some(e) => {
data.push(1u8);
self.serialize_expr(e, data)?;
}
None => {
data.push(0u8);
}
}
Ok(ASTRecordType::StmtReturn)
}
Stmt::If { cond, then, els } => {
data.push(2u8);
self.serialize_expr(cond, data)?;
self.serialize_stmt(then, data)?;
match els {
Some(e) => {
data.push(1u8);
self.serialize_stmt(e, data)?;
}
None => {
data.push(0u8);
}
}
Ok(ASTRecordType::StmtIf)
}
Stmt::While { cond, body } => {
data.push(3u8);
self.serialize_expr(cond, data)?;
self.serialize_stmt(body, data)?;
Ok(ASTRecordType::StmtWhile)
}
Stmt::DoWhile { body, cond } => {
data.push(4u8);
self.serialize_stmt(body, data)?;
self.serialize_expr(cond, data)?;
Ok(ASTRecordType::StmtDoWhile)
}
Stmt::For {
init,
cond,
incr,
body,
} => {
data.push(5u8);
match init {
Some(s) => {
data.push(1u8);
self.serialize_stmt(s, data)?;
}
None => {
data.push(0u8);
}
}
match cond {
Some(e) => {
data.push(1u8);
self.serialize_expr(e, data)?;
}
None => {
data.push(0u8);
}
}
match incr {
Some(e) => {
data.push(1u8);
self.serialize_expr(e, data)?;
}
None => {
data.push(0u8);
}
}
self.serialize_stmt(body, data)?;
Ok(ASTRecordType::StmtFor)
}
Stmt::Switch { expr, body } => {
data.push(6u8);
self.serialize_expr(expr, data)?;
self.serialize_stmt(body, data)?;
Ok(ASTRecordType::StmtSwitch)
}
Stmt::Case { value, stmt } => {
data.push(7u8);
self.serialize_expr(value, data)?;
self.serialize_stmt(stmt, data)?;
Ok(ASTRecordType::StmtCase)
}
Stmt::Default { stmt } => {
data.push(8u8);
self.serialize_stmt(stmt, data)?;
Ok(ASTRecordType::StmtDefault)
}
Stmt::Break => {
data.push(9u8);
Ok(ASTRecordType::StmtBreak)
}
Stmt::Continue => {
data.push(10u8);
Ok(ASTRecordType::StmtContinue)
}
Stmt::Goto { label } => {
data.push(11u8);
let label_bytes = label.as_bytes();
Self::write_u32_to_buf(label_bytes.len() as u32, data);
data.extend_from_slice(label_bytes);
Ok(ASTRecordType::StmtGoto)
}
Stmt::Label { name, stmt: inner } => {
data.push(12u8);
let name_bytes = name.as_bytes();
Self::write_u32_to_buf(name_bytes.len() as u32, data);
data.extend_from_slice(name_bytes);
self.serialize_stmt(inner, data)?;
Ok(ASTRecordType::StmtLabel)
}
Stmt::Expr(e) => {
data.push(13u8);
self.serialize_expr(e, data)?;
Ok(ASTRecordType::StmtExpr)
}
Stmt::Decl(d) => {
data.push(14u8);
let name_bytes = d.name.as_bytes();
Self::write_u32_to_buf(name_bytes.len() as u32, data);
data.extend_from_slice(name_bytes);
let ty_id = self.context.register_type(&d.ty);
Self::write_u32_to_buf(ty_id, data);
Ok(ASTRecordType::StmtDecl)
}
Stmt::Null => {
data.push(15u8);
Ok(ASTRecordType::StmtNull)
}
}
}
fn write_stmt_record(&mut self, record: &SerializedStmtRecord) -> Result<(), String> {
self.write_record_header(record.kind)?;
self.buffer.extend_from_slice(&record.data);
self.end_record()?;
Ok(())
}
fn serialize_expr(&mut self, expr: &Expr, data: &mut Vec<u8>) -> Result<ASTRecordType, String> {
match expr {
Expr::IntLiteral(v) => {
data.push(0u8);
data.extend_from_slice(&v.to_le_bytes());
Ok(ASTRecordType::ExprIntLiteral)
}
Expr::UIntLiteral(v, _) => {
data.push(1u8);
data.extend_from_slice(&v.to_le_bytes());
Ok(ASTRecordType::ExprUIntLiteral)
}
Expr::FloatLiteral(v) => {
data.push(2u8);
data.extend_from_slice(&v.to_le_bytes());
Ok(ASTRecordType::ExprFloatLiteral)
}
Expr::DoubleLiteral(v) => {
data.push(3u8);
data.extend_from_slice(&v.to_le_bytes());
Ok(ASTRecordType::ExprDoubleLiteral)
}
Expr::CharLiteral(c) => {
data.push(4u8);
data.extend_from_slice(&(*c as u32).to_le_bytes());
Ok(ASTRecordType::ExprCharLiteral)
}
Expr::StringLiteral(s) => {
data.push(5u8);
let s_bytes = s.as_bytes();
Self::write_u32_to_buf(s_bytes.len() as u32, data);
data.extend_from_slice(s_bytes);
Ok(ASTRecordType::ExprStringLiteral)
}
Expr::Ident(name) => {
data.push(6u8);
let name_bytes = name.as_bytes();
Self::write_u32_to_buf(name_bytes.len() as u32, data);
data.extend_from_slice(name_bytes);
Ok(ASTRecordType::ExprIdent)
}
Expr::Unary(op, inner) => {
data.push(7u8);
Self::serialize_unary_op(op, data);
self.serialize_expr(inner, data)?;
Ok(ASTRecordType::ExprUnary)
}
Expr::SizeOf(inner) => {
data.push(8u8);
self.serialize_expr(inner, data)?;
Ok(ASTRecordType::ExprSizeOf)
}
Expr::SizeOfType(ty) => {
data.push(9u8);
let ty_id = self.context.register_type(ty);
Self::write_u32_to_buf(ty_id, data);
Ok(ASTRecordType::ExprSizeOfType)
}
Expr::AlignOf(inner) => {
data.push(10u8);
self.serialize_expr(inner, data)?;
Ok(ASTRecordType::ExprAlignOf)
}
Expr::AlignOfType(ty) => {
data.push(11u8);
let ty_id = self.context.register_type(ty);
Self::write_u32_to_buf(ty_id, data);
Ok(ASTRecordType::ExprAlignOfType)
}
Expr::Cast(ty, inner) => {
data.push(12u8);
let ty_id = self.context.register_type(ty);
Self::write_u32_to_buf(ty_id, data);
self.serialize_expr(inner, data)?;
Ok(ASTRecordType::ExprCast)
}
Expr::Binary(op, lhs, rhs) => {
data.push(13u8);
Self::serialize_binary_op(op, data);
self.serialize_expr(lhs, data)?;
self.serialize_expr(rhs, data)?;
Ok(ASTRecordType::ExprBinary)
}
Expr::Assign(op, lhs, rhs) => {
data.push(14u8);
Self::serialize_binary_op(op, data);
self.serialize_expr(lhs, data)?;
self.serialize_expr(rhs, data)?;
Ok(ASTRecordType::ExprAssign)
}
Expr::Conditional(cond, then, els) => {
data.push(15u8);
self.serialize_expr(cond, data)?;
self.serialize_expr(then, data)?;
self.serialize_expr(els, data)?;
Ok(ASTRecordType::ExprConditional)
}
Expr::Call { callee, args } => {
data.push(16u8);
self.serialize_expr(callee, data)?;
Self::write_u32_to_buf(args.len() as u32, data);
for arg in args {
self.serialize_expr(arg, data)?;
}
Ok(ASTRecordType::ExprCall)
}
Expr::Subscript { base, index } => {
data.push(17u8);
self.serialize_expr(base, data)?;
self.serialize_expr(index, data)?;
Ok(ASTRecordType::ExprSubscript)
}
Expr::Member {
base,
field,
is_arrow,
} => {
data.push(18u8);
self.serialize_expr(base, data)?;
let field_bytes = field.as_bytes();
Self::write_u32_to_buf(field_bytes.len() as u32, data);
data.extend_from_slice(field_bytes);
data.push(if *is_arrow { 1u8 } else { 0u8 });
Ok(ASTRecordType::ExprMember)
}
Expr::PostInc(inner) => {
data.push(19u8);
self.serialize_expr(inner, data)?;
Ok(ASTRecordType::ExprPostInc)
}
Expr::PostDec(inner) => {
data.push(20u8);
self.serialize_expr(inner, data)?;
Ok(ASTRecordType::ExprPostDec)
}
Expr::PreInc(inner) => {
data.push(21u8);
self.serialize_expr(inner, data)?;
Ok(ASTRecordType::ExprPreInc)
}
Expr::PreDec(inner) => {
data.push(22u8);
self.serialize_expr(inner, data)?;
Ok(ASTRecordType::ExprPreDec)
}
Expr::CompoundLiteral(ty, init) => {
data.push(23u8);
let ty_id = self.context.register_type(ty);
Self::write_u32_to_buf(ty_id, data);
self.serialize_expr(init, data)?;
Ok(ASTRecordType::ExprCompoundLiteral)
}
Expr::AggregateLiteral(vals) => {
data.push(24u8);
let len = vals.len() as u32;
data.extend_from_slice(&len.to_le_bytes());
for v in vals {
self.serialize_expr(v, data)?;
}
Ok(ASTRecordType::ExprAggregateLiteral)
}
}
}
fn serialize_unary_op(op: &UnaryOp, data: &mut Vec<u8>) {
let code: u8 = match op {
UnaryOp::Plus => 0,
UnaryOp::Minus => 1,
UnaryOp::Not => 2,
UnaryOp::BitNot => 3,
UnaryOp::AddrOf => 4,
UnaryOp::Deref => 5,
};
data.push(code);
}
fn serialize_binary_op(op: &BinaryOp, data: &mut Vec<u8>) {
let code: u8 = match op {
BinaryOp::Add => 0,
BinaryOp::Sub => 1,
BinaryOp::Mul => 2,
BinaryOp::Div => 3,
BinaryOp::Mod => 4,
BinaryOp::And => 5,
BinaryOp::Or => 6,
BinaryOp::Xor => 7,
BinaryOp::Shl => 8,
BinaryOp::Shr => 9,
BinaryOp::Eq => 10,
BinaryOp::Ne => 11,
BinaryOp::Lt => 12,
BinaryOp::Gt => 13,
BinaryOp::Le => 14,
BinaryOp::Ge => 15,
BinaryOp::LogicAnd => 16,
BinaryOp::LogicOr => 17,
BinaryOp::Comma => 18,
BinaryOp::Assign => 19,
BinaryOp::AddAssign => 20,
BinaryOp::SubAssign => 21,
BinaryOp::MulAssign => 22,
BinaryOp::DivAssign => 23,
BinaryOp::ModAssign => 24,
BinaryOp::AndAssign => 25,
BinaryOp::OrAssign => 26,
BinaryOp::XorAssign => 27,
BinaryOp::ShlAssign => 28,
BinaryOp::ShrAssign => 29,
};
data.push(code);
}
pub fn write_macro_table(&mut self, macros: &HashMap<String, MacroDef>) -> Result<(), String> {
self.write_block_header(ASTBlockID::MacroBlock)?;
self.write_record_u32(ASTRecordType::MacroDefinitionOffset, macros.len() as u32)?;
for (i, (_, macro_def)) in macros.iter().enumerate() {
let record = self.serialize_macro_def(macro_def, i as u32)?;
self.macro_table.push(record);
}
for i in 0..self.macro_table.len() {
let record = self.macro_table[i].clone();
self.write_macro_record(&record)?;
}
self.stats.num_macros = self.macro_table.len() as u64;
self.write_block_trailer(ASTBlockID::MacroBlock)?;
Ok(())
}
fn serialize_macro_def(
&mut self,
m: &MacroDef,
macro_id: u32,
) -> Result<SerializedMacroRecord, String> {
let mut data = Vec::new();
let name_bytes = m.name.as_bytes();
Self::write_u32_to_buf(name_bytes.len() as u32, &mut data);
data.extend_from_slice(name_bytes);
let mut flags: u8 = 0;
if m.is_function_like {
flags |= 0x01;
}
if m.is_variadic {
flags |= 0x02;
}
data.push(flags);
Self::write_u32_to_buf(m.params.len() as u32, &mut data);
for param in &m.params {
let p_bytes = param.as_bytes();
Self::write_u32_to_buf(p_bytes.len() as u32, &mut data);
data.extend_from_slice(p_bytes);
}
let body_str = m
.body
.iter()
.map(|t| t.text.as_str())
.collect::<Vec<_>>()
.join(" ");
let body_bytes = body_str.as_bytes();
Self::write_u32_to_buf(body_bytes.len() as u32, &mut data);
data.extend_from_slice(body_bytes);
data.extend_from_slice(&m.def_location.line.to_le_bytes());
data.extend_from_slice(&m.def_location.column.to_le_bytes());
data.extend_from_slice(&(m.def_location.offset as u32).to_le_bytes());
Ok(SerializedMacroRecord {
name: m.name.clone(),
macro_id,
data,
})
}
fn write_macro_record(&mut self, record: &SerializedMacroRecord) -> Result<(), String> {
self.write_record_header(ASTRecordType::MacroDefinitionOffset)?;
self.write_u32(record.macro_id)?;
self.buffer.extend_from_slice(&record.data);
self.end_record()?;
Ok(())
}
fn write_block_header(&mut self, block_id: ASTBlockID) -> Result<(), String> {
let offset = self.buffer.len() as u64;
self.buffer.push(ASTControlCode::EnterSubBlock as u8);
self.write_vbr_u32(block_id.id())?;
while self.buffer.len() % 4 != 0 {
self.buffer.push(0);
}
self.write_u32(0)?;
self.current_offset = offset;
Ok(())
}
fn write_block_trailer(&mut self, _block_id: ASTBlockID) -> Result<(), String> {
self.buffer.push(ASTControlCode::EndBlock as u8);
while self.buffer.len() % 4 != 0 {
self.buffer.push(0);
}
Ok(())
}
fn write_record_header(&mut self, record_type: ASTRecordType) -> Result<(), String> {
let offset = self.buffer.len() as u64;
self.buffer.push(ASTControlCode::UnabbreviatedRecord as u8);
self.write_vbr_u32(record_type.value())?;
self.current_offset = offset;
Ok(())
}
fn end_record(&mut self) -> Result<(), String> {
while self.buffer.len() % 4 != 0 {
self.buffer.push(0);
}
Ok(())
}
fn write_record_u32(&mut self, record_type: ASTRecordType, value: u32) -> Result<(), String> {
self.write_record_header(record_type)?;
self.write_u32(value)?;
self.end_record()
}
fn write_trailer(&mut self) -> Result<(), String> {
let total_size = self.buffer.len() as u32;
self.buffer.extend_from_slice(&total_size.to_le_bytes());
self.stats.total_bytes = self.buffer.len() as u64;
Ok(())
}
fn write_u8(&mut self, v: u8) -> Result<(), String> {
self.buffer.push(v);
Ok(())
}
fn write_bool(&mut self, v: bool) -> Result<(), String> {
self.buffer.push(if v { 1u8 } else { 0u8 });
Ok(())
}
fn write_u32(&mut self, v: u32) -> Result<(), String> {
self.buffer.extend_from_slice(&v.to_le_bytes());
Ok(())
}
fn write_u64(&mut self, v: u64) -> Result<(), String> {
self.buffer.extend_from_slice(&v.to_le_bytes());
Ok(())
}
fn write_string(&mut self, s: &str) -> Result<(), String> {
let bytes = s.as_bytes();
self.write_u32(bytes.len() as u32)?;
self.buffer.extend_from_slice(bytes);
Ok(())
}
fn write_vbr_u32(&mut self, mut v: u32) -> Result<(), String> {
loop {
let mut byte = (v & 0x7F) as u8;
v >>= 7;
if v != 0 {
byte |= 0x80;
}
self.buffer.push(byte);
if v == 0 {
break;
}
}
Ok(())
}
fn write_u32_to_buf(v: u32, buf: &mut Vec<u8>) {
buf.extend_from_slice(&v.to_le_bytes());
}
pub fn statistics(&self) -> &SerializationStats {
&self.stats
}
pub fn errors(&self) -> &[String] {
&self.errors
}
}
impl Default for X86ASTWriter {
fn default() -> Self {
Self::new()
}
}
fn collect_types_from_tu(tu: &TranslationUnit, types: &mut HashMap<TypeHash, QualType>) {
for decl in &tu.decls {
collect_types_from_decl(decl, types);
}
}
fn collect_types_from_decl(decl: &Decl, types: &mut HashMap<TypeHash, QualType>) {
match decl {
Decl::Function(f) => {
let h = compute_type_hash(&f.ret_ty);
types.entry(h).or_insert_with(|| f.ret_ty.clone());
for param in &f.params {
let h = compute_type_hash(¶m.ty);
types.entry(h).or_insert_with(|| param.ty.clone());
}
if let Some(ref body) = f.body {
collect_types_from_compound_stmt(body, types);
}
}
Decl::Variable(v) => {
let h = compute_type_hash(&v.ty);
types.entry(h).or_insert_with(|| v.ty.clone());
if let Some(ref init) = v.init {
collect_types_from_expr(init, types);
}
}
Decl::Typedef(t) => {
let h = compute_type_hash(&t.underlying);
types.entry(h).or_insert_with(|| t.underlying.clone());
}
Decl::Struct(s) => {
for field in &s.fields {
let h = compute_type_hash(&field.ty);
types.entry(h).or_insert_with(|| field.ty.clone());
}
}
Decl::Enum(e) => {
let h = compute_type_hash(&e.underlying);
types.entry(h).or_insert_with(|| e.underlying.clone());
}
Decl::EnumVariant(_) => {}
}
}
fn collect_stmts_from_decl(
decl: &Decl,
stmt_data: &mut Vec<Vec<u8>>,
stmt_kinds: &mut Vec<ASTRecordType>,
writer: &mut X86ASTWriter,
) {
match decl {
Decl::Function(f) => {
if let Some(ref body) = f.body {
collect_stmts_from_compound(body, stmt_data, stmt_kinds, writer);
}
}
_ => {}
}
}
fn collect_stmts_from_compound(
stmt: &CompoundStmt,
stmt_data: &mut Vec<Vec<u8>>,
stmt_kinds: &mut Vec<ASTRecordType>,
writer: &mut X86ASTWriter,
) {
for s in &stmt.stmts {
let mut data = Vec::new();
if let Ok(kind) = writer.serialize_stmt(s, &mut data) {
stmt_data.push(data);
stmt_kinds.push(kind);
}
match s {
Stmt::Compound(c) => {
collect_stmts_from_compound(c, stmt_data, stmt_kinds, writer);
}
Stmt::If {
then,
els: Some(els),
..
} => {
if let Stmt::Compound(c) = then.as_ref() {
collect_stmts_from_compound(c, stmt_data, stmt_kinds, writer);
}
if let Stmt::Compound(c) = els.as_ref() {
collect_stmts_from_compound(c, stmt_data, stmt_kinds, writer);
}
}
Stmt::While { body, .. } | Stmt::DoWhile { body, .. } => {
if let Stmt::Compound(c) = body.as_ref() {
collect_stmts_from_compound(c, stmt_data, stmt_kinds, writer);
}
}
Stmt::For { body, .. } => {
if let Stmt::Compound(c) = body.as_ref() {
collect_stmts_from_compound(c, stmt_data, stmt_kinds, writer);
}
}
Stmt::Switch { body, .. } => {
if let Stmt::Compound(c) = body.as_ref() {
collect_stmts_from_compound(c, stmt_data, stmt_kinds, writer);
}
}
_ => {}
}
}
}
fn collect_types_from_compound_stmt(stmt: &CompoundStmt, types: &mut HashMap<TypeHash, QualType>) {
for s in &stmt.stmts {
collect_types_from_stmt(s, types);
}
}
fn collect_types_from_stmt(stmt: &Stmt, types: &mut HashMap<TypeHash, QualType>) {
match stmt {
Stmt::Compound(c) => collect_types_from_compound_stmt(c, types),
Stmt::Return(Some(e)) => collect_types_from_expr(e, types),
Stmt::If { cond, then, els } => {
collect_types_from_expr(cond, types);
collect_types_from_stmt(then, types);
if let Some(els_stmt) = els {
collect_types_from_stmt(els_stmt, types);
}
}
Stmt::While { cond, body } | Stmt::DoWhile { body, cond } => {
collect_types_from_expr(cond, types);
collect_types_from_stmt(body, types);
}
Stmt::For {
cond, incr, body, ..
} => {
if let Some(c) = cond {
collect_types_from_expr(c, types);
}
if let Some(i) = incr {
collect_types_from_expr(i, types);
}
collect_types_from_stmt(body, types);
}
Stmt::Switch { expr, body } => {
collect_types_from_expr(expr, types);
collect_types_from_stmt(body, types);
}
Stmt::Case { value, stmt: inner } => {
collect_types_from_expr(value, types);
collect_types_from_stmt(inner, types);
}
Stmt::Default { stmt: inner } => {
collect_types_from_stmt(inner, types);
}
Stmt::Expr(e) => collect_types_from_expr(e, types),
Stmt::Decl(d) => {
let h = compute_type_hash(&d.ty);
types.entry(h).or_insert_with(|| d.ty.clone());
}
_ => {}
}
}
fn collect_types_from_expr(expr: &Expr, types: &mut HashMap<TypeHash, QualType>) {
match expr {
Expr::SizeOfType(ty)
| Expr::AlignOfType(ty)
| Expr::Cast(ty, _)
| Expr::CompoundLiteral(ty, _) => {
let h = compute_type_hash(ty);
types.entry(h).or_insert_with(|| ty.clone());
}
Expr::Unary(_, inner)
| Expr::SizeOf(inner)
| Expr::AlignOf(inner)
| Expr::Cast(_, inner)
| Expr::PostInc(inner)
| Expr::PostDec(inner)
| Expr::PreInc(inner)
| Expr::PreDec(inner) => {
collect_types_from_expr(inner, types);
}
Expr::Binary(_, lhs, rhs) | Expr::Assign(_, lhs, rhs) => {
collect_types_from_expr(lhs, types);
collect_types_from_expr(rhs, types);
}
Expr::Conditional(cond, then, els) => {
collect_types_from_expr(cond, types);
collect_types_from_expr(then, types);
collect_types_from_expr(els, types);
}
Expr::Call { callee, args } => {
collect_types_from_expr(callee, types);
for arg in args {
collect_types_from_expr(arg, types);
}
}
Expr::Subscript { base, index } => {
collect_types_from_expr(base, types);
collect_types_from_expr(index, types);
}
Expr::Member { base, .. } => {
collect_types_from_expr(base, types);
}
_ => {}
}
}
#[derive(Debug)]
pub struct X86ASTReader {
pub buffer: Vec<u8>,
pub position: usize,
pub context: X86ASTContext,
pub declarations: Vec<Decl>,
pub type_table: BTreeMap<u32, QualType>,
pub identifiers: BTreeMap<u32, String>,
pub macros: HashMap<String, MacroDef>,
pub translation_unit: Option<TranslationUnit>,
pub source_manager: X86SourceManagerExtensions,
pub module: X86ModuleFile,
pub validator: X86PCHValidator,
pub chained_readers: Vec<X86ASTReader>,
pub is_initialized: bool,
pub errors: Vec<String>,
pub lazy_loading: bool,
pub stats: DeserializationStats,
}
#[derive(Debug, Clone, Default)]
pub struct DeserializationStats {
pub num_types: u64,
pub num_decls: u64,
pub num_stmts: u64,
pub num_exprs: u64,
pub num_identifiers: u64,
pub num_macros: u64,
pub num_errors: u64,
pub total_bytes_read: u64,
}
impl X86ASTReader {
pub fn from_bytes(data: Vec<u8>, file_path: &Path, is_pch: bool) -> Self {
let module = X86ModuleFile::new(file_path, is_pch);
Self {
buffer: data,
position: 0,
context: X86ASTContext::x86_64(),
declarations: Vec::new(),
type_table: BTreeMap::new(),
identifiers: BTreeMap::new(),
macros: HashMap::new(),
translation_unit: None,
source_manager: X86SourceManagerExtensions::new(),
module,
validator: X86PCHValidator::new(),
chained_readers: Vec::new(),
is_initialized: false,
errors: Vec::new(),
lazy_loading: true,
stats: DeserializationStats::default(),
}
}
pub fn from_file(file_path: &Path, is_pch: bool) -> Result<Self, String> {
let data =
std::fs::read(file_path).map_err(|e| format!("Failed to read AST file: {}", e))?;
Ok(Self::from_bytes(data, file_path, is_pch))
}
pub fn initialize(&mut self) -> Result<(), String> {
if self.is_initialized {
return Ok(());
}
self.read_header()?;
self.parse_blocks()?;
self.is_initialized = true;
self.stats.total_bytes_read = self.position as u64;
Ok(())
}
fn read_header(&mut self) -> Result<(), String> {
let magic = self.read_u32()?;
if magic != CLANG_AST_MAGIC {
return Err(format!(
"Invalid AST file magic: expected 0x{:08X}, got 0x{:08X}",
CLANG_AST_MAGIC, magic
));
}
let mut sig = [0u8; 8];
if self.position + 8 > self.buffer.len() {
return Err("AST file too short: missing signature".to_string());
}
sig.copy_from_slice(&self.buffer[self.position..self.position + 8]);
self.position += 8;
self.module.signature = sig;
let version_major = self.read_u32()?;
let version_minor = self.read_u32()?;
self.module.version_major = version_major;
self.module.version_minor = version_minor;
self.module.version = (version_major << 16) | version_minor;
if version_major > CLANG_AST_VERSION_MAJOR {
self.record_error(format!(
"AST file version {} is newer than reader version {}",
version_major, CLANG_AST_VERSION_MAJOR
));
}
if version_major < CLANG_AST_MIN_READER_VERSION {
return Err(format!(
"AST file version {} is too old (minimum: {})",
version_major, CLANG_AST_MIN_READER_VERSION
));
}
self.position += 8;
Ok(())
}
fn parse_blocks(&mut self) -> Result<(), String> {
while self.position < self.buffer.len() {
let code = self.read_u8().unwrap_or(0);
match code {
0 => {
break;
}
1 => {
let block_id_raw = self.read_vbr_u32()?;
self.parse_block(block_id_raw)?;
}
3 => {
let record_type_raw = self.read_vbr_u32()?;
self.parse_record_top_level(record_type_raw)?;
}
_ => {
self.record_error(format!("Unknown control code at top level: {}", code));
self.position += 1;
}
}
}
Ok(())
}
fn parse_block(&mut self, block_id_raw: u32) -> Result<(), String> {
let block_id = ASTBlockID::from_u32(block_id_raw).unwrap_or(ASTBlockID::BlockInfo);
self.align_to_4()?;
loop {
if self.position >= self.buffer.len() {
break;
}
let code = self.read_u8().unwrap_or(0);
match code {
0 => {
self.align_to_4()?;
break;
}
1 => {
let nested_id = self.read_vbr_u32()?;
self.align_to_4()?;
self.parse_block(nested_id)?;
}
3 => {
let record_type_raw = self.read_vbr_u32()?;
self.parse_record(block_id, record_type_raw)?;
}
_ => {
self.position = self.position.saturating_sub(1);
break;
}
}
}
Ok(())
}
fn parse_record(&mut self, block_id: ASTBlockID, record_type_raw: u32) -> Result<(), String> {
let record_type = match ASTRecordType::from_u32(record_type_raw) {
Some(rt) => rt,
None => {
self.record_error(format!(
"Unknown record type: {} in block {:?}",
record_type_raw, block_id
));
self.align_to_4()?;
return Ok(());
}
};
match block_id {
ASTBlockID::MetadataBlock => self.parse_metadata_record(record_type)?,
ASTBlockID::IdentifierBlock => self.parse_identifier_record(record_type)?,
ASTBlockID::TypeBlock => self.parse_type_record(record_type)?,
ASTBlockID::DeclarationBlock => self.parse_decl_record(record_type)?,
ASTBlockID::StatementBlock => self.parse_stmt_record(record_type)?,
ASTBlockID::MacroBlock => self.parse_macro_record(record_type)?,
ASTBlockID::SourceManagerBlock => self.parse_source_manager_record(record_type)?,
_ => {
self.align_to_4()?;
}
}
Ok(())
}
fn parse_record_top_level(&mut self, record_type_raw: u32) -> Result<(), String> {
let _record_type = ASTRecordType::from_u32(record_type_raw);
self.align_to_4()?;
Ok(())
}
fn parse_metadata_record(&mut self, record_type: ASTRecordType) -> Result<(), String> {
match record_type {
ASTRecordType::TargetTriple => {
let triple = self.read_string()?;
self.module.target_triple = triple;
}
ASTRecordType::LanguageOptions => {
self.module.language_options.c_standard = self.read_u8()?;
self.module.language_options.cxx = self.read_bool()?;
self.module.language_options.cxx20 = self.read_bool()?;
self.module.language_options.cxx23 = self.read_bool()?;
self.module.language_options.gnu_mode = self.read_bool()?;
self.module.language_options.ms_extensions = self.read_bool()?;
self.module.language_options.exceptions = self.read_bool()?;
self.module.language_options.rtti = self.read_bool()?;
self.module.language_options.openmp = self.read_bool()?;
self.module.language_options.freestanding = self.read_bool()?;
self.module.language_options.signed_char = self.read_bool()?;
self.module.language_options.char_width = self.read_u8()?;
self.module.language_options.short_width = self.read_u8()?;
self.module.language_options.int_width = self.read_u8()?;
self.module.language_options.long_width = self.read_u8()?;
self.module.language_options.long_long_width = self.read_u8()?;
self.module.language_options.pointer_width = self.read_u8()?;
self.module.language_options.size_t_width = self.read_u8()?;
self.module.language_options.wchar_width = self.read_u8()?;
self.validator.load_from_module(&self.module.clone());
}
ASTRecordType::TargetOptions => {
self.module.target_options.triple = self.read_string()?;
self.module.target_options.cpu = self.read_string()?;
self.module.target_options.features = self.read_string()?;
self.module.target_options.abi = self.read_string()?;
self.module.target_options.data_layout = self.read_string()?;
self.module.target_options.is_x86_64 = self.read_bool()?;
self.module.target_options.sse2 = self.read_bool()?;
self.module.target_options.avx = self.read_bool()?;
self.module.target_options.avx2 = self.read_bool()?;
self.module.target_options.avx512 = self.read_bool()?;
self.module.target_options.pic = self.read_bool()?;
self.module.target_options.soft_float = self.read_bool()?;
}
ASTRecordType::HeaderSearchOptions => {
let count = self.read_u32()?;
self.module.header_search_paths.clear();
for _ in 0..count {
let path = self.read_string()?;
self.module.header_search_paths.push(path);
}
}
ASTRecordType::PCHValidatorInfo => {
let version = self.read_u32()?;
let compiler_id = self.read_string()?;
self.module.version = version;
}
ASTRecordType::VersionControl => {
let _version = self.read_u32()?;
}
_ => {}
}
self.align_to_4()?;
Ok(())
}
fn parse_identifier_record(&mut self, record_type: ASTRecordType) -> Result<(), String> {
match record_type {
ASTRecordType::IdentifierOffset => {
let _count = self.read_u32()?;
}
ASTRecordType::IdentifierTable => {
let count = self.read_u32()?;
for _ in 0..count {
let id = self.read_u32()?;
let name = self.read_string()?;
self.identifiers.insert(id, name);
}
self.stats.num_identifiers = self.identifiers.len() as u64;
}
_ => {}
}
self.align_to_4()?;
Ok(())
}
fn parse_type_record(&mut self, record_type: ASTRecordType) -> Result<(), String> {
let type_id = self.read_u32()?;
if let Some(ty) = self.deserialize_type(record_type)? {
self.type_table.insert(type_id, ty.clone());
self.context.type_id_map.insert(type_id, ty);
}
self.stats.num_types += 1;
self.align_to_4()?;
Ok(())
}
fn deserialize_type(&mut self, record_type: ASTRecordType) -> Result<Option<QualType>, String> {
let is_const = self.read_u8()? != 0;
let is_volatile = self.read_u8()? != 0;
let is_restrict = self.read_u8()? != 0;
let _marker = self.read_u8()?;
self.position = self.position.saturating_sub(1);
let node = self.deserialize_type_node(record_type)?;
Ok(Some(QualType {
base: Box::new(node),
is_const,
is_volatile,
is_restrict,
}))
}
fn deserialize_type_node(&mut self, record_type: ASTRecordType) -> Result<TypeNode, String> {
match record_type {
ASTRecordType::TypeVoid => {
self.position += 1; Ok(TypeNode::Void)
}
ASTRecordType::TypeChar => {
self.position += 1;
Ok(TypeNode::Char)
}
ASTRecordType::TypeSChar => {
self.position += 1;
Ok(TypeNode::SChar)
}
ASTRecordType::TypeUChar => {
self.position += 1;
Ok(TypeNode::UChar)
}
ASTRecordType::TypeShort => {
self.position += 1;
Ok(TypeNode::Short)
}
ASTRecordType::TypeUShort => {
self.position += 1;
Ok(TypeNode::UShort)
}
ASTRecordType::TypeInt => {
self.position += 1;
Ok(TypeNode::Int)
}
ASTRecordType::TypeUInt => {
self.position += 1;
Ok(TypeNode::UInt)
}
ASTRecordType::TypeLong => {
self.position += 1;
Ok(TypeNode::Long)
}
ASTRecordType::TypeULong => {
self.position += 1;
Ok(TypeNode::ULong)
}
ASTRecordType::TypeLongLong => {
self.position += 1;
Ok(TypeNode::LongLong)
}
ASTRecordType::TypeULongLong => {
self.position += 1;
Ok(TypeNode::ULongLong)
}
ASTRecordType::TypeFloat => {
self.position += 1;
Ok(TypeNode::Float)
}
ASTRecordType::TypeDouble => {
self.position += 1;
Ok(TypeNode::Double)
}
ASTRecordType::TypeLongDouble => {
self.position += 1;
Ok(TypeNode::LongDouble)
}
ASTRecordType::TypeBool => {
self.position += 1;
Ok(TypeNode::Bool)
}
ASTRecordType::TypeComplex => {
self.position += 1;
Ok(TypeNode::Complex)
}
ASTRecordType::TypeAuto => {
self.position += 1;
Ok(TypeNode::Auto)
}
ASTRecordType::TypeRecord => {
self.position += 1;
let len = self.read_u32()? as usize;
let name_bytes = self.read_bytes(len)?;
let name = String::from_utf8(name_bytes)
.map_err(|e| format!("Invalid UTF-8 in record name: {}", e))?;
Ok(TypeNode::Record(name))
}
ASTRecordType::TypePointer => {
self.position += 1;
let inner_id = self.read_u32()?;
let inner_ty = self.resolve_type(inner_id)?;
Ok(TypeNode::Pointer(Box::new(inner_ty)))
}
ASTRecordType::TypeArray
| ASTRecordType::TypeConstantArray
| ASTRecordType::TypeIncompleteArray => {
self.position += 1;
let elem_id = self.read_u32()?;
let elem_ty = self.resolve_type(elem_id)?;
let has_size = self.read_u8()? != 0;
let size = if has_size {
Some(self.read_u32()? as usize)
} else {
None
};
Ok(TypeNode::Array {
elem: Box::new(elem_ty),
size,
})
}
ASTRecordType::TypeFunction => {
self.position += 1;
let ret_id = self.read_u32()?;
let ret_ty = self.resolve_type(ret_id)?;
let param_count = self.read_u32()? as usize;
let mut params = Vec::with_capacity(param_count);
for _ in 0..param_count {
let param_id = self.read_u32()?;
params.push(self.resolve_type(param_id)?);
}
let is_vararg = self.read_u8()? != 0;
Ok(TypeNode::Function {
ret: Box::new(ret_ty),
params,
is_vararg,
})
}
ASTRecordType::TypeStruct => {
self.position += 1;
let has_name = self.read_u8()? != 0;
let name = if has_name {
let len = self.read_u32()? as usize;
let name_bytes = self.read_bytes(len)?;
Some(
String::from_utf8(name_bytes)
.map_err(|e| format!("Invalid UTF-8: {}", e))?,
)
} else {
None
};
let is_union = self.read_u8()? != 0;
let field_count = self.read_u32()? as usize;
let mut fields = Vec::with_capacity(field_count);
for _ in 0..field_count {
let flen = self.read_u32()? as usize;
let fname_bytes = self.read_bytes(flen)?;
let fname = String::from_utf8(fname_bytes)
.map_err(|e| format!("Invalid UTF-8 in field name: {}", e))?;
let fty_id = self.read_u32()?;
let fty = self.resolve_type(fty_id)?;
let has_bitwidth = self.read_u8()? != 0;
let bit_width = if has_bitwidth {
Some(self.read_u32()?)
} else {
None
};
fields.push(FieldDecl {
name: fname,
ty: fty,
bit_width,
});
}
Ok(TypeNode::Struct {
name,
fields,
is_union,
})
}
ASTRecordType::TypeEnum => {
self.position += 1;
let has_name = self.read_u8()? != 0;
let name = if has_name {
let len = self.read_u32()? as usize;
let name_bytes = self.read_bytes(len)?;
Some(
String::from_utf8(name_bytes)
.map_err(|e| format!("Invalid UTF-8: {}", e))?,
)
} else {
None
};
let variant_count = self.read_u32()? as usize;
let mut variants = Vec::with_capacity(variant_count);
for _ in 0..variant_count {
let vlen = self.read_u32()? as usize;
let vname_bytes = self.read_bytes(vlen)?;
let vname = String::from_utf8(vname_bytes)
.map_err(|e| format!("Invalid UTF-8: {}", e))?;
let has_value = self.read_u8()? != 0;
let value = if has_value {
let mut val_bytes = [0u8; 8];
let bytes = self.read_bytes(8)?;
val_bytes.copy_from_slice(&bytes);
Some(i64::from_le_bytes(val_bytes))
} else {
None
};
variants.push(EnumVariant { name: vname, value });
}
Ok(TypeNode::Enum { name, variants })
}
ASTRecordType::TypeTypedef => {
self.position += 1;
let len = self.read_u32()? as usize;
let name_bytes = self.read_bytes(len)?;
let name =
String::from_utf8(name_bytes).map_err(|e| format!("Invalid UTF-8: {}", e))?;
let underlying_id = self.read_u32()?;
let underlying = self.resolve_type(underlying_id)?;
Ok(TypeNode::Typedef {
name,
underlying: Box::new(underlying),
})
}
_ => {
self.record_error(format!(
"Unknown type record for deserialization: {:?}",
record_type
));
self.align_to_4()?;
Ok(TypeNode::Void)
}
}
}
fn resolve_type(&self, type_id: u32) -> Result<QualType, String> {
self.type_table
.get(&type_id)
.cloned()
.or_else(|| self.context.type_id_map.get(&type_id).cloned())
.or_else(|| {
for reader in &self.chained_readers {
if let Some(ty) = reader.type_table.get(&type_id) {
return Some(ty.clone());
}
}
None
})
.ok_or_else(|| format!("Unresolved type ID: {}", type_id))
}
fn parse_decl_record(&mut self, record_type: ASTRecordType) -> Result<(), String> {
let decl_id = self.read_u32()?;
let offset = self.read_u64()?;
if let Some(decl) = self.deserialize_decl(record_type)? {
self.context.register_decl(decl_id, decl.clone());
self.declarations.push(decl);
}
self.stats.num_decls += 1;
self.align_to_4()?;
Ok(())
}
fn deserialize_decl(&mut self, record_type: ASTRecordType) -> Result<Option<Decl>, String> {
match record_type {
ASTRecordType::DeclFunction => {
let f = self.deserialize_function_decl()?;
Ok(Some(Decl::Function(f)))
}
ASTRecordType::DeclVariable => {
let v = self.deserialize_var_decl()?;
Ok(Some(Decl::Variable(v)))
}
ASTRecordType::DeclTypedef => {
let t = self.deserialize_typedef_decl()?;
Ok(Some(Decl::Typedef(t)))
}
ASTRecordType::DeclStruct => {
let s = self.deserialize_struct_decl()?;
Ok(Some(Decl::Struct(s)))
}
ASTRecordType::DeclEnum => {
let e = self.deserialize_enum_decl()?;
Ok(Some(Decl::Enum(e)))
}
ASTRecordType::DeclEnumVariant => {
let ev = self.deserialize_enum_variant_decl()?;
Ok(Some(Decl::EnumVariant(ev)))
}
_ => {
self.record_error(format!(
"Unknown declaration record type: {:?}",
record_type
));
Ok(None)
}
}
}
fn deserialize_function_decl(&mut self) -> Result<FunctionDecl, String> {
let name_len = self.read_u32()? as usize;
let name_bytes = self.read_bytes(name_len)?;
let name = String::from_utf8(name_bytes)
.map_err(|e| format!("Invalid UTF-8 in function name: {}", e))?;
let ret_ty_id = self.read_u32()?;
let ret_ty = self.resolve_type(ret_ty_id)?;
let param_count = self.read_u32()? as usize;
let mut params = Vec::with_capacity(param_count);
for _ in 0..param_count {
let param_ty_id = self.read_u32()?;
let param_ty = self.resolve_type(param_ty_id)?;
let pname_len = self.read_u32()? as usize;
let pname_bytes = self.read_bytes(pname_len)?;
let pname = String::from_utf8(pname_bytes)
.map_err(|e| format!("Invalid UTF-8 in param name: {}", e))?;
params.push(VarDecl {
name: pname,
ty: param_ty,
init: None,
linkage: Linkage::None,
is_global: false,
is_extern: false,
is_static: false,
});
}
let is_vararg = self.read_u8()? != 0;
let linkage = match self.read_u8()? {
0 => Linkage::External,
1 => Linkage::Internal,
_ => Linkage::None,
};
let is_inline = self.read_u8()? != 0;
let is_noreturn = self.read_u8()? != 0;
let has_body = self.read_u8()? != 0;
let body = if has_body {
Some(self.deserialize_compound_stmt()?)
} else {
None
};
Ok(FunctionDecl {
name,
ret_ty,
params,
body,
is_vararg,
linkage,
is_inline,
is_noreturn,
})
}
fn deserialize_var_decl(&mut self) -> Result<VarDecl, String> {
let name_len = self.read_u32()? as usize;
let name_bytes = self.read_bytes(name_len)?;
let name = String::from_utf8(name_bytes)
.map_err(|e| format!("Invalid UTF-8 in var name: {}", e))?;
let ty_id = self.read_u32()?;
let ty = self.resolve_type(ty_id)?;
let has_init = self.read_u8()? != 0;
let init = if has_init {
Some(Box::new(self.deserialize_expr()?))
} else {
None
};
let linkage = match self.read_u8()? {
0 => Linkage::External,
1 => Linkage::Internal,
_ => Linkage::None,
};
let flags = self.read_u8()?;
let is_global = (flags & 0x01) != 0;
let is_extern = (flags & 0x02) != 0;
let is_static = (flags & 0x04) != 0;
Ok(VarDecl {
name,
ty,
init,
linkage,
is_global,
is_extern,
is_static,
})
}
fn deserialize_typedef_decl(&mut self) -> Result<TypedefDecl, String> {
let name_len = self.read_u32()? as usize;
let name_bytes = self.read_bytes(name_len)?;
let name = String::from_utf8(name_bytes)
.map_err(|e| format!("Invalid UTF-8 in typedef name: {}", e))?;
let ty_id = self.read_u32()?;
let underlying = self.resolve_type(ty_id)?;
Ok(TypedefDecl { name, underlying })
}
fn deserialize_struct_decl(&mut self) -> Result<StructDecl, String> {
let has_name = self.read_u8()? != 0;
let name = if has_name {
let len = self.read_u32()? as usize;
let name_bytes = self.read_bytes(len)?;
Some(String::from_utf8(name_bytes).map_err(|e| format!("Invalid UTF-8: {}", e))?)
} else {
None
};
let is_union = self.read_u8()? != 0;
let field_count = self.read_u32()? as usize;
let mut fields = Vec::with_capacity(field_count);
for _ in 0..field_count {
let flen = self.read_u32()? as usize;
let fname_bytes = self.read_bytes(flen)?;
let fname =
String::from_utf8(fname_bytes).map_err(|e| format!("Invalid UTF-8: {}", e))?;
let fty_id = self.read_u32()?;
let fty = self.resolve_type(fty_id)?;
let has_bitwidth = self.read_u8()? != 0;
let bit_width = if has_bitwidth {
Some(self.read_u32()?)
} else {
None
};
fields.push(FieldDecl {
name: fname,
ty: fty,
bit_width,
});
}
Ok(StructDecl {
name,
fields,
is_union,
})
}
fn deserialize_enum_decl(&mut self) -> Result<EnumDecl, String> {
let has_name = self.read_u8()? != 0;
let name = if has_name {
let len = self.read_u32()? as usize;
let name_bytes = self.read_bytes(len)?;
Some(String::from_utf8(name_bytes).map_err(|e| format!("Invalid UTF-8: {}", e))?)
} else {
None
};
let variant_count = self.read_u32()? as usize;
let mut variants = Vec::with_capacity(variant_count);
for _ in 0..variant_count {
let vlen = self.read_u32()? as usize;
let vname_bytes = self.read_bytes(vlen)?;
let vname =
String::from_utf8(vname_bytes).map_err(|e| format!("Invalid UTF-8: {}", e))?;
let has_value = self.read_u8()? != 0;
let value = if has_value {
let mut val_bytes = [0u8; 8];
let bytes = self.read_bytes(8)?;
val_bytes.copy_from_slice(&bytes);
Some(i64::from_le_bytes(val_bytes))
} else {
None
};
variants.push(EnumVariant { name: vname, value });
}
let underlying_id = self.read_u32()?;
let underlying = self.resolve_type(underlying_id)?;
Ok(EnumDecl {
name,
variants,
underlying,
})
}
fn deserialize_enum_variant_decl(&mut self) -> Result<EnumVariantDecl, String> {
let name_len = self.read_u32()? as usize;
let name_bytes = self.read_bytes(name_len)?;
let name = String::from_utf8(name_bytes).map_err(|e| format!("Invalid UTF-8: {}", e))?;
let has_value = self.read_u8()? != 0;
let value = if has_value {
let mut val_bytes = [0u8; 8];
let bytes = self.read_bytes(8)?;
val_bytes.copy_from_slice(&bytes);
Some(i64::from_le_bytes(val_bytes))
} else {
None
};
let has_enum_name = self.read_u8()? != 0;
let enum_name = if has_enum_name {
let len = self.read_u32()? as usize;
let name_bytes = self.read_bytes(len)?;
Some(String::from_utf8(name_bytes).map_err(|e| format!("Invalid UTF-8: {}", e))?)
} else {
None
};
Ok(EnumVariantDecl {
name,
value,
enum_name,
})
}
fn parse_stmt_record(&mut self, record_type: ASTRecordType) -> Result<(), String> {
self.stats.num_stmts += 1;
self.align_to_4()?;
Ok(())
}
fn deserialize_compound_stmt(&mut self) -> Result<CompoundStmt, String> {
let stmt_count = self.read_u32()? as usize;
let mut stmts = Vec::with_capacity(stmt_count);
for _ in 0..stmt_count {
let stmt_marker = self.read_u8()?;
self.position = self.position.saturating_sub(1); let stmt = self.deserialize_stmt()?;
stmts.push(stmt);
}
Ok(CompoundStmt { stmts })
}
fn deserialize_stmt(&mut self) -> Result<Stmt, String> {
let marker = self.read_u8()?;
match marker {
0 => {
let inner = self.deserialize_compound_stmt()?;
Ok(Stmt::Compound(inner))
}
1 => {
let has_expr = self.read_u8()? != 0;
if has_expr {
let expr = self.deserialize_expr()?;
Ok(Stmt::Return(Some(Box::new(expr))))
} else {
Ok(Stmt::Return(None))
}
}
2 => {
let cond = self.deserialize_expr()?;
let then = self.deserialize_stmt()?;
let has_else = self.read_u8()? != 0;
let els = if has_else {
Some(Box::new(self.deserialize_stmt()?))
} else {
None
};
Ok(Stmt::If {
cond: Box::new(cond),
then: Box::new(then),
els,
})
}
3 => {
let cond = self.deserialize_expr()?;
let body = self.deserialize_stmt()?;
Ok(Stmt::While {
cond: Box::new(cond),
body: Box::new(body),
})
}
4 => {
let body = self.deserialize_stmt()?;
let cond = self.deserialize_expr()?;
Ok(Stmt::DoWhile {
body: Box::new(body),
cond: Box::new(cond),
})
}
5 => {
let has_init = self.read_u8()? != 0;
let init = if has_init {
Some(Box::new(self.deserialize_stmt()?))
} else {
None
};
let has_cond = self.read_u8()? != 0;
let cond = if has_cond {
Some(Box::new(self.deserialize_expr()?))
} else {
None
};
let has_incr = self.read_u8()? != 0;
let incr = if has_incr {
Some(Box::new(self.deserialize_expr()?))
} else {
None
};
let body = self.deserialize_stmt()?;
Ok(Stmt::For {
init,
cond,
incr,
body: Box::new(body),
})
}
6 => {
let expr = self.deserialize_expr()?;
let body = self.deserialize_stmt()?;
Ok(Stmt::Switch {
expr: Box::new(expr),
body: Box::new(body),
})
}
7 => {
let value = self.deserialize_expr()?;
let stmt = self.deserialize_stmt()?;
Ok(Stmt::Case {
value: Box::new(value),
stmt: Box::new(stmt),
})
}
8 => {
let stmt = self.deserialize_stmt()?;
Ok(Stmt::Default {
stmt: Box::new(stmt),
})
}
9 => Ok(Stmt::Break),
10 => Ok(Stmt::Continue),
11 => {
let label_len = self.read_u32()? as usize;
let label_bytes = self.read_bytes(label_len)?;
let label = String::from_utf8(label_bytes)
.map_err(|e| format!("Invalid UTF-8 in goto label: {}", e))?;
Ok(Stmt::Goto { label })
}
12 => {
let name_len = self.read_u32()? as usize;
let name_bytes = self.read_bytes(name_len)?;
let name = String::from_utf8(name_bytes)
.map_err(|e| format!("Invalid UTF-8 in label name: {}", e))?;
let inner = self.deserialize_stmt()?;
Ok(Stmt::Label {
name,
stmt: Box::new(inner),
})
}
13 => {
let expr = self.deserialize_expr()?;
Ok(Stmt::Expr(Box::new(expr)))
}
14 => {
let name_len = self.read_u32()? as usize;
let name_bytes = self.read_bytes(name_len)?;
let name = String::from_utf8(name_bytes)
.map_err(|e| format!("Invalid UTF-8 in decl stmt: {}", e))?;
let ty_id = self.read_u32()?;
let ty = self.resolve_type(ty_id)?;
Ok(Stmt::Decl(Box::new(VarDecl {
name,
ty,
init: None,
linkage: Linkage::None,
is_global: false,
is_extern: false,
is_static: false,
})))
}
15 => Ok(Stmt::Null),
_ => {
self.record_error(format!("Unknown statement marker: {}", marker));
Ok(Stmt::Null)
}
}
}
fn deserialize_expr(&mut self) -> Result<Expr, String> {
let marker = self.read_u8()?;
match marker {
0 => {
let mut val_bytes = [0u8; 8];
let bytes = self.read_bytes(8)?;
val_bytes.copy_from_slice(&bytes);
Ok(Expr::IntLiteral(i64::from_le_bytes(val_bytes)))
}
1 => {
let mut val_bytes = [0u8; 8];
let bytes = self.read_bytes(8)?;
val_bytes.copy_from_slice(&bytes);
let is_ll = self.read_u8()? != 0;
Ok(Expr::UIntLiteral(u64::from_le_bytes(val_bytes), is_ll))
}
2 => {
let mut val_bytes = [0u8; 8];
let bytes = self.read_bytes(8)?;
val_bytes.copy_from_slice(&bytes);
Ok(Expr::FloatLiteral(f64::from_le_bytes(val_bytes)))
}
3 => {
let mut val_bytes = [0u8; 8];
let bytes = self.read_bytes(8)?;
val_bytes.copy_from_slice(&bytes);
Ok(Expr::DoubleLiteral(f64::from_le_bytes(val_bytes)))
}
4 => {
let mut val_bytes = [0u8; 4];
let bytes = self.read_bytes(4)?;
val_bytes.copy_from_slice(&bytes);
let codepoint = u32::from_le_bytes(val_bytes);
let c = char::from_u32(codepoint).unwrap_or('\0');
Ok(Expr::CharLiteral(c))
}
5 => {
let len = self.read_u32()? as usize;
let s_bytes = self.read_bytes(len)?;
let s = String::from_utf8(s_bytes)
.map_err(|e| format!("Invalid UTF-8 in string literal: {}", e))?;
Ok(Expr::StringLiteral(s))
}
6 => {
let len = self.read_u32()? as usize;
let name_bytes = self.read_bytes(len)?;
let name = String::from_utf8(name_bytes)
.map_err(|e| format!("Invalid UTF-8 in ident: {}", e))?;
Ok(Expr::Ident(name))
}
7 => {
let op = self.deserialize_unary_op()?;
let inner = self.deserialize_expr()?;
Ok(Expr::Unary(op, Box::new(inner)))
}
8 => {
let inner = self.deserialize_expr()?;
Ok(Expr::SizeOf(Box::new(inner)))
}
9 => {
let ty_id = self.read_u32()?;
let ty = self.resolve_type(ty_id)?;
Ok(Expr::SizeOfType(ty))
}
10 => {
let inner = self.deserialize_expr()?;
Ok(Expr::AlignOf(Box::new(inner)))
}
11 => {
let ty_id = self.read_u32()?;
let ty = self.resolve_type(ty_id)?;
Ok(Expr::AlignOfType(ty))
}
12 => {
let ty_id = self.read_u32()?;
let ty = self.resolve_type(ty_id)?;
let inner = self.deserialize_expr()?;
Ok(Expr::Cast(ty, Box::new(inner)))
}
13 => {
let op = self.deserialize_binary_op()?;
let lhs = self.deserialize_expr()?;
let rhs = self.deserialize_expr()?;
Ok(Expr::Binary(op, Box::new(lhs), Box::new(rhs)))
}
14 => {
let op = self.deserialize_binary_op()?;
let lhs = self.deserialize_expr()?;
let rhs = self.deserialize_expr()?;
Ok(Expr::Assign(op, Box::new(lhs), Box::new(rhs)))
}
15 => {
let cond = self.deserialize_expr()?;
let then = self.deserialize_expr()?;
let els = self.deserialize_expr()?;
Ok(Expr::Conditional(
Box::new(cond),
Box::new(then),
Box::new(els),
))
}
16 => {
let callee = self.deserialize_expr()?;
let arg_count = self.read_u32()? as usize;
let mut args = Vec::with_capacity(arg_count);
for _ in 0..arg_count {
args.push(self.deserialize_expr()?);
}
Ok(Expr::Call {
callee: Box::new(callee),
args,
})
}
17 => {
let base = self.deserialize_expr()?;
let index = self.deserialize_expr()?;
Ok(Expr::Subscript {
base: Box::new(base),
index: Box::new(index),
})
}
18 => {
let base = self.deserialize_expr()?;
let flen = self.read_u32()? as usize;
let fbytes = self.read_bytes(flen)?;
let field = String::from_utf8(fbytes)
.map_err(|e| format!("Invalid UTF-8 in field: {}", e))?;
let is_arrow = self.read_u8()? != 0;
Ok(Expr::Member {
base: Box::new(base),
field,
is_arrow,
})
}
19 => {
let inner = self.deserialize_expr()?;
Ok(Expr::PostInc(Box::new(inner)))
}
20 => {
let inner = self.deserialize_expr()?;
Ok(Expr::PostDec(Box::new(inner)))
}
21 => {
let inner = self.deserialize_expr()?;
Ok(Expr::PreInc(Box::new(inner)))
}
22 => {
let inner = self.deserialize_expr()?;
Ok(Expr::PreDec(Box::new(inner)))
}
23 => {
let ty_id = self.read_u32()?;
let ty = self.resolve_type(ty_id)?;
let init = self.deserialize_expr()?;
Ok(Expr::CompoundLiteral(ty, Box::new(init)))
}
24 => {
let len = self.read_u32()? as usize;
let mut vals = Vec::with_capacity(len);
for _ in 0..len {
vals.push(self.deserialize_expr()?);
}
Ok(Expr::AggregateLiteral(vals))
}
_ => {
self.record_error(format!("Unknown expression marker: {}", marker));
Ok(Expr::IntLiteral(0))
}
}
}
fn deserialize_unary_op(&mut self) -> Result<UnaryOp, String> {
let code = self.read_u8()?;
match code {
0 => Ok(UnaryOp::Plus),
1 => Ok(UnaryOp::Minus),
2 => Ok(UnaryOp::Not),
3 => Ok(UnaryOp::BitNot),
4 => Ok(UnaryOp::AddrOf),
5 => Ok(UnaryOp::Deref),
_ => {
self.record_error(format!("Unknown unary op code: {}", code));
Ok(UnaryOp::Plus)
}
}
}
fn deserialize_binary_op(&mut self) -> Result<BinaryOp, String> {
let code = self.read_u8()?;
match code {
0 => Ok(BinaryOp::Add),
1 => Ok(BinaryOp::Sub),
2 => Ok(BinaryOp::Mul),
3 => Ok(BinaryOp::Div),
4 => Ok(BinaryOp::Mod),
5 => Ok(BinaryOp::And),
6 => Ok(BinaryOp::Or),
7 => Ok(BinaryOp::Xor),
8 => Ok(BinaryOp::Shl),
9 => Ok(BinaryOp::Shr),
10 => Ok(BinaryOp::Eq),
11 => Ok(BinaryOp::Ne),
12 => Ok(BinaryOp::Lt),
13 => Ok(BinaryOp::Gt),
14 => Ok(BinaryOp::Le),
15 => Ok(BinaryOp::Ge),
16 => Ok(BinaryOp::LogicAnd),
17 => Ok(BinaryOp::LogicOr),
18 => Ok(BinaryOp::Comma),
19 => Ok(BinaryOp::Assign),
20 => Ok(BinaryOp::AddAssign),
21 => Ok(BinaryOp::SubAssign),
22 => Ok(BinaryOp::MulAssign),
23 => Ok(BinaryOp::DivAssign),
24 => Ok(BinaryOp::ModAssign),
25 => Ok(BinaryOp::AndAssign),
26 => Ok(BinaryOp::OrAssign),
27 => Ok(BinaryOp::XorAssign),
28 => Ok(BinaryOp::ShlAssign),
29 => Ok(BinaryOp::ShrAssign),
_ => {
self.record_error(format!("Unknown binary op code: {}", code));
Ok(BinaryOp::Add)
}
}
}
fn parse_macro_record(&mut self, record_type: ASTRecordType) -> Result<(), String> {
if record_type == ASTRecordType::MacroDefinitionOffset {
let macro_id = self.read_u32()?;
if let Ok(macro_def) = self.deserialize_macro_def(macro_id) {
self.macros.insert(macro_def.name.clone(), macro_def);
}
}
self.stats.num_macros += 1;
self.align_to_4()?;
Ok(())
}
fn deserialize_macro_def(&mut self, _macro_id: u32) -> Result<MacroDef, String> {
let name_len = self.read_u32()? as usize;
let name_bytes = self.read_bytes(name_len)?;
let name = String::from_utf8(name_bytes)
.map_err(|e| format!("Invalid UTF-8 in macro name: {}", e))?;
let flags = self.read_u8()?;
let is_function_like = (flags & 0x01) != 0;
let is_variadic = (flags & 0x02) != 0;
let param_count = self.read_u32()? as usize;
let mut params = Vec::with_capacity(param_count);
for _ in 0..param_count {
let plen = self.read_u32()? as usize;
let pbytes = self.read_bytes(plen)?;
let pname = String::from_utf8(pbytes)
.map_err(|e| format!("Invalid UTF-8 in macro param: {}", e))?;
params.push(pname);
}
let body_len = self.read_u32()? as usize;
let body_bytes = self.read_bytes(body_len)?;
let body_str = String::from_utf8(body_bytes)
.map_err(|e| format!("Invalid UTF-8 in macro body: {}", e))?;
let body_tokens: Vec<Token> = body_str
.split_whitespace()
.map(|word| {
let kind = match word {
"int" | "char" | "void" | "float" | "double" => TokenKind::KwInt,
"if" => TokenKind::KwIf,
"else" => TokenKind::KwElse,
"return" => TokenKind::KwReturn,
"while" => TokenKind::KwWhile,
"for" => TokenKind::KwFor,
"break" => TokenKind::KwBreak,
"continue" => TokenKind::KwContinue,
_ => TokenKind::Identifier,
};
Token::new(kind, word, SourceLoc::unknown())
})
.collect();
let line = self.read_u32()?;
let column = self.read_u32()?;
let offset = self.read_u32()?;
let def_location = SourceLoc::new(line, column, offset as usize);
Ok(MacroDef {
name,
params,
body: body_tokens,
is_function_like,
is_variadic,
def_location,
})
}
fn parse_source_manager_record(&mut self, record_type: ASTRecordType) -> Result<(), String> {
match record_type {
ASTRecordType::DirectoryTable => {
let count = self.read_u32()? as usize;
self.source_manager.directories.clear();
for _ in 0..count {
let dir = self.read_string()?;
self.source_manager.directories.push(PathBuf::from(dir));
}
}
ASTRecordType::FileTable => {
let count = self.read_u32()? as usize;
self.source_manager.filenames.clear();
for _ in 0..count {
let file = self.read_string()?;
self.source_manager.filenames.push(file);
}
}
ASTRecordType::OriginalFileName => {
let name = self.read_string()?;
self.module.original_file = Some(name);
}
_ => {}
}
self.align_to_4()?;
Ok(())
}
pub fn get_decl_lazy(&mut self, decl_id: u32) -> Option<&Decl> {
if let Some(_decl) = self.context.get_decl(decl_id) {
return self.context.decl_id_map.get(&decl_id);
}
for reader in &self.chained_readers {
if let Some(decl) = reader.context.get_decl(decl_id) {
return Some(decl);
}
}
None
}
pub fn has_decl(&self, decl_id: u32) -> bool {
self.context.decl_id_map.contains_key(&decl_id)
|| self.chained_readers.iter().any(|r| r.has_decl(decl_id))
}
pub fn build_translation_unit(&mut self) -> Result<TranslationUnit, String> {
if let Some(ref tu) = self.translation_unit {
return Ok(tu.clone());
}
let filename = self
.module
.original_file
.clone()
.unwrap_or_else(|| self.module.file_name.clone());
let mut tu = TranslationUnit::new(&filename);
for decl in &self.declarations {
tu.add_decl(decl.clone());
}
self.translation_unit = Some(tu.clone());
Ok(tu)
}
pub fn add_chained_reader(&mut self, reader: X86ASTReader) {
self.chained_readers.push(reader);
}
pub fn num_chained_readers(&self) -> usize {
self.chained_readers.len()
}
fn record_error(&mut self, msg: String) {
self.errors.push(msg);
self.stats.num_errors += 1;
}
fn recover_from_error(&mut self) {
self.align_to_4().ok();
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
pub fn errors(&self) -> &[String] {
&self.errors
}
fn read_u8(&mut self) -> Result<u8, String> {
if self.position >= self.buffer.len() {
return Err("Unexpected end of AST file".to_string());
}
let v = self.buffer[self.position];
self.position += 1;
Ok(v)
}
fn read_bool(&mut self) -> Result<bool, String> {
self.read_u8().map(|v| v != 0)
}
fn read_u32(&mut self) -> Result<u32, String> {
if self.position + 4 > self.buffer.len() {
return Err("Unexpected end of AST file reading u32".to_string());
}
let mut bytes = [0u8; 4];
bytes.copy_from_slice(&self.buffer[self.position..self.position + 4]);
self.position += 4;
Ok(u32::from_le_bytes(bytes))
}
fn read_u64(&mut self) -> Result<u64, String> {
if self.position + 8 > self.buffer.len() {
return Err("Unexpected end of AST file reading u64".to_string());
}
let mut bytes = [0u8; 8];
bytes.copy_from_slice(&self.buffer[self.position..self.position + 8]);
self.position += 8;
Ok(u64::from_le_bytes(bytes))
}
fn read_string(&mut self) -> Result<String, String> {
let len = self.read_u32()? as usize;
if self.position + len > self.buffer.len() {
return Err(format!(
"String length {} exceeds buffer at position {}",
len, self.position
));
}
let bytes = self.buffer[self.position..self.position + len].to_vec();
self.position += len;
String::from_utf8(bytes).map_err(|e| format!("Invalid UTF-8 in AST file: {}", e))
}
fn read_bytes(&mut self, len: usize) -> Result<Vec<u8>, String> {
if self.position + len > self.buffer.len() {
return Err(format!(
"Read {} bytes at position {} exceeds buffer size {}",
len,
self.position,
self.buffer.len()
));
}
let bytes = self.buffer[self.position..self.position + len].to_vec();
self.position += len;
Ok(bytes)
}
fn read_vbr_u32(&mut self) -> Result<u32, String> {
let mut value: u32 = 0;
let mut shift: u32 = 0;
loop {
if self.position >= self.buffer.len() {
return Err("Unexpected end of AST file reading VBR".to_string());
}
let byte = self.buffer[self.position];
self.position += 1;
value |= ((byte & 0x7F) as u32) << shift;
if byte & 0x80 == 0 {
break;
}
shift += 7;
}
Ok(value)
}
fn align_to_4(&mut self) -> Result<(), String> {
while self.position % 4 != 0 {
if self.position >= self.buffer.len() {
return Ok(());
}
self.position += 1;
}
Ok(())
}
pub fn statistics(&self) -> &DeserializationStats {
&self.stats
}
}
pub fn create_pch(
tu: &TranslationUnit,
macros: &HashMap<String, MacroDef>,
output_path: &Path,
options: &ClangOptions,
) -> Result<(), String> {
let mut writer = X86ASTWriter::with_target(&options.target_triple);
writer.validator_info.language_options = language_options_from_clang_options(options);
writer.validator_info.target_options = target_options_from_clang_options(options);
writer.validator_info.header_search_paths = options.includes.clone();
let data = writer.write_pch(tu, macros)?;
std::fs::write(output_path, &data).map_err(|e| format!("Failed to write PCH file: {}", e))?;
Ok(())
}
pub fn read_pch(
pch_path: &Path,
options: &ClangOptions,
) -> Result<(TranslationUnit, HashMap<String, MacroDef>), String> {
let mut reader = X86ASTReader::from_file(pch_path, true)?;
reader.initialize()?;
let current_lang = language_options_from_clang_options(options);
let current_target = target_options_from_clang_options(options);
let current_headers = &options.includes;
let mut validator = X86PCHValidator::new();
validator.load_from_module(&reader.module);
if !validator.validate(¤t_lang, ¤t_target, current_headers) {
let incompat = validator.get_incompatibilities().join("; ");
return Err(format!("PCH validation failed: {}", incompat));
}
let tu = reader.build_translation_unit()?;
let macros = reader.macros.clone();
Ok((tu, macros))
}
pub fn is_pch_file(path: &Path) -> bool {
if let Ok(data) = std::fs::read(path) {
if data.len() >= 8 {
let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
return magic == CLANG_AST_MAGIC;
}
}
false
}
pub fn pch_version_string(path: &Path) -> Option<String> {
if let Ok(data) = std::fs::read(path) {
if data.len() >= 20 {
let major = u32::from_le_bytes([data[12], data[13], data[14], data[15]]);
let minor = u32::from_le_bytes([data[16], data[17], data[18], data[19]]);
return Some(format!("{}.{}", major, minor));
}
}
None
}
fn language_options_from_clang_options(options: &ClangOptions) -> LanguageOptionsEncoding {
let mut lang = LanguageOptionsEncoding::default();
match options.standard {
CLangStandard::C89 => lang.c_standard = 89,
CLangStandard::C99 => lang.c_standard = 99,
CLangStandard::C11 => lang.c_standard = 11,
CLangStandard::C17 => lang.c_standard = 17,
CLangStandard::C23 => lang.c_standard = 23,
CLangStandard::Gnu89 => {
lang.c_standard = 89;
lang.gnu_mode = true;
}
CLangStandard::Gnu99 => {
lang.c_standard = 99;
lang.gnu_mode = true;
}
CLangStandard::Gnu11 => {
lang.c_standard = 11;
lang.gnu_mode = true;
}
CLangStandard::Gnu17 => {
lang.c_standard = 17;
lang.gnu_mode = true;
}
}
lang
}
fn target_options_from_clang_options(options: &ClangOptions) -> TargetOptionsEncoding {
let mut target = TargetOptionsEncoding::default();
target.triple = options.target_triple.clone();
if options.target_triple.contains("x86_64") {
target.is_x86_64 = true;
} else if options.target_triple.contains("i386") || options.target_triple.contains("i686") {
target.is_x86 = true;
target.is_x86_64 = false;
target.pointer_width = 32;
target.size_t_width = 32;
target.long_width = 32;
}
target
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
fn make_int_type() -> QualType {
QualType::int()
}
fn make_void_type() -> QualType {
QualType::void()
}
fn make_char_ptr_type() -> QualType {
QualType::pointer_to(QualType::const_(TypeNode::Char))
}
fn make_simple_tu() -> TranslationUnit {
let mut tu = TranslationUnit::new("test.c");
tu.add_decl(Decl::Variable(VarDecl {
name: "global_x".to_string(),
ty: make_int_type(),
init: Some(Box::new(Expr::IntLiteral(42))),
linkage: Linkage::External,
is_global: true,
is_extern: false,
is_static: false,
}));
let mut body = CompoundStmt::new();
body.push(Stmt::Return(Some(Box::new(Expr::Binary(
BinaryOp::Add,
Box::new(Expr::Ident("a".to_string())),
Box::new(Expr::Ident("b".to_string())),
)))));
let func = FunctionDecl {
name: "add".to_string(),
ret_ty: make_int_type(),
params: vec![
VarDecl {
name: "a".to_string(),
ty: make_int_type(),
init: None,
linkage: Linkage::None,
is_global: false,
is_extern: false,
is_static: false,
},
VarDecl {
name: "b".to_string(),
ty: make_int_type(),
init: None,
linkage: Linkage::None,
is_global: false,
is_extern: false,
is_static: false,
},
],
body: Some(body),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
tu.add_decl(Decl::Function(func));
tu
}
fn make_test_macros() -> HashMap<String, MacroDef> {
let mut macros = HashMap::new();
macros.insert(
"PI".to_string(),
MacroDef::object_like(
"PI",
vec![Token::new(
TokenKind::NumericLiteral,
"3.14159",
SourceLoc::unknown(),
)],
),
);
macros.insert(
"MAX".to_string(),
MacroDef::function_like(
"MAX",
vec!["a".to_string(), "b".to_string()],
vec![
Token::new(TokenKind::Identifier, "a", SourceLoc::unknown()),
Token::new(TokenKind::Question, "?", SourceLoc::unknown()),
Token::new(TokenKind::Identifier, "a", SourceLoc::unknown()),
Token::new(TokenKind::Colon, ":", SourceLoc::unknown()),
Token::new(TokenKind::Identifier, "b", SourceLoc::unknown()),
],
false,
),
);
macros
}
#[test]
fn test_record_type_roundtrip() {
let test_types = vec![
ASTRecordType::TypeVoid,
ASTRecordType::TypeInt,
ASTRecordType::TypePointer,
ASTRecordType::DeclFunction,
ASTRecordType::DeclVariable,
ASTRecordType::StmtCompound,
ASTRecordType::StmtReturn,
ASTRecordType::ExprIntLiteral,
ASTRecordType::ExprBinary,
ASTRecordType::Metadata,
];
for ty in &test_types {
let val = ty.value();
let roundtripped = ASTRecordType::from_u32(val);
assert_eq!(
Some(*ty),
roundtripped,
"Record type {:?} (value {}) did not roundtrip",
ty,
val
);
}
}
#[test]
fn test_record_type_names() {
assert_eq!(ASTRecordType::TypeVoid.name(), "TYPE_VOID");
assert_eq!(ASTRecordType::DeclFunction.name(), "DECL_FUNCTION");
assert_eq!(ASTRecordType::StmtIf.name(), "STMT_IF");
assert_eq!(ASTRecordType::ExprBinary.name(), "EXPR_BINARY");
}
#[test]
fn test_block_id_roundtrip() {
for id_val in [0u32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] {
let block = ASTBlockID::from_u32(id_val);
assert!(block.is_some(), "Block ID {} should be valid", id_val);
assert_eq!(
block.unwrap().id(),
id_val,
"Block ID {} roundtrip failed",
id_val
);
}
}
#[test]
fn test_invalid_block_id() {
assert!(ASTBlockID::from_u32(999).is_none());
}
#[test]
fn test_type_hash_equality() {
let t1 = QualType::int();
let t2 = QualType::int();
assert_eq!(compute_type_hash(&t1), compute_type_hash(&t2));
let t3 = QualType::pointer_to(QualType::int());
let t4 = QualType::pointer_to(QualType::int());
assert_eq!(compute_type_hash(&t3), compute_type_hash(&t4));
}
#[test]
fn test_type_hash_inequality() {
let t1 = QualType::int();
let t2 = QualType::float_();
assert_ne!(compute_type_hash(&t1), compute_type_hash(&t2));
let t3 = QualType::const_(TypeNode::Int);
let t4 = QualType::int();
assert_ne!(compute_type_hash(&t3), compute_type_hash(&t4));
}
#[test]
fn test_source_location_encoding() {
let loc = SourceLoc::new(10, 20, 500);
let mut encoding = SourceLocationEncoding::default();
let encoded = encoding.encode(&loc);
assert_eq!(encoded.offset(), 500);
assert_eq!(encoded.file_id(), 1);
}
#[test]
fn test_encoded_source_loc_decode() {
let encoded = EncodedSourceLoc::new(1, 300);
assert_eq!(encoded.file_id(), 1);
assert_eq!(encoded.offset(), 300);
}
#[test]
fn test_source_manager_add_file() {
let mut sm = X86SourceManagerExtensions::new();
let entry = SourceFileEntry {
filename: "test.c".to_string(),
dir_index: 0,
size: 1024,
mod_time: 12345,
file_id: 0,
kind: SourceFileKind::User,
is_module_header: false,
};
let id = sm.add_file(entry);
assert_eq!(id, 0);
assert_eq!(sm.num_files(), 1);
}
#[test]
fn test_source_manager_add_directory() {
let mut sm = X86SourceManagerExtensions::new();
let idx = sm.add_directory(PathBuf::from("/usr/include"));
assert_eq!(idx, 0);
assert_eq!(sm.directories.len(), 1);
}
#[test]
fn test_source_manager_remap_file_id() {
let mut sm = X86SourceManagerExtensions::new();
sm.file_id_remap.insert(5, 10);
assert_eq!(sm.remap_file_id(5), 10);
assert_eq!(sm.remap_file_id(3), 3);
}
#[test]
fn test_module_file_creation() {
let module = X86ModuleFile::new(Path::new("/tmp/test.pch"), true);
assert!(module.is_pch);
assert!(!module.is_module);
assert_eq!(module.version, CLANG_AST_VERSION);
assert_eq!(module.version_major, CLANG_AST_VERSION_MAJOR);
assert_eq!(module.version_minor, CLANG_AST_VERSION_MINOR);
}
#[test]
fn test_module_file_signature_valid() {
let module = X86ModuleFile::new(Path::new("/tmp/test.pch"), true);
assert!(module.is_signature_valid());
}
#[test]
fn test_module_file_header_bytes() {
let module = X86ModuleFile::new(Path::new("/tmp/test.pch"), true);
let header = module.header_bytes();
assert!(header.len() >= 32);
assert_eq!(&header[0..4], &PCH_SIGNATURE[0..4]);
}
#[test]
fn test_module_file_errors() {
let mut module = X86ModuleFile::new(Path::new("/tmp/test.pch"), true);
assert!(module.is_valid);
module.record_error("Test error".to_string());
assert!(!module.is_valid);
assert_eq!(module.errors().len(), 1);
}
#[test]
fn test_validator_compatible() {
let mut validator = X86PCHValidator::new();
let current = LanguageOptionsEncoding::default();
assert!(validator.validate_language_options(¤t));
assert!(validator.compatible());
}
#[test]
fn test_validator_language_mismatch() {
let mut validator = X86PCHValidator::new();
validator.language_options.c_standard = 11;
let mut current = LanguageOptionsEncoding::default();
current.c_standard = 17;
assert!(!validator.validate_language_options(¤t));
assert!(!validator.compatible());
assert!(!validator.get_incompatibilities().is_empty());
}
#[test]
fn test_validator_cxx_mismatch() {
let mut validator = X86PCHValidator::new();
validator.language_options.cxx = true;
let current = LanguageOptionsEncoding::default();
assert!(!validator.validate_language_options(¤t));
}
#[test]
fn test_validator_target_triple_mismatch() {
let mut validator = X86PCHValidator::new();
validator.target_options.triple = "i386-unknown-linux-gnu".to_string();
let current = TargetOptionsEncoding::default();
assert!(!validator.validate_target_options(¤t));
}
#[test]
fn test_validator_header_paths() {
let mut validator = X86PCHValidator::new();
validator.header_search_paths = vec!["/usr/include".to_string()];
let current = vec!["/usr/include".to_string(), "/usr/local/include".to_string()];
assert!(validator.validate_header_search_paths(¤t));
let current2 = vec!["/opt/include".to_string()];
assert!(!validator.validate_header_search_paths(¤t2));
}
#[test]
fn test_validator_full_validation() {
let mut validator = X86PCHValidator::new();
validator.language_options = LanguageOptionsEncoding::default();
validator.target_options = TargetOptionsEncoding::default();
validator.header_search_paths = vec!["/usr/include".to_string()];
let lang = LanguageOptionsEncoding::default();
let target = TargetOptionsEncoding::default();
let headers = vec!["/usr/include".to_string()];
assert!(validator.validate(&lang, &target, &headers));
}
#[test]
fn test_ast_context_register_type() {
let mut ctx = X86ASTContext::new();
let ty = QualType::int();
let id = ctx.register_type(&ty);
assert_eq!(id, 0);
assert_eq!(ctx.next_type_id, 1);
let id2 = ctx.register_type(&QualType::int());
assert_eq!(id, id2);
}
#[test]
fn test_ast_context_get_type() {
let mut ctx = X86ASTContext::new();
let ty = QualType::float_();
let id = ctx.register_type(&ty);
let retrieved = ctx.get_type(id);
assert!(retrieved.is_some());
assert_eq!(*retrieved.unwrap(), ty);
}
#[test]
fn test_ast_context_identifier_table() {
let mut ctx = X86ASTContext::new();
let id_main = ctx.get_identifier_id("main");
let id_printf = ctx.get_identifier_id("printf");
let id_main_again = ctx.get_identifier_id("main");
assert_eq!(id_main, 0);
assert_eq!(id_printf, 1);
assert_eq!(id_main_again, 0);
assert_eq!(ctx.get_identifier(0), Some("main"));
assert_eq!(ctx.get_identifier(1), Some("printf"));
assert_eq!(ctx.get_identifier(99), None);
}
#[test]
fn test_ast_context_decl_registration() {
let mut ctx = X86ASTContext::new();
let decl = Decl::Variable(VarDecl::new("x", QualType::int()));
ctx.register_decl(0, decl.clone());
assert!(ctx.get_decl(0).is_some());
assert!(ctx.get_decl(1).is_none());
}
#[test]
fn test_ast_context_external_source() {
let mut ctx = X86ASTContext::new();
let module = X86ModuleFile::new(Path::new("/tmp/test.pch"), true);
ctx.add_external_source(module.clone());
assert_eq!(ctx.external_sources.len(), 1);
assert!(ctx.has_external_decl(0)); }
#[test]
fn test_writer_creation() {
let writer = X86ASTWriter::new();
assert!(writer.buffer.is_empty());
assert_eq!(writer.type_records.len(), 0);
}
#[test]
fn test_writer_with_target() {
let writer = X86ASTWriter::with_target("i686-unknown-linux-gnu");
assert!(writer.validator_info.target_options.triple.contains("i686"));
assert!(writer.validator_info.target_options.is_x86);
assert!(!writer.validator_info.target_options.is_x86_64);
}
#[test]
fn test_write_pch_header() {
let mut writer = X86ASTWriter::new();
writer.write_header().unwrap();
assert!(!writer.buffer.is_empty());
let magic = u32::from_le_bytes([
writer.buffer[0],
writer.buffer[1],
writer.buffer[2],
writer.buffer[3],
]);
assert_eq!(magic, CLANG_AST_MAGIC);
}
#[test]
fn test_write_metadata() {
let mut writer = X86ASTWriter::new();
writer.write_header().unwrap();
writer.write_metadata().unwrap();
assert!(writer.buffer.len() > 20);
}
#[test]
fn test_serialize_builtin_types() {
let mut writer = X86ASTWriter::new();
let types = vec![
TypeNode::Void,
TypeNode::Int,
TypeNode::Float,
TypeNode::Double,
TypeNode::Char,
TypeNode::Bool,
];
for ty in &types {
let mut data = Vec::new();
let kind = writer.serialize_type_node(ty, &mut data).unwrap();
assert!(!data.is_empty());
assert!(kind.name().starts_with("TYPE_"));
}
}
#[test]
fn test_serialize_type_deduplication() {
let mut writer = X86ASTWriter::new();
let ty = QualType::int();
let id1 = writer.context.register_type(&ty);
let id2 = writer.context.register_type(&QualType::int());
assert_eq!(id1, id2);
assert_eq!(writer.context.next_type_id, 1); }
#[test]
fn test_write_full_pch() {
let tu = make_simple_tu();
let macros = make_test_macros();
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, ¯os).unwrap();
assert!(!data.is_empty());
assert!(data.len() > 100);
assert!(writer.statistics().num_types > 0);
assert!(writer.statistics().num_decls > 0);
assert!(writer.statistics().num_macros > 0);
}
#[test]
fn test_write_pch_roundtrip() {
let tu = make_simple_tu();
let macros = make_test_macros();
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, ¯os).unwrap();
let temp_path = std::env::temp_dir().join("test_roundtrip.pch");
std::fs::write(&temp_path, &data).unwrap();
let reader = X86ASTReader::from_file(&temp_path, true);
assert!(reader.is_ok());
let _ = std::fs::remove_file(&temp_path);
}
#[test]
fn test_reader_creation() {
let reader = X86ASTReader::from_bytes(vec![0u8; 64], Path::new("/tmp/test.pch"), true);
assert!(!reader.is_initialized);
assert_eq!(reader.declarations.len(), 0);
}
#[test]
fn test_reader_header_validation() {
let mut writer = X86ASTWriter::new();
writer.write_header().unwrap();
let data = writer.buffer.clone();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/test.pch"), true);
reader.read_header().unwrap();
assert_eq!(reader.module.version_major, CLANG_AST_VERSION_MAJOR);
}
#[test]
fn test_reader_invalid_magic() {
let data = vec![0u8; 32]; let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/test.pch"), true);
let result = reader.read_header();
assert!(result.is_err());
}
#[test]
fn test_reader_type_deserialization() {
let mut writer = X86ASTWriter::new();
writer.write_header().unwrap();
let ty_int = QualType::int();
let ty_void = QualType::void();
let int_id = writer.context.register_type(&ty_int);
let void_id = writer.context.register_type(&ty_void);
writer.write_block_header(ASTBlockID::TypeBlock).unwrap();
writer
.write_record_u32(ASTRecordType::TypeOffset, 2)
.unwrap();
let mut data = Vec::new();
data.push(0u8); data.push(0u8); data.push(0u8); data.push(6u8); let int_record = SerializedTypeRecord {
kind: ASTRecordType::TypeInt,
type_id: int_id,
data,
};
writer.write_type_record(&int_record).unwrap();
let mut data2 = Vec::new();
data2.push(0u8);
data2.push(0u8);
data2.push(0u8);
data2.push(0u8); let void_record = SerializedTypeRecord {
kind: ASTRecordType::TypeVoid,
type_id: void_id,
data: data2,
};
writer.write_type_record(&void_record).unwrap();
writer.write_block_trailer(ASTBlockID::TypeBlock).unwrap();
let data = writer.buffer.clone();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/test.pch"), true);
reader.initialize().unwrap();
assert!(reader.type_table.contains_key(&int_id));
assert!(reader.type_table.contains_key(&void_id));
}
#[test]
fn test_reader_decl_deserialization() {
let tu = make_simple_tu();
let macros = make_test_macros();
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, ¯os).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/test.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
assert!(!reconstructed.is_empty());
assert_eq!(reconstructed.len(), 2); }
#[test]
fn test_reader_macro_deserialization() {
let tu = make_simple_tu();
let macros = make_test_macros();
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, ¯os).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/test.pch"), true);
reader.initialize().unwrap();
assert!(reader.macros.contains_key("PI"));
let pi_macro = &reader.macros["PI"];
assert!(!pi_macro.is_function_like);
assert_eq!(pi_macro.name, "PI");
}
#[test]
fn test_reader_roundtrip_complete() {
let tu = make_simple_tu();
let macros = make_test_macros();
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, ¯os).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/test.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
let globals: Vec<&VarDecl> = reconstructed
.decls
.iter()
.filter_map(|d| match d {
Decl::Variable(v) if v.is_global => Some(v),
_ => None,
})
.collect();
assert_eq!(globals.len(), 1);
assert_eq!(globals[0].name, "global_x");
assert!(globals[0].init.is_some());
let functions: Vec<&FunctionDecl> = reconstructed
.decls
.iter()
.filter_map(|d| match d {
Decl::Function(f) => Some(f),
_ => None,
})
.collect();
assert_eq!(functions.len(), 1);
assert_eq!(functions[0].name, "add");
assert_eq!(functions[0].params.len(), 2);
assert!(functions[0].body.is_some());
}
#[test]
fn test_reader_corrupted_file() {
let mut data = Vec::new();
data.extend_from_slice(&CLANG_AST_MAGIC.to_le_bytes());
data.extend_from_slice(&PCH_SIGNATURE);
data.extend_from_slice(&CLANG_AST_VERSION_MAJOR.to_le_bytes());
data.extend_from_slice(&CLANG_AST_VERSION_MINOR.to_le_bytes());
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/corrupt.pch"), true);
let result = reader.initialize();
if result.is_err() {
assert!(reader.errors().len() > 0 || true); }
}
#[test]
fn test_reader_error_recovery() {
let mut reader = X86ASTReader::from_bytes(vec![0u8; 128], Path::new("/tmp/test.pch"), true);
reader.record_error("Test error 1".to_string());
reader.record_error("Test error 2".to_string());
assert!(reader.has_errors());
assert_eq!(reader.errors().len(), 2);
assert_eq!(reader.statistics().num_errors, 2);
}
#[test]
fn test_is_pch_file() {
let mut writer = X86ASTWriter::new();
writer.write_header().unwrap();
let data = writer.buffer;
let temp_path = std::env::temp_dir().join("test_is_pch.pch");
std::fs::write(&temp_path, &data).unwrap();
assert!(is_pch_file(&temp_path));
let _ = std::fs::remove_file(&temp_path);
}
#[test]
fn test_is_not_pch_file() {
let temp_path = std::env::temp_dir().join("test_not_pch.txt");
std::fs::write(&temp_path, b"not a pch file").unwrap();
assert!(!is_pch_file(&temp_path));
let _ = std::fs::remove_file(&temp_path);
}
#[test]
fn test_pch_version_string() {
let mut writer = X86ASTWriter::new();
writer.write_header().unwrap();
let data = writer.buffer;
let temp_path = std::env::temp_dir().join("test_version.pch");
std::fs::write(&temp_path, &data).unwrap();
let version = pch_version_string(&temp_path);
assert!(version.is_some());
assert_eq!(
version.unwrap(),
format!("{}.{}", CLANG_AST_VERSION_MAJOR, CLANG_AST_VERSION_MINOR)
);
let _ = std::fs::remove_file(&temp_path);
}
#[test]
fn test_chained_pch_readers() {
let mut reader =
X86ASTReader::from_bytes(vec![0u8; 64], Path::new("/tmp/primary.pch"), true);
assert_eq!(reader.num_chained_readers(), 0);
let chained = X86ASTReader::from_bytes(vec![0u8; 64], Path::new("/tmp/chained.pch"), true);
reader.add_chained_reader(chained);
assert_eq!(reader.num_chained_readers(), 1);
}
#[test]
fn test_lazy_decl_registration() {
let mut ctx = X86ASTContext::new();
let lazy = LazyDeclData::new(0, ASTRecordType::DeclFunction, vec![1, 2, 3]);
ctx.register_lazy_decl(42, lazy);
assert!(ctx.has_external_decl(42));
assert!(!ctx.has_external_decl(99));
}
#[test]
fn test_serialization_stats() {
let tu = make_simple_tu();
let macros = make_test_macros();
let mut writer = X86ASTWriter::new();
writer.write_pch(&tu, ¯os).unwrap();
let stats = writer.statistics();
assert!(stats.num_types > 0);
assert!(stats.num_decls > 0);
assert!(stats.total_bytes > 0);
}
#[test]
fn test_vbr_encoding_roundtrip() {
let test_values = vec![0u32, 1, 127, 128, 255, 16383, 16384, 0xFFFFFFFF];
for val in test_values {
let mut writer = X86ASTWriter::new();
writer.write_vbr_u32(val).unwrap();
let mut reader =
X86ASTReader::from_bytes(writer.buffer.clone(), Path::new("/tmp/test.pch"), true);
let decoded = reader.read_vbr_u32().unwrap();
assert_eq!(val, decoded, "VBR roundtrip failed for {}", val);
}
}
#[test]
fn test_write_read_utf8_string() {
let mut writer = X86ASTWriter::new();
writer.write_string("héllo wörld").unwrap();
let mut reader =
X86ASTReader::from_bytes(writer.buffer.clone(), Path::new("/tmp/test.pch"), true);
let s = reader.read_string().unwrap();
assert_eq!(s, "héllo wörld");
}
#[test]
fn test_large_translation_unit() {
let mut tu = TranslationUnit::new("large.c");
for i in 0..100 {
tu.add_decl(Decl::Variable(VarDecl {
name: format!("var_{}", i),
ty: QualType::int(),
init: Some(Box::new(Expr::IntLiteral(i as i64))),
linkage: Linkage::External,
is_global: true,
is_extern: false,
is_static: false,
}));
}
let macros = HashMap::new();
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, ¯os).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/large.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
assert_eq!(reconstructed.len(), 100);
}
#[test]
fn test_struct_decl_roundtrip() {
let mut tu = TranslationUnit::new("structs.c");
let sd = StructDecl {
name: Some("Point".to_string()),
fields: vec![
FieldDecl::new("x", QualType::int()),
FieldDecl::new("y", QualType::int()),
],
is_union: false,
};
tu.add_decl(Decl::Struct(sd));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/structs.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
assert_eq!(reconstructed.len(), 1);
match &reconstructed.decls[0] {
Decl::Struct(s) => {
assert_eq!(s.name.as_deref(), Some("Point"));
assert_eq!(s.fields.len(), 2);
assert!(!s.is_union);
}
_ => panic!("Expected Struct decl"),
}
}
#[test]
fn test_enum_decl_roundtrip() {
let mut tu = TranslationUnit::new("enums.c");
let ed = EnumDecl {
name: Some("Color".to_string()),
variants: vec![
EnumVariant::new("Red", Some(0)),
EnumVariant::new("Green", Some(1)),
EnumVariant::new("Blue", Some(2)),
],
underlying: QualType::int(),
};
tu.add_decl(Decl::Enum(ed));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/enums.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
match &reconstructed.decls[0] {
Decl::Enum(e) => {
assert_eq!(e.name.as_deref(), Some("Color"));
assert_eq!(e.variants.len(), 3);
assert_eq!(e.variants[0].name, "Red");
}
_ => panic!("Expected Enum decl"),
}
}
#[test]
fn test_typedef_decl_roundtrip() {
let mut tu = TranslationUnit::new("typedefs.c");
tu.add_decl(Decl::Typedef(TypedefDecl::new("size_t", QualType::ulong())));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/typedefs.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
match &reconstructed.decls[0] {
Decl::Typedef(t) => {
assert_eq!(t.name, "size_t");
}
_ => panic!("Expected Typedef decl"),
}
}
#[test]
fn test_complex_expression_roundtrip() {
let expr = Expr::Call {
callee: Box::new(Expr::Ident("f".to_string())),
args: vec![
Expr::Binary(
BinaryOp::Add,
Box::new(Expr::Ident("a".to_string())),
Box::new(Expr::Binary(
BinaryOp::Mul,
Box::new(Expr::Ident("b".to_string())),
Box::new(Expr::Ident("c".to_string())),
)),
),
Expr::Conditional(
Box::new(Expr::Ident("d".to_string())),
Box::new(Expr::Ident("e".to_string())),
Box::new(Expr::Ident("f".to_string())),
),
],
};
let mut tu = TranslationUnit::new("complex.c");
let mut body = CompoundStmt::new();
body.push(Stmt::Expr(Box::new(expr)));
let func = FunctionDecl {
name: "test".to_string(),
ret_ty: QualType::void(),
params: vec![],
body: Some(body),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
tu.add_decl(Decl::Function(func));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/complex.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
assert_eq!(reconstructed.len(), 1);
}
#[test]
fn test_nested_control_flow_roundtrip() {
let mut inner_if_body = CompoundStmt::new();
inner_if_body.push(Stmt::Expr(Box::new(Expr::Assign(
BinaryOp::Assign,
Box::new(Expr::Ident("x".to_string())),
Box::new(Expr::Binary(
BinaryOp::Div,
Box::new(Expr::Ident("x".to_string())),
Box::new(Expr::IntLiteral(2)),
)),
))));
let mut inner_else_body = CompoundStmt::new();
inner_else_body.push(Stmt::Expr(Box::new(Expr::Assign(
BinaryOp::Assign,
Box::new(Expr::Ident("x".to_string())),
Box::new(Expr::Binary(
BinaryOp::Sub,
Box::new(Expr::Ident("x".to_string())),
Box::new(Expr::IntLiteral(1)),
)),
))));
let if_stmt = Stmt::If {
cond: Box::new(Expr::Binary(
BinaryOp::Eq,
Box::new(Expr::Binary(
BinaryOp::Mod,
Box::new(Expr::Ident("x".to_string())),
Box::new(Expr::IntLiteral(2)),
)),
Box::new(Expr::IntLiteral(0)),
)),
then: Box::new(Stmt::Compound(inner_if_body)),
els: Some(Box::new(Stmt::Compound(inner_else_body))),
};
let while_stmt = Stmt::While {
cond: Box::new(Expr::Binary(
BinaryOp::Gt,
Box::new(Expr::Ident("x".to_string())),
Box::new(Expr::IntLiteral(0)),
)),
body: Box::new(Stmt::Compound(CompoundStmt {
stmts: vec![if_stmt],
})),
};
let mut body = CompoundStmt::new();
body.push(while_stmt);
let mut tu = TranslationUnit::new("nested.c");
let func = FunctionDecl {
name: "nested_test".to_string(),
ret_ty: QualType::void(),
params: vec![],
body: Some(body),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
tu.add_decl(Decl::Function(func));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/nested.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
assert_eq!(reconstructed.len(), 1);
}
#[test]
fn test_all_statement_kinds_serialization() {
let stmts = vec![
Stmt::Null,
Stmt::Break,
Stmt::Continue,
Stmt::Return(None),
Stmt::Return(Some(Box::new(Expr::IntLiteral(1)))),
Stmt::Expr(Box::new(Expr::Ident("x".to_string()))),
Stmt::Decl(Box::new(VarDecl::new("x", QualType::int()))),
Stmt::Goto {
label: "end".to_string(),
},
Stmt::Label {
name: "loop".to_string(),
stmt: Box::new(Stmt::Null),
},
Stmt::Case {
value: Box::new(Expr::IntLiteral(1)),
stmt: Box::new(Stmt::Break),
},
Stmt::Default {
stmt: Box::new(Stmt::Break),
},
Stmt::DoWhile {
body: Box::new(Stmt::Null),
cond: Box::new(Expr::IntLiteral(0)),
},
Stmt::Switch {
expr: Box::new(Expr::Ident("x".to_string())),
body: Box::new(Stmt::Compound(CompoundStmt::new())),
},
Stmt::For {
init: None,
cond: None,
incr: None,
body: Box::new(Stmt::Null),
},
];
let mut body = CompoundStmt::new();
for stmt in stmts {
body.push(stmt);
}
let mut tu = TranslationUnit::new("all_stmts.c");
let func = FunctionDecl {
name: "all".to_string(),
ret_ty: QualType::void(),
params: vec![],
body: Some(body),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
tu.add_decl(Decl::Function(func));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/all_stmts.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
assert_eq!(reconstructed.len(), 1);
}
#[test]
fn test_all_expression_kinds_serialization() {
let exprs = vec![
Expr::IntLiteral(42),
Expr::UIntLiteral(42, false),
Expr::FloatLiteral(3.14),
Expr::DoubleLiteral(2.71828),
Expr::CharLiteral('A'),
Expr::StringLiteral("hello".to_string()),
Expr::Ident("x".to_string()),
Expr::Unary(UnaryOp::Minus, Box::new(Expr::IntLiteral(5))),
Expr::SizeOf(Box::new(Expr::Ident("x".to_string()))),
Expr::SizeOfType(QualType::int()),
Expr::AlignOf(Box::new(Expr::Ident("x".to_string()))),
Expr::AlignOfType(QualType::double_()),
Expr::Cast(QualType::int(), Box::new(Expr::FloatLiteral(3.14))),
Expr::Binary(
BinaryOp::Add,
Box::new(Expr::IntLiteral(1)),
Box::new(Expr::IntLiteral(2)),
),
Expr::Assign(
BinaryOp::Assign,
Box::new(Expr::Ident("x".to_string())),
Box::new(Expr::IntLiteral(10)),
),
Expr::Conditional(
Box::new(Expr::IntLiteral(1)),
Box::new(Expr::IntLiteral(2)),
Box::new(Expr::IntLiteral(3)),
),
Expr::Call {
callee: Box::new(Expr::Ident("f".to_string())),
args: vec![Expr::IntLiteral(1)],
},
Expr::Subscript {
base: Box::new(Expr::Ident("arr".to_string())),
index: Box::new(Expr::IntLiteral(0)),
},
Expr::Member {
base: Box::new(Expr::Ident("s".to_string())),
field: "x".to_string(),
is_arrow: false,
},
Expr::PostInc(Box::new(Expr::Ident("x".to_string()))),
Expr::PostDec(Box::new(Expr::Ident("x".to_string()))),
Expr::PreInc(Box::new(Expr::Ident("x".to_string()))),
Expr::PreDec(Box::new(Expr::Ident("x".to_string()))),
Expr::CompoundLiteral(QualType::int(), Box::new(CompoundStmt::new())),
];
let mut body = CompoundStmt::new();
for expr in exprs {
body.push(Stmt::Expr(Box::new(expr)));
}
let mut tu = TranslationUnit::new("all_exprs.c");
let func = FunctionDecl {
name: "all_exprs".to_string(),
ret_ty: QualType::void(),
params: vec![],
body: Some(body),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
tu.add_decl(Decl::Function(func));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/all_exprs.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
assert_eq!(reconstructed.len(), 1);
}
#[test]
fn test_type_deduplication_across_boundaries() {
let mut tu = TranslationUnit::new("dedup.c");
let int_ty = QualType::int();
for i in 0..10 {
let func = FunctionDecl {
name: format!("func_{}", i),
ret_ty: int_ty.clone(),
params: vec![VarDecl::new("x", int_ty.clone())],
body: None,
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
tu.add_decl(Decl::Function(func));
}
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let stats = writer.statistics();
assert!(stats.dedup_count > 0 || stats.num_types < 20);
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/dedup.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
assert_eq!(reconstructed.len(), 10);
}
#[test]
fn test_identifier_deduplication() {
let mut ctx = X86ASTContext::new();
for _ in 0..100 {
ctx.get_identifier_id("repeated_name");
}
assert_eq!(ctx.identifiers.len(), 1);
}
#[test]
fn test_empty_translation_unit() {
let tu = TranslationUnit::new("empty.c");
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/empty.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
assert!(reconstructed.is_empty());
}
#[test]
fn test_function_with_many_params() {
let mut tu = TranslationUnit::new("many_params.c");
let params: Vec<VarDecl> = (0..50)
.map(|i| VarDecl::new(&format!("p{}", i), QualType::int()))
.collect();
let func = FunctionDecl {
name: "many".to_string(),
ret_ty: QualType::void(),
params,
body: None,
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
tu.add_decl(Decl::Function(func));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/many_params.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
match &reconstructed.decls[0] {
Decl::Function(f) => assert_eq!(f.params.len(), 50),
_ => panic!("Expected function"),
}
}
#[test]
fn test_deeply_nested_compound_stmts() {
let mut inner = CompoundStmt::new();
inner.push(Stmt::Return(Some(Box::new(Expr::IntLiteral(0)))));
for _ in 0..20 {
let mut outer = CompoundStmt::new();
outer.push(Stmt::Compound(inner));
inner = outer;
}
let mut tu = TranslationUnit::new("deep.c");
let func = FunctionDecl {
name: "deep".to_string(),
ret_ty: QualType::int(),
params: vec![],
body: Some(inner),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
tu.add_decl(Decl::Function(func));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/deep.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
assert_eq!(reconstructed.len(), 1);
}
#[test]
fn test_max_u32_identifiers() {
let mut ctx = X86ASTContext::new();
for i in 0u32..1000 {
let id = ctx.get_identifier_id(&format!("id_{}", i));
assert_eq!(id, i);
}
assert_eq!(ctx.identifiers.len(), 1000);
}
#[test]
fn test_variadic_function_serialization() {
let mut tu = TranslationUnit::new("vararg.c");
let func = FunctionDecl {
name: "printf".to_string(),
ret_ty: QualType::int(),
params: vec![VarDecl::new("fmt", QualType::const_char_ptr())],
body: None,
is_vararg: true,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
tu.add_decl(Decl::Function(func));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/vararg.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
match &reconstructed.decls[0] {
Decl::Function(f) => assert!(f.is_vararg),
_ => panic!("Expected function"),
}
}
#[test]
fn test_static_inline_noreturn_flags() {
let mut tu = TranslationUnit::new("flags.c");
let func = FunctionDecl {
name: "abort".to_string(),
ret_ty: QualType::void(),
params: vec![],
body: None,
is_vararg: false,
linkage: Linkage::External,
is_inline: true,
is_noreturn: true,
};
tu.add_decl(Decl::Function(func));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/flags.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
match &reconstructed.decls[0] {
Decl::Function(f) => {
assert!(f.is_inline);
assert!(f.is_noreturn);
}
_ => panic!("Expected function"),
}
}
#[test]
fn test_write_pch_to_disk_and_read_back() {
let tu = make_simple_tu();
let macros = make_test_macros();
let options = ClangOptions::default();
let temp_path = std::env::temp_dir().join("test_disk_roundtrip.pch");
create_pch(&tu, ¯os, &temp_path, &options).unwrap();
assert!(is_pch_file(&temp_path));
let result = read_pch(&temp_path, &options);
assert!(result.is_ok());
let (reconstructed, reconstructed_macros) = result.unwrap();
assert_eq!(reconstructed.len(), 2);
assert!(reconstructed_macros.contains_key("PI"));
let _ = std::fs::remove_file(&temp_path);
}
#[test]
fn test_pch_validation_rejection() {
let tu = make_simple_tu();
let macros = make_test_macros();
let mut writer = X86ASTWriter::with_target("x86_64-unknown-linux-gnu");
let data = writer.write_pch(&tu, ¯os).unwrap();
let temp_path = std::env::temp_dir().join("test_validate_reject.pch");
std::fs::write(&temp_path, &data).unwrap();
let mut options = ClangOptions::default();
options.target_triple = "i686-unknown-linux-gnu".to_string();
let result = read_pch(&temp_path, &options);
assert!(result.is_err());
let _ = std::fs::remove_file(&temp_path);
}
#[test]
fn test_multi_pch_reading() {
let mut reader =
X86ASTReader::from_bytes(vec![0u8; 128], Path::new("/tmp/primary.pch"), true);
for i in 0..5 {
let chained = X86ASTReader::from_bytes(
vec![0u8; 64],
Path::new(&format!("/tmp/chained_{}.pch", i)),
true,
);
reader.add_chained_reader(chained);
}
assert_eq!(reader.num_chained_readers(), 5);
}
#[test]
fn test_serialized_size_reasonable() {
let tu = make_simple_tu();
let macros = make_test_macros();
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, ¯os).unwrap();
assert!(
data.len() < 10240,
"Serialized size {} exceeds expected max",
data.len()
);
}
#[test]
fn test_serialized_data_is_not_empty_for_nonempty_tu() {
let tu = make_simple_tu();
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
assert!(data.len() > 32); }
#[test]
fn test_writer_x86_32_target() {
let writer = X86ASTWriter::with_target("i386-unknown-linux-gnu");
let target = &writer.validator_info.target_options;
assert!(target.is_x86);
assert!(!target.is_x86_64);
assert_eq!(target.pointer_width, 32);
assert_eq!(target.size_t_width, 32);
assert_eq!(target.long_width, 32);
}
#[test]
fn test_writer_x86_64_target() {
let writer = X86ASTWriter::with_target("x86_64-unknown-linux-gnu");
let target = &writer.validator_info.target_options;
assert!(target.is_x86_64);
assert!(!target.is_x86);
assert_eq!(target.pointer_width, 64);
assert_eq!(target.size_t_width, 64);
assert_eq!(target.long_width, 64);
}
#[test]
fn test_source_location_max_offset() {
let encoded = EncodedSourceLoc::new(255, 0x00FF_FFFF);
assert_eq!(encoded.file_id(), 255);
assert_eq!(encoded.offset(), 0x00FF_FFFF);
}
#[test]
fn test_source_location_zero() {
let encoded = EncodedSourceLoc::new(0, 0);
assert_eq!(encoded.file_id(), 0);
assert_eq!(encoded.offset(), 0);
}
#[test]
fn test_macro_with_many_params() {
let params: Vec<String> = (0..20).map(|i| format!("p{}", i)).collect();
let m = MacroDef::function_like(
"MANY",
params.clone(),
vec![Token::new(TokenKind::Identifier, "x", SourceLoc::unknown())],
false,
);
let mut macros = HashMap::new();
macros.insert("MANY".to_string(), m);
let tu = TranslationUnit::new("empty.c");
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, ¯os).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/many_macro.pch"), true);
reader.initialize().unwrap();
assert!(reader.macros.contains_key("MANY"));
let recovered = &reader.macros["MANY"];
assert_eq!(recovered.params.len(), 20);
}
#[test]
fn test_variadic_macro_serialization() {
let m = MacroDef::function_like(
"DEBUG",
vec!["fmt".to_string()],
vec![Token::new(
TokenKind::Identifier,
"printf",
SourceLoc::unknown(),
)],
true,
);
let mut macros = HashMap::new();
macros.insert("DEBUG".to_string(), m);
let tu = TranslationUnit::new("empty.c");
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, ¯os).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/variadic.pch"), true);
reader.initialize().unwrap();
let recovered = &reader.macros["DEBUG"];
assert!(recovered.is_variadic);
assert!(recovered.is_function_like);
}
#[test]
fn test_reader_partial_buffer() {
let data = vec![0xAAu8; 8];
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/partial.pch"), true);
let result = reader.initialize();
assert!(result.is_err() || reader.has_errors());
}
#[test]
fn test_reader_seek_past_end() {
let data = vec![0u8; 32];
let reader = X86ASTReader::from_bytes(data, Path::new("/tmp/seek.pch"), true);
assert!(!reader.is_initialized);
}
#[test]
fn test_enum_variant_decl_roundtrip() {
let mut tu = TranslationUnit::new("ev.c");
tu.add_decl(Decl::EnumVariant(EnumVariantDecl {
name: "MAX_SIZE".to_string(),
value: Some(1024),
enum_name: Some("Limits".to_string()),
}));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/ev.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
match &reconstructed.decls[0] {
Decl::EnumVariant(ev) => {
assert_eq!(ev.name, "MAX_SIZE");
assert_eq!(ev.value, Some(1024));
assert_eq!(ev.enum_name.as_deref(), Some("Limits"));
}
_ => panic!("Expected EnumVariant decl"),
}
}
#[test]
fn test_const_qualified_type_serialization() {
let ty = QualType::const_(TypeNode::Int);
let mut ctx = X86ASTContext::new();
ctx.register_type(&ty);
let mut writer = X86ASTWriter::new();
writer.context = ctx;
let mut data = Vec::new();
let kind = writer
.serialize_type_node(&TypeNode::Int, &mut data)
.unwrap();
assert_eq!(kind, ASTRecordType::TypeInt);
}
#[test]
fn test_volatile_qualified_type_hash() {
let t1 = QualType::volatile(TypeNode::Int);
let t2 = QualType::int();
assert_ne!(compute_type_hash(&t1), compute_type_hash(&t2));
}
#[test]
fn test_restrict_qualified_type_hash() {
let t1 = QualType {
base: Box::new(TypeNode::Pointer(Box::new(QualType::int()))),
is_const: false,
is_volatile: false,
is_restrict: true,
};
let t2 = QualType::pointer_to(QualType::int());
assert_ne!(compute_type_hash(&t1), compute_type_hash(&t2));
}
#[test]
fn test_source_manager_file_table_population() {
let mut sm = X86SourceManagerExtensions::new();
sm.populate_from_tables(
vec![PathBuf::from("/usr/include"), PathBuf::from("/opt/include")],
vec!["stdio.h".to_string(), "stdlib.h".to_string()],
);
assert_eq!(sm.directories.len(), 2);
assert_eq!(sm.filenames.len(), 2);
assert_eq!(sm.directories[0], PathBuf::from("/usr/include"));
assert_eq!(sm.filenames[1], "stdlib.h");
}
#[test]
fn test_source_manager_main_file() {
let mut sm = X86SourceManagerExtensions::new();
sm.set_main_file("main.c");
assert_eq!(sm.main_file.as_deref(), Some("main.c"));
}
#[test]
fn test_source_manager_file_id_validation() {
let mut sm = X86SourceManagerExtensions::new();
assert!(!sm.is_valid_file_id(0));
sm.add_file(SourceFileEntry {
filename: "test.c".to_string(),
dir_index: 0,
size: 100,
mod_time: 0,
file_id: 0,
kind: SourceFileKind::User,
is_module_header: false,
});
assert!(sm.is_valid_file_id(0));
assert!(!sm.is_valid_file_id(1));
}
#[test]
fn test_abbreviation_creation() {
let abbrev = ASTAbbreviation {
record_type: ASTRecordType::TypeInt,
operands: vec![
ASTAbbrevOperand::Literal(0),
ASTAbbrevOperand::Fixed(32),
ASTAbbrevOperand::VBR(6),
],
abbrev_number: 1,
};
assert_eq!(abbrev.record_type, ASTRecordType::TypeInt);
assert_eq!(abbrev.operands.len(), 3);
assert_eq!(abbrev.abbrev_number, 1);
}
#[test]
fn test_abbreviation_operands() {
let operands = vec![
ASTAbbrevOperand::Literal(42),
ASTAbbrevOperand::Fixed(8),
ASTAbbrevOperand::VBR(12),
ASTAbbrevOperand::Char6,
ASTAbbrevOperand::Blob,
ASTAbbrevOperand::Array,
];
assert_eq!(operands.len(), 6);
}
#[test]
fn test_control_codes() {
assert_eq!(ASTControlCode::EndBlock.id(), 0);
assert_eq!(ASTControlCode::EnterSubBlock.id(), 1);
assert_eq!(ASTControlCode::DefineAbbrev.id(), 2);
assert_eq!(ASTControlCode::UnabbreviatedRecord.id(), 3);
}
#[test]
fn test_language_options_defaults() {
let lang = LanguageOptionsEncoding::default();
assert_eq!(lang.c_standard, 17);
assert!(!lang.cxx);
assert!(lang.exceptions);
assert!(lang.rtti);
assert_eq!(lang.pointer_width, 64);
assert_eq!(lang.size_t_width, 64);
assert_eq!(lang.long_width, 64);
assert_eq!(lang.int_width, 32);
assert_eq!(lang.short_width, 16);
assert_eq!(lang.char_width, 8);
}
#[test]
fn test_language_options_cxx20() {
let mut lang = LanguageOptionsEncoding::default();
lang.cxx = true;
lang.cxx20 = true;
assert!(lang.cxx);
assert!(lang.cxx20);
assert!(!lang.cxx23);
}
#[test]
fn test_target_options_default_x86_64() {
let target = TargetOptionsEncoding::default();
assert!(target.is_x86_64);
assert!(!target.is_x86);
assert!(target.sse2);
assert!(!target.avx);
assert!(!target.avx512);
assert!(target.pic);
assert!(!target.pie);
assert!(!target.soft_float);
}
#[test]
fn test_target_options_i386() {
let mut target = TargetOptionsEncoding::default();
target.triple = "i386-unknown-linux-gnu".to_string();
target.is_x86 = true;
target.is_x86_64 = false;
target.pointer_width = 32;
target.long_width = 32;
assert!(target.is_x86);
assert!(!target.is_x86_64);
assert_eq!(target.pointer_width, 32);
}
#[test]
fn test_validator_feature_compatibility() {
let mut validator = X86PCHValidator::new();
validator.target_options.avx = true;
validator.target_options.avx2 = true;
let mut current = TargetOptionsEncoding::default();
assert!(!validator.validate_target_options(¤t));
}
#[test]
fn test_validator_exception_mismatch() {
let mut validator = X86PCHValidator::new();
validator.language_options.exceptions = true;
let mut current = LanguageOptionsEncoding::default();
current.exceptions = false;
assert!(!validator.validate_language_options(¤t));
}
#[test]
fn test_validator_rtti_mismatch() {
let mut validator = X86PCHValidator::new();
validator.language_options.rtti = false;
let current = LanguageOptionsEncoding::default(); assert!(!validator.validate_language_options(¤t));
}
#[test]
fn test_validator_openmp_mismatch() {
let mut validator = X86PCHValidator::new();
validator.language_options.openmp = true;
let current = LanguageOptionsEncoding::default(); assert!(!validator.validate_language_options(¤t));
}
#[test]
fn test_validator_data_layout_mismatch() {
let mut validator = X86PCHValidator::new();
validator.target_options.data_layout = "e-m:e-i64:64".to_string();
let current = TargetOptionsEncoding::default();
assert!(!validator.validate_target_options(¤t));
}
#[test]
fn test_validator_non_strict_mode() {
let mut validator = X86PCHValidator::new();
validator.strict_mode = false;
validator.language_options.c_standard = 11;
let mut current = LanguageOptionsEncoding::default();
current.c_standard = 17;
assert!(validator.validate_language_options(¤t));
}
#[test]
fn test_validator_disable_extensions_check() {
let mut validator = X86PCHValidator::new();
validator.validate_extensions = false;
validator.language_options.gnu_mode = true;
let current = LanguageOptionsEncoding::default(); assert!(validator.validate_language_options(¤t));
}
#[test]
fn test_validator_disable_features_check() {
let mut validator = X86PCHValidator::new();
validator.validate_features = false;
validator.target_options.avx512 = true;
let current = TargetOptionsEncoding::default();
assert!(validator.validate_target_options(¤t));
}
#[test]
fn test_validator_disable_headers_check() {
let mut validator = X86PCHValidator::new();
validator.validate_headers = false;
validator.header_search_paths = vec!["/missing/path".to_string()];
let current: Vec<String> = vec![];
assert!(validator.validate_header_search_paths(¤t));
}
#[test]
fn test_ast_context_different_types_different_ids() {
let mut ctx = X86ASTContext::new();
let id_int = ctx.register_type(&QualType::int());
let id_float = ctx.register_type(&QualType::float_());
let id_double = ctx.register_type(&QualType::double_());
assert_ne!(id_int, id_float);
assert_ne!(id_float, id_double);
assert_ne!(id_int, id_double);
assert_eq!(ctx.next_type_id, 3);
}
#[test]
fn test_ast_context_pointer_type_dedup() {
let mut ctx = X86ASTContext::new();
let ptr1 = QualType::pointer_to(QualType::int());
let ptr2 = QualType::pointer_to(QualType::int());
let id1 = ctx.register_type(&ptr1);
let id2 = ctx.register_type(&ptr2);
assert_eq!(id1, id2);
}
#[test]
fn test_ast_context_complex_type_dedup() {
let mut ctx = X86ASTContext::new();
let arr1 = QualType::new(TypeNode::Array {
elem: Box::new(QualType::int()),
size: Some(10),
});
let arr2 = QualType::new(TypeNode::Array {
elem: Box::new(QualType::int()),
size: Some(10),
});
let id1 = ctx.register_type(&arr1);
let id2 = ctx.register_type(&arr2);
assert_eq!(id1, id2);
}
#[test]
fn test_ast_context_function_type_dedup() {
let mut ctx = X86ASTContext::new();
let fn1 = QualType::new(TypeNode::Function {
ret: Box::new(QualType::int()),
params: vec![QualType::int(), QualType::float_()],
is_vararg: false,
});
let fn2 = QualType::new(TypeNode::Function {
ret: Box::new(QualType::int()),
params: vec![QualType::int(), QualType::float_()],
is_vararg: false,
});
let id1 = ctx.register_type(&fn1);
let id2 = ctx.register_type(&fn2);
assert_eq!(id1, id2);
}
#[test]
fn test_stats_accumulation() {
let tu = make_simple_tu();
let macros = make_test_macros();
let mut writer = X86ASTWriter::new();
writer.write_pch(&tu, ¯os).unwrap();
let stats = writer.statistics();
let total = stats.num_types + stats.num_decls + stats.num_stmts + stats.num_macros;
assert!(total > 5, "Expected more than 5 total serialized items");
}
#[test]
fn test_deserialization_stats() {
let tu = make_simple_tu();
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/stats.pch"), true);
reader.initialize().unwrap();
reader.build_translation_unit().unwrap();
let stats = reader.statistics();
assert!(stats.total_bytes_read > 0);
}
#[test]
fn test_pch_version_string_nonexistent() {
let version = pch_version_string(Path::new("/nonexistent/file.pch"));
assert!(version.is_none());
}
#[test]
fn test_is_pch_file_nonexistent() {
assert!(!is_pch_file(Path::new("/nonexistent/file.pch")));
}
#[test]
fn test_create_pch_error_on_invalid_path() {
let tu = TranslationUnit::new("test.c");
let result = create_pch(
&tu,
&HashMap::new(),
Path::new("/nonexistent/directory/test.pch"),
&ClangOptions::default(),
);
assert!(result.is_err());
}
#[test]
fn test_type_hash_stable_across_calls() {
let ty = QualType::new(TypeNode::Struct {
name: Some("Point".to_string()),
fields: vec![
FieldDecl::new("x", QualType::int()),
FieldDecl::new("y", QualType::int()),
],
is_union: false,
});
let h1 = compute_type_hash(&ty);
let h2 = compute_type_hash(&ty);
let h3 = compute_type_hash(&ty);
assert_eq!(h1, h2);
assert_eq!(h2, h3);
}
#[test]
fn test_type_hash_different_struct_names() {
let t1 = QualType::new(TypeNode::Struct {
name: Some("A".to_string()),
fields: vec![],
is_union: false,
});
let t2 = QualType::new(TypeNode::Struct {
name: Some("B".to_string()),
fields: vec![],
is_union: false,
});
assert_ne!(compute_type_hash(&t1), compute_type_hash(&t2));
}
#[test]
fn test_type_hash_union_vs_struct() {
let t1 = QualType::new(TypeNode::Struct {
name: Some("X".to_string()),
fields: vec![],
is_union: false,
});
let t2 = QualType::new(TypeNode::Struct {
name: Some("X".to_string()),
fields: vec![],
is_union: true,
});
assert_ne!(compute_type_hash(&t1), compute_type_hash(&t2));
}
#[test]
fn test_block_write_and_parse() {
let mut writer = X86ASTWriter::new();
writer.write_header().unwrap();
writer.write_metadata().unwrap();
writer.write_trailer().unwrap();
let data = writer.buffer.clone();
assert!(data.len() > 32);
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/block.pch"), true);
reader.initialize().unwrap();
assert!(!reader.module.target_triple.is_empty());
}
#[test]
fn test_module_file_creation_for_module() {
let module = X86ModuleFile::new(Path::new("/tmp/test.pcm"), false);
assert!(!module.is_pch);
assert!(module.is_module);
assert_eq!(&module.signature[0..4], &MODULE_SIGNATURE[0..4]);
}
#[test]
fn test_module_file_base_dir() {
let module = X86ModuleFile::new(Path::new("/usr/lib/clang/test.pch"), true);
assert_eq!(module.base_dir, Some(PathBuf::from("/usr/lib/clang")));
}
#[test]
fn test_module_file_header_unit_flag() {
let mut module = X86ModuleFile::new(Path::new("/tmp/test.pch"), true);
assert!(!module.is_header_unit);
module.is_header_unit = true;
let header = module.header_bytes();
let flags = u32::from_le_bytes([header[28], header[29], header[30], header[31]]);
assert_eq!(flags & 0x04, 0x04);
}
#[test]
fn test_source_location_delta_encoding() {
let mut encoding = SourceLocationEncoding::default();
encoding.use_delta = true;
encoding.base_offset = 100;
let loc = SourceLoc::new(1, 1, 150);
let encoded = encoding.encode(&loc);
assert_eq!(encoded.offset(), 150);
}
#[test]
fn test_source_location_encoding_reset() {
let mut encoding = SourceLocationEncoding::default();
encoding.current_file_id = 5;
encoding.current_offset = 1000;
encoding.reset_for_file();
assert_eq!(encoding.current_file_id, 0);
assert_eq!(encoding.current_offset, 0);
}
#[test]
fn test_source_file_kinds() {
let kinds = vec![
SourceFileKind::User,
SourceFileKind::System,
SourceFileKind::ExternCSystem,
SourceFileKind::PCH,
SourceFileKind::ModuleMap,
];
assert_eq!(kinds.len(), 5);
}
#[test]
fn test_source_file_entry_creation() {
let entry = SourceFileEntry {
filename: "header.h".to_string(),
dir_index: 0,
size: 2048,
mod_time: 99999,
file_id: 42,
kind: SourceFileKind::System,
is_module_header: true,
};
assert_eq!(entry.filename, "header.h");
assert!(entry.is_module_header);
assert_eq!(entry.file_id, 42);
}
#[test]
fn test_writer_buffer_growth() {
let tu = make_simple_tu();
let mut writer = X86ASTWriter::new();
let initial_cap = writer.buffer.capacity();
writer.write_pch(&tu, &HashMap::new()).unwrap();
assert!(writer.buffer.len() > 0);
}
#[test]
fn test_reader_no_leak_on_drop() {
{
let _reader =
X86ASTReader::from_bytes(vec![0u8; 1024], Path::new("/tmp/drop.pch"), true);
}
}
}
#[test]
fn test_module_file_cxx20_signature() {
let module = X86ModuleFile::new(Path::new("/tmp/test.pcm"), false);
assert!(module.is_module);
assert!(!module.is_pch);
assert!(module.is_signature_valid());
}
#[test]
fn test_module_file_pch_signature() {
let module = X86ModuleFile::new(Path::new("/tmp/test.pch"), true);
assert!(module.is_pch);
assert!(!module.is_module);
assert!(module.is_signature_valid());
}
#[test]
fn test_module_file_invalid_signature() {
let mut module = X86ModuleFile::new(Path::new("/tmp/test.pch"), true);
module.signature = [0u8; 8];
assert!(!module.is_signature_valid());
}
#[test]
fn test_incremental_pch_merge() {
let mut tu1 = TranslationUnit::new("part1.c");
tu1.add_decl(Decl::Typedef(TypedefDecl::new("my_int", QualType::int())));
let mut macros1 = HashMap::new();
macros1.insert(
"VERSION".to_string(),
MacroDef::object_like(
"VERSION",
vec![Token::new(
TokenKind::NumericLiteral,
"1",
SourceLoc::unknown(),
)],
),
);
let mut writer1 = X86ASTWriter::new();
let data1 = writer1.write_pch(&tu1, ¯os1).unwrap();
let mut tu2 = TranslationUnit::new("part2.c");
let func = FunctionDecl {
name: "init".to_string(),
ret_ty: QualType::void(),
params: vec![],
body: None,
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
tu2.add_decl(Decl::Function(func));
let mut writer2 = X86ASTWriter::new();
let data2 = writer2.write_pch(&tu2, &HashMap::new()).unwrap();
let mut reader1 = X86ASTReader::from_bytes(data1, Path::new("/tmp/part1.pch"), true);
reader1.initialize().unwrap();
let reader2 = X86ASTReader::from_bytes(data2, Path::new("/tmp/part2.pch"), true);
reader1.add_chained_reader(reader2);
let tu_combined = reader1.build_translation_unit().unwrap();
assert!(!tu_combined.is_empty());
}
#[test]
fn test_chained_pch_type_resolution() {
let mut tu1 = TranslationUnit::new("types.c");
tu1.add_decl(Decl::Typedef(TypedefDecl::new("int32_t", QualType::int())));
let mut writer1 = X86ASTWriter::new();
let data1 = writer1.write_pch(&tu1, &HashMap::new()).unwrap();
let mut reader1 = X86ASTReader::from_bytes(data1, Path::new("/tmp/types.pch"), true);
reader1.initialize().unwrap();
let reader2 = X86ASTReader::from_bytes(vec![0u8; 64], Path::new("/tmp/chained.pch"), true);
reader1.add_chained_reader(reader2);
let tu = reader1.build_translation_unit().unwrap();
assert_eq!(tu.len(), 1);
}
#[test]
fn test_source_manager_load_from_module() {
let mut module = X86ModuleFile::new(Path::new("/tmp/mod.pch"), true);
module.directories = vec![PathBuf::from("/usr/include"), PathBuf::from("/opt/include")];
module.filenames = vec!["stdio.h".to_string(), "stdlib.h".to_string()];
module.is_pch = true;
module.base_dir = Some(PathBuf::from("/tmp"));
let mut sm = X86SourceManagerExtensions::new();
sm.load_from_module(&module).unwrap();
assert_eq!(sm.directories.len(), 2);
assert_eq!(sm.filenames.len(), 2);
assert!(sm.is_pch);
assert_eq!(sm.pch_base_dir, Some(PathBuf::from("/tmp")));
}
#[test]
fn test_source_manager_location_decode() {
let mut sm = X86SourceManagerExtensions::new();
sm.add_file(SourceFileEntry {
filename: "test.c".to_string(),
dir_index: 0,
size: 500,
mod_time: 0,
file_id: 0,
kind: SourceFileKind::User,
is_module_header: false,
});
let encoded = EncodedSourceLoc::new(0, 100);
let decoded = sm.decode_location(encoded);
assert!(decoded.is_some());
let loc = decoded.unwrap();
assert_eq!(loc.offset, 100);
}
#[test]
fn test_source_manager_range_translation() {
let mut sm = X86SourceManagerExtensions::new();
sm.add_file(SourceFileEntry {
filename: "test.c".to_string(),
dir_index: 0,
size: 1000,
mod_time: 0,
file_id: 0,
kind: SourceFileKind::User,
is_module_header: false,
});
let begin = EncodedSourceLoc::new(0, 50);
let end = EncodedSourceLoc::new(0, 200);
let range = sm.translate_range(begin, end);
assert!(range.is_some());
let (start, finish) = range.unwrap();
assert_eq!(start.offset, 50);
assert_eq!(finish.offset, 200);
}
#[test]
fn test_macro_body_token_preservation() {
let body_tokens = vec![
Token::new(TokenKind::Identifier, "x", SourceLoc::unknown()),
Token::new(TokenKind::Plus, "+", SourceLoc::unknown()),
Token::new(TokenKind::NumericLiteral, "1", SourceLoc::unknown()),
];
let m = MacroDef::object_like("INC", body_tokens);
let mut macros = HashMap::new();
macros.insert("INC".to_string(), m);
let tu = TranslationUnit::new("empty.c");
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, ¯os).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/pp.pch"), true);
reader.initialize().unwrap();
assert!(reader.macros.contains_key("INC"));
}
#[test]
fn test_macro_source_location_preservation() {
let loc = SourceLoc::new(42, 10, 1024);
let m = MacroDef {
name: "LOCATED".to_string(),
params: vec![],
body: vec![],
is_function_like: false,
is_variadic: false,
def_location: loc,
};
let mut macros = HashMap::new();
macros.insert("LOCATED".to_string(), m);
let tu = TranslationUnit::new("empty.c");
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, ¯os).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/loc.pch"), true);
reader.initialize().unwrap();
let recovered = &reader.macros["LOCATED"];
assert_eq!(recovered.def_location.line, 42);
assert_eq!(recovered.def_location.column, 10);
}
#[test]
fn test_header_search_path_serialization() {
let mut writer = X86ASTWriter::new();
writer.validator_info.header_search_paths = vec![
"/usr/include".to_string(),
"/usr/local/include".to_string(),
"/opt/custom/include".to_string(),
];
writer.write_header().unwrap();
writer.write_metadata().unwrap();
let data = writer.buffer.clone();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/headers.pch"), true);
reader.initialize().unwrap();
assert_eq!(reader.module.header_search_paths.len(), 3);
assert_eq!(reader.module.header_search_paths[0], "/usr/include");
}
#[test]
fn test_module_dependencies() {
let mut module = X86ModuleFile::new(Path::new("/tmp/mod.pch"), true);
module.dependencies = vec!["std.pcm".to_string(), "iostream.pcm".to_string()];
assert_eq!(module.dependencies.len(), 2);
}
#[test]
fn test_module_input_files() {
let mut module = X86ModuleFile::new(Path::new("/tmp/mod.pch"), true);
module.input_files = vec![
"header1.h".to_string(),
"header2.h".to_string(),
"source.c".to_string(),
];
assert_eq!(module.input_files.len(), 3);
assert_eq!(module.input_files[0], "header1.h");
}
#[test]
fn test_block_alignment() {
let mut writer = X86ASTWriter::new();
writer.buffer.push(0xAA);
writer
.write_block_header(ASTBlockID::MetadataBlock)
.unwrap();
assert_eq!(writer.buffer.len() % 4, 0);
}
#[test]
fn test_record_alignment() {
let mut writer = X86ASTWriter::new();
writer.write_record_header(ASTRecordType::Metadata).unwrap();
writer.write_u8(0x42).unwrap();
writer.end_record().unwrap();
assert_eq!(writer.buffer.len() % 4, 0);
}
#[test]
fn test_abbreviation_for_builtin_types() {
let abbrev = ASTAbbreviation {
record_type: ASTRecordType::TypeInt,
operands: vec![
ASTAbbrevOperand::Literal(ASTRecordType::TypeInt.value() as u64),
ASTAbbrevOperand::Fixed(32),
],
abbrev_number: 4,
};
assert_eq!(abbrev.record_type, ASTRecordType::TypeInt);
assert_eq!(abbrev.operands.len(), 2);
}
#[test]
fn test_abbreviation_for_declarations() {
let abbrev = ASTAbbreviation {
record_type: ASTRecordType::DeclFunction,
operands: vec![
ASTAbbrevOperand::VBR(6),
ASTAbbrevOperand::VBR(6),
ASTAbbrevOperand::Blob,
],
abbrev_number: 1,
};
assert_eq!(abbrev.abbrev_number, 1);
}
#[test]
fn test_writer_stats_after_multiple_writes() {
let tu = make_simple_tu();
let mut writer = X86ASTWriter::new();
writer.write_pch(&tu, &HashMap::new()).unwrap();
let stats1 = writer.statistics().clone();
let mut writer2 = X86ASTWriter::new();
writer2.write_pch(&tu, &HashMap::new()).unwrap();
let stats2 = writer2.statistics();
assert_eq!(stats1.num_decls, stats2.num_decls);
}
#[test]
fn test_reader_stats_correctness() {
let tu = make_simple_tu();
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/rstats.pch"), true);
reader.initialize().unwrap();
reader.build_translation_unit().unwrap();
let stats = reader.statistics();
assert!(stats.num_decls >= 2);
assert!(stats.total_bytes_read > 0);
}
#[test]
fn test_multiple_writers_independent() {
let tu = make_simple_tu();
let mut writer1 = X86ASTWriter::new();
let data1 = writer1.write_pch(&tu, &HashMap::new()).unwrap();
let mut writer2 = X86ASTWriter::new();
let data2 = writer2.write_pch(&tu, &HashMap::new()).unwrap();
let reader1 = X86ASTReader::from_bytes(data1, Path::new("/tmp/w1.pch"), true);
let reader2 = X86ASTReader::from_bytes(data2, Path::new("/tmp/w2.pch"), true);
assert!(reader1.module.is_pch);
assert!(reader2.module.is_pch);
}
#[test]
fn test_multiple_readers_independent() {
let tu = make_simple_tu();
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader1 = X86ASTReader::from_bytes(data.clone(), Path::new("/tmp/r1.pch"), true);
let mut reader2 = X86ASTReader::from_bytes(data, Path::new("/tmp/r2.pch"), true);
reader1.initialize().unwrap();
reader2.initialize().unwrap();
let tu1 = reader1.build_translation_unit().unwrap();
let tu2 = reader2.build_translation_unit().unwrap();
assert_eq!(tu1.len(), tu2.len());
}
#[test]
fn test_unicode_identifier_in_ast() {
let mut tu = TranslationUnit::new("unicode.c");
tu.add_decl(Decl::Variable(VarDecl::new("café", QualType::int())));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/unicode.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
match &reconstructed.decls[0] {
Decl::Variable(v) => assert_eq!(v.name, "café"),
_ => panic!("Expected variable"),
}
}
#[test]
fn test_unicode_string_literal() {
let expr = Expr::StringLiteral("héllo world 🌍".to_string());
let mut data = Vec::new();
let mut writer = X86ASTWriter::new();
let kind = writer.serialize_expr(&expr, &mut data).unwrap();
assert_eq!(kind, ASTRecordType::ExprStringLiteral);
}
#[test]
fn test_validator_char_width_mismatch() {
let mut validator = X86PCHValidator::new();
validator.language_options.char_width = 16;
let mut current = LanguageOptionsEncoding::default();
current.char_width = 8;
assert!(!validator.validate_language_options(¤t));
}
#[test]
fn test_validator_short_width_mismatch() {
let mut validator = X86PCHValidator::new();
validator.language_options.short_width = 32;
let current = LanguageOptionsEncoding::default(); assert!(!validator.validate_language_options(¤t));
}
#[test]
fn test_validator_long_width_mismatch() {
let mut validator = X86PCHValidator::new();
validator.language_options.long_width = 32;
let current = LanguageOptionsEncoding::default(); assert!(!validator.validate_language_options(¤t));
}
#[test]
fn test_validator_sse2_requirement() {
let mut validator = X86PCHValidator::new();
validator.target_options.sse2 = true;
let mut current = TargetOptionsEncoding::default();
current.sse2 = false;
assert!(!validator.validate_target_options(¤t));
}
#[test]
fn test_validator_avx2_requirement() {
let mut validator = X86PCHValidator::new();
validator.target_options.avx = true;
validator.target_options.avx2 = true;
let mut current = TargetOptionsEncoding::default();
current.avx = true;
current.avx2 = false;
assert!(!validator.validate_target_options(¤t));
}
#[test]
fn test_validator_soft_float_mismatch() {
let mut validator = X86PCHValidator::new();
validator.target_options.soft_float = true;
let current = TargetOptionsEncoding::default();
assert!(!validator.validate_target_options(¤t));
}
#[test]
fn test_validator_pic_mismatch() {
let mut validator = X86PCHValidator::new();
validator.target_options.pic = false;
let current = TargetOptionsEncoding::default();
assert!(!validator.validate_target_options(¤t));
}
#[test]
fn test_pch_extension_constant() {
assert_eq!(PCH_EXTENSION, "pch");
assert_eq!(PCM_EXTENSION, "pcm");
assert_eq!(AST_FILE_EXTENSION, "ast");
}
#[test]
fn test_ast_version_constants() {
assert!(CLANG_AST_VERSION_MAJOR > 0);
assert_eq!(
CLANG_AST_VERSION,
(CLANG_AST_VERSION_MAJOR << 16) | CLANG_AST_VERSION_MINOR
);
assert!(CLANG_AST_MIN_READER_VERSION <= CLANG_AST_VERSION_MAJOR);
}
#[test]
fn test_magic_constant() {
assert_ne!(CLANG_AST_MAGIC, 0);
assert_ne!(&PCH_SIGNATURE[0..4], &MODULE_SIGNATURE[0..4]);
}
#[test]
fn test_lazy_decl_deserialized_flag() {
let mut lazy = LazyDeclData::new(0, ASTRecordType::DeclFunction, vec![1, 2, 3]);
assert!(!lazy.is_deserialized);
lazy.is_deserialized = true;
assert!(lazy.is_deserialized);
}
#[test]
fn test_lazy_decl_offset() {
let lazy = LazyDeclData::new(1024, ASTRecordType::DeclVariable, vec![42]);
assert_eq!(lazy.bitstream_offset, 1024);
assert_eq!(lazy.record_type, ASTRecordType::DeclVariable);
}
#[test]
fn test_ast_context_lazy_decl_lookup() {
let mut ctx = X86ASTContext::new();
ctx.register_lazy_decl(
100,
LazyDeclData::new(500, ASTRecordType::DeclFunction, vec![]),
);
assert!(ctx.has_external_decl(100));
assert!(!ctx.has_external_decl(200));
}
#[test]
fn test_vbr_encoding_zero() {
let mut writer = X86ASTWriter::new();
writer.write_vbr_u32(0).unwrap();
assert_eq!(writer.buffer.len(), 1);
assert_eq!(writer.buffer[0], 0);
}
#[test]
fn test_vbr_encoding_max_u32() {
let mut writer = X86ASTWriter::new();
writer.write_vbr_u32(0xFFFFFFFF).unwrap();
assert!(writer.buffer.len() <= 5);
}
#[test]
fn test_vbr_decoding_truncated() {
let mut reader = X86ASTReader::from_bytes(vec![0x80], Path::new("/tmp/vbr.pch"), true);
let result = reader.read_vbr_u32();
assert!(result.is_err());
}
#[test]
fn test_read_header_too_short() {
let data = vec![0xAA; 4]; let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/short.pch"), true);
let result = reader.read_header();
assert!(result.is_err());
}
#[test]
fn test_read_header_wrong_magic() {
let mut data = Vec::new();
data.extend_from_slice(&0xDEADBEEFu32.to_le_bytes());
data.extend_from_slice(&[0u8; 32]);
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/bad.pch"), true);
let result = reader.read_header();
assert!(result.is_err());
}
#[test]
fn test_read_header_version_too_old() {
let mut data = Vec::new();
data.extend_from_slice(&CLANG_AST_MAGIC.to_le_bytes());
data.extend_from_slice(&PCH_SIGNATURE);
data.extend_from_slice(&1u32.to_le_bytes()); data.extend_from_slice(&0u32.to_le_bytes());
data.extend_from_slice(&[0u8; 8]);
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/old.pch"), true);
let result = reader.read_header();
assert!(result.is_err());
}
#[test]
fn test_read_header_version_newer_ok() {
let mut data = Vec::new();
data.extend_from_slice(&CLANG_AST_MAGIC.to_le_bytes());
data.extend_from_slice(&PCH_SIGNATURE);
data.extend_from_slice(&CLANG_AST_VERSION_MAJOR.to_le_bytes());
data.extend_from_slice(&CLANG_AST_VERSION_MINOR.to_le_bytes());
data.extend_from_slice(&[0u8; 8]);
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/ok.pch"), true);
let result = reader.read_header();
assert!(result.is_ok());
}
#[test]
fn test_bitfield_field_serialization() {
let sd = StructDecl {
name: Some("Flags".to_string()),
fields: vec![
FieldDecl::new_bitfield("enabled", QualType::int(), 1),
FieldDecl::new_bitfield("ready", QualType::int(), 1),
FieldDecl::new_bitfield("error", QualType::int(), 1),
],
is_union: false,
};
let mut tu = TranslationUnit::new("bits.c");
tu.add_decl(Decl::Struct(sd));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/bits.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
match &reconstructed.decls[0] {
Decl::Struct(s) => {
assert_eq!(s.fields.len(), 3);
assert_eq!(s.fields[0].bit_width, Some(1));
assert_eq!(s.fields[1].bit_width, Some(1));
assert_eq!(s.fields[2].bit_width, Some(1));
}
_ => panic!("Expected struct decl"),
}
}
#[test]
fn test_union_decl_roundtrip() {
let ud = StructDecl {
name: Some("Data".to_string()),
fields: vec![
FieldDecl::new("i", QualType::int()),
FieldDecl::new("f", QualType::float_()),
],
is_union: true,
};
let mut tu = TranslationUnit::new("union.c");
tu.add_decl(Decl::Struct(ud));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/union.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
match &reconstructed.decls[0] {
Decl::Struct(s) => assert!(s.is_union),
_ => panic!("Expected struct (union) decl"),
}
}
#[test]
fn test_anonymous_struct_roundtrip() {
let sd = StructDecl {
name: None,
fields: vec![FieldDecl::new("x", QualType::int())],
is_union: false,
};
let mut tu = TranslationUnit::new("anon.c");
tu.add_decl(Decl::Struct(sd));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/anon.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
match &reconstructed.decls[0] {
Decl::Struct(s) => assert!(s.name.is_none()),
_ => panic!("Expected anonymous struct"),
}
}
#[test]
fn test_anonymous_enum_roundtrip() {
let ed = EnumDecl {
name: None,
variants: vec![EnumVariant::new("A", Some(0))],
underlying: QualType::int(),
};
let mut tu = TranslationUnit::new("anon_enum.c");
tu.add_decl(Decl::Enum(ed));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/anon_enum.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
match &reconstructed.decls[0] {
Decl::Enum(e) => assert!(e.name.is_none()),
_ => panic!("Expected anonymous enum"),
}
}
#[test]
fn test_enum_auto_increment_values() {
let ed = EnumDecl {
name: Some("AutoEnum".to_string()),
variants: vec![
EnumVariant::new("First", None),
EnumVariant::new("Second", None),
EnumVariant::new("Third", Some(100)),
EnumVariant::new("Fourth", None),
],
underlying: QualType::int(),
};
let mut tu = TranslationUnit::new("auto_enum.c");
tu.add_decl(Decl::Enum(ed));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/auto_enum.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
match &reconstructed.decls[0] {
Decl::Enum(e) => {
assert!(e.variants[0].value.is_none());
assert!(e.variants[1].value.is_none());
assert_eq!(e.variants[2].value, Some(100));
assert!(e.variants[3].value.is_none());
}
_ => panic!("Expected enum"),
}
}
#[test]
fn test_pointer_type_chain_serialization() {
let ptr4 = QualType::pointer_to(QualType::pointer_to(QualType::pointer_to(
QualType::pointer_to(QualType::int()),
)));
let mut ctx = X86ASTContext::new();
let id = ctx.register_type(&ptr4);
let mut writer = X86ASTWriter::new();
writer.context = ctx;
let mut data = Vec::new();
let kind = writer
.serialize_type_node(
&TypeNode::Pointer(Box::new(QualType::new(TypeNode::Int))),
&mut data,
)
.unwrap();
assert_eq!(kind, ASTRecordType::TypePointer);
}
#[test]
fn test_function_pointer_type() {
let fn_ty = QualType::new(TypeNode::Function {
ret: Box::new(QualType::int()),
params: vec![QualType::int(), QualType::float_()],
is_vararg: false,
});
let fn_ptr = QualType::pointer_to(fn_ty);
let mut ctx = X86ASTContext::new();
let id = ctx.register_type(&fn_ptr);
assert_eq!(id, 0);
}
#[test]
fn test_array_of_function_pointers() {
let fn_ty = QualType::new(TypeNode::Function {
ret: Box::new(QualType::void()),
params: vec![],
is_vararg: false,
});
let fn_ptr = QualType::pointer_to(fn_ty);
let arr = QualType::new(TypeNode::Array {
elem: Box::new(fn_ptr),
size: Some(10),
});
let mut ctx = X86ASTContext::new();
let id = ctx.register_type(&arr);
assert_eq!(id, 0);
}
#[test]
fn test_enum_variant_decl_without_enum_name() {
let ev = EnumVariantDecl {
name: "STANDALONE".to_string(),
value: Some(-1),
enum_name: None,
};
let mut tu = TranslationUnit::new("standalone.c");
tu.add_decl(Decl::EnumVariant(ev));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/sa.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
match &reconstructed.decls[0] {
Decl::EnumVariant(ev) => {
assert_eq!(ev.name, "STANDALONE");
assert_eq!(ev.value, Some(-1));
assert!(ev.enum_name.is_none());
}
_ => panic!("Expected enum variant"),
}
}
#[test]
fn test_extern_variable_serialization() {
let v = VarDecl {
name: "errno".to_string(),
ty: QualType::int(),
init: None,
linkage: Linkage::External,
is_global: true,
is_extern: true,
is_static: false,
};
let mut tu = TranslationUnit::new("extern.c");
tu.add_decl(Decl::Variable(v));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/extern.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
match &reconstructed.decls[0] {
Decl::Variable(v) => {
assert!(v.is_extern);
assert!(v.is_global);
assert!(!v.is_static);
}
_ => panic!("Expected variable"),
}
}
#[test]
fn test_static_variable_serialization() {
let v = VarDecl {
name: "counter".to_string(),
ty: QualType::int(),
init: Some(Box::new(Expr::IntLiteral(0))),
linkage: Linkage::Internal,
is_global: true,
is_extern: false,
is_static: true,
};
let mut tu = TranslationUnit::new("static.c");
tu.add_decl(Decl::Variable(v));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/static.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
match &reconstructed.decls[0] {
Decl::Variable(v) => {
assert!(v.is_static);
assert!(!v.is_extern);
}
_ => panic!("Expected variable"),
}
}
#[test]
fn test_switch_with_cases_serialization() {
let switch_stmt = Stmt::Switch {
expr: Box::new(Expr::Ident("x".to_string())),
body: Box::new(Stmt::Compound(CompoundStmt {
stmts: vec![
Stmt::Case {
value: Box::new(Expr::IntLiteral(1)),
stmt: Box::new(Stmt::Break),
},
Stmt::Case {
value: Box::new(Expr::IntLiteral(2)),
stmt: Box::new(Stmt::Break),
},
Stmt::Default {
stmt: Box::new(Stmt::Break),
},
],
})),
};
let mut body = CompoundStmt::new();
body.push(switch_stmt);
let mut tu = TranslationUnit::new("switch.c");
let func = FunctionDecl {
name: "test_switch".to_string(),
ret_ty: QualType::void(),
params: vec![],
body: Some(body),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
tu.add_decl(Decl::Function(func));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/switch.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
assert_eq!(reconstructed.len(), 1);
}
#[test]
fn test_goto_and_label_serialization() {
let mut body = CompoundStmt::new();
body.push(Stmt::Goto {
label: "cleanup".to_string(),
});
body.push(Stmt::Expr(Box::new(Expr::Ident("x".to_string()))));
body.push(Stmt::Label {
name: "cleanup".to_string(),
stmt: Box::new(Stmt::Return(None)),
});
let mut tu = TranslationUnit::new("goto.c");
let func = FunctionDecl {
name: "goto_test".to_string(),
ret_ty: QualType::void(),
params: vec![],
body: Some(body),
is_vararg: false,
linkage: Linkage::External,
is_inline: false,
is_noreturn: false,
};
tu.add_decl(Decl::Function(func));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/goto.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
assert_eq!(reconstructed.len(), 1);
}
#[test]
fn test_member_expr_arrow() {
let expr = Expr::Member {
base: Box::new(Expr::Ident("ptr".to_string())),
field: "value".to_string(),
is_arrow: true,
};
let mut data = Vec::new();
let mut writer = X86ASTWriter::new();
let _ = writer.serialize_expr(&expr, &mut data).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/mem.pch"), true);
let recovered = reader.deserialize_expr().unwrap();
match recovered {
Expr::Member {
is_arrow, field, ..
} => {
assert!(is_arrow);
assert_eq!(field, "value");
}
_ => panic!("Expected member expression"),
}
}
#[test]
fn test_sizeof_type_vs_expr() {
let expr_type = Expr::SizeOfType(QualType::int());
let expr_expr = Expr::SizeOf(Box::new(Expr::Ident("x".to_string())));
let mut writer = X86ASTWriter::new();
let mut data1 = Vec::new();
let kind1 = writer.serialize_expr(&expr_type, &mut data1).unwrap();
assert_eq!(kind1, ASTRecordType::ExprSizeOfType);
let mut data2 = Vec::new();
let kind2 = writer.serialize_expr(&expr_expr, &mut data2).unwrap();
assert_eq!(kind2, ASTRecordType::ExprSizeOf);
}
#[test]
fn test_all_unary_operators_serialization() {
let ops = vec![
UnaryOp::Plus,
UnaryOp::Minus,
UnaryOp::Not,
UnaryOp::BitNot,
UnaryOp::AddrOf,
UnaryOp::Deref,
];
for op in ops {
let expr = Expr::Unary(op, Box::new(Expr::IntLiteral(1)));
let mut data = Vec::new();
let mut writer = X86ASTWriter::new();
let _ = writer.serialize_expr(&expr, &mut data).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/uop.pch"), true);
let recovered = reader.deserialize_expr().unwrap();
match recovered {
Expr::Unary(recovered_op, _) => {
}
_ => panic!("Expected unary expression for {:?}", op),
}
}
}
#[test]
fn test_all_binary_operators_serialization() {
let ops = vec![
BinaryOp::Add,
BinaryOp::Sub,
BinaryOp::Mul,
BinaryOp::Div,
BinaryOp::Mod,
BinaryOp::And,
BinaryOp::Or,
BinaryOp::Xor,
BinaryOp::Shl,
BinaryOp::Shr,
BinaryOp::Eq,
BinaryOp::Ne,
BinaryOp::Lt,
BinaryOp::Gt,
BinaryOp::Le,
BinaryOp::Ge,
BinaryOp::LogicAnd,
BinaryOp::LogicOr,
BinaryOp::Comma,
];
for op in ops {
let expr = Expr::Binary(
op,
Box::new(Expr::IntLiteral(1)),
Box::new(Expr::IntLiteral(2)),
);
let mut data = Vec::new();
let mut writer = X86ASTWriter::new();
let _ = writer.serialize_expr(&expr, &mut data).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/bop.pch"), true);
let recovered = reader.deserialize_expr().unwrap();
match recovered {
Expr::Binary(..) => {} _ => panic!("Expected binary expression for {:?}", op),
}
}
}
#[test]
fn test_all_assign_operators_serialization() {
let ops = vec![
BinaryOp::Assign,
BinaryOp::AddAssign,
BinaryOp::SubAssign,
BinaryOp::MulAssign,
BinaryOp::DivAssign,
BinaryOp::ModAssign,
BinaryOp::AndAssign,
BinaryOp::OrAssign,
BinaryOp::XorAssign,
BinaryOp::ShlAssign,
BinaryOp::ShrAssign,
];
for op in ops {
let expr = Expr::Assign(
op,
Box::new(Expr::Ident("x".to_string())),
Box::new(Expr::IntLiteral(5)),
);
let mut data = Vec::new();
let mut writer = X86ASTWriter::new();
let _ = writer.serialize_expr(&expr, &mut data).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/aop.pch"), true);
let recovered = reader.deserialize_expr().unwrap();
match recovered {
Expr::Assign(..) => {} _ => panic!("Expected assign expression for {:?}", op),
}
}
}
#[test]
fn test_compound_literal_serialization() {
let mut init = CompoundStmt::new();
init.push(Stmt::Expr(Box::new(Expr::IntLiteral(42))));
let expr = Expr::CompoundLiteral(QualType::int(), Box::new(init));
let mut data = Vec::new();
let mut writer = X86ASTWriter::new();
let _ = writer.serialize_expr(&expr, &mut data).unwrap();
}
#[test]
fn test_call_with_no_args() {
let expr = Expr::Call {
callee: Box::new(Expr::Ident("f".to_string())),
args: vec![],
};
let mut data = Vec::new();
let mut writer = X86ASTWriter::new();
let _ = writer.serialize_expr(&expr, &mut data).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/noargs.pch"), true);
let recovered = reader.deserialize_expr().unwrap();
match recovered {
Expr::Call { args, .. } => assert!(args.is_empty()),
_ => panic!("Expected call"),
}
}
#[test]
fn test_call_with_many_args() {
let args: Vec<Expr> = (0..100).map(|i| Expr::IntLiteral(i as i64)).collect();
let expr = Expr::Call {
callee: Box::new(Expr::Ident("big_fn".to_string())),
args,
};
let mut data = Vec::new();
let mut writer = X86ASTWriter::new();
let _ = writer.serialize_expr(&expr, &mut data).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/bigcall.pch"), true);
let recovered = reader.deserialize_expr().unwrap();
match recovered {
Expr::Call { args, .. } => assert_eq!(args.len(), 100),
_ => panic!("Expected call"),
}
}
#[test]
fn test_for_loop_with_all_parts() {
let stmt = Stmt::For {
init: Some(Box::new(Stmt::Expr(Box::new(Expr::Assign(
BinaryOp::Assign,
Box::new(Expr::Ident("i".to_string())),
Box::new(Expr::IntLiteral(0)),
))))),
cond: Some(Box::new(Expr::Binary(
BinaryOp::Lt,
Box::new(Expr::Ident("i".to_string())),
Box::new(Expr::IntLiteral(10)),
))),
incr: Some(Box::new(Expr::PostInc(Box::new(Expr::Ident(
"i".to_string(),
))))),
body: Box::new(Stmt::Null),
};
let mut data = Vec::new();
let mut writer = X86ASTWriter::new();
let _ = writer.serialize_stmt(&stmt, &mut data).unwrap();
}
#[test]
fn test_for_loop_empty() {
let stmt = Stmt::For {
init: None,
cond: None,
incr: None,
body: Box::new(Stmt::Null),
};
let mut data = Vec::new();
let mut writer = X86ASTWriter::new();
let kind = writer.serialize_stmt(&stmt, &mut data).unwrap();
assert_eq!(kind, ASTRecordType::StmtFor);
}
#[test]
fn test_subscript_with_complex_index() {
let expr = Expr::Subscript {
base: Box::new(Expr::Ident("arr".to_string())),
index: Box::new(Expr::Binary(
BinaryOp::Add,
Box::new(Expr::Ident("i".to_string())),
Box::new(Expr::IntLiteral(1)),
)),
};
let mut data = Vec::new();
let mut writer = X86ASTWriter::new();
let _ = writer.serialize_expr(&expr, &mut data).unwrap();
}
#[test]
fn test_nested_conditional() {
let expr = Expr::Conditional(
Box::new(Expr::Ident("a".to_string())),
Box::new(Expr::Conditional(
Box::new(Expr::Ident("b".to_string())),
Box::new(Expr::Ident("c".to_string())),
Box::new(Expr::Ident("d".to_string())),
)),
Box::new(Expr::Ident("e".to_string())),
);
let mut data = Vec::new();
let mut writer = X86ASTWriter::new();
let _ = writer.serialize_expr(&expr, &mut data).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/tern.pch"), true);
let recovered = reader.deserialize_expr().unwrap();
assert!(matches!(recovered, Expr::Conditional(..)));
}
#[test]
fn test_translation_unit_utility_methods() {
let mut tu = TranslationUnit::new("util.c");
assert!(tu.is_empty());
assert_eq!(tu.len(), 0);
tu.add_decl(Decl::Function(FunctionDecl::new("main", QualType::int())));
assert!(!tu.is_empty());
assert_eq!(tu.len(), 1);
let funcs: Vec<_> = tu.functions().collect();
assert_eq!(funcs.len(), 1);
let globals: Vec<_> = tu.globals().collect();
assert_eq!(globals.len(), 0);
tu.add_decl(Decl::Variable(VarDecl::new("g", QualType::int()).global()));
let globals: Vec<_> = tu.globals().collect();
assert_eq!(globals.len(), 1);
}
#[test]
fn test_ast_record_type_coverage() {
let all_types = vec![
ASTRecordType::Metadata,
ASTRecordType::IdentifierOffset,
ASTRecordType::IdentifierTable,
ASTRecordType::SourceLocationOffset,
ASTRecordType::SourceLocationTable,
ASTRecordType::TypeOffset,
ASTRecordType::DeclarationOffset,
ASTRecordType::StatementOffset,
ASTRecordType::MacroDefinitionOffset,
ASTRecordType::ModuleOffset,
ASTRecordType::FileTable,
ASTRecordType::DirectoryTable,
ASTRecordType::TargetTriple,
ASTRecordType::LanguageOptions,
ASTRecordType::TargetOptions,
ASTRecordType::HeaderSearchOptions,
ASTRecordType::PCHValidatorInfo,
ASTRecordType::ModuleMap,
ASTRecordType::SourceManagerBlock,
ASTRecordType::InputFiles,
ASTRecordType::PreprocessorOptions,
ASTRecordType::OriginalFileName,
ASTRecordType::OriginalFileID,
ASTRecordType::VersionControl,
];
for ty in &all_types {
let val = ty.value();
let rt = ASTRecordType::from_u32(val);
assert_eq!(rt, Some(*ty), "Failed for {:?}", ty);
}
}
#[test]
fn test_full_pch_workflow() {
let mut header_tu = TranslationUnit::new("common.h");
header_tu.add_decl(Decl::Typedef(TypedefDecl::new("size_t", QualType::ulong())));
header_tu.add_decl(Decl::Struct(StructDecl {
name: Some("Vec2".to_string()),
fields: vec![
FieldDecl::new("x", QualType::float_()),
FieldDecl::new("y", QualType::float_()),
],
is_union: false,
}));
let mut header_macros = HashMap::new();
header_macros.insert(
"NULL".to_string(),
MacroDef::object_like(
"NULL",
vec![Token::new(
TokenKind::NumericLiteral,
"0",
SourceLoc::unknown(),
)],
),
);
let mut options = ClangOptions::default();
let temp_pch = std::env::temp_dir().join("test_full_workflow.pch");
create_pch(&header_tu, &header_macros, &temp_pch, &options).unwrap();
assert!(is_pch_file(&temp_pch));
let version = pch_version_string(&temp_pch);
assert!(version.is_some());
let (recovered_tu, recovered_macros) = read_pch(&temp_pch, &options).unwrap();
assert_eq!(recovered_tu.len(), 2);
assert!(recovered_macros.contains_key("NULL"));
let _ = std::fs::remove_file(&temp_pch);
}
#[test]
fn test_max_length_identifier() {
let long_name = "a".repeat(1024);
let mut tu = TranslationUnit::new("long.c");
tu.add_decl(Decl::Variable(VarDecl::new(&long_name, QualType::int())));
let mut writer = X86ASTWriter::new();
let data = writer.write_pch(&tu, &HashMap::new()).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/long.pch"), true);
reader.initialize().unwrap();
let reconstructed = reader.build_translation_unit().unwrap();
match &reconstructed.decls[0] {
Decl::Variable(v) => assert_eq!(v.name, long_name),
_ => panic!("Expected variable with long name"),
}
}
#[test]
fn test_deep_expression_tree() {
let mut expr = Expr::IntLiteral(100);
for i in (1..100).rev() {
expr = Expr::Binary(BinaryOp::Add, Box::new(Expr::IntLiteral(i)), Box::new(expr));
}
let mut data = Vec::new();
let mut writer = X86ASTWriter::new();
let _ = writer.serialize_expr(&expr, &mut data).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/deep_expr.pch"), true);
let recovered = reader.deserialize_expr().unwrap();
assert!(matches!(recovered, Expr::Binary(..)));
}
#[test]
fn test_min_max_int_literals() {
let values = vec![i64::MIN, i64::MIN + 1, -1, 0, 1, i64::MAX - 1, i64::MAX];
for v in values {
let expr = Expr::IntLiteral(v);
let mut data = Vec::new();
let mut writer = X86ASTWriter::new();
let _ = writer.serialize_expr(&expr, &mut data).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/int.pch"), true);
let recovered = reader.deserialize_expr().unwrap();
match recovered {
Expr::IntLiteral(rv) => assert_eq!(rv, v),
_ => panic!("Expected IntLiteral({})", v),
}
}
}
#[test]
fn test_special_float_literals() {
let values = vec![
0.0f64,
-0.0,
f64::INFINITY,
f64::NEG_INFINITY,
f64::MIN,
f64::MAX,
std::f64::consts::PI,
std::f64::consts::E,
];
for v in values {
let expr = Expr::DoubleLiteral(v);
let mut data = Vec::new();
let mut writer = X86ASTWriter::new();
let _ = writer.serialize_expr(&expr, &mut data).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/float.pch"), true);
let recovered = reader.deserialize_expr().unwrap();
match recovered {
Expr::DoubleLiteral(rv) => {
if v.is_nan() {
assert!(rv.is_nan());
} else {
assert_eq!(rv, v);
}
}
_ => panic!("Expected DoubleLiteral"),
}
}
}
#[test]
fn test_special_char_literals() {
let chars = vec![
'\0', '\n', '\t', '\r', '\\', '\'', '"', 'A', 'z', '0', '9', ' ', '~',
];
for c in chars {
let expr = Expr::CharLiteral(c);
let mut data = Vec::new();
let mut writer = X86ASTWriter::new();
let _ = writer.serialize_expr(&expr, &mut data).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/char.pch"), true);
let recovered = reader.deserialize_expr().unwrap();
match recovered {
Expr::CharLiteral(rc) => assert_eq!(rc, c),
_ => panic!("Expected CharLiteral('{}')", c),
}
}
}
#[test]
fn test_empty_string_literal() {
let expr = Expr::StringLiteral(String::new());
let mut data = Vec::new();
let mut writer = X86ASTWriter::new();
let _ = writer.serialize_expr(&expr, &mut data).unwrap();
let mut reader = X86ASTReader::from_bytes(data, Path::new("/tmp/empty_str.pch"), true);
let recovered = reader.deserialize_expr().unwrap();
match recovered {
Expr::StringLiteral(s) => assert!(s.is_empty()),
_ => panic!("Expected empty StringLiteral"),
}
}