Index: include/llvm/Analysis/Passes.h =================================================================== --- include/llvm/Analysis/Passes.h +++ include/llvm/Analysis/Passes.h @@ -96,6 +96,13 @@ // FunctionPass *createMemDerefPrinter(); + //===--------------------------------------------------------------------===// + // + // createSceptrePass - This pass checks a program for Spectre variant 1 + // exploits. + // + FunctionPass *createSceptrePass(); + } #endif Index: include/llvm/IR/DiagnosticInfo.h =================================================================== --- include/llvm/IR/DiagnosticInfo.h +++ include/llvm/IR/DiagnosticInfo.h @@ -60,6 +60,7 @@ DK_DebugMetadataInvalid, DK_ISelFallback, DK_SampleProfile, + DK_Sceptre, DK_OptimizationRemark, DK_OptimizationRemarkMissed, DK_OptimizationRemarkAnalysis, @@ -848,6 +849,24 @@ PassName, Fn, Loc, Msg) {} }; +class SceptreRemark : public DiagnosticInfoWithLocationBase { +public: + SceptreRemark(const Function &Fn, const DiagnosticLocation &DL, + DiagnosticSeverity DS) + : DiagnosticInfoWithLocationBase(DK_Sceptre, DS, Fn, DL) { } + + static void diagnoseSceptre(const Function &Fn, Instruction *Vuln, + Instruction *Cmp, Value *V); + + static bool classof(const DiagnosticInfo *DI) { + return DI->getKind() == DK_Sceptre; + } + + Value *V = nullptr; + DISubprogram *Subprogram = nullptr; + void print(DiagnosticPrinter &DP) const override; +}; + /// Diagnostic information for optimization analysis remarks related to /// pointer aliasing. class OptimizationRemarkAnalysisAliasing : public OptimizationRemarkAnalysis { Index: include/llvm/InitializePasses.h =================================================================== --- include/llvm/InitializePasses.h +++ include/llvm/InitializePasses.h @@ -388,6 +388,7 @@ void initializeWriteThinLTOBitcodePass(PassRegistry&); void initializeXRayInstrumentationPass(PassRegistry&); void initializeMIRCanonicalizerPass(PassRegistry &); +void initializeSceptrePass(PassRegistry &); } // end namespace llvm Index: include/llvm/LinkAllPasses.h =================================================================== --- include/llvm/LinkAllPasses.h +++ include/llvm/LinkAllPasses.h @@ -210,6 +210,7 @@ (void) llvm::createFloat2IntPass(); (void) llvm::createEliminateAvailableExternallyPass(); (void) llvm::createScalarizeMaskedMemIntrinPass(); + (void) llvm::createSceptrePass(); (void)new llvm::IntervalPartition(); (void)new llvm::ScalarEvolutionWrapperPass(); Index: lib/Analysis/Analysis.cpp =================================================================== --- lib/Analysis/Analysis.cpp +++ lib/Analysis/Analysis.cpp @@ -81,6 +81,7 @@ initializeLCSSAVerificationPassPass(Registry); initializeMemorySSAWrapperPassPass(Registry); initializeMemorySSAPrinterLegacyPassPass(Registry); + initializeSceptrePass(Registry); } void LLVMInitializeAnalysis(LLVMPassRegistryRef R) { Index: lib/Analysis/CMakeLists.txt =================================================================== --- lib/Analysis/CMakeLists.txt +++ lib/Analysis/CMakeLists.txt @@ -85,6 +85,7 @@ ValueLatticeUtils.cpp ValueTracking.cpp VectorUtils.cpp + Sceptre.cpp ADDITIONAL_HEADER_DIRS ${LLVM_MAIN_INCLUDE_DIR}/llvm/Analysis Index: lib/Analysis/Sceptre.cpp =================================================================== --- lib/Analysis/Sceptre.cpp +++ lib/Analysis/Sceptre.cpp @@ -0,0 +1,526 @@ +//===- Sceptre.cpp - Spectre variant 1 detector Pass ----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file implements Sceptre, a detector for Spectre variant 1 exploits +// (bounds check bypass). The pass recognizes when a bounds-checked integer is +// used as an index to load a value, which is then used in address computation +// for a second load. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Analysis/Passes.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/DiagnosticInfo.h" +#include "llvm/IR/Dominators.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/Instructions.h" +#include "llvm/Pass.h" + +using namespace llvm; + +static cl::opt SceptreShowPHI("sceptre-show-phi", cl::Hidden, + cl::desc("Show vulnerabilities found by following phi nodes"), + cl::init(true)); + +static cl::opt SceptreShowCallEscapes("sceptre-show-call-escapes", + cl::Hidden, cl::desc("Show values escaping via a call"), + cl::init(false)); + +static cl::opt SceptreShowRetEscapes("sceptre-show-ret-escapes", + cl::Hidden, cl::desc("Show values escaping via a return"), + cl::init(false)); + +static cl::opt SceptreFindSecondLoad("sceptre-find-second-load", + cl::Hidden, + cl::desc("Only report vulnerabilities that involve a second load"), + cl::init(true)); + +static cl::opt SceptreAllowSimpleExpr("sceptre-allow-simple-expr", + cl::Hidden, cl::desc("Allow index to be a simple expression"), + cl::init(true)); + +static cl::opt SceptreAllowSelect("sceptre-allow-select", + cl::Hidden, cl::desc("Consider selects to also be vulnerable"), + cl::init(true)); + +namespace { + struct Sceptre : public FunctionPass { + static char ID; + Sceptre() : FunctionPass(ID) { + initializeSceptrePass(*PassRegistry::getPassRegistry()); + } + + void getAnalysisUsage(AnalysisUsage &AU) const override { + AU.addRequired(); + AU.addPreserved(); + } + + bool runOnFunction(Function &F) override; + }; +} + +typedef SmallVector, 4> InsBoolListTy; + +static void findGEPs(Value *V, InsBoolListTy &GEPs, InsBoolListTy &Escapes, + BasicBlock *OutBB, DominatorTree &DT, bool ThroughPN) { + for (User *U : V->users()) + if(Instruction *I = dyn_cast(U)) { + if (BinaryOperator *BO = dyn_cast(I)) { + if (SceptreAllowSimpleExpr && (isa(BO->getOperand(0)) || + isa(BO->getOperand(1)))) + findGEPs(I, GEPs, Escapes, OutBB, DT, ThroughPN); + } else { + switch(I->getOpcode()) { + default: + break; + + case Instruction::GetElementPtr: + GEPs.push_back(std::make_pair(I, ThroughPN)); + break; + + case Instruction::Call: + if (SceptreShowCallEscapes) + Escapes.push_back(std::make_pair(I, ThroughPN)); + break; + + case Instruction::Ret: + if (SceptreShowRetEscapes) + Escapes.push_back(std::make_pair(I, ThroughPN)); + break; + + case Instruction::Trunc: + case Instruction::ZExt: + case Instruction::SExt: + // GEP offset is 64-bit so we may have an extension + findGEPs(I, GEPs, Escapes, OutBB, DT, ThroughPN); + break; + + case Instruction::PHI: + PHINode *PN = dyn_cast(I); + unsigned NumValues = PN->getNumIncomingValues(); + bool FollowPHI = NumValues > 0; + + for (unsigned i = 0, e = NumValues; i != e && FollowPHI; ++i) { + BasicBlock *BB = PN->getIncomingBlock(i); + Value *PV = PN->getIncomingValue(i); + bool Dom = DT.dominates(OutBB, BB); + bool IsV = PV == V; + + FollowPHI &= Dom ^ IsV; + } + if (FollowPHI) + findGEPs(U, GEPs, Escapes, OutBB, DT, true); + break; + } + } + } +} + +static void findLoads(Value *V, InsBoolListTy &Loads, InsBoolListTy &Escapes, + BasicBlock *InBB, DominatorTree &DT, bool ThroughPN) { + for (User *U : V->users()) + if(Instruction *I = dyn_cast(U)) { + switch(I->getOpcode()) { + default: + break; + + case Instruction::Load: + if (ThroughPN || DT.dominates(InBB, I->getParent())) + Loads.push_back(std::make_pair(I, ThroughPN)); + break; + + case Instruction::Call: + if (SceptreShowCallEscapes) + Escapes.push_back(std::make_pair(I, ThroughPN)); + break; + + case Instruction::Ret: + if (SceptreShowRetEscapes) + Escapes.push_back(std::make_pair(I, ThroughPN)); + break; + + // Element address may have pointer cast + case Instruction::BitCast: + findLoads(I, Loads, Escapes, InBB, DT, ThroughPN); + break; + + case Instruction::PHI: + PHINode *PN = dyn_cast(I); + unsigned NumValues = PN->getNumIncomingValues(); + bool FollowPHI = NumValues > 0; + + for (unsigned i = 0, e = NumValues; i != e && FollowPHI; ++i) { + BasicBlock *BB = PN->getIncomingBlock(i); + Value *PV = PN->getIncomingValue(i); + bool Dom = DT.dominates(InBB, BB); + bool IsV = PV == V; + + FollowPHI &= Dom == IsV; + } + if (FollowPHI) + findLoads(U, Loads, Escapes, InBB, DT, true); + break; + } + } +} + +typedef SmallVector InsListTy; + +static void findGEPs(Value *V, InsListTy &GEPs, InsListTy &Escapes) { + for (User *U : V->users()) + if (Instruction *I = dyn_cast(U)) { + if (BinaryOperator *BO = dyn_cast(I)) { + if (SceptreAllowSimpleExpr && (isa(BO->getOperand(0)) || + isa(BO->getOperand(1)))) + findGEPs(I, GEPs, Escapes); + } else { + switch(I->getOpcode()) { + default: + break; + + case Instruction::GetElementPtr: + GEPs.push_back(I); + break; + + case Instruction::Call: + if (SceptreShowCallEscapes) + Escapes.push_back(I); + break; + + case Instruction::Ret: + if (SceptreShowRetEscapes) + Escapes.push_back(I); + break; + + case Instruction::ZExt: + case Instruction::SExt: + case Instruction::Trunc: + findGEPs(I, GEPs, Escapes); + break; + } + } + } +} + +static void findLoads(Value *V, InsListTy &Loads, InsListTy &Escapes) { + for (User *U : V->users()) + if (Instruction *I = dyn_cast(U)) { + switch(I->getOpcode()) { + default: + break; + + case Instruction::Load: + Loads.push_back(I); + break; + + case Instruction::Call: + if (SceptreShowCallEscapes) + Escapes.push_back(I); + break; + + case Instruction::Ret: + if (SceptreShowRetEscapes) + Escapes.push_back(I); + break; + + // Element address may have pointer cast + case Instruction::BitCast: + findLoads(I, Loads, Escapes); + break; + } + } +} + +typedef SmallVector CmpListTy; + +static void scanAndOrTree(Value *V, CmpListTy &Compares) { + if(Instruction *I = dyn_cast(V)) { + if (ICmpInst *Cmp = dyn_cast(I)) { + // icmp operands can be vector or pointer type + if (!Cmp->getOperand(0)->getType()->isIntegerTy()) + return; + + CmpInst::Predicate Pred = Cmp->getUnsignedPredicate(); + if (Pred == CmpInst::ICMP_ULT || Pred == CmpInst::ICMP_UGE || + Pred == CmpInst::ICMP_UGT || Pred == CmpInst::ICMP_ULE) + Compares.push_back(Cmp); + return; + } + switch (I->getOpcode()) { + default: + break; + + case Instruction::And: + case Instruction::Or: + scanAndOrTree(I->getOperand(0), Compares); + scanAndOrTree(I->getOperand(1), Compares); + break; + } + } +} + +static bool checkAndOrTree(Value *V, ICmpInst *Compare) { + if(Instruction *I = dyn_cast(V)) { + if (ICmpInst *Cmp = dyn_cast(I)) + return Compare == Cmp; + + unsigned Opc = I->getOpcode(); + switch (Opc) { + default: + break; + + case Instruction::And: + case Instruction::Or: + if (checkAndOrTree(I->getOperand(0), Compare) || + checkAndOrTree(I->getOperand(1), Compare)) { + CmpInst::Predicate Pred = Compare->getUnsignedPredicate(); + if (Pred == CmpInst::ICMP_ULT || Pred == CmpInst::ICMP_UGT) + return Opc == Instruction::And; + return Opc == Instruction::Or; + } + break; + } + } + return false; +} + +// SimplifyCFG collapses multiple ifs into a series of and/or expressions. +// We first scan the and/or tree to collect candidate compares. These compares +// are then checked to see if the compare must be true within the overall +// expression. +// For in-bounds checks (i.e. i < size), the expression tree must be 'and'. +// e.g. (i < size) && x && (y || z). +// For out-of-bounds checks (i.e. i >= size), the expression tree must be 'or' +// e.g. (i >= size) || x || (y && z) as when it is false, i < size must be +// true. +static void findCompares(Value *V, CmpListTy &Compares) { + CmpListTy ScanCmps; + scanAndOrTree(V, ScanCmps); + + for (auto &Cmp : ScanCmps) + if (checkAndOrTree(V, Cmp)) + Compares.push_back(Cmp); +} + +static Value *getOffset(ICmpInst *Cmp) { + Value *CmpLHS = Cmp->getOperand(0); + Value *CmpRHS = Cmp->getOperand(1); + CmpInst::Predicate Pred = Cmp->getUnsignedPredicate(); + + Value *Offset = Pred == CmpInst::ICMP_ULT || Pred == CmpInst::ICMP_UGE + ? CmpLHS : CmpRHS; + + if (dyn_cast(Offset)) + return nullptr; + + // Compare may have introduced a cast if operand size is different + while (Instruction *I = dyn_cast(Offset)) { + switch(I->getOpcode()) { + default: + return nullptr; + + case Instruction::Trunc: + case Instruction::ZExt: + case Instruction::SExt: + Offset = I->getOperand(0); + break; + } + } + + return Offset; +} + +static void processBranch(BranchInst *BI, Function &F, DominatorTree &DT) { + if (BI->isUnconditional()) + return; + + CmpListTy Compares; + findCompares(BI->getCondition(), Compares); + + for (auto &Cmp : Compares) { + CmpInst::Predicate Pred = Cmp->getUnsignedPredicate(); + unsigned InBBNum = + Pred == CmpInst::ICMP_ULT || Pred == CmpInst::ICMP_UGT ? 0 : 1; + + BasicBlock *InBoundsBB = BI->getSuccessor(InBBNum); + BasicBlock *OutBoundsBB = BI->getSuccessor(1 - InBBNum); + + bool SingleCond = true; + for (pred_iterator PI = pred_begin(InBoundsBB), E = pred_end(InBoundsBB); + PI != E && SingleCond; ++PI) { + BasicBlock *PredBB = *PI; + BranchInst *PBI = dyn_cast(PredBB->getTerminator()); + + SingleCond &= !PBI || PBI->isUnconditional() || PBI == BI; + } + + if(!SingleCond) + continue; + + Value *Offset = getOffset(Cmp); + if(!Offset) + continue; + + InsBoolListTy GEPs; + InsBoolListTy Escapes; + findGEPs(Offset, GEPs, Escapes, OutBoundsBB, DT, false); + + InsBoolListTy Loads; + for (auto &GEP : GEPs) + findLoads(GEP.first, Loads, Escapes, InBoundsBB, DT, GEP.second); + + for (auto &Load : Loads) { + if (!SceptreShowPHI && Load.second) + continue; + + if (SceptreFindSecondLoad) { + InsListTy SecondGEPs; + InsListTy SecondEscapes; + findGEPs(Load.first, SecondGEPs, SecondEscapes); + + InsListTy SecondLoads; + for (auto &GEP : SecondGEPs) + findLoads(GEP, SecondLoads, SecondEscapes); + + // TODO print out diagnostics for the second load(s)/escape(s). + if (SecondLoads.empty() && SecondEscapes.empty()) + continue; + } + SceptreRemark::diagnoseSceptre(F, Load.first, Cmp, Offset); + } + for (auto &Escape : Escapes) { + if (!SceptreShowPHI && Escape.second) + continue; + if (Escape.second || DT.dominates(InBoundsBB, Escape.first->getParent())) + SceptreRemark::diagnoseSceptre(F, Escape.first, Cmp, Offset); + } + } +} + +static bool checkSelectValue(Value *V, Value *Offset, bool &ThroughGEP) { + while(Instruction *I = dyn_cast(V)) { + if (BinaryOperator *BO = dyn_cast(I)) { + if (SceptreAllowSimpleExpr) { + if (isa(BO->getOperand(0))) { + V = BO->getOperand(1); + continue; + } + if (isa(BO->getOperand(1))) { + V = BO->getOperand(0); + continue; + } + } + return false; + } + + if (GetElementPtrInst *GEP = dyn_cast(I)) { + for (auto Idx = GEP->idx_begin(); Idx != GEP->idx_end(); ++Idx) + if (checkSelectValue(*Idx, Offset, ThroughGEP)) { + ThroughGEP = true; + return true; + } + return false; + } + + switch (I->getOpcode()) { + default: + return false; + + case Instruction::BitCast: + case Instruction::ZExt: + case Instruction::SExt: + case Instruction::Trunc: + V = I->getOperand(0); + break; + } + } + return V == Offset; +} + +static void processSelect(SelectInst *SI, Function &F) { + CmpListTy Compares; + findCompares(SI->getCondition(), Compares); + + for (auto &Cmp : Compares) { + Value *Offset = getOffset(Cmp); + if(!Offset) + continue; + + CmpInst::Predicate Pred = Cmp->getUnsignedPredicate(); + Value *InBoundsV; + + if (Pred == CmpInst::ICMP_ULT || Pred == CmpInst::ICMP_UGT) + InBoundsV = SI->getTrueValue(); + else + InBoundsV = SI->getFalseValue(); + + bool ThroughGEP = false; + if (!checkSelectValue(InBoundsV, Offset, ThroughGEP)) + continue; + + InsListTy GEPs; + InsListTy Escapes; + + if(ThroughGEP) + GEPs.push_back(SI); + else + findGEPs(SI, GEPs, Escapes); + + InsListTy Loads; + for (auto &GEP : GEPs) + findLoads(GEP, Loads, Escapes); + + for (auto &Load : Loads) { + if (SceptreFindSecondLoad) { + InsListTy SecondGEPs; + InsListTy SecondEscapes; + findGEPs(Load, SecondGEPs, SecondEscapes); + + InsListTy SecondLoads; + for (auto &GEP : SecondGEPs) + findLoads(GEP, SecondLoads, SecondEscapes); + + // TODO print out diagnostics for the second load(s)/escape(s). + if (SecondLoads.empty() && SecondEscapes.empty()) + continue; + } + SceptreRemark::diagnoseSceptre(F, Load, Cmp, Offset); + } + for (auto &Escape : Escapes) + SceptreRemark::diagnoseSceptre(F, Escape, Cmp, Offset); + } +} + +bool Sceptre::runOnFunction(Function &F) { + auto &DT = getAnalysis().getDomTree(); + + for (auto &BB : F) { + if (BranchInst *BI = dyn_cast(BB.getTerminator())) + processBranch(BI, F, DT); + + if (SceptreAllowSelect) + for (auto &I : BB) + if (SelectInst *SI = dyn_cast(&I)) + processSelect(SI, F); + } + + return true; +} + +char Sceptre::ID = 0; +INITIALIZE_PASS_BEGIN(Sceptre, "sceptre", "Spectre variant 1 detector", + false, false) +INITIALIZE_PASS_DEPENDENCY(DominatorTreeWrapperPass) +INITIALIZE_PASS_END(Sceptre, "sceptre", "Spectre variant 1 detector", + false, false) + +FunctionPass *llvm::createSceptrePass() { + return new Sceptre(); +} Index: lib/IR/DiagnosticInfo.cpp =================================================================== --- lib/IR/DiagnosticInfo.cpp +++ lib/IR/DiagnosticInfo.cpp @@ -77,6 +77,68 @@ DP << " exceeded (" << getResourceSize() << ") in " << getFunction(); } +static void spectreDiagnosticHelper(const Function &Fn, DebugLoc DL, + Value *V) { + LLVMContext &Ctx = Fn.getContext(); + DiagnosticSeverity DS = isa(V) ? DS_Warning : DS_Note; + SceptreRemark Remark(Fn, DiagnosticLocation(DL), DS); + + if (DL) + if (DILocation *DILoc = DL.get()) + if (DILoc->getScope()) + Remark.Subprogram = DILoc->getScope()->getSubprogram(); + + Remark.V = V; + Ctx.diagnose(Remark); + + // If DL is a location from an inlined subroutine, then emit a 'Note' + // for every entry in the inline stack. + if (DL) { + DILocation *IA = DL->getInlinedAt(); + while (IA) { + SceptreRemark Note(Fn, DiagnosticLocation(DebugLoc(IA)), DS_Note); + if (IA->getScope()) + Note.Subprogram = IA->getScope()->getSubprogram(); + + Ctx.diagnose(Note); + IA = IA->getInlinedAt(); + } + } else { + // Suggest to pass flag -g/-gmlt in order to get the full + // debug location info in the warning message. + SceptreRemark Note(Fn, DiagnosticLocation(DL), DS_Note); + Ctx.diagnose(Note); + } +} + +void SceptreRemark::diagnoseSceptre(const Function &Fn, Instruction *Vuln, + Instruction *Cmp, Value *V) { + + spectreDiagnosticHelper(Fn, Vuln->getDebugLoc(), Vuln); + spectreDiagnosticHelper(Fn, Cmp->getDebugLoc(), V); +} + +void SceptreRemark::print(DiagnosticPrinter &DP) const { + StringRef Name = Subprogram ? Subprogram->getName() + : getFunction().getName(); + if (getSeverity() == DS_Note) { + if (V) + DP << "bounds check with index \"" << *V << "\" is at: " + << getLocationStr() << ": in function " << Name; + else { + if (!isLocationAvailable()) + DP << "enable -g to get full debug location information for this " + << "diagnostic"; + else + DP << "inlined into function " << Name << " at: " << getLocationStr(); + } + } else { + Instruction *I = dyn_cast(V); + DP << getLocationStr() << ": in function " << Name + << ": found vulnerable " << I->getOpcodeName(); + } +} + void DiagnosticInfoDebugMetadataVersion::print(DiagnosticPrinter &DP) const { DP << "ignoring debug info with an invalid version (" << getMetadataVersion() << ") in " << getModule(); Index: lib/Transforms/IPO/PassManagerBuilder.cpp =================================================================== --- lib/Transforms/IPO/PassManagerBuilder.cpp +++ lib/Transforms/IPO/PassManagerBuilder.cpp @@ -144,6 +144,10 @@ "enable-gvn-sink", cl::init(false), cl::Hidden, cl::desc("Enable the GVN sinking pass (default = off)")); +static cl::opt EnableSceptre( + "enable-sceptre", cl::init(false), cl::Hidden, + cl::desc("Enable the Sceptre Spectre variant 1 detector (default = off)")); + PassManagerBuilder::PassManagerBuilder() { OptLevel = 2; SizeLevel = 0; @@ -512,6 +516,9 @@ addExtensionsToPM(EP_CGSCCOptimizerLate, MPM); addFunctionSimplificationPasses(MPM); + if (EnableSceptre) + MPM.add(createSceptrePass()); + // FIXME: This is a HACK! The inliner pass above implicitly creates a CGSCC // pass manager that we are specifically trying to avoid. To prevent this // we must insert a no-op module pass to reset the pass manager. @@ -786,6 +793,9 @@ PM.add(createGlobalOptimizerPass()); PM.add(createGlobalDCEPass()); // Remove dead functions. + if (EnableSceptre) + PM.add(createSceptrePass()); + // If we didn't decide to inline a function, check to see if we can // transform it to pass arguments by value instead of by reference. PM.add(createArgumentPromotionPass()); Index: test/Analysis/Sceptre/basic.ll =================================================================== --- test/Analysis/Sceptre/basic.ll +++ test/Analysis/Sceptre/basic.ll @@ -0,0 +1,439 @@ +; RUN: opt -sceptre -S < %s 2>&1 >/dev/null | FileCheck %s + +; Basic Sceptre tests. In addition to the standard Spectre variant 1 bounds +; check pattern, this includes examples involving and/or trees, PHI nodes, +; selects, etc. The examples were generated from the following C code: + +; extern char array1[]; +; extern char array2[]; +; extern unsigned array1_size; +; +; char basic_pattern(unsigned index) { +; if(index < array1_size) +; return array2[array1[index]]; +; return -1; +; } +; +; char simple_expression(unsigned index) { +; if(index >= array1_size) +; return -1; +; return array2[array1[index << 2] * 256]; +; } +; +; char and_expr(int index) { +; if(index >= 0 && index < array1_size) +; return array2[array1[index]]; +; return -1; +; } +; +; char or_expr(int index) { +; if(index < array1_size) { +; if(index < 0) +; return -1; +; } else +; return -1; +; return array2[array1[index]]; +; } +; +; extern unsigned bar(); +; +; char multiple_ifs(int index) { +; if(index < array1_size) { +; bar(); +; if(index < 0) +; return -1; +; } else +; return -1; +; return array2[array1[index]]; +; } +; +; char select(unsigned index) { +; unsigned i = index >= array1_size ? 0 : index; +; return array2[array1[i]]; +; } +; +; char phinode(unsigned index) { +; unsigned i = index >= array1_size ? bar() : index; +; return array2[array1[i]]; +; } +; +; char select_addr(unsigned index) { +; char *p = index >= array1_size ? array1 : array1 + index; +; return array2[*p]; +; } +; +; extern char *bar2(); +; +; char phinode_addr(unsigned index) { +; char *p = index >= array1_size ? bar2() : array1 + index; +; return array2[*p]; +; } + +; CHECK: warning: simple.c:7:19: in function basic_pattern: found vulnerable load +; CHECK: note: bounds check with index "index" is at: simple.c:6:12: in function basic_pattern +; CHECK: warning: simple.c:14:17: in function simple_expression: found vulnerable load +; CHECK: note: bounds check with index "index" is at: simple.c:12:12: in function simple_expression +; CHECK: warning: simple.c:19:19: in function and_expr: found vulnerable load +; CHECK: note: bounds check with index "index" is at: simple.c:18:26: in function and_expr +; CHECK: warning: simple.c:29:17: in function or_expr: found vulnerable load +; CHECK: note: bounds check with index "index" is at: simple.c:24:12: in function or_expr +; CHECK: warning: simple.c:41:17: in function multiple_ifs: found vulnerable load +; CHECK: note: bounds check with index "index" is at: simple.c:35:12: in function multiple_ifs +; CHECK: warning: simple.c:46:17: in function select: found vulnerable load +; CHECK: note: bounds check with index "index" is at: simple.c:45:22: in function select +; CHECK: warning: simple.c:51:17: in function phinode: found vulnerable load +; CHECK: note: bounds check with index "index" is at: simple.c:50:22: in function phinode +; CHECK: warning: simple.c:56:17: in function select_addr: found vulnerable load +; CHECK: note: bounds check with index "index" is at: simple.c:55:19: in function select_addr +; CHECK: warning: simple.c:63:17: in function phinode_addr: found vulnerable load +; CHECK: note: bounds check with index "index" is at: simple.c:62:19: in function phinode_addr + +@array1_size = external dso_local local_unnamed_addr global i32, align 4 +@array2 = external dso_local local_unnamed_addr global [0 x i8], align 1 +@array1 = external dso_local local_unnamed_addr global [0 x i8], align 1 + +define dso_local signext i8 @basic_pattern(i32 %index) local_unnamed_addr !dbg !7 { +entry: + call void @llvm.dbg.value(metadata i32 %index, metadata !13, metadata !DIExpression()), !dbg !14 + %0 = load i32, i32* @array1_size, align 4, !dbg !15, !tbaa !17 + %cmp = icmp ugt i32 %0, %index, !dbg !21 + br i1 %cmp, label %if.then, label %return, !dbg !22 + +if.then: ; preds = %entry + %idxprom = zext i32 %index to i64, !dbg !23 + %arrayidx = getelementptr inbounds [0 x i8], [0 x i8]* @array1, i64 0, i64 %idxprom, !dbg !23 + %1 = load i8, i8* %arrayidx, align 1, !dbg !23, !tbaa !24 + %idxprom1 = sext i8 %1 to i64, !dbg !25 + %arrayidx2 = getelementptr inbounds [0 x i8], [0 x i8]* @array2, i64 0, i64 %idxprom1, !dbg !25 + %2 = load i8, i8* %arrayidx2, align 1, !dbg !25, !tbaa !24 + br label %return, !dbg !26 + +return: ; preds = %entry, %if.then + %retval.0 = phi i8 [ %2, %if.then ], [ -1, %entry ] + ret i8 %retval.0, !dbg !27 +} + +define dso_local signext i8 @simple_expression(i32 %index) local_unnamed_addr !dbg !28 { +entry: + call void @llvm.dbg.value(metadata i32 %index, metadata !30, metadata !DIExpression()), !dbg !31 + %0 = load i32, i32* @array1_size, align 4, !dbg !32, !tbaa !17 + %cmp = icmp ugt i32 %0, %index, !dbg !34 + br i1 %cmp, label %if.end, label %return, !dbg !35 + +if.end: ; preds = %entry + %shl = shl i32 %index, 2, !dbg !36 + %idxprom = zext i32 %shl to i64, !dbg !37 + %arrayidx = getelementptr inbounds [0 x i8], [0 x i8]* @array1, i64 0, i64 %idxprom, !dbg !37 + %1 = load i8, i8* %arrayidx, align 1, !dbg !37, !tbaa !24 + %conv = sext i8 %1 to i32, !dbg !37 + %mul = shl nsw i32 %conv, 8, !dbg !38 + %idxprom1 = sext i32 %mul to i64, !dbg !39 + %arrayidx2 = getelementptr inbounds [0 x i8], [0 x i8]* @array2, i64 0, i64 %idxprom1, !dbg !39 + %2 = load i8, i8* %arrayidx2, align 1, !dbg !39, !tbaa !24 + br label %return, !dbg !40 + +return: ; preds = %entry, %if.end + %retval.0 = phi i8 [ %2, %if.end ], [ -1, %entry ] + ret i8 %retval.0, !dbg !41 +} + +define dso_local signext i8 @and_expr(i32 %index) local_unnamed_addr !dbg !42 { +entry: + call void @llvm.dbg.value(metadata i32 %index, metadata !47, metadata !DIExpression()), !dbg !48 + %cmp = icmp sgt i32 %index, -1, !dbg !49 + %0 = load i32, i32* @array1_size, align 4, !dbg !51 + %cmp1 = icmp ugt i32 %0, %index, !dbg !52 + %or.cond = and i1 %cmp, %cmp1, !dbg !53 + br i1 %or.cond, label %if.then, label %return, !dbg !53 + +if.then: ; preds = %entry + %idxprom = sext i32 %index to i64, !dbg !54 + %arrayidx = getelementptr inbounds [0 x i8], [0 x i8]* @array1, i64 0, i64 %idxprom, !dbg !54 + %1 = load i8, i8* %arrayidx, align 1, !dbg !54, !tbaa !24 + %idxprom2 = sext i8 %1 to i64, !dbg !55 + %arrayidx3 = getelementptr inbounds [0 x i8], [0 x i8]* @array2, i64 0, i64 %idxprom2, !dbg !55 + %2 = load i8, i8* %arrayidx3, align 1, !dbg !55, !tbaa !24 + br label %return, !dbg !56 + +return: ; preds = %entry, %if.then + %retval.0 = phi i8 [ %2, %if.then ], [ -1, %entry ] + ret i8 %retval.0, !dbg !57 +} + +define dso_local signext i8 @or_expr(i32 %index) local_unnamed_addr !dbg !58 { +entry: + call void @llvm.dbg.value(metadata i32 %index, metadata !60, metadata !DIExpression()), !dbg !61 + %0 = load i32, i32* @array1_size, align 4, !dbg !62, !tbaa !17 + %cmp = icmp ule i32 %0, %index, !dbg !64 + %cmp1 = icmp slt i32 %index, 0, !dbg !65 + %or.cond = or i1 %cmp1, %cmp, !dbg !68 + br i1 %or.cond, label %return, label %if.end3, !dbg !68 + +if.end3: ; preds = %entry + %idxprom = sext i32 %index to i64, !dbg !69 + %arrayidx = getelementptr inbounds [0 x i8], [0 x i8]* @array1, i64 0, i64 %idxprom, !dbg !69 + %1 = load i8, i8* %arrayidx, align 1, !dbg !69, !tbaa !24 + %idxprom4 = sext i8 %1 to i64, !dbg !70 + %arrayidx5 = getelementptr inbounds [0 x i8], [0 x i8]* @array2, i64 0, i64 %idxprom4, !dbg !70 + %2 = load i8, i8* %arrayidx5, align 1, !dbg !70, !tbaa !24 + br label %return, !dbg !71 + +return: ; preds = %entry, %if.end3 + %retval.0 = phi i8 [ %2, %if.end3 ], [ -1, %entry ] + ret i8 %retval.0, !dbg !72 +} + +define dso_local signext i8 @multiple_ifs(i32 %index) local_unnamed_addr !dbg !73 { +entry: + call void @llvm.dbg.value(metadata i32 %index, metadata !75, metadata !DIExpression()), !dbg !76 + %0 = load i32, i32* @array1_size, align 4, !dbg !77, !tbaa !17 + %cmp = icmp ugt i32 %0, %index, !dbg !79 + br i1 %cmp, label %if.then, label %return, !dbg !80 + +if.then: ; preds = %entry + %call = tail call i32 (...) @bar() #4, !dbg !81 + %cmp1 = icmp slt i32 %index, 0, !dbg !83 + br i1 %cmp1, label %return, label %if.end3, !dbg !85 + +if.end3: ; preds = %if.then + %idxprom = sext i32 %index to i64, !dbg !86 + %arrayidx = getelementptr inbounds [0 x i8], [0 x i8]* @array1, i64 0, i64 %idxprom, !dbg !86 + %1 = load i8, i8* %arrayidx, align 1, !dbg !86, !tbaa !24 + %idxprom4 = sext i8 %1 to i64, !dbg !87 + %arrayidx5 = getelementptr inbounds [0 x i8], [0 x i8]* @array2, i64 0, i64 %idxprom4, !dbg !87 + %2 = load i8, i8* %arrayidx5, align 1, !dbg !87, !tbaa !24 + br label %return, !dbg !88 + +return: ; preds = %entry, %if.then, %if.end3 + %retval.0 = phi i8 [ %2, %if.end3 ], [ -1, %if.then ], [ -1, %entry ] + ret i8 %retval.0, !dbg !89 +} + +declare dso_local i32 @bar(...) local_unnamed_addr + +define dso_local signext i8 @select(i32 %index) local_unnamed_addr !dbg !90 { +entry: + call void @llvm.dbg.value(metadata i32 %index, metadata !92, metadata !DIExpression()), !dbg !94 + %0 = load i32, i32* @array1_size, align 4, !dbg !95, !tbaa !17 + %cmp = icmp ugt i32 %0, %index, !dbg !96 + %1 = zext i32 %index to i64, !dbg !97 + %idxprom = select i1 %cmp, i64 %1, i64 0, !dbg !97 + %arrayidx = getelementptr inbounds [0 x i8], [0 x i8]* @array1, i64 0, i64 %idxprom, !dbg !97 + %2 = load i8, i8* %arrayidx, align 1, !dbg !97, !tbaa !24 + %idxprom1 = sext i8 %2 to i64, !dbg !98 + %arrayidx2 = getelementptr inbounds [0 x i8], [0 x i8]* @array2, i64 0, i64 %idxprom1, !dbg !98 + %3 = load i8, i8* %arrayidx2, align 1, !dbg !98, !tbaa !24 + ret i8 %3, !dbg !99 +} + +define dso_local signext i8 @phinode(i32 %index) local_unnamed_addr !dbg !100 { +entry: + call void @llvm.dbg.value(metadata i32 %index, metadata !102, metadata !DIExpression()), !dbg !104 + %0 = load i32, i32* @array1_size, align 4, !dbg !105, !tbaa !17 + %cmp = icmp ugt i32 %0, %index, !dbg !106 + br i1 %cmp, label %cond.end, label %cond.true, !dbg !107 + +cond.true: ; preds = %entry + %call = tail call i32 (...) @bar() #4, !dbg !108 + br label %cond.end, !dbg !107 + +cond.end: ; preds = %entry, %cond.true + %cond = phi i32 [ %call, %cond.true ], [ %index, %entry ], !dbg !107 + call void @llvm.dbg.value(metadata i32 %cond, metadata !103, metadata !DIExpression()), !dbg !109 + %idxprom = zext i32 %cond to i64, !dbg !110 + %arrayidx = getelementptr inbounds [0 x i8], [0 x i8]* @array1, i64 0, i64 %idxprom, !dbg !110 + %1 = load i8, i8* %arrayidx, align 1, !dbg !110, !tbaa !24 + %idxprom1 = sext i8 %1 to i64, !dbg !111 + %arrayidx2 = getelementptr inbounds [0 x i8], [0 x i8]* @array2, i64 0, i64 %idxprom1, !dbg !111 + %2 = load i8, i8* %arrayidx2, align 1, !dbg !111, !tbaa !24 + ret i8 %2, !dbg !112 +} + +define dso_local signext i8 @select_addr(i32 %index) local_unnamed_addr !dbg !113 { +entry: + call void @llvm.dbg.value(metadata i32 %index, metadata !115, metadata !DIExpression()), !dbg !118 + %0 = load i32, i32* @array1_size, align 4, !dbg !119, !tbaa !17 + %cmp = icmp ugt i32 %0, %index, !dbg !120 + %idx.ext = zext i32 %index to i64, !dbg !121 + %add.ptr = getelementptr inbounds [0 x i8], [0 x i8]* @array1, i64 0, i64 %idx.ext, !dbg !121 + %cond = select i1 %cmp, i8* %add.ptr, i8* getelementptr inbounds ([0 x i8], [0 x i8]* @array1, i64 0, i64 0), !dbg !122 + call void @llvm.dbg.value(metadata i8* %cond, metadata !116, metadata !DIExpression()), !dbg !123 + %1 = load i8, i8* %cond, align 1, !dbg !124, !tbaa !24 + %idxprom = sext i8 %1 to i64, !dbg !125 + %arrayidx = getelementptr inbounds [0 x i8], [0 x i8]* @array2, i64 0, i64 %idxprom, !dbg !125 + %2 = load i8, i8* %arrayidx, align 1, !dbg !125, !tbaa !24 + ret i8 %2, !dbg !126 +} + +define dso_local signext i8 @phinode_addr(i32 %index) local_unnamed_addr !dbg !127 { +entry: + call void @llvm.dbg.value(metadata i32 %index, metadata !129, metadata !DIExpression()), !dbg !131 + %0 = load i32, i32* @array1_size, align 4, !dbg !132, !tbaa !17 + %cmp = icmp ugt i32 %0, %index, !dbg !133 + br i1 %cmp, label %cond.false, label %cond.true, !dbg !134 + +cond.true: ; preds = %entry + %call = tail call i8* (...) @bar2() #4, !dbg !135 + br label %cond.end, !dbg !134 + +cond.false: ; preds = %entry + %idx.ext = zext i32 %index to i64, !dbg !136 + %add.ptr = getelementptr inbounds [0 x i8], [0 x i8]* @array1, i64 0, i64 %idx.ext, !dbg !136 + br label %cond.end, !dbg !134 + +cond.end: ; preds = %cond.false, %cond.true + %cond = phi i8* [ %call, %cond.true ], [ %add.ptr, %cond.false ], !dbg !134 + call void @llvm.dbg.value(metadata i8* %cond, metadata !130, metadata !DIExpression()), !dbg !137 + %1 = load i8, i8* %cond, align 1, !dbg !138, !tbaa !24 + %idxprom = sext i8 %1 to i64, !dbg !139 + %arrayidx = getelementptr inbounds [0 x i8], [0 x i8]* @array2, i64 0, i64 %idxprom, !dbg !139 + %2 = load i8, i8* %arrayidx, align 1, !dbg !139, !tbaa !24 + ret i8 %2, !dbg !140 +} + +declare dso_local i8* @bar2(...) local_unnamed_addr +declare void @llvm.dbg.value(metadata, metadata, metadata) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!3, !4, !5} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 7.0.0 (trunk 325675)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2) +!1 = !DIFile(filename: "simple.c", directory: "") +!2 = !{} +!3 = !{i32 2, !"Dwarf Version", i32 4} +!4 = !{i32 2, !"Debug Info Version", i32 3} +!5 = !{i32 1, !"wchar_size", i32 4} +!7 = distinct !DISubprogram(name: "basic_pattern", scope: !1, file: !1, line: 5, type: !8, isLocal: false, isDefinition: true, scopeLine: 5, flags: DIFlagPrototyped, isOptimized: true, unit: !0, variables: !12) +!8 = !DISubroutineType(types: !9) +!9 = !{!10, !11} +!10 = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_signed_char) +!11 = !DIBasicType(name: "unsigned int", size: 32, encoding: DW_ATE_unsigned) +!12 = !{!13} +!13 = !DILocalVariable(name: "index", arg: 1, scope: !7, file: !1, line: 5, type: !11) +!14 = !DILocation(line: 5, column: 29, scope: !7) +!15 = !DILocation(line: 6, column: 14, scope: !16) +!16 = distinct !DILexicalBlock(scope: !7, file: !1, line: 6, column: 6) +!17 = !{!18, !18, i64 0} +!18 = !{!"int", !19, i64 0} +!19 = !{!"omnipotent char", !20, i64 0} +!20 = !{!"Simple C/C++ TBAA"} +!21 = !DILocation(line: 6, column: 12, scope: !16) +!22 = !DILocation(line: 6, column: 6, scope: !7) +!23 = !DILocation(line: 7, column: 19, scope: !16) +!24 = !{!19, !19, i64 0} +!25 = !DILocation(line: 7, column: 12, scope: !16) +!26 = !DILocation(line: 7, column: 5, scope: !16) +!27 = !DILocation(line: 9, column: 1, scope: !7) +!28 = distinct !DISubprogram(name: "simple_expression", scope: !1, file: !1, line: 11, type: !8, isLocal: false, isDefinition: true, scopeLine: 11, flags: DIFlagPrototyped, isOptimized: true, unit: !0, variables: !29) +!29 = !{!30} +!30 = !DILocalVariable(name: "index", arg: 1, scope: !28, file: !1, line: 11, type: !11) +!31 = !DILocation(line: 11, column: 33, scope: !28) +!32 = !DILocation(line: 12, column: 15, scope: !33) +!33 = distinct !DILexicalBlock(scope: !28, file: !1, line: 12, column: 6) +!34 = !DILocation(line: 12, column: 12, scope: !33) +!35 = !DILocation(line: 12, column: 6, scope: !28) +!36 = !DILocation(line: 14, column: 30, scope: !28) +!37 = !DILocation(line: 14, column: 17, scope: !28) +!38 = !DILocation(line: 14, column: 36, scope: !28) +!39 = !DILocation(line: 14, column: 10, scope: !28) +!40 = !DILocation(line: 14, column: 3, scope: !28) +!41 = !DILocation(line: 15, column: 1, scope: !28) +!42 = distinct !DISubprogram(name: "and_expr", scope: !1, file: !1, line: 17, type: !43, isLocal: false, isDefinition: true, scopeLine: 17, flags: DIFlagPrototyped, isOptimized: true, unit: !0, variables: !46) +!43 = !DISubroutineType(types: !44) +!44 = !{!10, !45} +!45 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) +!46 = !{!47} +!47 = !DILocalVariable(name: "index", arg: 1, scope: !42, file: !1, line: 17, type: !45) +!48 = !DILocation(line: 17, column: 19, scope: !42) +!49 = !DILocation(line: 18, column: 12, scope: !50) +!50 = distinct !DILexicalBlock(scope: !42, file: !1, line: 18, column: 6) +!51 = !DILocation(line: 18, column: 28, scope: !50) +!52 = !DILocation(line: 18, column: 26, scope: !50) +!53 = !DILocation(line: 18, column: 17, scope: !50) +!54 = !DILocation(line: 19, column: 19, scope: !50) +!55 = !DILocation(line: 19, column: 12, scope: !50) +!56 = !DILocation(line: 19, column: 5, scope: !50) +!57 = !DILocation(line: 21, column: 1, scope: !42) +!58 = distinct !DISubprogram(name: "or_expr", scope: !1, file: !1, line: 23, type: !43, isLocal: false, isDefinition: true, scopeLine: 23, flags: DIFlagPrototyped, isOptimized: true, unit: !0, variables: !59) +!59 = !{!60} +!60 = !DILocalVariable(name: "index", arg: 1, scope: !58, file: !1, line: 23, type: !45) +!61 = !DILocation(line: 23, column: 18, scope: !58) +!62 = !DILocation(line: 24, column: 14, scope: !63) +!63 = distinct !DILexicalBlock(scope: !58, file: !1, line: 24, column: 6) +!64 = !DILocation(line: 24, column: 12, scope: !63) +!65 = !DILocation(line: 25, column: 14, scope: !66) +!66 = distinct !DILexicalBlock(scope: !67, file: !1, line: 25, column: 8) +!67 = distinct !DILexicalBlock(scope: !63, file: !1, line: 24, column: 27) +!68 = !DILocation(line: 24, column: 6, scope: !58) +!69 = !DILocation(line: 29, column: 17, scope: !58) +!70 = !DILocation(line: 29, column: 10, scope: !58) +!71 = !DILocation(line: 29, column: 3, scope: !58) +!72 = !DILocation(line: 30, column: 1, scope: !58) +!73 = distinct !DISubprogram(name: "multiple_ifs", scope: !1, file: !1, line: 34, type: !43, isLocal: false, isDefinition: true, scopeLine: 34, flags: DIFlagPrototyped, isOptimized: true, unit: !0, variables: !74) +!74 = !{!75} +!75 = !DILocalVariable(name: "index", arg: 1, scope: !73, file: !1, line: 34, type: !45) +!76 = !DILocation(line: 34, column: 23, scope: !73) +!77 = !DILocation(line: 35, column: 14, scope: !78) +!78 = distinct !DILexicalBlock(scope: !73, file: !1, line: 35, column: 6) +!79 = !DILocation(line: 35, column: 12, scope: !78) +!80 = !DILocation(line: 35, column: 6, scope: !73) +!81 = !DILocation(line: 36, column: 5, scope: !82) +!82 = distinct !DILexicalBlock(scope: !78, file: !1, line: 35, column: 27) +!83 = !DILocation(line: 37, column: 14, scope: !84) +!84 = distinct !DILexicalBlock(scope: !82, file: !1, line: 37, column: 8) +!85 = !DILocation(line: 37, column: 8, scope: !82) +!86 = !DILocation(line: 41, column: 17, scope: !73) +!87 = !DILocation(line: 41, column: 10, scope: !73) +!88 = !DILocation(line: 41, column: 3, scope: !73) +!89 = !DILocation(line: 42, column: 1, scope: !73) +!90 = distinct !DISubprogram(name: "select", scope: !1, file: !1, line: 44, type: !8, isLocal: false, isDefinition: true, scopeLine: 44, flags: DIFlagPrototyped, isOptimized: true, unit: !0, variables: !91) +!91 = !{!92, !93} +!92 = !DILocalVariable(name: "index", arg: 1, scope: !90, file: !1, line: 44, type: !11) +!93 = !DILocalVariable(name: "i", scope: !90, file: !1, line: 45, type: !11) +!94 = !DILocation(line: 44, column: 22, scope: !90) +!95 = !DILocation(line: 45, column: 25, scope: !90) +!96 = !DILocation(line: 45, column: 22, scope: !90) +!97 = !DILocation(line: 46, column: 17, scope: !90) +!98 = !DILocation(line: 46, column: 10, scope: !90) +!99 = !DILocation(line: 46, column: 3, scope: !90) +!100 = distinct !DISubprogram(name: "phinode", scope: !1, file: !1, line: 49, type: !8, isLocal: false, isDefinition: true, scopeLine: 49, flags: DIFlagPrototyped, isOptimized: true, unit: !0, variables: !101) +!101 = !{!102, !103} +!102 = !DILocalVariable(name: "index", arg: 1, scope: !100, file: !1, line: 49, type: !11) +!103 = !DILocalVariable(name: "i", scope: !100, file: !1, line: 50, type: !11) +!104 = !DILocation(line: 49, column: 23, scope: !100) +!105 = !DILocation(line: 50, column: 25, scope: !100) +!106 = !DILocation(line: 50, column: 22, scope: !100) +!107 = !DILocation(line: 50, column: 16, scope: !100) +!108 = !DILocation(line: 50, column: 39, scope: !100) +!109 = !DILocation(line: 50, column: 12, scope: !100) +!110 = !DILocation(line: 51, column: 17, scope: !100) +!111 = !DILocation(line: 51, column: 10, scope: !100) +!112 = !DILocation(line: 51, column: 3, scope: !100) +!113 = distinct !DISubprogram(name: "select_addr", scope: !1, file: !1, line: 54, type: !8, isLocal: false, isDefinition: true, scopeLine: 54, flags: DIFlagPrototyped, isOptimized: true, unit: !0, variables: !114) +!114 = !{!115, !116} +!115 = !DILocalVariable(name: "index", arg: 1, scope: !113, file: !1, line: 54, type: !11) +!116 = !DILocalVariable(name: "p", scope: !113, file: !1, line: 55, type: !117) +!117 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !10, size: 64) +!118 = !DILocation(line: 54, column: 27, scope: !113) +!119 = !DILocation(line: 55, column: 22, scope: !113) +!120 = !DILocation(line: 55, column: 19, scope: !113) +!121 = !DILocation(line: 55, column: 52, scope: !113) +!122 = !DILocation(line: 55, column: 13, scope: !113) +!123 = !DILocation(line: 55, column: 9, scope: !113) +!124 = !DILocation(line: 56, column: 17, scope: !113) +!125 = !DILocation(line: 56, column: 10, scope: !113) +!126 = !DILocation(line: 56, column: 3, scope: !113) +!127 = distinct !DISubprogram(name: "phinode_addr", scope: !1, file: !1, line: 61, type: !8, isLocal: false, isDefinition: true, scopeLine: 61, flags: DIFlagPrototyped, isOptimized: true, unit: !0, variables: !128) +!128 = !{!129, !130} +!129 = !DILocalVariable(name: "index", arg: 1, scope: !127, file: !1, line: 61, type: !11) +!130 = !DILocalVariable(name: "p", scope: !127, file: !1, line: 62, type: !117) +!131 = !DILocation(line: 61, column: 28, scope: !127) +!132 = !DILocation(line: 62, column: 22, scope: !127) +!133 = !DILocation(line: 62, column: 19, scope: !127) +!134 = !DILocation(line: 62, column: 13, scope: !127) +!135 = !DILocation(line: 62, column: 36, scope: !127) +!136 = !DILocation(line: 62, column: 52, scope: !127) +!137 = !DILocation(line: 62, column: 9, scope: !127) +!138 = !DILocation(line: 63, column: 17, scope: !127) +!139 = !DILocation(line: 63, column: 10, scope: !127) +!140 = !DILocation(line: 63, column: 3, scope: !127) Index: test/Analysis/Sceptre/escapes.ll =================================================================== --- test/Analysis/Sceptre/escapes.ll +++ test/Analysis/Sceptre/escapes.ll @@ -0,0 +1,117 @@ +; RUN: opt -sceptre -sceptre-show-call-escapes -S < %s 2>&1 >/dev/null | FileCheck %s + +; Sceptre escape tests. With the flag -sceptre-show-call-escapes the detector +; also report cases where the speculatively loaded value escapes by being +; passed into a non-inlined call. This can either be the index to the second +; load, or the computed address. The examples were generated from the following +; C code: + +; extern char array1[]; +; extern char array2[]; +; extern unsigned array1_size; +; +; extern void bar(char); +; extern void bar2(char*); +; +; void index_escapes(unsigned index) { +; if(index < array1_size) +; bar(array1[index]); +; } +; +; void address_escapes(unsigned index) { +; if(index < array1_size) +; bar2(&array2[array1[index]]); +; } + +; CHECK: warning: escapes.c:10:10: in function index_escapes: found vulnerable load +; CHECK: note: bounds check with index "index" is at: escapes.c:9:12: in function index_escapes +; CHECK: warning: escapes.c:15:19: in function address_escapes: found vulnerable load +; CHECK: note: bounds check with index "index" is at: escapes.c:14:12: in function address_escapes + +@array1_size = external dso_local local_unnamed_addr global i32, align 4 +@array1 = external dso_local local_unnamed_addr global [0 x i8], align 1 +@array2 = external dso_local global [0 x i8], align 1 + +define dso_local void @index_escapes(i32 %index) local_unnamed_addr !dbg !7 { +entry: + call void @llvm.dbg.value(metadata i32 %index, metadata !12, metadata !DIExpression()), !dbg !13 + %0 = load i32, i32* @array1_size, align 4, !dbg !14, !tbaa !16 + %cmp = icmp ugt i32 %0, %index, !dbg !20 + br i1 %cmp, label %if.then, label %if.end, !dbg !21 + +if.then: ; preds = %entry + %idxprom = zext i32 %index to i64, !dbg !22 + %arrayidx = getelementptr inbounds [0 x i8], [0 x i8]* @array1, i64 0, i64 %idxprom, !dbg !22 + %1 = load i8, i8* %arrayidx, align 1, !dbg !22, !tbaa !23 + tail call void @bar(i8 signext %1) #3, !dbg !24 + br label %if.end, !dbg !24 + +if.end: ; preds = %if.then, %entry + ret void, !dbg !25 +} + +declare dso_local void @bar(i8 signext) local_unnamed_addr + +define dso_local void @address_escapes(i32 %index) local_unnamed_addr !dbg !26 { +entry: + call void @llvm.dbg.value(metadata i32 %index, metadata !28, metadata !DIExpression()), !dbg !29 + %0 = load i32, i32* @array1_size, align 4, !dbg !30, !tbaa !16 + %cmp = icmp ugt i32 %0, %index, !dbg !32 + br i1 %cmp, label %if.then, label %if.end, !dbg !33 + +if.then: ; preds = %entry + %idxprom = zext i32 %index to i64, !dbg !34 + %arrayidx = getelementptr inbounds [0 x i8], [0 x i8]* @array1, i64 0, i64 %idxprom, !dbg !34 + %1 = load i8, i8* %arrayidx, align 1, !dbg !34, !tbaa !23 + %idxprom1 = sext i8 %1 to i64, !dbg !35 + %arrayidx2 = getelementptr inbounds [0 x i8], [0 x i8]* @array2, i64 0, i64 %idxprom1, !dbg !35 + tail call void @bar2(i8* nonnull %arrayidx2) #3, !dbg !36 + br label %if.end, !dbg !36 + +if.end: ; preds = %if.then, %entry + ret void, !dbg !37 +} + +declare dso_local void @bar2(i8*) local_unnamed_addr +declare void @llvm.dbg.value(metadata, metadata, metadata) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!3, !4, !5} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 7.0.0 (trunk 325675)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2) +!1 = !DIFile(filename: "escapes.c", directory: "") +!2 = !{} +!3 = !{i32 2, !"Dwarf Version", i32 4} +!4 = !{i32 2, !"Debug Info Version", i32 3} +!5 = !{i32 1, !"wchar_size", i32 4} +!7 = distinct !DISubprogram(name: "index_escapes", scope: !1, file: !1, line: 8, type: !8, isLocal: false, isDefinition: true, scopeLine: 8, flags: DIFlagPrototyped, isOptimized: true, unit: !0, variables: !11) +!8 = !DISubroutineType(types: !9) +!9 = !{null, !10} +!10 = !DIBasicType(name: "unsigned int", size: 32, encoding: DW_ATE_unsigned) +!11 = !{!12} +!12 = !DILocalVariable(name: "index", arg: 1, scope: !7, file: !1, line: 8, type: !10) +!13 = !DILocation(line: 8, column: 19, scope: !7) +!14 = !DILocation(line: 9, column: 14, scope: !15) +!15 = distinct !DILexicalBlock(scope: !7, file: !1, line: 9, column: 6) +!16 = !{!17, !17, i64 0} +!17 = !{!"int", !18, i64 0} +!18 = !{!"omnipotent char", !19, i64 0} +!19 = !{!"Simple C/C++ TBAA"} +!20 = !DILocation(line: 9, column: 12, scope: !15) +!21 = !DILocation(line: 9, column: 6, scope: !7) +!22 = !DILocation(line: 10, column: 10, scope: !15) +!23 = !{!18, !18, i64 0} +!24 = !DILocation(line: 10, column: 6, scope: !15) +!25 = !DILocation(line: 11, column: 1, scope: !7) +!26 = distinct !DISubprogram(name: "address_escapes", scope: !1, file: !1, line: 13, type: !8, isLocal: false, isDefinition: true, scopeLine: 13, flags: DIFlagPrototyped, isOptimized: true, unit: !0, variables: !27) +!27 = !{!28} +!28 = !DILocalVariable(name: "index", arg: 1, scope: !26, file: !1, line: 13, type: !10) +!29 = !DILocation(line: 13, column: 20, scope: !26) +!30 = !DILocation(line: 14, column: 14, scope: !31) +!31 = distinct !DILexicalBlock(scope: !26, file: !1, line: 14, column: 6) +!32 = !DILocation(line: 14, column: 12, scope: !31) +!33 = !DILocation(line: 14, column: 6, scope: !26) +!34 = !DILocation(line: 15, column: 19, scope: !31) +!35 = !DILocation(line: 15, column: 12, scope: !31) +!36 = !DILocation(line: 15, column: 6, scope: !31) +!37 = !DILocation(line: 16, column: 1, scope: !26) Index: test/Analysis/Sceptre/inline.ll =================================================================== --- test/Analysis/Sceptre/inline.ll +++ test/Analysis/Sceptre/inline.ll @@ -0,0 +1,137 @@ +; RUN: opt -O2 -enable-sceptre -S < %s 2>&1 >/dev/null | FileCheck %s + +; Sceptre inlining test. The Sceptre pass is inserted into the optimization +; pipeline after inlining so that vulnerabilities which occur across function +; boundaries can be detected. This is more accurate than using the Sceptre +; escape flags to display vulnerabilities that escape (obviously these flags +; must be used for non-inline functions). + +; Generated from the following C code: + +; extern char array1[]; +; extern char array2[]; +; extern unsigned array1_size; +; +; char is_valid_idx(unsigned index) { +; return index < array1_size; +; } +; +; char array1_load(unsigned index) { +; return array1[index]; +; } +; +; char array2_load(unsigned index) { +; return array2[index]; +; } +; +; char foo(unsigned index) { +; if(is_valid_idx(index)) +; return array2_load(array1_load(index)); +; return -1; +; } + +; CHECK: warning: inline.c:10:10: in function array1_load: found vulnerable load +; CHECK: note: inlined into function foo at: inline.c:19:24 +; CHECK: note: bounds check with index "index" is at: inline.c:6:16: in function is_valid_idx +; CHECK: note: inlined into function foo at: inline.c:18:6 + +@array1_size = external dso_local local_unnamed_addr global i32, align 4 +@array1 = external dso_local local_unnamed_addr global [0 x i8], align 1 +@array2 = external dso_local local_unnamed_addr global [0 x i8], align 1 + +define dso_local signext i8 @is_valid_idx(i32 %index) local_unnamed_addr !dbg !7 { +entry: + call void @llvm.dbg.value(metadata i32 %index, metadata !13, metadata !DIExpression()), !dbg !14 + %0 = load i32, i32* @array1_size, align 4, !dbg !15, !tbaa !16 + %cmp = icmp ugt i32 %0, %index, !dbg !20 + %conv1 = zext i1 %cmp to i8, !dbg !21 + ret i8 %conv1, !dbg !22 +} + +define dso_local signext i8 @array1_load(i32 %index) local_unnamed_addr !dbg !23 { +entry: + call void @llvm.dbg.value(metadata i32 %index, metadata !25, metadata !DIExpression()), !dbg !26 + %idxprom = zext i32 %index to i64, !dbg !27 + %arrayidx = getelementptr inbounds [0 x i8], [0 x i8]* @array1, i64 0, i64 %idxprom, !dbg !27 + %0 = load i8, i8* %arrayidx, align 1, !dbg !27, !tbaa !28 + ret i8 %0, !dbg !29 +} + +define dso_local signext i8 @array2_load(i32 %index) local_unnamed_addr !dbg !30 { +entry: + call void @llvm.dbg.value(metadata i32 %index, metadata !32, metadata !DIExpression()), !dbg !33 + %idxprom = zext i32 %index to i64, !dbg !34 + %arrayidx = getelementptr inbounds [0 x i8], [0 x i8]* @array2, i64 0, i64 %idxprom, !dbg !34 + %0 = load i8, i8* %arrayidx, align 1, !dbg !34, !tbaa !28 + ret i8 %0, !dbg !35 +} + +define dso_local signext i8 @foo(i32 %index) local_unnamed_addr !dbg !36 { +entry: + call void @llvm.dbg.value(metadata i32 %index, metadata !38, metadata !DIExpression()), !dbg !39 + %call = tail call signext i8 @is_valid_idx(i32 %index), !dbg !40 + %tobool = icmp eq i8 %call, 0, !dbg !40 + br i1 %tobool, label %return, label %if.then, !dbg !42 + +if.then: ; preds = %entry + %call1 = tail call signext i8 @array1_load(i32 %index), !dbg !43 + %conv = sext i8 %call1 to i32, !dbg !43 + %call2 = tail call signext i8 @array2_load(i32 %conv), !dbg !44 + br label %return, !dbg !45 + +return: ; preds = %entry, %if.then + %retval.0 = phi i8 [ %call2, %if.then ], [ -1, %entry ] + ret i8 %retval.0, !dbg !46 +} + +declare void @llvm.dbg.value(metadata, metadata, metadata) + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!3, !4, !5} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 7.0.0 (trunk 325675)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2) +!1 = !DIFile(filename: "inline.c", directory: "") +!2 = !{} +!3 = !{i32 2, !"Dwarf Version", i32 4} +!4 = !{i32 2, !"Debug Info Version", i32 3} +!5 = !{i32 1, !"wchar_size", i32 4} +!7 = distinct !DISubprogram(name: "is_valid_idx", scope: !1, file: !1, line: 5, type: !8, isLocal: false, isDefinition: true, scopeLine: 5, flags: DIFlagPrototyped, isOptimized: true, unit: !0, variables: !12) +!8 = !DISubroutineType(types: !9) +!9 = !{!10, !11} +!10 = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_signed_char) +!11 = !DIBasicType(name: "unsigned int", size: 32, encoding: DW_ATE_unsigned) +!12 = !{!13} +!13 = !DILocalVariable(name: "index", arg: 1, scope: !7, file: !1, line: 5, type: !11) +!14 = !DILocation(line: 5, column: 28, scope: !7) +!15 = !DILocation(line: 6, column: 18, scope: !7) +!16 = !{!17, !17, i64 0} +!17 = !{!"int", !18, i64 0} +!18 = !{!"omnipotent char", !19, i64 0} +!19 = !{!"Simple C/C++ TBAA"} +!20 = !DILocation(line: 6, column: 16, scope: !7) +!21 = !DILocation(line: 6, column: 10, scope: !7) +!22 = !DILocation(line: 6, column: 3, scope: !7) +!23 = distinct !DISubprogram(name: "array1_load", scope: !1, file: !1, line: 9, type: !8, isLocal: false, isDefinition: true, scopeLine: 9, flags: DIFlagPrototyped, isOptimized: true, unit: !0, variables: !24) +!24 = !{!25} +!25 = !DILocalVariable(name: "index", arg: 1, scope: !23, file: !1, line: 9, type: !11) +!26 = !DILocation(line: 9, column: 27, scope: !23) +!27 = !DILocation(line: 10, column: 10, scope: !23) +!28 = !{!18, !18, i64 0} +!29 = !DILocation(line: 10, column: 3, scope: !23) +!30 = distinct !DISubprogram(name: "array2_load", scope: !1, file: !1, line: 13, type: !8, isLocal: false, isDefinition: true, scopeLine: 13, flags: DIFlagPrototyped, isOptimized: true, unit: !0, variables: !31) +!31 = !{!32} +!32 = !DILocalVariable(name: "index", arg: 1, scope: !30, file: !1, line: 13, type: !11) +!33 = !DILocation(line: 13, column: 27, scope: !30) +!34 = !DILocation(line: 14, column: 10, scope: !30) +!35 = !DILocation(line: 14, column: 3, scope: !30) +!36 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 17, type: !8, isLocal: false, isDefinition: true, scopeLine: 17, flags: DIFlagPrototyped, isOptimized: true, unit: !0, variables: !37) +!37 = !{!38} +!38 = !DILocalVariable(name: "index", arg: 1, scope: !36, file: !1, line: 17, type: !11) +!39 = !DILocation(line: 17, column: 19, scope: !36) +!40 = !DILocation(line: 18, column: 6, scope: !41) +!41 = distinct !DILexicalBlock(scope: !36, file: !1, line: 18, column: 6) +!42 = !DILocation(line: 18, column: 6, scope: !36) +!43 = !DILocation(line: 19, column: 24, scope: !41) +!44 = !DILocation(line: 19, column: 12, scope: !41) +!45 = !DILocation(line: 19, column: 5, scope: !41) +!46 = !DILocation(line: 21, column: 1, scope: !36) Index: test/Analysis/Sceptre/struct.ll =================================================================== --- test/Analysis/Sceptre/struct.ll +++ test/Analysis/Sceptre/struct.ll @@ -0,0 +1,194 @@ +; RUN: opt -sceptre -S < %s 2>&1 >/dev/null | FileCheck %s + +; The Sceptre detector recognizes when a bounds-checked integer is used as +; an index to load a value, which is then used in address computation for +; a second load. Currently it does place restrictions on the size or type +; of the loads. This means arrays of structs are recognized as vulnerable, +; as is access of a structure member through a pointer. In both cases they +; appear the same (load of a value which is used to compute the address of +; another load). The basic Spectre bounds check exploit, however, involved +; only byte arrays. Checks can be added later if it is determined that +; the examples in this file are not vulnerable. + +; Generated from the following C code: + +; struct foo { +; float f; +; char c; +; int i; +; }; +; +; extern struct foo struct_array[]; +; extern struct foo *ptr_array[]; +; extern char char_array[]; +; extern int int_array[]; +; extern int array_size; +; extern char temp; +; +; void foo(unsigned index) { +; if(index < array_size) +; temp &= char_array[struct_array[index].c]; +; } +; +; void foo2(unsigned index) { +; if(index < array_size) +; temp &= ptr_array[index]->c; +; } +; +; void foo3(unsigned index) { +; if(index < array_size) +; temp &= int_array[struct_array[index].i]; +; } + +; CHECK: warning: struct.c:16:44: in function foo: found vulnerable load +; CHECK: note: bounds check with index "index" is at: struct.c:15:12: in function foo +; CHECK: warning: struct.c:21:13: in function foo2: found vulnerable load +; CHECK: note: bounds check with index "index" is at: struct.c:20:12: in function foo2 +; CHECK: warning: struct.c:26:43: in function foo3: found vulnerable load +; CHECK: note: bounds check with index "index" is at: struct.c:25:12: in function foo3 + +%struct.foo = type { float, i8, i32 } + +@array_size = external dso_local local_unnamed_addr global i32, align 4 +@char_array = external dso_local local_unnamed_addr global [0 x i8], align 1 +@struct_array = external dso_local local_unnamed_addr global [0 x %struct.foo], align 4 +@temp = external dso_local local_unnamed_addr global i8, align 1 +@ptr_array = external dso_local local_unnamed_addr global [0 x %struct.foo*], align 8 +@int_array = external dso_local local_unnamed_addr global [0 x i32], align 4 + +define dso_local void @foo(i32 %index) local_unnamed_addr !dbg !7 { +entry: + call void @llvm.dbg.value(metadata i32 %index, metadata !12, metadata !DIExpression()), !dbg !13 + %0 = load i32, i32* @array_size, align 4, !dbg !14, !tbaa !16 + %cmp = icmp ugt i32 %0, %index, !dbg !20 + br i1 %cmp, label %if.then, label %if.end, !dbg !21 + +if.then: ; preds = %entry + %idxprom = zext i32 %index to i64, !dbg !22 + %c = getelementptr inbounds [0 x %struct.foo], [0 x %struct.foo]* @struct_array, i64 0, i64 %idxprom, i32 1, !dbg !23 + %1 = load i8, i8* %c, align 4, !dbg !23, !tbaa !24 + %idxprom1 = sext i8 %1 to i64, !dbg !27 + %arrayidx2 = getelementptr inbounds [0 x i8], [0 x i8]* @char_array, i64 0, i64 %idxprom1, !dbg !27 + %2 = load i8, i8* %arrayidx2, align 1, !dbg !27, !tbaa !28 + %3 = load i8, i8* @temp, align 1, !dbg !29, !tbaa !28 + %and6 = and i8 %3, %2, !dbg !29 + store i8 %and6, i8* @temp, align 1, !dbg !29, !tbaa !28 + br label %if.end, !dbg !30 + +if.end: ; preds = %if.then, %entry + ret void, !dbg !31 +} + +define dso_local void @foo2(i32 %index) local_unnamed_addr !dbg !32 { +entry: + call void @llvm.dbg.value(metadata i32 %index, metadata !34, metadata !DIExpression()), !dbg !35 + %0 = load i32, i32* @array_size, align 4, !dbg !36, !tbaa !16 + %cmp = icmp ugt i32 %0, %index, !dbg !38 + br i1 %cmp, label %if.then, label %if.end, !dbg !39 + +if.then: ; preds = %entry + %idxprom = zext i32 %index to i64, !dbg !40 + %arrayidx = getelementptr inbounds [0 x %struct.foo*], [0 x %struct.foo*]* @ptr_array, i64 0, i64 %idxprom, !dbg !40 + %1 = load %struct.foo*, %struct.foo** %arrayidx, align 8, !dbg !40, !tbaa !41 + %c = getelementptr inbounds %struct.foo, %struct.foo* %1, i64 0, i32 1, !dbg !43 + %2 = load i8, i8* %c, align 4, !dbg !43, !tbaa !24 + %3 = load i8, i8* @temp, align 1, !dbg !44, !tbaa !28 + %and4 = and i8 %3, %2, !dbg !44 + store i8 %and4, i8* @temp, align 1, !dbg !44, !tbaa !28 + br label %if.end, !dbg !45 + +if.end: ; preds = %if.then, %entry + ret void, !dbg !46 +} + +define dso_local void @foo3(i32 %index) local_unnamed_addr !dbg !47 { +entry: + call void @llvm.dbg.value(metadata i32 %index, metadata !49, metadata !DIExpression()), !dbg !50 + %0 = load i32, i32* @array_size, align 4, !dbg !51, !tbaa !16 + %cmp = icmp ugt i32 %0, %index, !dbg !53 + br i1 %cmp, label %if.then, label %if.end, !dbg !54 + +if.then: ; preds = %entry + %idxprom = zext i32 %index to i64, !dbg !55 + %i = getelementptr inbounds [0 x %struct.foo], [0 x %struct.foo]* @struct_array, i64 0, i64 %idxprom, i32 2, !dbg !56 + %1 = load i32, i32* %i, align 4, !dbg !56, !tbaa !57 + %idxprom1 = sext i32 %1 to i64, !dbg !58 + %arrayidx2 = getelementptr inbounds [0 x i32], [0 x i32]* @int_array, i64 0, i64 %idxprom1, !dbg !58 + %2 = load i32, i32* %arrayidx2, align 4, !dbg !58, !tbaa !16 + %3 = load i8, i8* @temp, align 1, !dbg !59, !tbaa !28 + %4 = trunc i32 %2 to i8, !dbg !59 + %conv3 = and i8 %3, %4, !dbg !59 + store i8 %conv3, i8* @temp, align 1, !dbg !59, !tbaa !28 + br label %if.end, !dbg !60 + +if.end: ; preds = %if.then, %entry + ret void, !dbg !61 +} + +; Function Attrs: nounwind readnone speculatable +declare void @llvm.dbg.value(metadata, metadata, metadata) #1 + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!3, !4, !5} + +!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 7.0.0 (trunk 325675)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2) +!1 = !DIFile(filename: "struct.c", directory: "") +!2 = !{} +!3 = !{i32 2, !"Dwarf Version", i32 4} +!4 = !{i32 2, !"Debug Info Version", i32 3} +!5 = !{i32 1, !"wchar_size", i32 4} +!7 = distinct !DISubprogram(name: "foo", scope: !1, file: !1, line: 14, type: !8, isLocal: false, isDefinition: true, scopeLine: 14, flags: DIFlagPrototyped, isOptimized: true, unit: !0, variables: !11) +!8 = !DISubroutineType(types: !9) +!9 = !{null, !10} +!10 = !DIBasicType(name: "unsigned int", size: 32, encoding: DW_ATE_unsigned) +!11 = !{!12} +!12 = !DILocalVariable(name: "index", arg: 1, scope: !7, file: !1, line: 14, type: !10) +!13 = !DILocation(line: 14, column: 19, scope: !7) +!14 = !DILocation(line: 15, column: 14, scope: !15) +!15 = distinct !DILexicalBlock(scope: !7, file: !1, line: 15, column: 6) +!16 = !{!17, !17, i64 0} +!17 = !{!"int", !18, i64 0} +!18 = !{!"omnipotent char", !19, i64 0} +!19 = !{!"Simple C/C++ TBAA"} +!20 = !DILocation(line: 15, column: 12, scope: !15) +!21 = !DILocation(line: 15, column: 6, scope: !7) +!22 = !DILocation(line: 16, column: 24, scope: !15) +!23 = !DILocation(line: 16, column: 44, scope: !15) +!24 = !{!25, !18, i64 4} +!25 = !{!"foo", !26, i64 0, !18, i64 4, !17, i64 8} +!26 = !{!"float", !18, i64 0} +!27 = !DILocation(line: 16, column: 13, scope: !15) +!28 = !{!18, !18, i64 0} +!29 = !DILocation(line: 16, column: 10, scope: !15) +!30 = !DILocation(line: 16, column: 5, scope: !15) +!31 = !DILocation(line: 17, column: 1, scope: !7) +!32 = distinct !DISubprogram(name: "foo2", scope: !1, file: !1, line: 19, type: !8, isLocal: false, isDefinition: true, scopeLine: 19, flags: DIFlagPrototyped, isOptimized: true, unit: !0, variables: !33) +!33 = !{!34} +!34 = !DILocalVariable(name: "index", arg: 1, scope: !32, file: !1, line: 19, type: !10) +!35 = !DILocation(line: 19, column: 20, scope: !32) +!36 = !DILocation(line: 20, column: 14, scope: !37) +!37 = distinct !DILexicalBlock(scope: !32, file: !1, line: 20, column: 6) +!38 = !DILocation(line: 20, column: 12, scope: !37) +!39 = !DILocation(line: 20, column: 6, scope: !32) +!40 = !DILocation(line: 21, column: 13, scope: !37) +!41 = !{!42, !42, i64 0} +!42 = !{!"any pointer", !18, i64 0} +!43 = !DILocation(line: 21, column: 31, scope: !37) +!44 = !DILocation(line: 21, column: 10, scope: !37) +!45 = !DILocation(line: 21, column: 5, scope: !37) +!46 = !DILocation(line: 22, column: 1, scope: !32) +!47 = distinct !DISubprogram(name: "foo3", scope: !1, file: !1, line: 24, type: !8, isLocal: false, isDefinition: true, scopeLine: 24, flags: DIFlagPrototyped, isOptimized: true, unit: !0, variables: !48) +!48 = !{!49} +!49 = !DILocalVariable(name: "index", arg: 1, scope: !47, file: !1, line: 24, type: !10) +!50 = !DILocation(line: 24, column: 20, scope: !47) +!51 = !DILocation(line: 25, column: 14, scope: !52) +!52 = distinct !DILexicalBlock(scope: !47, file: !1, line: 25, column: 6) +!53 = !DILocation(line: 25, column: 12, scope: !52) +!54 = !DILocation(line: 25, column: 6, scope: !47) +!55 = !DILocation(line: 26, column: 23, scope: !52) +!56 = !DILocation(line: 26, column: 43, scope: !52) +!57 = !{!25, !17, i64 8} +!58 = !DILocation(line: 26, column: 13, scope: !52) +!59 = !DILocation(line: 26, column: 10, scope: !52) +!60 = !DILocation(line: 26, column: 5, scope: !52) +!61 = !DILocation(line: 27, column: 1, scope: !47)