Author: Joshua Batista Date: 2026-01-09T13:37:49-08:00 New Revision: 719006a1950ad190882483a8342be4f593164c94
URL: https://github.com/llvm/llvm-project/commit/719006a1950ad190882483a8342be4f593164c94 DIFF: https://github.com/llvm/llvm-project/commit/719006a1950ad190882483a8342be4f593164c94.diff LOG: [HLSL][Sema] Validate that occupied register numbers never exceed UINT32_MAX (#174028) This PR adds validation for register numbers. Register numbers ought never to exceed UINT32_MAX, or 4294967295 Additionally, resource arrays will have each resource element bound sequentially, and those resource's register numbers should not exceed UINT32_MAX, or 4294967295. Even though not explicitly given a register number, their effective register number is also validated. This accounts for nested resource declarations and resource arrays too. Fixes https://github.com/llvm/llvm-project/issues/136809 Added: clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl Modified: clang/include/clang/Basic/DiagnosticSemaKinds.td clang/lib/Sema/SemaHLSL.cpp clang/test/SemaHLSL/resource_binding_attr_error.hlsl Removed: ################################################################################ diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index fdd9b9cd3a996..cd0b5b6343b9e 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -13341,6 +13341,7 @@ def warn_hlsl_register_type_c_packoffset: Warning<"binding type 'c' ignored in b def warn_hlsl_deprecated_register_type_b: Warning<"binding type 'b' only applies to constant buffers. The 'bool constant' binding type is no longer supported">, InGroup<LegacyConstantRegisterBinding>, DefaultError; def warn_hlsl_deprecated_register_type_i: Warning<"binding type 'i' ignored. The 'integer constant' binding type is no longer supported">, InGroup<LegacyConstantRegisterBinding>, DefaultError; def err_hlsl_unsupported_register_number : Error<"register number should be an integer">; +def err_hlsl_register_number_too_large : Error<"register number should not exceed 4294967295">; def err_hlsl_expected_space : Error<"invalid space specifier '%0' used; expected 'space' followed by an integer, like space1">; def err_hlsl_space_on_global_constant : Error<"register space cannot be specified on global constants">; def warn_hlsl_implicit_binding : Warning<"resource has implicit register binding">, InGroup<HLSLImplicitBinding>, DefaultIgnore; diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp index a6de1cd550212..0b6f1d8075985 100644 --- a/clang/lib/Sema/SemaHLSL.cpp +++ b/clang/lib/Sema/SemaHLSL.cpp @@ -2361,6 +2361,123 @@ static bool DiagnoseHLSLRegisterAttribute(Sema &S, SourceLocation &ArgLoc, return ValidateMultipleRegisterAnnotations(S, D, RegType); } +// return false if the slot count exceeds the limit, true otherwise +static bool AccumulateHLSLResourceSlots(QualType Ty, uint64_t &StartSlot, + const uint64_t &Limit, + const ResourceClass ResClass, + ASTContext &Ctx, + uint64_t ArrayCount = 1) { + Ty = Ty.getCanonicalType(); + const Type *T = Ty.getTypePtr(); + + // Early exit if already overflowed + if (StartSlot > Limit) + return false; + + // Case 1: array type + if (const auto *AT = dyn_cast<ArrayType>(T)) { + uint64_t Count = 1; + + if (const auto *CAT = dyn_cast<ConstantArrayType>(AT)) + Count = CAT->getSize().getZExtValue(); + + QualType ElemTy = AT->getElementType(); + return AccumulateHLSLResourceSlots(ElemTy, StartSlot, Limit, ResClass, Ctx, + ArrayCount * Count); + } + + // Case 2: resource leaf + if (auto ResTy = dyn_cast<HLSLAttributedResourceType>(T)) { + // First ensure this resource counts towards the corresponding + // register type limit. + if (ResTy->getAttrs().ResourceClass != ResClass) + return true; + + // Validate highest slot used + uint64_t EndSlot = StartSlot + ArrayCount - 1; + if (EndSlot > Limit) + return false; + + // Advance SlotCount past the consumed range + StartSlot = EndSlot + 1; + return true; + } + + // Case 3: struct / record + if (const auto *RT = dyn_cast<RecordType>(T)) { + const RecordDecl *RD = RT->getDecl(); + + if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(RD)) { + for (const CXXBaseSpecifier &Base : CXXRD->bases()) { + if (!AccumulateHLSLResourceSlots(Base.getType(), StartSlot, Limit, + ResClass, Ctx, ArrayCount)) + return false; + } + } + + for (const FieldDecl *Field : RD->fields()) { + if (!AccumulateHLSLResourceSlots(Field->getType(), StartSlot, Limit, + ResClass, Ctx, ArrayCount)) + return false; + } + + return true; + } + + // Case 4: everything else + return true; +} + +// return true if there is something invalid, false otherwise +static bool ValidateRegisterNumber(const StringRef SlotNumStr, Decl *TheDecl, + ASTContext &Ctx, RegisterType RegTy, + unsigned &Result) { + uint64_t SlotNum; + if (SlotNumStr.getAsInteger(10, SlotNum)) + return true; + + const uint64_t Limit = UINT32_MAX; + if (SlotNum > Limit) + return true; + + // after verifying the number doesn't exceed uint32max, we don't need + // to look further into c or i register types + if (RegTy == RegisterType::C || RegTy == RegisterType::I) { + SlotNumStr.getAsInteger(10, Result); + return false; + } + + if (VarDecl *VD = dyn_cast<VarDecl>(TheDecl)) { + uint64_t BaseSlot = SlotNum; + + if (!AccumulateHLSLResourceSlots(VD->getType(), SlotNum, Limit, + getResourceClass(RegTy), Ctx)) + return true; + + // After AccumulateHLSLResourceSlots runs, SlotNum is now + // the first free slot; last used was SlotNum - 1 + if (BaseSlot > Limit) + return true; + + SlotNumStr.getAsInteger(10, Result); + return false; + } + // handle the cbuffer/tbuffer case + if (dyn_cast<HLSLBufferDecl>(TheDecl)) { + // resources cannot be put within a cbuffer, so no need + // to analyze the structure since the register number + // won't be pushed any higher. + if (SlotNum > Limit) + return true; + + SlotNumStr.getAsInteger(10, Result); + return false; + } + + // we don't expect any other decl type, so fail + return true; +} + void SemaHLSL::handleResourceBindingAttr(Decl *TheDecl, const ParsedAttr &AL) { if (VarDecl *VD = dyn_cast<VarDecl>(TheDecl)) { QualType Ty = VD->getType(); @@ -2419,12 +2536,25 @@ void SemaHLSL::handleResourceBindingAttr(Decl *TheDecl, const ParsedAttr &AL) { Diag(SlotLoc, diag::warn_hlsl_deprecated_register_type_i); return; } - StringRef SlotNumStr = Slot.substr(1); + const StringRef SlotNumStr = Slot.substr(1); + unsigned N; - if (SlotNumStr.getAsInteger(10, N)) { + + // validate that the slot number is a non-empty number + if (SlotNumStr.empty() || !llvm::all_of(SlotNumStr, llvm::isDigit)) { Diag(SlotLoc, diag::err_hlsl_unsupported_register_number); return; } + + // Validate register number. It should not exceed UINT32_MAX, + // including if the resource type is an array that starts + // before UINT32_MAX, but ends afterwards. + if (ValidateRegisterNumber(SlotNumStr, TheDecl, getASTContext(), RegType, + N)) { + Diag(SlotLoc, diag::err_hlsl_register_number_too_large); + return; + } + SlotNum = N; } diff --git a/clang/test/SemaHLSL/resource_binding_attr_error.hlsl b/clang/test/SemaHLSL/resource_binding_attr_error.hlsl index afd7d407c34c2..e2d72197ef602 100644 --- a/clang/test/SemaHLSL/resource_binding_attr_error.hlsl +++ b/clang/test/SemaHLSL/resource_binding_attr_error.hlsl @@ -34,6 +34,21 @@ cbuffer D : register(b 2, space3) {} // expected-error@+1 {{expected <numeric_constant>}} cbuffer E : register(u-1) {}; +// expected-error@+1 {{expected <numeric_constant>}} +cbuffer F : register(u) {}; + +// expected-error@+1 {{binding type 'u' only applies to UAV resources}} +cbuffer G : register(u13) {}; + +// expected-error@+1 {{binding type 'c' only applies to numeric variables in the global scope}} +cbuffer H : register(c0) {}; + +// expected-error@+1 {{binding type 't' only applies to SRV resources}} +cbuffer I : register(t0) {} + +// expected-error@+1 {{binding type 'b' only applies to constant buffer resources}} +tbuffer J : register(b0) {}; + // expected-error@+1 {{'register' attribute only applies to cbuffer/tbuffer and external global variables}} static MyTemplatedSRV<float> U : register(u5); diff --git a/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl b/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl new file mode 100644 index 0000000000000..464eb12669a9c --- /dev/null +++ b/clang/test/SemaHLSL/resource_binding_attr_error_uint32_max.hlsl @@ -0,0 +1,90 @@ +// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -o - -fsyntax-only %s -verify + +// test semantic validation for register numbers that exceed UINT32_MAX + +struct S { + RWBuffer<float> A[4]; + RWBuffer<int> B[10]; +}; + +// do some more nesting +struct S2 { + S a[3]; +}; + +// test that S.A carries the register number over the limit and emits the error +// expected-error@+1 {{register number should not exceed 4294967295}} +S s : register(u4294967294); // UINT32_MAX - 1 + +// test the error is also triggered when analyzing S.B +// expected-error@+1 {{register number should not exceed 4294967295}} +S s2 : register(u4294967289); + + +// test the error is also triggered when analyzing S2.a[1].B +// expected-error@+1 {{register number should not exceed 4294967295}} +S2 s3 : register(u4294967275); + +// expected-error@+1 {{register number should not exceed 4294967295}} +RWBuffer<float> Buf[10][10] : register(u4294967234); + +// test a standard resource array +// expected-error@+1 {{register number should not exceed 4294967295}} +RWBuffer<float> Buf2[10] : register(u4294967294); + +// test directly an excessively high register number. +// expected-error@+1 {{register number should not exceed 4294967295}} +RWBuffer<float> A : register(u9995294967294); + +// test a cbuffer directly with an excessively high register number. +// expected-error@+1 {{register number should not exceed 4294967295}} +cbuffer MyCB : register(b9995294967294) { + float F[4]; + int I[10]; +}; + +struct MySRV { + __hlsl_resource_t [[hlsl::resource_class(SRV)]] x; +}; + +struct MySampler { + __hlsl_resource_t [[hlsl::resource_class(Sampler)]] x; +}; + +struct MyUAV { + __hlsl_resource_t [[hlsl::resource_class(UAV)]] x; +}; + +// test that diff erent resource classes don't contribute to the +// maximum limit +struct MyResources { + MySRV TheSRV[10]; // t + MySampler TheSampler[20]; // s + MyUAV TheUAV[40]; // u +}; + +// no failures here, since only the SRV contributes to the count, +// and the count + 10 does not exceed uint32 max. +MyResources M : register(t4294967284); + +// three failures, since each of the three resources exceed the limit +// expected-error@+3 {{register number should not exceed 4294967295}} +// expected-error@+2 {{register number should not exceed 4294967295}} +// expected-error@+1 {{register number should not exceed 4294967295}} +MyResources M2 : register(t4294967294) : register(s4294967294) : register(u4294967294); + +// one failure here, just because the final UAV exceeds the limit. +// expected-error@+1 {{register number should not exceed 4294967295}} +MyResources M3 : register(t2) : register(s3) : register(u4294967280); + + +// expected-error@+1 {{register number should be an integer}} +RWBuffer<float> Buf3[10][10] : register(ud); + +// this should work +RWBuffer<float> GoodBuf : register(u4294967295); + +// no errors expected, all 100 register numbers are occupied here +RWBuffer<float> GoodBufArray[10][10] : register(u4294967194); + + _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
