diff --git a/clang/include/clang/CIR/Analysis/CIRAnalysisKind.h b/clang/include/clang/CIR/Analysis/CIRAnalysisKind.h new file mode 100644 index 0000000000000..304acfcf433db --- /dev/null +++ b/clang/include/clang/CIR/Analysis/CIRAnalysisKind.h @@ -0,0 +1,117 @@ +//===--- CIRAnalysisKind.h - CIR Analysis Pass Kinds -----------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +/// \file +/// Defines the CIR analysis pass kinds enum and related utilities. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_CIR_SEMA_CIRANALYSISKIND_H +#define LLVM_CLANG_CIR_SEMA_CIRANALYSISKIND_H + +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/raw_ostream.h" +#include +#include +#include + +namespace cir { + +/// Enumeration of available CIR semantic analysis passes +enum class CIRAnalysisKind : unsigned { + Unrecognized = 0, + FallThrough = 1 << 0, // Fallthrough warning analysis + UnreachableCode = 1 << 1, // Unreachable code detection + NullCheck = 1 << 2, // Null pointer checks + UninitializedVar = 1 << 3, // Uninitialized variable detection + // Add more analysis passes here as needed +}; + +/// A set of CIR analysis passes (bitmask) +class CIRAnalysisSet { + unsigned mask = 0; + +public: + CIRAnalysisSet() = default; + explicit CIRAnalysisSet(CIRAnalysisKind kind) + : mask(static_cast(kind)) {} + explicit CIRAnalysisSet(unsigned mask) : mask(mask) {} + + /// Check if a specific analysis is enabled + bool has(CIRAnalysisKind kind) const { + return (mask & static_cast(kind)) != 0; + } + + /// Enable a specific analysis + void enable(CIRAnalysisKind kind) { + mask |= static_cast(kind); + } + + /// Disable a specific analysis + void disable(CIRAnalysisKind kind) { + mask &= ~static_cast(kind); + } + + /// Check if any analysis is enabled + bool hasAny() const { return mask != 0; } + + /// Check if no analysis is enabled + bool empty() const { return mask == 0; } + + /// Get the raw mask value + unsigned getMask() const { return mask; } + + /// Union with another set + CIRAnalysisSet &operator|=(const CIRAnalysisSet &other) { + mask |= other.mask; + return *this; + } + + /// Union operator + CIRAnalysisSet operator|(const CIRAnalysisSet &other) const { + return CIRAnalysisSet(mask | other.mask); + } + + /// Intersection with another set + CIRAnalysisSet &operator&=(const CIRAnalysisSet &other) { + mask &= other.mask; + return *this; + } + + /// Intersection operator + CIRAnalysisSet operator&(const CIRAnalysisSet &other) const { + return CIRAnalysisSet(mask & other.mask); + } + + bool operator==(const CIRAnalysisSet &other) const { + return mask == other.mask; + } + + bool operator!=(const CIRAnalysisSet &other) const { + return mask != other.mask; + } + + /// Print the analysis set to an output stream + void print(llvm::raw_ostream &OS) const; +}; + +/// Parse a single analysis name into a CIRAnalysisKind +/// Returns std::nullopt if the name is not recognized +CIRAnalysisKind parseCIRAnalysisKind(llvm::StringRef name); + +/// Parse a list of analysis names (from command line) into a CIRAnalysisSet +/// Handles comma and semicolon separators +/// Invalid names are ignored and optionally reported via InvalidNames +CIRAnalysisSet parseCIRAnalysisList( + const std::vector &analysisList, + llvm::SmallVectorImpl *invalidNames = nullptr); + +} // namespace cir + +#endif // LLVM_CLANG_CIR_SEMA_CIRANALYSISKIND_H diff --git a/clang/include/clang/CIR/Analysis/FallThroughWarning.h b/clang/include/clang/CIR/Analysis/FallThroughWarning.h new file mode 100644 index 0000000000000..f78a07d5212c3 --- /dev/null +++ b/clang/include/clang/CIR/Analysis/FallThroughWarning.h @@ -0,0 +1,73 @@ +//===--- FallThroughWarning.h - CIR Fall-Through Analysis ------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +/// \file +/// Defines the FallThroughWarningPass for CIR fall-through analysis. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_CIR_SEMA_FALLTHROUGHWARNING_H +#define LLVM_CLANG_CIR_SEMA_FALLTHROUGHWARNING_H + +#include "clang/AST/Type.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Sema/AnalysisBasedWarnings.h" + +#include "mlir/IR/Block.h" +#include "mlir/IR/Operation.h" +#include "mlir/Support/LLVM.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" +namespace cir { +class FuncOp; +} // namespace cir + +namespace clang { +class Sema; +Decl *getDeclByName(ASTContext &context, StringRef name); +enum ControlFlowKind { + UnknownFallThrough, + NeverFallThrough, + MaybeFallThrough, + AlwaysFallThrough, + NeverFallThroughOrReturn +}; + +/// Configuration for fall-through diagnostics +struct CheckFallThroughDiagnostics { + unsigned diagFallThroughHasNoReturn = 0; + unsigned diagFallThroughReturnsNonVoid = 0; + unsigned diagNeverFallThroughOrReturn = 0; + unsigned funKind = 0; + SourceLocation funcLoc; + + static CheckFallThroughDiagnostics makeForFunction(Sema &s, const Decl *func); + static CheckFallThroughDiagnostics makeForCoroutine(const Decl *func); + static CheckFallThroughDiagnostics makeForBlock(); + static CheckFallThroughDiagnostics makeForLambda(); + bool checkDiagnostics(DiagnosticsEngine &d, bool returnsVoid, + bool hasNoReturn) const; +}; +/// Check if a return operation returns a phony value (uninitialized __retval) +bool isPhonyReturn(cir::ReturnOp returnOp); + +/// Pass for analyzing fall-through behavior in CIR functions +class FallThroughWarningPass { +public: + FallThroughWarningPass() = default; + + /// Check fall-through behavior for a CIR function body + void checkFallThroughForFuncBody(Sema &s, cir::FuncOp cfg, QualType blockType, + const CheckFallThroughDiagnostics &cd); + ControlFlowKind checkFallThrough(cir::FuncOp cfg); + mlir::DenseSet getLiveSet(cir::FuncOp cfg); +}; + +} // namespace clang + +#endif // LLVM_CLANG_CIR_SEMA_FALLTHROUGHWARNING_H diff --git a/clang/include/clang/Frontend/FrontendOptions.h b/clang/include/clang/Frontend/FrontendOptions.h index c919a53ae089e..a152309212bf8 100644 --- a/clang/include/clang/Frontend/FrontendOptions.h +++ b/clang/include/clang/Frontend/FrontendOptions.h @@ -420,6 +420,9 @@ class FrontendOptions { LLVM_PREFERRED_TYPE(bool) unsigned ClangIRDisableCIRVerifier : 1; + /// List of ClangIR semantic analysis passes to enable + std::vector ClangIRAnalysisList; + CodeCompleteOptions CodeCompleteOpts; /// Specifies the output format of the AST. diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 2f7434d8afe11..16821a7b6f230 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -3150,6 +3150,11 @@ def clangir_disable_verifier : Flag<["-"], "clangir-disable-verifier">, HelpText<"ClangIR: Disable MLIR module verifier">, MarshallingInfoFlag>; +def fclangir_analysis_EQ : CommaJoined<["-"], "fclangir-analysis=">, + Visibility<[ClangOption, CC1Option]>, + HelpText<"Enable ClangIR semantic analysis passes. Pass comma or semicolon separated list of analysis names">, + MarshallingInfoStringVector>; + defm clangir : BoolFOption<"clangir", FrontendOpts<"UseClangIRPipeline">, DefaultFalse, PosFlag, diff --git a/clang/lib/CIR/Analysis/CIRAnalysisKind.cpp b/clang/lib/CIR/Analysis/CIRAnalysisKind.cpp new file mode 100644 index 0000000000000..f27f8dbeb731d --- /dev/null +++ b/clang/lib/CIR/Analysis/CIRAnalysisKind.cpp @@ -0,0 +1,67 @@ +//===--- CIRAnalysisKind.cpp - CIR Analysis Pass Kinds -------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/CIR/Analysis/CIRAnalysisKind.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/raw_ostream.h" +#include + +namespace cir { + +CIRAnalysisKind parseCIRAnalysisKind(llvm::StringRef name) { + auto parseResult = llvm::StringSwitch(name) + .Case("fallthrough", CIRAnalysisKind::FallThrough) + .Case("fall-through", CIRAnalysisKind::FallThrough) + .Default(CIRAnalysisKind::Unrecognized); + + return parseResult; +} + + +CIRAnalysisSet parseCIRAnalysisList( + const std::vector &analysisList, + llvm::SmallVectorImpl *invalidNames) { + CIRAnalysisSet result; + + for (const std::string &item : analysisList) { + llvm::StringRef remaining = item; + CIRAnalysisKind parseKind = parseCIRAnalysisKind(remaining); + if (parseKind == CIRAnalysisKind::Unrecognized) { + llvm::errs() << "Unrecognized CIR analysis option: " << remaining << "\n"; + continue; + } + result.enable(parseKind); + } + + return result; +} + +void CIRAnalysisSet::print(llvm::raw_ostream &OS) const { + if (empty()) { + OS << "none"; + return; + } + + bool first = true; + auto printIfEnabled = [&](CIRAnalysisKind kind, llvm::StringRef name) { + if (has(kind)) { + if (!first) + OS << ", "; + OS << name; + first = false; + } + }; + + printIfEnabled(CIRAnalysisKind::FallThrough, "fallthrough"); + printIfEnabled(CIRAnalysisKind::UnreachableCode, "unreachable-code"); + printIfEnabled(CIRAnalysisKind::NullCheck, "null-check"); + printIfEnabled(CIRAnalysisKind::UninitializedVar, "uninitialized-var"); +} + +} // namespace cir diff --git a/clang/lib/CIR/Analysis/CMakeLists.txt b/clang/lib/CIR/Analysis/CMakeLists.txt new file mode 100644 index 0000000000000..acf49742d8e4c --- /dev/null +++ b/clang/lib/CIR/Analysis/CMakeLists.txt @@ -0,0 +1,19 @@ +add_clang_library(clangCIRSema + FallThroughWarning.cpp + CIRAnalysisKind.cpp + + DEPENDS + MLIRCIRPassIncGen + + LINK_LIBS PUBLIC + clangAST + clangBasic + + MLIRAnalysis + MLIRIR + MLIRPass + MLIRTransformUtils + + MLIRCIR + MLIRCIRInterfaces +) diff --git a/clang/lib/CIR/Analysis/FallThroughWarning.cpp b/clang/lib/CIR/Analysis/FallThroughWarning.cpp new file mode 100644 index 0000000000000..0b4ddfa5cd113 --- /dev/null +++ b/clang/lib/CIR/Analysis/FallThroughWarning.cpp @@ -0,0 +1,380 @@ +#include "clang/CIR/Analysis/FallThroughWarning.h" +#include "clang/AST/ASTContext.h" +#include "clang/Basic/DiagnosticSema.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/TargetInfo.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" +#include "clang/Sema/Sema.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/raw_ostream.h" + +using namespace mlir; +using namespace cir; +using namespace clang; + +namespace clang { + +//===----------------------------------------------------------------------===// +// Helper function to lookup a Decl by name from ASTContext +//===----------------------------------------------------------------------===// + +/// Lookup a declaration by name in the translation unit. +/// \param Context The ASTContext to search in +/// \param Name The name of the declaration to find +/// \return The found Decl, or nullptr if not found + +/// WARN: I have to say, we only use this because a lot of the time, attribute +/// that we might need are not port to CIR currently so this function is +/// basically a crutch for that +Decl *getDeclByName(ASTContext &context, StringRef name) { + // Get the identifier for the name + IdentifierInfo *ii = &context.Idents.get(name); + + // Create a DeclarationName from the identifier + DeclarationName dName(ii); + + // Lookup in the translation unit + TranslationUnitDecl *tu = context.getTranslationUnitDecl(); + DeclContext::lookup_result result = tu->lookup(dName); + + // Return the first match, or nullptr if not found + if (result.empty()) + return nullptr; + + return result.front(); +} + +CheckFallThroughDiagnostics +CheckFallThroughDiagnostics::makeForFunction(Sema &s, const Decl *func) { + CheckFallThroughDiagnostics d; + d.funcLoc = func->getLocation(); + d.diagFallThroughHasNoReturn = diag::warn_noreturn_has_return_expr; + d.diagFallThroughReturnsNonVoid = diag::warn_falloff_nonvoid; + + // Don't suggest that virtual functions be marked "noreturn", since they + // might be overridden by non-noreturn functions. + bool isVirtualMethod = false; + if (const CXXMethodDecl *method = dyn_cast(func)) + isVirtualMethod = method->isVirtual(); + + // Don't suggest that template instantiations be marked "noreturn" + bool isTemplateInstantiation = false; + if (const FunctionDecl *function = dyn_cast(func)) { + isTemplateInstantiation = function->isTemplateInstantiation(); + if (!s.getLangOpts().CPlusPlus && !s.getLangOpts().C99 && + function->isMain()) { + d.diagFallThroughReturnsNonVoid = diag::ext_main_no_return; + } + } + + if (!isVirtualMethod && !isTemplateInstantiation) + d.diagNeverFallThroughOrReturn = diag::warn_suggest_noreturn_function; + + d.funKind = diag::FalloffFunctionKind::Function; + return d; +} + +CheckFallThroughDiagnostics +CheckFallThroughDiagnostics::makeForCoroutine(const Decl *func) { + CheckFallThroughDiagnostics d; + d.funcLoc = func->getLocation(); + d.diagFallThroughReturnsNonVoid = diag::warn_falloff_nonvoid; + d.funKind = diag::FalloffFunctionKind::Coroutine; + return d; +} + +CheckFallThroughDiagnostics CheckFallThroughDiagnostics::makeForBlock() { + CheckFallThroughDiagnostics D; + D.diagFallThroughHasNoReturn = diag::err_noreturn_has_return_expr; + D.diagFallThroughReturnsNonVoid = diag::err_falloff_nonvoid; + D.funKind = diag::FalloffFunctionKind::Block; + return D; +} + +CheckFallThroughDiagnostics CheckFallThroughDiagnostics::makeForLambda() { + CheckFallThroughDiagnostics d; + d.diagFallThroughHasNoReturn = diag::err_noreturn_has_return_expr; + d.diagFallThroughReturnsNonVoid = diag::warn_falloff_nonvoid; + d.funKind = diag::FalloffFunctionKind::Lambda; + return d; +} + +//===----------------------------------------------------------------------===// +// Check for phony return values (returning uninitialized __retval) +//===----------------------------------------------------------------------===// + +/// Check if a return operation returns a phony value. +/// A phony return is when a function returns a value loaded from an +/// uninitialized __retval alloca, which indicates the function doesn't +/// actually return a meaningful value. +/// +/// Example of phony return: +/// \code +/// cir.func @test1() -> !s32i { +/// %0 = cir.alloca !s32i, !cir.ptr, ["__retval"] +/// %1 = cir.load %0 : !cir.ptr, !s32i +/// cir.return %1 : !s32i +/// } +/// \endcode +/// +/// \param returnOp The return operation to check +/// \return true if this is a phony return, false otherwise +bool isPhonyReturn(cir::ReturnOp returnOp) { + if (!returnOp) + return false; + + // Get the returned value - return operations use $input as the operand + if (!returnOp.hasOperand()) + return false; + + auto returnValue = returnOp.getInput()[0]; + + // Check if the return value comes from a load operation + auto loadOp = returnValue.getDefiningOp(); + if (!loadOp) + return false; + + // Check if the load is from an alloca + auto allocaOp = loadOp.getAddr().getDefiningOp(); + if (!allocaOp) + return false; + + // Check if the alloca is named "__retval" + auto name = allocaOp.getName(); + if (name != "__retval") + return false; + + // Check if the alloca has any stores to it (if not, it's uninitialized) + // We need to search for store operations that write to this alloca + mlir::Value allocaResult = allocaOp.getResult(); + + for (auto *user : allocaResult.getUsers()) { + if (auto storeOp = dyn_cast(user)) { + // If there's a store to this alloca, it's not phony + // (assuming the store happens before the load in control flow) + return false; + } + } + + // No stores found to __retval alloca - this is a phony return + return true; +} + +//===----------------------------------------------------------------------===// +// Check for missing return value. +//===----------------------------------------------------------------------===// + +bool CheckFallThroughDiagnostics::checkDiagnostics(DiagnosticsEngine &d, + bool returnsVoid, + bool hasNoReturn) const { + if (funKind == diag::FalloffFunctionKind::Function) { + return (returnsVoid || d.isIgnored(diag::warn_falloff_nonvoid, funcLoc)) && + (d.isIgnored(diag::warn_noreturn_has_return_expr, funcLoc) || + !hasNoReturn) && + (!returnsVoid || + d.isIgnored(diag::warn_suggest_noreturn_block, funcLoc)); + } + if (funKind == diag::FalloffFunctionKind::Coroutine) { + return (returnsVoid || d.isIgnored(diag::warn_falloff_nonvoid, funcLoc)) && + (!hasNoReturn); + } + // For blocks / lambdas. + return returnsVoid && !hasNoReturn; +} + +// TODO: Add a class for fall through config later + +void FallThroughWarningPass::checkFallThroughForFuncBody( + Sema &s, cir::FuncOp cfg, QualType blockType, + const CheckFallThroughDiagnostics &cd) { + + auto *d = getDeclByName(s.getASTContext(), cfg.getName()); + auto *body = d->getBody(); + assert(d && "we need non null decl"); + + bool returnsVoid = false; + bool hasNoReturn = false; + + SourceLocation lBrace = body->getBeginLoc(), rBrace = body->getEndLoc(); + // Supposedly all function in cir is FuncOp + // 1. If normal function (FunctionDecl), check if it's coroutine. + // 1a. if coroutine -> check the fallthrough handler (idk what this means, + // TODO for now) + if (const auto *fd = dyn_cast(d)) { + if (const auto *cBody = dyn_cast(d->getBody())) + returnsVoid = cBody->getFallthroughHandler() != nullptr; + else + returnsVoid = fd->getReturnType()->isVoidType(); + hasNoReturn = fd->isNoReturn() || fd->hasAttr(); + } else if (const auto *md = dyn_cast(d)) { + returnsVoid = md->getReturnType()->isVoidType(); + hasNoReturn = md->hasAttr(); + } else if (isa(d)) { + if (const FunctionType *ft = + blockType->getPointeeType()->getAs()) { + if (ft->getReturnType()->isVoidType()) + returnsVoid = true; + if (ft->getNoReturnAttr()) + hasNoReturn = true; + } + } + + DiagnosticsEngine &diags = s.getDiagnostics(); + + // Short circuit for compilation speed. + if (cd.checkDiagnostics(diags, returnsVoid, hasNoReturn)) + return; + + // cpu_dispatch functions permit empty function bodies for ICC compatibility. + // TODO: Do we have isCPUDispatchMultiVersion? + + switch (ControlFlowKind fallThroughType = checkFallThrough(cfg)) { + case UnknownFallThrough: + break; + case MaybeFallThrough: + [[fallthrough]]; + case AlwaysFallThrough: + if (hasNoReturn && cd.diagFallThroughHasNoReturn) { + + } else if (!returnsVoid && cd.diagFallThroughReturnsNonVoid) { + // If the final statement is a call to an always-throwing function, + // don't warn about the fall-through. + if (d->getAsFunction()) { + if (const auto *cs = dyn_cast(body); + cs && !cs->body_empty()) { + const Stmt *lastStmt = cs->body_back(); + // Unwrap ExprWithCleanups if necessary. + if (const auto *ewc = dyn_cast(lastStmt)) { + lastStmt = ewc->getSubExpr(); + } + if (const auto *ce = dyn_cast(lastStmt)) { + if (const FunctionDecl *callee = ce->getDirectCallee(); + callee && callee->hasAttr()) { + return; // Don't warn about fall-through. + } + } + // Direct throw. + if (isa(lastStmt)) { + return; // Don't warn about fall-through. + } + } + } + bool notInAllControlPaths = fallThroughType == MaybeFallThrough; + s.Diag(rBrace, cd.diagFallThroughReturnsNonVoid) + << cd.funKind << notInAllControlPaths; + } + break; + case NeverFallThroughOrReturn: + if (returnsVoid && !hasNoReturn && cd.diagNeverFallThroughOrReturn) { + } + break; + + case NeverFallThrough: { + } break; + } +} + +mlir::DenseSet +FallThroughWarningPass::getLiveSet(cir::FuncOp cfg) { + mlir::DenseSet liveSet; + if (cfg.getBody().empty()) + return liveSet; + + auto &first = cfg.getBody().getBlocks().front(); + + for (auto &block : cfg.getBody()) { + if (&first == &block || first.isReachable(&block)) + liveSet.insert(&block); + } + return liveSet; +} + +ControlFlowKind FallThroughWarningPass::checkFallThrough(cir::FuncOp cfg) { + + assert(cfg && "there can't be a null func op"); + + // TODO: Is no CFG akin to a declaration? + if (cfg.isDeclaration()) { + return UnknownFallThrough; + } + + mlir::DenseSet liveSet = this->getLiveSet(cfg); + + unsigned count = liveSet.size(); + + bool hasLiveReturn = false; + bool hasFakeEdge = false; + bool hasPlainEdge = false; + bool hasAbnormalEdge = false; + + auto &exitBlock = cfg.getBody().back(); + // INFO: in OG clang CFG, they have an empty exit block, so when they query + // pred of exit OG, they get all exit blocks + // + // I guess in CIR, we can pretend exit blocks are all blocks that have no + // successor? + for (mlir::Block &pred : cfg.getBody().getBlocks()) { + if (!liveSet.contains(&pred)) + continue; + + // We consider no predecessors as 'exit blocks' + if (!pred.hasNoSuccessors()) + continue; + + if (!pred.mightHaveTerminator()) + continue; + + mlir::Operation *term = pred.getTerminator(); + + // TODO: hasNoReturnElement() in OG here, not sure how to work it in here + // yet + + // INFO: In OG, we'll be looking for destructor since it can appear past + // return but i guess not in CIR? In this case we'll only be examining the + // terminator + + if (isa(term)) { + hasAbnormalEdge = true; + continue; + } + + // INFO: OG clang has this equals true whenever ri == re, which means this + // is true only when a block only has the terminator, or its size is 1. + hasPlainEdge = std::distance(pred.begin(), pred.end()) == 1; + + if (auto returnOp = dyn_cast(term)) { + if (!isPhonyReturn(returnOp)) { + hasLiveReturn = true; + continue; + } + } + if (isa(term)) { + hasLiveReturn = true; + continue; + } + + // TODO: Maybe one day throw will be terminator? + // + // TODO: We need to add a microsoft inline assembly enum + + // TODO: We don't concer with try op either since it's not terminator + + hasPlainEdge = true; + } + + if (!hasPlainEdge) { + if (hasLiveReturn) + return NeverFallThrough; + return NeverFallThroughOrReturn; + } + if (hasAbnormalEdge || hasFakeEdge || hasLiveReturn) + return MaybeFallThrough; + // This says AlwaysFallThrough for calls to functions that are not marked + // noreturn, that don't return. If people would like this warning to be more + // accurate, such functions should be marked as noreturn. + // + // llvm_unreachable(""); + return AlwaysFallThrough; +} +} // namespace clang diff --git a/clang/lib/CIR/CMakeLists.txt b/clang/lib/CIR/CMakeLists.txt index 7bdf3fcc59035..c698267b04ef5 100644 --- a/clang/lib/CIR/CMakeLists.txt +++ b/clang/lib/CIR/CMakeLists.txt @@ -17,3 +17,4 @@ add_subdirectory(CodeGen) add_subdirectory(FrontendAction) add_subdirectory(Interfaces) add_subdirectory(Lowering) +add_subdirectory(Analysis) diff --git a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp index 67bb5657d4001..627265cec3d3b 100644 --- a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp +++ b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp @@ -10,12 +10,20 @@ #include "mlir/IR/MLIRContext.h" #include "mlir/IR/OwningOpRef.h" #include "clang/Basic/DiagnosticFrontend.h" +#include "clang/Basic/DiagnosticSema.h" #include "clang/CIR/CIRGenerator.h" #include "clang/CIR/CIRToCIRPasses.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" #include "clang/CIR/LowerToLLVM.h" +#include "clang/CIR/Analysis/CIRAnalysisKind.h" +#include "clang/CIR/Analysis/FallThroughWarning.h" #include "clang/CodeGen/BackendUtil.h" #include "clang/Frontend/CompilerInstance.h" +#include "clang/Sema/AnalysisBasedWarnings.h" +#include "clang/Sema/Sema.h" #include "llvm/IR/Module.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/raw_ostream.h" using namespace cir; using namespace clang; @@ -108,6 +116,42 @@ class CIRGenConsumer : public clang::ASTConsumer { mlir::ModuleOp MlirModule = Gen->getModule(); mlir::MLIRContext &MlirCtx = Gen->getMLIRContext(); + // Run CIR analysis passes if requested + if (!FEOptions.ClangIRAnalysisList.empty()) { + CIRAnalysisSet AnalysisSet = + parseCIRAnalysisList(FEOptions.ClangIRAnalysisList); + + if (AnalysisSet.has(CIRAnalysisKind::FallThrough)) { + if (CI.hasSema()) { + Sema &S = CI.getSema(); + FallThroughWarningPass FallThroughPass; + + // Iterate over all functions in the CIR module + MlirModule.walk([&](cir::FuncOp FuncOp) { + // TODO: Get the proper QualType for the function + // For now, use an invalid QualType as placeholder + + QualType FuncType; + + // Set up diagnostics configuration + // INFO: This is not full + Decl *D = getDeclByName(S.getASTContext(), FuncOp.getName()); + const CheckFallThroughDiagnostics &CD = + (isa(D) ? CheckFallThroughDiagnostics::makeForBlock() + : (isa(D) && + cast(D)->getOverloadedOperator() == + OO_Call && + cast(D)->getParent()->isLambda()) + ? CheckFallThroughDiagnostics::makeForLambda() + : CheckFallThroughDiagnostics::makeForFunction(S, D)); + // Run fall-through analysis on this function + FallThroughPass.checkFallThroughForFuncBody(S, FuncOp, FuncType, + CD); + }); + } + } + } + if (!FEOptions.ClangIRDisablePasses) { // Setup and run CIR pipeline. if (runCIRToCIRPasses(MlirModule, MlirCtx, C, diff --git a/clang/lib/CIR/FrontendAction/CMakeLists.txt b/clang/lib/CIR/FrontendAction/CMakeLists.txt index 50d6ea7108ce1..f43ef2d8cbbf5 100644 --- a/clang/lib/CIR/FrontendAction/CMakeLists.txt +++ b/clang/lib/CIR/FrontendAction/CMakeLists.txt @@ -18,6 +18,7 @@ add_clang_library(clangCIRFrontendAction clangBasic clangFrontend clangCIR + clangCIRSema clangCIRLoweringCommon clangCIRLoweringDirectToLLVM clangCodeGen diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index 41a98323450e4..a2f7f61b57d95 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -53,6 +53,7 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Debug.h" +#include "llvm/Support/ErrorHandling.h" #include #include #include diff --git a/clang/test/CIR/Analysis/fallthrough_1.c b/clang/test/CIR/Analysis/fallthrough_1.c new file mode 100644 index 0000000000000..267745afeba5d --- /dev/null +++ b/clang/test/CIR/Analysis/fallthrough_1.c @@ -0,0 +1,33 @@ +// REQUIRES: false + +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu %s -fclangir-analysis="fallthrough" -w +// INFO: These test cases are derived from clang/test/Sema/return.c +int unknown(void); + +int test7(void) { + unknown(); +} // expected-warning {{non-void function does not return a value}} + +int test8(void) { + (void)(1 + unknown()); +} // expected-warning {{non-void function does not return a value}} + + + +int test14(void) { + (void)(1 || unknown()); +} // expected-warning {{non-void function does not return a value}} + +int test15(void) { + (void)(0 || unknown()); +} // expected-warning {{non-void function does not return a value}} + +int test16(void) { + (void)(0 && unknown()); +} // expected-warning {{non-void function does not return a value}} + +int test17(void) { + (void)(1 && unknown()); +} // expected-warning {{non-void function does not return a value}} + + diff --git a/clang/test/CIR/Analysis/fallthrough_2.c b/clang/test/CIR/Analysis/fallthrough_2.c new file mode 100644 index 0000000000000..22c399730a4ff --- /dev/null +++ b/clang/test/CIR/Analysis/fallthrough_2.c @@ -0,0 +1,37 @@ +// REQUIRES: false + +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu %s -fclangir-analysis="fallthrough" -w +// INFO: These test cases are derived from clang/test/Sema/return.c + +int test1(void) { +} // expected-warning {{non-void function does not return a value}} + +int test3(void) { + goto a; + a: ; +} // expected-warning {{non-void function does not return a value}} + +int test20(void) { + int i; + if (i) + return 0; + else if (0) + return 2; +} // expected-warning {{non-void function does not return a value in all control paths}} + +int test22(void) { + int i; + switch (i) default: ; +} // expected-warning {{non-void function does not return a value}} + +int test23(void) { + int i; + switch (i) { + case 0: + return 0; + case 2: + return 2; + } +} // expected-warning {{non-void function does not return a value in all control paths}} + +