diff --git a/include/Optimizer/GILPasses.def b/include/Optimizer/GILPasses.def index 5382c858..c8965393 100644 --- a/include/Optimizer/GILPasses.def +++ b/include/Optimizer/GILPasses.def @@ -8,6 +8,7 @@ GIL_PASS("void-main", VoidMainPass) GIL_PASS("dce", DeadCodeEliminationPass) GIL_PASS("unreachable-checker", UnreachableInstChecker) +GIL_PASS("detect-uninitialized", GILDetectUninitializedPass) // Lowering passes GIL_PASS("drop-lowering", DropLoweringPass) diff --git a/lib/Optimizer/CMakeLists.txt b/lib/Optimizer/CMakeLists.txt index 1bd22138..c6f979b5 100644 --- a/lib/Optimizer/CMakeLists.txt +++ b/lib/Optimizer/CMakeLists.txt @@ -24,4 +24,5 @@ target_sources(Optimizer GILPasses/UnreachableInstChecker.cpp GILPasses/DropLoweringPass.cpp GILPasses/CopyLoweringPass.cpp + GILPasses/DetectUninitializedPass.cpp ) diff --git a/lib/Optimizer/GILPasses/DetectUninitializedPass.cpp b/lib/Optimizer/GILPasses/DetectUninitializedPass.cpp new file mode 100644 index 00000000..556eb59d --- /dev/null +++ b/lib/Optimizer/GILPasses/DetectUninitializedPass.cpp @@ -0,0 +1,356 @@ +#include "Basic/Diagnostic.hpp" +#include "Basic/SourceLocation.hpp" +#include "GIL/BasicBlock.hpp" +#include "GIL/Function.hpp" +#include "GIL/InstVisitor.hpp" +#include "GIL/Instructions.hpp" +#include "GIL/Module.hpp" +#include "PassManager.hpp" + +#include +#include +#include + +namespace glu::optimizer { + +/// @brief GIL optimizer pass that reports loads and stores involving +/// uninitialized memory locations. +/// +/// The pass performs a fixed-point data-flow analysis across all basic +/// blocks in a GIL function, tracking a tri-state initialization lattice for +/// each memory value. Diagnostics are emitted when a load observes an +/// uninitialized location or a store that expects an initialized target sees +/// only a maybe-initialized state. +class DetectUninitializedPass + : public gil::InstVisitor { +private: + DiagnosticManager &diagManager; + + llvm::DenseMap> + predecessorMap; + + enum class MemoryState { Uninitialized, MaybeInitialized, Initialized }; + + llvm::DenseMap currentState; + + llvm::DenseMap> + blockEndStates; + + static MemoryState mergeMemoryStates(MemoryState lhs, MemoryState rhs) + { + if (lhs == rhs) { + return lhs; + } + + return MemoryState::MaybeInitialized; + } + + void buildPredecessorMap(gil::Function *func) + { + predecessorMap.clear(); + + llvm::DenseSet visited; + llvm::SmallVector stack; + + if (func->getBasicBlockCount() > 0) { + stack.push_back(func->getEntryBlock()); + } + + while (!stack.empty()) { + gil::BasicBlock *currentBB = stack.pop_back_val(); + + if (!visited.insert(currentBB).second) { + continue; + } + + auto *terminator = currentBB->getTerminator(); + if (!terminator) + continue; + + llvm::SmallVector successors; + + if (auto *brInst = llvm::dyn_cast(terminator)) { + if (auto *dest = brInst->getDestination()) { + successors.push_back(dest); + } + } else if (auto *condBr + = llvm::dyn_cast(terminator)) { + if (auto *thenBlock = condBr->getThenBlock()) { + successors.push_back(thenBlock); + } + if (auto *elseBlock = condBr->getElseBlock()) { + successors.push_back(elseBlock); + } + } + + for (auto *successor : successors) { + predecessorMap[successor].push_back(currentBB); + + if (visited.find(successor) == visited.end()) { + stack.push_back(successor); + } + } + } + } + + llvm::ArrayRef getPredecessors(gil::BasicBlock *bb) + { + auto it = predecessorMap.find(bb); + if (it != predecessorMap.end()) { + return it->second; + } + return {}; + } + + void analyzeBasicBlockState( + gil::BasicBlock *bb, llvm::DenseMap &state + ) + { + for (auto &inst : bb->getInstructions()) { + if (auto *store = llvm::dyn_cast(&inst)) { + state[store->getDest()] = MemoryState::Initialized; + } else if (auto *alloca = llvm::dyn_cast(&inst)) { + state[alloca->getResult(0)] = MemoryState::Uninitialized; + } else if (auto *ptrOffsets + = llvm::dyn_cast(&inst)) { + state[ptrOffsets->getResult(0)] + = state.lookup(ptrOffsets->getBasePtr()); + } else if (auto *structFieldPtr + = llvm::dyn_cast(&inst)) { + state[structFieldPtr->getResult(0)] + = state.lookup(structFieldPtr->getStructPtr()); + } else if (auto *bitcastInst + = llvm::dyn_cast(&inst)) { + auto source = bitcastInst->getOperand(); + auto result = bitcastInst->getResult(0); + + if (llvm::isa(&*source.getType()) + && llvm::isa(&*result.getType())) { + state[result] = state.lookup(source); + } + } + } + } + + static MemoryState getTrackedStateOrDefault( + gil::Value value, + llvm::DenseMap const &stateMap, + MemoryState defaultState + ) + { + auto it = stateMap.find(value); + if (it != stateMap.end()) { + return it->second; + } + return defaultState; + } + + void mergeStatesFromPredecessors(gil::BasicBlock *bb) + { + auto preds = getPredecessors(bb); + if (preds.empty()) { + currentState.clear(); + return; + } + + llvm::DenseSet allValues; + + for (auto *pred : preds) { + auto predIt = blockEndStates.find(pred); + if (predIt != blockEndStates.end()) { + for (auto const &entry : predIt->second) { + allValues.insert(entry.first); + } + } + } + + currentState.clear(); + + for (gil::Value value : allValues) { + bool hasAnyState = false; + MemoryState mergedState = MemoryState::Uninitialized; + + for (auto *pred : preds) { + auto predIt = blockEndStates.find(pred); + if (predIt == blockEndStates.end()) { + continue; + } + + auto valueIt = predIt->second.find(value); + if (valueIt != predIt->second.end()) { + if (!hasAnyState) { + mergedState = valueIt->second; + hasAnyState = true; + } else { + mergedState + = mergeMemoryStates(mergedState, valueIt->second); + } + } + } + + if (!hasAnyState) { + currentState[value] = MemoryState::Uninitialized; + } else { + currentState[value] = mergedState; + } + } + } + +public: + DetectUninitializedPass(DiagnosticManager &diagManager) + : diagManager(diagManager) + { + } + + void beforeVisitFunction(gil::Function *func) + { + buildPredecessorMap(func); + + blockEndStates.clear(); + currentState.clear(); + + bool changed = true; + int iteration = 0; + while (changed && iteration < 100) { + changed = false; + iteration++; + + for (auto &bb : func->getBasicBlocks()) { + auto oldState = blockEndStates.lookup(&bb); + + mergeStatesFromPredecessors(&bb); + + analyzeBasicBlockState(&bb, currentState); + + if (blockEndStates[&bb] != oldState) { + changed = true; + } + + blockEndStates[&bb] = currentState; + } + } + } + + void beforeVisitBasicBlock(gil::BasicBlock *bb) + { + auto it = blockEndStates.find(bb); + if (it != blockEndStates.end()) { + mergeStatesFromPredecessors(bb); + } else { + currentState.clear(); + } + } + + void afterVisitBasicBlock(gil::BasicBlock *bb) + { + blockEndStates[bb] = currentState; + } + + void visitStoreInst(gil::StoreInst *store) + { + gil::Value destPtr = store->getDest(); + + MemoryState prevState = currentState.lookup(destPtr); + + if (prevState == MemoryState::Uninitialized) { + store->setOwnershipKind(gil::StoreOwnershipKind::Init); + } else { + store->setOwnershipKind(gil::StoreOwnershipKind::Set); + } + + if (prevState == MemoryState::MaybeInitialized) { + diagManager.error( + store->getLocation(), + "Store to memory location with uncertain initialization" + ); + } + + currentState[destPtr] = MemoryState::Initialized; + if (auto *structFieldPtr + = llvm::dyn_cast_or_null( + destPtr.getDefiningInstruction() + )) { + gil::Value baseStruct = structFieldPtr->getStructPtr(); + currentState[baseStruct] = MemoryState::Initialized; + } + } + + void visitLoadInst(gil::LoadInst *load) + { + gil::Value srcPtr = load->getValue(); + + MemoryState state = currentState.lookup(srcPtr); + if (!currentState.contains(srcPtr)) { + // Default to initialized for untracked values + state = MemoryState::Initialized; + } + + if (state != MemoryState::Initialized) { + diagManager.error( + load->getLocation(), "Load from uninitialized memory location" + ); + } + + currentState[load->getResult(0)] = state; + + if (load->getOwnershipKind() == gil::LoadOwnershipKind::Take) { + currentState[srcPtr] = MemoryState::Uninitialized; + } + } + + void visitAllocaInst(gil::AllocaInst *alloca) + { + currentState[alloca->getResult(0)] = MemoryState::Uninitialized; + } + + void visitPtrOffsetInst(gil::PtrOffsetInst *inst) + { + currentState[inst->getResult(0)] = getTrackedStateOrDefault( + inst->getBasePtr(), currentState, MemoryState::Uninitialized + ); + } + + void visitStructFieldPtrInst(gil::StructFieldPtrInst *inst) + { + currentState[inst->getResult(0)] = getTrackedStateOrDefault( + inst->getStructPtr(), currentState, MemoryState::Uninitialized + ); + } + + void visitBitcastInst(gil::BitcastInst *inst) + { + auto value = inst->getOperand(); + auto result = inst->getResult(0); + + if (!llvm::isa(&*value.getType()) + || !llvm::isa(&*result.getType())) { + return; + } + + MemoryState sourceState = getTrackedStateOrDefault( + value, currentState, MemoryState::Uninitialized + ); + + currentState[result] = sourceState; + } + + void visitStructExtractInst(gil::StructExtractInst *inst) + { + currentState[inst->getResult(0)] = MemoryState::Initialized; + } + + void afterVisitFunction(gil::Function *func) + { + predecessorMap.clear(); + blockEndStates.clear(); + currentState.clear(); + } +}; + +void PassManager::runGILDetectUninitializedPass() +{ + DetectUninitializedPass pass(_diagManager); + pass.visit(_module); +} + +} // namespace glu::optimizer diff --git a/stdlib/defaultImports/stringType.glu b/stdlib/defaultImports/stringType.glu index 2fa6a236..4b519946 100644 --- a/stdlib/defaultImports/stringType.glu +++ b/stdlib/defaultImports/stringType.glu @@ -5,11 +5,5 @@ public struct String { } @inline @no_mangling public func glu_createConstantString(s: *Char, length: Int) -> String { - var str: String; - - str.data = s; - str.length = length; - str.isAllocated = false; - - return str; + return {s, length, false}; } diff --git a/test/functional/Sema/use_uninitialized_var.glu b/test/functional/Sema/use_uninitialized_var.glu new file mode 100644 index 00000000..1f0205f3 --- /dev/null +++ b/test/functional/Sema/use_uninitialized_var.glu @@ -0,0 +1,194 @@ +// +// RUN: not gluc %s -c -o %t 2>&1 | FileCheck -v %s +// + +@no_mangling +func memcmp(s1: *Char, s2: *Char, n: Int) -> Int; + +@no_mangling +func memcpy(dst: *Char, src: *Char, n: Int) -> *Char; + +import std::allocate; +import std::print; + +func rfeergerg(machin: Bool) -> Void { + var x : Int; + + if (machin) { + x = 0; + } + // CHECK: use_uninitialized_var.glu:21:11: error: Load from uninitialized memory location + print(x); +} + +func ergfer(machin: Bool) -> Void { + var x : Int; + + if (machin) { + x = 0; + } else { + x = 1; + } + // OK + print(x); +} + +func qewrff(machin: Bool) -> Void { + var x : Int; + + if (machin) { + x = 0; // init + } else { + x = 1; // init + } + x = x + 1; // set + // OK + print(x); +} + + +func rthg(machin: Bool) -> Void { + var x : Int; + + if (machin) { + x = 0; // init + } + // CHECK: use_uninitialized_var.glu:57:7: error: Store to memory location with uncertain initialization + x = 1; + print(x); +} + + +func qewrffasddd(machin: Bool) -> Void { + var x : Int; + + if (machin) { + x = 0; // init + } else { + x = 1; // init + } + + if (machin) { + x = 0; // set + } else { + x = 1; // set + } + x = x + 1; // set + // OK + print(x); +} + +func qewrffasddd() -> Void { + var result: String = ""; + result.data = allocate(1); + // OK + result.data[0]; +} + +func <(lhs: Char, rhs: Char) -> Bool { + return (lhs as UInt8) < (rhs as UInt8); +} + +public func stringToInt(s: String) -> Int { + var i: Int = 0; + var result: Int = 0; + + // Skip leading whitespace + while (i < s.length) { + i = i + 1; + } + + // OK + return result; +} + +func asd(s: String) -> Void { + s.data[0]; +} + +public func stringIndexOf(i: Int, s: String) -> Int { + + while (i <= s.length) { + s.data[i]; + } + + return -1; +} + +func createStringFromPointer(s: *Char, length: Int, allocated: Bool) -> String { + return { s, length, allocated }; +} + +func ddsdadasfasf(machin: Bool) -> Void { + var x : Int; + var y : Int = 0; + + if (machin) { + x = 0; + } + + while (machin) { + y = y + 1; + + if (y > 10) { + y = 10; + break; + } + } + // CHECK: use_uninitialized_var.glu:139:11: error: Load from uninitialized memory location + print(x); +} + +func ddsdadasdadasfasf(machin: Bool) -> Void { + var x : Int; + var y : Int = 0; + var z : Int; + + if (machin) { + x = 0; + } + + while (machin) { + y = y + 1; + + if (y > 10) { + y = 10; + break; + } + // CHECK: use_uninitialized_var.glu:159:15: error: Load from uninitialized memory location + print(z); + + // CHECK: use_uninitialized_var.glu:162:13: error: Load from uninitialized memory location + if (z == 1) { + // CHECK: use_uninitialized_var.glu:164:15: error: Store to memory location with uncertain initialization + z = 0; + } + } + // CHECK: use_uninitialized_var.glu:168:11: error: Load from uninitialized memory location + print(x); + // CHECK: use_uninitialized_var.glu:170:11: error: Load from uninitialized memory location + print(z); +} + +func gjkjalshdlhaskld () -> Void { + var x : Int; + while(true) { + x = 1; + break; + } + // CHECK: use_uninitialized_var.glu:180:11: error: Load from uninitialized memory location + print(x); +} + +func gjkjaldasdshdlhaskld () -> Void { + var x : Int; + + while(true) { + // CHECK: use_uninitialized_var.glu:188:11: error: Store to memory location with uncertain initialization + x = 1; + } + // CHECK: use_uninitialized_var.glu:191:11: error: Load from uninitialized memory location + print(x); +} + +// CHECK: 11 error(s)