-
Notifications
You must be signed in to change notification settings - Fork 2
Feature: Detect Uninitialized Variables Uses #678
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
511ed57
feat: add basic test
yunsecode 4436f43
chore: add prints
yunsecode a86644d
fix: idk
yunsecode 6ca6e37
chore: fk every tests works
yunsecode 2e1f7b8
fix: err in while
yunsecode b48b888
fix: err in multile condition in while
yunsecode 1fc674f
chore: delete all print lines
yunsecode 907b576
fix: createConstantString
yunsecode 6b00157
chore: change all comments
yunsecode a51d68b
chore: delete comment in tests
yunsecode c32af09
chore: change for that tests works
yunsecode d009342
chore: detect-uninitialized not in lowering
yunsecode 4dda8ae
chore: add some tests
yunsecode 5d4991d
chore: change some find to look up
yunsecode adcc6e9
fix: copilot comments
yunsecode 19efdc2
chore: delete unused include
yunsecode 28d0c5f
fix: copilot comments
yunsecode e974f83
refactore: private functions in one area
yunsecode b063171
chore: delete unused imcludes
yunsecode 955c6e4
chore: delete unused condition
yunsecode 9ac7ffc
chore: add some tests
yunsecode c72837a
fix: uninitialized checks for arrays (handle bitcast)
Snowy1803 38a2547
style: simplify uninitialized pass
Snowy1803 06df8b4
test: fix uninitialized var sema test
Snowy1803 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 <llvm/ADT/DenseMap.h> | ||
| #include <llvm/ADT/DenseSet.h> | ||
| #include <llvm/ADT/SmallVector.h> | ||
|
|
||
| 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<DetectUninitializedPass> { | ||
| private: | ||
| DiagnosticManager &diagManager; | ||
|
|
||
| llvm::DenseMap<gil::BasicBlock *, llvm::SmallVector<gil::BasicBlock *, 4>> | ||
| predecessorMap; | ||
|
|
||
| enum class MemoryState { Uninitialized, MaybeInitialized, Initialized }; | ||
|
|
||
| llvm::DenseMap<gil::Value, MemoryState> currentState; | ||
|
|
||
| llvm::DenseMap<gil::BasicBlock *, llvm::DenseMap<gil::Value, MemoryState>> | ||
| 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<gil::BasicBlock *> visited; | ||
| llvm::SmallVector<gil::BasicBlock *, 32> 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<gil::BasicBlock *, 4> successors; | ||
|
|
||
| if (auto *brInst = llvm::dyn_cast<gil::BrInst>(terminator)) { | ||
| if (auto *dest = brInst->getDestination()) { | ||
| successors.push_back(dest); | ||
| } | ||
| } else if (auto *condBr | ||
| = llvm::dyn_cast<gil::CondBrInst>(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<gil::BasicBlock *> getPredecessors(gil::BasicBlock *bb) | ||
| { | ||
| auto it = predecessorMap.find(bb); | ||
| if (it != predecessorMap.end()) { | ||
| return it->second; | ||
| } | ||
| return {}; | ||
| } | ||
|
|
||
| void analyzeBasicBlockState( | ||
| gil::BasicBlock *bb, llvm::DenseMap<gil::Value, MemoryState> &state | ||
| ) | ||
| { | ||
| for (auto &inst : bb->getInstructions()) { | ||
| if (auto *store = llvm::dyn_cast<gil::StoreInst>(&inst)) { | ||
| state[store->getDest()] = MemoryState::Initialized; | ||
| } else if (auto *alloca = llvm::dyn_cast<gil::AllocaInst>(&inst)) { | ||
| state[alloca->getResult(0)] = MemoryState::Uninitialized; | ||
| } else if (auto *ptrOffsets | ||
| = llvm::dyn_cast<gil::PtrOffsetInst>(&inst)) { | ||
| state[ptrOffsets->getResult(0)] | ||
| = state.lookup(ptrOffsets->getBasePtr()); | ||
| } else if (auto *structFieldPtr | ||
| = llvm::dyn_cast<gil::StructFieldPtrInst>(&inst)) { | ||
| state[structFieldPtr->getResult(0)] | ||
| = state.lookup(structFieldPtr->getStructPtr()); | ||
| } else if (auto *bitcastInst | ||
| = llvm::dyn_cast<gil::BitcastInst>(&inst)) { | ||
| auto source = bitcastInst->getOperand(); | ||
| auto result = bitcastInst->getResult(0); | ||
|
|
||
| if (llvm::isa<glu::types::PointerTy>(&*source.getType()) | ||
| && llvm::isa<glu::types::PointerTy>(&*result.getType())) { | ||
| state[result] = state.lookup(source); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| static MemoryState getTrackedStateOrDefault( | ||
| gil::Value value, | ||
| llvm::DenseMap<gil::Value, MemoryState> 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<gil::Value> 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) { | ||
yunsecode marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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); | ||
yunsecode marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| 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<gil::StructFieldPtrInst>( | ||
| 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<glu::types::PointerTy>(&*value.getType()) | ||
| || !llvm::isa<glu::types::PointerTy>(&*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 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.