From 2f0ddb7d339f307d81e462ea80ed236db22d55c3 Mon Sep 17 00:00:00 2001 From: Marcos Maronas Date: Wed, 24 Jun 2026 08:40:22 -0500 Subject: [PATCH 1/2] Add additional guardrails against unsigned underflows caused by wrong WordCount. --- lib/SPIRV/libSPIRV/SPIRVDecorate.h | 5 +++++ lib/SPIRV/libSPIRV/SPIRVEntry.h | 5 +++++ lib/SPIRV/libSPIRV/SPIRVFnVar.h | 3 +++ lib/SPIRV/libSPIRV/SPIRVInstruction.h | 7 +++++++ lib/SPIRV/libSPIRV/SPIRVModule.cpp | 9 +++++++++ lib/SPIRV/libSPIRV/SPIRVType.h | 4 ++++ lib/SPIRV/libSPIRV/SPIRVValue.h | 4 ++++ .../invalid-wordcount-aliasscopedecl.spt | 12 ++++++++++++ .../invalid-wordcount-asmcallintel.spt | 12 ++++++++++++ .../invalid-wordcount-branchconditional.spt | 0 .../invalid-wordcount-compositeconstruct.spt | 12 ++++++++++++ ...nvalid-wordcount-conditionalcopyobjectintel.spt | 12 ++++++++++++ ...nvalid-wordcount-conditionalentrypointintel.spt | 13 +++++++++++++ .../invalid-wordcount-constant.spt | 13 +++++++++++++ .../invalid-wordcount-constantcomposite.spt | 0 .../invalid-wordcount-copymemory.spt | 12 ++++++++++++ .../invalid-wordcount-copymemorysized.spt | 12 ++++++++++++ .../invalid-wordcount-decorate.spt} | 0 .../invalid-wordcount-decorateid.spt | 12 ++++++++++++ .../invalid-wordcount-entrypoint.spt | 0 .../invalid-wordcount-functioncall.spt | 0 .../invalid-wordcount-groupdecorate.spt | 0 .../invalid-wordcount-groupmemberdecorate.spt | 12 ++++++++++++ .../invalid-wordcount-insttemplate.spt | 14 ++++++++++++++ .../invalid-wordcount/invalid-wordcount-load.spt | 12 ++++++++++++ .../invalid-wordcount-loopcontrolintel.spt | 12 ++++++++++++ .../invalid-wordcount-loopmerge.spt | 13 +++++++++++++ .../invalid-wordcount-memberdecorate.spt | 12 ++++++++++++ .../invalid-wordcount/invalid-wordcount-phi.spt | 12 ++++++++++++ ...lid-wordcount-specconstantcapabilitiesintel.spt | 13 +++++++++++++ .../invalid-wordcount-specconstanttargetintel.spt | 12 ++++++++++++ .../invalid-wordcount/invalid-wordcount-store.spt | 12 ++++++++++++ .../invalid-wordcount/invalid-wordcount-switch.spt | 12 ++++++++++++ .../invalid-wordcount-typefunction.spt | 12 ++++++++++++ .../invalid-wordcount-typeimage.spt | 13 +++++++++++++ .../invalid-wordcount-typestruct.spt | 0 .../invalid-wordcount-variable.spt | 0 37 files changed, 308 insertions(+) create mode 100644 test/negative/invalid-wordcount/invalid-wordcount-aliasscopedecl.spt create mode 100644 test/negative/invalid-wordcount/invalid-wordcount-asmcallintel.spt rename test/negative/{ => invalid-wordcount}/invalid-wordcount-branchconditional.spt (100%) create mode 100644 test/negative/invalid-wordcount/invalid-wordcount-compositeconstruct.spt create mode 100644 test/negative/invalid-wordcount/invalid-wordcount-conditionalcopyobjectintel.spt create mode 100644 test/negative/invalid-wordcount/invalid-wordcount-conditionalentrypointintel.spt create mode 100644 test/negative/invalid-wordcount/invalid-wordcount-constant.spt rename test/negative/{ => invalid-wordcount}/invalid-wordcount-constantcomposite.spt (100%) create mode 100644 test/negative/invalid-wordcount/invalid-wordcount-copymemory.spt create mode 100644 test/negative/invalid-wordcount/invalid-wordcount-copymemorysized.spt rename test/negative/{invalid-wordcount.spt => invalid-wordcount/invalid-wordcount-decorate.spt} (100%) create mode 100644 test/negative/invalid-wordcount/invalid-wordcount-decorateid.spt rename test/negative/{ => invalid-wordcount}/invalid-wordcount-entrypoint.spt (100%) rename test/negative/{ => invalid-wordcount}/invalid-wordcount-functioncall.spt (100%) rename test/negative/{ => invalid-wordcount}/invalid-wordcount-groupdecorate.spt (100%) create mode 100644 test/negative/invalid-wordcount/invalid-wordcount-groupmemberdecorate.spt create mode 100644 test/negative/invalid-wordcount/invalid-wordcount-insttemplate.spt create mode 100644 test/negative/invalid-wordcount/invalid-wordcount-load.spt create mode 100644 test/negative/invalid-wordcount/invalid-wordcount-loopcontrolintel.spt create mode 100644 test/negative/invalid-wordcount/invalid-wordcount-loopmerge.spt create mode 100644 test/negative/invalid-wordcount/invalid-wordcount-memberdecorate.spt create mode 100644 test/negative/invalid-wordcount/invalid-wordcount-phi.spt create mode 100644 test/negative/invalid-wordcount/invalid-wordcount-specconstantcapabilitiesintel.spt create mode 100644 test/negative/invalid-wordcount/invalid-wordcount-specconstanttargetintel.spt create mode 100644 test/negative/invalid-wordcount/invalid-wordcount-store.spt create mode 100644 test/negative/invalid-wordcount/invalid-wordcount-switch.spt create mode 100644 test/negative/invalid-wordcount/invalid-wordcount-typefunction.spt create mode 100644 test/negative/invalid-wordcount/invalid-wordcount-typeimage.spt rename test/negative/{ => invalid-wordcount}/invalid-wordcount-typestruct.spt (100%) rename test/negative/{ => invalid-wordcount}/invalid-wordcount-variable.spt (100%) diff --git a/lib/SPIRV/libSPIRV/SPIRVDecorate.h b/lib/SPIRV/libSPIRV/SPIRVDecorate.h index b9aea52ee1..bd1e720a00 100644 --- a/lib/SPIRV/libSPIRV/SPIRVDecorate.h +++ b/lib/SPIRV/libSPIRV/SPIRVDecorate.h @@ -125,6 +125,7 @@ class SPIRVDecorate : public SPIRVDecorateGeneric { public: static const Op OC = OpDecorate; static const SPIRVWord FixedWC = 3; + SPIRVWord getFixedWordCount() const override { return FixedWC; } // Complete constructor for decorations without literals SPIRVDecorate(Decoration TheDec, SPIRVEntry *TheTarget) : SPIRVDecorateGeneric(OC, 3, TheDec, TheTarget) {} @@ -240,6 +241,7 @@ class SPIRVDecorateId : public SPIRVDecorateGeneric { public: static const Op OC = OpDecorateId; static const SPIRVWord FixedWC = 3; + SPIRVWord getFixedWordCount() const override { return FixedWC; } // Complete constructor for decorations with one id operand SPIRVDecorateId(Decoration TheDec, SPIRVEntry *TheTarget, SPIRVId V) : SPIRVDecorateGeneric(OC, 4, TheDec, TheTarget, V) {} @@ -330,6 +332,7 @@ class SPIRVMemberDecorate : public SPIRVDecorateGeneric { public: static const Op OC = OpMemberDecorate; static const SPIRVWord FixedWC = 4; + SPIRVWord getFixedWordCount() const override { return FixedWC; } // Complete constructor for decorations without literals SPIRVMemberDecorate(Decoration TheDec, SPIRVWord Member, SPIRVEntry *TheTarget) @@ -447,6 +450,8 @@ class SPIRVGroupDecorateGeneric : public SPIRVEntryNoIdGeneric { SPIRVCK(WordCount >= FixedWC, InvalidWordCount, ""); Targets.resize(WordCount - FixedWC); } + + SPIRVWord getFixedWordCount() const override { return FixedWC; } virtual void decorateTargets() = 0; _SPIRV_DCL_ENCDEC protected: diff --git a/lib/SPIRV/libSPIRV/SPIRVEntry.h b/lib/SPIRV/libSPIRV/SPIRVEntry.h index 3439b10981..7e4f277beb 100644 --- a/lib/SPIRV/libSPIRV/SPIRVEntry.h +++ b/lib/SPIRV/libSPIRV/SPIRVEntry.h @@ -380,6 +380,10 @@ class SPIRVEntry { /// its variable sized member before decoding the remaining words. virtual void setWordCount(SPIRVWord TheWordCount); + /// Return the minimum valid WordCount (1, the OpCode). Other entries will + /// override to return their respective fixed word counts. + virtual SPIRVWord getFixedWordCount() const { return 1; } + /// Create an empty SPIRV object by op code, e.g. OpTypeInt creates /// SPIRVTypeInt. static SPIRVEntry *create(Op); @@ -541,6 +545,7 @@ class SPIRVAnnotation : public SPIRVAnnotationGeneric { class SPIRVEntryPoint : public SPIRVAnnotation { public: static const SPIRVWord FixedWC = 4; + SPIRVWord getFixedWordCount() const override { return FixedWC; } SPIRVEntryPoint(SPIRVModule *TheModule, SPIRVExecutionModelKind, SPIRVId TheId, const std::string &TheName, std::vector Variables); diff --git a/lib/SPIRV/libSPIRV/SPIRVFnVar.h b/lib/SPIRV/libSPIRV/SPIRVFnVar.h index 7e1950cc94..8f4306f4f7 100644 --- a/lib/SPIRV/libSPIRV/SPIRVFnVar.h +++ b/lib/SPIRV/libSPIRV/SPIRVFnVar.h @@ -51,6 +51,7 @@ bool specializeFnVariants(SPIRVModule *BM, std::string &ErrMsg); class SPIRVConditionalEntryPointINTEL : public SPIRVAnnotation { public: static const SPIRVWord FixedWC = 5; + SPIRVWord getFixedWordCount() const override { return FixedWC; } SPIRVConditionalEntryPointINTEL(SPIRVModule *TheModule, SPIRVId Condition, SPIRVExecutionModelKind TheExecModel, SPIRVId TheId, const std::string &TheName, @@ -258,6 +259,7 @@ class SPIRVSpecConstantTargetINTEL : public SPIRVValue { Features.resize(WordCount - FixedWC); NumWords = WordCount - FixedWC; } + SPIRVWord getFixedWordCount() const override { return FixedWC; } private: unsigned NumWords; @@ -435,6 +437,7 @@ class SPIRVSpecConstantCapabilitiesINTEL : public SPIRVValue { Capabilities.resize(WordCount - FixedWC); NumWords = WordCount - FixedWC; } + SPIRVWord getFixedWordCount() const override { return FixedWC; } private: unsigned NumWords; diff --git a/lib/SPIRV/libSPIRV/SPIRVInstruction.h b/lib/SPIRV/libSPIRV/SPIRVInstruction.h index 331c87f9f3..8054f039fe 100644 --- a/lib/SPIRV/libSPIRV/SPIRVInstruction.h +++ b/lib/SPIRV/libSPIRV/SPIRVInstruction.h @@ -280,8 +280,14 @@ class SPIRVInstTemplateBase : public SPIRVInstruction { // instructions. updateModuleVersion(); } + SPIRVWord getFixedWordCount() const override { + // OpCode word plus the optional result-type and + // result-id words. Operands beyond this are variable length. + return 1 + (hasType() ? 1 : 0) + (hasId() ? 1 : 0); + } void setWordCount(SPIRVWord TheWordCount) override { SPIRVEntry::setWordCount(TheWordCount); + SPIRVCK(WordCount >= getFixedWordCount(), InvalidWordCount, ""); auto NumOps = WordCount - 1; if (hasId()) --NumOps; @@ -525,6 +531,7 @@ class SPIRVVariableBase : public SPIRVInstruction { SPIRVCK(WordCount >= FixedWC, InvalidWordCount, ""); Initializer.resize(WordCount - FixedWC); } + SPIRVWord getFixedWordCount() const override { return FixedWC; } _SPIRV_DEF_ENCDEC4(Type, Id, StorageClass, Initializer) SPIRVStorageClassKind StorageClass; diff --git a/lib/SPIRV/libSPIRV/SPIRVModule.cpp b/lib/SPIRV/libSPIRV/SPIRVModule.cpp index e3284dc079..f944ea18a7 100644 --- a/lib/SPIRV/libSPIRV/SPIRVModule.cpp +++ b/lib/SPIRV/libSPIRV/SPIRVModule.cpp @@ -2484,6 +2484,15 @@ static SPIRVEntry *parseAndCreateSPIRVEntry(SPIRVWord &WordCount, Op &OpCode, if (Scope && !isModuleScopeAllowedOpCode(OpCode)) { Entry->setScope(Scope); } + // Reject a WordCount below the instruction's minimum before setWordCount(). + if (!M.getErrorLog().checkError(WordCount >= Entry->getFixedWordCount(), + SPIRVEC_InvalidWordCount, + "WordCount is smaller than the instruction's " + "minimum word count")) { + M.setInvalid(); + delete Entry; + return nullptr; + } Entry->setWordCount(WordCount); if (OpCode != OpLine) Entry->setLine(M.getCurrentLine()); diff --git a/lib/SPIRV/libSPIRV/SPIRVType.h b/lib/SPIRV/libSPIRV/SPIRVType.h index a8b690f564..7d8c2b8729 100644 --- a/lib/SPIRV/libSPIRV/SPIRVType.h +++ b/lib/SPIRV/libSPIRV/SPIRVType.h @@ -676,6 +676,7 @@ class SPIRVTypeImage : public SPIRVType { SPIRVCK(WordCount >= FixedWC, InvalidWordCount, ""); Acc.resize(WordCount - FixedWC); } + SPIRVWord getFixedWordCount() const override { return FixedWC; } private: SPIRVId SampledType; @@ -800,6 +801,8 @@ class SPIRVTypeStruct : public SPIRVType { MemberTypeIdVec.resize(WordCount - FixedWC); } + SPIRVWord getFixedWordCount() const override { return FixedWC; } + // TODO: Should we attach operands of continued instructions as well? std::vector getNonLiteralOperands() const override { std::vector Operands(MemberTypeIdVec.size()); @@ -880,6 +883,7 @@ class SPIRVTypeFunction : public SPIRVType { SPIRVCK(WordCount >= FixedWC, InvalidWordCount, ""); ParamTypeIdVec.resize(WordCount - FixedWC); } + SPIRVWord getFixedWordCount() const override { return FixedWC; } void validate() const override { SPIRVEntry::validate(); ReturnType->validate(); diff --git a/lib/SPIRV/libSPIRV/SPIRVValue.h b/lib/SPIRV/libSPIRV/SPIRVValue.h index 8e859c2a47..1ca2c85d85 100644 --- a/lib/SPIRV/libSPIRV/SPIRVValue.h +++ b/lib/SPIRV/libSPIRV/SPIRVValue.h @@ -210,8 +210,10 @@ template class SPIRVConstantBase : public SPIRVValue { } void setWordCount(SPIRVWord WordCount) override { SPIRVValue::setWordCount(WordCount); + SPIRVCK(WordCount >= FixedWC, InvalidWordCount, ""); NumWords = WordCount - FixedWC; } + SPIRVWord getFixedWordCount() const override { return FixedWC; } void decode(std::istream &I) override { getDecoder(I) >> Type >> Id; Words.resize(NumWords); @@ -372,6 +374,8 @@ template class SPIRVConstantCompositeBase : public SPIRVValue { Elements.resize(WordCount - FixedWC); } + SPIRVWord getFixedWordCount() const override { return FixedWC; } + void encode(spv_ostream &O) const override { getEncoder(O) << Type << Id << Elements; } diff --git a/test/negative/invalid-wordcount/invalid-wordcount-aliasscopedecl.spt b/test/negative/invalid-wordcount/invalid-wordcount-aliasscopedecl.spt new file mode 100644 index 0000000000..58c2b2eeeb --- /dev/null +++ b/test/negative/invalid-wordcount/invalid-wordcount-aliasscopedecl.spt @@ -0,0 +1,12 @@ +; OpAliasScopeDeclINTEL minimum = 2 (header + result-id); the scope arguments +; are variable length. WordCount=1 is malformed. +; Test that malformed input is rejected early rather than silently causing +; integer underflow and potential process hang. + +; RUN: not llvm-spirv %s -to-binary -o %t.spv 2>&1 | FileCheck %s +; CHECK: InvalidWordCount: + +119734787 65536 0 10 0 +2 Capability Kernel +3 MemoryModel 1 2 +1 AliasScopeDeclINTEL 1 diff --git a/test/negative/invalid-wordcount/invalid-wordcount-asmcallintel.spt b/test/negative/invalid-wordcount/invalid-wordcount-asmcallintel.spt new file mode 100644 index 0000000000..00607821cf --- /dev/null +++ b/test/negative/invalid-wordcount/invalid-wordcount-asmcallintel.spt @@ -0,0 +1,12 @@ +; OpAsmCallINTEL minimum = 4 (header + result-type + result-id + asm-target); +; the arguments are variable length. WordCount=3 is malformed. +; Test that malformed input is rejected early rather than silently causing +; integer underflow and potential process hang. + +; RUN: not llvm-spirv %s -to-binary -o %t.spv 2>&1 | FileCheck %s +; CHECK: InvalidWordCount: + +119734787 65536 0 10 0 +2 Capability Kernel +3 MemoryModel 1 2 +3 AsmCallINTEL 1 2 3 diff --git a/test/negative/invalid-wordcount-branchconditional.spt b/test/negative/invalid-wordcount/invalid-wordcount-branchconditional.spt similarity index 100% rename from test/negative/invalid-wordcount-branchconditional.spt rename to test/negative/invalid-wordcount/invalid-wordcount-branchconditional.spt diff --git a/test/negative/invalid-wordcount/invalid-wordcount-compositeconstruct.spt b/test/negative/invalid-wordcount/invalid-wordcount-compositeconstruct.spt new file mode 100644 index 0000000000..fafa9acb04 --- /dev/null +++ b/test/negative/invalid-wordcount/invalid-wordcount-compositeconstruct.spt @@ -0,0 +1,12 @@ +; OpCompositeConstruct minimum = 3 (header + result-type + result-id); the +; constituents are variable length. WordCount=2 is malformed. +; Test that malformed input is rejected early rather than silently causing +; integer underflow and potential process hang. + +; RUN: not llvm-spirv %s -to-binary -o %t.spv 2>&1 | FileCheck %s +; CHECK: InvalidWordCount: + +119734787 65536 0 10 0 +2 Capability Kernel +3 MemoryModel 1 2 +2 CompositeConstruct 1 2 diff --git a/test/negative/invalid-wordcount/invalid-wordcount-conditionalcopyobjectintel.spt b/test/negative/invalid-wordcount/invalid-wordcount-conditionalcopyobjectintel.spt new file mode 100644 index 0000000000..710b6c76bf --- /dev/null +++ b/test/negative/invalid-wordcount/invalid-wordcount-conditionalcopyobjectintel.spt @@ -0,0 +1,12 @@ +; OpConditionalCopyObjectINTEL minimum = 3 (header + result-type + result-id); +; the condition/operand pairs are variable length. WordCount=2 is malformed. +; Test that malformed input is rejected early rather than silently causing +; integer underflow and potential process hang. + +; RUN: not llvm-spirv %s -to-binary -o %t.spv 2>&1 | FileCheck %s +; CHECK: InvalidWordCount: + +119734787 65536 0 10 0 +2 Capability Kernel +3 MemoryModel 1 2 +2 ConditionalCopyObjectINTEL 1 2 diff --git a/test/negative/invalid-wordcount/invalid-wordcount-conditionalentrypointintel.spt b/test/negative/invalid-wordcount/invalid-wordcount-conditionalentrypointintel.spt new file mode 100644 index 0000000000..417ec51479 --- /dev/null +++ b/test/negative/invalid-wordcount/invalid-wordcount-conditionalentrypointintel.spt @@ -0,0 +1,13 @@ +; OpConditionalEntryPointINTEL minimum = 5 (header + condition + execution-model +; + entry-point + name); the interface ids are variable length. WordCount=4 is +; malformed. +; Test that malformed input is rejected early rather than silently causing +; integer underflow and potential process hang. + +; RUN: not llvm-spirv %s -to-binary -o %t.spv 2>&1 | FileCheck %s +; CHECK: InvalidWordCount: + +119734787 65536 0 10 0 +2 Capability Kernel +3 MemoryModel 1 2 +4 ConditionalEntryPointINTEL 1 6 2 3 diff --git a/test/negative/invalid-wordcount/invalid-wordcount-constant.spt b/test/negative/invalid-wordcount/invalid-wordcount-constant.spt new file mode 100644 index 0000000000..a32909fa0f --- /dev/null +++ b/test/negative/invalid-wordcount/invalid-wordcount-constant.spt @@ -0,0 +1,13 @@ +; OpConstant minimum = 3 (header + result-type + result-id); the value words +; are variable length. WordCount=2 is malformed. +; Test that malformed input is rejected early rather than silently causing +; integer underflow and potential process hang. + +; RUN: not llvm-spirv %s -to-binary -o %t.spv 2>&1 | FileCheck %s +; CHECK: InvalidWordCount: + +119734787 65536 0 6 0 +2 Capability Kernel +3 MemoryModel 1 2 +4 TypeInt 3 32 0 +2 Constant 3 5 diff --git a/test/negative/invalid-wordcount-constantcomposite.spt b/test/negative/invalid-wordcount/invalid-wordcount-constantcomposite.spt similarity index 100% rename from test/negative/invalid-wordcount-constantcomposite.spt rename to test/negative/invalid-wordcount/invalid-wordcount-constantcomposite.spt diff --git a/test/negative/invalid-wordcount/invalid-wordcount-copymemory.spt b/test/negative/invalid-wordcount/invalid-wordcount-copymemory.spt new file mode 100644 index 0000000000..f7d8dfb3f9 --- /dev/null +++ b/test/negative/invalid-wordcount/invalid-wordcount-copymemory.spt @@ -0,0 +1,12 @@ +; OpCopyMemory minimum = 3 (header + target + source); the optional +; memory-access operands are variable length. WordCount=2 is malformed. +; Test that malformed input is rejected early rather than silently causing +; integer underflow and potential process hang. + +; RUN: not llvm-spirv %s -to-binary -o %t.spv 2>&1 | FileCheck %s +; CHECK: InvalidWordCount: + +119734787 65536 0 10 0 +2 Capability Kernel +3 MemoryModel 1 2 +2 CopyMemory 1 2 diff --git a/test/negative/invalid-wordcount/invalid-wordcount-copymemorysized.spt b/test/negative/invalid-wordcount/invalid-wordcount-copymemorysized.spt new file mode 100644 index 0000000000..cfb906a144 --- /dev/null +++ b/test/negative/invalid-wordcount/invalid-wordcount-copymemorysized.spt @@ -0,0 +1,12 @@ +; OpCopyMemorySized minimum = 4 (header + target + source + size); the optional +; memory-access operands are variable length. WordCount=3 is malformed. +; Test that malformed input is rejected early rather than silently causing +; integer underflow and potential process hang. + +; RUN: not llvm-spirv %s -to-binary -o %t.spv 2>&1 | FileCheck %s +; CHECK: InvalidWordCount: + +119734787 65536 0 10 0 +2 Capability Kernel +3 MemoryModel 1 2 +3 CopyMemorySized 1 2 3 diff --git a/test/negative/invalid-wordcount.spt b/test/negative/invalid-wordcount/invalid-wordcount-decorate.spt similarity index 100% rename from test/negative/invalid-wordcount.spt rename to test/negative/invalid-wordcount/invalid-wordcount-decorate.spt diff --git a/test/negative/invalid-wordcount/invalid-wordcount-decorateid.spt b/test/negative/invalid-wordcount/invalid-wordcount-decorateid.spt new file mode 100644 index 0000000000..acafa93834 --- /dev/null +++ b/test/negative/invalid-wordcount/invalid-wordcount-decorateid.spt @@ -0,0 +1,12 @@ +; OpDecorateId minimum = 3 (header + target + decoration); the decoration +; literals are variable length. WordCount=2 is malformed. +; Test that malformed input is rejected early rather than silently causing +; integer underflow and potential process hang. + +; RUN: not llvm-spirv %s -to-binary -o %t.spv 2>&1 | FileCheck %s +; CHECK: InvalidWordCount: + +119734787 65536 0 10 0 +2 Capability Kernel +3 MemoryModel 1 2 +2 DecorateId 1 2 diff --git a/test/negative/invalid-wordcount-entrypoint.spt b/test/negative/invalid-wordcount/invalid-wordcount-entrypoint.spt similarity index 100% rename from test/negative/invalid-wordcount-entrypoint.spt rename to test/negative/invalid-wordcount/invalid-wordcount-entrypoint.spt diff --git a/test/negative/invalid-wordcount-functioncall.spt b/test/negative/invalid-wordcount/invalid-wordcount-functioncall.spt similarity index 100% rename from test/negative/invalid-wordcount-functioncall.spt rename to test/negative/invalid-wordcount/invalid-wordcount-functioncall.spt diff --git a/test/negative/invalid-wordcount-groupdecorate.spt b/test/negative/invalid-wordcount/invalid-wordcount-groupdecorate.spt similarity index 100% rename from test/negative/invalid-wordcount-groupdecorate.spt rename to test/negative/invalid-wordcount/invalid-wordcount-groupdecorate.spt diff --git a/test/negative/invalid-wordcount/invalid-wordcount-groupmemberdecorate.spt b/test/negative/invalid-wordcount/invalid-wordcount-groupmemberdecorate.spt new file mode 100644 index 0000000000..85c25c1a29 --- /dev/null +++ b/test/negative/invalid-wordcount/invalid-wordcount-groupmemberdecorate.spt @@ -0,0 +1,12 @@ +; OpGroupMemberDecorate minimum = 2 (header + decoration-group); the +; target/member pairs are variable length. WordCount=1 is malformed. +; Test that malformed input is rejected early rather than silently causing +; integer underflow and potential process hang. + +; RUN: not llvm-spirv %s -to-binary -o %t.spv 2>&1 | FileCheck %s +; CHECK: InvalidWordCount: + +119734787 65536 0 10 0 +2 Capability Kernel +3 MemoryModel 1 2 +1 GroupMemberDecorate 1 diff --git a/test/negative/invalid-wordcount/invalid-wordcount-insttemplate.spt b/test/negative/invalid-wordcount/invalid-wordcount-insttemplate.spt new file mode 100644 index 0000000000..2dea1c763b --- /dev/null +++ b/test/negative/invalid-wordcount/invalid-wordcount-insttemplate.spt @@ -0,0 +1,14 @@ +; Generic SPIRVInstTemplate decode path: minimum = 3 for an instruction with +; both a result type and a result id (header + result-type + result-id); +; operands are variable length. WordCount=2 is malformed. +; Test that malformed input is rejected early rather than silently causing +; integer underflow and potential process hang. + +; RUN: not llvm-spirv %s -to-binary -o %t.spv 2>&1 | FileCheck %s +; CHECK: InvalidWordCount: + +119734787 65536 0 6 0 +2 Capability Kernel +3 MemoryModel 1 2 +4 TypeInt 3 32 0 +2 IAdd 3 5 diff --git a/test/negative/invalid-wordcount/invalid-wordcount-load.spt b/test/negative/invalid-wordcount/invalid-wordcount-load.spt new file mode 100644 index 0000000000..dad0a021fc --- /dev/null +++ b/test/negative/invalid-wordcount/invalid-wordcount-load.spt @@ -0,0 +1,12 @@ +; OpLoad minimum = 4 (header + result-type + result-id + pointer); the optional +; memory-access operands are variable length. WordCount=3 is malformed. +; Test that malformed input is rejected early rather than silently causing +; integer underflow and potential process hang. + +; RUN: not llvm-spirv %s -to-binary -o %t.spv 2>&1 | FileCheck %s +; CHECK: InvalidWordCount: + +119734787 65536 0 10 0 +2 Capability Kernel +3 MemoryModel 1 2 +3 Load 1 2 3 diff --git a/test/negative/invalid-wordcount/invalid-wordcount-loopcontrolintel.spt b/test/negative/invalid-wordcount/invalid-wordcount-loopcontrolintel.spt new file mode 100644 index 0000000000..1f8ee1cb83 --- /dev/null +++ b/test/negative/invalid-wordcount/invalid-wordcount-loopcontrolintel.spt @@ -0,0 +1,12 @@ +; OpLoopControlINTEL minimum = 2 (header + loop-control); the loop-control +; parameters are variable length. WordCount=1 is malformed. +; Test that malformed input is rejected early rather than silently causing +; integer underflow and potential process hang. + +; RUN: not llvm-spirv %s -to-binary -o %t.spv 2>&1 | FileCheck %s +; CHECK: InvalidWordCount: + +119734787 65536 0 10 0 +2 Capability Kernel +3 MemoryModel 1 2 +1 LoopControlINTEL 0 diff --git a/test/negative/invalid-wordcount/invalid-wordcount-loopmerge.spt b/test/negative/invalid-wordcount/invalid-wordcount-loopmerge.spt new file mode 100644 index 0000000000..b7a18566ca --- /dev/null +++ b/test/negative/invalid-wordcount/invalid-wordcount-loopmerge.spt @@ -0,0 +1,13 @@ +; OpLoopMerge minimum = 4 (header + merge-block + continue-target + +; loop-control); the loop-control parameters are variable length. WordCount=3 +; is malformed. +; Test that malformed input is rejected early rather than silently causing +; integer underflow and potential process hang. + +; RUN: not llvm-spirv %s -to-binary -o %t.spv 2>&1 | FileCheck %s +; CHECK: InvalidWordCount: + +119734787 65536 0 10 0 +2 Capability Kernel +3 MemoryModel 1 2 +3 LoopMerge 1 2 0 diff --git a/test/negative/invalid-wordcount/invalid-wordcount-memberdecorate.spt b/test/negative/invalid-wordcount/invalid-wordcount-memberdecorate.spt new file mode 100644 index 0000000000..71da0e5d37 --- /dev/null +++ b/test/negative/invalid-wordcount/invalid-wordcount-memberdecorate.spt @@ -0,0 +1,12 @@ +; OpMemberDecorate minimum = 4 (header + struct-type + member + decoration); +; the decoration literals are variable length. WordCount=3 is malformed. +; Test that malformed input is rejected early rather than silently causing +; integer underflow and potential process hang. + +; RUN: not llvm-spirv %s -to-binary -o %t.spv 2>&1 | FileCheck %s +; CHECK: InvalidWordCount: + +119734787 65536 0 10 0 +2 Capability Kernel +3 MemoryModel 1 2 +3 MemberDecorate 1 0 2 diff --git a/test/negative/invalid-wordcount/invalid-wordcount-phi.spt b/test/negative/invalid-wordcount/invalid-wordcount-phi.spt new file mode 100644 index 0000000000..bacb6f81f9 --- /dev/null +++ b/test/negative/invalid-wordcount/invalid-wordcount-phi.spt @@ -0,0 +1,12 @@ +; OpPhi minimum = 3 (header + result-type + result-id); the variable/parent +; pairs are variable length. WordCount=2 is malformed. +; Test that malformed input is rejected early rather than silently causing +; integer underflow and potential process hang. + +; RUN: not llvm-spirv %s -to-binary -o %t.spv 2>&1 | FileCheck %s +; CHECK: InvalidWordCount: + +119734787 65536 0 10 0 +2 Capability Kernel +3 MemoryModel 1 2 +2 Phi 1 2 diff --git a/test/negative/invalid-wordcount/invalid-wordcount-specconstantcapabilitiesintel.spt b/test/negative/invalid-wordcount/invalid-wordcount-specconstantcapabilitiesintel.spt new file mode 100644 index 0000000000..a2bc8039e3 --- /dev/null +++ b/test/negative/invalid-wordcount/invalid-wordcount-specconstantcapabilitiesintel.spt @@ -0,0 +1,13 @@ +; OpSpecConstantCapabilitiesINTEL minimum = 3 (header + result-type + +; result-id); the capability operands are variable length. WordCount=2 is +; malformed. +; Test that malformed input is rejected early rather than silently causing +; integer underflow and potential process hang. + +; RUN: not llvm-spirv %s -to-binary -o %t.spv 2>&1 | FileCheck %s +; CHECK: InvalidWordCount: + +119734787 65536 0 10 0 +2 Capability Kernel +3 MemoryModel 1 2 +2 SpecConstantCapabilitiesINTEL 1 2 diff --git a/test/negative/invalid-wordcount/invalid-wordcount-specconstanttargetintel.spt b/test/negative/invalid-wordcount/invalid-wordcount-specconstanttargetintel.spt new file mode 100644 index 0000000000..987ee0c57a --- /dev/null +++ b/test/negative/invalid-wordcount/invalid-wordcount-specconstanttargetintel.spt @@ -0,0 +1,12 @@ +; OpSpecConstantTargetINTEL minimum = 4 (header + result-type + result-id + +; target); the feature operands are variable length. WordCount=3 is malformed. +; Test that malformed input is rejected early rather than silently causing +; integer underflow and potential process hang. + +; RUN: not llvm-spirv %s -to-binary -o %t.spv 2>&1 | FileCheck %s +; CHECK: InvalidWordCount: + +119734787 65536 0 10 0 +2 Capability Kernel +3 MemoryModel 1 2 +3 SpecConstantTargetINTEL 1 2 3 diff --git a/test/negative/invalid-wordcount/invalid-wordcount-store.spt b/test/negative/invalid-wordcount/invalid-wordcount-store.spt new file mode 100644 index 0000000000..b968397963 --- /dev/null +++ b/test/negative/invalid-wordcount/invalid-wordcount-store.spt @@ -0,0 +1,12 @@ +; OpStore minimum = 3 (header + pointer + object); the optional memory-access +; operands are variable length. WordCount=2 is malformed. +; Test that malformed input is rejected early rather than silently causing +; integer underflow and potential process hang. + +; RUN: not llvm-spirv %s -to-binary -o %t.spv 2>&1 | FileCheck %s +; CHECK: InvalidWordCount: + +119734787 65536 0 10 0 +2 Capability Kernel +3 MemoryModel 1 2 +2 Store 1 2 diff --git a/test/negative/invalid-wordcount/invalid-wordcount-switch.spt b/test/negative/invalid-wordcount/invalid-wordcount-switch.spt new file mode 100644 index 0000000000..2d56dae7e1 --- /dev/null +++ b/test/negative/invalid-wordcount/invalid-wordcount-switch.spt @@ -0,0 +1,12 @@ +; OpSwitch minimum = 3 (header + selector + default); the literal/label target +; pairs are variable length. WordCount=2 is malformed. +; Test that malformed input is rejected early rather than silently causing +; integer underflow and potential process hang. + +; RUN: not llvm-spirv %s -to-binary -o %t.spv 2>&1 | FileCheck %s +; CHECK: InvalidWordCount: + +119734787 65536 0 10 0 +2 Capability Kernel +3 MemoryModel 1 2 +2 Switch 1 2 diff --git a/test/negative/invalid-wordcount/invalid-wordcount-typefunction.spt b/test/negative/invalid-wordcount/invalid-wordcount-typefunction.spt new file mode 100644 index 0000000000..ecf01b10d4 --- /dev/null +++ b/test/negative/invalid-wordcount/invalid-wordcount-typefunction.spt @@ -0,0 +1,12 @@ +; OpTypeFunction minimum = 3 (header + result-id + return-type); the parameter +; types are variable length. WordCount=2 is malformed. +; Test that malformed input is rejected early rather than silently causing +; integer underflow and potential process hang. + +; RUN: not llvm-spirv %s -to-binary -o %t.spv 2>&1 | FileCheck %s +; CHECK: InvalidWordCount: + +119734787 65536 0 10 0 +2 Capability Kernel +3 MemoryModel 1 2 +2 TypeFunction 1 2 diff --git a/test/negative/invalid-wordcount/invalid-wordcount-typeimage.spt b/test/negative/invalid-wordcount/invalid-wordcount-typeimage.spt new file mode 100644 index 0000000000..88ce3cf861 --- /dev/null +++ b/test/negative/invalid-wordcount/invalid-wordcount-typeimage.spt @@ -0,0 +1,13 @@ +; OpTypeImage minimum = 9 (header + result-id + sampled-type + dim + depth + +; arrayed + ms + sampled + format); an optional access qualifier may follow. +; WordCount=8 is malformed. +; Test that malformed input is rejected early rather than silently causing +; integer underflow and potential process hang. + +; RUN: not llvm-spirv %s -to-binary -o %t.spv 2>&1 | FileCheck %s +; CHECK: InvalidWordCount: + +119734787 65536 0 10 0 +2 Capability Kernel +3 MemoryModel 1 2 +8 TypeImage 1 2 0 0 0 0 0 diff --git a/test/negative/invalid-wordcount-typestruct.spt b/test/negative/invalid-wordcount/invalid-wordcount-typestruct.spt similarity index 100% rename from test/negative/invalid-wordcount-typestruct.spt rename to test/negative/invalid-wordcount/invalid-wordcount-typestruct.spt diff --git a/test/negative/invalid-wordcount-variable.spt b/test/negative/invalid-wordcount/invalid-wordcount-variable.spt similarity index 100% rename from test/negative/invalid-wordcount-variable.spt rename to test/negative/invalid-wordcount/invalid-wordcount-variable.spt From 059eaa7ea4f557cf8c8763d2418a987feff25de3 Mon Sep 17 00:00:00 2001 From: Marcos Maronas Date: Wed, 8 Jul 2026 08:45:55 -0500 Subject: [PATCH 2/2] Address clang-tidy issues. --- lib/SPIRV/libSPIRV/SPIRVFnVar.h | 4 ++-- lib/SPIRV/libSPIRV/SPIRVInstruction.h | 2 +- lib/SPIRV/libSPIRV/SPIRVType.h | 4 ++-- lib/SPIRV/libSPIRV/SPIRVValue.h | 5 ++--- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/SPIRV/libSPIRV/SPIRVFnVar.h b/lib/SPIRV/libSPIRV/SPIRVFnVar.h index 8f4306f4f7..5d1d9fa89c 100644 --- a/lib/SPIRV/libSPIRV/SPIRVFnVar.h +++ b/lib/SPIRV/libSPIRV/SPIRVFnVar.h @@ -250,6 +250,7 @@ class SPIRVSpecConstantTargetINTEL : public SPIRVValue { return Res; } + SPIRVWord getFixedWordCount() const override { return FixedWC; } protected: _SPIRV_DEF_ENCDEC4(Type, Id, Target, Features); @@ -259,7 +260,6 @@ class SPIRVSpecConstantTargetINTEL : public SPIRVValue { Features.resize(WordCount - FixedWC); NumWords = WordCount - FixedWC; } - SPIRVWord getFixedWordCount() const override { return FixedWC; } private: unsigned NumWords; @@ -416,6 +416,7 @@ class SPIRVSpecConstantCapabilitiesINTEL : public SPIRVValue { return Res; } + SPIRVWord getFixedWordCount() const override { return FixedWC; } protected: _SPIRV_DEF_ENCDEC3(Type, Id, Capabilities); @@ -437,7 +438,6 @@ class SPIRVSpecConstantCapabilitiesINTEL : public SPIRVValue { Capabilities.resize(WordCount - FixedWC); NumWords = WordCount - FixedWC; } - SPIRVWord getFixedWordCount() const override { return FixedWC; } private: unsigned NumWords; diff --git a/lib/SPIRV/libSPIRV/SPIRVInstruction.h b/lib/SPIRV/libSPIRV/SPIRVInstruction.h index 8054f039fe..c4947ed6ba 100644 --- a/lib/SPIRV/libSPIRV/SPIRVInstruction.h +++ b/lib/SPIRV/libSPIRV/SPIRVInstruction.h @@ -518,6 +518,7 @@ class SPIRVVariableBase : public SPIRVInstruction { return std::vector(1, V); return std::vector(); } + SPIRVWord getFixedWordCount() const override { return FixedWC; } protected: void validate() const override { @@ -531,7 +532,6 @@ class SPIRVVariableBase : public SPIRVInstruction { SPIRVCK(WordCount >= FixedWC, InvalidWordCount, ""); Initializer.resize(WordCount - FixedWC); } - SPIRVWord getFixedWordCount() const override { return FixedWC; } _SPIRV_DEF_ENCDEC4(Type, Id, StorageClass, Initializer) SPIRVStorageClassKind StorageClass; diff --git a/lib/SPIRV/libSPIRV/SPIRVType.h b/lib/SPIRV/libSPIRV/SPIRVType.h index 7d8c2b8729..ff1c38227a 100644 --- a/lib/SPIRV/libSPIRV/SPIRVType.h +++ b/lib/SPIRV/libSPIRV/SPIRVType.h @@ -655,6 +655,7 @@ class SPIRVTypeImage : public SPIRVType { std::vector getNonLiteralOperands() const override { return std::vector(1, get(SampledType)); } + SPIRVWord getFixedWordCount() const override { return FixedWC; } protected: _SPIRV_DEF_ENCDEC9(Id, SampledType, Desc.Dim, Desc.Depth, Desc.Arrayed, @@ -676,7 +677,6 @@ class SPIRVTypeImage : public SPIRVType { SPIRVCK(WordCount >= FixedWC, InvalidWordCount, ""); Acc.resize(WordCount - FixedWC); } - SPIRVWord getFixedWordCount() const override { return FixedWC; } private: SPIRVId SampledType; @@ -875,6 +875,7 @@ class SPIRVTypeFunction : public SPIRVType { Operands.push_back(getEntry(I)); return Operands; } + SPIRVWord getFixedWordCount() const override { return FixedWC; } protected: _SPIRV_DEF_ENCDEC3(Id, ReturnType, ParamTypeIdVec) @@ -883,7 +884,6 @@ class SPIRVTypeFunction : public SPIRVType { SPIRVCK(WordCount >= FixedWC, InvalidWordCount, ""); ParamTypeIdVec.resize(WordCount - FixedWC); } - SPIRVWord getFixedWordCount() const override { return FixedWC; } void validate() const override { SPIRVEntry::validate(); ReturnType->validate(); diff --git a/lib/SPIRV/libSPIRV/SPIRVValue.h b/lib/SPIRV/libSPIRV/SPIRVValue.h index 1ca2c85d85..b2ff971256 100644 --- a/lib/SPIRV/libSPIRV/SPIRVValue.h +++ b/lib/SPIRV/libSPIRV/SPIRVValue.h @@ -174,6 +174,7 @@ template class SPIRVConstantBase : public SPIRVValue { double getDoubleValue() const { return getValue(); } unsigned getNumWords() const { return NumWords; } const std::vector &getSPIRVWords() { return Words; } + SPIRVWord getFixedWordCount() const override { return FixedWC; } protected: constexpr static SPIRVWord FixedWC = 3; @@ -213,7 +214,6 @@ template class SPIRVConstantBase : public SPIRVValue { SPIRVCK(WordCount >= FixedWC, InvalidWordCount, ""); NumWords = WordCount - FixedWC; } - SPIRVWord getFixedWordCount() const override { return FixedWC; } void decode(std::istream &I) override { getDecoder(I) >> Type >> Id; Words.resize(NumWords); @@ -360,6 +360,7 @@ template class SPIRVConstantCompositeBase : public SPIRVValue { for (auto &I : ContinuedInstructions) O << *I; } + SPIRVWord getFixedWordCount() const override { return FixedWC; } protected: void validate() const override { @@ -374,8 +375,6 @@ template class SPIRVConstantCompositeBase : public SPIRVValue { Elements.resize(WordCount - FixedWC); } - SPIRVWord getFixedWordCount() const override { return FixedWC; } - void encode(spv_ostream &O) const override { getEncoder(O) << Type << Id << Elements; }